Skip to content

TOML Schema

envpkt.toml is validated against a JSON Schema. The schema is published at:

  • npm: envpkt/schema (importable via package exports)
  • GitHub: schemas/envpkt.schema.json

Enable editor autocompletion by adding the schema directive on line 1:

#:schema https://raw.githubusercontent.com/jordanburke/envpkt/main/schemas/envpkt.schema.json

| Field | Type | Required | Description | | --------- | --------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | version | number | Yes | Schema version number (currently 1) | | catalog | string | No | Path to shared secret catalog (relative to config file) | | scope | "shell" | "exec" | No | Whether env export emits this package’s secrets for ambient shell loading (shell) or withholds them so they’re only available via envpkt exec (exec). Default exec. Never affects envpkt exec/env github/env dotenv. |

Optional. Prefixes the injected environment variable name for every [secret.*] and [env.*] entry, without changing the TOML keys you author or the names shown in audit/inspect. The TOML key stays the logical name; the prefix is applied only at the process.env boundary (boot(), exec, env export).

| Field | Type | Required | Description | | ----------- | -------- | -------- | ------------------------------------------------------------------------------------------ | | prefix | string | Yes | Prefix applied to injected names (e.g. CIV → CIV__API_KEY) | | separator | string | No | Separator between prefix and key. Default __. Must be shell-safe — see warning below |

[namespace]
prefix = "CIV"
separator = "__" # optional, default
[secret.API_KEY] # injected as CIV__API_KEY
service = "example"
[env.LOG_LEVEL] # injected as CIV__LOG_LEVEL
value = "info"

A logical key keeps its canonical name everywhere internal (audit, from_key references, fnox lookup, catalog merge); only the injected/exported name is prefixed. BootResult.envNames maps each logical key to its wire name.

Identity and capabilities.

| Field | Type | Required | Description | | -------------- | --------------------------------------------- | ------------------------ | ------------------------------------------ | | name | string | Yes (if section present) | Agent display name | | consumer | "agent" \| "service" \| "developer" \| "ci" | No | Consumer type classification | | description | string | No | Agent description or role | | capabilities | string[] | No | Capabilities this agent provides | | expires | string (date) | No | Agent credential expiration (YYYY-MM-DD) | | services | string[] | No | Service dependencies | | key_file | string | No | Path to age identity (private key) file | | recipient | string | No | Agent’s age public key for encryption | | secrets | string[] | No | Secret keys this agent needs from catalog |

Per-secret metadata. Each key corresponds to an environment variable name.

| Field | Type | Required | Tier | Description | | ----------------- | --------------- | -------- | ----------- | ------------------------------------------------------------------------------------------ | | service | string | No | Scan-first | Service this secret authenticates to | | expires | string (date) | No | Scan-first | Expiration date (YYYY-MM-DD) | | rotation_url | string (URI) | No | Scan-first | URL for rotation procedure | | purpose | string | No | Context | Why this secret exists | | comment | string | No | Context | Free-form annotation or note | | capabilities | string[] | No | Context | Operations this secret grants | | created | string (date) | No | Context | Provisioning date (YYYY-MM-DD) | | rotates | string | No | Operational | Rotation schedule (e.g., 90d, quarterly) | | rate_limit | string | No | Operational | Rate limit info (e.g., 1000/min) | | model_hint | string | No | Operational | Suggested model or tier | | source | string | No | Operational | Secret origin (e.g., vault, ci, manual) | | encrypted_value | string | No | Value | Age-encrypted ciphertext — mutually exclusive with from_key | | from_key | string | No | Alias | Reference another secret entry (format: "secret.<KEY>") — see Aliases | | required | boolean | No | Enforcement | Whether secret is required for operation | | tags | object | No | Enforcement | Key-value tags for grouping/filtering | | namespace | string | No | Enforcement | Override the file-level [namespace] prefix for this entry. "" opts out entirely | | mcp | object[] | No | Integration | MCP servers this secret authenticates to — see MCP header bindings |

mcp lists the MCP servers a secret is sent to as an HTTP header. envpkt headers reads it to answer Claude Code’s headersHelper.

| Field | Type | Required | Default | Description | | -------- | -------- | -------- | ----------------- | ------------------------------------------------------------- | | server | string | Yes | — | Server name, as keyed in .mcp.json | | header | string | No | "Authorization" | Header to send the value in. Must be a valid HTTP header name | | scheme | string | No | "Bearer" | One word placed before the value. "" sends the raw value |

