
Building Vulnerability Exception Tracking System
- 152 installs
- 27.3k repo stars
- Updated August 2, 2026
- mukul975/anthropic-cybersecurity-skills
Helps with security tasks.
About
building-vulnerability-exception-tracking-system is a Claude Code skill in the Security category.
- building-vulnerability-exception-tracking-system
- Security
- AI-coding skill
Building Vulnerability Exception Tracking System by the numbers
- 152 all-time installs (skills.sh)
- +6 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #886 of 2,203 Security skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/mukul975/anthropic-cybersecurity-skills --skill building-vulnerability-exception-tracking-systemAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 152 |
|---|---|
| repo stars | ★ 27.3k |
| Last updated | August 2, 2026 |
| Repository | mukul975/anthropic-cybersecurity-skills ↗ |
What it does
Helps with security tasks.
Files
Building Vulnerability Exception Tracking System
Overview
A vulnerability exception tracking system manages cases where vulnerabilities cannot be remediated within SLA timelines. It provides structured workflows for requesting exceptions, documenting compensating controls, obtaining risk acceptance approvals, and automatically expiring exceptions when their validity period ends. This ensures organizations maintain visibility into accepted risks while complying with frameworks like PCI DSS, SOC 2, and NIST CSF.
When to Use
- When deploying or configuring building vulnerability exception tracking system capabilities in your environment
- When establishing security controls aligned to compliance requirements
- When building or improving security architecture for this domain
- When conducting security assessments that require this implementation
Prerequisites
- Python 3.9+ with
flask,sqlalchemy,requests,jinja2 - PostgreSQL or SQLite database
- Email/Slack integration for approval notifications
- Vulnerability management platform API (DefectDojo, Qualys, Tenable)
Exception Request Workflow
Exception Categories
| Category | Description | Max Duration | Approver Level |
|---|---|---|---|
| Remediation Delay | Patch available but deployment blocked | 30 days | Team Lead + Security |
| No Fix Available | Vendor has not released a patch | 90 days | Security Director |
| Business Critical | System cannot be patched without outage | 60 days | VP Engineering + CISO |
| False Positive | Finding is not a real vulnerability | Permanent | Security Analyst |
| Compensating Control | Alternative mitigation in place | 180 days | Security Architect |
Required Fields for Exception Request
exception_schema = {
"cve_id": "CVE-2024-XXXX",
"finding_id": "unique-finding-reference",
"asset_hostname": "prod-db-01.corp.local",
"severity": "high",
"cvss_score": 8.1,
"category": "remediation_delay",
"justification": "Database upgrade required before patch can be applied",
"compensating_controls": [
"WAF rule blocking exploit pattern deployed",
"Network segmentation restricting access to trusted VLANs only",
"Enhanced monitoring via Splunk alert for exploitation indicators"
],
"requested_expiration": "2024-06-15",
"requestor_email": "dbadmin@company.com",
"approver_emails": ["security-lead@company.com", "ciso@company.com"],
"risk_rating": "medium",
}Database Schema
CREATE TABLE vulnerability_exceptions (
id SERIAL PRIMARY KEY,
cve_id VARCHAR(20) NOT NULL,
finding_id VARCHAR(100) NOT NULL,
asset_hostname VARCHAR(255),
severity VARCHAR(20),
cvss_score DECIMAL(3,1),
category VARCHAR(50) NOT NULL,
justification TEXT NOT NULL,
compensating_controls TEXT,
status VARCHAR(20) DEFAULT 'pending',
requested_by VARCHAR(255) NOT NULL,
approved_by VARCHAR(255),
requested_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
approved_at TIMESTAMP,
expires_at TIMESTAMP NOT NULL,
expired BOOLEAN DEFAULT FALSE,
risk_rating VARCHAR(20),
review_notes TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE exception_audit_log (
id SERIAL PRIMARY KEY,
exception_id INTEGER REFERENCES vulnerability_exceptions(id),
action VARCHAR(50) NOT NULL,
actor VARCHAR(255) NOT NULL,
details TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_exception_status ON vulnerability_exceptions(status);
CREATE INDEX idx_exception_expires ON vulnerability_exceptions(expires_at);
CREATE INDEX idx_exception_cve ON vulnerability_exceptions(cve_id);Implementation
Exception Request API
from flask import Flask, request, jsonify
from datetime import datetime, timezone
import json
app = Flask(__name__)
@app.route("/api/exceptions", methods=["POST"])
def create_exception():
data = request.json
required = ["cve_id", "finding_id", "category", "justification", "expires_at", "requestor_email"]
for field in required:
if field not in data:
return jsonify({"error": f"Missing required field: {field}"}), 400
# Validate expiration does not exceed category maximum
max_days = {"remediation_delay": 30, "no_fix": 90, "business_critical": 60,
"false_positive": 365, "compensating_control": 180}
# Insert into database and notify approvers
return jsonify({"status": "pending", "id": "exc-12345"})
@app.route("/api/exceptions/<exc_id>/approve", methods=["POST"])
def approve_exception(exc_id):
approver = request.json.get("approver_email")
notes = request.json.get("notes", "")
# Update status to approved, record approver and timestamp
return jsonify({"status": "approved"})
@app.route("/api/exceptions/<exc_id>/reject", methods=["POST"])
def reject_exception(exc_id):
reviewer = request.json.get("reviewer_email")
reason = request.json.get("reason")
# Update status to rejected, record reviewer and reason
return jsonify({"status": "rejected"})Expiration Checker (Daily Cron)
# Check for expired exceptions daily
python3 scripts/process.py --check-expirations
# Generate monthly exception report
python3 scripts/process.py --report --output exception_report.jsonCompensating Controls Documentation
For each exception, compensating controls must address: 1. Detection: How will exploitation attempts be detected? 2. Prevention: What barriers reduce exploitation likelihood? 3. Response: What incident response procedures are in place? 4. Monitoring: What continuous monitoring ensures controls remain effective?
References
Vulnerability Exception Request Template
Exception Request Form
Vulnerability Information
- CVE ID: CVE-YYYY-NNNNN
- Finding ID: [Scanner reference number]
- Affected Asset(s): [hostname/IP]
- Severity: [Critical/High/Medium/Low]
- CVSS Score: [0.0 - 10.0]
- Discovery Date: [YYYY-MM-DD]
- Original SLA Deadline: [YYYY-MM-DD]
Exception Details
- Category: [ ] Remediation Delay [ ] No Fix Available [ ] Business Critical [ ] False Positive [ ] Compensating Control
- Requested Expiration Date: [YYYY-MM-DD]
- Justification: [Detailed explanation of why remediation cannot be completed within SLA]
Compensating Controls
1. Detection Control: [How will exploitation attempts be detected?] 2. Prevention Control: [What barriers reduce exploitation likelihood?] 3. Response Procedure: [What IR procedures are in place for this vulnerability?] 4. Monitoring: [What ongoing monitoring ensures controls remain effective?]
Risk Assessment
- Residual Risk Rating: [High/Medium/Low]
- Business Impact if Exploited: [Description]
- Likelihood of Exploitation: [High/Medium/Low]
Requestor
- Name: [Full name]
- Email: [email@company.com]
- Department: [Team/Department]
- Date: [YYYY-MM-DD]
---
Approval Section (For Approver Use)
Decision
- [ ] Approved - Exception granted with conditions below
- [ ] Rejected - See rejection reason below
- [ ] More Information Required - See notes below
Conditions (if approved)
- [List any additional conditions]
Reviewer Notes
- [Notes from security review]
Approver
- Name: [Full name]
- Title: [Job title]
- Date: [YYYY-MM-DD]
- Signature: [Digital signature or email confirmation reference]
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to the Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by the Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding any notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. Please do not remove or change
the license header comment from a contributed file except when
necessary.
Copyright 2026 mukul975
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
API Reference: Vulnerability Exception Tracking
Exception States
| State | Description |
|---|---|
| draft | Initial creation, not yet submitted |
| pending_approval | Awaiting approval chain |
| approved | All approvers accepted |
| rejected | Any approver denied |
| expired | Past expiration date |
| revoked | Manually revoked |
Approval Chain by Severity
| Severity | Approvers |
|---|---|
| Critical | Security Lead -> CISO -> Risk Committee |
| High | Security Lead -> CISO |
| Medium | Security Lead |
| Low | Security Lead |
Maximum Exception Duration
| Severity | Max Days |
|---|---|
| Critical | 30 |
| High | 90 |
| Medium | 180 |
| Low | 365 |
ServiceNow GRC API
# Create risk exception
curl -X POST "https://instance.service-now.com/api/now/table/sn_grc_exception" \
-u "user:pass" \
-H "Content-Type: application/json" \
-d '{"short_description":"CVE-2024-1234 exception","risk_score":"8.5","state":"draft"}'Archer GRC API
# Create exception record
curl -X POST "https://archer.example.com/api/core/content" \
-H "Authorization: Archer session-token=$TOKEN" \
-d '{"Content":{"LevelId":42,"FieldContents":{"1001":{"Value":"Exception for CVE-2024-1234"}}}}'Compensating Control Categories
| Category | Examples |
|---|---|
| Network | Segmentation, ACLs, micro-segmentation |
| Monitoring | Enhanced logging, alerting, SIEM rules |
| Application | WAF rules, input validation, rate limiting |
| Access | MFA, PAM, least privilege enforcement |
| Process | Manual review, change control, audit |
Standards and References - Vulnerability Exception Tracking
Primary Standards
NIST SP 800-53 Rev 5 - RA-5(5)
- Title: Vulnerability Monitoring and Scanning - Privileged Access
- URL: https://csrc.nist.gov/publications/detail/sp/800-53/rev-5/final
- Relevance: Requires organizations to track and manage vulnerability exceptions with documented risk acceptance
PCI DSS v4.0 - Compensating Controls
- URL: https://docs-prv.pcisecuritystandards.org/PCI%20DSS/Standard/PCI-DSS-v4_0.pdf
- Relevance: Appendix B defines requirements for compensating controls when a PCI requirement cannot be met as stated
ISO 27001:2022 - Clause 6.1.3
- Title: Information Security Risk Treatment
- Relevance: Risk acceptance must be formally documented with appropriate authority approval
CIS Controls v8 - Control 7
- Title: Continuous Vulnerability Management
- Sub-control 7.7: Remediate detected vulnerabilities within prescribed timelines; document exceptions with compensating controls
SOC 2 - CC3.2
- Title: Risk Assessment
- Relevance: Requires evidence of risk acceptance decisions and compensating controls documentation
Compliance Requirements for Exceptions
| Framework | Exception Requirement | Documentation Required |
|---|---|---|
| PCI DSS 4.0 | Compensating Controls Worksheet | Constraint, objective, controls, validation |
| SOC 2 Type II | Risk acceptance evidence | Approval chain, justification, review cadence |
| HIPAA | Risk analysis documentation | PHI impact, safeguards, timeline |
| NIST CSF 2.0 | Risk response decisions | Acceptance criteria, residual risk |
| ISO 27001 | Statement of Applicability | Risk owner approval, review schedule |
Workflows - Vulnerability Exception Tracking
Workflow 1: Exception Request and Approval
Steps
1. Asset owner identifies vulnerability that cannot be remediated within SLA 2. Owner submits exception request with justification and compensating controls 3. System validates request completeness and category-specific fields 4. System routes request to appropriate approver based on severity and category 5. Approver reviews justification and compensating controls 6. Approver approves, rejects, or requests additional information 7. If approved, exception is recorded with expiration date 8. Vulnerability status updated in scanner/DefectDojo to "exception_approved" 9. Audit log entry created with full approval chain
Workflow 2: Daily Expiration Check
Steps
1. Cron job queries all active exceptions with expires_at <= today + 14 days 2. For exceptions expiring within 14 days: send renewal reminder to requestor 3. For exceptions expiring within 7 days: send urgency reminder with escalation 4. For expired exceptions: update status to "expired", revert vulnerability to "open" 5. Send expiration notification to asset owner and security team 6. Regenerate SLA tracking to include re-opened findings
Workflow 3: Quarterly Exception Review
Steps
1. Generate report of all active exceptions grouped by category and severity 2. For each exception, verify compensating controls are still in place 3. Review if vendor patch has become available for "no_fix" exceptions 4. Re-assess risk rating based on current threat landscape 5. Escalate exceptions with changed risk profiles for re-approval 6. Update exception records with review notes and new risk ratings 7. Submit quarterly report to security governance committee
Workflow 4: Compensating Control Validation
Steps
1. For each active exception, extract listed compensating controls 2. Validate each control is still operational:
- WAF rules: Query WAF API for rule status
- Network segmentation: Verify firewall rules
- Monitoring alerts: Confirm SIEM rules are active and triggering
3. Flag exceptions where compensating controls have degraded 4. Notify exception requestor and security team of control failures 5. If controls cannot be restored within 48 hours, revoke exception
#!/usr/bin/env python3
"""Vulnerability exception tracking system.
Manages risk acceptance workflows for vulnerabilities that cannot be
remediated within SLA, including approval chains, expiration tracking,
and compensating control documentation.
"""
import json
import datetime
import uuid
import collections
EXCEPTION_STATES = ["draft", "pending_approval", "approved", "rejected", "expired", "revoked"]
APPROVAL_CHAIN = {
"critical": ["security_lead", "ciso", "risk_committee"],
"high": ["security_lead", "ciso"],
"medium": ["security_lead"],
"low": ["security_lead"],
}
MAX_EXCEPTION_DAYS = {
"critical": 30,
"high": 90,
"medium": 180,
"low": 365,
}
def create_exception_request(vuln_id, severity, justification, compensating_controls, requestor):
"""Create a new vulnerability exception request."""
now = datetime.datetime.utcnow()
max_days = MAX_EXCEPTION_DAYS.get(severity.lower(), 180)
chain = APPROVAL_CHAIN.get(severity.lower(), ["security_lead"])
return {
"exception_id": "EXC-" + uuid.uuid4().hex[:8].upper(),
"vuln_id": vuln_id,
"severity": severity.lower(),
"status": "draft",
"requestor": requestor,
"created_date": now.isoformat() + "Z",
"expiration_date": (now + datetime.timedelta(days=max_days)).isoformat() + "Z",
"max_duration_days": max_days,
"justification": justification,
"compensating_controls": compensating_controls,
"approval_chain": chain,
"approvals": [],
"risk_accepted": False,
}
def submit_for_approval(exception):
"""Submit exception request for approval."""
if exception["status"] != "draft":
return {"error": "Can only submit from draft state"}
exception["status"] = "pending_approval"
exception["submitted_date"] = datetime.datetime.utcnow().isoformat() + "Z"
return exception
def process_approval(exception, approver, decision, comments=""):
"""Process an approval decision."""
if exception["status"] != "pending_approval":
return {"error": "Not in pending_approval state"}
chain = exception["approval_chain"]
approved_by = [a["approver"] for a in exception["approvals"]]
next_approver_idx = len(approved_by)
if next_approver_idx >= len(chain):
return {"error": "All approvals already processed"}
if approver != chain[next_approver_idx]:
return {"error": "Not the next approver in chain. Expected: " + chain[next_approver_idx]}
exception["approvals"].append({
"approver": approver,
"decision": decision,
"comments": comments,
"timestamp": datetime.datetime.utcnow().isoformat() + "Z",
})
if decision == "rejected":
exception["status"] = "rejected"
exception["risk_accepted"] = False
elif len(exception["approvals"]) == len(chain):
if all(a["decision"] == "approved" for a in exception["approvals"]):
exception["status"] = "approved"
exception["risk_accepted"] = True
return exception
def check_expirations(exceptions):
"""Check all exceptions for expiration."""
now = datetime.datetime.now(datetime.timezone.utc)
expired = []
for exc in exceptions:
if exc["status"] != "approved":
continue
try:
exp_date = datetime.datetime.fromisoformat(exc["expiration_date"].replace("Z", "+00:00"))
if now > exp_date:
exc["status"] = "expired"
exc["risk_accepted"] = False
expired.append(exc["exception_id"])
except (ValueError, KeyError):
pass
return expired
def generate_exception_report(exceptions):
"""Generate exception tracking report."""
status_counts = collections.Counter(e["status"] for e in exceptions)
severity_counts = collections.Counter(e["severity"] for e in exceptions)
active = [e for e in exceptions if e["status"] == "approved"]
now = datetime.datetime.now(datetime.timezone.utc)
expiring_soon = []
for e in active:
try:
exp = datetime.datetime.fromisoformat(e["expiration_date"].replace("Z", "+00:00"))
days_left = (exp - now).days
if days_left <= 30:
expiring_soon.append({"exception_id": e["exception_id"], "days_remaining": days_left})
except (ValueError, KeyError):
pass
return {
"total_exceptions": len(exceptions),
"by_status": dict(status_counts),
"by_severity": dict(severity_counts),
"active_exceptions": len(active),
"expiring_within_30_days": expiring_soon,
}
if __name__ == "__main__":
print("=" * 60)
print("Vulnerability Exception Tracking System")
print("Risk acceptance workflows, approval chains, expiration tracking")
print("=" * 60)
exc1 = create_exception_request(
vuln_id="CVE-2024-1234", severity="critical",
justification="Legacy system cannot be patched without major rebuild",
compensating_controls=["Network segmentation", "Enhanced monitoring", "WAF rule"],
requestor="john.doe"
)
print("\n Created: {} for {} [{}]".format(exc1["exception_id"], exc1["vuln_id"], exc1["severity"]))
print(" Approval chain: {}".format(" -> ".join(exc1["approval_chain"])))
print(" Max duration: {} days".format(exc1["max_duration_days"]))
exc1 = submit_for_approval(exc1)
print(" Status: {}".format(exc1["status"]))
exc1 = process_approval(exc1, "security_lead", "approved", "Compensating controls adequate")
exc1 = process_approval(exc1, "ciso", "approved", "Accepted with monitoring requirement")
exc1 = process_approval(exc1, "risk_committee", "approved", "Approved for 30 days")
print(" Final status: {} (risk_accepted={})".format(exc1["status"], exc1["risk_accepted"]))
report = generate_exception_report([exc1])
print("\n--- Report ---")
for k, v in report.items():
print(" {}: {}".format(k, v))
print("\n" + json.dumps({"exceptions_tracked": 1}, indent=2))
#!/usr/bin/env python3
"""Vulnerability Exception Tracking System.
Manages vulnerability exception requests, approvals, expiration tracking,
and compensating controls documentation.
"""
import argparse
import json
import os
import sqlite3
from datetime import datetime, timedelta, timezone
from pathlib import Path
import requests
DB_PATH = os.environ.get("EXCEPTION_DB_PATH", "vulnerability_exceptions.db")
def init_db(db_path=DB_PATH):
conn = sqlite3.connect(db_path)
conn.execute("""
CREATE TABLE IF NOT EXISTS vulnerability_exceptions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
cve_id TEXT NOT NULL,
finding_id TEXT NOT NULL,
asset_hostname TEXT,
severity TEXT,
cvss_score REAL,
category TEXT NOT NULL,
justification TEXT NOT NULL,
compensating_controls TEXT,
status TEXT DEFAULT 'pending',
requested_by TEXT NOT NULL,
approved_by TEXT,
requested_at TEXT DEFAULT CURRENT_TIMESTAMP,
approved_at TEXT,
expires_at TEXT NOT NULL,
risk_rating TEXT,
review_notes TEXT,
created_at TEXT DEFAULT CURRENT_TIMESTAMP
)
""")
conn.execute("""
CREATE TABLE IF NOT EXISTS exception_audit_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
exception_id INTEGER,
action TEXT NOT NULL,
actor TEXT NOT NULL,
details TEXT,
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (exception_id) REFERENCES vulnerability_exceptions(id)
)
""")
conn.commit()
return conn
def create_exception(conn, data):
max_days = {
"remediation_delay": 30,
"no_fix": 90,
"business_critical": 60,
"false_positive": 365,
"compensating_control": 180,
}
category = data.get("category", "remediation_delay")
expires = data.get("expires_at")
if expires:
exp_date = datetime.fromisoformat(expires)
max_exp = datetime.now(timezone.utc) + timedelta(days=max_days.get(category, 30))
if exp_date.replace(tzinfo=timezone.utc) > max_exp:
print(f"[-] Expiration exceeds maximum {max_days[category]} days for {category}")
return None
cursor = conn.execute(
"""INSERT INTO vulnerability_exceptions
(cve_id, finding_id, asset_hostname, severity, cvss_score, category,
justification, compensating_controls, requested_by, expires_at, risk_rating)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
(
data["cve_id"], data["finding_id"], data.get("asset_hostname", ""),
data.get("severity", ""), data.get("cvss_score", 0),
category, data["justification"],
json.dumps(data.get("compensating_controls", [])),
data["requestor_email"], expires, data.get("risk_rating", "medium"),
),
)
exc_id = cursor.lastrowid
conn.execute(
"INSERT INTO exception_audit_log (exception_id, action, actor, details) VALUES (?, ?, ?, ?)",
(exc_id, "created", data["requestor_email"], f"Exception request for {data['cve_id']}"),
)
conn.commit()
print(f"[+] Exception created: ID {exc_id} for {data['cve_id']}")
return exc_id
def approve_exception(conn, exc_id, approver_email, notes=""):
conn.execute(
"""UPDATE vulnerability_exceptions
SET status = 'approved', approved_by = ?, approved_at = ?, review_notes = ?
WHERE id = ?""",
(approver_email, datetime.now(timezone.utc).isoformat(), notes, exc_id),
)
conn.execute(
"INSERT INTO exception_audit_log (exception_id, action, actor, details) VALUES (?, ?, ?, ?)",
(exc_id, "approved", approver_email, notes),
)
conn.commit()
print(f"[+] Exception {exc_id} approved by {approver_email}")
def reject_exception(conn, exc_id, reviewer_email, reason):
conn.execute(
"UPDATE vulnerability_exceptions SET status = 'rejected', review_notes = ? WHERE id = ?",
(reason, exc_id),
)
conn.execute(
"INSERT INTO exception_audit_log (exception_id, action, actor, details) VALUES (?, ?, ?, ?)",
(exc_id, "rejected", reviewer_email, reason),
)
conn.commit()
print(f"[+] Exception {exc_id} rejected by {reviewer_email}: {reason}")
def check_expirations(conn, slack_webhook=None):
now = datetime.now(timezone.utc).isoformat()
warn_date = (datetime.now(timezone.utc) + timedelta(days=14)).isoformat()
expiring_soon = conn.execute(
"SELECT * FROM vulnerability_exceptions WHERE status = 'approved' AND expires_at BETWEEN ? AND ?",
(now, warn_date),
).fetchall()
expired = conn.execute(
"SELECT * FROM vulnerability_exceptions WHERE status = 'approved' AND expires_at < ?",
(now,),
).fetchall()
columns = [d[0] for d in conn.execute("SELECT * FROM vulnerability_exceptions LIMIT 0").description]
for row in expired:
record = dict(zip(columns, row))
conn.execute("UPDATE vulnerability_exceptions SET status = 'expired' WHERE id = ?", (record["id"],))
conn.execute(
"INSERT INTO exception_audit_log (exception_id, action, actor, details) VALUES (?, ?, ?, ?)",
(record["id"], "expired", "system", f"Exception expired on {record['expires_at']}"),
)
print(f"[!] Exception {record['id']} ({record['cve_id']}) EXPIRED")
conn.commit()
print(f"\n[*] Expiration Check Results:")
print(f" Expired: {len(expired)}")
print(f" Expiring within 14 days: {len(expiring_soon)}")
if slack_webhook and (expired or expiring_soon):
payload = {
"text": f"Vulnerability Exception Alert: {len(expired)} expired, {len(expiring_soon)} expiring soon"
}
requests.post(slack_webhook, json=payload, timeout=10)
return {"expired": len(expired), "expiring_soon": len(expiring_soon)}
def generate_report(conn, output_path):
report = {
"generated_at": datetime.now(timezone.utc).isoformat(),
"summary": {},
"exceptions": [],
}
cursor = conn.execute(
"""SELECT status, COUNT(*) as count FROM vulnerability_exceptions GROUP BY status"""
)
for row in cursor.fetchall():
report["summary"][row[0]] = row[1]
cursor = conn.execute(
"SELECT * FROM vulnerability_exceptions ORDER BY CASE status "
"WHEN 'expired' THEN 1 WHEN 'pending' THEN 2 WHEN 'approved' THEN 3 ELSE 4 END"
)
columns = [d[0] for d in cursor.description]
for row in cursor.fetchall():
report["exceptions"].append(dict(zip(columns, row)))
with open(output_path, "w", encoding="utf-8") as f:
json.dump(report, f, indent=2)
print(f"[+] Exception report written to {output_path}")
return report
def main():
parser = argparse.ArgumentParser(description="Vulnerability Exception Tracking System")
parser.add_argument("--db", default=DB_PATH)
parser.add_argument("--create", help="JSON file with exception request")
parser.add_argument("--approve", type=int, help="Exception ID to approve")
parser.add_argument("--reject", type=int, help="Exception ID to reject")
parser.add_argument("--approver", help="Approver email")
parser.add_argument("--reason", help="Rejection reason")
parser.add_argument("--notes", default="", help="Approval notes")
parser.add_argument("--check-expirations", action="store_true")
parser.add_argument("--report", action="store_true")
parser.add_argument("--output", default="exception_report.json")
parser.add_argument("--slack-webhook", help="Slack webhook for notifications")
args = parser.parse_args()
conn = init_db(args.db)
if args.create:
with open(args.create, "r") as f:
data = json.load(f)
create_exception(conn, data)
elif args.approve and args.approver:
approve_exception(conn, args.approve, args.approver, args.notes)
elif args.reject and args.approver and args.reason:
reject_exception(conn, args.reject, args.approver, args.reason)
elif args.check_expirations:
check_expirations(conn, args.slack_webhook)
elif args.report:
generate_report(conn, args.output)
else:
parser.print_help()
conn.close()
if __name__ == "__main__":
main()