Skip to content

fix(qa): sanitize evidence_field diagnostics against terminal escape injection - #379

Merged
barbatdev merged 1 commit into
mainfrom
fix/378-escape-evidence-field
Oct 23, 2026
Merged

barbatdev merged 1 commit into
mainfrom
fix/378-escape-evidence-field

Conversation

@barbatdev

Copy link
Copy Markdown
Contributor

Fixes #378

Problem

The evidence_field= diagnostic shipped in #376 prints the failing field name unescaped. For unknown_field and duplicate_field rejections the field value comes from a caller-supplied JSON key name in the --evidence payload, so a key containing terminal escape or control characters could print raw bytes to the operator's stderr.

Change

runQA now renders the diagnostic field through sanitizeDiagnosticField, which keeps only printable ASCII word characters ([A-Za-z0-9_.-]) and collapses every other rune to ?. Escape and control sequences can no longer reach the terminal. evidence_detail is unaffected because it already quotes values with %q.

Test

Added a TestQARunEvidenceDiagnosticAttribution case with an evidence record whose unknown field key contains ESC[31m sequences. RED before the fix (raw escape bytes in stderr), GREEN after (evidence_field=evil??31m??0m). Focused ./internal/cli and ./internal/qapi suites plus go vet pass.

Refs #376, #305.

…injection

The evidence_field diagnostic value can originate from caller-supplied JSON
key names (unknown_field, duplicate_field rejections). Print it through a
safe-charset sanitizer so control and terminal escape sequences cannot reach
the operator's stderr (Refs #378, refs #376, #305).
@barbatdev barbatdev added the type:bug Bug fix label Oct 23, 2026
@barbatdev
barbatdev merged commit 3d027e2 into main Oct 23, 2026
3 checks passed
@barbatdev

Copy link
Copy Markdown
Contributor Author

Post-merge QA (glm)

Lens: requirements + adversarial. Sanitizes the evidence_field diagnostic against terminal escape injection (#378). sanitizeDiagnosticField keeps only [A-Za-z0-9_.-] and collapses every other rune to '?', applied at the only unescaped sink in runQA (doctor.go evidence_field line). Checked the other diagnostic channels: evidence_reason is a fixed token set from code, evidence_detail values are %q-quoted or numeric, and the Error() string is not printed raw in this path, so no bypass remains. Test asserts ESC[31m renders as evil??31m??0m and no raw bytes reach stderr. Multibyte runes collapsing to a single '?' only shortens output; fail-closed semantics and exit codes unchanged.

No findings.

Verdict: clean

@barbatdev

Copy link
Copy Markdown
Contributor Author

Post-merge QA (deepseek)

Lens: evidence + test design. The merge does what #378 asked for the evidence_field line: sanitizeDiagnosticField keeps [A-Za-z0-9_.-] and collapses the rest, the regression case reproduces the escape payload and pins the sanitized rendering, and the focused suites plus go vet are stated as run. It does not close the class the issue names in its second acceptance criterion ('The same rule applies to any future diagnostic fields derived from caller-supplied input'): the same code path still prints evidence_detail with %s, and for expected_array rejections that Detail is built from caller-supplied JSON object keys joined with %s, so terminal escape bytes still reach stderr through the sibling diagnostic line. The PR body states that evidence_detail is unaffected 'because it already quotes values with %q', which is not true for the object-keys construction in validateEvidence, and the new test asserts only the field line, so nothing in the suite would catch the surviving path. Test design gap: the new case checks that the sanitized field line is present but never asserts that no raw ESC byte appears anywhere in the stderr stream, which is the property the issue actually asks for.

Blocking (1)

Question (1)

  • The regression test does not assert the absence of raw escape bytes (internal/cli/qa_test.go): The new case 'unknown field with terminal escape bytes' asserts only that stderr contains evidence_field=evil??31m??0m. It never asserts that no 0x1b byte appears in the captured stderr, so any other diagnostic line that still echoes caller input (evidence_detail today, or a future field) keeps the test green. The issue asks for 'one test proving an evidence file with a control-character key name produces a sanitized stderr line and no raw escape bytes'.

Verdict: blocking

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type:bug Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

evidence_field diagnostic prints attacker-controlled JSON key names unescaped (terminal escape hygiene)

1 participant