Policies, exceptions and release gates
A policy turns findings into a pass/fail answer. That answer drives the CI result, the GitHub pull request check and the release gate. Without a policy there is no policy-based protection: the CI result passes with no policies evaluated and the report's Policy tab shows the policy gate as Not set.
Policies
Open Governance → Policies (Admin) and create a policy. Fields:
| Field | Values | Meaning |
|---|---|---|
name | text | Shown in results |
repository_id | a repository or empty | Empty = every asset |
source_type | repository, web, cloud, ai, resilience or empty | Limit the policy to one pillar; empty = all pillars |
environment | all, development, staging, production | Which reports it applies to |
severity_threshold | NONE, INFO, LOW, MEDIUM, HIGH, CRITICAL | Fail when any active finding is at or above this |
block_severities | list | Fail on exactly these severities |
license_allow_list, license_block_list | SPDX ids, upper-case | Fail on a blocked licence, or on any licence outside the allow list. The form offers "block a family" buttons (for example all strong copyleft) |
posture_rules | object, below | Organization posture a release must meet |
Example: block criticals and highs in production and GPL licences everywhere (Admin, web session):
POST /policies
{
"name": "Production release bar",
"environment": "production",
"severity_threshold": "HIGH",
"license_block_list": ["GPL-2.0-ONLY", "GPL-3.0-ONLY", "AGPL-3.0-ONLY"]
}
Only active findings count: open and reopened ones. Fixed findings and findings under an approved exception do not.
Posture rules
A policy for no single repository may also carry posture_rules, read across the whole organization:
"posture_rules": {
"cloud_severity_threshold": "CRITICAL", // fail while any cloud account's newest report has an open CRITICAL
"block_cloud_drift": true, // fail while a cloud account drifted from its baseline
"block_open_kev": true, // fail while any asset has an open CISA KEV issue
"resilience": "no_failure", // off | no_failure | proven
"resilience_gameday_days": 0 // N: a fully passed GameDay within N days
}
Posture rules apply only where a release is gated (the CI result, the GitHub check and the release gate), never to a frozen report's own policy answer.
Exceptions
Accepting a risk or suppressing a finding is a governed decision, not a deletion. On a finding choose Request exception:
POST /security-reports/{report_id}/exceptions
{
"finding_key": "<the finding's dedupe key>",
"decision": "accepted_risk", // or "suppressed"
"owner": "payments-team",
"justification": "Only reachable from the admin VPN; fix planned for Q1 with the auth rewrite.",
"compensating_controls": ["VPN-only access", "WAF rule 9031"],
"expires_at": "2027-01-31T00:00:00Z"
}
A different Admin or Owner approves or rejects it (approved, rejected, later revoked) with a note. The approval history is kept. An expired, rejected or revoked exception stops suppressing the finding at once.
The CI result
GET /v1/security-reports/{id}/ci-result (scope gates:read) is what the CLI and CI pipelines read:
{
"report_id": "9a1e...",
"conclusion": "fail", // pass | fail | pending | error
"passed": false,
"exit_code": 1, // 0 pass, 1 fail, 2 pending or error
"summary": "FAIL: 2 policy rule(s) were violated.",
"policy": {
"outcome": "failed",
"evaluated_policy_ids": ["..."],
"violations": [
{"policy_name": "Production release bar", "rule_type": "severity_threshold",
"reason": "3 active finding(s) meet or exceed the HIGH threshold.",
"matching_values": ["CRITICAL", "HIGH"], "finding_ids": ["..."]}
]
}
}
A report that is still running answers pending; one that ended in any state other than completed answers error. A scan that assessed nothing never passes silently. With a repository baseline set, findings already present in the baseline are ignored for the gate (baseline_ignored says how many), so a pull request is judged on what it introduced.
The GitHub pull request check
With the GitHub App, each pull request gets a DVSentry security scan check: success (no policy violations), failure (violations in findings new since the baseline, with up to 50 line annotations) or neutral (some scanners did not finish, or nothing was assessed).
Release gates
A release gate compares a candidate report with an approved baseline report of the same asset.
- Mark a known-good report as a release baseline: the report's Release Gate tab, or
POST /security-reports/{id}/release-baseline. - Evaluate a newer report against it: Evaluate on the tab, or
POST /v1/security-reports/{candidate_id}/release-gate/evaluatewith{"baseline_report_id": "..."}(scopegates:evaluate). - Read the recommendation:
| Recommendation | When |
|---|---|
| Block | New or resurfaced CRITICAL or HIGH findings, a blocked licence, a new high-risk attack path, the threat model regressed to critical, or the policy gate failed |
| Conditional | New or resurfaced MEDIUM findings, unapproved licences, or unexpected new SBOM packages |
| Pass | None of the above |
A Conditional release can be approved or rejected once, with a justification, by an Admin or Owner other than the person who asked for the evaluation (POST /release-gates/{id}/approval). The decision re-signs the evidence. Download it as GET /v1/release-gates/{id}/evidence.json (Ed25519 signed) or evidence.md.
If no baseline exists yet, the tab says "No prior release baselines found" and how to create one, instead of showing a blank panel.
Regression versus release gate
- The Security Regression tab (Reports) always compares with the previous report of the same asset: it answers "what changed since last time?".
- The Release Gate compares with a baseline you approved: it answers "may this ship?".
Remediation SLAs
Open issues carry a due date by severity: CRITICAL 1 day, HIGH 7 days, MEDIUM 30 days, LOW 90 days (app/services/remediation_sla.py). The risk inbox and scorecards use them to show what is overdue.
Something missing or unclear? Tell us with Feedback in the app.