CI Security Policy and Exception Standard
- Policy version: 1.0.0
- Schema version: 1
- Applies to: Pull requests, merge queues, default-branch pushes, scheduled scans, release builds, plugin publication, and deployment artifacts
- Policy owner: Repository security owner
- Enforcement command:
go run ./cmd/security-policy
1. Purpose
Consize changes production infrastructure. Its repository security checks must therefore fail predictably and must not be made green by hiding findings, ignoring scanner errors, or accepting broad risk.
This document defines:
- which security findings block a pull request or release;
- which findings are recorded for triage;
- when an exception is permitted;
- who owns and approves an exception;
- how long an exception can remain active;
- how scanners exchange findings with the common policy evaluator;
- what happens when a scanner or report is incomplete;
- how unresolved findings affect releases;
- how sensitive evidence is handled.
The human-readable policy in this file and the machine-readable files under
.github/security/ form one control. A scanner-specific workflow must not
invent a weaker exception mechanism.
2. Authority and Scope
This policy applies to first-party code, generated code that is committed to the repository, dependencies, container images, GitHub Actions, build scripts, Kubernetes and infrastructure-as-code configuration, plugin packages, release artifacts, software bills of materials, signatures, checksums, provenance, and security reports.
The policy applies equally to production and development dependencies unless a rule below explicitly assigns different release impact. Development context can change the approval scope; it does not make a known high-risk finding invisible.
This policy does not replace incident response. A live credential, malicious package, compromised signing identity, or tampered release is an incident and must follow the incident process. Such an event cannot be converted into an ordinary vulnerability exception.
3. Control Principles
3.1 Fail closed
A scanner crash, timeout, unavailable vulnerability database, invalid output, missing report, or report upload failure is an incomplete security check. It is not a clean scan.
3.2 Keep execution separate from disposition
An exception may affect the disposition of one exact finding. It must never:
- prevent the scanner from running;
- skip target or artifact validation;
- skip report-schema validation;
- change all findings to a lower severity;
- turn a scanner error into success;
- exclude an entire source directory, environment-file class, or dependency type;
- disable release-integrity verification.
3.3 Match narrowly
An exception is matched against the scanner, rule ID, stable fingerprint, finding type, component, environment, execution context, and affected version. Images and plugin packages also require an exact SHA-256 artifact digest.
Glob patterns, all, wildcard version ranges, and blanket paths are rejected by
the validator.
3.4 Treat expiry as a failure
An expired, malformed, over-duration, or overdue-for-review exception makes the exception registry invalid. CI fails until the finding is fixed, the exception is removed, or a new independently reviewed exception is approved.
3.5 Keep evidence honest
Reports may contain finding metadata, package names, file locations, redacted fingerprints, and remediation guidance. They must not contain secret values, private incident evidence, access tokens, exploit payloads, or other material that would increase exposure.
3.6 Separate security risk from release ownership
The engineering owner investigates and remediates. The security or repository owner approves eligible risk. The release owner decides whether a release can proceed under the policy and must explicitly review every unresolved release-blocking finding.
4. Repository Artifacts
The control is implemented by these reviewed files:
docs/engineering/ci-security.mdis the contributor-facing policy..github/security/policy.jsoncontains the machine-readable blocking, exception, ownership, duration, and service-level rules..github/security/policy.schema.jsonversions the policy format..github/security/exceptions.jsonis the only accepted repository exception registry..github/security/exceptions.schema.jsonversions the exception format..github/security/findings.schema.jsondefines the normalized scanner report.internal/securitypolicyvalidates policy, exceptions, reports, matching, and gate decisions.cmd/security-policyprovides the local and CI command-line interface..github/CODEOWNERSrequires security-owner review of policy and exception changes.
The JSON Schemas support editor tooling and external integrations. The Go validator performs strict decoding and the semantic checks that JSON Schema cannot fully express, including current-time expiry, approval independence, maximum duration, review cadence, exact scope, and non-exceptionable classes.
5. Common Gate Flow
Every security scanner must eventually use the following sequence:
Run pinned scanner against the intended target
-> retry only a transient scanner failure within the bounded retry policy
-> preserve a redacted raw report when useful
-> normalize findings to .github/security/findings.schema.json
-> validate the policy and exception registry
-> evaluate every normalized finding through cmd/security-policy
-> upload the redacted report and policy decision when required
-> return the evaluator's gate result to CI
The scanner is responsible for finding security issues. The common evaluator is responsible for consistent disposition and exceptions. Neither replaces the other.
6. Scanner Completion and Retry Rules
Security jobs must distinguish findings from tool failures.
6.1 Completed scan
A completed scan queried its required data, scanned the intended target, produced syntactically valid output, and was normalized without dropping findings. A completed scan can contain zero or many findings.
6.2 Incomplete scan
The normalized status must be one of the following when a scan did not complete:
failedfor a scanner process or adapter failure;timed_outfor a bounded timeout;data_unavailablewhen required advisory or metadata sources are unavailable;invalid_reportwhen scanner output cannot be parsed or validated.
All four statuses fail the gate as incomplete.
6.3 Bounded retry
A scanner integration may make one initial attempt and one retry for a transient network, service, or data error. A third attempt requires an explicit workflow change reviewed with the scanner integration. Findings themselves are not retried away.
The normalized report records the number of attempts. A retry must use the same target identity. It must not silently change the image, commit, dependency graph, or rule set being scanned.
6.4 Invalid reports
Unknown fields, missing required identity, malformed timestamps, duplicate fingerprints, unredacted secret findings, and unsupported finding types make a report invalid. Invalid reports fail before exception matching.
7. Blocking and Triage Policy
7.1 Secrets
Any detected secret blocks. It remains blocked until one of these outcomes is recorded:
- the credential is removed and, if it was live, revoked or rotated;
- the value is confirmed through narrow review to be a non-secret fixture or false positive.
An exception for a secret finding may use only the false_positive
disposition. It must include non-secret evidence without recording the matched
value. If the report marks the value as a live credential, no exception can
match it.
Deleting a live credential from the current file is not containment if it remains valid or exists in Git history. Incident handling must include revocation or rotation and history review.
7.2 Frontend dependencies
High and critical vulnerabilities in production or development dependencies block unless an approved, active exception matches exactly. Medium and low findings are reported and assigned for triage.
The scanner must use the repository lockfile. CI must not run an automatic fix command or modify the lockfile to obtain a green result.
7.3 Go vulnerabilities
A source scan that identifies a reachable Go vulnerability blocks even when the advisory does not provide a CVSS severity. Reachability is the blocking criterion.
Module or package findings not shown as reachable are retained for triage. They must not be presented as proven exploitability. Scanner errors and standard library or toolchain findings remain distinct from application findings.
7.4 Deployable runtime images
High and critical vulnerabilities in a deployable runtime image block unless a valid exception matches the exact image digest and version. Findings marked unfixed remain visible in the report and follow the same exception rules.
Build stages and final runtime images are reported separately. When API and worker targets share an identical final digest, one vulnerability scan may be reused only after both targets have built and the shared digest has been verified.
7.5 Development-only images
High and critical findings in development-only images require triage and block
until fixed or covered by an active exception whose environment is exactly
development and whose context identifies a development image.
The result must not be described as a production-image release failure. It is a development dependency gate with its own accountable owner.
7.6 Repository and configuration rules
A high or critical violation of a mandatory repository rule blocks. A
contextual exception is permitted only when the originating rule explicitly
sets exception_allowed in the normalized finding.
Mandatory rules include controls such as tracked private keys, credential files, production secrets in Docker or Compose configuration, deliberate exposure of server-only configuration to a public client bundle, and prohibited generated or runtime files.
Pattern matching is a focused control and must not be described as proof that no secret exists.
7.7 SAST and code scanning
Supported high and critical findings block when all of these conditions apply:
- scanner confidence is medium or high;
- reachability is
reachableorunknownfor a relevant source path; - the rule is supported by the configured policy.
Low-confidence and demonstrably unreachable findings are retained for triage. Contextual exceptions require the scanner or adapter to mark the rule as exceptionable. A tool error is a scanner failure, not a finding.
7.8 CI workflow and GitHub Actions supply chain
High and critical workflow findings block. This class includes:
- unpinned third-party actions where immutable pinning is required;
- excessive token permissions;
- unsafe privileged execution of untrusted pull-request code;
- untrusted artifact download or execution;
- unsafe interpolation into shell commands;
- credential exposure through logs or artifacts;
- mutable or unverified tool downloads.
Actions must be pinned to immutable commit SHAs with a version comment. Workflow tokens use least privilege. A privileged workflow must not execute untrusted PR code.
7.9 License and dependency source policy
High and critical prohibited-license or incompatible-license findings block. Dependencies from prohibited, untrusted, or unverifiable sources also block.
A legal or repository-owner decision may accept a narrowly scoped eligible license risk. Malicious-package indicators and unverifiable source integrity are not ordinary license exceptions.
7.10 SBOM generation and validation
A required SBOM that is missing, invalid, incomplete, or cannot be associated with the release artifact fails the release security gate. This is not exceptionable.
An SBOM tool failure must not produce an empty document that is reported as a successful result.
7.11 Release provenance, checksums, and signatures
Missing, invalid, or unverifiable release provenance, checksum, or signature blocks release and is not exceptionable. Verification must be performed against the artifact being released, not only against a similarly tagged build.
7.12 Kubernetes and infrastructure as code
Mandatory high and critical Kubernetes or IaC findings block. Contextual exceptions are allowed only for rules that explicitly permit them and must identify the exact path, environment, deployment context, and version.
At minimum, future gates should cover privileged execution, host access, dangerous capabilities, root containers, writable root filesystems, unrestricted RBAC, public service exposure, unpinned images, secret material, and missing resource or security settings where the deployment policy requires them.
7.13 Plugin permissions and trust
A plugin permission expansion blocks until the new capability is reviewed. The finding and exception, when allowed, must identify the exact plugin artifact digest, version, requested permission, and target environment.
A revoked plugin version or a plugin signed by an untrusted identity is not exceptionable. The plugin must not be installed or executed.
The exception system cannot authorize a plugin to bypass server-side policy, target validation, signature validation, or compatibility checks.
7.14 End-of-life components
High and critical findings for unsupported runtimes, base images, or dependencies block. A temporary exception must identify the exact component, supported migration plan, compensating controls, and expiry.
7.15 Security-control regressions
A required security regression test failure is not exceptionable. Examples include tests proving server-side authorization, policy enforcement, secret redaction, plugin signature rejection, rollback drift protection, or audit durability.
If a control is intentionally replaced, the replacement and its test must be reviewed as a policy change. The existing test must not simply be suppressed.
7.16 Required report delivery and schema
Failure to generate, retain, upload, or validate a required security report fails the gate. The control is not satisfied by a successful scanner process if reviewers cannot inspect the required redacted evidence.
7.17 Lower-severity findings
Medium, low, and informational findings below the applicable blocking threshold
do not fail the pull request by default. They are emitted as triage decisions
with the responsible owner from the policy. They must enter the team's issue or
risk-tracking process within the acknowledgement target below.
Repeated lower-severity findings may be promoted through a policy change when their combination, exploit chain, or affected context creates higher risk.
8. Normalized Finding Contract
Scanner output formats differ. Each scanner adapter must produce a report that
matches .github/security/findings.schema.json before policy evaluation.
8.1 Report identity
Every report identifies:
- schema version;
- scanner ID;
- unique scan ID;
- generation time;
- completion status;
- attempt count;
- target component;
- target artifact digest when applicable;
- environment and CI context;
- target version or revision.
8.2 Finding identity
Every finding identifies:
- stable fingerprint;
- scanner rule or advisory ID;
- common finding type;
- severity and confidence when supported;
- reachability when supported;
- whether the rule is mandatory;
- whether the originating rule allows an exception;
- whether the finding represents a live credential;
- exact component and component type;
- repository path when applicable;
- artifact digest when applicable;
- environment and execution context;
- affected version;
- redacted summary;
- optional assigned owner and remediation issue.
8.3 Adapter requirements
An adapter must not discard a finding because it lacks CVSS data. It must use
unknown for unsupported severity, confidence, or reachability fields.
An adapter must not synthesize not_reachable merely because the scanner did
not provide reachability. The correct value is unknown.
Secret findings set redacted: true and must not include the matched value in
the summary, fingerprint, path, output, or retained artifact.
9. Exception Registry
The only accepted exception registry is
.github/security/exceptions.json. Scanner-local allowlists do not replace it.
A scanner configuration may contain a reviewed false-positive rule only when
that rule itself is narrow, documented, and unable to conceal unrelated
findings. The common registry remains the source of expiry, ownership, and
approval.
9.1 Required exception identity
Every exception contains:
- an ID in
SEC-EXC-YYYY-NNNNformat; - exact scanner ID;
- exact finding type;
- exact rule ID;
- stable exact fingerprint;
- assigned risk class;
- disposition of
risk_acceptanceorfalse_positive.
9.2 Required scope
Every exception is bound to:
- one component;
- one component type;
- a repository-relative path for source, workflow, configuration, Kubernetes, or IaC findings;
- exact artifact digest for image and plugin permission findings;
- one environment;
- one execution context;
- an exact version or constrained semantic-version range.
Supported version expressions are:
Wildcards and global version scopes are invalid.
9.3 Required justification
Every exception records:
- a specific reason;
- at least one compensating control;
- false-positive evidence when the disposition is
false_positive; - accountable owner;
- requester;
- approver;
- approver role;
- approval time;
- expiry;
- last review and next review;
- HTTPS remediation issue.
Sensitive exploit details and incident evidence belong in an access-controlled system. The public exception should contain enough redacted information to explain scope and decision without increasing exploitability.
9.4 Exact matching
An active exception applies only when all applicable fields match the normalized finding. A scanner name, rule, component, environment, context, version, path, or digest mismatch leaves the finding blocked.
An exception cannot change an incomplete scanner result into a pass.
9.5 False positives
A false-positive disposition means the detected material is not the security condition represented by the rule. It does not mean the risk is small.
A secret false positive must explain why the matched material is a non-secret without copying it. A known live credential is never a false positive.
9.6 Risk acceptance
Risk acceptance means the finding is real, remediation is deferred for a bounded period, compensating controls are active, and an accountable person has accepted the residual risk.
Risk acceptance is not available when the policy marks the finding type non-exceptionable.
10. Non-Exceptionable Conditions
The following cannot be suppressed through the ordinary exception registry:
- scanner execution failure after bounded retry;
- target identity or artifact validation failure;
- invalid normalized report;
- required report delivery failure;
- a confirmed live credential exposure;
- malicious-package indicators;
- missing or invalid required SBOM;
- missing, invalid, or unverifiable provenance, checksum, or signature;
- revoked plugin version;
- untrusted plugin signing identity;
- required security-control regression failure.
Resolving one of these conditions requires restoring the control, replacing the artifact, responding to the incident, or changing the policy through a separately reviewed design decision. Adding an exception entry is not a valid resolution.
11. Approval Independence and CODEOWNERS
High and critical exceptions require an approver different from the requester and from the author of the exception change.
The validator checks requester and approver identity. On pull requests that change the exception registry, CI compares the base and proposed registries and checks changed entries against the PR author. CODEOWNERS review provides the repository-level approval evidence.
CODEOWNERS enforcement depends on repository settings. The main branch or
repository ruleset must require:
- pull requests for changes;
- at least one approving review;
- code-owner review for owned paths;
- dismissal of stale approvals after new commits;
- required
Security policyand existing quality checks; - no force pushes or branch deletion;
- administrator bypass restricted and audited.
The initial CODEOWNER is the repository security owner. As the team grows, the repository should create a security-maintainers team with at least two eligible reviewers and replace the individual owner. A one-person team cannot provide independent approval when that person authors the exception.
12. Exception Duration and Review Cadence
Durations run from approved_at, not from the first CI run that notices the
exception.
| Risk class | Maximum duration | Maximum review interval | Independent approval |
|---|---|---|---|
| Critical | 7 days | 24 hours | Required |
| High | 30 days | 7 days | Required |
| Medium | 90 days | 30 days | Policy reviewer required |
| Low | 180 days | 90 days | Policy reviewer required |
The next review must occur after the last review and no later than expiry. An overdue review invalidates the registry even when the final expiry is later.
Shorter durations may be required by an incident commander, legal obligation, customer commitment, or release owner.
13. Renewals
Expiry is never extended automatically.
A renewal requires:
- A new exception ID.
renewal_ofpointing to the prior ID.- A new explanation of why remediation is incomplete.
- Confirmation that compensating controls still operate.
- A new approval and review timeline.
- An updated remediation issue.
- Removal of the expired record when its audit history is preserved elsewhere.
CI verifies that renewal_of exists in the pull request's base revision, that
the new record preserves the exact scanner, finding, fingerprint, risk,
disposition and scope, and that approval occurred before the prior exception's
expiry and next-review deadline. renewal_of cannot revive an expired or
overdue exception, point to an arbitrary ID, or broaden the accepted risk.
The same maximum duration applies to the new record. Repeated renewals should trigger escalation to the repository owner and release owner because they indicate that temporary risk has become normal operating practice.
14. Triage and Remediation Service Levels
Targets begin when CI or a scheduled scan first records the finding.
| Risk class | Acknowledge within | Remediate or make risk decision within | Release impact |
|---|---|---|---|
| Critical | 4 hours | 24 hours | Blocks merge and release |
| High | 24 hours | 7 days | Blocks merge and release |
| Medium | 3 days | 30 days | Release-owner review if unresolved |
| Low | 10 days | 90 days | Track in normal backlog |
These are maximum initial targets, not promises to delay urgent work. A known active exploit, credential exposure, malicious dependency, or compromised release process is handled immediately as an incident.
15. Ownership and Escalation
15.1 Change author
The change author fixes the introduced finding or supplies the technical context needed for triage. The author cannot self-approve a high or critical exception.
15.2 Engineering owner
The owner named by the policy or normalized report investigates reachability, proposes remediation, implements compensating controls, and maintains the remediation issue.
Default policy ownership is:
- frontend team for frontend dependency findings;
- backend team for reachable Go findings;
- integration team for Kubernetes and IaC findings;
- plugin team for plugin permissions and trust;
- security owner for secrets, SAST, workflow supply chain, configuration, reporting, and general policy;
- release owner for runtime images, SBOM, integrity, and release artifacts.
15.3 Security approver
The security approver confirms classification, scope, compensating controls, duration, review cadence, and residual risk. Approval must be independent for high and critical risk.
15.4 Release owner
The release owner reviews every unresolved finding whose policy release impact
is block or release_owner_review. A release cannot proceed while a blocking
finding lacks an eligible active exception.
The review must use the decision report for the exact release commit and artifact digest.
15.5 Escalation route
Escalation proceeds from engineering owner to security owner, then repository owner, then release owner. Active exploitation or credential compromise enters the incident process immediately rather than waiting for ordinary SLA expiry.
16. Release Rules
A release security decision is valid only for the exact commit and artifacts evaluated.
Release must stop when:
- a required security gate is blocked or incomplete;
- an exception is expired, malformed, overdue, or out of scope;
- the release artifact does not match the scanned digest;
- SBOM, signature, checksum, or provenance verification fails;
- a plugin dependency is revoked or signed by an untrusted identity;
- a required report cannot be retained and reviewed;
- the release owner has not reviewed unresolved release-impacting findings.
Re-running a job without changing the underlying condition is not a risk decision.
17. Report Retention and Access
Public pull-request output should contain concise, redacted summaries and links to remediation guidance. It must not expose credentials, private vulnerability details, customer identifiers, internal infrastructure addresses, or exploit material.
Raw scanner reports may be retained as restricted CI artifacts when needed for investigation. Scanner integrations must define:
- artifact retention period;
- who can access the report;
- redaction performed before upload;
- schema and scanner version;
- target commit or digest;
- deletion or incident handling for accidentally captured secrets.
Public OSS documentation may explain the control. Sensitive incident evidence belongs in a restricted tracker or incident system.
18. Local Commands
Validate the repository policy and active exception registry:
go run ./cmd/security-policy validate \
--policy .github/security/policy.json \
--exceptions .github/security/exceptions.json
Use a deterministic time while testing fixtures:
go run ./cmd/security-policy validate \
--policy .github/security/policy.json \
--exceptions internal/securitypolicy/testdata/valid-exceptions.json \
--at 2026-09-28T02:00:00Z
Evaluate a normalized scanner report:
go run ./cmd/security-policy evaluate \
--policy .github/security/policy.json \
--exceptions .github/security/exceptions.json \
--report path/to/normalized-findings.json \
--output path/to/security-decision.json
The command exits:
0when the report is complete and no unexcepted blocking findings remain;1when the gate is blocked or incomplete;2when policy, exception, arguments, or report data are malformed.
Run the policy tests:
19. Adding a Scanner Gate
A new scanner integration is complete only when it does all of the following:
- Pins the scanner, action, and downloaded tools to reviewed immutable versions.
- Uses least-privilege workflow permissions.
- Identifies the exact target commit, lockfile, image digest, plugin digest, or release artifact.
- Runs on pull requests, default-branch pushes, merge groups when enabled, and the appropriate scheduled or release event.
- Applies a bounded timeout and one transient retry.
- Distinguishes a finding from scanner failure.
- Produces a redacted normalized report.
- Validates the report before evaluation.
- Uses the shared exception registry and evaluator.
- Uploads the required redacted evidence even when the gate fails.
- Returns the evaluator result without
continue-on-error, unconditional success, blanket severity downgrade, or broad path exclusion. - Documents the local equivalent, owner, troubleshooting, and report retention.
The exact emitted check name must be stable before it is added to the repository ruleset as a required check.
20. Changing the Policy
A policy change affects every security gate and requires:
- a dedicated pull request;
- security-owner review;
- tests for the new blocking and exception behavior;
- schema changes when the stored format changes;
- a policy-version increase for semantic behavior changes;
- migration instructions when existing exceptions or adapters are affected;
- confirmation that required check names remain stable.
Do not weaken a global threshold to make one finding pass. Add a narrow eligible exception or fix the finding.
21. Fixture Coverage
The repository tests currently prove:
- a valid exact exception passes;
- a missing accountable owner fails;
- an expired exception fails;
- wildcard rules, fingerprints, components, and versions fail;
- a requester cannot approve the same high-risk exception;
- a changed high-risk exception cannot name the PR author as approver;
- an unrelated PR author does not invalidate an unchanged exception;
- a matching exception permits an eligible high dependency finding;
- the same mechanism cannot permit a live credential;
- a reachable Go finding blocks without CVSS severity;
- a lower-severity finding is assigned for triage without blocking;
- a scanner timeout produces an incomplete gate;
- report paths, contexts, versions, component types, and required artifact digests are validated before evaluation;
- an unredacted secret report is rejected;
- non-exceptionable findings reject registry entries;
- development-image exceptions must stay development-scoped;
- a rule that forbids contextual exception remains blocked;
- an exception with a mismatched version cannot apply;
- semantic and exact version scopes match only their intended versions.
Future scanner tickets must add adapter-specific fixtures proving parsing, redaction, failure propagation, and exact target identity.
22. Known Boundaries
This control establishes policy, exception validation, and a common evaluation contract. It does not by itself install every scanner listed in the policy. Scanner-specific CI jobs are separate implementation work and must adopt this contract as they are added.
CODEOWNERS is committed in this change, but it is enforced only after the
repository ruleset requires code-owner review. Required-check configuration is
also a repository setting and must be verified after the Security policy
check name appears on a successful pull request.
The evaluator cannot prove that a human identity written into JSON is truthful. Independent GitHub review, CODEOWNERS, protected branches, and audit history provide that external evidence.
23. Contributor Checklist
Before opening a change that affects security findings or exceptions, confirm:
- the scanner still executes against the intended target;
- the report contains no secret value;
- the finding type and rule exist in policy;
- the fingerprint is stable and narrow;
- the component, path, environment, context, version, and digest are exact;
- the remediation issue is accessible to the responsible team;
- compensating controls already exist and can be checked;
- the approver is eligible and independent where required;
- expiry and next review comply with the risk class;
- release impact is understood;
- local policy tests pass.
Green CI is evidence that the defined checks completed under this policy. It is not proof that the repository contains no vulnerability. The team must continue to review coverage, new attack paths, scanner quality, and the threat model.