fix(cloud): encrypt agent sandbox environment secrets at rest - #11402
Conversation
agent_sandboxes.environment_vars is plain jsonb — the PATCH /v1/eliza/agents/:id/environment BYO-key path (and agent/coding-container create) persisted user provider keys (ANTHROPIC_API_KEY, OPENAI_API_KEY, ...) in plaintext at rest. encrypt secret-bearing values on write with the existing org-scoped envelope crypto (FieldEncryptionService: AES-256-GCM, org DEK wrapped by SECRETS_MASTER_KEY, enc:v1: strings — same primitive as tenant DB DSNs); decrypt only where the env is materialized for the agent (provision, fleet upgrade/rollback, runtime bootstrap), so containers still see real values. backward compatible: legacy plaintext rows pass through decrypt untouched (no backfill required); platform control-plane tokens (reserved keys + ELIZAOS_API_KEY) stay plaintext for the sync bridge-auth/proxy/pairing reads; without SECRETS_MASTER_KEY writes keep legacy plaintext behavior with a loud warning. no schema change, no migration. test proves the written secret is enc:v1: ciphertext at rest (fails on the old write path), round-trips on materialization, and legacy plaintext rows still read correctly. fixes the environment_vars finding in #11332
There was a problem hiding this comment.
Your trial has ended. Reactivate Greptile to resume code reviews.
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Maintainer review (security): reuses FieldEncryptionService (AES-256-GCM, per-org DEK under SECRETS_MASTER_KEY) — no new key material; decryption failures fail closed (agent errored, propagated) with the only fail-open being key-not-configured legacy passthrough (loud warn); all materialization paths decrypt, direct env reads touch only NEVER_ENCRYPT platform keys. No backfill job — untouched plaintext rows persist until next write, worth a follow-up sweep. 7/7. Merging. |
…ey gate #11271 clobbered that neither #11403 nor #11422 covers (#11429) #11271 clobbered 3 [cloud-security] money gates. Coverage on develop: - #11227 pairing-token gate → my #11403 (merged; only this commit landed). - #11240 eliza-app provisioning gate → shaw's #11422 (open). - #11261 shared-turn refund guard → NOT restored by either. Fell through the cracks (my #11403's 2nd commit never merged; #11422's 5 files don't touch eliza-sandbox). Still a live hole: on the DEFAULT (shared) agent tier, a throw between reserveCredits and settle strands the hold (settleReservation(0) was back to 2 — the pre-existing degraded+billing-catch paths; my outer guard, the 3rd, was gone). eliza-sandbox.ts EVOLVED since (#11402 secret-encryption, #11375 quota), so this re-applies ONLY the outer try/catch → settleReservation(0) guard onto the current file — verified the #11402/#11375 changes are preserved (25 evolution markers intact; no reverse-clobber). Test restored (was deleted by #11271). typecheck + biome clean; shared-turn refund test 2 pass. This closes the last open #11271 [cloud-security] money hole. Refs #11271 #11413 #11419. [cloud-security]
|
Claude encountered an error —— View job I'll analyze this and get back to you. |
Problem
agent_sandboxes.environment_varsis a plain jsonb column.PATCH /v1/eliza/agents/:id/environment— the only BYO-key surface today — persisted user provider keys (ANTHROPIC_API_KEY,OPENAI_API_KEY, GitHub tokens, ...) verbatim into the row, so user secrets sat unencrypted at rest. Agent create and coding-container create take the same plaintext path. This is theenvironment_varsfinding in #11332.Verified before fixing: schema (
packages/cloud/shared/src/db/schemas/agent-sandboxes.ts:124), the write path (updateAgentEnvironment→agentSandboxesRepository.update, no crypto anywhere), and the read path (provision()spreadsrec.environment_varsstraight intoprovider.create;buildRuntimeBootstrapAgentcopiesANTHROPIC_API_KEY/OPENAI_API_KEY/... into runtime secrets). The new test fails against that write path (negative control below).Fix — reuse the existing envelope crypto, no schema change, no migration
updateAgentEnvironment,createAgent,createCodingContainerAgent): values whose key looks secret-bearing (KEY|TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL|PRIVATE) are encrypted with the existingFieldEncryptionService— AES-256-GCM, per-org DEK wrapped bySECRETS_MASTER_KEY,enc:v1:encoded strings — the same primitive already protecting tenant DB DSNs (user-database.ts). No new crypto was written.provision()(container env, also covers wake), fleetexecuteUpgrade/executeDowngrade, and the runtime bootstrap payload. The running agent sees identical real values.RESERVED_PLATFORM_ENV_KEYS+ the legacyELIZAOS_API_KEYalias — platform-minted tokens the control plane reads synchronously from the stored row (bridge auth headers, dedicated-agent proxy, pairing routes). These are blocked from the user PATCH surface by the existing reserved-key gate anyway.Backward compatibility (prod table, conservative rollout)
enc:v1:value through untouched → legacy plaintext rows keep working with no forced backfill. They are opportunistically re-encrypted the next time the env is written.SECRETS_MASTER_KEYconfigured, writes keep exact legacy plaintext behavior with a loud structured warning — local dev / self-hosters don't break. To activate, set the same key on the cloud API Worker and the provisioning daemon (the same deployment requirement tenant-DB DSN encryption already imposes).isEncryptedguard).Tests
packages/cloud/shared/src/lib/services/agent-env-crypto.test.ts— realFieldEncryptionServicecrypto (only its org-key persistence is swapped for an in-memory store, the same db-helpers boundary the sibling suites already mock), repository write captured viaspyOnso assertions run against the exact bytes that would land in Postgres:enc:v1:ciphertext at rest, not the plaintext, and round-trips on materializationSECRETS_MASTER_KEY→ legacy plaintext (graceful degradation)Verification (real output)
Negative control — same test against the pre-fix write path (fix stashed):
With the fix (plus all neighboring sandbox suites):
Biome on the touched files: no new findings (the pre-existing
eliza-sandbox.tslints are byte-identical on develop, line numbers shifted).Evidence notes: UI screenshots/video N/A — backend-only, no UI surface changed. Live-LLM trajectory N/A — no agent/prompt/model behavior changed; the container env contract is proven identical by the round-trip + neighboring-suite tests.
Scope / follow-ups
containers.environment_varslane is out of scope here (separate table/lane, tracked by design: team credential-pooling (shared org Claude/Codex/API-key pool) — mostly assembly, orgs/members already exist #11332's phase work).Fixes the
agent_sandboxes.environment_varsplaintext finding in #11332.