Writable-config rule ATL-163 + drift handle-replay DRF-009/010#104
Merged
Conversation
…class) ingest_mcp checks each config file for world-write (o+w on the file or its parent dir, fail-closed, group-write deliberately ignored to spare macOS staff-group setups) and stamps _config_world_writable on every server the file registers: each entry in a writable config is independently swappable for an attacker's launch command, the persistence step in the Windsurf attack (a prompt injection rewrote the local MCP config to auto-register a malicious stdio server). Covers repo-committed configs and scan --local, including Claude Code's nested project-scope servers. Pack 258 -> 259. Fixture examples/writable-mcp-config (the o+w bit is set by the test and the benchmark harness; git cannot store it). Benchmark recall 145/145, 0 FP. Cites OWASP-ASI05, CWE-732, NIST CM-5. Closes #98 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The stateless-core SEPs make servers mint portable task/resource handles that travel in conversation text, so a handle is a bearer credential: any principal that sees it can spend it. drift now builds a handle-provenance ledger from the optional handle + handle_op (mint/spend) event fields. DRF-009 (critical): a spend whose principal differs from the handle's minter, a replay or confused deputy in progress; first mint wins, so a thief cannot launder a stolen handle by re-minting it. DRF-010 (high): a spend with no observed mint, unknown provenance. Fail-closed in the DRF-008 style: only a well-formed handle pair participates, so handle-free logs behave byte-identically. Batch detect_drift and streaming DriftMonitor share one detector; findings truncate the handle so a report never re-publishes a spendable credential. Double-spend is deliberately out of scope until the SEP lands a single-use marker. Closes #101 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ruff 0.16.0 (released today) fires 187 new-rule violations repo-wide (I001, B167, UP037, RUF022, ...), so the unpinned dev extra broke every CI run overnight on files no PR touched. Pin to the series the repo actually lints with; unpin after a dedicated 0.16 adoption pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This was referenced Jul 23, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two issue-driven checks from the 2026-07-22 research backlog, one static and one runtime.
Closes #98. Closes #101.
ATL-163: MCP config file writable by other users (high)
CVE-2026-30615 (Windsurf) turned a prompt injection into persistence: the injected agent rewrote the local MCP config and auto-registered a malicious stdio server on every future session. The static precondition of that chain is a world-writable config, and that is what this rule flags.
ingest_mcpchecks each config file once (o+w on the file, falling back to its parent dir; the same_world_writablehelper ATL-113 uses) and stamps_config_world_writableon every server the file registers. Per-server findings are honest: each entry in a writable config is independently swappable for an attacker's launch command.bool(servers)gates the stat, so empty configs cost nothing.scan --localalike, including Claude Code's nested project-scope servers, which bypassingest_mcpand get the stamp re-applied inbuild_local_model.examples/writable-mcp-configwith two deliberately boring pinned servers, so the file permission is the only signal. The o+w bit is set by the test and the benchmark harness (world_writable:case setup) since git cannot store it.DRF-009/010: portable-handle replay under the stateless core (critical/high)
MCP SEP-2567/2575 make servers mint portable task/resource handles that travel in conversation text, so a handle is a bearer credential: any principal that sees it can spend it.
driftnow builds a handle-provenance ledger from the optionalhandle+handle_op(mint/spend) event fields.detect_driftand streamingDriftMonitorshare one detector. Findings truncate the handle so a report never re-publishes a spendable credential.Gates
attestral/+tests/(19 new tests acrosstest_writable_config_rule.pyandtest_drift_handle_replay.py).handle/handle_opevent fields.Generated with Claude Code.