Skip to content

fix: keep obfuscated copy row-aligned with the source - #9617

Merged
pjwerneck merged 4 commits into
OpenMined:devfrom
atosh502:a3_obfuscator_desync
Nov 13, 2026
Merged

pjwerneck merged 4 commits into
OpenMined:devfrom
atosh502:a3_obfuscator_desync

Conversation

@atosh502

@atosh502 atosh502 commented Nov 6, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes finding A3: the obfuscated copy could end up with a different number of rows than the source. The hide step uses row numbers from the source, so the wrong rows got hidden. Hidden code was printed in the clear and public lines were blanked or rewritten.

Changes

  • Add astutil.source_rows(), which splits on \n only, the same way tokenize counts rows. str.splitlines also splits on form feeds and other separators. The obfuscator and the marker parser now use source_rows().
  • A blanked string that spans several rows keeps all of its rows (a triple-quoted ■ with the same number of newlines). Multi-line f-string text also keeps its newlines.
  • run() now checks that the obfuscated copy has the same number of rows as the source. If not, it writes no copy, issues no certificate, and reports a new row-count-mismatch violation (raised as PolicyViolation in strict mode).
  • Added row-count-mismatch to docs/blacklist.md and to the ViolationCode list.

Testing

  • New tests check that the row count doesn't change in two cases: a form feed before a private block, and a multi-line docstring in an obfuscate block. Each case runs with and without a hide block after it. The form-feed tests fail on the old code.
  • just test-unit-restrict: 237 passed.

Asana task

atosh502 and others added 2 commits November 6, 2026 12:22
…ned copy

The obfuscator computed its edits and the certificate ranges from tokenize
rows, then applied them to rows from str.splitlines. splitlines also breaks
on a form feed and seven other characters that tokenize treats as ordinary
text. One form feed before a private block moved every edit up a row: the
private class was printed in the clear and a public comment was rewritten
(finding A3, form feed).

A new astutil.source_rows() splits on \n only, like tokenize. The
obfuscator's edit and hide steps and the marker parser now use it.

run() now compares the row counts of the obfuscated copy and the source.
On a mismatch it writes no copy and issues no certificate. It reports a
new row-count-mismatch violation (raised as PolicyViolation under strict).
This also refuses the docstring-shift form of A3, where a multi-line string
in an obfuscate block collapses to one row, until the obfuscator keeps
those rows.

Tests: a form feed inside a public comment before an obfuscate block, and
before an obfuscate-then-hide block, keeps the row count. Both fail on the
old code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The obfuscator replaced a string token with one "■", however many rows it
spanned. A five-line docstring in an obfuscate block collapsed to one row.
The hide step then used source row numbers on the shorter text: it blanked
a public line and printed the hidden lines in the clear (finding A3,
docstring shift). Since the previous commit, run() refused such a file.

A string that spans rows is now replaced by a triple-quoted "■" with the
same number of newlines. Multi-line f-string text keeps its newlines too.
The copy keeps the source's row count, so run() accepts the file again and
the hide step blanks the right rows.

Tests: a three-line docstring in an obfuscate block, with and without a
hide block after it, keeps the row count.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@atosh502 atosh502 changed the title A3 obfuscator desync fix: keep obfuscated copy row-aligned with the source Nov 6, 2026
@atosh502 atosh502 changed the title fix: keep obfuscated copy row-aligned with the source fix: keep obfuscated copy row-aligned with the source Nov 6, 2026
@github-actions github-actions Bot added the bugfix label Nov 6, 2026
A different row count in the display copy is an error in the obfuscator.
It is not a problem in the private code. Therefore run() now raises
ObfuscationError for it, also with strict=False. The row-count-mismatch
violation code no longer exists. The check is in
_require_same_row_count().

A new test makes the copy lose a row and expects the error. The
row-count tests of the obfuscator also check that hidden rows show the
hide marker and that public rows do not change.
@pjwerneck pjwerneck changed the title fix: keep obfuscated copy row-aligned with the source fix: keep obfuscated copy row-aligned with the source. Nov 13, 2026
@pjwerneck pjwerneck changed the title fix: keep obfuscated copy row-aligned with the source. fix: keep obfuscated copy row-aligned with the source Nov 13, 2026
@pjwerneck
pjwerneck merged commit 425d615 into OpenMined:dev Nov 13, 2026
21 of 22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants