OneSeal
Secrets as code, encrypted by default, type-safe by construction
Secrets, configs, and platform outputs as code — typed, versioned, encrypted. One Terraform state file in, one type-safe TypeScript SDK out.
The Problem
Every team eventually hits the same wall: 50 environment variables to manage, a Postgres password buried in a Slack thread, an API key copied from the wiki that was last updated in 2021. The actual source of truth is scattered across Terraform outputs, .env files, secret managers, and developer laptops — and none of it is type-safe.
The failure modes are predictable. process.env.DATBASE_URL (note the typo) silently returns undefined and takes down production at 3 AM. A new developer spends their first day asking “where do I find the Redis password?” on Teams. A rotation event means updating 50 variables across 8 services instead of one key.
OneSeal treats this as a code generation problem.
The Solution
OneSeal takes platform secrets — Terraform state outputs, for now — and generates a versioned, type-safe TypeScript SDK with all sensitive values encrypted using Age (X25519 + ChaCha20). The SDK is safe to commit to a private Git repository and installed like any other dependency.
The before/after is stark:
Before:
// runtime errors waiting to happenconst dbPass = process.env.POSTGRES_PASSWORD; // undefined?const apiKey = process.env.STRIPE_KEY; // or was it STRIPE_API_KEY?After:
import { State } from '@contoso/my-infra';
const state = await new State().initialize();
// full type safety — IDE knows the exact structureconst db = state.database.postgresql;const stripeKey = state.payments.stripe.secretKey;No more typos. No more “ask Sarah, she set it up.” The SDK is self-documenting by construction.
Architecture
The workflow has three stages:
Generate (build/CI time): The CLI reads a Terraform state file, extracts all outputs, encrypts those flagged sensitive = true using Age with the team’s public keys, and emits a versioned npm package containing the typed SDK and the encrypted blobs.
# generate SDK from Terraform state for prod and stagingoneseal generate terraform.tfstate \ --name @contoso/my-infra \ --public-key-path ./oneseal-keys/ # directory of .pub files — one per team memberDistribute (package registry or private Git): The generated SDK is committed to a private repository or pushed to a private npm registry. It follows the same versioning and review process as any other dependency.
Consume (runtime): Applications install the SDK normally. At initialization, the SDK decrypts secrets in-memory using the private Age key — from ~/.oneseal/age.key locally, or from the ONESEAL_AGE_PRIVATE_KEY environment variable in CI/CD.
// secrets are decrypted in-memory on initialize()// nothing is ever written to disk in plaintextconst state = await new State().initialize();Multi-Recipient Encryption
Each team member gets their own Age keypair. When generating the SDK, all public keys are provided — OneSeal encrypts each secret for all recipients simultaneously. Adding a new developer means adding their .pub file to the team keys repository and regenerating. No need to re-share secrets through side channels.
# generate-key creates a keypair stored in ~/.oneseal/oneseal generate-key
# CI/CD gets its own key — private key goes in the secret storeoneseal generate-key --output ./ciEncryption Details
- Algorithm: Age with X25519 (Curve25519) for key exchange, ChaCha20 for data encryption
- Selective: only Terraform outputs marked
sensitive = trueare encrypted — non-sensitive config (URLs, feature flags, resource IDs) is plaintext in the SDK - Ephemeral: each secret uses a fresh symmetric key, providing forward secrecy
- Git-safe: encrypted blobs are compact and stable in diffs
CI/CD Integration
The Docker image fits naturally into existing pipelines:
- name: Generate OneSeal SDK run: | docker run --rm \ -v ${{ github.workspace }}:/app \ -v ${{ github.workspace }}/prod.tfstate:/tmp/prod.tfstate:ro \ stanguc/oneseal:latest generate \ --public-key "${{ secrets.ONESEAL_AGE_PUBLIC_KEY }}" \ --state-path /tmp/prod.tfstate \ --name @contoso/my-infra \ --output-directory /app/@contoso/my-infra
- name: Commit SDK working-directory: '@contoso/my-infra' run: | git add . git commit -m "feat: update infrastructure outputs SDK" git push origin mainOne secret in the CI environment (ONESEAL_AGE_PRIVATE_KEY), access to all of them at runtime.
Why Not Just Use…
Vault / AWS Secrets Manager? These are storage solutions — keep using them. OneSeal complements them by removing runtime network dependencies from application code. Instead of calling Vault at startup (retry logic, network failures, VPN requirements), you call initialize() on a local package. Secrets are fetched once during SDK generation, not on every app start.
Plain environment variables? No compile-time safety, no version history, no structure, no audit trail. process.env is a flat namespace of untyped strings that fails at runtime.
.env files? The same, but also committed to repos in plaintext “temporarily” for six years.
Status
Currently in v0.1.0. Terraform state is the only supported input source. Python, Go, and PHP SDK targets are in progress. Planned integrations include .env files, Pulumi state, HashiCorp Vault KV, and AWS Secrets Manager.