Skip to content

Fence finding text structurally in the swarm merge/verify prompts#28

Merged
gering merged 4 commits into
mainfrom
task/fence-finding-text-in-pipeline
Jul 13, 2026
Merged

Fence finding text structurally in the swarm merge/verify prompts#28
gering merged 4 commits into
mainfrom
task/fence-finding-text-in-pipeline

Conversation

@gering

@gering gering commented Jul 12, 2026

Copy link
Copy Markdown
Owner

Summary

  • Closes the second-order prompt-injection path in the swarm review pipeline: finding free-text from external backends (codex/grok) flowed raw into the merge- and verify-stage prompts with only an instruction-level "treat as DATA" guard, no structural delimiter.
  • Mirrors the existing diff-nonce fence for the finding fields, so a malicious diff can no longer plant reviewer instructions in a finding summary/failure_scenario/mechanism that the merge/verify agents then read unfenced.
  • Layers WITH the existing DATA instruction and the output-gate secret scrub — defense in depth, not a replacement.

Changes

  • Skill (skills/review/SKILL.md) — mint a per-run FINDING_NONCE (secrets.token_hex) in the Bash prep, deliberately not written into the external prompt so backends never see it and cannot forge the delimiter; pass it via args.findingNonce.
  • Workflow (workflows/swarm-review.js)fenceFindings() wraps the numbered merge list (FINDINGS-<nonce>) and per-solo verifier fields (FINDING-<nonce>) in nonce-carrying delimiters. The sandbox has no RNG, so the workflow only collision-checks the nonce against the returned findings and extends it deterministically (nonce-1, -2…) on collision.
  • Docspipeline-blueprint.md § Security documents both fence hops (and corrects a stale note claiming the diff was unfenced); README.md security paragraph updated; knowledge entry swarm-review-pipeline.md captures the RNG-in-sandbox constraint.
  • Version — swarm 0.3.00.3.1 (plugin.json + marketplace.json).

Readiness

  • ✅ Uncommitted: clean
  • ✅ README: security paragraph updated
  • ✅ Version: bumped 0.3.0 → 0.3.1 (both files in sync)
  • ➖ Changelog: none in repo
  • ✅ Knowledge: swarm-review-pipeline.md updated
  • ✅ Structure check: 0 errors (2 pre-existing unrelated warnings)
  • ➖ Lint/Build: declarative plugin repo, no build step

Test plan

  • scrub_secrets / output-gate still runs at every stage boundary (unchanged in this PR)
  • Run /swarm:review on a diff and confirm merge/verify prompts carry FINDINGS-<nonce> / FINDING-<nonce> delimiters
  • Craft a diff whose finding text contains the base nonce and confirm the workflow extends it (no early fence close)

🤖 Generated with Claude Code

gering and others added 4 commits July 13, 2026 12:55
Close the second-order prompt-injection path: finding free-text from
external backends (codex/grok) flowed raw into the merge- and verify-stage
prompts with only an instruction-level "treat as DATA" guard. Mirror the
diff-nonce fence for finding fields.

- Add a per-run FINDING_NONCE in the skill's Bash prep (secrets.token_hex),
  deliberately NOT written into the external prompt so backends can't forge
  the delimiter; pass it to the workflow via args.findingNonce.
- Workflow fenceFindings(): wrap the numbered merge list (FINDINGS-<nonce>)
  and per-solo verifier fields (FINDING-<nonce>) in nonce-carrying delimiters.
  Sandbox has no RNG, so the workflow only collision-checks the nonce against
  the returned findings and extends it deterministically on collision.
- Fences layer WITH the existing DATA instruction and the output-gate scrub,
  not instead of them; scrub_secrets stage boundaries unchanged.
- Document both fence hops in pipeline-blueprint.md § Security; bump swarm
  0.3.0 -> 0.3.1.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019FzeHRNUA2eud1nRSyCGbB
Address findings from a swarm review of the fence patch itself:

