The Vulnerability Exceptions API module provides comprehensive functionality for managing vulnerability exceptions in InsightVM. Vulnerability exceptions allow security teams to document and track decisions about vulnerabilities that are false positives, have acceptable risk, have compensating controls, or require temporary exclusion from reporting.
- Getting Started
- Core Operations
- Status Management
- Expiration Management
- Advanced Features
- Use Cases
- API Reference
from rapid7 import InsightVMClient
# Initialize client
client = InsightVMClient()
# Access vulnerability exceptions API
exceptions_api = client.vulnerability_exceptions# List all vulnerability exceptions
exceptions = client.vulnerability_exceptions.list(page=0, size=50)
# Get details of a specific exception
exception = client.vulnerability_exceptions.get_exception(42)
print(f"Status: {exception['state']}")
print(f"Reason: {exception['submit']['reason']}")Retrieve paginated lists of vulnerability exceptions with optional sorting.
# Get first page with default size
exceptions = client.vulnerability_exceptions.list()
# Get larger page with custom sorting
exceptions = client.vulnerability_exceptions.list(
page=0,
size=100,
sort=["submit.date,DESC", "scope.vulnerability,ASC"]
)
# Process results
for exc in exceptions['resources']:
print(f"Exception {exc['id']}: {exc['state']}")
print(f" Vulnerability: {exc['scope']['vulnerability']}")
print(f" Submitted: {exc['submit']['date']}")Retrieve comprehensive information about a specific exception.
# Get exception by ID
exception = client.vulnerability_exceptions.get_exception(42)
# Access exception details
print(f"ID: {exception['id']}")
print(f"Status: {exception['state']}")
print(f"Vulnerability: {exception['scope']['vulnerability']}")
print(f"Scope Type: {exception['scope']['type']}")
print(f"Reason: {exception['submit']['reason']}")
print(f"Submitter: {exception['submit']['name']}")
print(f"Comment: {exception['submit']['comment']}")
# Check expiration
if 'expires' in exception:
print(f"Expires: {exception['expires']}")
# Check review status
if 'review' in exception:
print(f"Reviewed by: {exception['review']['name']}")
print(f"Review date: {exception['review']['date']}")
print(f"Review comment: {exception['review']['comment']}")Create new vulnerability exceptions with various scopes.
# Create global exception for false positive
new_exception = {
"scope": {
"type": "global",
"vulnerability": "apache-httpd-cve-2002-0392"
},
"submit": {
"reason": "false_positive",
"comment": "This CVE does not apply to our Apache configuration"
},
"expires": "2025-12-31T23:59:59.000Z"
}
result = client.vulnerability_exceptions.create(new_exception)
print(f"Created exception ID: {result['id']}")# Exception for specific site
site_exception = {
"scope": {
"type": "site",
"vulnerability": "ssh-weak-mac",
"id": 5 # Site ID
},
"submit": {
"reason": "acceptable_risk",
"comment": "Internal dev environment, low risk"
},
"expires": "2026-01-31T23:59:59.000Z"
}
result = client.vulnerability_exceptions.create(site_exception)# Exception for asset group
group_exception = {
"scope": {
"type": "asset_group",
"vulnerability": "tls-weak-cipher",
"id": 12 # Asset Group ID
},
"submit": {
"reason": "compensating_control",
"comment": "Protected by network segmentation and IDS"
}
}
result = client.vulnerability_exceptions.create(group_exception)# Exception for specific asset and port
asset_exception = {
"scope": {
"type": "asset",
"vulnerability": "ssh-weak-cipher",
"id": 123, # Asset ID
"port": 22
},
"submit": {
"reason": "compensating_control",
"comment": "SSH access restricted by firewall rules"
}
}
result = client.vulnerability_exceptions.create(asset_exception)Remove exceptions to restore normal vulnerability reporting.
# Delete an exception
client.vulnerability_exceptions.delete_exception(42)
print("Exception deleted - vulnerability will now appear in reports")Vulnerability exceptions go through a workflow:
- under_review: Initial state when exception is submitted
- approved: Exception has been approved and suppresses findings
- rejected: Exception was rejected and does not suppress findings
- recall_requested: Approved exception is being recalled for re-review
- deleted: Exception has been removed
State Transitions:
- Submit →
under_review - Approve action →
approvedstate - Reject action →
rejectedstate - Recall action →
recall_requestedstate
# Approve an exception with comment
result = client.vulnerability_exceptions.approve(
exception_id=42,
comment="Risk accepted by security team after review"
)
print("Exception approved")
# Or use update_status directly
result = client.vulnerability_exceptions.update_status(
exception_id=42,
status="approve",
comment="Verified compensating controls are in place"
)# Reject an exception
result = client.vulnerability_exceptions.reject(
exception_id=43,
comment="Insufficient justification - requires additional controls"
)
print("Exception rejected")
# Alternative using update_status
result = client.vulnerability_exceptions.update_status(
exception_id=43,
status="reject",
comment="Does not meet security policy requirements"
)# Recall an approved exception for re-review
result = client.vulnerability_exceptions.recall(
exception_id=42,
comment="Compensating control no longer in place"
)
print("Exception recalled for review")
# Or use update_status
result = client.vulnerability_exceptions.update_status(
exception_id=42,
status="recall",
comment="Infrastructure changes require re-evaluation"
)# Check when an exception expires
expiration = client.vulnerability_exceptions.get_expiration(42)
if expiration:
print(f"Exception expires: {expiration}")
else:
print("Exception has no expiration date")# Set expiration to specific date
from datetime import datetime, timedelta
# Set to end of year
result = client.vulnerability_exceptions.set_expiration(
exception_id=42,
expiration_date="2025-12-31T23:59:59.000Z"
)
print("Expiration date set")
# Calculate dynamic expiration (90 days from now)
expiration = (datetime.now() + timedelta(days=90)).isoformat() + 'Z'
result = client.vulnerability_exceptions.set_expiration(
exception_id=42,
expiration_date=expiration
)
print(f"Exception will expire in 90 days: {expiration}")
# Set 1 year expiration
one_year = (datetime.now() + timedelta(days=365)).isoformat() + 'Z'
result = client.vulnerability_exceptions.set_expiration(
exception_id=42,
expiration_date=one_year
)# Get all exceptions (handles pagination automatically)
all_exceptions = client.vulnerability_exceptions.get_all(
sort=["submit.date,DESC"]
)
print(f"Total exceptions: {len(all_exceptions)}")
# Process all exceptions
for exc in all_exceptions:
print(f"{exc['id']}: {exc['state']} - {exc['scope']['vulnerability']}")# Get all exceptions
all_exceptions = client.vulnerability_exceptions.get_all()
# Filter by status
under_review = [e for e in all_exceptions if e['state'] == 'under_review']
approved = [e for e in all_exceptions if e['state'] == 'approved']
rejected = [e for e in all_exceptions if e['state'] == 'rejected']
print(f"Under Review: {len(under_review)}")
print(f"Approved: {len(approved)}")
print(f"Rejected: {len(rejected)}")from datetime import datetime
# Get all exceptions
all_exceptions = client.vulnerability_exceptions.get_all()
# Find expired exceptions
now = datetime.now()
expired = []
for exc in all_exceptions:
if 'expires' in exc and exc['expires']:
expiry_date = datetime.fromisoformat(
exc['expires'].replace('Z', '+00:00')
)
if expiry_date < now:
expired.append(exc)
print(f"Found {len(expired)} expired exceptions")
for exc in expired:
print(f" {exc['id']}: Expired {exc['expires']}")# Generate exception summary report
all_exceptions = client.vulnerability_exceptions.get_all()
# Analyze by reason
reasons = {}
for exc in all_exceptions:
reason = exc['submit']['reason']
reasons[reason] = reasons.get(reason, 0) + 1
print("Exception Reasons Summary:")
for reason, count in sorted(reasons.items(), key=lambda x: x[1], reverse=True):
print(f" {reason}: {count}")
# Analyze by scope type
scopes = {}
for exc in all_exceptions:
scope_type = exc['scope']['type']
scopes[scope_type] = scopes.get(scope_type, 0) + 1
print("\nException Scope Types:")
for scope, count in sorted(scopes.items(), key=lambda x: x[1], reverse=True):
print(f" {scope}: {count}")Scenario: Security team needs to mark false positive vulnerabilities to reduce noise in reports.
from rapid7 import InsightVMClient
client = InsightVMClient()
# Create false positive exception for a global vulnerability
false_positive = {
"scope": {
"type": "global",
"vulnerability": "apache-struts-cve-2017-5638"
},
"submit": {
"reason": "false_positive",
"comment": "Detection triggered by HTTP headers but Struts is not in use"
},
"expires": "2026-06-30T23:59:59.000Z"
}
result = client.vulnerability_exceptions.create(false_positive)
exception_id = result['id']
print(f"Created false positive exception: {exception_id}")
# Approve the exception
client.vulnerability_exceptions.approve(
exception_id=exception_id,
comment="Confirmed false positive through manual verification"
)
print("Exception approved - vulnerability will not appear in reports")Scenario: Accept vulnerability risk temporarily while patch testing is in progress.
from datetime import datetime, timedelta
client = InsightVMClient()
# Create temporary exception for critical patch testing
temp_exception = {
"scope": {
"type": "site",
"vulnerability": "windows-kb5001234-missing",
"id": 8 # Production site ID
},
"submit": {
"reason": "acceptable_risk",
"comment": "Patch being tested in staging, will deploy in 30 days"
}
}
result = client.vulnerability_exceptions.create(temp_exception)
exception_id = result['id']
# Set 30-day expiration
expiration = (datetime.now() + timedelta(days=30)).isoformat() + 'Z'
client.vulnerability_exceptions.set_expiration(
exception_id=exception_id,
expiration_date=expiration
)
# Approve for temporary risk acceptance
client.vulnerability_exceptions.approve(
exception_id=exception_id,
comment="Approved for 30-day testing window"
)
print(f"Temporary exception created, expires in 30 days")
print(f"Expiration date: {expiration}")Scenario: Document that compensating controls are in place for a known vulnerability.
client = InsightVMClient()
# Exception for asset with compensating controls
compensating_control = {
"scope": {
"type": "asset",
"vulnerability": "ssl-weak-protocol-tlsv1",
"id": 456, # Legacy system asset ID
"port": 443
},
"submit": {
"reason": "compensating_control",
"comment": (
"Compensating controls in place:\n"
"- Traffic restricted to trusted internal network only\n"
"- Web application firewall inspects all traffic\n"
"- Network IDS monitors for anomalies\n"
"- Legacy system scheduled for replacement in Q2"
)
}
}
result = client.vulnerability_exceptions.create(compensating_control)
exception_id = result['id']
# Set expiration to Q2 replacement date
client.vulnerability_exceptions.set_expiration(
exception_id=exception_id,
expiration_date="2026-06-30T23:59:59.000Z"
)
# Approve with additional documentation
client.vulnerability_exceptions.approve(
exception_id=exception_id,
comment="Compensating controls reviewed and approved by CISO"
)
print("Compensating control exception documented and approved")Scenario: Implement a review workflow for vulnerability exceptions.
client = InsightVMClient()
# Get all exceptions pending review
all_exceptions = client.vulnerability_exceptions.get_all()
pending_review = [
e for e in all_exceptions
if e['state'] == 'under_review'
]
print(f"Found {len(pending_review)} exceptions awaiting review")
# Review each exception
for exc in pending_review:
exception_id = exc['id']
vulnerability = exc['scope']['vulnerability']
reason = exc['submit']['reason']
comment = exc['submit']['comment']
submitter = exc['submit']['name']
print(f"\n=== Exception {exception_id} ===")
print(f"Vulnerability: {vulnerability}")
print(f"Reason: {reason}")
print(f"Justification: {comment}")
print(f"Submitted by: {submitter}")
# Example: Auto-approve false positives, manual review for others
if reason == 'false_positive':
client.vulnerability_exceptions.approve(
exception_id=exception_id,
comment="Auto-approved: False positive exceptions are pre-approved"
)
print("Status: AUTO-APPROVED")
else:
print("Status: Requires manual security team review")Scenario: Monitor and manage exception lifecycle, including expirations and recalls.
from datetime import datetime, timedelta
client = InsightVMClient()
# Get all approved exceptions
all_exceptions = client.vulnerability_exceptions.get_all()
approved = [e for e in all_exceptions if e['state'] == 'approved']
print(f"Monitoring {len(approved)} approved exceptions\n")
now = datetime.now()
expiring_soon = []
no_expiration = []
for exc in approved:
exception_id = exc['id']
vulnerability = exc['scope']['vulnerability']
# Check expiration
expiration = client.vulnerability_exceptions.get_expiration(exception_id)
if not expiration:
no_expiration.append(exc)
print(f"WARNING: Exception {exception_id} has no expiration")
print(f" Vulnerability: {vulnerability}")
print(f" Setting 1-year expiration")
# Set expiration if missing
one_year = (now + timedelta(days=365)).isoformat() + 'Z'
client.vulnerability_exceptions.set_expiration(
exception_id=exception_id,
expiration_date=one_year
)
else:
# Parse expiration date
expiry_date = datetime.fromisoformat(expiration.replace('Z', '+00:00'))
days_until_expiry = (expiry_date - now).days
if days_until_expiry < 30:
expiring_soon.append((exc, days_until_expiry))
print(f"ALERT: Exception {exception_id} expires in {days_until_expiry} days")
print(f" Vulnerability: {vulnerability}")
# Recall for review if expiring
if days_until_expiry < 7:
client.vulnerability_exceptions.recall(
exception_id=exception_id,
comment=f"Automatic recall: Exception expires in {days_until_expiry} days"
)
print(f" Action: Recalled for review")
print(f"\nSummary:")
print(f" Approved exceptions: {len(approved)}")
print(f" Expiring within 30 days: {len(expiring_soon)}")
print(f" Missing expiration: {len(no_expiration)}")def list(page: int = 0, size: int = 10, sort: Optional[List[str]] = None) -> DictRetrieve paginated list of vulnerability exceptions.
Parameters:
page(int): Zero-based page index (default: 0)size(int): Records per page, max 500 (default: 10)sort(List[str], optional): Sorting criteria ["property,ASC|DESC"]
Returns: Dict with resources, page metadata, and links
def get_exception(exception_id: int) -> DictGet detailed information about a specific exception.
Parameters:
exception_id(int): Unique identifier of the exception
Returns: Dict with complete exception details
def create(vulnerability_exception: Dict) -> DictCreate a new vulnerability exception.
Parameters:
vulnerability_exception(Dict): Exception definition with scope, submit, and optional expires
Returns: Dict with ID and links to new exception
def delete_exception(exception_id: int) -> NoneDelete a vulnerability exception.
Parameters:
exception_id(int): Unique identifier of the exception
Returns: None
def update_status(exception_id: int, status: str, comment: Optional[str] = None) -> DictUpdate exception status (approve/reject/recall).
Parameters:
exception_id(int): Unique identifier of the exceptionstatus(str): New status ("approve", "reject", or "recall")comment(str, optional): Comment explaining status change
Returns: Dict with links to updated exception
def approve(exception_id: int, comment: Optional[str] = None) -> DictApprove a vulnerability exception (convenience method).
Parameters:
exception_id(int): Unique identifier of the exceptioncomment(str, optional): Comment explaining approval
Returns: Dict with links to updated exception
def reject(exception_id: int, comment: Optional[str] = None) -> DictReject a vulnerability exception (convenience method).
Parameters:
exception_id(int): Unique identifier of the exceptioncomment(str, optional): Comment explaining rejection
Returns: Dict with links to updated exception
def recall(exception_id: int, comment: Optional[str] = None) -> DictRecall an exception for re-review (convenience method).
Parameters:
exception_id(int): Unique identifier of the exceptioncomment(str, optional): Comment explaining recall
Returns: Dict with links to updated exception
def get_expiration(exception_id: int) -> strGet the expiration date of an exception.
Parameters:
exception_id(int): Unique identifier of the exception
Returns: ISO 8601 date string or empty string if no expiration
def set_expiration(exception_id: int, expiration_date: str) -> DictSet or update exception expiration date.
Parameters:
exception_id(int): Unique identifier of the exceptionexpiration_date(str): ISO 8601 format date (e.g., "2025-12-31T23:59:59.000Z")
Returns: Dict with links to updated exception
def get_all(sort: Optional[List[str]] = None) -> List[Dict]Get all exceptions with automatic pagination.
Parameters:
sort(List[str], optional): Sorting criteria
Returns: List of all exception dictionaries
The following exception reasons are supported:
- false_positive: Vulnerability detection is incorrect
- compensating_control: Controls in place mitigate the risk
- acceptable_risk: Risk is acceptable to the organization
- other: Other justified reason (requires detailed comment)
Exceptions can be scoped to different levels:
- global: All instances of vulnerability across all assets
- site: Vulnerability instances in specific sites
- asset_group: Vulnerability instances in specific asset groups
- asset: Specific vulnerability instances on specific assets
- Always Provide Comments: Include detailed justification for exceptions
- Set Expiration Dates: Time-bound exceptions for periodic review
- Use Appropriate Scope: Apply narrowest scope necessary
- Document Compensating Controls: Detail security measures in place
- Regular Reviews: Periodically review approved exceptions
- Track Expiring Exceptions: Monitor and renew before expiration
- Workflow Consistency: Establish approval workflows
- Audit Trail: Use comment fields to maintain complete audit trail