Tier 1: Static and Security Validation
Run Tier 1 directly to check a skill’s structure, security, quality, and dependencies:
Static checks need no model key. With a configured provider, this command also
runs rubric judging, LLM security analysis, and finding verification. Without a
provider, those LLM stages are explicitly reported as skipped. Use --no-llm
for static checks only, or --llm to require a provider instead of allowing a
keyless run. Full scanner coverage still requires the external tools listed in
Installation.
The two-variable provider setup is enough to configure the
LLM stages for NVIDIA Build or OpenAI. Calling skillevaluator tier1 without a
path shows help; expert subcommands remain available below.
What Tier 1 checks
The direct tier1 PATH workflow runs Tier 1 only, as does
tier1 validate PATH. The focused checks below are also available as
skillevaluator tier1 <command>. Top-level validate is the combined pipeline.
validate is the umbrella command: all three tiers run by default for skills, with autopilot creating one starter evaluation case if no source exists. Tier 1 checks always gate the exit code (non-zero on failure or incomplete required scanner evidence), Tier 2 dedup runs and gates by default, and Tier 3 is advisory by default. Use --tiers 1 for static checks only, --no-block-on-dedup to keep Tier 2 advisory, or --block-on-agent-eval to promote Tier 3 into the gate.
Run the combined or focused validation commands
Unlike direct tier1 PATH, validate includes Tier 2 and Tier 3 by default
for skills. Its Tier 1 LLM checks still run only when requested.
Run the default suite
By default you get a compact pipeline view in the terminal — a per-check ticker per tier — and the run writes HTML and JSON report files unless you pass -r explicitly, which is then honored exactly (including -r cli for terminal-only output). Add --verbose for the full per-check detail stream instead of the compact view. The full pipeline needs the provider and runtime setup; use --tiers 1 for static checks only. The exit code is 0 when every gate passes and 1 when one fails.
Run the complete offline gate
Install the scanners, then select only Tier 1 for a fully hermetic run:
With skillevaluator[security], Semgrep, SkillSpector, and Gitleaks installed, every required scanner produces evidence and the run is deterministic — the same input yields the same exit code, with nothing leaving your machine. Run skillevaluator doctor --tier 1 first to confirm that each scanner resolves and reports a supported version.
Narrow to specific checks
--checks (alias --tier1-checks) takes a comma-separated subset when you want fast, targeted feedback:
Only the selected Tier 1 checks run; the others are skipped entirely.
Add LLM analysis (optional)
With a provider configured, --llm (alias --tier1-llm; disable with --no-llm or --no-tier1-llm) adds LLM security analysis on top of the static findings, and --llm-verify runs a second pass that reviews each static finding in context:
Confirmed findings keep their original severity. High-confidence false positives stay visible but are downgraded to INFO and carry verification metadata explaining the review. Static scanning always runs first — if a requested LLM stage is unavailable or returns unusable evidence, the run does not silently turn green. security-scan and pii-scan accept --llm-verify too.
Direct tier1 PATH and tier1 validate PATH print the terminal report and
write JSON and HTML reports by default. Use -r cli for terminal-only output.
Reports for any of these runs are selected with -r/--report (cli, json, html, markdown, sarif) and written to -o/--output-dir (default reports/). File naming, BENCHMARK.md, and the JSON contract are covered in Reports & Results.
Content types
validate auto-detects what you point it at. Use --type when the path is ambiguous:
Plugins are validated against their public contract; the quality, lint, and version checks are skill-only and are skipped for plugins, rules, and workflows.
For collections, pass a folder containing skills under skills/ or team-skills/. Folder-level validation discovers each live SKILL.md below the target while excluding configured names such as evals, results, versions, .git, .venv, node_modules, and __pycache__.
Validation profiles and policies
The bundled external profile is the default public-publication policy. It requires a well-formed metadata.author value but does not restrict the email domain. Missing or malformed author attribution is HIGH severity. Blocked license findings are CRITICAL; missing or unknown licenses are reported for review.
Precedence, highest to lowest: --policy overlay, --profile flag, SKILLEVALUATOR_PROFILE environment variable, then the bundled external default.
Write a custom policy overlay
A custom policy overlays the external profile. It can narrow the accepted author domain and change severities by CATEGORY.check_name or CATEGORY.*:
Severity overrides change how findings gate a run; they do not change the
quality score or waive its --min-score requirement. Even an override of
QUALITY.*: info leaves a below-minimum quality score failing.
The active profile name is stamped into the HTML report header and BENCHMARK.md, and printed in the run banner under --verbose, so reviewers can see which gate was applied.
The checks
For validate, the default set is schema, version, security, pii, license, code-integrity, unicode, quality, and lint. The dependency check requires explicit selection with --checks (alias --tier1-checks) for validate; direct tier1 PATH includes it by default. Accepted check-name aliases include code/code-risk for code-integrity, scripts/script-lint for lint, dependencies/deps/dependency-audit for dependency, and licence/license-check for license.
schema — frontmatter and repository governance
Validates SKILL.md frontmatter and repository placement:
- YAML frontmatter must parse and satisfy the skill schema.
- Frontmatter keys, including keys nested under
metadata, must be strings. An unquoted key that YAML reads as a number, boolean, null, or date (123,yes,null,2026-01-01) is a HIGH finding that names the key in YAML form, such asField 'metadata.true': Keys should be strings; quote the key ("yes":) to keep it. Rules, workflows, and plugin manifests report non-string keys as schema errors too. nameanddescriptionare required. Names are 1–64 characters; descriptions are 1–1024 characters.- Names use kebab-case, start with a letter, and cannot have trailing or consecutive hyphens. The directory name must match the frontmatter
name. The reserved wordsanthropicandclaudemust not appear in a skill name. alwaysApplyandglobsare forbidden skill fields. Optional fields includelicense,compatibility,metadata, andallowed-tools.- The standard hierarchy is
skills/<skill-name>/orteam-skills/<team>/<skill-name>/. A nonstandard standalone location is reported as an advisory finding. - Recognized skill-root subdirectories are
agents/,references/,scripts/,assets/,evals/,tests/,tools/, andconfig/; anything else gets a LOW advisory finding. Extend the set per repository withSKILLEVALUATOR_SCHEMA_ALLOWED_DIRS(comma-separated names are added to the defaults, never replace them). SKILL.mdshould stay at or below 500 lines; larger files receive an advisory finding.- The default profile expects
metadata.authorinName <email@host>form but does not require a particular email domain. metadata.domainaccepts one category string or a list of them, such asdomain: [backend, ai-ml]. Values are trimmed and deduplicated in order; a blank or null scalar means no domain. A list with more than 16 entries, or a non-string, blank, or over-128-character entry, is a HIGHField 'metadata.domain'finding. The 128-character limit also applies to a single string.
To fix common findings: rename the directory to match name, remove forbidden fields, and move the skill under skills/.
security — vulnerability scanning
security-scan, and the security check inside validate, run SkillSpector. Static scanning covers common skill attack patterns such as prompt injection and data exfiltration; each finding carries the matched pattern, severity, and location.
The LLM pass requires a configured provider. It enriches the static result but does not replace static findings or make missing scanner evidence pass.
SkillSpector’s text pattern analyzers cannot read images, so it reports the analysis of a skill that references a PNG or JPEG file as partial. SkillEvaluator accepts that gap with a warning instead of INCOMPLETE only when the report shows no other gap and no fatal scope exclusion, and each referenced image is a regular, single-link (not hard-linked), non-executable file whose bytes have the chunk structure of a PNG or the marker structure of a JPEG and that is not reached through a symlink. The gap is accepted for at most 256 images and 128 MiB of image data in total. An image is never accepted inside a hooks, scripts, tools, agents, commands, prompts, rules, team-rules or workflows directory at any depth, or under a top-level config, skills, .mcp, .agents, .claude, .codex, .continue, .cursor, .gemini or .windsurf directory (names compared without regard to case). The structure check corroborates the file type; it does not decode the image or prove that it renders. PNG IDAT compressed data and ancillary chunks, and JPEG entropy-coded data and APPn or COM segments, are not inspected and can still carry other bytes. The image content itself is not assessed. Any other partial reason still leaves the scan INCOMPLETE. SkillSpector also reports a High AE1 (incomplete referenced artifact analysis) finding for such an image; from v2.12.0 it omits that finding when the skill renders a PNG with Markdown image syntax, such as . An AE1 finding fails the check like any other High finding.
To explain why two otherwise identical --llm scans differ, the reports record the LLM provenance that SkillSpector v2.12.0 and later return. The Security result in the JSON report carries:
skillspector_llm_provenance: SkillSpector’s provider adapter, the model and SkillSpector revision behind each LLM analyzer, the requested and forwarded temperature, seed, and reasoning-effort controls, and a determinism classification. Provider output is never guaranteed to be identical.skillspector_provider_id: the SkillEvaluator provider the scan was bridged from (nv_build,openai,openai-compatible,anthropic, orbedrock).nv_buildandopenai-compatibleboth run as SkillSpector’sopenaiadapter. The field is omitted whenSKILLSPECTOR_PROVIDERselects a SkillSpector adapter directly (anthropic,bedrock, oropenai) or names an unsupported provider.SKILLSPECTOR_PROVIDER=nv_buildis bridged likeSKILL_EVAL_LLM_PROVIDER=nv_buildand recorded asnv_build.
The HTML report shows the same data in the Tier 1 LLM Provenance table on the Tier 1 tab. The record contains no prompts, endpoints, or credentials. A record that does not match SkillSpector’s provenance contract, or that contains a URL or credential-shaped value, is left out rather than partially shown. If the LLM stage fails or SkillSpector does not confirm LLM analysis, a valid record in SkillSpector’s output is still kept to show what was attempted, and the Security result stays incomplete. Provenance is recorded when you scan a single skill. A scan of a folder with several skills does not record it yet.
SkillSpector 2.11.1 and later tags an exact read-only /etc/passwd bind mount, such as docker run -v /etc/passwd:/etc/passwd:ro or the Podman, --volume, and quoted forms, as likely benign but still reports it as a HIGH Credential Access (PE3) finding. SkillEvaluator ignores that finding only when SkillSpector tagged it benign and every /etc/passwd on the reported source line is the source or target of such a mount. Writable or implicit-mode mounts, other source or target paths, and reads such as cat /etc/passwd still block.
SkillSpector 2.13.3 can report separate findings for the mount’s source and target; both must pass the same checks. Ignoring a mount never clears incomplete inspection, including refused or truncated LLM responses. A static report can say an LLM provider is available without having requested or applied LLM analysis; provider availability alone does not invalidate the static scan.
To accept reviewed findings, pass an audited baseline with --skillspector-baseline; see Audited SkillSpector baselines.
pii — PII, credentials, and local identifiers
pii-scan, and the pii check inside validate, use configurable regex patterns to detect sensitive values.
Instruction content in .mdc rules and workflow files is included, for both
direct file inputs and directory scans.
Common placeholders such as YOUR_API_KEY_HERE, example, test, dummy, and placeholder, along with @example.com and @test.com, are excluded, as are configured allowed paths.
A card-shaped number is reported only when it passes the Luhn checksum; all-zero numbers do not. Digits inside a canonical GUID/UUID, such as the COM interface ID {00000000-0000-0000-C000-000000000046}, are treated as an identifier unless a payment label (card_number, credit_card, cc_no, cc_number, pan, and similar) directly precedes the GUID on its line or ends the line before it. Every card-shaped number on a line is checked, so an identifier or non-card number earlier on the line does not hide a card after it.
The home-path check compares detected user paths against the submitter’s identity. When no identity is available, that check is skipped and logged — set SKILLEVALUATOR_SUBMITTER (or add author to SKILL.md) to enable it. SKILLEVALUATOR_SUBMITTER is combined with GITHUB_ACTOR, USER, LOGNAME, USERNAME, the OS login name, and the skill’s declared author — every identity that resolves is protected.
license — compliance
Looks for license evidence in frontmatter, common license files, and SPDX headers. Detected identifiers are normalized and matched against the packaged permissive allowlist and restrictive blocklist. Missing or unknown licenses are reported for review; blocked licenses fail according to the active profile (CRITICAL under the default external profile).
Frontmatter values and SPDX headers can be SPDX license expressions with AND, OR, WITH, and parentheses, such as MIT OR Apache-2.0. An expression passes only when every license in it is allowed, and any blocked license fails it. The +, -only, and -or-later forms of a blocked license, such as GPL-3.0+ or LGPL-2.1-only, and deprecated ids such as GPL-2.0-with-classpath-exception are blocked too. A WITH exception keeps an allowed license allowed only when it is in the packaged allowed_license_exceptions list (LLVM-exception, Swift-exception); other exceptions are reported for review. An expression that uses an upper-case AND, OR, or WITH but has unbalanced parentheses or a missing or repeated operator fails with an unsupported_license_expression finding. A value without those operators, such as MIT License or MIT License (MIT), is treated as a license name: it is matched by name as before or reported for review. A blocked license fails the value even when it does not parse, so GPL-3.0 (or later) and (GPL-3.0 OR MIT are blocked.
code-integrity — code risk, secrets, and hygiene
Combines three validation groups:
- Code risk (Bandit and Semgrep) — SQL or command injection, hardcoded passwords, insecure cryptography, unsafe deserialization,
shell=True,eval()/exec(), CWE patterns, and language-specific anti-patterns. - Secrets (Gitleaks) — API keys, tokens, passwords, private keys, database credentials, and other high-confidence secret patterns.
- Hygiene — dead relative Markdown links, banned or unpinned dependency declarations, and static discovery of conventional Python test filenames.
Default Tier 1 does not import or execute target-controlled Python code. The test_discovery result is filename evidence only: it counts regular, in-tree, non-symlink test_*.py and *_test.py candidates without parsing or importing them. It reports execution_performed=false and coverage_measured=false; test success and code coverage must be established separately in a trusted project environment or an explicit sandbox.
A missing, timed-out, crashed, or malformed Bandit, Semgrep, Gitleaks, or SkillSpector run is treated as INCOMPLETE: terminal and saved reports are non-green and validate exits non-zero. See Scanner setup for installation.
unicode — smuggling detection
Detects invisible Unicode characters associated with ASCII smuggling, hidden-data encoding, and trojan-source attacks.
Severity increases with suspicious run length. A BOM (U+FEFF) at byte zero is downgraded to INFO, and binary files are skipped using MIME and null-byte detection.
quality — weighted scoring
quality-check, and the quality check inside validate, produce a 0–100 composite score across four weighted categories.
The 5,000-token budget for SKILL.md is a recommendation, not a hard limit. A larger SKILL.md gets a MEDIUM quality_efficiency finding and a 15-point Efficiency deduction. The finding does not fail the check by itself, but the deduction can still take the score below --min-score.
Grades are A for scores of at least 90, B for at least 80, C for at least 70, D for at least 60, and F below 60. The default --min-score is 70. Passing requires the score to reach that minimum and no HIGH or CRITICAL quality findings under the active policy.
The minimum score is an independent gate. Policy severity overrides cannot
waive it, and a collection average cannot hide an individual skill below
the minimum. A below-minimum result includes a diagnostic such as
Quality score 67.8/100 is below the required 70/100, retained in the JSON
result’s gate_failures list. Improve the skill or explicitly choose the
required threshold with --min-score.
Correctness reports XML-style tags in any frontmatter value as a HIGH finding (potential prompt injection). A string argument-hint, or an unquoted hint such as [<file>] that YAML reads as a list of strings, may use command-line placeholders such as <file> [--strict], <plan|apply|verify>, or <codec e.g. av1|h264>. A placeholder has at most four words, and | separates choices that are each one word or a quoted literal, after an optional label such as stage: or codec e.g.. Longer free text, shell command syntax (;, &, a backtick, $(, ${, or a pipeline such as <curl x | sh>), closing tags, attributes, self-closing tags, and role or control tags such as <system>, <tool_call>, or <script> are still reported there. A role word (system, developer, assistant, user, tool, context, instructions, prompt, override, rules, or policy) followed by prompt, message, call, use, result, or instruction counts as a role tag (<system_prompt>, <toolCall>, <user_message>), while other names such as <tool_name>, <user_id>, <prompt_file>, or <toolchain> are ordinary placeholders. A hint with a redirect, that is a > that closes no group (<cmd > file>, <cmd>file>, <cmd 2>file>, <curl x> /tmp/y>), or a group with anything after a nested placeholder other than its own closing > (<<curl x>/tmp/<y>>, <<x><y>>) or anything other than a plain name before it (<curl;sh<x>>, <$(curl x|sh)<x>>), is not masked at all, so it is still reported. A placeholder nested directly after a plain name, such as <R<NNN>>, is masked. For a list value, each item and the space-joined hint are checked separately. An arrow or comparison between groups, such as <old> -> <new>, <n> >= 1, or <count> (>0), is not a redirect. Every other field keeps the strict check.
The checker detects five skill shapes and applies relevant checks: script-based (Python or shell files under scripts/), lib-based (a Python module containing __init__.py), resource-based (assets, templates, design-system content, or resources), guide-only (documentation without executable or resource content), and hybrid (scripts plus library or resource content).
Quality signals use boundary-aware lexical matching for unambiguous terms and CommonMark parsing for structural link evidence. Links inside code, images, or comments do not count, and external URLs do not satisfy local-reference checks. Deterministic Tier 1 checks do not infer author intent from ambiguous prose such as MCP negation, temporal language, README guidance, or exclusivity claims. Use rubric-eval for semantic judgments about the documentation and Tier 3 for observed agent behavior.
If a quality score changes unexpectedly, inspect the finding message rather than adding keywords. The checker rewards usable instructions and evidence in their relevant context, not vocabulary alone.
Expected headings. Reliability deducts points when SKILL.md lacks an expected heading. The packaged quality-default.yaml expects ## Prerequisites (or ## Requirements), ## Limitations, and ## Troubleshooting (or ## Common Issues or ## FAQ), each worth 5 points. Pass --quality-config PATH to quality-check, tier1, or validate to use your own list instead. The file replaces the packaged defaults rather than merging with them, so copy any default entries you want to keep.
The packaged defaults set message and suggestion explicitly, so their findings keep the original wording.
An entry is satisfied when the heading or any alternative appears anywhere in SKILL.md as text, so ## Security also matches ### Security Notes or a heading inside a code block.
The file is checked before any validation runs. A file that is missing, unreadable, not UTF-8, invalid YAML, has an unknown key (such as point instead of points), or lists the same heading twice is a usage error: the command exits with code 2 and names the file and the problem. validate checks the file even when --checks leaves out quality.
rubric-eval — LLM-as-judge (requires a provider)
rubric-eval sends bounded skill documentation and selected supplementary content to your configured LLM provider. It scores nine criteria:
Each criterion is scored from 0 to 10. The local evaluator, not the model’s boolean, treats 7 or higher as passing. The overall score is an importance-weighted mean, and the rubric passes only when that score reaches --min-score and every criterion passes.
lint — script linting (advisory)
lint-scripts, and the lint check inside validate, parse Python scripts under scripts/ and report advisory findings. These findings do not make the Tier 1 gate fail.
version — semantic version check
The default version check validates an optional metadata.version as a three-part numeric value in major.minor.patch form:
When --previous-version or SKILLEVALUATOR_PREVIOUS_VERSION is set, the current version must be present and numerically greater than the previous value. Without a previous-version bound, the version remains optional and the skill can rely on commit history. A previous-version bound is valid only when checking one skill; catalog validation must check each skill separately with its own bound.
dependency — CVE audit (opt-in)
Scans requirements*.txt for known Python package vulnerabilities. For
pyproject.toml, reads static PEP 621 dependencies and every optional group,
then audits their exact version pins without installing the target or
executing its build scripts. This covers direct declarations only;
transitive dependencies require a resolved requirements file. Markers are
evaluated against the scanner’s Python/platform and optional-group context;
nonmatching declarations are reported as skipped. Version ranges, URL/path
dependencies, dynamic or Poetry metadata, malformed declarations, and
missing or failed scanner evidence leave the audit INCOMPLETE. Supply
pinned requirements.txt for unresolved declarations. pip-audit is the
primary scanner; Safety supplies secondary coverage for requirements files
when installed separately. The audit may use network-backed vulnerability databases.
JSON and HTML export data retain dependency_coverage per target, including
requested and actually audited pins. Unreadable declarations have unknown
declaration counts and an empty audited inventory, not a completed scan.
Scanner setup
Install skillevaluator[security] for the bundled Python scanners (Bandit and pip-audit). Install Semgrep and SkillSpector in separate tool environments, and install Gitleaks separately — it is a Go binary, not a pip package. See Installation for commands and the extras table.
Semgrep uses a policy packaged with the installed distribution, with metrics and version checks disabled; Tier 1 never fetches a registry policy at runtime. The other public Tier 1 configuration also ships inside the package under skillevaluator/config/: pii_patterns.yaml, unicode_smuggle_patterns.yaml, license_config.yaml, and profiles/external.yaml.
Bundled Python scanners resolve next to the SkillEvaluator interpreter before PATH; external Semgrep and SkillSpector installations resolve from PATH. Intentional replacements require an auditable absolute executable path in SKILLEVALUATOR_BANDIT_PATH, SKILLEVALUATOR_SEMGREP_PATH, or SKILLEVALUATOR_SKILLSPECTOR_PATH.
Scanner path overrides fail closed: a relative, missing, or non-executable override path fails the run rather than silently falling back. Required scanners that are missing or produce malformed output leave the run INCOMPLETE and non-green.
Audited SkillSpector baselines
Record reviewed SkillSpector exceptions in a SkillSpector YAML or JSON baseline and pass it with --skillspector-baseline to validate, tier1, or security-scan. SkillEvaluator never discovers a baseline on its own, including a .skillspector-baseline.yaml shipped inside the skill: applying exceptions is an operator or CI decision. A scan that reports suppressed findings without a requested baseline stays INCOMPLETE.
Keep the baseline outside the skill under review, for example in a checkout of the base branch or a separate policy repository. A baseline kept inside the skill is only as trusted as the review of that file: whoever edits the skill can edit its exceptions, SkillSpector leaves the selected baseline file out of its own scan so extra text in it is never inspected, and a version 2 rule needs only a non-empty reason. In a pull-request pipeline, the checked-out skill is the PR’s version, so pass a baseline from the target branch instead.
Prefer narrow rules. Every field a rule sets must match the finding. id (the rule ID), path (the file), and message (the finding text) are all case-insensitive globs, and reason is required.
The bracket class [d] matches the letter d, so the rule still matches --privileged, but the baseline file does not contain the literal text that triggers the finding. SkillEvaluator rejects a rule unless its id has at least two literal letters or digits in a row, or its path or message has at least four. Wildcards and multi-character classes break a run. This rejects id: "*" and also message: "*e*", which matches almost every finding because SkillSpector matches the glob case-insensitively against the finding text. Before the scan it also rejects a version 2 rule or fingerprint without a reason, rules or fingerprints that are not lists or contain entries that are not mappings, a file that is not valid YAML or JSON (including an impossible date such as 2026-06-31), and a file larger than 2 MiB or whose aliases and merge keys expand beyond SkillSpector’s own limits. An empty or comment-only file is an empty baseline, as in SkillSpector. SkillSpector checks the rest of the schema. If it rejects the baseline, it exits with code 2, the security check is INCOMPLETE, and the error suggests checking the file with skillspector scan <skill> --baseline <file>.
To waive individual findings exactly, generate fingerprints with SkillSpector. The command records every current finding, so review each entry before you commit the file:
Fingerprints bind to the scanned file content and to the SkillSpector version that generated them. After a source edit, the affected findings are reported again. After a scanner upgrade, SkillSpector ignores every fingerprint, and SkillEvaluator reports fingerprint_version_mismatch with both versions: as an error when blocking findings remain, otherwise as a warning. Rules keep working across scanner versions.
Fingerprints only match static-only runs: tier1 --no-llm, or validate and security-scan without --llm. When the LLM security stage runs (tier1 by default when a provider is configured, or --llm), SkillSpector’s meta-analysis rewrites each finding’s message, explanation, remediation, confidence, or tags before the baseline is applied. Those fields are part of the fingerprint, so fingerprints from skillspector baseline --no-llm no longer match, and the LLM stage reports the findings again. SkillEvaluator then reports llm_stage_unmatched with both stages’ suppressed counts: as an error when blocking findings remain, otherwise as a warning. For runs with LLM analysis, use narrow id, path, and message rules instead; they still match because SkillSpector also checks message against the matched text.
A baseline stored inside the skill is excluded from the scan, including when SkillEvaluator scans a staged copy without evals/ outputs. For a folder of skills, the same baseline applies to each skill, and rule paths are relative to each skill.
For a single-skill target, when SkillSpector is installed, JSON reports record the outcome under results[].skillspector_baseline:
status:applied,no_matches,fingerprint_version_mismatch,llm_stage_unmatched, ornot_applied.fileanddigest: the baseline’s file name and asha256:digest of its bytes. The local path is not recorded.- The baseline and runtime scanner versions, and the rule and fingerprint counts.
suppressed_findingsfrom the static stage and, when the LLM stage completed,llm_stage_suppressed_findings; and the active and active blocking finding counts. All are taken from SkillSpector’s reports before any--llm-verifydowngrade or policy severity override.suppressed_by_severityandsuppressed_rule_ids: the static stage’s suppressed findings by severity and their rule IDs, so CI can refuse a baseline that hides anything blocking. The Markdown and HTML line shows the severity counts.message: the outcome or the reason the baseline was rejected.
Markdown and HTML reports show the same outcome as a skillspector_baseline line in the Security result, with the file name and digest, and a scan whose only findings were suppressed reads “No active security findings; N finding(s) suppressed by the requested baseline” rather than a clean result.
security-scan and tier1 security-scan on a folder of skills write one merged report without the JSON field. When a skill in the folder passes, its outcome is appended to that skill’s “All checks passed” line in the Markdown and HTML reports. validate on a folder validates each skill as its own job, so each skill’s JSON report records the field. The field keeps the fingerprint_version_mismatch and llm_stage_unmatched messages even when a later --llm-verify downgrade or policy severity override rebuilds the error and warning lists. See the SkillSpector suppression reference for the full baseline schema.
Exit behavior and scoring
Tier 1 checks always gate the exit code inside validate. Tier 2 gates when it runs unless --no-block-on-dedup is set. Tier 3 is advisory unless --block-on-agent-eval is set. The quality check requires the composite score to reach --min-score (default 70) as well as no HIGH or CRITICAL quality findings under the active policy. Severity overrides cannot waive the minimum. Reports record the effective gate membership for every tier so saved evidence and process status agree.
--fail-fast stops after the first failed check. -c/--continue-on-failure overrides it, records the full pipeline, and keeps scanning a collection past a CRITICAL finding. Wiring these exit codes into a merge gate — including a complete GitHub Actions recipe — is covered in Gate Your CI.
Troubleshooting
Gitleaks is not installed
Install it with brew install gitleaks or download a binary from the official Gitleaks releases.
A scanner is missing
Install Bandit and pip-audit with skillevaluator[security]. Install Semgrep separately with brew install semgrep or uv tool install semgrep, and SkillSpector with uv tool install git+https://github.com/NVIDIA/SkillSpector.git@v2.13.3. Required scanner failures remain INCOMPLETE until the scanner is installed and returns valid evidence. skillevaluator doctor --tier 1 lists every scanner the selected checks need, with its resolved path and version.
SkillSpector reports incomplete analysis for a valid helper script
SkillSpector releases before v2.13.1 can stop inspecting a valid Python or Perl helper early and record a static_parse_limit, obfuscated_instruction_text, or runtime_limit ledger exception. The security check is then INCOMPLETE, often with a High Incomplete referenced artifact analysis (AE1) finding at each line that names the helper. Check the installed release with skillspector --version and upgrade with uv tool install git+https://github.com/NVIDIA/SkillSpector.git@v2.13.3. If the exception remains after the upgrade, investigate it like any other incomplete scan.
SkillSpector times out
Each SkillSpector run (the static stage, and the LLM stage with --llm)
is stopped after 1,200 seconds by default, and the security scan is reported
INCOMPLETE with skillspector timed out after 1200 seconds. SkillSpector
also ends a scan at its own deadline (1,080 seconds by default in v2.13.3) and reports partial
analysis, which is INCOMPLETE as well. Slow or local LLM providers can
need more time. Raise both limits, keeping SkillEvaluator’s watchdog higher:
Each stage gets the full watchdog, so a scan that stalls in both stages can take up to twice the value before it is reported. See Environment Variables.
An LLM-backed stage fails immediately
Configure a supported provider or select a keyless Tier 1 run with
skillevaluator tier1 ./my-skill --no-llm or
skillevaluator validate ./my-skill --tiers 1 --no-llm (without
--llm-verify). Direct Tier 1 automatically enables LLM stages when a
provider is configured; --no-llm explicitly disables them. Top-level
validate --no-dedup skips Tier 2 but still runs Tier 3 by default.
No scannable files are found
Confirm that the skill contains supported text or code extensions such as .py, .sh, .yaml, .yml, .json, .md, or .txt.
An executable override is rejected
Use an existing, executable absolute path for the relevant SKILLEVALUATOR_*_PATH variable.
SkillSpector stops at a runtime limit
When SkillSpector reports partial analysis, the security scan is
INCOMPLETE. The error quotes up to three of SkillSpector’s completeness
exceptions as path [phase; reason_code; analyzers=...]. When there are more,
it adds how many files each reason affected, for example
and 463 more (incomplete files by reason: runtime_limit 462, obfuscated_instruction_text 4).
For runtime_limit, the error also says what to do, with one piece of advice
for each kind of row the report contains:
- Scan time budget. Rows that name regular analyzers, or no analyzer,
mean SkillSpector ran out of time for the bundle: at its own whole-scan
deadline (1,080 seconds by default in v2.13.3) or at a shorter stage
budget such as bundle discovery. A skill that bundles a large corpus, such
as vendored documents, generated figures or renderer scripts, hits the
same limit on every rerun. Keep large reference material outside the
skill and point to it. A plain rerun helps only when a few files are
affected on a busy machine. If SkillEvaluator’s own SkillSpector timeout
stops the run before SkillSpector’s deadline, the error is
skillspector timed out after N secondsinstead, with no list of files. - Sub-analysis budget. Analyzers named
<analyzer>_runtime_limit, such asstatic_patterns_supply_chain_bytecode_runtime_limit, stopped at a time budget of their own, so bundle size does not matter. Releases before v2.13.0 charge the SC8 bytecode walk for time spent waiting behind other analyzers, so it can stop even on a skill with no bytecode. Rerun the scan, and update SkillSpector if the limit repeats.