Tier 1: Static and Security Validation

View as Markdown

Tier 1 gives you a deterministic quality gate for your skill: schema, quality, security, PII, license, secrets, code-integrity, Unicode-safety, and script checks that all run offline once the scanner binaries are installed. You need no API key for any of it — the only LLM-backed pieces are rubric-eval and the optional --llm/--llm-verify flags, which require a configured provider (see Providers & Credentials).

What Tier 1 checks

Tier 1 is exposed through one umbrella command and five standalone commands. Each is also available under the expert alias group as skillevaluator tier1 <command>.

CommandPurposeNeeds a key?
validateRun the selected Tier 1 checks, Tier 2 deduplication by default, and optional advisory Tier 3 evaluationNo (only with --llm/--llm-verify)
quality-checkScore skill quality across four weighted categoriesNo
rubric-evalRun the nine-criterion LLM-as-judge rubricYes
security-scanRun SkillSpector static scanning and optional LLM analysisNo (only with --llm/--llm-verify)
pii-scanDetect PII, credentials, secrets, and local identifiersNo (only with --llm-verify)
lint-scriptsRun advisory AST-based checks on Python scriptsNo

validate is the umbrella command: Tier 1 checks gate the exit code (non-zero on failure or incomplete required scanner evidence), Tier 2 dedup runs by default and degrades gracefully without embedding access, and Tier 3 can be attached as an advisory pass with --tier3, --autopilot, --full, or an explicit tier selection such as --tiers 1,3 (--agent-eval remains a supported compatibility alias and is not currently deprecated).

Run a validation

1

Run the default suite

Tier 1 + Tier 2, no key needed
$skillevaluator validate ./my-skill

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. Tier 2 dedup is skipped gracefully when no embedding provider is configured. The exit code is 0 when every gate passes and 1 when one fails.

2

Run the complete offline gate

Install Gitleaks once, then disable dedup for a fully hermetic run:

Complete offline Tier 1
$brew install gitleaks semgrep
$uv tool install git+https://github.com/NVIDIA/SkillSpector.git
$skillevaluator validate ./my-skill --no-dedup

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.

3

Narrow to specific checks

--checks (alias --tier1-checks) takes a comma-separated subset when you want fast, targeted feedback:

Selective checks
$skillevaluator validate ./my-skill --checks schema,security

Only the selected Tier 1 checks run; the others are skipped entirely.

4

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:

LLM-backed validation
$skillevaluator validate ./my-skill --llm --llm-verify

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.

Reports for any of these runs are selected with -r/--report (cli, json, html, markdown) 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:

Type--type valueAuto-detection signal
SkillskillSKILL.md in skills/ or team-skills/
Rulesrules.mdc files in team-rules/
Workflowsworkflowsworkflow-rules.mdc in a workflow directory
PluginpluginA bundle-reference agent_plugin.yaml/.yml manifest, or a contained .claude-plugin/plugin.json manifest

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__.

Validate a skill or a collection
$skillevaluator validate ./skills/my-skill --type skill
$skillevaluator validate ./skills --type skill --continue-on-failure

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.

Profile selection
$skillevaluator validate ./my-skill # default = external
$skillevaluator validate ./my-skill --profile external # explicit public profile
$skillevaluator validate ./my-skill --external # alias for --profile external
$skillevaluator validate ./my-skill --policy ./policy.yaml # custom overlay
$SKILLEVALUATOR_PROFILE=external skillevaluator validate ./my-skill

Precedence, highest to lowest: --policy overlay, --profile flag, SKILLEVALUATOR_PROFILE environment variable, then the bundled external default.

A custom policy overlays the external profile. It can narrow the accepted author domain and change severities by CATEGORY.check_name or CATEGORY.*:

policy.yaml
1profile: community-strict
2identity:
3 author_email_regex: '<[^>]+@example\.org>'
4severity_overrides:
5 SCHEMA.author_missing: critical
6 LICENSE.*: critical

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

The default set is schema, version, security, pii, license, code-integrity, unicode, quality, and lint. Only the dependency check requires explicit selection with --checks (alias --tier1-checks). 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.

Validates SKILL.md frontmatter and repository placement:

  • YAML frontmatter must parse and satisfy the skill schema.
  • name and description are 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 words anthropic and claude must not appear in a skill name.
  • alwaysApply and globs are forbidden skill fields. Optional fields include license, compatibility, metadata, and allowed-tools.
  • The standard hierarchy is skills/<skill-name>/ or team-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/, and config/; anything else gets a LOW advisory finding. Extend the set per repository with SKILLEVALUATOR_SCHEMA_ALLOWED_DIRS (comma-separated names are added to the defaults, never replace them).
  • SKILL.md should stay at or below 500 lines; larger files receive an advisory finding.
  • The default profile expects metadata.author in Name <email@host> form but does not require a particular email domain.

