Compatibility contract

File format

msecrets.json is plain JSON with a published schema. It holds recipients, environments, and signed age ciphertext, and nothing else.

{
  "$schema": "https://cdn.jsdelivr.net/npm/@msecrets/ts-sdk@4/schema/v4.json",
  "version": 4,
  "id": "…",
  "recipients": {},
  "environments": {},
  "secrets": {}
}

Example

{
  "$schema": "https://cdn.jsdelivr.net/npm/@msecrets/ts-sdk@4/schema/v4.json",
  "version": 4,
  "id": "6f1c0b4f8c0e4a35a1b7f6e2d9c3a8b0",
  "recipients": {
    "alice": { "key": "msecrets1q…" },
    "api-production": { "key": "age1…", "description": "Production API containers" }
  },
  "environments": {
    "development": { "recipients": ["alice"] },
    "production": { "recipients": ["alice", "api-production"] }
  },
  "secrets": {
    "DATABASE_URL": {
      "description": "Primary database",
      "values": {
        "development": { "to": "0606e5bc…", "by": "alice", "sig": "WMwc…", "data": "YWdlLWVu…" },
        "production": { "to": "9a41c2d7…", "by": "alice", "sig": "Hk2N…", "data": "YWdlLWVu…" }
      }
    },
    "SENTRY_DSN": { "environments": ["production"], "values": { "production": { "…": "…" } } }
  }
}

The JSON schema, published with @msecrets/ts-sdk, gives editors completion and validation.

Fields

Field  
version Always 4.
id Random 128-bit id, bound into every signature so values cannot be copied between files.
recipients.NAME.key msecrets1… (read and write), age1… / age1pq1… (read only), or ssh-ed25519 AAAA….
environments.NAME.recipients Who can read the environment. Recipients with a signing key can also write it. Order of environments is display order.
secrets.NAME.environments Optional: the environments the secret applies to. Absent means all.
secrets.NAME.values.ENV.to Digest of the recipient keys the value is encrypted to. When it differs from the environment’s current recipients, the value needs sync.
secrets.NAME.values.ENV.by / .sig The signing recipient and its Ed25519 signature.
secrets.NAME.values.ENV.data Base64 of a binary age file, so age itself can decrypt it (see below).

Decrypting without msecrets

jq -r '.secrets.DATABASE_URL.values.production.data' msecrets.json | base64 -d | age -d -i key.txt

Rules

Coming from msecrets 3

Version 4 replaces OpenPGP with age and the v2/v3 .env.ms.json with msecrets.json; there is no automatic conversion, because the new format adds signatures that only the current value holders can create. Both files can exist side by side while you migrate:

# 1. With msecrets 3 (its UI or SDK), collect each environment's values into a dotenv file outside the repository.
# 2. With msecrets 4:
npx msecrets init --env development,production
npx msecrets import ~/production.env --env production
npx msecrets import ~/development.env --env development
npx msecrets check
# 3. Delete the dotenv files and .env.ms.json.