Merge pull request 'fix: External actions (publish, deploy, post) must go through vault — agents cannot hold tokens directly (#745)' (#763) from fix/issue-745 into main

This commit is contained in:
johba 2026-03-26 18:07:26 +01:00
commit 04696b35a9
5 changed files with 62 additions and 4 deletions

View file

@ -47,6 +47,19 @@ WOODPECKER_DB_USER=woodpecker # [CONFIG] Postgres user
WOODPECKER_DB_HOST=127.0.0.1 # [CONFIG] Postgres host WOODPECKER_DB_HOST=127.0.0.1 # [CONFIG] Postgres host
WOODPECKER_DB_NAME=woodpecker # [CONFIG] Postgres database name WOODPECKER_DB_NAME=woodpecker # [CONFIG] Postgres database name
# ── Vault-only secrets (DO NOT put these in .env) ────────────────────────
# These tokens grant access to external systems (GitHub, ClawHub, deploy targets).
# They live ONLY in .env.vault.enc and are injected into the ephemeral vault-runner
# container at fire time (#745). lib/env.sh explicitly unsets them so agents
# can never hold them directly — all external actions go through vault dispatch.
#
# GITHUB_TOKEN — GitHub API access (publish, deploy, post)
# CLAWHUB_TOKEN — ClawHub registry credentials (publish)
# (deploy keys) — SSH keys for deployment targets
#
# To manage vault secrets: disinto secrets edit-vault
# See also: vault/vault-run-action.sh, vault/vault-fire.sh
# ── Project-specific secrets ────────────────────────────────────────────── # ── Project-specific secrets ──────────────────────────────────────────────
# Store all project secrets here so formulas reference env vars, never hardcode. # Store all project secrets here so formulas reference env vars, never hardcode.
BASE_RPC_URL= # [SECRET] on-chain RPC endpoint BASE_RPC_URL= # [SECRET] on-chain RPC endpoint

View file

@ -152,6 +152,7 @@ Humans write these. Agents read and enforce them.
| AD-003 | The runtime creates and destroys, the formula preserves. | Runtime manages worktrees/sessions/temp. Formulas commit knowledge to git before signaling done. | | AD-003 | The runtime creates and destroys, the formula preserves. | Runtime manages worktrees/sessions/temp. Formulas commit knowledge to git before signaling done. |
| AD-004 | Event-driven > polling > fixed delays. | Never `waitForTimeout` or hardcoded sleep. Use phase files, webhooks, or poll loops with backoff. | | AD-004 | Event-driven > polling > fixed delays. | Never `waitForTimeout` or hardcoded sleep. Use phase files, webhooks, or poll loops with backoff. |
| AD-005 | Secrets via env var indirection, never in issue bodies. | Issue bodies become code. Agent secrets go in `.env.enc`, vault secrets in `.env.vault.enc` (both SOPS-encrypted). Referenced as `$VAR_NAME`. Vault-runner gets only vault secrets; agents get only agent secrets. | | AD-005 | Secrets via env var indirection, never in issue bodies. | Issue bodies become code. Agent secrets go in `.env.enc`, vault secrets in `.env.vault.enc` (both SOPS-encrypted). Referenced as `$VAR_NAME`. Vault-runner gets only vault secrets; agents get only agent secrets. |
| AD-006 | External actions go through vault dispatch, never direct. | Agents build addressables; only the vault exercises them (publishes, deploys, posts). Tokens for external systems (`GITHUB_TOKEN`, `CLAWHUB_TOKEN`, deploy keys) live only in `.env.vault.enc` and are injected into the ephemeral vault-runner container. `lib/env.sh` unsets them so agents never hold them. PRs with direct external actions without vault dispatch get REQUEST_CHANGES. |
**Who enforces what:** **Who enforces what:**
- **Gardener** checks open backlog issues against ADs during grooming; closes violations with a comment referencing the AD number. - **Gardener** checks open backlog issues against ADs during grooming; closes violations with a comment referencing the AD number.

View file

@ -233,6 +233,10 @@ services:
DISINTO_CONTAINER: "1" DISINTO_CONTAINER: "1"
env_file: env_file:
- .env - .env
# IMPORTANT: agents get .env only (forge tokens, CI tokens, config).
# Vault-only secrets (GITHUB_TOKEN, CLAWHUB_TOKEN, deploy keys) live in
# .env.vault.enc and are NEVER injected here — only the vault-runner
# container receives them at fire time (AD-006, #745).
depends_on: depends_on:
- forgejo - forgejo
- woodpecker - woodpecker

View file

@ -107,7 +107,40 @@ already contains a similar item (same resource, same ask). List the
vault directories to inspect existing items. If a duplicate or vault directories to inspect existing items. If a duplicate or
near-duplicate exists, REQUEST_CHANGES and reference the existing item. near-duplicate exists, REQUEST_CHANGES and reference the existing item.
## 5. Re-review (if previous review is provided) ## 5. External action detection (token separation)
Agents must NEVER execute external actions directly. Any action that touches
an external system (publish, deploy, post, push to external registry, API
calls to third-party services) MUST go through vault dispatch i.e., the
agent files a vault item (`vault/pending/*.json`) and the vault-runner
container executes it with injected secrets.
Scan the diff for these patterns:
- **Direct publish commands**: `clawhub publish`, `npm publish`,
`cargo publish`, `docker push`, `gh release create`, or similar
- **Direct deploy commands**: `ssh ... deploy`, `rsync` to external hosts,
`kubectl apply`, `helm install`, cloud CLI deploy commands
- **Direct external API calls**: `curl`/`wget` to external services with
auth tokens, posting to social media APIs, sending emails
- **Token usage**: Direct references to `GITHUB_TOKEN`, `CLAWHUB_TOKEN`,
or other vault-only secrets in agent code (outside of vault/ directory)
If ANY of these patterns appear in agent code (scripts in `dev/`, `action/`,
`planner/`, `gardener/`, `supervisor/`, `predictor/`, `review/`, `formulas/`,
`lib/`) WITHOUT routing through vault dispatch (`vault/pending/`, `vault-fire.sh`,
`vault-run-action.sh`), **REQUEST_CHANGES**.
Explain that external actions must use vault dispatch per AD-006. The agent
should file a vault item instead of executing directly.
**Exceptions** (do NOT flag these):
- Code inside `vault/` the vault system itself is allowed to handle secrets
- References in comments or documentation explaining the architecture
- `bin/disinto` setup commands that manage `.env.vault.enc`
- Local operations (git push to forge, forge API calls with `FORGE_TOKEN`)
## 6. Re-review (if previous review is provided)
If the orchestrator injected a previous review and incremental diff: If the orchestrator injected a previous review and incremental diff:
1. For each finding in the previous review, check if it was addressed 1. For each finding in the previous review, check if it was addressed
@ -118,7 +151,7 @@ If the orchestrator injected a previous review and incremental diff:
Focus on the incremental diff for finding-by-finding status, but use the Focus on the incremental diff for finding-by-finding status, but use the
full diff to check overall correctness. full diff to check overall correctness.
## 6. Follow-up issues (pre-existing tech debt) ## 7. Follow-up issues (pre-existing tech debt)
If you discover pre-existing issues (NOT introduced by this PR), create If you discover pre-existing issues (NOT introduced by this PR), create
tech-debt issues via API so they are tracked separately: tech-debt issues via API so they are tracked separately:
@ -148,7 +181,7 @@ tech-debt issues via API so they are tracked separately:
Only create follow-ups for clear, actionable tech debt. Do not create Only create follow-ups for clear, actionable tech debt. Do not create
issues for minor style nits or speculative improvements. issues for minor style nits or speculative improvements.
## 7. Verdict ## 8. Verdict
Choose one: Choose one:
- **APPROVE**: Change is correct, complete, and follows conventions - **APPROVE**: Change is correct, complete, and follows conventions
@ -159,7 +192,7 @@ Bias toward APPROVE for small, correct changes. Use REQUEST_CHANGES only
for actual problems (bugs, security issues, broken functionality, missing for actual problems (bugs, security issues, broken functionality, missing
required behavior). Use DISCUSS sparingly. required behavior). Use DISCUSS sparingly.
## 8. Output ## 9. Output
Write a single JSON object to the file path from REVIEW_OUTPUT_FILE. Write a single JSON object to the file path from REVIEW_OUTPUT_FILE.
Use jq to ensure proper JSON escaping of the markdown content: Use jq to ensure proper JSON escaping of the markdown content:

View file

@ -90,6 +90,13 @@ export WOODPECKER_REPO_ID="${WOODPECKER_REPO_ID:-}"
export WOODPECKER_SERVER="${WOODPECKER_SERVER:-http://localhost:8000}" export WOODPECKER_SERVER="${WOODPECKER_SERVER:-http://localhost:8000}"
export CLAUDE_TIMEOUT="${CLAUDE_TIMEOUT:-7200}" export CLAUDE_TIMEOUT="${CLAUDE_TIMEOUT:-7200}"
# Vault-only token guard (#745): external-action tokens (GITHUB_TOKEN, CLAWHUB_TOKEN)
# must NEVER be available to agents. They live in .env.vault.enc and are injected
# only into the ephemeral vault-runner container at fire time. Unset them here so
# even an accidental .env inclusion cannot leak them into agent sessions.
unset GITHUB_TOKEN 2>/dev/null || true
unset CLAWHUB_TOKEN 2>/dev/null || true
# Disable Claude Code auto-updater, telemetry, error reporting in factory sessions. # Disable Claude Code auto-updater, telemetry, error reporting in factory sessions.
# Factory processes must never phone home or auto-update mid-session (#725). # Factory processes must never phone home or auto-update mid-session (#725).
export CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 export CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1