Handoff Agent Least-Privilege Identity Model
Status: Proposed Date: 2026-06-20 UTC Author: software-architect ADR: 0131-handoff-agent-least-privilege-identity Extends: vault-multi-agent-access-pattern.md / ADR-0130 Parent card: #3738
1. Context
ADR-0130 and its companion design doc established the per-agent Infisical machine-identity model for vault access: every agent class gets its own scoped, read-only, independently revocable identity rather than sharing the admin identity. That model was designed for the vault surface specifically.
A handoff agent is a more demanding threat model than an ephemeral local sub-agent. A handoff runs unattended — it is a Claude Cloud / scheduled CCR agent, a new-repo agent, or any other autonomous task that operates without an operator present. It touches not just vault but the full set of credential surfaces a coding or ops agent needs: GitHub write access, cloud provider APIs, MCP tool bindings, and potentially payment or broker APIs.
Today, in the absence of a handoff permission model:
-
The GitHub identity is one of three shared bots (raxx-dev-bot, raxx-ops-bot, raxx-pm-bot), with permissions scoped to the entire repo and granted to the entire agent class. A compromised handoff that authenticates as raxx-dev-bot can push branches across the whole repo, create PRs, write issues, and trigger workflows — not just the narrow set of actions the handoff was dispatched to perform.
-
Cloud credentials (Heroku API key, AWS keys, Stripe keys, Cloudflare tokens) are the same full-scope keys used for live operations. A misbehaving handoff could trigger a live Stripe charge, modify DNS, or remove a Heroku dyno.
-
MCP tool bindings are inherited from the ambient session context, not trimmed to the minimum the handoff needs.
The operator-stated invariant: "anything handed off to any agent — a Claude Cloud / scheduled CCR agent, a new repo's agent, any unattended task — must get its OWN minted, least-privilege, independently-revocable identity; NEVER the shared admin identity or blanket permissions."
This document designs the complete least-privilege handoff permission model across every credential surface.
2. Invariants
The following invariants apply to every handoff agent. They are non-negotiable extensions of the platform-level invariants.
| # | Invariant |
|---|---|
| H1 | No handoff agent ever holds the admin identity or blanket-scope credentials. Every handoff identity set is the minimum required for its task. |
| H2 | Every handoff identity is independently revocable. Revoking one handoff agent's access does not affect other agents or services. |
| H3 | Every handoff identity is time-bound. Short TTL on tokens; credentials not valid indefinitely. |
| H4 | Provisioning a scoped identity set is step 0 of every handoff dispatch. No task runs before its identity set is in place. |
| H5 | Every handoff identity access is audited. Who read/wrote what, when, under which handoff task. |
| H6 | No stored credentials in form that can be replayed (platform invariant I1). Handoff identity secrets live in env/secret stores injected at spawn time; never in committed files. |
| H7 | No handoff agent receives live/prod financial credentials unless the task explicitly requires them and the operator confirms. Default is test/sandbox mode for every financial surface. |
| H8 | Every credential surface the handoff touches has a kill-switch. Killing the handoff identity revokes access within one TTL cycle without requiring redeploy of shared infrastructure. |
3. Credential-Surface Inventory
The following table enumerates every credential surface a handoff agent may need and what least-privilege looks like for each.
3.1 Full surface table
| Surface | Full / Admin shape (never for handoffs) | Least-privilege handoff shape | TTL / rotation | Independent revocation mechanism |
|---|---|---|---|---|
| Infisical vault | Admin machine identity — full project read/write/delete | Per-handoff read-only identity scoped to a single secret path glob (e.g., /MooseQuest/handoff/<task-id>/) |
Access token: 15 min; client secret: Velvet rotation cycle | Revoke the per-handoff machine identity in Infisical (<60 s). ADR-0130 §6.5. |
| GitHub — repo contents | raxx-dev-bot GitHub App, Contents R/W, Workflows R/W — entire repo | Fine-grained PAT or per-handoff GitHub App install scoped to: Contents R/W for the target branch only (not protected branches), Pull Requests W (create + update own PRs), Issues W (create + comment). No admin, no secrets:read, no Workflows write, no merge to protected branches without review. | 1-hour installation token (GitHub App model) or expiring PAT (max 24h for handoffs) | Revoke the fine-grained PAT or uninstall the per-handoff App install from the repo. |
| GitHub — Actions secrets | Full org/repo secrets admin | No access. Handoffs never read or write Actions secrets. | N/A | N/A — forbidden by design. |
| GitHub — branch protection | Bypass protection (admin) | No bypass. Handoffs must open PRs for review; they cannot push directly to main. |
N/A | N/A — forbidden by design. |
| Heroku | Full platform API key (all apps, scale, config, dynos) | Task-scoped Heroku API key with scope limited to: config-vars read/set on one named app, OR deploy to one named pipeline stage. No dyno scale, no add-on admin, no billing, no collaborator management. | Short-lived (Heroku does not support sub-hour token TTL natively; rotate per Velvet cycle at task end) | Delete the task-scoped API key from Heroku account. |
| AWS | IAM user (claude-infisical-bootstrap) with broad permissions |
Per-task IAM role assumed via STS AssumeRole with explicit policy: only the SSM Parameter Store paths the handoff needs (ssm:GetParameter on /raxx/<task>/) or the S3 prefix it needs. Session duration: max 1 hour. |
STS session token: 15 min – 1 hour | Revoke the STS session by revoking the IAM role's trust policy or deleting the session. Per feedback_aws_workloads_use_ssm_not_vault. |
| Stripe | Full live secret key (sk_live_...) — billing, refunds, customers |
Test key only by default (sk_test_...), read-only scope if test mode supports it. Live key only if operator explicitly approves and task is production billing work. |
Stripe keys are long-lived; isolate by using restricted keys (Stripe Restricted Keys feature: specify read/write per resource type). | Delete the restricted key in Stripe dashboard. For live keys: rotate the key; the old key is dead immediately. |
| Cloudflare (WAF, DNS, Bot Management) | Zone-level API token with all permissions (CF_DNS_EDIT, Bot Management write) | Per-handoff CF API token with zone-scoped permissions limited to the exact resource and action needed (e.g., DNS read-only on one zone; WAF rule read-only). No Zone Settings edit, no Bot Management write, no Accounts admin. | CF API tokens support expiry dates; set max 24h for handoff tokens. | Delete the CF API token in the CF dashboard or via API. |
| MCP servers / tool bindings | Ambient session MCP: Infisical MCP (full path), filesystem, shell | Only the MCP tools the handoff needs. e.g., a PR-writing handoff needs GitHub tools and vault read; it does NOT need filesystem write outside its worktree, shell exec, or Stripe tools. MCP tool list is explicitly enumerated in session_context. |
Session-scoped (live only while the handoff session is active) | Terminate the session. MCP bindings are not persistent. |
| Alpaca / broker credentials | Operator's live Alpaca OAuth token | No access by default. Paper credentials only if the handoff needs to test a trading flow. Live credentials require paper-first gate pass + explicit operator override. | Per paper-first gating invariant. | Revoke OAuth grant in Alpaca dashboard. |
| Apple IAP server keys | Full StoreKit server key (production) | Test StoreKit environment key only for handoffs that need IAP testing. Production key only for designated IAP notification handler task. | Apple keys are long-lived; isolate by environment (sandbox vs production). | Revoke key in Apple Developer portal. |
4. The Provisioning Model
4.1 Step 0 is not optional
Every handoff dispatch sequence begins with Step 0: provision the scoped identity set for this specific handoff task. The handoff task does not start until Step 0 is confirmed complete. This is an enforced gate, not a suggestion.
4.2 Identity naming
Handoff identities follow a canonical naming convention so they can be found, audited, and revoked without searching:
<surface>-handoff-<task-slug>-<YYYYMMDD>
Examples:
- infisical-handoff-iap-notif-handler-20260620
- gh-pat-handoff-stripe-checkout-founders-20260620
- stripe-restricted-handoff-founders-checkout-20260620
- cf-token-handoff-waf-read-20260620
The task-slug matches the GitHub issue slug for the handoff card. This links the
credential directly back to the card so revocation is unambiguous.
4.3 Where scoped credentials live
Scoped handoff credentials are stored in Infisical at:
/MooseQuest/handoffs/<task-slug>/
Each secret in that path is named after the surface:
- INFISICAL_CLIENT_ID / INFISICAL_CLIENT_SECRET
- GH_HANDOFF_TOKEN
- STRIPE_RESTRICTED_KEY
- CF_HANDOFF_TOKEN
- etc.
The handoff receives ONLY the secrets at its task-slug path. It cannot read
/MooseQuest/raptor/, /MooseQuest/velvet/, or any other production service path.
4.4 Velvet integration
Handoff identity credentials that are time-bound but task-duration-spanning (e.g., a
multi-day scheduled CCR run) are enrolled in the Velvet subscription manifest at the
time the handoff is provisioned, under a handoff consumer class:
# velvet subscription entry for a handoff
- token_name: STRIPE_RESTRICTED_KEY
consumer_id: handoff-founders-checkout-20260620
env: prod
active: true
update_endpoint: "velvet://infisical/handoffs/founders-checkout-20260620/STRIPE_RESTRICTED_KEY"
update_method: INFISICAL_WRITE
update_auth_token_name: null
healthcheck_endpoint: null
required: false
capabilities:
- update
description: "Handoff task: founders-checkout-20260620 — Stripe restricted test key"
auto_revoke_after: "2026-06-21T00:00:00Z" # task-specific TTL (new Velvet field)
The auto_revoke_after field (new Velvet capability — see sub-card SC-HANDOFF-VELVET-01)
triggers an automatic revocation of the credential and removal of the manifest entry when
the task TTL expires. No manual cleanup required.
For short-lived handoffs (single CI run, one-shot task < 1 hour), Velvet enrollment is not required — the credential TTL is set at the surface level and the credential is manually deleted on task completion.
4.5 Session context injection
For Claude Cloud / scheduled CCR agents, the scoped credential set is injected into
the agent's session_context (or equivalent env block) at spawn time:
{
"env": {
"INFISICAL_CLIENT_ID": "<handoff-specific-client-id>",
"INFISICAL_CLIENT_SECRET": "<handoff-specific-client-secret>",
"INFISICAL_SECRET_PATH": "/MooseQuest/handoffs/<task-slug>/",
"GH_TOKEN": "<scoped-installation-token-or-pat>",
"STRIPE_KEY": "<restricted-test-key>",
"HANDOFF_TASK_SLUG": "<task-slug>",
"HANDOFF_EXPIRES_AT": "<ISO-8601-UTC>"
},
"mcp_tools": ["github_pr_create", "github_pr_comment", "infisical_get_secret"],
"mcp_tools_excluded": ["filesystem_write_outside_worktree", "stripe_charge", "heroku_scale"]
}
The explicit mcp_tools list is the tool allowlist. Anything not on the list is not
available to the handoff session. The orchestrator enforces this at spawn time via the
agent_token_injector extension (sub-card SC-HANDOFF-SESSIONCTX-01).
4.6 Independent revocation
To revoke any handoff agent's access:
- For Infisical: delete the machine identity
infisical-handoff-<task-slug>-<date>in Infisical admin panel or via API. Takes effect within one token TTL cycle (15 min max). - For GitHub: delete the fine-grained PAT or revoke the GitHub App install for the handoff. Immediate effect.
- For Stripe: delete the restricted key. Immediate effect.
- For Cloudflare: delete the API token. Immediate effect.
- For AWS: revoke the STS session or delete the task-specific IAM role assumption. Takes effect within the session token TTL (max 1 hour).
- For Heroku: delete the task-scoped API key. Immediate effect.
None of these revocations affect any other agent or service. Each surface credential is independent of all others.
5. GitHub Scoping for Handoff Agents
The existing GitHub identity model (ADR-0128, agent-github-identity.md) uses three
shared GitHub Apps (raxx-dev-bot, raxx-ops-bot, raxx-pm-bot) with repo-level permissions
granted to the entire agent class. This is appropriate for live, operator-present sessions
but too broad for unattended handoffs.
5.1 The least-privilege GitHub shape for a handoff
A handoff agent needs:
- contents:write — to create commits and branches in its worktree
- pull_requests:write — to open and update its own PR
- issues:write — to create issue comments reporting results
A handoff agent must NOT have:
- Admin permissions (branch protection override, repo settings)
- secrets:read or secrets:write — Actions secrets are off-limits
- workflows:write — handoffs do not modify CI definitions
- The ability to push directly to main, staging, or any other protected branch
- The ability to merge without review (cannot approve its own PR)
5.2 Implementation options
Option A — Fine-grained PAT (simplest)
Create a fine-grained PAT under a bot account scoped to the specific repo, with exactly
the three permissions listed above, expiring in 24 hours. Store in Infisical at
/MooseQuest/handoffs/<task-slug>/GH_HANDOFF_TOKEN. The orchestrator reads this at
spawn time and injects it as GH_TOKEN.
Limitation: Fine-grained PATs require a user account or organization-owned bot account. Until a dedicated per-handoff bot account exists, raxx-dev-bot can generate a fine-grained PAT via the GitHub API under its own account, but this does not provide stronger repo isolation than the existing installation token.
Option B — Per-handoff GitHub App install scope (preferred for multi-task) When the "GitHub connected to repo" prerequisite is met (see §7), the GitHub App install can be scoped at install time to a specific repo with specific permissions. For handoffs launched from multiple repos, each repo gets its own scoped App install. This is the correct steady-state model and what the sub-card SC-HANDOFF-GITHUB-01 implements.
Recommendation: Option A for the immediate near-term (until App scoping is wired). Option B as the target state. The choice does not affect the security invariants — both options prohibit protected-branch push and admin permissions.
5.3 Branch protection as a backstop
Even if a handoff agent's GitHub token were broader than intended, branch protection
on main and staging provides a hard backstop: direct pushes to those branches are
rejected by GitHub regardless of token scope. The handoff can only open a PR, which
requires human review before merge.
6. Blast-Radius Invariant and Enforcement Gate
6.1 The invariant (formal statement)
HANDOFF-INVARIANT-1: No handoff agent ever holds the admin identity or any blanket-scope credential. Every handoff agent's access is: (a) least-privilege — minimum permissions to complete the task; (b) scoped — bounded to the task's specific resources, not the whole platform; (c) time-bound — credentials expire within the handoff's expected runtime or a fixed maximum (24h); (d) independently revocable — removing one handoff agent's access has no effect on other agents or services; (e) audited — every access event is logged with the handoff task slug.
6.2 The handoff provisioning gate (checklist)
This checklist is mandatory before any handoff task is dispatched. It is the operator-or-orchestrator's responsibility to confirm each item.
HANDOFF PROVISIONING GATE — must complete before task dispatch
Surface checklist:
[ ] Infisical: per-handoff read-only identity provisioned at /MooseQuest/handoffs/<task-slug>/
Scope: only the secret paths this task needs. No admin project scope. TTL: 15-min access tokens.
[ ] GitHub: fine-grained PAT or scoped App install provisioned.
Permissions: contents:write, pull_requests:write, issues:write — nothing else.
Expiry: 24h max. Protected-branch push disabled.
[ ] Cloud/CI creds: task-specific, test/sandbox by default.
If prod creds needed: operator explicit confirmation required (mark here: ___).
AWS: STS AssumeRole with narrowly-scoped session policy. Max 1-hour session.
Heroku: task-scoped key, single app, no scale/admin/billing.
Stripe: restricted test key unless operator approves live key.
Cloudflare: scoped token with expiry. No Zone Settings write, no Bot Management write.
[ ] MCP tools: explicit allowlist defined. Excluded tools listed.
[ ] session_context / env: only the above scoped credentials injected. No admin env vars.
[ ] Velvet enrollment: if task duration > 1 hour, enrolled in subscription-manifest.yml.
If task duration < 1 hour: credential will be manually deleted on task completion.
[ ] Revocation path documented: for each surface, who deletes what and where.
[ ] Audit path confirmed: credential access logs will be retained per platform retention policy (90 days).
Task context:
[ ] Task slug: ______________________
[ ] Expected runtime: _______________
[ ] Handoff expires at (UTC): _______
[ ] Operator who approved: __________
[ ] Linked card: #__________________
6.3 Enforcement mechanism
The gate is currently a manual checklist. The sub-card SC-HANDOFF-GATE-01 implements
an automated gate: a script (scripts/agents/provision_handoff_identity.py) that:
1. Reads a handoff spec file (handoff-spec.json) defining task slug, surfaces needed, and TTL.
2. Provisions each surface's scoped credential in sequence (or surfaces an error if provisioning fails).
3. Writes a confirmation manifest (/MooseQuest/handoffs/<task-slug>/PROVISION_MANIFEST) to vault.
4. Returns a structured env block for the orchestrator to inject at spawn time.
The orchestrator blocks dispatch unless it can read the PROVISION_MANIFEST for the task.
7. GitHub Connection Prerequisite for Cloud Agents
Cloud-hosted CCR agents that need GitHub access require the GitHub App to be installed on the repo with the agent's permitted scope. This is not a one-time setup — it is a per-handoff configuration concern.
Recommendation:
- The raxx-dev-bot GitHub App installation on the repo should be configured with the
minimum permissions the handoff class needs (contents:write, pull_requests:write,
issues:write — matching §5.1).
- For cloud agents operating on separate repos, a new App install is required per repo.
The App should NOT be installed with org-wide permissions.
- The GitHub App's Web URL setup (/web-setup) should be performed once per repo where
a cloud agent will operate. This doubles as the per-agent GitHub boundary: the agent
can only act on repos where the App is installed.
- App installation with excess permissions (e.g., admin, secrets) is a violation of
HANDOFF-INVARIANT-1 and must be caught by the provisioning gate.
8. Handoff Candidates: #3629 and #3206
8.1 #3629 — Apple IAP server-side notification handler
Task scope: Implement the StoreKit 2 server-to-server notification endpoint that receives Apple's purchase/subscription/refund events and reconciles them against Queue's billing state.
Required scoped identity set for a cloud handoff:
| Surface | Scoped shape |
|---|---|
| Infisical vault | Read-only identity scoped to /MooseQuest/handoffs/iap-notif-handler-<date>/ and /MooseQuest/queue/staging/APPLE_IAP_* (staging keys only). No prod billing path. |
| GitHub | fine-grained PAT: contents:write on TradeMasterAPI, pull_requests:write. Expiry: 24h. |
| Apple IAP key | Sandbox StoreKit server key ONLY. No production key. The endpoint must be implemented and tested in Apple's Sandbox environment before any production key is issued. |
| Stripe | None. IAP billing is Apple IAP, not Stripe. |
| AWS | None unless the notification endpoint involves SQS/SNS email; if so: STS session, sqs:SendMessage on one specific queue ARN. |
| Heroku | None for implementation. If integration test requires staging deploy: config-vars read on raxx-api-staging only. |
| MCP tools | GitHub PR tools, Infisical read. No filesystem write outside worktree, no shell exec of deployment commands. |
Prod-key gate: Apple production IAP key must NOT be provided to this handoff. A separate operator-approved handoff is required for the production enablement after staging validation. This enforces the paper-first gating invariant adapted to IAP: "sandbox-first, prod key only after sandbox profitable / stable."
8.2 #3206 — Founders Stripe Checkout
Task scope: Implement the Founders promotional pricing Stripe Checkout flow — create sessions, handle webhook events for subscription activation, reconcile with the founders trial engine.
Required scoped identity set for a cloud handoff:
| Surface | Scoped shape |
|---|---|
| Infisical vault | Read-only identity scoped to /MooseQuest/handoffs/founders-checkout-<date>/ and /MooseQuest/queue/staging/STRIPE_* (test keys only). No prod Stripe path. |
| GitHub | fine-grained PAT: contents:write on TradeMasterAPI, pull_requests:write. Expiry: 24h. |
| Stripe | Restricted TEST key (sk_test_...) with permissions: checkout_sessions:write, subscriptions:read, webhook_endpoints:read. No live key. No customer PII write. No refund capability. |
| AWS | None unless the Stripe webhook flow uses SQS DLQ; if so: STS session scoped to that queue ARN. |
| Heroku | None for implementation. If staging deploy is needed: config-vars read on raxx-api-staging only. |
| MCP tools | GitHub PR tools, Infisical read. No live Stripe charge tool. No Heroku scale/admin tool. |
Live-key gate: Stripe live key (sk_live_...) is explicitly withheld from this handoff.
The task is: implement and test in Stripe test mode. A separate operator-approved handoff
is required for live-key provisioning after the test-mode flow is reviewed and merged.
This enforces HANDOFF-INVARIANT-1 H7 and the platform's no-live-financial-creds-without-approval rule.
9. Sequence: Handoff Provisioning + Dispatch
sequenceDiagram
actor OP as Operator
participant GATE as Provisioning Script (provision_handoff_identity.py)
participant IS as Infisical Admin
participant GH as GitHub (fine-grained PAT API)
participant VLT as Vault (/MooseQuest/handoffs/<slug>/)
participant ORCH as Orchestrator / CCR dispatch
participant AGNT as Handoff Agent (cloud / CCR)
OP->>GATE: provision_handoff_identity.py --spec handoff-spec.json
GATE->>GATE: validate spec (task-slug, surfaces, TTL, operator-approval)
GATE->>IS: create machine identity infisical-handoff-<slug>-<date>
IS-->>GATE: { client_id, client_secret }
GATE->>VLT: write client_id + client_secret to /MooseQuest/handoffs/<slug>/
GATE->>GH: create fine-grained PAT for task (24h expiry, scoped permissions)
GH-->>GATE: { token: "github_pat_..." }
GATE->>VLT: write GH_HANDOFF_TOKEN to /MooseQuest/handoffs/<slug>/
Note over GATE: Repeat for each surface in spec (Stripe, CF, AWS STS, etc.)
GATE->>VLT: write PROVISION_MANIFEST (confirmed, timestamp, surfaces, TTLs)
GATE-->>OP: provisioning complete — env block ready
OP->>ORCH: dispatch handoff agent for task <slug>
ORCH->>VLT: read PROVISION_MANIFEST (confirm provisioned)
ORCH->>VLT: read env block from /MooseQuest/handoffs/<slug>/
ORCH->>AGNT: spawn with env={scoped creds only}, mcp_tools={allowlist}
AGNT->>AGNT: performs task with scoped credentials only
Note over AGNT: Task completes (or expires)
AGNT->>ORCH: task result
OP->>GATE: provision_handoff_identity.py --revoke --slug <slug>
GATE->>IS: delete machine identity infisical-handoff-<slug>-<date>
GATE->>GH: delete fine-grained PAT
GATE->>VLT: delete /MooseQuest/handoffs/<slug>/ (all secrets)
Note over GATE: All handoff credentials revoked; no residue
10. Migrations
No application schema changes. No new Postgres tables.
Infisical config changes:
- New vault path /MooseQuest/handoffs/ (folder must be created before first use
per feedback_vault_folder_must_exist).
- New machine identity per handoff (provisioned and revoked per task).
Velvet subscription manifest changes:
- New auto_revoke_after field on handoff consumer entries (backward-compatible;
non-handoff entries are unaffected).
GitHub App changes: - No changes to existing App installations. - Per-handoff fine-grained PATs are created on demand and deleted on task completion.
Rollback: If the provisioning script fails, no handoff credentials are in play. The handoff is not dispatched. Revocation of a partially-provisioned set is surfaced by the script — each surface is checked for provisioning state before write.
11. Rollout Plan
dark (now) Handoff provisioning checklist (manual) enforced for all new handoff dispatches.
Document the gate in runbook: docs/ops/runbooks/handoff-provisioning.md.
Apply retroactively to #3629 and #3206 before dispatch.
flag provision_handoff_identity.py script ships (SC-HANDOFF-GATE-01).
Orchestrator reads PROVISION_MANIFEST before dispatch (SC-HANDOFF-SESSIONCTX-01).
Manual gate is the fallback until script is proven.
beta Velvet auto_revoke_after field implemented (SC-HANDOFF-VELVET-01).
All handoff credentials with duration > 1 hour auto-enrolled in Velvet.
ga GitHub App per-handoff scoping (SC-HANDOFF-GITHUB-01) replaces fine-grained PATs.
Full automated provisioning + revocation with no manual steps.
12. Security Considerations
PII collected: Handoff audit logs record the machine identity (task slug), secret path, and timestamp. No credential values are logged. If the handoff processes user data (e.g., IAP notification contains Apple subscription UUID correlated to a Raxx user), that data is handled by the handoff's task logic — it is not part of the identity provisioning log. Retention: 90 days, matching platform policy.
Deletion on DSR: Handoff access logs do not contain end-user PII by default. If a handoff processes user-correlated events, the task-level data is in Queue's billing tables (DSR path: Queue's data export / erasure endpoint). Handoff identity audit logs are not user-correlated.
Stored credentials: Scoped credentials live in Infisical at /MooseQuest/handoffs/<slug>/
and in the spawned agent's env. They are not committed to git, not written to disk, not
included in logs. Satisfies invariant H6 and platform invariant I1.
Breach notification: If a handoff identity is compromised, blast radius is limited to the task's scoped paths and surfaces. Discovery: Infisical audit log shows reads from unexpected IP/time. Response: revoke the per-handoff identity (<60 seconds); the handoff's scoped credentials have no access beyond the task's resources. If the compromised credential had access to user-correlated data (e.g., Stripe test customer IDs): GDPR 72-hour notification clock starts on confirmation of compromise. Test/sandbox credentials (the default) do not expose live customer data.
Does any part of this store a credential that can be replayed? Handoff credentials are stored in Infisical (environment variable injection at spawn). They are time-bound (TTL enforced). After task expiry or revocation, the credential is no longer valid. No replay is possible beyond the TTL window. This satisfies H3 and H6.
Kill-switch: Revoke the per-handoff Infisical machine identity. All other surfaces
(GitHub PAT, Stripe restricted key, CF token) are independently revocable. The Velvet
auto_revoke_after field automates expiry without operator action.
Live financial credentials: HANDOFF-INVARIANT-1 H7 requires explicit operator confirmation before a live financial key (Stripe live, Apple production, Alpaca live) is issued to any handoff. This confirmation must be recorded in the handoff's provisioning manifest. Test/sandbox is the default and requires no special approval.
Kill-switch for live execution paths: The paper-first gating platform invariant applies to any handoff that touches trading execution paths. No handoff receives live broker credentials without a paper-profitable-for-N-cycles gate pass or explicit per-flow operator override.
13. Open Questions
| # | Question | Blocks | Urgency |
|---|---|---|---|
| OQ1 | Fine-grained PAT vs per-handoff GitHub App install. GitHub's fine-grained PAT requires a user/bot account that owns the PAT. Until SC-HANDOFF-GITHUB-01 wires up per-install App scoping, which bot account do fine-grained handoff PATs live under? Recommendation: raxx-dev-bot account, but this still grants the PAT the bot's account identity. Does the operator want to accept this for the interim, or block on App install scoping? | SC-HANDOFF-GITHUB-01 (shape) | Blocks first handoff dispatch |
| OQ2 | Heroku task-scoped API key shape. Heroku does not natively support per-resource-scoped API keys (keys are account-scoped). The workaround is a dedicated sub-account per task or per-app OAuth token. Which does the operator prefer, or is Heroku excluded from handoff self-provisioning (operator action required for any Heroku credential)? | SC-HANDOFF-GATE-01 | Should resolve before handoff spec for tasks needing Heroku access |
| OQ3 | auto_revoke_after Velvet field. Is the operator ready to extend the Velvet manifest schema with auto_revoke_after? This is a new optional field; backward-compatible, but needs Velvet code change and PR. Should this be part of SC-HANDOFF-VELVET-01 or a separate Velvet maintenance card? |
SC-HANDOFF-VELVET-01 | Does not block initial handoffs (short-lived tasks can use manual revocation) |
| OQ4 | Apple IAP production key handoff gate. After #3629 sandbox implementation is reviewed + merged, what is the approval signal required to issue the production StoreKit key to a handoff? Operator explicit confirmation via issue comment? Or does production IAP key issuance always require a human (operator) in the loop and cannot be scripted? | #3629 prod enablement | Does not block #3629 sandbox handoff |