Secrets security
This page describes the security model for Loom secrets: what it protects, how protection works, and what you are responsible for.
Read this page if you are evaluating Loom for production use, configuring secrets providers, or responding to a suspected credential leak.
Security goals
Loom's secrets system is designed around four objectives:
- No secret bytes in static artifacts. Workflow YAML, Graph IR, compiled output, and receipts contain references only.
- Minimal runtime exposure. Resolved values exist in memory only for the duration of injection and are discarded after the job finishes.
- Fail closed on unsafe conditions. Missing provider auth, invalid references, and dangerous debug configurations all produce hard failures — never silent fallbacks.
- Shareable logs. Automatic redaction ensures logs and receipts can be shared for debugging without exposing secret values.
Threat model
Protected assets
| Asset | Examples |
|---|---|
| Secret values | Passwords, API tokens, signing keys, connection strings |
| Vault credentials | KeePass master passwords, keyfiles, 1Password service account tokens |
| Vault topology | Entry paths, vault names, and organizational structure |
Threat scenarios
| Threat | Attack path | Loom control |
|---|---|---|
| Credential in CI logs | echo, set -x, or command-line interpolation prints a secret | File-first injection (file: true) prevents value from appearing in command lines; redaction scrubs output boundaries |
| Credential in artifacts | Secret stored as a plain variable ends up in compiled YAML or receipts | Secrets use reference-only storage; schema rejects overlapping variables and secrets keys |
| Auth material in source | Master password or service account token committed in workflow YAML | Provider architecture requires runtime-only credential configuration; no workflow field accepts vault auth material |
| Provider fallback drift | A missing provider silently falls back to a weaker credential source | Providers fail closed with SECRETS_PROVIDER_UNAVAILABLE; no implicit fallback chain exists |
| Debug trace leaks direct values | CI_DEBUG_TRACE=true enables shell tracing that can expose file: false secrets | Hard failure (SECRETS_UNSAFE_DEBUG_TRACE) before job execution when this combination is detected |
Core controls
1. References-only workflows
Workflow YAML stores ref URIs, not values. This prevents secret bytes from entering source control, loom compile output, or receipt artifacts. The separation is enforced at the schema level — there is no field that accepts inline secret values.
2. Job-scoped declaration
Secrets are allowed only at the job level. default.secrets is rejected by the schema validator to prevent a single declaration from fanning out sensitive material to every job in the workflow.
3. File-first injection
file: true is the default. The resolved value is written to a temporary file with 0600 permissions, and the environment variable is set to the file path. This prevents secret values from appearing in:
- Shell trace output (
set -x) - Process listings (
ps aux) - Command-line argument logs
- Docker
inspectoutput
For Docker jobs, file-injected secrets are bind-mounted read-only into the container.
4. Redaction at output boundaries
Before writing to any output surface, Loom replaces resolved secret values with stable redaction tokens:
[REDACTED:SECRET_DATABASE_PASSWORD]
Redacted surfaces include console streams, receipt stdout/stderr, error fields, and provider lifecycle messages.
Limitation: Redaction operates on exact byte matching. If a script transforms a secret value (e.g., base64-encodes it) before printing, the transformed output will not be redacted. Treat redaction as defense-in-depth, not a guarantee.
5. Fail-closed provider behavior
When provider auth or configuration is missing or invalid, resolution fails immediately with a deterministic SECRETS_* error code. There is no silent fallback to alternate providers, implicit credentials, or empty values.
Provider-specific security notes
env://
- Risk profile: The host process environment may contain values visible to unrelated tooling or inherited by child processes.
- Guidance: Use
env://only for values already secured at the runner or session boundary. Avoid long-lived shell sessions with broadly exported secrets.
keepass://
- Risk profile: Security depends on local filesystem hygiene and unlock credential handling. A compromised keyfile or weak master password undermines all entries.
- Guidance:
- Map aliases through runtime config environment variables (
LOOM_KEEPASS_DB_<ALIAS>_PATH). - Use credential indirection (
..._PASSWORD_ENV,..._KEYFILE_ENV) — never place master passwords in workflow YAML. - Restrict filesystem permissions on
.kdbxfiles and keyfiles.
- Map aliases through runtime config environment variables (
op://
- Risk profile: Security depends on the 1Password service account token scope and rotation.
- Guidance:
- Use least-privilege token scoping — grant access only to the vaults and items required.
- Rotate service account tokens on a defined schedule.
- Keep
OP_SERVICE_ACCOUNT_TOKENout of workflow YAML andvariablesblocks.
Operational guardrails
| Guideline | Rationale |
|---|---|
Prefer file: true for all secrets | Reduces leakage surface from shell tracing, ps, and interpolation |
Disable CI_DEBUG_TRACE on secrets-heavy jobs | Prevents hard failure and avoids accidental exposure of file: false values |
| Avoid printing secret env vars in scripts | Redaction is defense-in-depth, not foolproof |
| Rotate provider credentials independently | Workflow updates and credential rotation should be decoupled |
| Validate workflows before running | loom check catches schema violations, invalid refs, and scope conflicts |
Debug-trace safety
When CI_DEBUG_TRACE=true and any secret uses file: false, Loom hard-fails with SECRETS_UNSAFE_DEBUG_TRACE before execution begins. This prevents shell traces from printing direct-injected secret values.
To debug a job that uses direct-injected secrets, either switch the secrets to file: true or remove CI_DEBUG_TRACE.
Incident response
If you suspect a secret has leaked:
- Revoke immediately. Rotate the exposed credential at the source provider (1Password, KeePass, environment).
- Scope the exposure. Identify the run ID and job, then check logs and receipts for the redaction token to confirm whether the raw value appeared.
- Assess downstream impact. Determine whether the leaked value was consumed by external systems or persisted to external sinks.
- Patch the source. Fix the workflow or script pattern that caused the leak — typically switching from
file: falsetofile: trueor removing anechostatement. - Verify the fix. Re-run with redaction verification before restoring normal operation.
Security review checklist (for contributors)
When adding a new secrets provider, verify:
- Provider contract includes explicit auth-source policy and allowlisting.
- Resolver fails closed on unavailable or invalid auth.
- No secret bytes are written to planner, receipt, or event artifacts.
- Redaction test coverage includes quoted and newline variants.
- Error codes are deterministic and do not contain sensitive content.
- Documentation covers setup, runtime boundaries, and risk caveats.
Related pages
- Secrets overview — configuration reference, injection modes, and error codes.
- Secrets providers — provider-specific setup and runtime requirements.
- KeePass provider — KeePass URI format, config, and failure codes.
- 1Password provider — 1Password URI format, auth, and failure codes.