Skip to Content
GovernanceSuppressions & Baselines

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

ReasonUse CaseAudit Implications
false_positiveInkog incorrectly flagged safe codeMay indicate rule needs improvement
accepted_riskRisk acknowledged, mitigation in placeRequires business justification
wont_fixKnown issue, won’t be addressedRequires documented rationale
mitigatedExternal controls reduce riskDocument 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 baseline

GitHub 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}/baseline

Returns 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:

ParameterDescription
agent_idFilter by agent
pattern_idFilter by pattern
reasonFilter by reason type
include_expiredInclude 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=csv

4. 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" }

Reference tickets for traceability:

{ "justification": "Known issue tracked in JIRA-5678. Fix in progress, ETA 2026-02-01." }
Last updated on