Skip to main content

Output Policy Operator Guide

This guide documents Cordum output safety scanning for operator workflows. Output policy evaluates job results after execution and can allow, redact, or quarantine output before release.

1. Overview

Input policy protects execution-time boundaries. Output policy protects result-time boundaries. A job that was safe to run can still return:

  • secret material (API keys, private keys, auth tokens)
  • PII (emails, SSNs, payment card numbers)
  • unsafe payloads (SQL/shell/prompt injection strings)
  • oversized outputs that should be blocked or redacted

Primary references:

  • gRPC contract: core/protocol/proto/v1/output_policy.proto
  • scheduler model/types: core/controlplane/scheduler/types.go
  • scheduler result flow: core/controlplane/scheduler/engine.go
  • safety policy schema: core/infra/config/safety_policy.go
  • JSON schema: core/infra/config/schema/safety_policy.schema.json

2. Architecture (Sync + Async)

Output safety is a two-phase model:

Worker -> sys.job.result -> Scheduler.handleJobResult
|
| (sync, hot path)
+-> CheckOutputMeta(res, req)
|-- ALLOW -> job stays SUCCEEDED
|-- REDACT -> prefer redacted_ptr, stay SUCCEEDED
`-- QUARANTINE -> OUTPUT_QUARANTINED + DLQ (output_quarantined)
|
| (only if still succeeded)
`-> background CheckOutputContent(ctx, res, req) [30s timeout]
`-- QUARANTINE -> retroactive SUCCEEDED -> OUTPUT_QUARANTINED
+ DLQ (output_quarantined_async)

Operational characteristics from current scheduler code:

  • sync checks are fail-open on checker error/unavailable (result flow continues)
  • async checks are best-effort and non-blocking
  • output safety record is persisted for API/dashboard retrieval

3. Enabling Output Scanning

3.1 Feature flag

OUTPUT_POLICY_ENABLED is parsed into Config.OutputPolicyEnabled in core/infra/config/config.go.

OUTPUT_POLICY_ENABLED=true

Release default: In docker-compose.release.yml, output policy is enabled by default (OUTPUT_POLICY_ENABLED=${OUTPUT_POLICY_ENABLED:-true}). To disable it in a specific deployment, explicitly set OUTPUT_POLICY_ENABLED=false in your environment. Production Gate 18 (gate_18_release_config) verifies this default has not regressed.

3.2 Fail mode

Current scheduler behavior is effectively fail_mode: open:

  • if sync check fails, scheduler increments output_policy_skipped and does not block result completion

fail_mode: closed is not currently implemented as a first-class runtime toggle in the scheduler path.

3.3 Per-topic override pattern

Per-topic control is expressed in output_rules[].match.topics using topic globs.

output_rules:
- id: quarantine-sensitive-reports
match:
topics: ["job.reports.*"]
detectors: ["secret_leak", "pii"]
decision: quarantine
reason: "Sensitive report outputs require quarantine review."

- id: allow-low-risk-healthchecks
match:
topics: ["job.healthcheck.*"]
decision: allow
reason: "Healthcheck output is low-risk."

3.4 Default rule set in config/safety.yaml

Cordum ships a baseline output rule set (disabled by default through output_policy.enabled: false):

Rule IDDefault stateMatch intentDecisionSeverity
output-secret-leakenabledSecret scanners on job.* outputquarantinecritical
output-pii-leakdisabledPII scanners on job.* outputquarantinehigh
output-injection-detectedenabledInjection scanners on job.* outputquarantinehigh
output-size-limitenabledOutputs larger than 10 MiB (output_size_gt)quarantinemedium
output-error-secret-leakenabledSecret scanners when has_error: truequarantinehigh

3.5 Per-topic overrides in Dashboard

/settings/output-safety includes a Per-Topic Overrides table for operational exceptions. Each override entry contains:

  • topic_pattern
  • enabled
  • fail_mode (open or closed)
  • scanners (multi-select)

When saved from dashboard, these overrides are persisted under output_policy.topic_overrides and take precedence over global defaults for matching topics.

3.6 Policy Studio (Input > Global) workflow

The dashboard Policy Studio → Input Policy → Global flow is now the primary global editor and writes full safety.yaml bundle content through /api/v1/policy/bundles/{id}. Legacy /policies/builder links redirect to /policies/input.

Operational behavior:

  • First match wins (input rules): global input rules are ordered and evaluated top-to-bottom.
  • default_decision: applied only when no input rule matches. Recommended production default remains deny (fail-closed).
  • Constraints & remediations: each input rule can define constraints (budgets, sandbox, toolchain, diff, redaction_level) and remediations.
  • Output policy controls: global scope exposes output_policy.enabled, output_policy.fail_mode, and ordered output_rules (detectors/content_patterns + severity + decision + reason).
  • Visual/Split/YAML sync: visual edits update YAML immediately; YAML edits parse back into visual state when syntax is valid.
  • Validation before save: malformed YAML is surfaced inline and save is blocked until syntax errors are fixed.
  • Conflict handling: save errors from stale writes are surfaced as conflict responses (HTTP 409) so operators can refresh and reapply.
  • Actionable errors: validation/conflict/request failures are distinguished in UI feedback for save/simulate actions.

Recommended pre-publish path:

  1. Open Input Policy (Global), choose target bundle.
  2. Apply global input/output changes (verify rule ordering).
  3. Run simulate from the same bundle context.
  4. Resolve YAML validation issues and save.
  5. Publish via existing policy publish flow (/api/v1/policy/publish) after review.

Current milestone scope: only Input > Global has been migrated to the new builder surface. Workflow/Tenant/other scopes remain on existing UX and are planned for later parity work.

4. Rule Format (output_rules)

output_rules are part of safety policy YAML and validated by safety_policy.schema.json.

output_rules:
- id: <string>
enabled: <bool> # optional, default true
severity: low|medium|high|critical
description: <string>
match:
tenants: [<tenant_id>, ...]
topics: [<topic_glob>, ...]
capabilities: [<capability>, ...]
risk_tags: [<risk_tag>, ...]
scanners: [secret|pii|injection, ...]
content_patterns: [<regex_string>, ...]
detectors: [secret_leak|pii|code_injection|custom, ...]
output_size_gt: <int>
max_output_bytes: <int>
has_error: <bool>
decision: allow|deny|quarantine|redact
reason: <string>

Fields mapped from code:

  • id: rule identifier
  • enabled: optional rule toggle
  • severity: rule severity label (low|medium|high|critical)
  • description: operator-facing rule description
  • match.tenants: tenant selector
  • match.topics: topic globs
  • match.capabilities: capability filter
  • match.risk_tags: risk-tag filter
  • match.scanners: scanner aliases (secret, pii, injection)
  • match.content_patterns: regex/pattern list for output content checks
  • match.detectors: scanner aliases/synonyms (secret_leak, code_injection, etc.)
  • match.output_size_gt: size threshold comparator (rule matches when output size is greater than this value)
  • match.max_output_bytes: output size ceiling
  • match.has_error: restrict rule to outputs with/without worker error payload
  • decision: allow|deny|quarantine|redact
  • reason: operator-facing decision explanation

5. YAML Examples for Common Rule Types

output_rules:
# 1) Secret leak rule
- id: out-secret-aws
match:
topics: ["job.*"]
detectors: ["secret_leak"]
content_patterns: ["AKIA[0-9A-Z]{16}", "gh[pousr]_[A-Za-z0-9]{20,}"]
decision: quarantine
reason: "Potential cloud credential or token in output."

# 2) PII rule
- id: out-pii-critical
match:
topics: ["job.customer.*"]
detectors: ["pii"]
decision: quarantine
reason: "PII detected in customer workflow output."

# 3) Injection rule
- id: out-injection-patterns
match:
topics: ["job.codegen.*", "job.agent.*"]
detectors: ["code_injection"]
content_patterns:
- "(?i)(union\\s+select|drop\\s+table)"
- "(?i)(ignore\\s+previous\\s+instructions|jailbreak)"
decision: redact
reason: "Potential injection payload detected; redact before release."

# 4) Size-limit rule
- id: out-size-limit
enabled: true
severity: medium
description: Quarantine oversized export payloads.
match:
topics: ["job.export.*"]
output_size_gt: 1048576
decision: quarantine
reason: "Output exceeds 1 MiB policy limit."

# 5) Error-path leak rule
- id: out-error-secret-leak
enabled: true
severity: high
description: Scan failure/error payloads for secret leakage.
match:
topics: ["job.*"]
scanners: ["secret"]
has_error: true
decision: quarantine
reason: "Secret-like data found in error output."

6. Scanner Types

Built-in scanner implementations currently live in core/controlplane/safetykernel/scanners.go.

Scanner nameDetection targetExample patterns in codeTypical severity
secret_leak / secretCredentials and key materialAWS access key id, GitHub token, private key header, generic api_key/token/password assignmentcritical, high
piiPersonal dataemail, SSN, phone; payment card with Luhn validationhigh
code_injection / injectionInjection and exploit fragmentsSQL (union select, drop table), shell (rm -rf, `curlsh`), prompt injection phrases
max_output_bytes rule matchOversized outputmetadata size thresholdpolicy-defined

7. Scanner Pattern Definitions (config/output_scanners.yaml)

Cordum ships scanner definitions in config/output_scanners.yaml. Safety Kernel loads this file at startup and falls back to built-in scanners if parsing fails.

version: v1

scanners:
secret:
finding_type: secret_leak
patterns:
- name: aws_access_key
severity: critical
regex: "AKIA[0-9A-Z]{16}"
- name: github_token
regex: "gh[pousr]_[A-Za-z0-9]{20,}"
- name: db_connection_string_password
regex: "(?i)(postgres(?:ql)?|mysql|mariadb|mongodb|sqlserver):\\/\\/[^\\s:@]{1,64}:[^\\s@]{4,}@[^\\s]+"
- name: base64_high_entropy_secret
regex: "(?i)(secret|token|password|private)[^\\n]{0,32}[=:][^\\n]{0,16}[A-Za-z0-9+/]{64,}={0,2}"
context_required: true

pii:
finding_type: pii
patterns:
- name: email_address
regex: "\\b[A-Za-z0-9._%+\\-]+@[A-Za-z0-9.\\-]+\\.[A-Za-z]{2,}\\b"
- name: ssn
regex: "\\b\\d{3}-\\d{2}-\\d{4}\\b"
- name: credit_card
regex: "\\b(?:\\d[ -]*?){13,19}\\b"
context_required: true

injection:
finding_type: code_injection
patterns:
- name: shell_command
regex: "(?i)(\\brm\\s+-rf\\b|\\bcurl\\b[^\\n]{0,80}\\|\\s*(sh|bash)\\b|\\bwget\\b[^\\n]{0,80}\\|\\s*(sh|bash)\\b)"
- name: sql_injection
regex: "(?i)union\\s+select"
- name: prompt_injection
regex: "(?i)(ignore\\s+previous\\s+instructions|reveal\\s+system\\s+prompt|jailbreak)"

The default scanner set directly covers:

  • AWS credentials and secret key material
  • private key headers
  • API tokens/keys (ghp_, xoxb-, sk-, generic key/token/password assignments)
  • DB connection strings with embedded credentials
  • common PII (email, ssn, payment-card-like number patterns)
  • high-entropy base64 secret heuristics
  • shell/SQL/prompt-injection fragments

8. Job Lifecycle Impact

Output policy impacts lifecycle in handleJobResult:

  • terminal state can become OUTPUT_QUARANTINED (JobStateQuarantined)
  • DLQ reason codes used by scheduler:
    • output_quarantined (sync phase)
    • output_quarantined_async (async phase)
  • output safety record is persisted and returned via job APIs:
    • decision, reason, rule_id, policy_snapshot
    • findings[], phase, redacted_ptr, original_ptr

Persistence path:

  • dedicated Redis key: job:<job_id>:output_decision
  • metadata field: job:meta:<job_id> -> output_safety

9. Dashboard Integration

Dashboard workflows for output policy:

  • Settings / Output Safety (/settings/output-safety): operators can enable/disable scanning, set fail mode (open|closed), configure scan timeout/payload limits, and review runtime status/denials from one page.
    • Screenshot placeholder: docs/assets/output-safety-settings.png (capture pending).
  • Policy authoring flow (Policy Studio / Output Rules UX): operators define output_rules (topics, scanners, severity, decision, reason) and publish policy bundles; authoring and review UX is specified in dashboard/src/components/jobs/OUTPUT_QUARANTINE_UX.md.
    • Policy Studio now exposes an Output Rules section in /policies/rules with per-rule enabled toggles and a drawer-style rule detail view (config JSON, scanners/patterns, and recent findings from GET /api/v1/policy/audit?type=output&rule_id=<id>).
  • Jobs and Quarantine monitoring: output_quarantined appears in job filters and state machine views (JobFiltersBar, JobStateMachine) so analysts can triage blocked outputs.
  • DLQ triage UX: DLQ adds Result Type = Quarantined filtering, an orange quarantine shield badge, inline finding details (matched rule + original pointer), and a Release Output action for false positives (internally uses DLQ retry).
  • DLQ review actions:
    • list quarantined: GET /api/v1/jobs?state=OUTPUT_QUARANTINED
    • inspect findings: GET /api/v1/jobs/{jobId}
    • release false positive: POST /api/v1/dlq/{jobId}/retry
    • confirm quarantine: DELETE /api/v1/dlq/{jobId}

10. Operator Runbook

10.1 Review quarantined outputs

  1. List quarantined jobs: GET /api/v1/jobs?state=OUTPUT_QUARANTINED
  2. Inspect a job: GET /api/v1/jobs/{job_id}
  3. Review output_safety:
    • decision, rule_id, reason
    • findings (type/severity/scanner/pattern/confidence)
    • phase (sync or async)

10.2 Release false positives

If quarantine is false positive and output is acceptable:

  • POST /api/v1/dlq/{job_id}/retry
  • Dashboard path: DLQ -> Result Type = Quarantined -> open entry -> Release Output

10.3 Confirm quarantine

If quarantine is valid and you want to keep the item handled in DLQ workflow:

  • DELETE /api/v1/dlq/{job_id}

10.4 Tuning guidance

  • narrow topics scope first, then add capabilities / risk_tags
  • prefer targeted content_patterns over broad regex
  • quarantine only high-confidence/high-impact findings
  • use redact for partial-sensitive payloads where sanitized output is acceptable
  • monitor false positive rate and adjust pattern confidence/severity

11. Environment Variables

VariableComponentPurpose
OUTPUT_POLICY_ENABLEDscheduler config loaderEnables output policy feature flag parsing (true/1) in Config.OutputPolicyEnabled.
SAFETY_KERNEL_ADDRschedulergRPC endpoint for safety kernel checks.
SAFETY_KERNEL_TLS_CAscheduler + gatewayCA bundle used for TLS verification when dialing safety kernel.
SAFETY_KERNEL_TLS_REQUIREDscheduler + gatewayRequires TLS when connecting to safety kernel.
SAFETY_KERNEL_INSECUREscheduler + gatewayAllows insecure transport in non-production/testing scenarios.
SAFETY_KERNEL_TLS_CERTsafety kernel serverServer certificate path for TLS listener.
SAFETY_KERNEL_TLS_KEYsafety kernel serverServer key path for TLS listener.
OUTPUT_SCANNERS_PATHsafety kernel serverOptional path to scanner regex definitions (config/output_scanners.yaml by default).
SAFETY_POLICY_PATHsafety kernelLocal policy bundle path (includes output_rules).
SAFETY_POLICY_URLsafety kernelRemote policy URL (overrides path when set).
SAFETY_POLICY_URL_ALLOWLISTsafety kernelComma-separated allowed hosts for policy URL fetch.
SAFETY_POLICY_URL_ALLOW_PRIVATEsafety kernelAllows private/loopback hosts for policy fetch (disabled by default).
SAFETY_POLICY_MAX_BYTESsafety kernelMax policy bundle size for file/URL loading.
SAFETY_POLICY_SIGNATURE_REQUIREDsafety kernelRequire signed policy bundle.
SAFETY_POLICY_PUBLIC_KEYsafety kernelEd25519 public key for signature verification.
SAFETY_POLICY_SIGNATUREsafety kernelInline signature (base64/hex).
SAFETY_POLICY_SIGNATURE_PATHsafety kernelPath to detached signature file.
SAFETY_POLICY_RELOAD_INTERVALsafety kernelHot-reload interval for policy refresh.
SAFETY_POLICY_CONFIG_DISABLEsafety kernelDisable config-service policy loading path.
SAFETY_POLICY_CONFIG_SCOPEsafety kernelConfig-service scope (system, org, team, etc.).
SAFETY_POLICY_CONFIG_IDsafety kernelConfig-service object id for policy fetch.
SAFETY_POLICY_CONFIG_KEYsafety kernelConfig-service key for policy payload.

12. Cross-References