fix(security): close RiskChainAccumulator cross-turn and cross-session gaps#6602
Merged
Conversation
bug-ops
enabled auto-merge (squash)
July 20, 2026 18:50
bug-ops
force-pushed
the
fix/6561-risk-chain-lifecycle
branch
from
July 20, 2026 19:02
9938dae to
45ea85b
Compare
bug-ops
force-pushed
the
fix/6561-risk-chain-lifecycle
branch
3 times, most recently
from
July 20, 2026 19:55
ea78e8d to
c2ba570
Compare
…n gaps RiskChainAccumulator (crates/zeph-tools/src/risk_chain.rs) detects multi-step exfiltration chains (sensitive read followed by network egress) and blocks the shell command when a chain fires. Three related gaps in this control, all following PR #6587's entry-point wiring: - The accumulator was fully cleared at every turn boundary, so a chain deliberately split across turns (read in turn N, exfiltrate in turn N+1) went completely undetected despite the module doc claiming a cross-turn fallback existed. `reset()` is replaced by `advance_turn()`, which prunes only calls older than a bounded window (3 turns) and recomputes the cumulative score from what remains, so a split chain within the window is still caught. A dedup marker prevents a single live chain from re-pushing its signal on every subsequent call while it stays matched, which would otherwise let one detection flood TrajectorySentinel into a false session-wide block. - Signal codes for the two chain patterns were never mapped in RiskSignal::from_code, so a confirmed chain-fire signal reaching TrajectorySentinel fell through to the lowest-weight fallback tier. Added explicit ExfilReadThenSend/CredThenEgress variants weighted in line with the other high-confidence signals. - ACP and serve built one RiskChainAccumulator per connection/server and shared it via Arc across every concurrent session, so one session's turn reset could wipe another session's chain state or combine unrelated sessions' activity into a spurious block. Each session now gets its own ShellExecutor/RiskChainAccumulator built at session-construction time, composed over the shared connection/server base (sandbox, audit logger, permission policy). Added end-to-end tests per entry point (runner, daemon, acp, serve) that dispatch a real chained tool-call sequence through each entry point's production executor construction path and assert the block, plus dedicated regression tests for the cross-turn split and the session-isolation behavior. Closes #6561 Closes #6588 Closes #6589
… feature Both call sites of this helper (introduced by the #6588 per-session isolation fix) live in acp.rs behind #[cfg(feature = "acp")], but the function's own definition in agent_setup.rs had no matching gate. Any feature bundle without acp (bench, chat, desktop, server) compiled it with zero callers, tripping dead_code under this project's build.warnings = deny.
bug-ops
force-pushed
the
fix/6561-risk-chain-lifecycle
branch
from
July 20, 2026 20:02
c2ba570 to
0f84d43
Compare
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.
Summary
RiskChainAccumulator(crates/zeph-tools/src/risk_chain.rs) is the shell executor's multi-step exfiltration-chain detector — it blocks a command when it observes an ordered pattern like a sensitive-file read followed by network egress. This PR closes three related gaps in that control, all surfacing after PR #6587's entry-point wiring landed:reset()is replaced byadvance_turn(), which prunes only calls older than a bounded 3-turn window and recomputes the cumulative score from what remains, so a split chain within the window is still caught. A dedup marker prevents one live chain from re-pushing its signal on every subsequent call while still matched, which would otherwise let a single detection floodTrajectorySentinelinto a false session-wide block. Signal codes for the two chain patterns are now also explicitly mapped inRiskSignal::from_codeat an appropriate weight — previously they fell through to the lowest fallback tier, making a confirmed chain-fire near-invisible toTrajectorySentinel/MAGE.RiskChainAccumulatorper connection/server and shared it viaArcacross every concurrent session, so one session's turn reset could wipe another session's chain state, or unrelated sessions' activity could combine into a spurious block. Each session now gets its ownShellExecutor/RiskChainAccumulatorbuilt at session-construction time, composed over the shared connection/server base (sandbox, audit logger, permission policy).A follow-up P3 issue will be filed separately for making the 3-turn cross-turn window configurable (currently a documented, bounded constant) rather than blocking this PR on it.
Test plan
cargo +nightly fmt --checkcargo clippy --profile ci --workspace --all-targets --features "desktop,ide,server,chat,pdf,scheduler,testing" -- -D warningscargo nextest run --config-file .github/nextest.toml --workspace --features "desktop,ide,server,chat,pdf,scheduler" --lib --bins— 14962 passed, 0 failed, 36 skippedRUSTFLAGS="-D warnings" RUSTDOCFLAGS="--deny rustdoc::broken_intra_doc_links")cargo test --docfor touched cratesgitleaks protect --staged.local/testing/playbooks/risk-chain-accumulator.mdand.local/testing/coverage-status.mdupdatedUntestedin coverage-status.md pending a live-tester CI cycle