Heimdall findings represent discovered security vulnerabilities and logic flaws. Each finding contains rich metadata, remediation guidance, and a complete audit trail from discovery to resolution.
Finding Structure
Findings are stored in the findings table with comprehensive metadata:
CREATE TABLE findings (
id UUID PRIMARY KEY ,
scan_id UUID REFERENCES scans(id),
repo_id UUID REFERENCES repos(id),
-- Classification
source VARCHAR ( 50 ) NOT NULL , -- 'ai', 'semgrep', 'taint', 'config'
severity VARCHAR ( 20 ) NOT NULL , -- 'critical', 'high', 'medium', 'low'
confidence VARCHAR ( 20 ) NOT NULL , -- 'high', 'medium', 'low'
status VARCHAR ( 20 ) DEFAULT 'open' , -- 'open', 'fixed', 'accepted', 'false_positive'
-- Details
title VARCHAR ( 500 ) NOT NULL ,
description TEXT ,
cwe_id VARCHAR ( 20 ), -- e.g., 'CWE-89'
-- Location
file_path VARCHAR ( 1000 ) NOT NULL ,
line_start INTEGER NOT NULL ,
line_end INTEGER ,
code_snippet TEXT ,
-- Evidence
fingerprint VARCHAR ( 64 ) UNIQUE , -- SHA256 hash for deduplication
agent_reasoning TEXT , -- Step-by-step investigation notes
-- Validation
poc_validated BOOLEAN DEFAULT false, -- Garmr confirmed exploitability
poc_exploit_json JSONB, -- PoC script and results
-- Remediation
suggested_patch TEXT , -- Unified diff format
created_at TIMESTAMPTZ DEFAULT now (),
updated_at TIMESTAMPTZ DEFAULT now ()
);
Severity Levels
Heimdall uses a four-tier severity system:
Definition: Immediate threat to confidentiality, integrity, or availability. Exploitable without authentication.Examples:
SQL injection in public API
Authentication bypass
Remote code execution
Hardcoded admin credentials
Insecure deserialization
Response Time: Fix within 24 hoursRisk: Complete system compromise possibleDefinition: Significant security risk requiring prompt remediation. May require authentication or specific conditions.Examples:
IDOR (Insecure Direct Object Reference)
Privilege escalation
Sensitive data exposure
SSRF (Server-Side Request Forgery)
Path traversal
Response Time: Fix within 7 daysRisk: User data compromise or lateral movement possibleDefinition: Security issue that reduces overall posture but requires multiple steps to exploit.Examples:
Username enumeration
Missing rate limiting
Weak password policy
Information disclosure (non-sensitive)
Logic flaws with low impact
Response Time: Fix within 30 daysRisk: Could be chained with other vulnerabilitiesDefinition: Security concern with minimal immediate risk or exploitability.Examples:
Missing security headers
Verbose error messages
Outdated dependencies (no known CVEs)
Code quality issues
Non-security logic bugs
Response Time: Fix in next release cycleRisk: Minimal direct impact, may enable future attacks
Severity is initially assigned by the Hunt agent or static analyzer, then refined by Víðarr (adversarial verification) and Garmr (sandbox validation).
Confidence Levels
Confidence indicates certainty that the finding is a true positive:
Confidence Definition Example High Validated by Garmr PoC or obvious pattern SQL injection confirmed with working exploit Medium Strong evidence but not validated AI agent traced input to SQL query without seeing parameterization Low Potential issue requiring manual review Static pattern matched but context unclear
Findings with poc_validated = true always have high confidence, regardless of the original assessment.
Finding Sources
The source field indicates which stage discovered the finding:
Stage: Hunt (agentic discovery)Characteristics:
Context-aware vulnerabilities
Logic flaws
Business logic issues
Complex data flow analysis
Confidence: Starts at medium, upgraded by Víðarr/Garmr
Stage: Static AnalysisCharacteristics:
Pattern-based detection
Known vulnerability signatures
Fast and deterministic
May have false positives
Confidence: Medium (requires manual review)
Stage: Taint AnalysisCharacteristics:
Data flow from source to sink
Input validation gaps
Sanitization failures
Cross-function taint propagation
Confidence: Medium to high (depends on flow confidence)
Stage: Config ScanCharacteristics:
IaC misconfigurations
Weak security settings
Missing encryption
Overly permissive policies
Confidence: High (objective checks)
Viewing Findings
Access findings through the API:
curl https://app.heimdall.security/api/v1/scans/{scan_id}/findings \
-H "Authorization: Bearer YOUR_TOKEN"
Applying Patches
Some findings include AI-generated patches in unified diff format:
SELECT suggested_patch
FROM findings
WHERE id = 'YOUR_FINDING_ID' ;
Example patch:
--- src/api/search.rs
+++ src/api/search.rs
@@ -45,7 +45,8 @@
async fn search_users(query: Query<SearchParams>) -> Result<Json<Vec<User>>> {
- let sql = format!("SELECT * FROM users WHERE username LIKE '%{}%'", query.q);
- let users = sqlx::query_as::<_, User>(&sql)
+ // Use parameterized query to prevent SQL injection
+ let users = sqlx::query_as::<_, User>(
+ "SELECT * FROM users WHERE username LIKE $1")
+ .bind(format!("%{}%", query.q))
.fetch_all(&state.db)
.await?;
Ok(Json(users))
Apply via API
curl -X POST https://app.heimdall.security/api/v1/findings/{finding_id}/apply-patch \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"commit_message": "fix: prevent SQL injection in user search",
"create_branch": true,
"branch_name": "heimdall/fix-sql-injection-search"
}'
Always review AI-generated patches before merging. They may not account for all edge cases or architectural constraints.
Manual Application
Download the patch and apply locally:
# Download patch
curl https://app.heimdall.security/api/v1/findings/{finding_id}/patch \
-H "Authorization: Bearer YOUR_TOKEN" \
-o fix.patch
# Review
cat fix.patch
# Apply
git apply fix.patch
# Test
cargo test
# Commit
git add .
git commit -m "fix: apply Heimdall patch for SQL injection"
Creating Issues
Heimdall can automatically create issues in your repository:
Manual Issue Creation
curl -X POST https://app.heimdall.security/api/v1/findings/{finding_id}/create-issue \
-H "Authorization: Bearer YOUR_TOKEN"
Generated issue format:
# [ Heimdall ] SQL injection in user search endpoint
**Severity:** Critical
**Confidence:** High (validated)
**CWE:** CWE-89 (SQL Injection)
## Description
The `search_users` function in `src/api/search.rs:45` constructs SQL queries using string formatting with unsanitized user input from the `q` query parameter. An attacker can inject arbitrary SQL commands to read, modify, or delete database contents.
## Location
- **File:** `src/api/search.rs`
- **Line:** 45-48
- **Endpoint:** `GET /api/users/search?q=<input>`
## Vulnerability Details
```rust
let sql = format! ( "SELECT * FROM users WHERE username LIKE '%{}%'" , query . q);
let users = sqlx :: query_as :: < _ , User >( & sql )
. fetch_all ( & state . db)
. await ? ;
Proof of Concept
import requests
# Exfiltrate all users
payload = "' OR '1'='1"
response = requests.get( f "https://api.example.com/users/search?q= { payload } " )
print (response.json()) # Returns all users
# Drop users table
payload = "'; DROP TABLE users; --"
requests.get( f "https://api.example.com/users/search?q= { payload } " )
Recommended Fix
Use parameterized queries:
let users = sqlx :: query_as :: < _ , User >(
"SELECT * FROM users WHERE username LIKE $1" )
. bind ( format! ( "%{}%" , query . q))
. fetch_all ( & state . db)
. await ? ;
References
This issue was automatically created by Heimdall Security
### Automatic Issue Creation
Enable auto-creation for high-severity findings:
```sql
UPDATE repos
SET
issue_auto_create_enabled = true,
issue_auto_create_min_severity = 'high' -- 'critical', 'high', 'medium', 'low'
WHERE id = 'YOUR_REPO_ID';
Configuration:
await heimdall . repos . updateIssueSettings ( repoId , {
autoCreateEnabled: true ,
minSeverity: 'high' ,
requiresValidation: true // Only create for poc_validated = true
});
Automatic creation happens during the Report stage:
async fn auto_create_repo_issues ( & self , repo : & Repo ) {
if ! repo . issue_auto_create_enabled {
return ;
}
let qualifying_findings = findings
. iter ()
. filter ( | finding | {
issues :: finding_qualifies_for_auto_issue (
finding ,
& repo . issue_auto_create_min_severity,
)
})
. cloned ()
. collect :: < Vec < Finding >>();
for finding in qualifying_findings {
match issues :: create_or_link_issue (
& self . db,
self . encryption_key . as_ref (),
repo ,
& finding ,
true , // auto_created = true
) . await {
Ok (( repo_issue , was_created )) => {
if was_created {
log :: info! ( "Created issue: {}" , repo_issue . issue_url);
}
}
Err ( e ) => log :: warn! ( "Issue creation failed: {e}" ),
}
}
}
Finding Events
Every finding has a complete audit trail in the finding_events table:
CREATE TABLE finding_events (
id UUID PRIMARY KEY ,
finding_id UUID REFERENCES findings(id),
user_id UUID REFERENCES users(id),
event_type VARCHAR ( 50 ) NOT NULL , -- 'status_changed', 'severity_changed', etc.
old_value VARCHAR ( 500 ),
new_value VARCHAR ( 500 ),
comment TEXT ,
metadata JSONB,
created_at TIMESTAMPTZ DEFAULT now ()
);
Event Types
Event Type Description Triggered By discoveredFinding created during scan Hunt, Static Analysis, etc. status_changedStatus updated (open → fixed) User action or patch application severity_changedSeverity adjusted Víðarr or user override validatedPoC confirmed exploitability Garmr false_positive_markedMarked as false positive Víðarr or user issue_linkedGitHub/GitLab issue created Auto-creation or manual patch_appliedRemediation patch applied API or git integration comment_addedUser added note User annotation
Query Event History
SELECT
fe . event_type ,
fe . old_value ,
fe . new_value ,
fe . comment ,
fe . metadata ,
u . display_name as user,
fe . created_at
FROM finding_events fe
LEFT JOIN users u ON u . id = fe . user_id
WHERE fe . finding_id = 'YOUR_FINDING_ID'
ORDER BY fe . created_at ASC ;
Example timeline:
Event User Timestamp Details discoveredSystem 2026-03-12 10:23:15 Source: ai (Hunt agent) validatedSystem 2026-03-12 10:25:47 Garmr confirmed with PoC issue_linkedSystem 2026-03-12 10:26:02 Created GitHub issue #123 severity_changed[email protected] 2026-03-12 14:30:00 high → critical (exploited in wild) patch_applied[email protected] 2026-03-12 15:45:22 Applied via PR #124 status_changed[email protected] 2026-03-13 09:12:00 open → fixed (merged and deployed)
Status Workflow
Findings progress through states:
open
fixed
accepted
false_positive
Definition: Confirmed vulnerability requiring remediationActions:
Investigate
Apply patch
Create issue
Change severity
Definition: Remediation deployed to productionTransition: Set automatically when patch is applied or manually confirmedReversal: Can reopen if vulnerability recurs in future scans
Definition: Risk acknowledged but not remediated (business decision)Use Cases:
Low-risk finding with high fix cost
Mitigated by external controls
Temporary workaround in place
Requires: Comment explaining justificationDefinition: Not a real vulnerability (incorrect detection)Causes:
Missing context from static analysis
AI agent misinterpreted code
Validation on wrong code path
Effect: Excluded from counts and reports
Update Status
curl -X PATCH https://app.heimdall.security/api/v1/findings/{finding_id} \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"status": "fixed",
"comment": "Deployed fix in release v2.1.4"
}'
Fingerprinting & Deduplication
Each finding has a unique fingerprint based on:
fn make_fingerprint ( title : & str , file : & str , line : i32 ) -> String {
let input = format! ( "hunt:{title}:{file}:{line}" );
let mut hasher = Sha256 :: new ();
hasher . update ( input . as_bytes ());
hex :: encode ( hasher . finalize ())
}
This prevents duplicate findings across scans:
CREATE UNIQUE INDEX findings_fingerprint_idx ON findings(fingerprint);
If the same vulnerability appears in multiple scans, Heimdall:
Links to the original finding
Updates last_seen_scan_id
Creates a finding_recurred event
Fingerprints are line-number sensitive . Refactoring code may create “new” findings for the same vulnerability.
Next Steps
Hunt Agent Learn how vulnerabilities are discovered
Sandbox Validation Understand PoC validation with Garmr
Scan Pipeline See the complete analysis workflow
Threat Modeling Understand attack surface identification