SkillSpector's analyzers — especially the LLM semantic ones — can produce findings that are correct in general but not actionable for your skills (framework/architectural patterns, first-party tooling conventions, accepted lab practices). A baseline lets you suppress those known findings so that:
- the risk score reflects only un-triaged issues,
- re-scans surface only new findings (incremental CI/CD), and
- every suppression carries an auditable reason.
Suppressed findings never count toward the risk score and are excluded from the
SARIF results. They are shown in the terminal/Markdown report only when you pass
--show-suppressed, and are always listed (machine-readable) in the JSON report
under suppressed / suppressed_count.
Addresses issue #88.
# 1. Accept all current findings into a baseline (run once).
skillspector baseline ./my-skill/ -o .skillspector-baseline.yaml
# 2. Commit the baseline, then scan against it. Only NEW findings are reported.
skillspector scan ./my-skill/ --baseline .skillspector-baseline.yaml
# Review what was suppressed.
skillspector scan ./my-skill/ --baseline .skillspector-baseline.yaml --show-suppressed| Command / option | Description |
|---|---|
skillspector baseline <path> [-o FILE] [--no-llm] [--reason TEXT] |
Scan and write a baseline that fingerprint-suppresses every current finding. Default output: .skillspector-baseline.yaml. |
skillspector scan <path> --baseline FILE (-b) |
Suppress findings matching the baseline before scoring/reporting. |
skillspector scan <path> --baseline FILE --show-suppressed |
Also list the suppressed findings (they still don't affect the score). |
A missing or malformed baseline file exits with code 2.
YAML or JSON (the .json extension selects JSON output when generating). Two
complementary mechanisms:
version: 1
rules: # human-authored, glob-based, drift-tolerant
- id: "SQP-1" # glob over the finding's rule id
reason: "Trigger-phrase breadth is a description nit, not a vuln"
- id: "SSD-2"
path: "example-skill/SKILL.md" # glob over the finding's file
message: "*example false-positive phrase*" # glob over the finding's message
reason: "False positive: benign trigger phrase, not an instruction"
fingerprints: # machine-generated, exact
- hash: "sha256:1a2b3c4d5e6f7081"
rule_id: "SDI-2" # informational (for humans reading the file)
file: "example-skill/SKILL.md"
reason: "Accepted — reads its own environment for context"A finding is suppressed when every field a rule specifies matches it; unspecified fields match anything. Use this for:
- Global pattern suppression —
id: "SQP-1"(orid: "SQP-*") drops a rule or rule family across all skills. - Skill/file-scoped suppression — add
path:(and optionallymessage:) to scope the suppression to a specific skill, file, or message.
Field reference:
| Field | Matches against | Notes |
|---|---|---|
id (or rule_id) |
Finding.rule_id |
glob |
path (or file) |
Finding.file |
glob; * crosses /, ** is an alias for * |
message |
Finding.message |
glob, case-insensitive; wrap a keyword in * for substring |
reason |
— | required; recorded in reports and audits |
Glob matching uses Python's fnmatch,
so * matches across path separators (*SKILL.md matches a/b/SKILL.md).
Rules are drift-tolerant: they keep working after line numbers shift or
content is reworded.
Each entry is the stable hash of one finding
(sha256(rule_id|file|start_line|end_line|message), truncated). Generated by
skillspector baseline. Because the hash includes the line span and message,
editing a skill so a finding moves or is reworded changes its fingerprint —
regenerate the baseline after material changes, or prefer rules for
suppressions you want to survive edits.
An entry may be a bare string ("sha256:...") or a mapping with hash,
optional reason, and informational rule_id / file.
Suppression is applied in the report node (skillspector/nodes/report.py),
the single place where findings are scored and formatted, so the CLI and any
future REST API behave identically. The CLI loads the baseline file into a
skillspector.suppression.Baseline and passes it via graph state
(state["baseline"], state["show_suppressed"]); the report node partitions
findings into kept vs. suppressed via
skillspector.suppression.partition_findings.
- Triage the first scan. For genuine false positives, prefer a
rulesentry with a clearreason(drift-tolerant). For "accept everything as-is right now", runskillspector baselineto fingerprint them. - Commit the baseline file to the repo.
- In CI, run
skillspector scan <path> --baseline <file>; the build fails (exit 1) only when a new finding pushes the risk score above threshold. - Periodically review with
--show-suppressedand prune stale entries.