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:
- References-only static definitions. Workflow YAML, Graph IR, and compiled workflow output contain provider references rather than resolved values.
- Job-scoped injection. Loom supplies resolved values to the job through temporary files or environment variables. File injection reduces exposure through the job environment, but scripts can still copy or publish the contents.
- Fail closed for required secrets. Missing provider auth or invalid references prevent required secrets from resolving; unsafe debug-trace settings stop execution. An unresolved
required: falsesecret can be omitted without stopping the job. - Exact-match redaction. Loom replaces declared secret values in the text outputs listed below. Operators still inspect and sanitize evidence before sharing it.
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 reduces command-line exposure; exact-value redaction covers verified textual sinks |
| Credential in static definitions | Resolved value is placed in workflow YAML or compiled Graph IR | Secrets use reference-only storage; schema rejects overlapping variables and secrets keys |
| Credential in declared artifacts | A job writes a resolved or transformed secret into an artifact file | Artifact contents are not scanned; workflows and operators must prevent or sanitize that output |
| 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 keeps resolved values out of source control and loom compile output. The separation is enforced at the schema level — there is no secret field that accepts an inline value.
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. Loom writes the value to a temporary file with 0600 permissions and sets the job variable to that file path. Tools that accept the path directly can avoid putting secret bytes in the following outputs. This depends on the file keeping its permissions, being removed after use, and consumers not reading its contents into those outputs:
- 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.
File injection reduces trace and argument exposure; it does not make logs, receipts, or declared artifacts generally shareable. A script or tool that reads and emits the file contents can still disclose the value.
4. Redaction at output boundaries
Loom replaces exact matches for declared secret values in console/event output, receipt stdout/stderr, output-bearing errors, and provider lifecycle messages. It uses tokens such as:
[REDACTED:SECRET_DATABASE_PASSWORD]
Exact matches in host and Docker stdout/stderr can span output frames, including newline and 8 KiB boundaries. Loom processes stdout and stderr separately.
Stream-matching details
Loom redacts continuous byte streams before splitting them into frames. For a longest configured raw, trimmed, or quoted secret variant of L bytes, each stream retains at most L - 1 unresolved bytes, including when L exceeds 8 KiB. It releases the tail at the end of the stream. Matching proceeds from left to right and chooses the longest match at each byte offset.
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.
Loom does not inspect raw declared artifact contents. Inspect and sanitize console/event output, receipts, errors, provider messages, and declared artifacts before sharing them.
5. Fail-closed provider behavior
Missing or invalid provider authentication or configuration produces a SECRETS_* resolution error. A required secret prevents the job from continuing. A secret marked required: false can be omitted; Loom does not substitute another provider, implicit credentials, or an empty value.
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 |
| Inspect and sanitize evidence before sharing | Transformed values and raw artifact contents are outside exact-match redaction |
| Rotate provider credentials independently | Workflow updates and credential rotation should be decoupled |
| Validate workflows before running | loom check catches schema violations and scope conflicts; provider URI syntax, configuration, and resolution are checked at runtime |
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 inspect logs, receipts, errors, provider messages, and declared artifacts. A redaction token confirms an exact match was removed; its absence does not prove non-exposure.
- 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 the corrected workflow with a disposable test credential. Inspect its console/event output, receipt stdout/stderr, output-bearing errors, provider messages, and declared artifacts for the value and any transformations the script creates. Confirm the leaking command or file output is gone before restoring normal operation. A missing redaction token alone is not proof that the output is safe.
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.
- Static planner/compile outputs store references only. Exact secret values are replaced in receipt/event text and the other outputs listed under Redaction at output boundaries.
- 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.