Run security through the teams that own the software. Keep an inventory naming each service’s accountable engineering owner, trigger reviews on changes that move a trust boundary, and move findings through explicit states from reported to verified closed. Set remediation deadlines from exploitation evidence such as KEV listing, EPSS and exposure, not CVSS alone. Give each team a security champion with real time for the role, and report open risk by age so exceptions never count as fixes.
- Unknown ownership is itself a finding: nobody can accept a fix for, or retire, a service without an owner.
- Tie reviews to changes that move a trust boundary, and sample merged changes to check that the triggers fire.
- Fix ready, deployed, verified closed and exception active are different states that need different evidence.
- Prioritize with CVSS v4.0 for severity, EPSS for the likelihood of exploitation, the KEV catalog for known exploitation and your own exposure data.
- Security champions extend a small team’s reach only with a written charter, agreed time and support.
- Report capacity honestly: 5 of 8 eligible requests reviewed is 62.5%, and six review slots cap the week at 75%.
| Setting | Value | Note | Source |
|---|---|---|---|
| Technical severity | CVSS v4.0 score with its full vector string | Severity describes impact if exploited, not the likelihood that it will be. | FIRST CVSS v4.0 specification (November 2023) |
| Likelihood of exploitation | EPSS probability that the CVE is exploited in the wild in the next 30 days | — | FIRST EPSS |
| Known exploitation | Listing in CISA’s Known Exploited Vulnerabilities catalog | — | CISA KEV catalog |
| US federal civilian deadline, most urgent tier | Remediate within 3 days, then forensic triage of the asset | For example, a publicly exposed asset with a KEV-listed vulnerability that gives an attacker total control. | CISA BOD 26-04 (10 June 2026), which replaced BOD 22-01 |
- List services without an engineering owner and confirm each has an escalation date.
- Sample ten merged changes and check that every trust-boundary change triggered a review.
- Pick a KEV-listed vulnerability in your inventory and confirm its deadline came from the written remediation policy.
- Confirm the dashboard counts active exceptions as open risk, not as remediated findings.
- Check that each team shipping triggered changes has a named champion with agreed time.
- Applies to
Operating product-security practices with accountable teams. Activity counts alone do not measure reduced exposure.
- Part of
- Learning path: Build an evidence-driven security program (step 1 of 11). Workflow: Build an AppSec program
Learning goals and prerequisites
What you will learn
- Assign accountable owners and escalate unowned services.
- Set remediation deadlines from exploitation evidence and exposure.
- Enforce finding and exception state transitions with roles and evidence.
- Report review capacity and open risk with accurate denominators.
Before you start
AppSec leads and engineering managers running an established security program
- Name product owners, risk tiers, release paths and the decisions a program should support.
Refresh first: Start a minimal AppSec program with owners, triggers and metrics
Go to the guided practiceAn operating security program needs an inventory that connects software to people who can change it. Repository names alone are insufficient: a repository may produce several services, and a deployed service may combine several repositories and suppliers. Record product, deployment boundary, data/privilege consequences, accountable engineering owner, release path and support status. Unknown ownership is actionable risk because nobody can reliably accept a fix or validate a retirement.
For the fictional Elm organization, the service inventory has Checkout, Export and Notifications. The security team supplies methods and challenges evidence; it does not own every product defect. The accountable engineering lead must have authority over delivery, while a separate business role accepts residual risk within delegated limits. SSDF provides lifecycle practice guidance and SAMM supports improvement assessment, but neither substitutes for Elm's concrete escalation rule. The operating design below deliberately distinguishes queue administration from authority to accept risk.
Security review becomes scalable when tied to changes that alter trust boundaries: new externally reachable features, sensitive-data flows, identity/authorization changes, package publishers or privileged deployment paths. The trigger must be visible in the delivery system and testable against actual changes. A checkbox that engineers always answer no does not provide coverage. Sample completed changes to see whether the trigger rules recognize the intended cases.
Elm's normal review service has a two-business-day response target and capacity for six reviews weekly. The current week has eight eligible requests. Silently reviewing only the easiest six makes coverage look better while leaving the queue unmanaged. Triage by consequence, use reusable guidance for repetitive changes and escalate the two-request capacity gap to delivery leadership. A blocking rule without a response route encourages bypass; a response target without a consequence for missed work is merely aspirational.
A finding moves through distinct states: reported, triaged, action agreed, fix ready, deployed, verified closed or exception active. Each transition needs evidence and an actor. Triaging a duplicate does not mean fixing it; an active exception does not mean closing the vulnerability; a candidate passing test is not a deployed result. A service-level report should preserve those distinctions so leadership can choose whether to fund fixes, reduce exposure or accept a bounded delay.
In the exercise, the security facilitator tries to activate an exception for Export's finding on an untested WAF rule. That move is refused: only the business risk owner can accept the risk, and the request must first be recorded as exception_requested. The Export owner agreeing a fix with a due date is allowed, and so is security marking a scanner duplicate with a reference to the original. Notifications has no assigned owner, so the next action is ownership escalation, not assigning the security team an indefinite remediation promise. All evidence labels refer to fictional records; a real system should use durable accessible links and retain their relevant content.
Encode the transitions in the tracker or in a small service the tracker calls, so a finding cannot jump from triaged to closed or have its risk accepted by the wrong role. Each allowed move names the roles that may make it and the evidence it needs. Only the business risk owner can activate an exception, and only with a scope, a tested interim control and a future expiry; only security can verify closure, and only with a release identifier and verification evidence. Exceptions stay in the open-risk set.
"""Finding states, who may move a finding between them, and the evidence each move needs."""
from datetime import date
# (from_state, to_state): (roles allowed, evidence fields required)
TRANSITIONS = {
("reported", "triaged"): ({"security", "champion"}, {"severity", "owner"}),
("reported", "duplicate"): ({"security", "champion"}, {"duplicate_of"}),
("triaged", "action_agreed"): ({"engineering_owner"}, {"due_date"}),
("triaged", "exception_requested"): ({"engineering_owner"}, {"reason"}),
("action_agreed", "fix_ready"): ({"engineering_owner"}, {"change"}),
("fix_ready", "deployed"): ({"engineering_owner"}, {"release"}),
("deployed", "verified_closed"): ({"security"}, {"release", "verification"}),
("exception_requested", "exception_active"): ({"risk_owner"}, {"scope", "interim_control", "expires"}),
("exception_active", "exception_expired"): ({"system"}, set()),
("exception_expired", "action_agreed"): ({"engineering_owner"}, {"due_date"}),
("exception_expired", "exception_requested"): ({"engineering_owner"}, {"reason"}),
}
OPEN_RISK = {"reported", "triaged", "action_agreed", "fix_ready", "deployed",
"exception_requested", "exception_active", "exception_expired"}
class TransitionError(ValueError):
pass
def move(finding, to_state, actor_role, evidence, today=None):
key = (finding["state"], to_state)
if key not in TRANSITIONS:
raise TransitionError(f"{finding['id']}: {key[0]} -> {to_state} is not allowed")
roles, required = TRANSITIONS[key]
if actor_role not in roles:
raise TransitionError(f"{finding['id']}: {actor_role} may not move {key[0]} -> {to_state}")
missing = required - {k for k, v in evidence.items() if v}
if missing:
raise TransitionError(f"{finding['id']}: missing evidence {sorted(missing)}")
if to_state == "exception_active" and date.fromisoformat(evidence["expires"]) <= (today or date.today()):
raise TransitionError(f"{finding['id']}: exception expiry must be in the future")
return {**finding, "state": to_state, **evidence}Tests pin the rules that matter most when people are under pressure: security cannot accept risk, an exception without an expiry or interim control is rejected, triage cannot skip to closed, and closure needs both a release and verification. All five pass with Python 3.14 and pytest 9.1.1; change a rule and the matching test fails in review.
from datetime import date
import pytest
from finding_states import OPEN_RISK, TransitionError, move
TODAY = date(2026, 10, 10)
def finding(state):
return {"id": "F2", "state": state}
def test_security_cannot_accept_risk():
evidence = {"scope": "export v5", "interim_control": "WAF rule 41", "expires": "2026-11-09"}
with pytest.raises(TransitionError, match="security may not"):
move(finding("exception_requested"), "exception_active", "security", evidence, TODAY)
def test_exception_needs_expiry_and_interim_control():
with pytest.raises(TransitionError, match="missing evidence"):
move(finding("exception_requested"), "exception_active", "risk_owner", {"scope": "export v5"}, TODAY)
def test_risk_owner_can_accept_a_bounded_exception():
evidence = {"scope": "export v5", "interim_control": "WAF rule 41", "expires": "2026-11-09"}
result = move(finding("exception_requested"), "exception_active", "risk_owner", evidence, TODAY)
assert result["state"] == "exception_active" and result["state"] in OPEN_RISK
def test_triage_cannot_skip_to_closed():
with pytest.raises(TransitionError, match="not allowed"):
move(finding("triaged"), "verified_closed", "security", {"release": "C8", "verification": "E2"}, TODAY)
def test_closure_needs_release_and_verification():
with pytest.raises(TransitionError, match="missing evidence"):
move(finding("deployed"), "verified_closed", "security", {"release": "C8"}, TODAY)A severity score says how bad a vulnerability could be, not how likely it is to be used against you. Combine four signals: CVSS v4.0 for technical severity (score vectors with /tools/cvss-calculator/), EPSS for the probability of exploitation in the next 30 days, CISA’s Known Exploited Vulnerabilities (KEV) catalog for evidence of exploitation in the wild, and your own data on exposure and reachability.
For US federal civilian agencies, CISA Binding Operational Directive 26-04 of 10 June 2026, which replaced BOD 22-01, sets deadlines from four facts: whether the asset is publicly exposed, whether the vulnerability is in KEV, whether exploitation can be automated, and whether it gives an attacker partial or total control. Deadlines run from three days, in the most urgent cases followed by forensic triage of the asset, to the next scheduled upgrade; a publicly exposed asset with a KEV-listed vulnerability that gives total control is in the three-day tier with triage. Other organizations can reuse the decision points without the federal deadlines.
The table shows an illustrative local policy, not a standard. Write your own numbers down, measure against them, and send each missed deadline to the risk owner as a decision instead of letting it age silently. The full decision table, with presence, reachability and asset adjustments, is in /frameworks/vulnerability-prioritization/.
| Condition | Illustrative target | Reason |
|---|---|---|
| In KEV or exploited in the wild, on an internet-exposed asset | Mitigate or fix within 3 days and check for compromise | Close to the most urgent BOD 26-04 tier |
| In KEV on an internal asset, or exposed with an EPSS score of 10% or more | 14 days | Exploitation is likely but needs more access |
| Critical or high CVSS, reachable, no exploitation evidence | 30 days | Severe if used, with no current sign of use |
| Everything else | Next planned release, at most 90 days | Batch with routine dependency updates |
A security champion is an engineer in a product team who spends part of their time on security: recognizing triggered changes, applying the team’s secure patterns, triaging findings first and bringing hard cases to the security team. Champions extend a small security team’s reach. They do not take over the product owner’s accountability, and a champion without time or support quickly becomes a name on a list. The full playbook, with a charter and metrics, is at /frameworks/security-champions-program/.
- Charter: a written role with a time allocation agreed with the engineering manager, so the work does not happen only in spare time.
- Coverage: one champion for each team that ships triggered changes; record the teams that have none as an inventory gap.
- Enablement: onboarding on the weakness classes the team actually produces, a regular session with the security team and review help within the response target.
- Work: first-line triage of scanner findings, threat-model questions for triggered changes and upkeep of the team’s secure-pattern examples.
- Measures: teams with an active champion, triggered changes reviewed within target, and repeat findings of the same class in that team.
Measure how work moves: age from confirmation to owner assignment, time to deployed fix, expired exceptions, recurrence and review coverage. Use populations that match the question. Median closure time among completed fixes can improve while the oldest unresolved issues get worse, so include open-age distributions and high-consequence backlog. Count an exception as managed open risk, not successful remediation. Otherwise the dashboard rewards paperwork that makes exposure disappear only statistically.
The weekly meeting should decide one concrete change: unblock a release fix, assign an owner, adjust a trigger or invest in a reusable control. It should not re-read every ticket. Elm's capacity shortfall suggests reserving urgent slots and teaching a repeated review pattern, but the result needs validation against subsequent missed triggers and delay. More review is not always better if it creates long queues with no security improvement. The goal is timely, accountable changes to engineering behavior with a record a later reviewer can follow.
Guided practice
Run Elm's weekly security review: set deadlines from exploitation evidence, apply the state rules, judge an exception request and handle a review queue that is larger than capacity. Elm and its data are fictional.
Supplied scenario material
Roles: owners Ada (Checkout), Ben (Export); Notifications has no owner. Security facilitator Cai; business risk owner Dana.
Policy (illustrative targets): KEV or exploited on an internet-exposed asset: 3 days and a compromise check; KEV on an internal asset, or exposed with EPSS >= 0.10: 14 days; critical or high CVSS, reachable, no exploitation evidence: 30 days; everything else: next release, at most 90 days.
Weekly review held on 2026-10-09. Findings this week (all detected 2026-10-05):
F1 Checkout (internet-exposed): library CVE added to KEV on 2026-10-04; EPSS 0.31
F2 Export (internet-exposed): CVSS-B 8.7; EPSS 0.02; reachable; not in KEV
F3 Notifications (internal): CVSS-B 9.1; not in KEV; EPSS 0.12
F4 Checkout: duplicate of F1 reported by a scanner
Requested transitions:
T1 Cai moves F2 from triaged to exception_active with expiry 2026-11-04 and a WAF rule marked "not tested"
T2 Ben moves F2 from triaged to action_agreed with due date 2026-11-04
T3 Cai moves F4 from reported to duplicate, duplicate_of F1
Review queue: 8 triggered requests, capacity 6, 5 done so far.1. Set the deadlines
Give F1 to F3 a target and due date under the policy.
Expected result and reasoning
F1: KEV on an internet-exposed asset, so 3 days with a compromise check; the clock starts at the earlier of KEV listing and detection, 2026-10-04, so due 2026-10-07 and already overdue. F2: critical-or-high, reachable, no exploitation evidence, so 30 days, due 2026-11-04. F3: internal asset with EPSS 0.12 is not covered by the 14-day row (that row needs KEV, or exposure with EPSS >= 0.10), and there is no evidence about reachability, so treat it as reachable: 30 days, due 2026-11-04.
2. Apply the state rules
Which of T1 to T3 are allowed?
Is each transition allowed?
Expected result and reasoning
T1 is rejected twice over: only the risk owner (Dana) may activate an exception, and the move from triaged must first go to exception_requested; the untested WAF rule could not support approval anyway. T2 is allowed: the owner agrees an action with a due date. T3 is allowed: security may mark a duplicate with a reference.
3. Handle the unowned service
What happens to F3 while Notifications has no owner?
Expected result and reasoning
Cai escalates ownership to engineering leadership now; the finding stays triaged with its due date running. Security does not quietly become the owner.
4. Report capacity honestly
Compute current and maximum review coverage this week.
Expected result and reasoning
Current: 5/8 = 62.5%. Even with all six slots used, coverage would be 6/8 = 75%; two requests are beyond capacity and need a decision from delivery leadership.
5. Make one decision
Choose the single most important action for this week's meeting.
Expected result and reasoning
F1: Ada mitigates now and checks Checkout for compromise, because the KEV deadline has passed. Record the overdue state for the risk owner rather than resetting the clock.
Elm, its people and the policy targets are fictional; the targets illustrate the approach and are not a standard. BOD 26-04 deadlines bind only US federal civilian agencies.
Save a draft on this device to resume later. Automatic saving begins after you choose Save. Drafts are never sent by this application; use synthetic data.
No draft saved on this device yet.
Sources & further reading
The standards, specifications and documentation behind the guidance above. Each is the evidence for a claim on this page; consult it for detail beyond the defaults and versions listed here.
- NIST SP 800-218: SSDF 1.1, final. Version 1.1 (February 2022).
- OWASP SAMM version 2 model
- OWASP Security Champions Guide
- FIRST: CVSS v4.0 Specification Document. Document version 1.2.
- FIRST: EPSS frequently asked questions (30-day probability of exploitation)
- CISA: Known Exploited Vulnerabilities Catalog
- CISA BOD 26-04: Prioritizing Security Updates Based on Risk (10 June 2026)
Reference information
- SDLC phase
- Security program