Goal
Let a platform operator further restrict an agent's guardrails.json — tighten it, never loosen it — the same one-way ratchet the capability layers (platform_policy.yaml, system/user/workspace) already apply to tools/egress/models/channels.
Two parts:
- Add a platform guardrails overlay merged most-restrictively over the agent's
guardrails.json.
- Remove the DB-mode guardrails construct entirely — it was never used and is the wrong model (wholesale replacement, not a further-restrict merge).
Part 1 — Platform guardrails overlay
Current state (the gap)
Two governance systems exist and don't meet:
- Platform policy layers (
forge-core/security/platform_policy*.go) restrict the capability surface (tools, egress, models, channels, counts). They know nothing about guardrails.json content.
guardrails.json (models.StructuredGuardrails from github.com/initializ/guardrails) is loaded per-agent from the workdir (forge-cli/runtime/guardrails_loader.go: LoadGuardrailsJSON). Nothing lets the platform layer on top of it to tighten it.
So today the platform can't say "whatever the agent declared, also force commandInjection to block and add these secret-redaction rules." That capability doesn't exist.
What guardrails.json provides (the surface to ratchet)
StructuredGuardrails sections: pii, moderation, security (jailbreak / promptInjection / sqlInjection / commandInjection + customPatterns), urlFilter, customRules, approvalGates, nsfwText, hallucination, skillConstraints, gateConfig.
Proposed merge semantics (platform can only tighten)
| Field |
Ratchet rule |
gateConfig.* |
boolean OR — platform can force a gate ON, never OFF |
detections (pii, security.*, nsfwText, moderation, hallucination) |
force enabled=true; take most-severe action (warn < mask < block); take lower threshold (more sensitive) |
customRules.rules, security.customPatterns, customRules.hardConstraints/softConstraints |
union — platform rules always added; agent can't delete them |
urlFilter |
union denylist; intersect allowlist; force enabled |
approvalGates |
union — platform can add required-approval conditions |
skillConstraints |
union blockedSkills; intersect allowedSkills |
Implementation sketch
MergeGuardrails(agent *models.StructuredGuardrails, platform *models.StructuredGuardrails) (*models.StructuredGuardrails, []GuardrailTightening) in forge-core/security, pure + unit-tested, returning which layer tightened what for audit.
- Load the platform guardrails overlay from the SAME layer mechanism as
platform_policy_layers.go (system → user → workspace) so operators author it alongside the capability policy. Union across layers is itself most-restrictive.
- Wire into
BuildGuardrailChecker (forge-cli/runtime/guardrails_loader.go) so the effective StructuredGuardrails = merge(agent file, platform overlay) before the engine is built.
- Audit each tightening (mirror
platform_policy_enforce.go's violation attribution) so operators see "commandInjection action raised warn→block by workspace layer."
- Define a total order for action severity (
warn < mask < block) and thresholds (lower = stricter) once, shared by the merge.
Acceptance
- Platform overlay can force-enable gates/detections, raise actions, lower thresholds, and union rule/denylist/blocked-skill sets over an agent's
guardrails.json.
- Overlay can NEVER loosen: a disabled/absent platform field leaves the agent's setting untouched; there is no "allow-override".
- Effective merge is audited with per-field layer attribution.
- Unit tests for every ratchet rule incl. the no-loosen invariant.
Supersedes/links #238 (platform-authored denied_command_patterns is just the customRules/security.customPatterns slice of this broader overlay).
Part 2 — Remove the DB-mode guardrails construct (unused)
guardrails.json has a second, unused load path: DB mode via FORGE_GUARDRAILS_DB (a MongoDB URI). It loads the entire config from Mongo and is mutually exclusive with the file (DB wins, file ignored). It was never adopted, adds a heavy dependency, and is the wrong model for "further restrict" (replacement, not merge) — the Part 1 overlay is the correct mechanism for platform control.
Remove
forge-cli/runtime/guardrails_engine.go: NewDBGuardrailEngine, the useDB field + its branch (~line 148), and the go.mongodb.org/mongo-driver/mongo + .../options imports.
forge-cli/runtime/guardrails_loader.go: the DB-mode branch (~lines 112–158), EnvGuardrailsDB / EnvGuardrailsDBRequired, dbModeRequired(), and the file/DB exclusivity warning (dbFileExclusivityOnce, guardrailsFilePathIfPresent if only used for that warning).
forge-cli/cmd/guardrails.go: DB-mode help text.
- Tests referencing DB mode in
guardrails_loader_test.go / guardrails_test.go.
- Run
go mod tidy — drops the go.mongodb.org/mongo-driver dependency from forge-cli entirely (confirmed: it's used ONLY by the guardrails DB path). Reduces dependency + vuln surface.
- Docs:
docs/security/guardrails.md (remove the DB-vs-file section).
Acceptance
- File mode is the only agent-level guardrails source; platform control is via the Part 1 overlay.
go.mongodb.org/mongo-driver no longer in forge-cli's go.mod.
- No dangling references to
FORGE_GUARDRAILS_DB*; docs updated.
Suggested order
Part 1 (overlay) first so platform control exists, then Part 2 (remove DB mode) so there's no window without a platform lever. Related: #238, platform_policy_layers.go / platform_policy_enforce.go (the pattern to mirror).
Goal
Let a platform operator further restrict an agent's
guardrails.json— tighten it, never loosen it — the same one-way ratchet the capability layers (platform_policy.yaml, system/user/workspace) already apply to tools/egress/models/channels.Two parts:
guardrails.json.Part 1 — Platform guardrails overlay
Current state (the gap)
Two governance systems exist and don't meet:
forge-core/security/platform_policy*.go) restrict the capability surface (tools, egress, models, channels, counts). They know nothing aboutguardrails.jsoncontent.guardrails.json(models.StructuredGuardrailsfromgithub.com/initializ/guardrails) is loaded per-agent from the workdir (forge-cli/runtime/guardrails_loader.go: LoadGuardrailsJSON). Nothing lets the platform layer on top of it to tighten it.So today the platform can't say "whatever the agent declared, also force
commandInjectionto block and add these secret-redaction rules." That capability doesn't exist.What guardrails.json provides (the surface to ratchet)
StructuredGuardrailssections:pii,moderation,security(jailbreak / promptInjection / sqlInjection / commandInjection + customPatterns),urlFilter,customRules,approvalGates,nsfwText,hallucination,skillConstraints,gateConfig.Proposed merge semantics (platform can only tighten)
gateConfig.*pii,security.*,nsfwText,moderation,hallucination)enabled=true; take most-severe action (warn < mask < block); take lower threshold (more sensitive)customRules.rules,security.customPatterns,customRules.hardConstraints/softConstraintsurlFilterdenylist; intersectallowlist; forceenabledapprovalGatesskillConstraintsblockedSkills; intersectallowedSkillsImplementation sketch
MergeGuardrails(agent *models.StructuredGuardrails, platform *models.StructuredGuardrails) (*models.StructuredGuardrails, []GuardrailTightening)inforge-core/security, pure + unit-tested, returning which layer tightened what for audit.platform_policy_layers.go(system → user → workspace) so operators author it alongside the capability policy. Union across layers is itself most-restrictive.BuildGuardrailChecker(forge-cli/runtime/guardrails_loader.go) so the effectiveStructuredGuardrails= merge(agent file, platform overlay) before the engine is built.platform_policy_enforce.go's violation attribution) so operators see "commandInjection action raised warn→block by workspace layer."warn < mask < block) and thresholds (lower = stricter) once, shared by the merge.Acceptance
guardrails.json.Supersedes/links #238 (platform-authored
denied_command_patternsis just thecustomRules/security.customPatternsslice of this broader overlay).Part 2 — Remove the DB-mode guardrails construct (unused)
guardrails.jsonhas a second, unused load path: DB mode viaFORGE_GUARDRAILS_DB(a MongoDB URI). It loads the entire config from Mongo and is mutually exclusive with the file (DB wins, file ignored). It was never adopted, adds a heavy dependency, and is the wrong model for "further restrict" (replacement, not merge) — the Part 1 overlay is the correct mechanism for platform control.Remove
forge-cli/runtime/guardrails_engine.go:NewDBGuardrailEngine, theuseDBfield + its branch (~line 148), and thego.mongodb.org/mongo-driver/mongo+.../optionsimports.forge-cli/runtime/guardrails_loader.go: the DB-mode branch (~lines 112–158),EnvGuardrailsDB/EnvGuardrailsDBRequired,dbModeRequired(), and the file/DB exclusivity warning (dbFileExclusivityOnce,guardrailsFilePathIfPresentif only used for that warning).forge-cli/cmd/guardrails.go: DB-mode help text.guardrails_loader_test.go/guardrails_test.go.go mod tidy— drops thego.mongodb.org/mongo-driverdependency from forge-cli entirely (confirmed: it's used ONLY by the guardrails DB path). Reduces dependency + vuln surface.docs/security/guardrails.md(remove the DB-vs-file section).Acceptance
go.mongodb.org/mongo-driverno longer in forge-cli'sgo.mod.FORGE_GUARDRAILS_DB*; docs updated.Suggested order
Part 1 (overlay) first so platform control exists, then Part 2 (remove DB mode) so there's no window without a platform lever. Related: #238,
platform_policy_layers.go/platform_policy_enforce.go(the pattern to mirror).