Skip to content

Resolve concatenation in the html-safety check, and empty the baseline - #54

Merged
abrignoni merged 1 commit into
mainfrom
fix/checker-concat-resolution
Aug 7, 2026
Merged

Resolve concatenation in the html-safety check, and empty the baseline#54
abrignoni merged 1 commit into
mainfrom
fix/checker-concat-resolution

Conversation

@abrignoni

Copy link
Copy Markdown
Owner

Port of ALEAPP #1045. Every baselined entry in this core turned out to be a false positive rather than an unescaped sink, so the baseline goes to zero.

The checker gap

It did not resolve + chains at all, so an accumulator read as unknown even when every write to it was escaped:

agg = agg + f'<td>{esc(value)}</td>'   # escaped, but reported as unknown
agg = agg + '</table>'                  # concat with a tool-owned literal

It now resolves a concatenation when both sides resolve, with the self-reference terminating through the existing seen set. Accumulate-then-emit is how most of these builders work, so this was the single largest source of noise.

This does not weaken the check. The regression probe includes an unescaped accumulator concat, and it is still caught.

media_to_html

source was assigned four times and then emitted. The final write was safe_local_path() and nothing read it earlier, so the function was correct — but a name counts as safe only when every assignment to it is, because this check does not order assignments.

The escaped values now bind to their own names, safe_source and safe_filename. That makes the safety local and obvious to a reader as much as to the checker, which is the better shape regardless of tooling.

Validation

  • py_compile clean; lint_changed reports no new warnings
  • unit tests OK; PluginLoader loads every plugin
  • the check exits 0 with 0 baselined, and still exits 1 on injected probes — including an unescaped accumulator concat

🤖 Generated with Claude Code

Port of ALEAPP #1045. Every baselined entry in this core turned out to be a
false positive rather than an unescaped sink, so the baseline goes to zero.

The check did not resolve `+` chains, so an accumulator read as unknown even
when every write to it was escaped. It now resolves a concatenation when both
sides resolve, with the self-reference terminating through the existing `seen`
set. Accumulate-then-emit is how most of these builders work, so this was the
single largest source of noise.

media_to_html assigned `source` four times and then emitted it. The final write
was safe_local_path() and nothing read it earlier, so the function was correct,
but a name counts as safe only when every assignment to it is -- this check does
not order assignments. The escaped values now bind to their own names,
safe_source and safe_filename, which makes the safety local and obvious to a
reader as much as to the checker.
Also corrects the docstring: a dynamic destination may come from
safe_local_path() as well as safe_local_link().

Validated: py_compile clean; lint_changed reports no new warnings; unit tests
OK; PluginLoader loads every plugin; the check exits 0 with 0 baselined, and
still exits 1 on injected probes -- including an unescaped accumulator concat,
which confirms the relaxation above did not blind it to the real thing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@abrignoni
abrignoni merged commit ea2741b into main Aug 7, 2026
4 checks passed
@abrignoni
abrignoni deleted the fix/checker-concat-resolution branch August 7, 2026 02:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant