Skip to content
Conduit

skill

security-ai-scanner

AI-powered security scanner for source code — agentic LLM analysis with SARIF output

elvezjp/security-ai-scannercommunitylistedunreviewed

Set up

Skills are installed by placing their folder where your agent loads skills.

The download comes from github.com/elvezjp/security-ai-scanner itself, not from our server, so what you get is exactly what its author published.

Install instructions come from the README

Skills do not use an MCP config. Follow the steps in the README below.

README

synced Oct 25, 2026

security-ai-scanner

English | 日本語

Elvez IXV Ecosystem PyPI version License: MIT Python Stars

security-ai-scanner execute demo

AI-powered security scanner for source code. An LLM agent reads your repository with read-only tools, traces data flows from source to sink, and reports validated vulnerabilities as SARIF, JSON, and a human-readable Markdown report.

Shared AI connection settings

sais and cair read the same machine-owned TOML file at $XDG_CONFIG_HOME/elvez-quality-gate/config.toml (default: ~/.config/elvez-quality-gate/config.toml). Create it outside the repositories you scan or review:

[profiles.local]
engine = "openai"
base_url = "http://127.0.0.1:18080/v1"
model = "auto"
auth_token_file = "~/.config/elvez-quality-gate/local-token"

Create the token file privately and run chmod 600 on POSIX. The file is read at each run, preserving all contents except one final LF or CRLF. Relative credential paths in a profile resolve against the configuration directory. A file readable by group/others, an invalid token, or a connection file inside the target repository is rejected, including symlink destinations and parent Git repositories when running from a subdirectory. No repository configuration is discovered automatically.

sais scan /path/to/repo --profile local --json
cair review main...HEAD --profile local --json
sais mcp --profile local

Select a profile with --profile or SAIS_PROFILE / CAIR_PROFILE; override the configuration file with SAIS_CONFIG / CAIR_CONFIG. A profile must be selected to read the file. Field precedence is CLI > environment > profile > existing defaults. Both tools accept their own *_ENGINE, *_BASE_URL, *_MODEL, *_AUTH_TOKEN, and *_AUTH_TOKEN_FILE variables. Credential value and credential file are one setting: a higher-priority value replaces the lower-priority file, and vice versa. Supplying both forms at the same level is an error. An explicit --auth-token "" suppresses lower-priority credentials. --auth-token VALUE remains supported with a process-list/history warning; prefer --auth-token-file PATH or the environment. Tokens, credential paths, and profile documents are never included in provenance; reflected credentials are redacted and HTTP error bodies are not printed. Redaction applies to free text after schema and evidence validation; finding keys, severity, and category keep their meaning even when a local token is a common word. HTTP redirects are refused.

model = "auto" works with the OpenAI-compatible engine only. It queries GET /models and uses exactly one valid advertised model. Empty, malformed, or multiple-model responses fail with exit code 2 (multiple-model errors list up to 20 IDs). IXV beast mode is refused when reported by discovery. Each response's model, when present, must equal the selected ID; a change aborts without publishing findings. Discovery does not change token budgets. summary.json.provenance records engine, model_requested, model, and model_metadata (only file basename, context_length, and ixv_mode when provided). Endpoints and arbitrary discovery fields are excluded. Full prompt provenance and SARIF provenance remain separate work.

MCP resolves the operator's settings when the server starts and validates connection-file locations against each scan target. Its model-controlled tool arguments cannot choose a profile, endpoint, or credential. Restart the server to change a profile; rotating the credential file takes effect on the next scan. Direct Python ScanConfig / ReviewConfig calls never load profiles or tool connection environment variables implicitly. Applications can explicitly opt in with the package's connection.load_connection_settings(prefix="SAIS" or "CAIR", target=..., profile=...), then pass settings.as_kwargs(target) into their config. Explicit auth_token_file also works in the Python API. Hosted Claude authentication still uses the Claude SDK's own authentication mechanism. Timeout/retry profile fields are not yet supported.

Features

  • Agentic Analysis: The AI agent explores the repository itself — enumerating entry points, tracing untrusted input to dangerous sinks, and validating each candidate before reporting
  • Read-Only by Design: The agent is restricted to Read / Glob / Grep; shell, write, and network tools are disallowed during a scan
  • SARIF 2.1.0 Output: Findings integrate directly with GitHub Code Scanning and standard AppSec tooling
  • CI Gate Built In: --fail-on high exits non-zero when findings meet the threshold, so a scan can block a pipeline
  • Structured Findings: Results are produced against a JSON Schema (severity, confidence, CWE, evidence, recommendation) — no fragile text parsing
  • Bilingual Reports: Finding descriptions and the Markdown report can be generated in English or Japanese (--language ja)
  • Local LLM Support: Scan without sending code off the machine — point the built-in openai engine at any OpenAI-compatible server (vLLM, Ollama, LM Studio, llama.cpp), or the default claude engine at an Anthropic-compatible endpoint
  • Engine-Agnostic Core: The scanner core talks to a thin engine adapter; the default engine is the Claude Agent SDK, and other backends can be added without touching the core

Use Cases

  • Pre-Release Audit: Run a full scan before shipping and review the Markdown report
  • CI Security Gate: Fail pull-request pipelines when new high-severity findings appear
  • GitHub Code Scanning: Upload findings.sarif to surface findings in the Security tab
  • Security Triage Input: Feed findings.json into your own tracking or ticketing workflow

Documentation

Installation

Requires Python 3.11 or higher. Install the claude extra for the default Claude engine. The Claude Agent SDK bundles the Claude Code CLI and its use is also governed by Anthropic's published commercial terms.

pip install 'security-ai-scanner[claude]'
# or with uv
uv add 'security-ai-scanner[claude]'

The built-in OpenAI-compatible local engine has no third-party runtime dependency. Install the base package when only that engine is needed:

pip install security-ai-scanner
sais scan ./repo --engine openai --base-url http://127.0.0.1:11434/v1 --model MODEL

After installation, the security-ai-scanner command (and its short alias sais) is available on your PATH.

Authentication

The default engine authenticates the same way Claude Code does. Pick one:

# Option 1: sign in with your Claude account
claude login

# Option 2: use an API key
export ANTHROPIC_API_KEY=sk-ant-...

Usage

security-ai-scanner scan path/to/repo

This generates, under ./security-scan-results/:

  • findings.json: Structured findings with severity, confidence, CWE, evidence, and recommendations
  • findings.sarif: SARIF 2.1.0 log for GitHub Code Scanning and SARIF viewers
  • report.md: Human-readable Markdown report
  • summary.json: Schema-version-1 run manifest and completion marker

Artifacts are atomically committed within one output directory, with summary.json committed last. A .sais.lock file rejects concurrent writers. If an interrupted process leaves the lock behind, confirm that no scan is active before removing it.

Version 0.3.0 introduces native artifact schema version 1 as a deliberate breaking change from 0.2.0. Consumers must validate schema_version and must not interpret 0.2.0 summary.json or findings.json files as schema version 1.

The exit code is 0 when no finding meets the --fail-on threshold, 1 when one does (CI gate), and 2 on errors.

After a run has acquired its output lock, execution failures commit a schema-version-1 status: "error" summary when possible. With --json, that same object is printed to stdout while the process still exits 2. Validation or lock failures before publication print no JSON and leave existing artifacts unchanged.

Common Examples

Scan with a Japanese report:

sais scan path/to/repo --language ja

Use as a CI gate (fail the build on critical findings only):

sais scan . --fail-on critical

SARIF derived output, into a custom directory:

sais scan . --format sarif -o ./out

The native findings.json and summary.json are always written. --format selects additional derived outputs.

Give the scanner extra context (scope, threat model notes):

sais scan . --context "Internet-facing Flask API. Focus on the api/ directory."

Watch the agent's progress:

sais scan . -v

Scanning with a Local LLM

Two engines connect to self-hosted endpoints. Either way, nothing is sent to a hosted API.

The OpenAI endpoint must return exactly one completion choice. Final answers require finish_reason: "stop" without a refusal or unfinished tool calls. A tool turn requires finish_reason: "tool_calls" and valid calls while tools are enabled. length, content_filter, refusal, missing/unknown termination metadata, and inconsistent tool states fail with exit code 2 even if the content is valid JSON. This also applies to the final request after a local turn/token limit; a normally finished partial answer still retains the existing incomplete status.

OpenAI-compatible servers (--engine openai)

Most self-hosted inference speaks the OpenAI API — vLLM, Ollama, LM Studio, llama.cpp server, and corporate gateways. The openai engine connects to them directly with a minimal built-in agent loop: its only tools are read_file / glob / grep, implemented in this package and sandboxed to the scan root, so the read-only guarantee is structural — no shell, write, or network tool exists in the loop, and no third-party agent harness is involved.

sais scan ./repo \
  --engine openai \
  --base-url http://127.0.0.1:11434/v1 \
  --model qwen2.5-coder:32b

--model is required (or set SAIS_MODEL): there is no default on purpose, because some servers ignore the model field and a guessed default would record a wrong model name in the scan outputs. When the model is missing, the error lists what the server actually offers (via GET /v1/models).

--max-tokens N sets a total-token budget for the scan. When the budget is reached the engine stops calling tools, collects the findings it has, and marks summary.json with "stopped": "budget_exceeded". A budget-stopped scan with no findings still exits 0, and the model's prose summary may not admit the scan was cut short — in CI, also check that stopped is null before trusting a clean result. Note that servers that omit the usage field in responses under-count total_tokens, which weakens budget enforcement on such servers.

Anthropic-compatible servers (--engine claude, the default)

Point --base-url at a self-hosted, Anthropic-compatible endpoint. Note that this path runs the Claude Agent SDK's bundled Claude Code CLI locally as the agent harness; only the inference endpoint changes.

sais scan ./repo \
  --base-url http://127.0.0.1:8000 \
  --auth-token local \
  --model your-local-model

The scanner pins every model slot the agent harness uses (opus / sonnet / haiku / subagent) to --model, since a local server usually serves a single model, and clears any hosted credentials from the subprocess environment so they cannot take precedence over the local endpoint.

Common to both engines

The scanner sends repository contents to whatever --base-url you give it — treat that URL as trusted infrastructure and never point it at an endpoint you do not control.

If your endpoint requires a real credential, prefer the SAIS_AUTH_TOKEN environment variable over --auth-token: command-line arguments are visible in process lists, shell history, and CI logs.

Schema-constrained structured output is turned off automatically for a custom --base-url, because support in local servers is inconsistent. The scanner instead asks for a fenced JSON block and parses that. If your server does support it (response_format: json_schema for the openai engine), re-enable with --structured-output.

Scope the target to fit the context window

This is the main practical constraint. The agent reads source files into its context as it works, so a whole repository can exhaust a smaller context window mid-scan. In our testing, a 43-file Python repository failed against a 100K-token local endpoint (the run stopped at ~98.5K), while the same scan scoped to the application package (backend/app, 25 files) completed normally.

Point the scan at a component rather than a repository root when using a local endpoint. This is rarely a real limitation in practice, since the security-relevant code is usually one package.

What to expect

Hosted vs local (measured with the claude engine)

We measured this by scanning a repository whose vulnerabilities were already known and confirmed by hand:

Hosted APILocal endpoint
Known vulnerabilities found3 of 32 of 3, with matching file and line
Additional real issues found—CORS wildcard with credentials, debug output leaking request contents, dependencies pinned to a branch
False positivesnone observednone observed
Severity calibrationconsistentinconsistent — rated one path traversal lower and one missing-auth finding higher
Throughputminutesroughly an order of magnitude slower

A local model is a genuine reviewer, not a keyword matcher: it traced data flows and cited accurate line numbers, and it surfaced real issues the hosted run did not. Where it is weaker is judging how much a finding matters — treat its severities as a starting point for triage rather than a ranking you can act on directly.

A practical split is local for privacy-sensitive screening, hosted for release audits and anything where the severity ranking itself drives a decision.

Model comparison (openai engine)

Four live runs against two server implementations, scanning a 4-file Flask fixture with four planted vulnerabilities (command injection, SQL injection, path traversal, hardcoded key):

Model (server)Planted foundFalse positivesDuration / tokens
Muse-Glimmer-30B GGUF (llama.cpp)4 / 4none83 s / 15,310
qwen3.8:27b (Ollama)4 / 4none131 s / 10,759
ornith-1.5:35b (Ollama)4 / 4none25 s / 6,887
qwen2.5-coder:7b (Ollama)0 / 4 — cannot drive the tool loop—0.5 s / 1,300

Every passing model also flagged the same unplanted real issue (DEBUG = True) that was not part of the fixture design. Among capable models, detection was uniform; what varied was severity calibration and speed. Model capability — not the server or the transport — is the gating factor: prefer roughly 27B-class or larger. A model that cannot emit parseable tool calls produces an instant "clean" verdict having read nothing, which the engine rejects as an error (exit 2) rather than passing it off as a clean scan.

Full method, per-run findings, and failure analysis: docs/local-llm-e2e-verification.md.

Note: if your server processes one request at a time, run scans serially rather than in parallel.

Use as a Library

security-ai-scanner is also usable as a Python library.

from pathlib import Path
from security_ai_scanner import ScanConfig, run_scan

result = run_scan(ScanConfig(target=Path("path/to/repo"), language="ja"))

for finding in result.output.findings:
    print(finding.severity, finding.file, finding.title)

print(result.gate_failed)   # True if findings meet the fail-on threshold

CLI options map 1:1 to ScanConfig fields (e.g. fail_on="critical", formats=("sarif",)).

From source

git clone https://github.com/elvezjp/security-ai-scanner.git
cd security-ai-scanner
uv sync

See CONTRIBUTING.md for the full developer setup.

Key Options

OptionDefaultDescription
-o, --output-dir./security-scan-resultsOutput directory
--engineclaudeAI engine backend: claude (Claude Agent SDK) or openai (built-in loop for OpenAI-compatible endpoints)
--modelEngine default (claude) / required (openai)Model passed to the engine (or SAIS_MODEL env var)
--base-urlHosted APISelf-hosted endpoint: Anthropic-compatible (claude) or OpenAI-compatible (openai)
--auth-tokenSAIS_AUTH_TOKEN env varAuth token for --base-url (prefer the env var for real credentials)
--structured-outputAutoForce schema-constrained output on/off (--no-structured-output to disable)
--languageenLanguage for findings and report (en / ja)
--context-Extra security context for the scan
--fail-onhighCI gate threshold (critical/high/medium/low/info/none)
--formatAllAdditional derived output, repeatable (json/sarif/markdown); native JSON is always written
--max-turns100Maximum agent turns
--max-tokensNo capTotal-token budget for the scan (openai engine); stops early with partial findings
-v, --verbosefalseStream agent progress to stderr
--jsonfalsePrint the machine-readable summary to stdout (for agents and scripts)
--notify-webhook-Webhook URL to POST the run summary to on completion/failure (or SAIS_NOTIFY_WEBHOOK)
--notify-formatgenericWebhook payload: generic JSON, or a discord / slack message

For AI Agents

sais is designed to be easy for coding agents (Claude Code, Codex, Cursor, VS Code agents, ...) to drive: stable exit codes, a machine-readable summary.json (always written), and --json for a single-line JSON summary on stdout. See AGENTS.md for the agent-facing specification.

Claude Code skill

skills/sais-scan/SKILL.md is a ready-made skill for Claude Code and compatible agents. Copy it into your skills directory (e.g. .claude/skills/sais-scan/) and ask for a security scan — the agent runs sais, reads the JSON summary, and reports findings as review leads.

MCP server

With the mcp extra installed (pip install 'security-ai-scanner[mcp]'), sais mcp serves the scanner over the Model Context Protocol (stdio), so MCP clients — Claude Code, VS Code, Cursor, Codex — can scan without shelling out:

# Claude Code
claude mcp add sais -- sais mcp
// VS Code / Cursor (mcp.json)
{ "servers": { "sais": { "command": "sais", "args": ["mcp"] } } }

Tools: scan_repository(path, ...) → run summary + scan_id, get_summary(scan_id), get_findings(scan_id, min_severity). A scan takes minutes; the server sends MCP progress notifications while it runs.

Notifications

--notify-webhook URL (or the SAIS_NOTIFY_WEBHOOK environment variable) POSTs the run summary when a scan completes or fails — so unattended scans never fail silently:

# Discord channel notification (counts and gate verdict only)
sais scan . --notify-webhook "$DISCORD_WEBHOOK_URL" --notify-format discord

--notify-format selects the payload: generic (the summary JSON, for CI and custom receivers), discord, or slack. Chat formats send a one-line message with severity counts only — never finding details. A failed notification prints a warning and does not change the scan's exit code; the URL is treated as a secret and never printed.

GitHub Action

The repository doubles as a composite action on top of the SARIF output:

- uses: elvezjp/security-ai-scanner@main
  with:
    anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }}
    fail-on: high
- uses: github/codeql-action/upload-sarif@v3
  if: always()
  with:
    sarif_file: security-scan-results/findings.sarif

Inputs: target, fail-on, language, output-dir, version, extra-args. Outputs: sarif-file, summary-file, exit-code.

By default the action installs the latest scanner release from PyPI. For reproducible CI runs, pin it with version: "0.3.0" — pinning the action reference alone does not pin the scanner itself.

How It Works

