JMo Security Results Guide
The Complete Guide to Understanding, Triaging, and Acting on Your Security Scan Results
Table of Contents
- Quick Start: Understanding Your Results
- The Results Directory Structure
- Reading the Summary Report
- Understanding Compliance Reports
- Triage Workflow: What to Fix First
- Working with Findings Data
- Using the Interactive Dashboard
- Suppressing False Positives
- Integrating with Your Workflow
- Advanced: SARIF and CI/CD Integration
- EPSS/KEV Risk Prioritization
- Cross-Tool Deduplication
- Output Format Reference
- Related Documentation
Quick Start: Understanding Your Results
After running a JMo Security scan, you'll get multiple output formats. Here's where to start:
1. Start with the Summary (30 seconds)
cat results/summaries/SUMMARY.md
```text
**What you'll see:**
- Total findings count with severity breakdown (CRITICAL/HIGH/MEDIUM/LOW/INFO)
- Top files with the most findings
- Which tools found what
- Quick remediation priorities
**Example:**
```text
Total findings: 8058 | 🔴 3 CRITICAL | 🔴 91 HIGH | 🟡 280 MEDIUM | ⚪ 7391 LOW
```text
**What this means:** Most findings are LOW severity (common for code quality checks). Focus on the 3 CRITICAL and 91 HIGH first.
### 2. Open the Interactive Dashboard (2 minutes)
```bash
# Linux/WSL
xdg-open results/summaries/dashboard.html
# macOS
open results/summaries/dashboard.html
# Windows
start results/summaries/dashboard.html
```text
**What you'll see:**
- Visual charts showing severity distribution
- Filterable table of all findings
- Direct links to affected files
- Compliance framework mappings
**Pro Tip:** Use the dashboard's search/filter to focus on specific file paths, rule IDs, or severities.
### 3. Check Compliance Requirements (1 minute)
```bash
cat results/summaries/COMPLIANCE_SUMMARY.md
```text
**What you'll see:**
- Which OWASP Top 10 categories are affected
- CWE Top 25 coverage
- NIST CSF, PCI DSS, CIS Controls, MITRE ATT&CK mappings
**Example:**
```text
OWASP Top 10 2021: 4/10 categories
- A02:2021 (Cryptographic Failures): 102 findings
- A03:2021 (Injection): 301 findings
```text
**What this means:** If you need SOC 2, PCI DSS, or ISO 27001 compliance, these reports show exactly which security findings map to which requirements.
---
## The Results Directory Structure
After a scan, you'll have this directory structure:
```text
results/
├── individual-repos/ # Raw tool outputs per repository
│ └── <repo-name>/
│ ├── trivy.json
│ ├── semgrep.json
│ ├── trufflehog.json
│ └── ...
├── individual-images/ # Raw outputs per container image (v0.6.0+)
│ └── <image-name>/
│ ├── trivy.json
│ └── syft.json
├── individual-iac/ # Raw outputs per IaC file (v0.6.0+)
│ └── <file-name>/
│ └── checkov.json
├── individual-web/ # Raw outputs per web URL (v0.6.0+)
│ └── <domain>/
│ ├── zap.json
│ └── nuclei.json
├── individual-gitlab/ # Raw outputs per GitLab repo (v0.6.0+)
│ └── <group>_<repo>/
│ └── trufflehog.json
├── individual-k8s/ # Raw outputs per K8s cluster (v0.6.0+)
│ └── <context>_<namespace>/
│ └── trivy.json
└── summaries/ # Aggregated, normalized outputs
├── findings.json # All findings in CommonFinding schema
├── findings.yaml # YAML format (optional)
├── findings.sarif # SARIF 2.1.0 for CI/CD integration
├── SUMMARY.md # Human-readable summary
├── COMPLIANCE_SUMMARY.md # Multi-framework compliance report
├── PCI_DSS_COMPLIANCE.md # Detailed PCI DSS report
├── attack-navigator.json # MITRE ATT&CK Navigator visualization
├── dashboard.html # Interactive web dashboard
└── timings.json # Performance profiling (if --profile used)
```text
### Which Files to Use When
| Task | File to Use | Why |
|------|-------------|-----|
| **Quick overview** | `SUMMARY.md` | Human-readable, shows priorities |
| **Deep investigation** | `dashboard.html` | Interactive filtering, clickable links |
| **Compliance audit** | `COMPLIANCE_SUMMARY.md`, `PCI_DSS_COMPLIANCE.md` | Framework-specific reports |
| **CI/CD integration** | `findings.sarif` | Standard format for GitHub/GitLab/Azure DevOps |
| **Custom scripting** | `findings.json` | Machine-readable, stable schema |
| **Threat modeling** | `attack-navigator.json` | Import into MITRE ATT&CK Navigator |
| **Tool-specific deep dive** | `individual-*/tool.json` | Original tool output (before normalization) |
---
## Reading the Summary Report
The `SUMMARY.md` file is your starting point for triage. Here's how to read it:
### Section 1: Headline Stats
```markdown
Total findings: 8058 | 🔴 3 CRITICAL | 🔴 91 HIGH | 🟡 280 MEDIUM | ⚪ 7391 LOW
```text
**What to look for:**
- **CRITICAL/HIGH count** - Your immediate priority
- **Total vs. actionable** - If you have 8000 findings but only 94 are HIGH+, most are noise or low-priority code quality issues
### Section 2: Top Risks by File
```markdown
| File | Findings | Severity | Top Issue |
|------|----------|----------|-----------|
| tests/e2e/fixtures/iac/aws-s3-public.tf | 19 | 🔴 CRITICAL | RDS Cluster backup retention <1 day |
| Dockerfile.alpine | 6 | 🔴 CRITICAL | ':latest' tag used |
```text
**What to do:**
1. **Check if it's production code** - Test fixtures, examples, archived code can often be suppressed
2. **Assess real-world impact** - "RDS backup retention" is critical for production, not for a demo repo
3. **Prioritize by exposure** - Public-facing code (web apps, APIs) > internal tools > test code
### Section 3: By Tool
```markdown
- **trivy**: 81 findings (🔴 3 CRITICAL, 🔴 28 HIGH)
- **semgrep**: 32 findings (🔴 6 HIGH, 🟡 24 MEDIUM)
- **trufflehog**: 7 findings (🟡 7 MEDIUM)
```text
**What this tells you:**
- **Trivy found CRITICAL** - Likely container or dependency vulnerabilities (CVEs)
- **Semgrep found HIGH** - Code security issues (SQL injection, XSS, command injection)
- **TruffleHog found MEDIUM** - Potential secrets (unverified by default)
**Tool-Specific Context:**
- **TruffleHog MEDIUM = unverified secrets** - May be false positives (test keys, examples)
- **Trivy CRITICAL = CVE** - Likely real vulnerability with CVSS ≥9.0
- **Bandit LOW = assert statements** - Code quality, not security risk
### Section 4: Remediation Priorities
```markdown
1. **Fix Image user should not be 'root'** (5 findings) → Review container security
2. **Address 63 code security issues** → Review SAST findings
```text
**How to use this:**
- Start with **systemic issues** (same fix applies to multiple findings)
- Example: "User should not be root" in 5 Dockerfiles → One fix (add `USER jmo` to base Dockerfile template)
### Section 5: By Category
```markdown
- 🔧 Code Quality: 7673 findings (95% of total)
- 🛡️ Vulnerabilities: 79 findings (1% of total)
- 🔑 Secrets: 9 findings (0% of total)
```text
**What this means:**
- **95% Code Quality** - Bandit flagging `assert` statements, missing type hints, etc. (LOW priority)
- **1% Vulnerabilities** - CVEs in dependencies (CRITICAL/HIGH priority)
- **0% Secrets** - Good! But verify the 9 findings aren't real credentials
---
## Understanding Compliance Reports
JMo Security auto-enriches findings with 6 compliance frameworks. Here's how to use each:
### 1. OWASP Top 10 2021
**File:** `COMPLIANCE_SUMMARY.md` → OWASP section
**Example:**
```markdown
| Category | Findings |
|----------|----------|
| A02:2021 | 102 |
| A03:2021 | 301 |
```text
**What it means:**
- **A02:2021 - Cryptographic Failures:** 102 findings related to weak crypto, missing encryption, insecure storage
- **A03:2021 - Injection:** 301 findings related to SQL/command/code injection
**When to use:**
- **Web app security audits** - OWASP is the standard for web application risks
- **Developer training** - Show devs which OWASP categories they're triggering
- **Compliance:** Required for PCI DSS 6.2.4, SOC 2
### 2. CWE Top 25 2024
**File:** `COMPLIANCE_SUMMARY.md` → CWE section
**Example:**
```markdown
| CWE ID | Rank | Findings |
|--------|------|----------|
| CWE-798 | 18 | 7 |
```text
**What it means:**
- **CWE-798 (Rank 18):** Use of Hard-coded Credentials - 7 findings
- **Rank:** Position in MITRE's "Most Dangerous Software Weaknesses" list (1 = worst)
**When to use:**
- **CVE remediation** - CWEs are referenced in CVE descriptions
- **Secure coding standards** - Map your findings to industry-recognized weakness categories
- **Vendor assessments** - Customers often ask "Do you scan for CWE Top 25?"
### 3. NIST Cybersecurity Framework 2.0
**File:** `COMPLIANCE_SUMMARY.md` → NIST CSF section
**Example:**
```markdown
| Function | Findings |
|----------|----------|
| GOVERN | 374 |
| IDENTIFY | 8047 |
| PROTECT | 22 |
| DETECT | 7673 |
```text
**What it means:**
- **GOVERN:** Findings related to policies, risk management, supply chain (CWE-798, dependency issues)
- **IDENTIFY:** Asset management, vulnerability discovery (most SBOM findings)
- **PROTECT:** Access control, secure configuration (Dockerfile USER, file permissions)
- **DETECT:** Monitoring, logging, threat detection (code quality checks)
**When to use:**
- **Enterprise risk management** - Map security findings to business risk functions
- **Compliance:** Required for NIST 800-53, FISMA, some government contracts
- **Executive reporting** - CISOs understand NIST CSF better than CWE IDs
### 4. PCI DSS 4.0
**File:** `PCI_DSS_COMPLIANCE.md` (dedicated report)
**Example:**
```markdown
### Requirement 6.2.4: Bespoke software is developed securely
**Priority:** CRITICAL
**Findings:** 7673
```text
**What it means:**
- **Requirement 6.2.4:** All custom code must be scanned for vulnerabilities
- **7673 findings:** Includes code quality (many LOW), but also HIGH findings like SQL injection
**When to use:**
- **Payment processing apps** - Required if you handle credit cards
- **Compliance audits** - Auditors want evidence of secure development
- **Merchant onboarding** - Payment processors require PCI DSS evidence
**Pro Tip:** Filter by HIGH/CRITICAL only for audit reports. Auditors care about exploitable vulnerabilities, not `assert` statements.
### 5. CIS Controls v8.1
**File:** `COMPLIANCE_SUMMARY.md` → CIS section
**Example:**
```markdown
| Framework | Coverage |
|-----------|----------|
| CIS Controls v8.1 | 14 controls |
```text
**What it means:**
- **14 controls triggered** - Your findings map to 14 of the 18 CIS Critical Security Controls
- **Implementation Groups (IG1/IG2/IG3):** IG1 = basic cyber hygiene, IG3 = advanced controls
**When to use:**
- **Cyber insurance** - Insurers often require CIS Controls compliance
- **Benchmarking** - Compare your posture against CIS benchmarks
- **Prioritization** - Focus on IG1 controls first (foundational)
### 6. MITRE ATT&CK
**Files:**
- `COMPLIANCE_SUMMARY.md` → MITRE ATT&CK section
- `attack-navigator.json` → Import into [ATT&CK Navigator](https://mitre-attack.github.io/attack-navigator/)
**Example:**
```markdown
**Top 5 Techniques:**
1. **T1195** - Supply Chain Compromise (374 findings)
2. **T1552** - Unsecured Credentials (7 findings)
```text
**What it means:**
- **T1195 - Supply Chain Compromise:** Findings in dependencies, SBOM packages (Syft detected 374 packages)
- **T1552 - Unsecured Credentials:** Hardcoded secrets, weak crypto
**When to use:**
- **Threat modeling** - Map findings to attacker tactics (Initial Access, Persistence, etc.)
- **Purple team exercises** - Focus defensive controls on detected techniques
- **SOC analysis** - Help SOC analysts understand attack paths
**How to visualize:**
1. Open [ATT&CK Navigator](https://mitre-attack.github.io/attack-navigator/)
2. Upload `attack-navigator.json`
3. See heatmap of which techniques your findings map to
---
## Triage Workflow: What to Fix First
You've got 8000 findings. Here's how to prioritize them in 30 minutes:
### Step 1: Filter by Severity (5 minutes)
```bash
# Extract only CRITICAL and HIGH findings
jq '[.[] | select(.severity == "CRITICAL" or .severity == "HIGH")]' \
results/summaries/findings.json > critical-high.json
# Count them
jq 'length' critical-high.json
# Output: 94
```text
**Result:** You now have 94 findings to review instead of 8058.
### Step 2: Categorize by Location (10 minutes)
```bash
# Group by file path pattern
jq 'group_by(.location.path | split("/")[0:3] | join("/"))' critical-high.json > grouped.json
```text
**Categories to look for:**
1. **Production code** (`src/`, `scripts/`, root files)
2. **Test code** (`tests/`, `fixtures/`, `samples/`)
3. **Dependencies** (`.venv/`, `node_modules/`, `vendor/`)
4. **CI/CD** (`.github/`, `Dockerfile`, `docker-compose.yml`)
**Triage decision tree:**
```text
Is it in production code?
YES → Priority 1 (fix immediately)
NO → Is it in dependencies?
YES → Priority 3 (check if exploitable in production)
NO → Is it a test fixture?
YES → Priority 4 (suppress or document as intentional)
NO → Priority 2 (CI/CD hardening)
```text
### Step 3: Check for False Positives (10 minutes)
**Common false positives:**
| Tool | Rule | False Positive Pattern | How to Verify |
|------|------|------------------------|---------------|
| Bandit | B101 | `assert` statements in test files | Check file path contains `tests/` or `test_` |
| Bandit | B411 | XML parsing in PyPI packages | Check path is `.venv/lib/python3.X/site-packages/` |
| Semgrep | `run-shell-injection` | GitHub Actions `${{ github.* }}` in echo statements | Check it's not used in script execution |
| TruffleHog | Generic secrets | Example credentials in docs/README | Check for comments like `# Example (not real)` |
| Trivy | CVEs in test dependencies | Vulnerable packages only imported in tests | Check if imported in production code |
### Example: Bandit B101 in test files
```bash
# Find all B101 findings in test files
jq '.[] | select(.ruleId == "B101" and (.location.path | contains("test")))' \
critical-high.json | jq -s 'length'
# Output: 62
```text
**Decision:** Suppress B101 for test files (pytest uses `assert` extensively)
### Step 4: Identify Systemic Issues (5 minutes)
**What to look for:**
```bash
# Top 5 most common rules
jq '[.[] | .ruleId] | group_by(.) | map({rule: .[0], count: length}) | sort_by(.count) | reverse | .[0:5]' \
critical-high.json
```text
**Example output:**
```json
[
{"rule": "B101", "count": 62},
{"rule": "CVE-2023-12345", "count": 15},
{"rule": "root-user", "count": 5}
]
```text
**What this means:**
1. **B101 (62 occurrences)** - Systemic pattern (likely test files) → One suppression rule fixes all 62
2. **CVE-2023-12345 (15 occurrences)** - Same vulnerable dependency in 15 places → One `pip install --upgrade` fixes all 15
3. **root-user (5 occurrences)** - 5 Dockerfiles missing `USER` statement → One template fix
**Time savings:** Fixing 3 root causes eliminates 82 findings (87% of HIGH findings).
---
## Working with Findings Data
All findings follow the **CommonFinding schema v1.2.0**. Here's how to query them:
### Schema Overview
```json
{
"schemaVersion": "1.2.0",
"id": "ca88e028c8a99735", // Unique fingerprint
"ruleId": "DL3018", // Tool-specific rule ID
"severity": "MEDIUM", // CRITICAL/HIGH/MEDIUM/LOW/INFO
"title": "Pin versions in apk add",
"message": "Instead of `apk add <package>` use `apk add <package>=<version>`",
"tool": {
"name": "hadolint",
"version": "2.12.0"
},
"location": {
"path": "/home/user/repo/Dockerfile.alpine",
"startLine": 14,
"endLine": 14
},
"compliance": {
"owaspTop10_2021": ["A05:2021"],
"cweTop25_2024": [{"id": "CWE-1104", "rank": 23}],
"cisControlsV8_1": [{"control": "4.1", "implementationGroup": "IG1"}],
"nistCsf2_0": [{"function": "PROTECT", "category": "PR.IP", "subcategory": "PR.IP-1"}],
"pciDss4_0": [{"requirement": "2.2.1", "priority": "HIGH"}],
"mitreAttack": [{"tactic": "Initial Access", "technique": "T1190"}]
}
}
```text
### Common Queries
#### 1. Find All Secrets
```bash
jq '[.[] | select(.tags[]? == "secret" or .ruleId | contains("secret"))]' \
results/summaries/findings.json > secrets.json
```text
#### 2. Find Exploitable CVEs (CVSS ≥7.0)
```bash
jq '[.[] | select(.cvss? and (.cvss.score >= 7.0))]' \
results/summaries/findings.json > exploitable-cves.json
```text
#### 3. Find All SQL Injection Issues
```bash
jq '[.[] | select(.ruleId | contains("sql") or (.message | ascii_downcase | contains("sql injection")))]' \
results/summaries/findings.json > sql-injection.json
```text
#### 4. Get OWASP A03 (Injection) Findings
```bash
jq '[.[] | select(.compliance.owaspTop10_2021[]? == "A03:2021")]' \
results/summaries/findings.json > owasp-a03.json
```text
#### 5. Find Findings in Production Code Only
```bash
jq '[.[] | select(.location.path | contains("tests/") | not)
| select(.location.path | contains(".venv/") | not)
| select(.location.path | contains("fixtures/") | not)]' \
results/summaries/findings.json > production-only.json
```text
#### 6. Group Findings by File
```bash
jq 'group_by(.location.path)
| map({path: .[0].location.path, count: length, severities: [.[] | .severity] | unique})
| sort_by(.count) | reverse' \
results/summaries/findings.json > by-file.json
```text
---
## Using the Interactive Dashboard
The HTML dashboard is the fastest way to explore findings visually.
### Features
1. **Severity Charts**
- Pie chart: Distribution by severity
- Bar chart: Findings per tool
- Trend chart: If you run scans over time
2. **Filterable Table**
- Click column headers to sort
- Use search box to filter by file path, rule ID, message
- Click severity badges to filter by severity
3. **Direct Links**
- Click file paths to open in your editor (if configured)
- Click rule IDs to see documentation (if tool supports it)
4. **Compliance Tabs**
- Switch between OWASP, CWE, NIST, PCI DSS views
- See which findings map to which requirements
### Tips for Effective Use
**1. Start with Severity Filter**
- Click the "HIGH" severity badge to see only high-priority findings
- Review each finding's remediation field for fix guidance
**2. Use Path Filters to Focus**
- Search for `src/` to see only production code
- Search for `Dockerfile` to see container issues
- Search for `.tf` to see IaC findings
**3. Export Filtered Results**
- After filtering, use browser's "Save Page As" to save filtered view
- Or copy filtered table data to Excel/Sheets for team review
**4. Share with Non-Technical Stakeholders**
- Dashboard is self-contained (no external dependencies)
- Can be emailed or uploaded to internal wiki
- Works offline
---
## Suppressing False Positives
Once you've identified false positives, suppress them to reduce noise in future scans.
### Method 1: Create `jmo.suppress.yml`
Create `jmo.suppress.yml` in your repo root:
```yaml
suppressions:
# Suppress by fingerprint ID (most specific)
- id: "ca88e028c8a99735"
reason: "Accepted risk: Using alpine:latest for faster builds"
# Suppress by rule + path pattern
- ruleId: "B101"
reason: "pytest uses assert statements extensively"
# Suppress by path only (all findings in directory)
- path: ".venv/*"
reason: "Third-party dependencies vetted by PyPI"
# Suppress by rule + line number (very specific)
- ruleId: "run-shell-injection"
line: 74
reason: "Read-only echo of commit message in CI logs"
```text
### Method 2: Update Scan Configuration
Edit `jmo.yml`:
```yaml
# Exclude entire directories from scanning
exclude_paths:
- ".venv/"
- ".venv-*/"
- "node_modules/"
- "tests/e2e/fixtures/"
# Per-tool configuration
per_tool:
bandit:
flags:
- "--exclude"
- ".venv,.venv-pypi,.post-release-venv"
- "--skip"
- "B101,B404" # Skip assert and import-related checks
semgrep:
flags:
- "--exclude"
- "tests/e2e/fixtures/"
- "--exclude"
- "docs/archive/"
```text
### Method 3: Tool-Specific Configuration
Some tools have their own config files:
**Bandit** (`.bandit`):
```yaml
exclude_dirs:
- .venv
- tests/fixtures
- samples/
skips:
- B101 # assert_used
- B404 # import_subprocess
```text
**Semgrep** (`.semgrepignore`):
```text
.venv/
tests/e2e/fixtures/
docs/archive/
```text
### Viewing Suppressed Findings
After adding suppressions, re-run the scan:
```bash
jmo balanced --repos-dir .
cat results/summaries/SUPPRESSIONS.md
```text
**Example output:**
```markdown
# Suppression Summary
**Total Suppressions:** 1,245
## By Reason
- Third-party dependencies vetted by PyPI: 1,180
- Test fixtures with intentional vulnerabilities: 62
- Accepted risk: alpine:latest for faster builds: 3
```text
---
## Integrating with Your Workflow
### Scenario 1: Pre-Commit Hooks
**Goal:** Catch HIGH/CRITICAL issues before committing
**Setup:**
1. Add to `.pre-commit-config.yaml`:
```yaml
- repo: local
- id: jmo-security-scan
entry: bash -c 'jmo fast --repos-dir . && [ $(jq "[.[] | select(.severity == \"HIGH\" or .severity == \"CRITICAL\")] | length" results/summaries/findings.json) -eq 0 ]'
language: system
pass_filenames: false
always_run: true
```text
1. Install: `pre-commit install`
**Result:** Commits are blocked if HIGH/CRITICAL findings exist.
### Scenario 2: CI/CD Pipeline (GitHub Actions)
**Goal:** Gate deployments on security scan results
**Setup:**
Add to `.github/workflows/security-scan.yml`:
```yaml
name: Security Scan
on:
pull_request:
branches: [main]
push:
branches: [main]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run JMo Security Scan
docker run --rm -v "$(pwd):/scan" jmo-security:latest \
scan --repo /scan --profile-name balanced --human-logs
- name: Check for HIGH/CRITICAL findings
HIGH_COUNT=$(jq '[.[] | select(.severity == "HIGH" or .severity == "CRITICAL")] | length' \
results/summaries/findings.json)
if [ "$HIGH_COUNT" -gt 0 ]; then
echo "❌ Found $HIGH_COUNT HIGH/CRITICAL findings"
cat results/summaries/SUMMARY.md
exit 1
fi
- name: Upload SARIF to GitHub Security
with:
sarif_file: results/summaries/findings.sarif
- name: Archive Results
with:
name: security-scan-results
path: results/summaries/
```text
**Result:** PRs are blocked if HIGH/CRITICAL findings exist, and results appear in GitHub Security tab.
### Scenario 3: Weekly Scheduled Scans
**Goal:** Track security posture over time
**Setup:**
Add to `.github/workflows/weekly-scan.yml`:
```yaml
name: Weekly Security Audit
on:
schedule:
- cron: '0 10 * * 1' # Every Monday at 10 AM UTC
workflow_dispatch:
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Deep Scan
docker run --rm -v "$(pwd):/scan" jmo-security:latest \
scan --repo /scan --profile-name deep --human-logs --profile
- name: Generate Trend Report
# Compare to last week's results
CURRENT_HIGH=$(jq '[.[] | select(.severity == "HIGH")] | length' results/summaries/findings.json)
PREVIOUS_HIGH=$(curl -s "<https://api.github.com/repos/${{> github.repository }}/actions/artifacts" \
| jq '.artifacts[] | select(.name == "weekly-scan-results") | .id' | head -1)
echo "## Security Trend Report" >> $GITHUB_STEP_SUMMARY
echo "**Current HIGH findings:** $CURRENT_HIGH" >> $GITHUB_STEP_SUMMARY
echo "**Change from last week:** ..." >> $GITHUB_STEP_SUMMARY
- name: Upload Results
with:
name: weekly-scan-results
path: results/summaries/
retention-days: 90
```text
**Result:** Weekly audit with historical tracking.
### Scenario 4: Slack/Email Notifications
**Goal:** Alert team when new HIGH findings appear
**Setup:**
```yaml
- name: Send Slack Alert
uses: slackapi/slack-github-action@v1
with:
webhook-url: ${{ secrets.SLACK_WEBHOOK }}
payload: |
{
"text": "⚠️ Security Scan Failed",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Security scan found HIGH/CRITICAL findings*\n\nView results: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
}
}
]
}
```text
---
## Advanced: SARIF and CI/CD Integration
### What is SARIF?
**SARIF (Static Analysis Results Interchange Format)** is an industry-standard JSON format for security findings, supported by:
- GitHub Code Scanning
- GitLab SAST
- Azure DevOps
- SonarQube
- Visual Studio Code
JMo Security outputs `findings.sarif` (SARIF 2.1.0) for seamless integration.
### GitHub Code Scanning Integration
**Upload findings to GitHub Security tab:**
```yaml
- name: Upload SARIF to GitHub
with:
sarif_file: results/summaries/findings.sarif
category: jmo-security-scan
```text
**What you get:**
- Findings appear in **Security → Code scanning** tab
- Pull requests show findings as annotations
- Auto-dismiss findings when fixed
- Track trends over time
**Required permissions:**
```yaml
permissions:
security-events: write # Required for SARIF upload
contents: read
```text
### GitLab SAST Integration
**Upload to GitLab Security Dashboard:**
```yaml
security-scan:
stage: test
script:
- docker run --rm -v "$(pwd):/scan" jmo-security:latest scan --repo /scan
- cp results/summaries/findings.sarif gl-sast-report.json
reports:
sast: gl-sast-report.json
```text
**Result:** Findings appear in GitLab Security Dashboard and Merge Request widget.
### Azure DevOps Integration
**Upload to Azure DevOps Security tab:**
```yaml
- task: PublishBuildArtifacts@1
pathtoPublish: 'results/summaries/findings.sarif'
artifactName: 'CodeAnalysisLogs'
publishLocation: 'Container'
```text
---
## Real-World Triage Examples
### Example 1: You Have 8058 Findings (Like Our Sample)
**Initial Reaction:** Overwhelming! Where do I start?
**Triage Process:**
1. **Check the breakdown:**
- 3 CRITICAL
- 91 HIGH
- 280 MEDIUM
- 7,391 LOW
- 293 INFO
2. **Focus on CRITICAL (3 findings):**
- 2 in test fixtures (`tests/e2e/fixtures/iac/aws-s3-public.tf`)
- 1 in sample code (`samples/fixtures/infra-demo/`)
- **Decision:** Suppress (not production code)
3. **Triage HIGH (91 findings):**
- 83 in `.venv/` (third-party dependencies)
- 6 in `tests/e2e/fixtures/` (intentional vulnerabilities)
- 2 in `.github/workflows/` (false positive: echo statements)
- **Decision:** Suppress all 91 (see `dev-only/triage-high-findings.md`)
4. **Result:**
- **0 findings in production code requiring fixes**
- 15 minutes to triage
- 5 minutes to add suppressions
**Lesson:** Most findings in large scans are noise. Use systematic triage to find the signal.
### Example 2: You Have 50 CRITICAL Findings
**Initial Reaction:** Panic! Are we insecure?
**Triage Process:**
1. **Group by tool:**
- Trivy: 40 CRITICAL (CVEs in dependencies)
- Checkov: 10 CRITICAL (IaC misconfigurations)
2. **Investigate Trivy CVEs:**
jq '[.[] | select(.tool.name == "trivy" and .severity == "CRITICAL")]' findings.json
```
**Example finding:**
```json
{
"ruleId": "CVE-2023-12345",
"message": "SQL injection in package XYZ <1.2.3",
"cvss": {"score": 9.8},
"remediation": "Upgrade to XYZ >=1.2.4"
}
```
1. **Check if exploitable:**
- Is the vulnerable function actually used in our code?
- Use `grep -r "vulnerable_function" src/` to check
2. **Fix strategy:**
- **Immediate:** Upgrade dependencies with known exploits
- **Short-term:** Add suppressions for unused vulnerable code paths
- **Long-term:** Remove unused dependencies
3. **Result:**
- 40 CVEs → 8 actually exploitable
- Fixed with 2 `pip install --upgrade` commands
- Took 1 hour
**Lesson:** Not all CRITICAL findings are equally critical. Context matters.
---
## Compliance Audit Checklist
### Preparing for a SOC 2 Audit
**Auditor will ask:** "How do you ensure secure development?"
**Your answer:** "We scan all code with JMo Security. Here's our report:"
1. **Provide SUMMARY.md** - Show total findings and triage process
2. **Provide COMPLIANCE_SUMMARY.md** - Map findings to NIST CSF
3. **Provide SUPPRESSIONS.md** - Show false positives are documented
4. **Provide scan schedule** - Show evidence of weekly/monthly scans
**Pro Tip:** Create a "SOC 2 Evidence" folder:
```bash
mkdir -p compliance/soc2/
cp results/summaries/SUMMARY.md compliance/soc2/scan-$(date +%Y-%m-%d).md
cp results/summaries/COMPLIANCE_SUMMARY.md compliance/soc2/
```text
### Preparing for a PCI DSS Audit
**Auditor will ask:** "Do you scan custom code for vulnerabilities? (Requirement 6.2.4)"
**Your answer:** "Yes. See attached PCI DSS compliance report:"
1. **Provide PCI_DSS_COMPLIANCE.md**
2. **Filter to show only production code:**
jq '[.[] | select(.location.path | contains("tests/") | not)]' findings.json > production-findings.json
```
1. **Document remediation:**
- HIGH findings fixed within 30 days
- MEDIUM findings fixed within 90 days
**Pro Tip:** Use `--fail-on HIGH` in CI to prevent merging code with HIGH findings:
```bash
jmo ci --repo . --fail-on HIGH
```text
---
## Troubleshooting Common Issues
### Issue 1: "Too many LOW findings - can't find real issues"
**Solution:** Filter aggressively
```bash
# Create a "findings-priority.json" with only actionable items
jq '[.[] | select(.severity == "CRITICAL" or .severity == "HIGH")
| select(.location.path | contains("tests/") | not)
| select(.location.path | contains(".venv/") | not)]' \
findings.json > findings-priority.json
```text
### Issue 2: "Same CVE appears in 50 different locations"
**Solution:** Group by ruleId to see systemic issues
```bash
jq 'group_by(.ruleId) | map({rule: .[0].ruleId, count: length, severity: .[0].severity})
| sort_by(.count) | reverse' findings.json
```text
**Example output:**
```json
[
{"rule": "CVE-2023-12345", "count": 50, "severity": "HIGH"}
]
```text
**Fix:** One `pip install --upgrade vulnerable-package` fixes all 50.
### Issue 3: "Dashboard won't open / shows blank page"
**Cause:** Browser security restrictions on local HTML files with embedded JavaScript
**Solution:**
1. **Use a local web server:**
```bash
cd results/summaries
python3 -m http.server 8000
# Open http://localhost:8000/dashboard.html
```
1. **Or use `file://` with Chrome flag:**
```bash
google-chrome --allow-file-access-from-files dashboard.html
```
### Issue 4: "Compliance report shows 0 mappings"
**Cause:** Using an old version of JMo Security (pre-v0.5.1)
**Solution:** Upgrade to v0.6.2+:
```bash
pip install --upgrade jmo-security-audit
```text
Compliance auto-enrichment was added in v0.5.1.
---
## Quick Reference
### Essential Commands
```bash
# View summary
cat results/summaries/SUMMARY.md
# Count HIGH/CRITICAL findings
jq '[.[] | select(.severity == "HIGH" or .severity == "CRITICAL")] | length' results/summaries/findings.json
# Find secrets
jq '[.[] | select(.tags[]? == "secret")]' results/summaries/findings.json
# Group by file
jq 'group_by(.location.path) | map({file: .[0].location.path, count: length})' results/summaries/findings.json
# Get OWASP A03 findings
jq '[.[] | select(.compliance.owaspTop10_2021[]? == "A03:2021")]' results/summaries/findings.json
# Filter production code only
jq '[.[] | select(.location.path | contains("tests/") or contains(".venv/") | not)]' results/summaries/findings.json
```text
### File Quick Reference
| File | What It Is | When to Use |
|------|------------|-------------|
| `SUMMARY.md` | Human-readable overview | First stop for triage |
| `dashboard.html` | Interactive web UI | Deep investigation |
| `findings.json` | Machine-readable findings | Scripting, custom analysis |
| `findings.sarif` | SARIF 2.1.0 format | CI/CD, GitHub/GitLab integration |
| `COMPLIANCE_SUMMARY.md` | Multi-framework report | Compliance audits |
| `PCI_DSS_COMPLIANCE.md` | PCI DSS-specific report | Payment compliance |
| `attack-navigator.json` | MITRE ATT&CK heatmap | Threat modeling |
---
## EPSS/KEV Risk Prioritization
JMo Security automatically enriches CVE findings with EPSS (Exploit Prediction Scoring System) and CISA KEV (Known Exploited Vulnerabilities) data to help you prioritize remediation efforts based on real-world exploit activity.
### How It Works
When findings contain CVE identifiers, the system:
1. **Queries EPSS API** (FIRST.org) - Gets exploit probability (0.0-1.0) and percentile ranking
2. **Checks CISA KEV Catalog** - Identifies CVEs actively exploited in the wild
3. **Calculates Priority Score** (0-100) - Combines severity, EPSS, KEV status, and reachability
### Priority Formula
```text
severity_score = {CRITICAL: 10, HIGH: 7, MEDIUM: 4, LOW: 2, INFO: 1}
epss_multiplier = 1.0 + (epss_score * 4.0) # Scale 0.0-1.0 -> 1.0-5.0
kev_multiplier = 3.0 if is_kev else 1.0
reachability_multiplier = 1.0 # Placeholder for future enhancement
priority = (severity_score * epss_multiplier * kev_multiplier * reachability_multiplier) / 1.5
# Normalized to 0-100 scale, capped at 100
Priority Thresholds
| Priority Range | Classification | Action |
|---|---|---|
| >=80 | Critical | Immediate action required (KEV findings, high EPSS + CRITICAL severity) |
| 60-79 | High | Prioritize in next sprint (high EPSS or HIGH severity) |
| 40-59 | Medium | Address in upcoming release (moderate risk) |
| <40 | Low | Backlog (low exploitability) |
Where You'll See It
- HTML Dashboard - Priority column with color-coded badges, KEV indicator badges, sortable by priority
- SUMMARY.md - Dedicated "Priority Analysis (EPSS/KEV)" section showing:
- KEV findings (actively exploited CVEs)
- High EPSS findings (>50% exploit probability in next 30 days)
- Priority distribution (Critical/High/Medium/Low)
- Top priority findings with score breakdown
- findings.json -
priorityobject with: priority: float (0-100)epss: float (0.0-1.0, probability of exploitation)epss_percentile: float (0.0-1.0, ranking against all CVEs)is_kev: boolean (true if CISA KEV)kev_due_date: string (YYYY-MM-DD, federal agency deadline if KEV)components: dict (severity_score, epss_multiplier, kev_multiplier, breakdown)
Example SUMMARY.md Output
## Priority Analysis (EPSS/KEV)
### CISA KEV: Actively Exploited (Immediate Action Required)
1. **CVE-2024-1234** (lodash@4.17.19)
- Priority: 100/100 (CRITICAL + KEV)
- EPSS: 0.95 (95% exploit probability, 99.9th percentile)
- KEV Due Date: 2024-10-15
- Location: package.json:12
### High EPSS (>50% Exploit Probability in Next 30 Days)
1. **CVE-2024-5678** (express@4.17.1)
- Priority: 68/100 (HIGH)
- EPSS: 0.76 (76% exploit probability, 92nd percentile)
- Location: package.json:15
Caching for Performance
- EPSS: SQLite cache with 7-day TTL (~/.jmo/cache/epss.db)
- KEV: JSON cache with 1-day TTL (~/.jmo/cache/kev_catalog.json)
- Bulk API optimization: Fetches all CVEs in single API call for speed
Graceful Degradation
- If EPSS/KEV APIs unavailable, prioritization falls back to severity-only scoring
- Non-CVE findings (secrets, code quality) still receive priority scores based on severity
- No configuration required - automatic enrichment when CVEs detected
Recommendations
- Triage: Sort dashboard by priority to focus on highest-risk findings first
- SLA Management: Use KEV due dates for federal compliance or internal SLAs
- Metrics: Track "Critical Priority" count over time as a security KPI
- Communication: Share KEV count with executives ("3 actively exploited CVEs found")
Cross-Tool Deduplication
JMo Security automatically clusters duplicate findings detected by multiple tools, reducing noise by 30-40%.
How It Works
When multiple tools detect the same underlying issue, JMo clusters them into a single "consensus finding":
Before (3 separate findings):
- Trivy: HIGH - SQL Injection in app.py:42
- Semgrep: HIGH - SQL injection detected in app.py:42
- Bandit: MEDIUM - Possible SQL injection in app.py:43
After (1 consensus finding):
- Detected by 3 tools | HIGH CONFIDENCE
- Tools: trivy, semgrep, bandit
- SQL Injection vulnerability in query construction
- app.py:42-43
Confidence Levels
| Level | Tool Count | Interpretation |
|---|---|---|
| HIGH | 4+ tools | Very likely true positive |
| MEDIUM | 2-3 tools | Likely true positive |
| LOW | 1 tool | Requires validation |
Configuration
# jmo.yml
deduplication:
cross_tool_clustering: true # Enable/disable
similarity_threshold: 0.65 # Strictness (0.5-1.0, default: 0.65)
The Algorithm
Cross-tool deduplication uses a multi-dimensional similarity algorithm combining:
- Location (50%): Path + line range overlap (primary signal for same-issue detection)
- Message (25%): Fuzzy + token matching (e.g., "SQL injection" vs "SQL Injection vulnerability")
- Metadata (25%): CWE/CVE/Rule ID matching + rule equivalence mapping
Findings with >=65% similarity are clustered together. The highest-severity finding becomes the representative, and others are attached as duplicates in context.duplicates.
Algorithm Selection:
- <500 findings: Greedy algorithm (O(n×k), simpler overhead)
- ≥500 findings: LSH algorithm (O(n log n), uses locality-sensitive hashing for scalability)
Example Consensus Finding
{
"id": "cluster-abc123",
"severity": "HIGH",
"message": "SQL Injection vulnerability in query construction",
"detected_by": [
{"name": "trivy", "version": "0.50.0"},
{"name": "semgrep", "version": "1.60.0"},
{"name": "bandit", "version": "1.7.0"}
],
"confidence": {
"level": "HIGH",
"tool_count": 3,
"avg_similarity": 0.87
},
"context": {
"duplicates": [
{
"id": "fp2",
"tool": {"name": "semgrep"},
"similarity_score": 0.90
},
{
"id": "fp3",
"tool": {"name": "bandit"},
"similarity_score": 0.85
}
]
}
}
Disabling Clustering
If you prefer to see all findings from all tools separately:
This reverts to Phase 1 deduplication only (same tool, same location).
Performance
| Metric | Value |
|---|---|
| Time | <2 seconds for 1000 findings, <10 seconds for 10000 findings |
| Scalability | LSH algorithm enables O(n log n) clustering for large scans |
| Reduction | 30-40% fewer reported findings (noise elimination) |
| Accuracy | >=85% clustering accuracy (validated on 200+ finding sample) |
Best Practices
- Trust HIGH confidence findings first - Multiple tools agreeing is strong signal
- Validate MEDIUM confidence - 2 tools may still have false positives
- Review LOW confidence carefully - Single tool detections need scrutiny
- Check duplicate findings - Expand duplicates in dashboard to see all detections
Output Format Reference
JMo Security supports 7 output formats, all with a standardized metadata wrapper:
Metadata Envelope
All formats include consistent metadata:
{
"meta": {
"output_version": "1.0.0",
"jmo_version": "1.0.0",
"schema_version": "1.2.0",
"timestamp": "2025-11-04T12:34:56Z",
"scan_id": "scan-abc123",
"profile": "balanced",
"tools": ["trivy", "semgrep", "trufflehog"],
"target_count": 5,
"finding_count": 42,
"platform": "Linux"
},
"findings": [...]
}
Format Summary
| Format | File | Use Case | Size (1000 findings) |
|---|---|---|---|
| JSON | findings.json |
Machine parsing, CI/CD automation | ~500 KB |
| YAML | findings.yaml |
Configuration management, human review | ~600 KB |
| CSV | findings.csv |
Excel/Sheets analysis, stakeholder reports | ~200 KB |
| Markdown | SUMMARY.md |
Documentation, quick review | ~100 KB |
| HTML | dashboard.html |
Interactive exploration | ~1.6 MB (inline) |
| Simple HTML | simple-report.html |
Email reports, offline viewing | ~50 KB |
| SARIF | findings.sarif |
GitHub/GitLab Security, code scanning | ~700 KB |
HTML Dashboard Modes
The HTML dashboard automatically selects the optimal mode:
- Inline Mode (≤1000 findings): JSON embedded in HTML, self-contained
- External Mode (>1000 findings): JSON loaded via fetch(), prevents browser freeze
Inline Mode Features:
- Self-contained single file
- No external dependencies
- Instant loading (<500ms)
- Easy to share (drag-and-drop)
External Mode Features:
- React app + separate findings.json
- Prevents 50-100 MB files
- Loading spinner with error handling
- Tested up to 10,000+ findings
Troubleshooting External Mode:
If "Failed to load findings.json" appears:
# Serve via HTTP server
cd results/summaries
python3 -m http.server 8000
# Open http://localhost:8000/dashboard.html
Simple HTML for Email
The simple HTML reporter (simple-report.html) generates static HTML compatible with email clients:
- No JavaScript required
- Inline CSS (Gmail, Outlook, Apple Mail compatible)
- Severity color-coding
- Summary statistics
SARIF for Code Scanning
SARIF 2.1.0 format for CI/CD integration:
# GitHub Actions
- uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: results/summaries/findings.sarif
# GitLab CI
artifacts:
reports:
sast: results/summaries/findings.sarif
Configuration
Enable/disable formats in jmo.yml:
outputs:
- json # Always included (primary format)
- md # Summary markdown
- yaml # Optional (requires PyYAML)
- html # Interactive dashboard
- sarif # Code scanning platforms
- csv # Spreadsheet analysis
- simple-html # Email-compatible report
Next Steps
- Start with SUMMARY.md - Get the big picture
- Open dashboard.html - Explore interactively
- Triage HIGH/CRITICAL - Focus on production code
- Suppress false positives - Create
jmo.suppress.yml - Integrate with CI/CD - Add to GitHub Actions
- Track trends - Run weekly scans
Questions?
Related Documentation
Core Guides:
- User Guide - Complete reference documentation
- Quick Start - 5-minute setup
- Profiles and Tools - Tool lists and dependencies
Historical and Comparison:
- Historical Storage Guide - SQLite database for scan persistence
- Trend Analysis Guide - Statistical trend analysis over time
- Machine-Readable Diffs Guide - Compare two scans
Supply Chain and Compliance:
- SLSA Attestation Guide - Provenance and tamper detection
- Policy as Code - Compliance framework mappings
Integration:
- MCP Setup Guide - AI remediation orchestration
- CI/CD Integration - CI/CD integration help
- Docker Guide - Container deployment
Documentation Hub: docs/index.md | Project Home: README.md
Last Updated: February 2026