[secret.GITHUB_MCP_API_KEY]
mcp = [{ server = "civala-github" }] # Authorization: Bearer <value>
[secret.CF_API_KEY]
mcp = [{ server = "cloudflare", header = "x-api-key", scheme = "" }] # x-api-key: <value>

Several secrets may name the same server; their headers merge into one object. Two secrets may not claim the same (server, header) pair. Header names compare case-insensitively, and envpkt validate reports a clash, checking the catalog-merged config.

With a catalog, an agent that overrides a secret and sets mcp replaces the catalog’s whole mcp list for that secret. It does not add to it.

Plaintext environment defaults. Each key corresponds to an environment variable. These are non-secret values that are safe to commit — use [secret.*] for sensitive credentials.

| Field | Type | Required | Description | | ----------- | -------- | ------------------ | ----------------------------------------------------------------------------- | | value | string | Yes unless aliased | Default value for this env variable — mutually exclusive with from_key | | from_key | string | No | Reference another env entry (format: "env.<KEY>") — see Aliases | | purpose | string | No | Why this env var exists | | comment | string | No | Free-form annotation or note | | tags | object | No | Key-value tags for grouping/filtering | | namespace | string | No | Override the file-level [namespace] prefix for this entry. "" opts out |

[env.NODE_ENV]
value = "production"
purpose = "Runtime environment mode"
comment = "Override to 'development' for local testing"
[env.LOG_LEVEL]
value = "info"
purpose = "Application log verbosity"

Some consumers expect a credential under a different env var name than the one you use canonically. from_key exposes the same governed value under multiple canonical names without duplicating it.

[secret.API_KEY]
service = "example"
expires = "2026-12-31"
rotation_url = "https://api.example.com/settings/keys"
purpose = "Authenticates envpkt-managed tooling"
# Same governed value, exposed under a second name for a consumer
# that hardcodes a different env var name.
[secret.LEGACY_API_KEY]
from_key = "secret.API_KEY"
purpose = "Name required by the legacy consumer"

Works identically for [env.*]:

[env.SERVICE_URL]
value = "https://api.example.com"
[env.LEGACY_URL]
from_key = "env.SERVICE_URL"

Rules:

  • from_key must be formatted as "secret.<KEY>" or "env.<KEY>".
  • The target must exist in the same resolved config (after catalog merge).
  • The target must not itself be an alias — single hop only.
  • An alias cannot also declare a value-producing field (encrypted_value for secrets, value for env entries). It has no value of its own.
  • Cross-type aliasing (secret → env or env → secret) is rejected at load time.

Runtime behavior:

At boot, envpkt injects both the target AND every alias pointing at it, as separate entries in process.env holding the same value:

$ envpkt exec -- env | grep API_KEY
API_KEY=sk-...
LEGACY_API_KEY=sk-...

Audit, envpkt inspect, envpkt env export, and MCP responses all list aliases as first-class entries, tagged with alias_of so the relationship is visible. Status (expiration, health) is inherited from the target — an alias is healthy iff its target is healthy.

envpkt audit --all includes env default drift alongside secret health. Use envpkt audit --env-only to see only env defaults.

Aliases + namespace: a from_key reference is always the logical key ("secret.API_KEY"), independent of any namespace. The alias is injected under its own namespace resolution, so it can bridge a namespaced canonical value to a different injected name — e.g. expose a namespaced secret under a plain, un-prefixed legacy name:

[namespace]
prefix = "CIV"
[secret.API_KEY] # injected as CIV__API_KEY
service = "example"
[secret.LEGACY_API_KEY]
from_key = "secret.API_KEY" # value copied from API_KEY
namespace = "" # injected as plain LEGACY_API_KEY

Policy configuration for credential lifecycle management.

| Field | Type | Default | Description | | -------------------- | --------- | ------- | ---------------------------------------------- | | stale_warning_days | number | 90 | Days since creation to consider a secret stale | | require_expiration | boolean | false | Require expires on all secrets | | require_service | boolean | false | Require service on all secrets |

Automation callbacks for lifecycle events.

| Field | Type | Description | | --------------- | -------- | -------------------------------------------- | | on_expiring | string | Command or webhook when secrets are expiring | | on_expired | string | Command or webhook when secrets have expired | | on_audit_fail | string | Command or webhook on audit failure |

Tool integration configuration. Open namespace for third-party extensions.

[tools]
fnox = true
mcp = true