Scan Policies
Scan policies define scanner behavior without code changes. Start with a built-in preset, then override only what your organization needs.
Which Preset Should I Use?
Recommended Settings answers this by goal, with measured numbers. Every recommended setup runs the LLM judge (--use-llm). In short:
| Scanning... | Use |
|---|---|
| Your own skills, locally, in pre-commit or in CI | low-noise with the LLM judge |
| Third-party skills, highest F1 | balanced (default) with the LLM judge; block HIGH, review MEDIUM |
| Third-party skills, lowest false-positive rate | quiet with the LLM judge, never without it |
| Audits and threat hunting | strict with the LLM judge; read everything, never a gate |
permissive turns CEL and correlation off. It has not been measured as a gate, so for fewer false
positives prefer low-noise.
Built-In Presets
| Preset | CEL / correlation | Posture | Typical Use |
|---|---|---|---|
strict | shadow / on | Maximum sensitivity | Untrusted content, compliance audits, security reviews |
balanced | shadow / on | Default blend | General CI usage, day-to-day development scanning |
permissive | off / off | Lower noise | Trusted internal workflows, known-safe skill directories |
low-noise | shadow / on | Balanced, fewer harmless flags | Your own skills, and the judge with a smaller queue: 11 rules reported at LOW; LLM findings the model rates low-confidence capped at LOW |
quiet | shadow / on | Fewest flags | Third-party skills with the judge, when review capacity is the constraint: 19 rules reported at LOW, and LLM low-confidence and contextual-risk findings capped at LOW. Not without the judge |
skill-scanner scan ./my-skill --use-llm --policy balanced
skill-scanner scan ./my-skill --use-llm --policy low-noise
skill-scanner scan ./my-skill --use-llm --policy quiet
skill-scanner scan ./my-skill --use-llm --policy strict
Override the selected policy for one invocation with
--cel-mode off|shadow|enforce. All bundled CEL gates currently remain in
per-rule shadow rollout, so global enforce mode does not suppress them until
individual promotion. ATR is an optional --rule-packs atr input and is
outside the current core + CEL release gate.
Generate and Customize a Policy
# Generate a policy YAML from a preset (any of the five)
skill-scanner generate-policy --preset low-noise -o my_policy.yaml
# Interactive TUI for editing every policy section
skill-scanner configure-policy -i my_policy.yaml -o my_policy.yaml
# Use the custom policy
skill-scanner scan ./my-skill --use-llm --policy my_policy.yaml
Merge Behavior
Custom policy files merge over defaults:
- Missing keys inherit defaults from the base preset
- Scalar fields override directly
- Lists replace defaults entirely (they do not append)
Policy Sections
Policies cover file classification, analyzer behavior, adjudication, LLM budgets, finding normalization, and rule governance.
High-Impact Sections
| Section | What It Controls |
|---|---|
pipeline | Command-chain demotion, known installer handling, trusted domains, exfil hint words |
rule_scoping | Which rules fire on which file types; docs vs code path gating |
file_limits | Maximum file count, individual file size, nesting depth |
analysis_thresholds | Analyzability risk levels, unicode steganography sensitivity |
analyzers.correlation | Bounded source/sink and staged-behavior correlation |
cel.mode | CEL decisions: off, shadow, or enforce |
severity_overrides | Per-rule severity remapping (promote or demote) |
suppressions | Silence or re-rate a rule for named skills or paths only |
disabled_rules | Switch specific rule IDs off everywhere |
adjudicator | Enable demote-only review and set the minimum false-positive confidence |
llm_analysis | Prompt/output budgets, caps on low-confidence and contextual-risk LLM findings, meta multiplier, and trusted reference domains |
All Sections
| Section | Purpose |
|---|---|
analyzers | Enable/disable core analyzers (static, bytecode, pipeline) |
file_limits | Size, count, and depth constraints |
file_classification | Binary tier mappings and hidden file allowlists |
analysis_thresholds | Analyzability scoring and unicode detection thresholds |
rule_scoping | Rule-to-filetype mapping and doc-path exclusions |
pipeline | Shell pipeline analysis tuning |
command_safety | Command risk tier classification |
severity_overrides | Per-rule severity adjustments |
suppressions | Scoped, expiring, audited exceptions for named skills or paths |
disabled_rules | Rules switched off everywhere |
finding_output | Deduplication and co-occurrence annotation settings |
metadata | Policy fingerprint and metadata attachment |
llm_analysis | LLM context/output budgets, LLM severity caps, meta multiplier, and trusted reference domains |
adjudicator | Per-finding adjudication toggle and confidence threshold |
docs_scanning | Documentation path scanning behavior |
hidden_files | Hidden file handling allowlists |
Common Policy Tweaks
Demote a Noisy Rule (Visible, Not Gating)
severity_overrides:
- rule_id: HIDDEN_DATA_FILE
severity: LOW
reason: "our skills ship dotfiles on purpose"
Promote a Rule to CRITICAL
severity_overrides:
- rule_id: DATA_EXFIL_HTTP_POST
severity: CRITICAL
reason: "any outbound POST from a skill is a blocker for us"
Suppress a Reviewed Finding for One Skill or Path
suppressions:
- rule_id: HOMOGLYPH_ATTACK
skills: ["docs-translator"]
reason: "Skill legitimately contains Cyrillic prose"
expires: 2030-12-31
A suppressed finding no longer affects the verdict or exit code, but it is kept under
suppressed_findings in JSON, and SARIF shows it as dismissed.
Switch a Rule Off Everywhere
disabled_rules:
- BINARY_FILE_DETECTED
Disabled rules produce no findings at all. Prefer a severity override or a scoped suppression.
Cap the LLM Judge's Weakest Findings
llm_analysis:
low_confidence_max_severity: LOW # on in low-noise and quiet
contextual_risk_max_severity: LOW # on in quiet
Capped findings are still reported, at LOW. Results and Tuning shows what each cap costs in recall.
Restrict File Size Limits
file_limits:
max_files: 50
max_file_size_bytes: 1048576
max_nesting_depth: 2
Adjust Analyzability Thresholds
analysis_thresholds:
analyzability_low_risk: 90
analyzability_medium_risk: 70
Trust Internal Reference Domains
llm_analysis:
trusted_reference_domains:
- git.example.com
- packages.example.com
LLM transitive-trust and supply-chain findings that reference only these domains are demoted to LOW. Keep this list narrow and organization-controlled.
Configure Adjudication
adjudicator:
enabled: true
min_fp_confidence: 4
The adjudicator is demote-only, but an incorrect LLM verdict can still demote a real threat. It is disabled by default and records its decisions for audit.
Using Policies in the SDK
from skill_scanner import SkillScanner
from skill_scanner.core.scan_policy import ScanPolicy
# Use a built-in preset
policy = ScanPolicy.from_preset("low-noise")
# Or load a custom YAML file
policy = ScanPolicy.from_yaml("my_policy.yaml")
scanner = SkillScanner(policy=policy)
result = scanner.scan_skill("/path/to/skill")
Policy in CI/CD
Commit your custom policy to the repository and reference it in CI:
skill-scanner scan-all ./skills --use-llm --policy .github/scan-policy.yaml --fail-on-severity high
Or in GitHub Actions:
with:
skill_path: .cursor/skills
policy: .github/scan-policy.yaml
use_llm: true
llm_model: anthropic/claude-sonnet-5-5
Full Reference
For exhaustive knob-by-knob documentation of every policy field, see:
- Custom Policy Configuration — full authoring guide
- Policy Quick Reference — compact field reference with defaults