Central Environment Store and Host-Side Secret Injection¶
Status: Accepted (Amends ADR 0038 and ADR 0053)
We establish a dedicated private GitHub repository (env-store under ${GH_ORG_NAME}) as the centralized, SOPS-encrypted secrets vault for all public and private target repositories, shift secret decryption entirely host-side in SandboxLauncher using native modal.Secret.from_dict(), and introduce zero-clone developer CLI tools (push-repo-env and pull-repo-env) to eliminate in-repo encrypted files and SOPS metadata from open-source codebases.
Context¶
Under ADR 0038, target repositories stored encrypted environment files directly in version control within <repo>/.secrets/ accompanied by an env.map manifest. While effective for internal private repositories, this in-repo model introduces significant friction for open-source software (OSS) projects:
1. Security Scanner False Alarms: Automated secret scanners (GitGuardian, TruffleHog, GitHub Secret Scanning) regularly trigger on high-entropy SOPS ciphertexts committed to public repositories.
2. Confusing Contributor Experience: Open-source contributors expect a clean repository with a .env.example template where external contributors supply their own credentials ("BYOK"). An encrypted .secrets/ directory clutters public trees with inaccessible blobs.
3. Sandbox Key Exposure: In-container decryption during shared/startup.sh required temporarily staging AGE_PRIVATE_KEY inside the sandbox before scrubbing, creating unnecessary risk if container initialization was interrupted or inspected by untrusted code.
4. Permanent Public Git History Risk: If an Age private key were ever compromised, all historical encrypted secrets stored across public Git logs would be retroactively readable.
Decisions¶
- Central Environment Store (
env-store): - Create a dedicated private repository at
github.com/${GH_ORG_NAME}/env-storeto house all target repository environment configurations. - Organize secrets by project directory:
env-store/<repo-name>/default.env(SOPS-encrypted with the organization's Age public key) and an optionalenv.mapfor monorepo destination mapping (e.g.web.env=apps/web/.env). -
Authenticate access using the existing
GH_TOKENconfigured in the factory.env. -
Host-Side Decryption & Isolation (
SandboxLauncher): - Shift decryption completely out of the sandbox container and into
SandboxLauncheron the host/control plane. - When launching for target repository
<repo>,SandboxLauncher:- Fetches the encrypted secret file(s) directly from
env-storeusing the GitHub Contents REST API (api.github.com/repos/${GH_ORG_NAME}/env-store/contents/<repo>/). - Decrypts the secret in host memory using
AGE_PRIVATE_KEYviasops decrypt. - Injects decrypted variables directly into
modal.Sandbox.create(..., secrets=[modal.Secret.from_dict(...)]).
- Fetches the encrypted secret file(s) directly from
-
AGE_PRIVATE_KEYandenv-storeaccess tokens never enter the container filesystem or process environment. -
In-Container Filesystem Materialization:
-
Because application toolchains (e.g., Next.js, Vite, Prisma, Docker Compose) explicitly read a physical
.envfile on disk,shared/startup.shmaterializes the injected environment variables as a local file at/root/<repo>/.env(and any mapped subpaths) with restricted0600permissions. -
Zero-Clone Developer CLI Tooling (
push-repo-envandpull-repo-env): - Provide
scripts/push_repo_env.py(aliased aspush-repo-env):- Requires only the organization's public Age recipient key (
age1zwr6flwy...), allowing any team member or contributor to encrypt and push secrets without possessing the private key. - Auto-detects the repository name from
git remote get-url origin. - Automatically verifies and guards
.gitignoreto prevent accidental commits of local plaintext.envfiles. - Commits the encrypted file directly to
env-storevia the GitHub Contents API without requiring a local clone ofenv-store.
- Requires only the organization's public Age recipient key (
-
Provide
scripts/pull_repo_env.py(aliased aspull-repo-env):- Enables authorized developers holding
AGE_PRIVATE_KEYto retrieve and decrypt a project's.envlocally when onboarding a new workstation.
- Enables authorized developers holding
-
Transitional Backward Compatibility:
SandboxLauncherchecksenv-storefirst. If no secrets are found inenv-store, it falls back to inspecting the legacy target repository.secrets/env.mapand logs a deprecation warning, ensuring zero downtime for unmigrated repositories.
Consequences¶
- Public open-source repositories remain 100% clean, containing only standard
.env.exampletemplates and code. - Developers configure project secrets once via
push-repo-env; all subsequent sandbox launches across any machine automatically pull and inject secrets. AGE_PRIVATE_KEYis fully eliminated from the container sandbox environment, hardening container isolation.sopscan eventually be pruned from the Shared Base Image once all legacy in-repo repositories are migrated.- All code modules remain strictly within the Universal File Line Budget (<= 500 physical lines).