- Critical: the verify stage interpolated `c.file`/`c.line` OUTSIDE the
  finding fence — but `file` is backend-controlled (schema caps length, not
  charset), so a newline-laden `file` value could pose as trusted scaffolding
  and flip the verifier's verdict: the exact second-order hole the patch closes.
  Move file/line inside the fenced body and label the path a claim, not a
  trusted coordinate.
- Warning: a missing/malformed findingNonce dropped the structural fence
  silently, contradicting the "never silently insecure" contract. Log a
  visible degradation warning at both the absent and malformed cases.
- Minor: blueprint § Security implied a merge/verify secret-scrub that does
  not exist (scrub runs only at the output gate). Reword to say fences — not a
  scrub — protect the merge/verify agents; item 2's scrub runs on the way out.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019FzeHRNUA2eud1nRSyCGbB
Second swarm-review round on the fence patch surfaced follow-ups:

- Security (new vector): the verifier reads the backend-supplied `c.file`
  path, so a crafted `../../.ssh/id_rsa` / `~/.aws/credentials` / absolute
  path could exfiltrate a file. Fencing stopped instruction-injection in the
  text; add repoSafePath() to confine the READ target — out-of-repo paths are
  flagged and the verifier is told not to open them.
- Fail closed at the source: SKILL.md aborts (SWARM_NONCE_UNAVAILABLE) if
  python3/secrets can't mint the finding nonce, instead of silently degrading
  the workflow fence to the instruction-only guard downstream.
- Nonce validator now enforces a length floor (>=16, matching token_hex(8))
  not just shape, so a short/low-entropy caller token is rejected.
- Docs: reconcile blueprint items 2 and 3 — scrubbing runs at the adapter and
  output boundaries, NOT at merge/verify; fencing (not a scrub) is what
  protects those in-session agents. Removes the self-contradiction.

Consensus-cluster fencing (raised, verdict: disagree) left as-is: that text is
already fenced at merge and re-enters no later agent prompt; "don't fully trust
consensus" is the documented residual (§ Security item 5).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019FzeHRNUA2eud1nRSyCGbB
Follow-ups from a third swarm-review pass:

- Fix (regression I introduced): under `set -euo pipefail` a failed python3
  command substitution aborts before the `[ -z ]` guard, so the nonce
  fail-closed check was dead code and $TMPD leaked. Catch failure in an `|| {…}`
  list at both nonce sites (diff + finding) so the marker + cleanup fire.
- --fix path-safety gate: the `--fix` re-read opens the backend-supplied
  `file:line` in the unsandboxed main session. Add a step-0 repoSafePath gate
  (skipped-unsafe-path) and carry a `pathSafe` flag on every returned finding so
  consensus clusters + --fix reads are covered, not only the solo verifier.
- Visible degradation: propagate `fenceDegraded` into the return payload +
  balance (not just the /workflows log); presenter renders a prominent warning.
- Harden repoSafePath: also reject Windows drive-absolute paths and any
  control/newline chars; tighten the nonce validator to hex (token_hex shape).
- Sanitize newlines when building the merge numbered list so each finding is one
  line (mis-clustering hardening; coverage guard already prevents loss).
- Docs: blueprint records the finding-fence threat-model assumptions (nonce
  secrecy needs a non-steerable orchestrator; symlink-realpath is the residual).

Left as residual (declined, beyond this minimal feature): symlink realpath
containment (no fs in the workflow sandbox) and re-architecting nonce transport;
over-rejection of `..` paths kept as the conservative security default.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019FzeHRNUA2eud1nRSyCGbB
@gering gering force-pushed the task/fence-finding-text-in-pipeline branch from b3bc627 to 38a5824 Compare July 13, 2026 10:55
@gering gering merged commit 59996c2 into main Jul 13, 2026
1 check passed
@gering gering deleted the task/fence-finding-text-in-pipeline branch July 13, 2026 10:57
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.

1 participant