Skip to content

v0.1.23: collection controls, by resolved path set

Choose a tag to compare

@taipei49314 taipei49314 released this 11 Aug 17:35
· 57 commits to main since this release

Rows 81 and 83. Three gaps in collection-control detection — and fixing one of them naively would have created a false positive worse than the bypass.

Slice targets

collect_ignore[:] = ["tests/test_billing.py"] is an ast.Subscript, not an ast.Name, so the entire form was invisible. Accepted now, for Assign and AugAssign alike.

except handlers were never walked at all

The recursion iterated ("body", "orelse", "finalbody") and required ast.stmt members. An ExceptHandler is not one, so the whole list was skipped in silence.

And here is the trap. The overwhelmingly common thing inside such a handler is:

try:
    import redis
except ImportError:
    collect_ignore.append("tests/test_redis.py")

Recording that control without a condition would have turned every optional-dependency gate in the ecosystem into an unconditional kill — the exact false positive an adversarial audit already caught this build committing once, on a PR that added the tests it was guarding.

So the handler records the condition it actually expresses: find_spec("redis") is None — the spelling the compat-gate logic already recognises, and cites in its own comment. A bare except, a different exception type, or a try body that is not a plain import records text that does not parse as a condition and therefore earns nothing. That is the fail-toward-flagging side of the same choice: an unconditional control hidden in a try still fires.

Appending to a control that already existed

Markers deduplicate by name, and the name never moves — so collect_ignore.append(...) beside an existing collect_ignore = [...] removed a whole test file with no event of any kind. The resolved set of ignored paths is compared now.

Both extra conditions on that comparison were learned by breaking existing fixtures rather than by foresight. It fires only when the control is unguarded (a growing compat gate is still a gate) and only when the marker is not itself newly added (that event already exists; reporting it twice is noise). The first cut had neither and turned three green fixtures red.

That is the second time this week a positive fixture written for a long-closed bug caught a new rule overreaching — and the argument for never deleting them.

Cost

36 → 36 blocks, no verdict moved in either direction, opaque exemption unchanged at 24/1800, zero engine errors. Recall unchanged, enforced by the replay gate.

342 tests, dogfood clean, CI green on all 12 jobs. D-041.

pipx install git+https://github.com/taipei49314/greenwash@v0.1.23