Vet a skill before quay add

quay add fetches Markdown that your agent will execute with implicit trust. quay verifies provenance and integrity — that the file you got is the file the remote has. It does not read the skill and judge whether the instructions inside it are hostile. Nothing in quay does.

SkillSpector (NVIDIA, Apache-2.0) does that part: 68 patterns across 17 categories — prompt injection, data exfiltration, privilege escalation, supply chain, excessive agency, MCP tool poisoning, plus AST-level dangerous-code detection and taint tracking.

The two tools are not integrated and do not need to be. Run the scan, then add.

Scan before you add

# Scan the source repo without downloading it yourself
skillspector scan https://github.com/someone/their-skill --no-llm

# Happy with it?
quay add their-skill

Exit codes: 0 = risk score ≤ 50, 1 = over threshold, 2 = scan error. The threshold is hard-coded in SkillSpector; there is no flag to move it.

--no-llm runs static analysis only. Without it, skill content is sent to an LLM provider for semantic evaluation — worth it for a manual review of something you distrust, wrong for anything automatic.

Audit what you already installed

# Everything in canonical, one summary table
skillspector scan .agents/skills/ --recursive --no-llm

Findings you have triaged and accepted go in a baseline so re-scans surface only what is new:

skillspector baseline .agents/skills/ -o .skillspector-baseline.yaml
skillspector scan .agents/skills/ --recursive --no-llm --baseline .skillspector-baseline.yaml

Commit the baseline.

Gate it in CI

If you want this on every PR, four things are worth knowing before you write the workflow — each one cost us a red run:

  • --recursive with --format sarif writes concatenated text, not merged SARIF. Scan each skill directory separately.
  • Uploading a directory of per-skill SARIF files is rejected: code scanning refuses multiple runs sharing a category. Fold them into a single run (dedupe tool.driver.rules by id) and upload one file.
  • Findings carry uris relative to the scanned dir (a bare SKILL.md), so rewrite them to repo-root-relative or every annotation lands on a path that doesn't exist.
  • Exit code 1 means "over threshold"; anything else means the scan didn't happen. Treat them differently — an unscanned skill is not a safe skill, and collapsing both into one branch is how a crashed scanner produces a green check.

Pin the install to a commit. SkillSpector publishes no tagged releases and moves fast; an unpinned git+https:// install means your CI gate changes without you.

Let the agent do it

Both tools ship MCP servers — quay mcp and skillspector mcp. An agent with both configured can scan and then install in one flow, gating the install on the result, with no wiring between the projects:

{
  "mcpServers": {
    "quay":         { "command": "quay",         "args": ["mcp"] },
    "skillspector": { "command": "skillspector", "args": ["mcp"] }
  }
}

Requires the MCP extra: uv tool install 'skillspector[mcp] @ git+https://github.com/NVIDIA/skillspector.git'.

Why this isn't built into quay

A pre-install gate inside quay add would mean a Python runtime in a Rust tool's install path, and a hard dependency on a CLI contract that has no released version yet. Scanning stays a separate step you compose, by choice.