Skip to content

Secrets

Sensitive values like database passwords and API keys have no place in config files. noorm provides encrypted secret storage that travels with your project, unlocks with your identity, and injects into templates at runtime.

What Secrets Are

Secrets are encrypted key-value pairs stored in your local state file (.noorm/state/state.enc). They never touch disk in plain text.

Secrets are for sensitive values that get inserted into your database via SQL templates—not for database connection credentials. Common uses:

  • API keys stored in configuration tables
  • Encryption keys for sensitive data columns
  • Service credentials (AWS, Stripe, SendGrid)
  • OAuth client secrets

Secrets integrate directly with SQL templates, so you can reference them without exposing values in your source code or version control.

Setting a Secret

Local secret values are managed through the TUI so that passwords never touch shell history or ps listings:

bash
noorm ui

From there, press s for Settings → Secrets. The secret form masks password input, validates connection strings as URIs, and stores the result encrypted in .noorm/state/state.enc. The same screen lists the secrets for the active config — showing which are set, which are missing, and their declared types. Values stay hidden.

For CI/CD pipelines that need to push secrets non-interactively, use the vault — it stores encrypted team secrets directly in the database and is fully scriptable (noorm vault set KEY "value").

Config-Scoped vs Global Secrets

Secrets come in two scopes:

ScopeUse CaseExample
Config-scopedPer-environment credentialsDatabase password for prod
GlobalShared across all configsThird-party API key

Config-scoped secrets belong to a single config. When you delete the config, its secrets go with it.

Global secrets persist independently and are available everywhere.

In templates, access them differently:

sql
-- Config-scoped secret
CREATE USER readonly WITH PASSWORD '{%~ $.secrets.DB_PASSWORD %}';

-- Global secret
-- Using API key: {%~ $.globalSecrets.SHARED_API_KEY %}

See Templates for full syntax reference.

Using Secrets in Templates

Secrets inject into SQL templates via the $ context object:

sql
-- sql/users/create-readonly.sql.tmpl
CREATE USER {%~ $.secrets.READONLY_USER %}
WITH PASSWORD '{%~ $.secrets.READONLY_PASSWORD %}';

GRANT SELECT ON ALL TABLES TO {%~ $.secrets.READONLY_USER %};

If a template references a missing secret, it fails at runtime. Define your required secrets upfront to catch this early.

Secret Requirements in Settings

You can require secrets at two levels: universally (all configs) or per-stage (specific environments).

Universal Secrets

Define in the root secrets section of settings.yml:

yaml
# .noorm/settings.yml
secrets:
    - key: ENCRYPTION_KEY
      type: password
      description: App-wide encryption key

Every config requires these secrets, regardless of stage.

Stage Secrets

Define within a stage to require secrets only for matching configs:

yaml
# .noorm/settings.yml
stages:
    prod:
        description: Production database
        secrets:
            - key: DB_PASSWORD
              type: password
              description: Main database password
              required: true

    staging:
        secrets:
            - key: DB_PASSWORD
              type: password
            - key: DEBUG_KEY
              type: string

A config named prod requires its stage secrets plus universal secrets. A config named dev requires only universal secrets (plus any dev-stage secrets).

Secret Types

The type field controls input behavior:

TypeInput Behavior
stringPlain text
passwordMasked, no echo
api_keyMasked, no echo
connection_stringPlain text, URI validation

Types are hints for the CLI. All secrets are stored identically (encrypted strings).

Secrets in CI/CD

The local secret store exists for per-developer overrides — it's deliberately TUI-only so that raw values never leak into shell history. For non-interactive environments, push values to the vault instead, which is scriptable end-to-end:

bash
# Write a secret to the vault
noorm vault set DB_PASSWORD "$DB_PASSWORD"

# Pipe a secret to avoid process listings
echo "$DB_PASSWORD" | noorm vault set DB_PASSWORD

# List vault secrets as JSON
noorm --json vault list

# Remove a vault secret
noorm vault rm OLD_API_KEY

Vault secrets sit below local secrets in the resolution hierarchy, so a developer can still override a vault value locally through noorm ui without affecting anyone else.

Security Notes

noorm takes several measures to protect your secrets:

  1. Encryption at rest - Secrets are stored in .noorm/state/state.enc using AES-256-GCM encryption
  2. Key derivation - The encryption key derives from your private identity key via HKDF
  3. Values never displayed - The CLI shows secret keys only, never values
  4. Masked input - Password-type secrets use non-echoing input
  5. No logging - Secret values are never emitted to observer events
  6. Automatic redaction - The logger masks secret fields if they appear in event data

Don't Commit state.enc

Add .noorm/state/state.enc to your .gitignore. The file is encrypted with machine-specific keys and won't work on other machines.

Deleting Secrets

Remove optional local secrets through the TUI: noorm ui → Settings → Secrets → highlight the entry and press d. For team-shared values stored in the vault, use noorm vault rm <KEY>.

Required secrets (those declared in settings.yml) cannot be deleted, only updated. The TUI tells you whether a secret is universal or stage-specific when you try to delete a required one.

What's Next?

  • Stages - Environment templates with secret requirements
  • Templates - Using secrets in dynamic SQL
  • Configs - Config-scoped vs global secrets