┌─────────────┐   prompt +       ┌──────────────────┐
│  Scanner    │   JSON schema    │  Engine adapter  │
│  core       │ ───────────────▶ │  (claude, ...)   │
│             │                  └────────┬─────────┘
│  findings   │                           │ read-only tools
│  validation │                  ┌────────▼─────────┐
│  SARIF /    │ ◀─────────────── │  AI agent reads  │
│  report     │   structured     │  the repository  │
└─────────────┘   findings       └──────────────────┘
  1. The scanner builds a security-audit prompt and a findings JSON Schema
  2. The engine runs an AI agent over the target directory with read-only tools (Read / Glob / Grep only)
  3. The agent maps entry points, traces data flows, validates candidates, and returns findings as structured output
  4. The scanner validates, ranks, and writes SARIF / JSON / Markdown

Directory Structure

security-ai-scanner/
├── security_ai_scanner/     # Main package
│   ├── cli.py               # Command-line interface
│   ├── config.py            # Scan configuration
│   ├── findings.py          # Finding model, schema, validation
│   ├── sarif.py             # SARIF 2.1.0 export
│   ├── report.py            # Markdown report generation
│   ├── runner.py            # Scan orchestration
│   ├── engine/              # Engine adapters (claude, ...)
│   └── prompts/             # Scan methodology prompts
├── tests/                   # Test suite
├── spec.md                  # Specification
├── docs/                    # Documentation
├── pyproject.toml           # Project metadata
├── LICENSE                  # MIT License
├── README.md / _ja.md       # README (English / Japanese)
├── CONTRIBUTING.md / _ja.md # Contribution guide (English / Japanese)
├── SECURITY.md / _ja.md     # Security policy (English / Japanese)
└── CHANGELOG.md / _ja.md    # Version history (English / Japanese)

Security

For security concerns, please see SECURITY.md.

Key security notes:

  • The scan agent runs with read-only tools; it does not modify the target or execute repository code
  • Repository contents are sent to the configured AI engine for analysis — only scan code you are authorized to submit to that engine. Use --base-url with a self-hosted endpoint when the code must not leave your infrastructure
  • Findings may include false positives and false negatives; treat reports as expert input to human review, not as a certification
  • Scan only code you own or are authorized to assess

Contributing

Contributions are welcome! See CONTRIBUTING.md for details.

  • Report bugs via GitHub Issues
  • Submit pull requests for improvements
  • Follow existing code style
  • Add tests for new features

Roadmap

Planned work for making the public scanner usable in CI and on self-hosted LLMs. Order is priority, not a promise. Scope may change based on feedback. Tracked issues use labels priority:P0 / P1 / P2 and roadmap.

P0 — usable in public CI and local scans

  • Path include/exclude and .saisignore — keep agents out of node_modules, build artifacts, and other noise (#3)
  • Project config file (sais.toml / .sais.yaml) — pin fail-on, language, and scope; connection settings belong only to machine profiles (#4)
  • Diff scan — limit a scan to a PR / commit range (#5)
  • Baseline, suppressions, and stable finding IDs — stop recurring false positives from failing every run (#6)

P1 — generic product depth

  • Completed: Second engine backend — the OpenAI-compatible adapter now proves the engine-agnostic core beyond Claude (#7, #17)
  • Scan profiles — pluggable methodology prompts (security default, plus a small set of generic profiles; org-specific checklists stay outside the public core) (#8)
  • Batch scan — multiple roots serially with batch-summary.json (today: loop sais scan and read each summary.json) (#9)
  • Local LLM operational hardening — clearer context/scope failures, serial defaults for single-session endpoints (#10)

P2 — trust and ongoing use

  • Known-vulnerability fixture benchmark — reproducible recall checks for hosted vs local models (#11)
  • Triage — re-evaluate findings and learn from false-positive feedback (#12)
  • Release maturity — support matrix, SBOM where practical, pinned Action guidance (#13)

Non-goals for the public core

  • Vendor- or org-specific “repo hygiene” checklists (docs drift, internal ADR rules). Those belong in private wrappers or custom profiles passed via the planned --profile option (#8), not hard-coded into this package
  • Turning the tool into a general-purpose code review bot. The product focus stays security findings with structured evidence

Changelog

See CHANGELOG.md for details.

Background

This tool was created as a small utility during the development of IXV (Ixiv), a development support AI for Japanese development documents and specifications.

IXV addresses the challenges of understanding, structuring, and utilizing Japanese documents in system development. This repository publishes a portion of that work.

The scan methodology (agentic scan → validation → structured findings) is an independent implementation inspired by the design of OpenAI Codex Security. No code is shared between the two projects.

License

MIT License - See LICENSE for details. Third-party components and separate service terms are recorded in THIRD_PARTY_NOTICES.md.

Contact

↑↓ navigate openesc close