Skip to content

0.2.0 release — taint soundness + hardening scrub, version bump + changelog#12

Merged
tachyon-beep merged 3 commits into
mainfrom
scrub/soundness-and-hardening
May 31, 2026
Merged

0.2.0 release — taint soundness + hardening scrub, version bump + changelog#12
tachyon-beep merged 3 commits into
mainfrom
scrub/soundness-and-hardening

Conversation

@tachyon-beep

@tachyon-beep tachyon-beep commented May 31, 2026

Copy link
Copy Markdown
Collaborator

This is the 0.2.0 release PR. Merging it to main and pushing the
v0.2.0 tag triggers release.yml → build → PyPI Trusted Publishing.

Release prep (0adeda7): _version.py 0.1.0 → 0.2.0; CHANGELOG.md
[0.2.0] finalized (MCP server SP8, Clarion store SP9, docs site SP7, the
soundness + hardening fixes here, and the loom-extra removal); live-docs
version strings bumped. Build verified — 923 tests pass, mkdocs --strict
clean, sdist+wheel build as wardline-0.2.0, wheel metadata valid.

Publish steps (after merge):

git checkout main && git pull
git tag -a v0.2.0 -m "wardline 0.2.0"
git push origin v0.2.0    # → CI builds + publishes to PyPI

Summary

A full-codebase scrub of the one-shot SP0–SP9 system, driven by a 7-agent review panel → 9 findings, all fixed with test-first coverage. Each fix was implemented by a fresh subagent, independently reviewed, and empirically re-verified against a known-vulnerable taint battery. A later pass (commit 58cca6d) closes two of the three deferred residuals.

  • Suite: 843 → 923 tests · ruff clean · mypy --strict clean · self-hosting green
  • Acceptance oracle: a 30-case taint battery (FN-fires + FP-clean, both directions) — kept out-of-tree

P0 — L2 taint soundness hole (silent false negatives)

_resolve_expr fell through to return function_taint for unmodelled AST shapes. In a @trusted producer that resets untrusted data to the trusted tier (fail-open) and emits a clean report the user wrongly trusts — the worst outcome for a static analyzer. Verified-and-fixed shapes: f-strings, str()/.format()/.join(), dict.get()/subscript, BoolOp, attribute reads, await, comprehensions, container-writes, self/cls method calls, aliased serialization sinks.

  • String-building combines via least_trusted (weakest-link), so a benign literal + validated data stays clean — no new false positives on validated code (caught and corrected mid-review via base-commit diffing).
  • The unknown-call fallback is unchanged (no FP explosion). The expression combiners (BinOp/List/Dict/IfExp/BoolOp/…) were initially left on taint_join; they are now migrated to least_trusted in the follow-up below.

P0 — security: symlink confine_to_root bypass (THREAT-001 residual)

The per-file discovery loop used a lexical path check, so a symlinked .py inside a legit source-root could escape the root and the analyzer would read out-of-tree content (reproduced: → /etc/passwd) via the MCP scan tool. Now each discovered file is resolved under root when confine_to_root is set (MCP path). CLI default behavior unchanged.

Follow-up — expression-combiner over-tainting (false positives) · 58cca6d

Closes residuals #1 and #2 below. The L2 resolver used taint_join (provenance-clash; any cross-family pair → MIXED_RAW) for value-building / either-or / container-summary combiners, where the rank-meet least_trusted (weakest-link) is correct — the operator already used by the f-string/.format/.join paths. taint_join(INTEGRAL, ASSURED) = MIXED_RAW manufactured a spurious clash on benign literal+validated combinations, firing PY-WL-101 on validated data.

  • 9 sites in variable_level.py switched to least_trusted: BinOp, List/Tuple/Set, Dict, IfExp, BoolOp, DictComp, .get/.pop/.setdefault default, augmented-assign, and container writes (_taint_container_base).
  • Control-flow MERGES (if/else, loops, match arms) deliberately keep taint_join — the documented join lattice; the full-sweep conversion is tracked separately (wardline-4d9f840c24).
  • No false negatives: both firing rules gate on a clean declared level, so any raw result (rank 6 or 7) is strictly above it and still fires — the MIXED_RAWUNKNOWN_RAW relabel never straddles a threshold.
  • Verified: +10 clean-direction tests, 15 buggy-MIXED_RAW assertions corrected to the precise least_trusted value, soundness battery 30/30, end-to-end FP repro 3 → 0, self-host 0 PY-WL defects.

Hardening

  • Scan observability: parse-error / unreadable / recursion-skipped / missing-source-root files are now counted (ScanSummary.unanalyzed) and surfaced (CLI summary + MCP result), with opt-in --fail-on-unanalyzed. A no-module file (top-level __init__.py) emits an observable WLN-ENGINE-NO-MODULE FACT (deliberately not counted as unanalyzed, to avoid signal dilution).
  • An explicit --config path that does not exist now raises ConfigError instead of silently running with default policy.
  • Line-less engine-diagnostic DEFECTs (WLN-L3-* / WLN-ENGINE-DIAGNOSTIC) no longer crash the run via the suppression line-invariant assert.
  • MCP: an unexpected exception in a tool handler now returns an isError result (which clients reliably surface) instead of a -32603 error; McpError still propagates as a JSON-RPC protocol fault.
  • clarion/client docstring corrected (routes on HTTP status band, not envelope code).

Known residuals (left for triage)

  • BinOp "" + validated over-taintsfixed in 58cca6d (wardline-4d94577013).
  • .get/.pop tainted-default over-taintsfixed in 58cca6d (wardline-4d94577013).
  • Star-import of markers is a false negative but observable via WLN-ENGINE-UNKNOWN-IMPORT (wardline-2b427a9579).
  • Control-flow merges (if/else, loops, match) still over-taint two clean-but-different-family branches — the "full sweep" deferred from the combiner fix (wardline-4d9f840c24).

