Context
When `FORGE_GUARDRAILS_DB` is set, the resolution ladder in
`forge-cli/runtime/guardrails_loader.go:56-89` has three quiet
behaviors that mismatch what operators expect from a
"production-grade guardrails" deploy:
-
Silent fall-back on DB unreachable — `BuildGuardrailChecker`
logs a `Warn` and continues into file mode (or defaults) when the
Mongo connect fails. An operator who deliberately set the env var
probably wanted DB-mode-only behavior; silently downgrading to
stale or absent policy is the wrong default for a security
subsystem.
-
Default rules are not applied in DB mode — `DefaultStructuredGuardrails()`
carries 11 built-in secret-pattern rules
(`sk-ant-...`, `ghp_...`, `AKIA...`, etc.) plus PII and
jailbreak/prompt-injection/command-injection thresholds. These
are only used as a fallback in file mode when `guardrails.json`
is missing. They are never applied in DB mode. If the
operator's MongoDB `AgentConfig` document is empty or lacks the
secret-pattern rules, secret leaks in outbound responses / tool
calls will not be redacted. A DB-mode deploy with an
incomplete-but-present config is strictly less safe than a
file-mode deploy with no config at all.
-
No merge between file and DB — When both
`FORGE_GUARDRAILS_DB` is set AND a `guardrails.json` exists,
the DB wins exclusively and the file is never read. There's no
merge, no override, no warning that the file is being ignored.
Repo readers see a `guardrails.json` checked in and assume it's
active; in DB-mode deploys it's dead config that drifts from
what's actually enforced.
Proposal
Three small, related changes — all in one PR.
Part 1 — `FORGE_GUARDRAILS_DB_REQUIRED` fail-loud knob
Add an env var that flips the silent-fallback to a startup abort:
| Env |
Default |
Behavior |
| `FORGE_GUARDRAILS_DB_REQUIRED` |
`false` |
(existing) DB unreachable → log warn, fall through to file mode |
| `FORGE_GUARDRAILS_DB_REQUIRED=true` |
— |
DB unreachable → `r.logger.Error` + return non-nil error from runner startup; the agent refuses to serve. |
Same shape as `forge build`'s policy-violation-at-build-time
behavior (#89) — a deliberate fail-fast for security-critical
subsystems. Operators on the Initializ platform should set this in
their deployment manifest by default; one-off dev usage of
`FORGE_GUARDRAILS_DB` without the flag keeps the fall-back.
Implementation: one branch in `BuildGuardrailChecker` (~5 lines).
Test: mock a failing Mongo connect, assert startup returns an error
when the flag is on.
Part 2 — Carry default rules into DB mode
When the engine constructs `NewDBGuardrailEngine`, it currently
passes `StructuredGuardrails: nil` to every gate call
(`guardrails_engine.go:120`) so the library reads from MongoDB.
Two options:
Option A — Merge defaults at gate-call time:
Pass `DefaultStructuredGuardrails()` as a base layer that the
library's DB-loaded config overrides field-by-field. Requires the
library to support config merging (likely doesn't today — would
need a small library-side change).
Option B — Library-side seed responsibility:
Document that the operator's MongoDB `AgentConfig` seed MUST include
the equivalent of `DefaultStructuredGuardrails()` for the agent to
have baseline secret/PII protection. Provide a CLI command
`forge guardrails seed-defaults` that prints the default JSON
suitable for piping into MongoDB.
Recommended: Option B for first cut. Option A requires library
coordination (the guardrails library would need a documented
merge semantics that's stable across versions). Option B is a
pure docs + tooling change in Forge.
Implementation: new `forge guardrails` subcommand with two
operations:
- `forge guardrails seed-defaults` — print
`DefaultStructuredGuardrails()` as JSON to stdout
- `forge guardrails validate-db` — connect to
`FORGE_GUARDRAILS_DB`, fetch the agent's config, warn if
baseline rules are missing (e.g., no PII config + no
jailbreak detection + < 5 secret-pattern rules)
Part 3 — Document exclusivity + emit a startup warning
Three documentation gaps and one runtime change:
Documentation (`docs/security/guardrails.md`):
- Promote the resolution-ladder table to top of the page
- Explicitly state "DB and file are mutually exclusive; there is
no merge"
- Add a "Why DB mode is strictly more dangerous than file mode"
callout explaining the missing default rules
Runtime (`forge-cli/runtime/guardrails_loader.go`):
- When `FORGE_GUARDRAILS_DB` is set AND a
`/guardrails.json` (or the cfg-specified path) exists,
emit a one-shot startup warning:
```
guardrails: DB mode active but guardrails.json also present —
the file is IGNORED. DB and file are mutually exclusive.
Remove the file or unset FORGE_GUARDRAILS_DB to avoid drift.
```
- One-shot, not per-request, so the warning doesn't spam logs.
Files
| File |
Change |
| `forge-cli/runtime/guardrails_loader.go` |
Fail-fast branch when `FORGE_GUARDRAILS_DB_REQUIRED=true` + DB fails; one-shot warn when both DB env var AND guardrails.json present |
| `forge-cli/cmd/guardrails.go` (new) |
`forge guardrails seed-defaults` + `forge guardrails validate-db` subcommands |
| `forge-cli/runtime/runner.go` |
Surface the fail-fast error from BuildGuardrailChecker as a hard startup error in the required-mode case |
| `docs/security/guardrails.md` |
Promote the resolution-ladder section; add the exclusivity callout and the "DB-mode missing-defaults" footgun; document the new env var + subcommand |
| Tests |
Mock unreachable Mongo + required env flag → assert startup error; both-present case → assert warning logged once |
Out of scope
- Library-side config merge (Option A above) — requires
coordination with the guardrails library; defer until there's a
clear API contract for merge semantics.
- Hot-reload of DB config — separate concern. Today the
library reads on each gate call; that's already "hot" in a
sense, but full hot-reload of the agent's policy snapshot is
larger work.
- Per-agent DB connection multiplexing — for the multi-agent
/ single-Mongo deploy. Today each agent process holds its own
client. Defer.
Verification
- `forge run` with `FORGE_GUARDRAILS_DB=mongodb://unreachable` and
no `FORGE_GUARDRAILS_DB_REQUIRED`. Confirm warn-and-fallback
behavior is unchanged (back-compat).
- Same with `FORGE_GUARDRAILS_DB_REQUIRED=true` set. Confirm
startup fails with non-zero exit and an explicit error message
referencing the env var.
- `forge run` with `FORGE_GUARDRAILS_DB=` AND a
`guardrails.json` present in the workdir. Confirm the one-shot
startup warning is emitted (in the ops log, NOT the audit
stream) and the file's contents are confirmed unread.
- `forge guardrails seed-defaults > defaults.json`; round-trip
`defaults.json` through `models.StructuredGuardrails`
unmarshalling to confirm valid library-consumable shape.
- `forge guardrails validate-db`: connect, fetch agent config,
confirm it warns when fewer than 5 secret-pattern rules are
present.
Related
Context
When `FORGE_GUARDRAILS_DB` is set, the resolution ladder in
`forge-cli/runtime/guardrails_loader.go:56-89` has three quiet
behaviors that mismatch what operators expect from a
"production-grade guardrails" deploy:
Silent fall-back on DB unreachable — `BuildGuardrailChecker`
logs a `Warn` and continues into file mode (or defaults) when the
Mongo connect fails. An operator who deliberately set the env var
probably wanted DB-mode-only behavior; silently downgrading to
stale or absent policy is the wrong default for a security
subsystem.
Default rules are not applied in DB mode — `DefaultStructuredGuardrails()`
carries 11 built-in secret-pattern rules
(`sk-ant-...`, `ghp_...`, `AKIA...`, etc.) plus PII and
jailbreak/prompt-injection/command-injection thresholds. These
are only used as a fallback in file mode when `guardrails.json`
is missing. They are never applied in DB mode. If the
operator's MongoDB `AgentConfig` document is empty or lacks the
secret-pattern rules, secret leaks in outbound responses / tool
calls will not be redacted. A DB-mode deploy with an
incomplete-but-present config is strictly less safe than a
file-mode deploy with no config at all.
No merge between file and DB — When both
`FORGE_GUARDRAILS_DB` is set AND a `guardrails.json` exists,
the DB wins exclusively and the file is never read. There's no
merge, no override, no warning that the file is being ignored.
Repo readers see a `guardrails.json` checked in and assume it's
active; in DB-mode deploys it's dead config that drifts from
what's actually enforced.
Proposal
Three small, related changes — all in one PR.
Part 1 — `FORGE_GUARDRAILS_DB_REQUIRED` fail-loud knob
Add an env var that flips the silent-fallback to a startup abort:
Same shape as `forge build`'s policy-violation-at-build-time
behavior (#89) — a deliberate fail-fast for security-critical
subsystems. Operators on the Initializ platform should set this in
their deployment manifest by default; one-off dev usage of
`FORGE_GUARDRAILS_DB` without the flag keeps the fall-back.
Implementation: one branch in `BuildGuardrailChecker` (~5 lines).
Test: mock a failing Mongo connect, assert startup returns an error
when the flag is on.
Part 2 — Carry default rules into DB mode
When the engine constructs `NewDBGuardrailEngine`, it currently
passes `StructuredGuardrails: nil` to every gate call
(`guardrails_engine.go:120`) so the library reads from MongoDB.
Two options:
Option A — Merge defaults at gate-call time:
Pass `DefaultStructuredGuardrails()` as a base layer that the
library's DB-loaded config overrides field-by-field. Requires the
library to support config merging (likely doesn't today — would
need a small library-side change).
Option B — Library-side seed responsibility:
Document that the operator's MongoDB `AgentConfig` seed MUST include
the equivalent of `DefaultStructuredGuardrails()` for the agent to
have baseline secret/PII protection. Provide a CLI command
`forge guardrails seed-defaults` that prints the default JSON
suitable for piping into MongoDB.
Recommended: Option B for first cut. Option A requires library
coordination (the guardrails library would need a documented
merge semantics that's stable across versions). Option B is a
pure docs + tooling change in Forge.
Implementation: new `forge guardrails` subcommand with two
operations:
`DefaultStructuredGuardrails()` as JSON to stdout
`FORGE_GUARDRAILS_DB`, fetch the agent's config, warn if
baseline rules are missing (e.g., no PII config + no
jailbreak detection + < 5 secret-pattern rules)
Part 3 — Document exclusivity + emit a startup warning
Three documentation gaps and one runtime change:
Documentation (`docs/security/guardrails.md`):
no merge"
callout explaining the missing default rules
Runtime (`forge-cli/runtime/guardrails_loader.go`):
`/guardrails.json` (or the cfg-specified path) exists,
emit a one-shot startup warning:
```
guardrails: DB mode active but guardrails.json also present —
the file is IGNORED. DB and file are mutually exclusive.
Remove the file or unset FORGE_GUARDRAILS_DB to avoid drift.
```
Files
Out of scope
coordination with the guardrails library; defer until there's a
clear API contract for merge semantics.
library reads on each gate call; that's already "hot" in a
sense, but full hot-reload of the agent's policy snapshot is
larger work.
/ single-Mongo deploy. Today each agent process holds its own
client. Defer.
Verification
no `FORGE_GUARDRAILS_DB_REQUIRED`. Confirm warn-and-fallback
behavior is unchanged (back-compat).
startup fails with non-zero exit and an explicit error message
referencing the env var.
`guardrails.json` present in the workdir. Confirm the one-shot
startup warning is emitted (in the ops log, NOT the audit
stream) and the file's contents are confirmed unread.
`defaults.json` through `models.StructuredGuardrails`
unmarshalling to confirm valid library-consumable shape.
confirm it warns when fewer than 5 secret-pattern rules are
present.
Related
resolution ladder
`structuredIfFileMode` showing the nil-passing behavior
Forge security analyzer; same fail-loud-by-default philosophy
audit-stream parity that makes incident debugging tractable