Suppressions & Baselines
Manage known findings with suppressions to focus on new issues and reduce noise in CI/CD pipelines.
Suppressions create an audit trail but reduce security visibility. Use sparingly and always provide justification.
Overview
Suppressions allow you to:
- Mark false positives - When Inkog incorrectly flags safe code
- Accept known risks - When a finding is acknowledged but not fixed
- Track remediation - When a fix is planned but not yet deployed
- Create baselines - Focus CI/CD on new findings only
Suppression Reasons
| Reason | Use Case | Audit Implications |
|---|---|---|
false_positive | Inkog incorrectly flagged safe code | May indicate rule needs improvement |
accepted_risk | Risk acknowledged, mitigation in place | Requires business justification |
wont_fix | Known issue, won’t be addressed | Requires documented rationale |
mitigated | External controls reduce risk | Document compensating controls |
Creating Suppressions
Via Dashboard
Step 1: View Finding
Click on any finding in the scan results.
Step 2: Click “Suppress”
In the finding details panel, click the “Suppress Finding” button.
Step 3: Select Reason
Choose from false_positive, accepted_risk, wont_fix, or mitigated.
Step 4: Provide Justification
Enter a clear explanation that will be part of the audit trail.
Step 5: Set Expiration (Optional)
Suppressions can expire automatically for temporary exceptions.
Via API
POST /v1/orgs/{org_id}/suppressions
Authorization: Bearer {token}
Content-Type: application/json
{
"pattern_id": "universal_prompt_injection",
"file_path": "agent/handlers/chat.py",
"line_number": 42,
"reason": "accepted_risk",
"justification": "Input sanitized by upstream middleware - see PR #1234",
"expires_at": "2026-01-01T00:00:00Z"
}Via CLI (Coming Soon)
inkog suppress \
--pattern universal_prompt_injection \
--file agent/handlers/chat.py \
--reason accepted_risk \
--justification "Input sanitized upstream"Suppression Scope
Suppressions can be scoped at different levels:
Pattern-Level (Broadest)
Suppress all findings for a pattern across the organization:
{
"pattern_id": "universal_missing_rate_limits",
"reason": "mitigated",
"justification": "Rate limiting handled by API gateway - Cloudflare"
}File-Level
Suppress all findings for a pattern in a specific file:
{
"pattern_id": "universal_hardcoded_credentials",
"file_path": "tests/fixtures/mock_credentials.py",
"reason": "false_positive",
"justification": "Test fixtures with fake credentials"
}Line-Level (Narrowest)
Suppress a specific finding at an exact location:
{
"pattern_id": "universal_sql_injection",
"file_path": "agent/db.py",
"line_number": 156,
"reason": "false_positive",
"justification": "Query uses parameterized statements - line 154"
}Agent-Level
Suppress findings for a specific agent only:
{
"agent_id": "agent_abc123",
"pattern_id": "universal_infinite_loop",
"reason": "accepted_risk",
"justification": "Agent has external timeout - Lambda 15min limit"
}Finding Hash Matching
For exact matching, Inkog computes a hash of each finding based on:
- Pattern ID
- File path
- Relative line position in file
- Code snippet context
This ensures suppressions don’t drift when code changes:
{
"finding_hash": "sha256:a1b2c3d4e5f6...",
"reason": "false_positive",
"justification": "Exact finding match by hash"
}Baseline Management
Setting a Baseline
Use diff mode to compare against a baseline scan:
# First, create a baseline
inkog -path ./agent -update-baseline
# Subsequent scans compare against baseline
inkog -path ./agent -diff
# Output shows only NEW findings since baselineGitHub Actions with Baseline
name: Inkog Security Scan
on: pull_request
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Inkog
uses: inkog-io/inkog-action@v1
with:
path: './agent'
diff: true
fail-on-findings: true # Fail only on NEW findings
api-key: ${{ secrets.INKOG_API_KEY }}Baseline Storage
Baselines are stored server-side per agent/organization:
GET /v1/orgs/{org_id}/agents/{agent_id}/baselineReturns the baseline scan metadata:
{
"baseline_scan_id": "scan_abc123",
"created_at": "2025-12-01T00:00:00Z",
"finding_count": 12,
"created_by": "user_xyz789"
}API Endpoints
Create Suppression
POST /v1/orgs/{org_id}/suppressions
Authorization: Bearer {token}Request body:
{
"pattern_id": "universal_prompt_injection",
"agent_id": "agent_abc123",
"file_path": "agent/chat.py",
"line_number": 42,
"finding_hash": "sha256:...",
"reason": "accepted_risk",
"justification": "Mitigated by input validation layer",
"expires_at": "2026-01-01T00:00:00Z"
}List Active Suppressions
GET /v1/orgs/{org_id}/suppressions
Authorization: Bearer {token}Query parameters:
| Parameter | Description |
|---|---|
agent_id | Filter by agent |
pattern_id | Filter by pattern |
reason | Filter by reason type |
include_expired | Include expired suppressions |
Response:
{
"suppressions": [
{
"id": "supp_abc123",
"pattern_id": "universal_prompt_injection",
"file_path": "agent/chat.py",
"line_number": 42,
"reason": "accepted_risk",
"justification": "Mitigated by input validation",
"created_at": "2025-12-01T00:00:00Z",
"created_by": {
"id": "user_xyz789",
"email": "alice@example.com"
},
"expires_at": "2026-01-01T00:00:00Z"
}
]
}Revoke Suppression
DELETE /v1/orgs/{org_id}/suppressions/{suppression_id}
Authorization: Bearer {token}Suppressions are soft-deleted for audit purposes:
{
"id": "supp_abc123",
"revoked_at": "2025-12-28T10:00:00Z",
"revoked_by": "user_xyz789"
}Check if Finding is Suppressed
POST /v1/orgs/{org_id}/suppressions/check
Authorization: Bearer {token}
Content-Type: application/json
{
"pattern_id": "universal_prompt_injection",
"file_path": "agent/chat.py",
"line_number": 42,
"finding_hash": "sha256:..."
}Response:
{
"is_suppressed": true,
"suppression": {
"id": "supp_abc123",
"reason": "accepted_risk",
"justification": "Mitigated by input validation",
"expires_at": "2026-01-01T00:00:00Z"
}
}Audit Trail
All suppression actions are logged:
{
"event_type": "suppression.created",
"actor_id": "user_abc123",
"org_id": "org_xyz789",
"resource_type": "suppression",
"resource_id": "supp_def456",
"metadata": {
"pattern_id": "universal_prompt_injection",
"reason": "accepted_risk",
"expires_at": "2026-01-01T00:00:00Z"
}
}Best Practices
1. Always Provide Justification
Suppressions without context are useless for audits:
// BAD
{
"reason": "false_positive"
}
// GOOD
{
"reason": "false_positive",
"justification": "Line 154 uses parameterized query - the SQL variable is never interpolated directly. See safe_query() on line 150."
}2. Use Expiration for Temporary Exceptions
Don’t create permanent suppressions for temporary situations:
{
"reason": "accepted_risk",
"justification": "Fix scheduled for Q1 2026 - JIRA-1234",
"expires_at": "2026-04-01T00:00:00Z"
}3. Review Suppressions Regularly
Set up periodic reviews:
# Export suppressions for review
GET /v1/orgs/{org_id}/suppressions?format=csv4. Use Narrow Scope When Possible
Prefer line-level over file-level, file-level over pattern-level:
// AVOID: Too broad
{
"pattern_id": "universal_hardcoded_credentials"
}
// PREFER: Specific scope
{
"pattern_id": "universal_hardcoded_credentials",
"file_path": "tests/fixtures/mock_creds.py",
"justification": "Test fixtures only - never deployed"
}5. Link to Issue Trackers
Reference tickets for traceability:
{
"justification": "Known issue tracked in JIRA-5678. Fix in progress, ETA 2026-02-01."
}Related
- Organizations - Multi-org setup
- Audit Logging - Activity tracking
- Diff Mode - CI/CD baseline comparison