fleetd #639: redact block scalars by line number
CI / shell-tests (pull_request) Failing after 6s
CI / build (pull_request) Failing after 1m53s
CI / contract (pull_request) Successful in 1m21s

This commit is contained in:
Dai Ha
2026-10-03 15:46:20 +02:00
parent a42b12440c
commit 3b69e0103b
+79 -22
View File
@@ -154,18 +154,10 @@ done
# story: a YAML block scalar (`|`, `|-`, `>`, `>-`, ...) puts the VALUE on the lines that follow
# the key, each indented deeper than it. The key-name match above only ever sees the key line
# itself, so those continuation lines used to flow straight through unredacted while the key line
# right above them printed a reassuring "<redacted>" — an incomplete redactor that looks complete
# is worse than one that visibly does nothing, because it stops a reviewer from looking further.
# The fix is structural, not another name to match: once a key line is masked, every following
# line indented STRICTLY DEEPER than that key is masked too, by indentation alone, until the
# indentation returns to the key's own level or shallower. This needs no knowledge of the key's
# name, so it covers a block scalar under any masked key — but ONLY while that key's own line is
# itself inside the hunk being printed. `diff -u` prints just three lines of context, so a block
# scalar's body often reaches this function with its key line left out; there is then nothing to
# anchor to, `masked` is never set, and the body prints in full. A blank line inside a block
# scalar loses the anchor the same way, because a blank diff line measures as indent 0. Both are
# measured and filed as fleetd #639 — do not read this paragraph as a guarantee that a masked
# key's value can never be printed.
# right above them printed a reassuring "<redacted>". The redactor maps masked continuation lines
# from each complete file before it reads the diff. It then masks a printed line when that file
# line is inside a masked key's value. This covers block-scalar bodies even when the key line is
# outside the printed hunk, and it keeps blank lines inside the value masked.
#
# `redact` is always fed `diff -u` output, and every line of a unified diff starts with exactly
# one of ' ', '+', '-' (the three body markers; '@'/'-'/'+' for the three header-line kinds too).
@@ -174,37 +166,102 @@ done
# column shallower than it really is, and either wrongly escapes a continuation mask or wrongly
# ends one early. Tabs are out of scope: YAML forbids them for indentation, and this is a bounded
# fix, not a YAML parser.
map_masked_lines() {
local file="$1" side="$2" line content indent lead key line_number=0
local masked=0 masked_indent=0
case "$side" in
old) OLD_MASKED_LINES=() ;;
new) NEW_MASKED_LINES=() ;;
*) die "internal error: unknown redaction map side $side" ;;
esac
while IFS= read -r line || [ -n "$line" ]; do
line_number=$((line_number + 1))
content="$line"
indent=0
while [ "${content:$indent:1}" = " " ]; do indent=$((indent + 1)); done
if [ "$masked" = 1 ]; then
if [ -z "${content// /}" ] || [ "$indent" -gt "$masked_indent" ]; then
case "$side" in
old) OLD_MASKED_LINES[$line_number]=1 ;;
new) NEW_MASKED_LINES[$line_number]=1 ;;
esac
continue
fi
masked=0
fi
if [[ "$content" =~ ^([[:space:]]*)([A-Za-z0-9_.-]+:) ]]; then
lead="${BASH_REMATCH[1]}"
key="${BASH_REMATCH[2]}"
if [[ "$key" =~ (TOKEN|SECRET|PASSWORD|PASSWD|PASSPHRASE|CREDENTIAL|URI|_KEY) ]]; then
masked=1
masked_indent="$indent"
fi
fi
done < "$file"
}
redact() {
local line prefix content indent lead key
local masked=0 masked_indent=0 saved_nocasematch=0
local old_file="$1" new_file="$2"
local line prefix content indent lead key old_line=0 new_line=0 in_hunk=0
local old_masked new_masked saved_nocasematch=0
shopt -q nocasematch && saved_nocasematch=1
shopt -s nocasematch
sed -E 's#://[^@]*@#://<redacted>@#g' | while IFS= read -r line || [ -n "$line" ]; do
map_masked_lines "$old_file" old
map_masked_lines "$new_file" new
while IFS= read -r line || [ -n "$line" ]; do
if [[ "$line" =~ ^@@\ -([0-9]+)(,([0-9]+))?\ \+([0-9]+)(,([0-9]+))?\ @@ ]]; then
old_line="${BASH_REMATCH[1]}"
new_line="${BASH_REMATCH[4]}"
in_hunk=1
printf '%s\n' "$line"
continue
fi
case "$line" in
[\ +-]*) prefix="${line:0:1}"; content="${line:1}" ;;
*) prefix=""; content="$line" ;;
esac
old_masked=0
new_masked=0
if [ "$in_hunk" = 1 ]; then
case "$prefix" in
' ')
[ "${OLD_MASKED_LINES[$old_line]:-}" = 1 ] && old_masked=1
[ "${NEW_MASKED_LINES[$new_line]:-}" = 1 ] && new_masked=1
old_line=$((old_line + 1)); new_line=$((new_line + 1)) ;;
-)
[ "${OLD_MASKED_LINES[$old_line]:-}" = 1 ] && old_masked=1
old_line=$((old_line + 1)) ;;
+)
[ "${NEW_MASKED_LINES[$new_line]:-}" = 1 ] && new_masked=1
new_line=$((new_line + 1)) ;;
esac
fi
indent=0
while [ "${content:$indent:1}" = " " ]; do indent=$((indent + 1)); done
if [ "$masked" = 1 ] && [ "$indent" -gt "$masked_indent" ]; then
if [ "$old_masked" = 1 ] || [ "$new_masked" = 1 ]; then
printf '%s%*s<redacted>\n' "$prefix" "$indent" ""
continue
fi
masked=0
if [[ "$content" =~ ^([[:space:]]*)([A-Za-z0-9_.-]+:) ]]; then
lead="${BASH_REMATCH[1]}"
key="${BASH_REMATCH[2]}"
if [[ "$key" =~ (TOKEN|SECRET|PASSWORD|PASSWD|PASSPHRASE|CREDENTIAL|URI|_KEY) ]]; then
printf '%s%s%s <redacted>\n' "$prefix" "$lead" "$key"
masked=1
masked_indent="$indent"
continue
fi
fi
printf '%s\n' "$line"
printf '%s\n' "$line" | sed -E 's#://[^@]*@#://<redacted>@#g'
done
[ "$saved_nocasematch" = 1 ] || shopt -u nocasematch
}
@@ -627,7 +684,7 @@ run_edit() {
apply_mode "$cand" "$orig_mode"
say "change (redacted)"
diff -u "$backup" "$cand" | redact || true
diff -u "$backup" "$cand" | redact "$backup" "$cand" || true
say "install"
install_candidate "$cand" "$CONFIG" \
@@ -656,7 +713,7 @@ dry_run_diff() {
die "candidate does not parse as valid YAML — this was a --dry-run, nothing would have been installed either"
fi
say "dry run — diff (redacted), nothing installed"
diff -u "$CONFIG" "$cand" | redact || true
diff -u "$CONFIG" "$cand" | redact "$CONFIG" "$cand" || true
rm -f "$cand"; CAND=""
return 0
}