To fix common findings: rename the directory to match name, remove forbidden fields, and move the skill under skills/.

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.

Security scanning
$skillevaluator security-scan ./my-skill
$skillevaluator security-scan ./my-skill --llm
$skillevaluator security-scan ./my-skill --llm --llm-verify

The LLM pass requires a configured provider. It enriches the static result but does not replace static findings or make missing scanner evidence pass.

pii-scan, and the pii check inside validate, use configurable regex patterns to detect sensitive values.

GroupExamplesNotes
Personal informationPersonal paths, non-placeholder email addresses, phone numbers, SSNs, GPS coordinates, MAC addressesPersonal macOS/Windows paths are flagged; SSNs are CRITICAL
Credentials and secretsDatabase connection strings, hardcoded API keys, cloud keys, GitHub tokens, private keys, JWTs, webhook URLsMost credential findings are CRITICAL
Network and financialPublic IPs, credit-card numbers, cryptocurrency wallet addressesPrivate RFC1918 addresses are excluded

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.

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.

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).

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.

Detects invisible Unicode characters associated with ASCII smuggling, hidden-data encoding, and trojan-source attacks.

CategoryRangeDefault severity
Unicode Tags (ASCII smuggling)U+E0000..U+E007FCRITICAL; decodes the hidden ASCII payload
BiDi overrides (trojan source)U+202A..U+202E, U+2066..U+2069, U+200E/F, U+061CHIGH
Zero-width charactersU+200B/C/D, U+034F, U+180E, U+2060, U+FEFFMEDIUM or LOW
Invisible operators and deprecated controlsU+2061..U+2064, U+206A..U+206FMEDIUM
Variation selectorsU+FE00..U+FE0F, U+E0100..U+E01EFLOW

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-check, and the quality check inside validate, produce a 0–100 composite score across four weighted categories.

CategoryWeightWhat it evaluates
Correctness35%Frontmatter, naming, examples, paths, and skill-type-specific structure
Discoverability25%Description quality, trigger wording, purpose, scope, and naming
Reliability25%Error handling, prerequisites, limitations, troubleshooting, and implementation safeguards
Efficiency15%Token budget, body length, repetition, instruction clarity, and reference use

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.

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).

rubric-eval sends bounded skill documentation and selected supplementary content to your configured LLM provider. It scores nine criteria:

#CriterionImportance
1Description clarity and when to use the skillHigh
2Instruction clarity and actionable stepsHigh
3Example quality and query variationHigh
4Documentation completenessMedium
5Scope definitionMedium
6Professional tone and formattingLow
7Trigger simulation against positive and negative queriesHigh
8End-to-end workflow completenessHigh
9Actionable error handlingMedium

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.

Rubric evaluation
$skillevaluator rubric-eval ./my-skill --min-score 70

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.

CheckSeverityDetects
flat_scriptMEDIUMNo function definitions
deep_nestingMEDIUMControl-flow nesting deeper than 6
magic_numbersLOWRaw numeric constants outside the safe constant set
missing_shebangLOWNo leading shebang (#!)
no_input_validationLOWNo argparse, click, typer, or explicit raise pattern

The default version check validates an optional metadata.version as a three-part numeric value in major.minor.patch form:

SKILL.md frontmatter
1metadata:
2 version: "1.2.3"

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.

Version check against a previous release
$skillevaluator validate ./my-skill --previous-version 1.2.2

Scans requirements*.txt for known Python package vulnerabilities. When pyproject.toml is present, pip-audit --local audits the active Python environment rather than resolving the dependencies declared in that file. pip-audit is the primary scanner; Safety supplies secondary coverage for requirements files when installed separately. The audit may use network-backed vulnerability databases.

Opt-in dependency audit
$skillevaluator validate ./my-skill --checks dependency

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.

Exit behavior and scoring

Exit codeMeaning
0Success — every gate passed
1Validation failed — a check failed or required scanner evidence is incomplete
2Configuration error — invalid flags, arguments, or target path
3Runtime error — an unexpected internal failure

Tier 1 checks — and the Tier 2 dedup pass, when it runs — gate the exit code inside validate; Tier 3 findings are advisory and never change it. The quality check passes when the composite score reaches --min-score (default 70).

--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

Install it with brew install gitleaks or download a binary from the official Gitleaks releases.

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. Required scanner failures remain INCOMPLETE until the scanner is installed and returns valid evidence.

Configure a supported provider or omit --llm/--llm-verify. Use --no-tier2 (or --no-dedup) to disable dedup when a fully key-free Tier 1 run is required.

Confirm that the skill contains supported text or code extensions such as .py, .sh, .yaml, .yml, .json, .md, or .txt.

Use an existing, executable absolute path for the relevant SKILLEVALUATOR_*_PATH variable.

Next steps