Test plan

  • ruff check src tests · mypy · pytest -q (923 passed) · pytest tests/test_self_hosting.py
  • 30-case soundness battery: all FN cases fire, all validated/clean cases stay clean

🤖 Generated with Claude Code

…rub)

A 7-agent review of the one-shot SP0–SP9 system surfaced 9 findings; all
fixed here with test-first coverage. Suite 843→913, ruff/mypy clean,
self-hosting green. Acceptance oracle: a 30-case FN-fire/FP-clean taint
battery (kept out-of-tree).

P0 — L2 taint soundness hole (false negatives):
  `_resolve_expr` fell through to `return function_taint` for unmodelled
  AST shapes; in a @trusted producer that resets untrusted data to the
  trusted tier (fail-open) and yields a clean report. Now modelled:
  Subscript/Attribute/Await/BoolOp/JoinedStr(f-string)/comprehensions;
  container-write taint (Assign/AugAssign/AnnAssign subscript+attr targets);
  curated taint-propagating ops in _resolve_call (str/repr/ascii/bytes/
  bytearray/format/next; .format/.join; .get/.pop/.setdefault); self/cls
  method-call L2 keys (build_call_taint_map); serialization sink-alias
  closure (call_taint_map). String-building combines via least_trusted
  (weakest-link) so a benign literal + validated data stays clean — no new
  FP on validated code. The unknown-call fallback is unchanged (no FP
  explosion); pre-existing combiners (BinOp/List/Dict/IfExp) untouched.

P0 — security: symlink confine_to_root bypass (THREAT-001 residual).
  discovery.py now resolves each discovered file under root when
  confine_to_root (MCP path); a symlinked .py can no longer escape to
  read out-of-root content. CLI default behavior unchanged.

Hardening:
- Scan observability: parse-error / unreadable / recursion-skipped /
  missing-source-root files are now COUNTED (ScanSummary.unanalyzed) and
  surfaced (CLI summary + MCP result), with opt-in `--fail-on-unanalyzed`;
  a no-module file (top-level __init__.py) emits an observable
  WLN-ENGINE-NO-MODULE FACT (deliberately NOT counted as unanalyzed).
- An explicit --config path that does not exist now raises ConfigError
  instead of silently running with default policy.
- Line-less engine-diagnostic DEFECTs (WLN-L3-* / WLN-ENGINE-DIAGNOSTIC)
  no longer crash the run via the suppression line-invariant assert; the
  <engine> sentinel is exempted (ENGINE_PATH hoisted to core/finding).
- MCP: an unexpected exception in a tool handler now returns an isError
  result (which clients reliably surface) instead of a -32603 error;
  McpError still propagates as a JSON-RPC protocol fault.
- clarion/client docstring corrected: routes on the HTTP status band, not
  the envelope code.

Known residuals left for triage (pre-existing / cross-rule, out of scope):
BinOp `"" + validated` over-taints; .get/.pop tainted-default over-taints
(IfExp-consistent); star-import of markers is a FN but observable via
WLN-ENGINE-UNKNOWN-IMPORT.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 31, 2026 03:07

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

John Morrissey and others added 2 commits May 31, 2026 13:38
The L2 variable-level resolver used taint_join (provenance-clash; any
cross-family pair → MIXED_RAW) for value-building / either-or /
container-summary combiners, where the rank-meet least_trusted
(weakest-link) is correct — the operator already used by the
f-string/.format/.join/builtins paths. taint_join(INTEGRAL, ASSURED) =
MIXED_RAW manufactured a spurious provenance clash on benign
literal+validated combinations, firing PY-WL-101 on validated data.

Switch 9 expression/value-building sites in variable_level.py to
least_trusted: BinOp, List/Tuple/Set, Dict, IfExp, BoolOp, DictComp,
.get/.pop/.setdefault default, augmented-assign, and container writes
(_taint_container_base). Control-flow MERGES (if/else, loops, match
arms) deliberately keep taint_join — the documented join lattice; their
full-sweep conversion is tracked separately (wardline-4d9f840c24).

Soundness: both firing rules gate on a clean declared level, so any raw
result (rank 6 or 7) still fires — no false negatives. Verified: 923
tests (+10 clean-direction, 15 buggy-MIXED_RAW assertions corrected to
the precise least_trusted value), soundness battery 30/30, end-to-end
FP repro 3→0, self-host 0 PY-WL defects, ruff + mypy --strict clean.

Closes wardline-4d94577013.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bump _version.py 0.1.0 → 0.2.0 and finalize the CHANGELOG [0.2.0] entry:
MCP server (SP8), explain_taint provenance + N-hop chain, Clarion-backed
taint store (SP9), the Material for MkDocs docs site (SP7), the L2 taint
soundness fix and expression-combiner FP fix, scan observability + the
other hardening fixes, the symlink path-confinement fix (Security), and
the loom-extra removal.

Live-docs version strings bumped (getting-started, cli reference); the
__version__ guard test updated to 0.2.0. Build verified: 923 tests pass,
mkdocs --strict clean, sdist+wheel build as wardline-0.2.0, wheel
metadata valid (MIT, py>=3.12, extras clarion/dev/docs/scanner).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@tachyon-beep tachyon-beep changed the title Close taint soundness hole + 8 hardening fixes (full-codebase scrub) 0.2.0 release — taint soundness + hardening scrub, version bump + changelog May 31, 2026
@tachyon-beep tachyon-beep merged commit 4ed3fb0 into main May 31, 2026
5 checks passed
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.

2 participants