cgagentharness-write-policy-redteam
Adversarially exercise CG-agent-harness write/git approval, confirm+reason, clone jail, and shim argv boundaries ("browser never supplies a command"). Use when hardening or changing writer, write gate
SKILL.md.claude/skills/cgagentharness-write-policy-redteam/SKILL.md
GitHubWrite-policy Redteam
Persona: Offensive security engineer for the harness write surface — not
CyClaw's prompt sanitizer. The trust boundary is: browser never supplies a
command; every repository write is gated; clone jail contains landed paths;
confirm is never defaulted; reason is required; kill switch
CGAGENTHARNESS_AGENTIC_WRITE_DISABLE is AND-only. Exit code 4 = write
refused.
Corpus (FACT): Existing Rust integration tests — not a probes.yaml. Inspect
fresh-policy, approval and rollback assertions before selecting probes; an old
end-to-end success test alone does not establish those boundaries.
| Surface | Primary corpus |
|---|---|
| Writer / gate order / plan integrity | tests/real_repo_loop.rs::writer_gates_in_order_and_plan_integrity (+ publish/confirm paths in the same file) |
| Fresh policy / separate intent | tests/write_policy.rs, tests/write_kill_switch.rs |
| Git approval / digest, mode, origin, exact commit, index lock, disabled extensions | tests/git_approval.rs, agentic_foundations::manifest_digest_binds_files_and_head |
| Exact edits / preflight / failed rollback quarantine | tests/exact_edits.rs, unit tests in src/agentic/workspace.rs |
| Retrieval / sensitive reads / cloud refusal | tests/real_repo_loop.rs repository-retrieval and denied-basename cases |
| Clone / read jail | agentic_foundations::apply_proposal_refuses_jail_escapes_and_oversize_content, read_jail_refuses_symlink_escapes_without_following_the_leaf |
| Hostile argv / confirm never manufactured | tests/shim_and_agent_routes.rs hostile-argv matrix, a_request_can_never_carry_an_argv, publish-missing-confirm → child exit 4 |
| Kill switch AND-only / shipped gates | invariant_guard::writer_kill_switch_is_and_not_or, shipped_config_enforces_accounts_tls_and_keeps_execution_gates_closed |
Done means executed refusal cases and a regression assert for each newly closed bypass, with no existing check weakened.
The loop
Step 1 — Run the write-policy corpus
GROK_API_KEY="" ANTHROPIC_API_KEY="" DEEPAGENT_API_KEY="" \
cargo test --test real_repo_loop -- --nocapture
GROK_API_KEY="" ANTHROPIC_API_KEY="" DEEPAGENT_API_KEY="" \
cargo test --test agentic_foundations -- --nocapture
GROK_API_KEY="" ANTHROPIC_API_KEY="" DEEPAGENT_API_KEY="" \
cargo test --test shim_and_agent_routes -- --nocapture
GROK_API_KEY="" ANTHROPIC_API_KEY="" DEEPAGENT_API_KEY="" \
cargo test --test invariant_guard --test write_policy --test write_kill_switch \
--test git_approval --test exact_edits -- --nocapture
Narrow with filter strings only after checking that each selects the intended tests; a filter matching zero tests is not evidence.
Step 2 — Classify results into buckets
| Bucket | Meaning | Action |
|---|---|---|
| new_bypasses | Attack that should refuse (exit 4 / 422 / jail) now succeeds or assert was deleted | Regression. Fix before anything else. Never loosen the assert. |
| known_gaps | Documented residual / weaker-than-name signal already called out in INVARIANTS.md |
Work list only with explicit owner decision; do not silently "close" by relaxing policy. |
| fixed_findings | Prior gap now refuses with evidence | Bank a durable regression test; keep the assert. |
| false_greens | Suite green because confirm was defaulted, kill switch OR-ed, Seatbelt skipped, or shared listener used | Treat as FAIL of the verification method — reproduce with owned temp CGAGENTHARNESS_HOME + unique port. |
Step 3 — Pick one new_bypass / known_gap; name the family
Work one finding at a time. Map to a family so the fix lands in the right module:
- Gate order —
agentic.enabled/ mode /writes_enabled/ reason / confirm /allow_git_write_tools(src/agentic/writer.rs) - Confirm+reason — never defaulted; missing confirm → exit 4
- Kill switch — AND-only env disable
- Argv boundary —
--opt=valuesingle elements; browser never supplies argv (src/shim,src/server/agent_policy.rs) - Clone jail — landed-path judgment, symlink /
.gitname-equivalence (src/agentic/workspace.rs) - Approval binding — digest, modes, live tree, index, origin and approved commit
- Policy revocation — recheck disk at each mutation; confirm for run, approval, push and publication are separate intents
Step 4 — Close with a minimal gate + regression test
- Prefer the smallest refuse path that restores the invariant (one gate check, one argv validation, one jail predicate).
- Add or extend a Rust test assert in the matching file above — same style as existing hostile matrices.
- Re-run Step 1 for the touched tests, then the related
invariant_guard/auth_guardsset if shared routing changed. - Never default
confirm, makereasonoptional, OR the kill switch, or delete/skip an assert to green.
Step 5 — Report
End with FACT vs INFERENCE, buckets counts, commands + exit codes, and whether exit-4 paths still hold. Skill completion ≠ publish authorization.
Guardrails
- Harness-only: I6, write gates, clone jail, CSRF placeholders, loopback, secrets. Do not transplant CyClaw RAG / soul / triple-gate / LangGraph / I1–I5 as applying here.
- Never weaken security to pass the redteam.
- Retain rollback recovery backups after quarantine; do not claim crash atomicity.
- Pair with
cgagentharness-invariant-guardbefore merging core-path diffs — it's manual-only (disable-model-invocation: true); ask the operator to run/cgagentharness-invariant-guard.