ai-security-scan
v1.3.0
Published
AI-aware security scanner for source code, CI/CD pipelines and containers. Detects prompt injection, RAG/agent/MCP risks, secrets, and OWASP LLM Top 10 issues.
Downloads
1,096
Maintainers
Readme
ai-security-scan
AI-aware security scanner for source code, CI/CD pipelines and containers. Detects prompt injection, RAG/agent/MCP risks, secrets, and the OWASP LLM Top 10 — with a hybrid AST + regex + LLM engine.
ai-security-scan is an open-source, modular SAST tool in the spirit of Semgrep / Snyk / Trivy, but specialized for AI and LLM application security. Run it locally, in Docker, or in any CI/CD pipeline. It returns proper exit codes so it can gate your builds.
Quick start
# Scan the current directory
npx ai-security-scan .
# Scan a subfolder, write a SARIF report, fail the build on HIGH+ findings
npx ai-security-scan ./src --report console,sarif --output report.sarif --fail-on high
# Generate an interactive HTML dashboard
npx ai-security-scan . --report html --output report.html
# Build a review artifact with deterministic remediation guidance
npx ai-security-scan review . --artifact-output .ai-security-review.json
# Build a dry-run proposal artifact for fixable findings
npx ai-security-scan propose . --artifact-output .ai-security-propose.jsonDocker:
docker build -t ai-security-scan .
docker run --rm -v "$(pwd)":/workspace ai-security-scan .
docker run --rm -v "$(pwd)":/workspace ai-security-scan scan /workspace --report sarif --output /workspace/out.sarifDefault Behavior
The default CLI command is intentionally simple:
npx ai-security-scan .By default, the package:
- runs AST + regex scanning immediately
- attempts a built-in lightweight local LLM pass using a managed Ollama container
- uses a fixed lightweight code model behind the scenes
- falls back to deterministic scanning only if Docker or the local runtime is unavailable
That means most users do not need to choose a provider, model, or auth flow just to use the package in a pipeline.
For blocking CI, prefer an explicit deterministic gate:
npx [email protected] . --deterministic-gate --fail-on highFeatures
- Hybrid detection engine
- Layer 1 — AST (
ts-morph/ TypeScript compiler API): semantic checks for prompt construction, dynamic code execution and insecure output handling in TS/JS. - Layer 2 — Regex rule packs: fast, language-aware pattern detection.
- Layer 3 — LLM analysis (optional): sends bounded code chunks, config files, and common dependency manifests/lockfiles to an LLM and validates structured JSON output with Zod.
- Layer 1 — AST (
- AI-focused rule packs: Prompt Injection, RAG Security, AI Agent Security, MCP Security, Secrets, OWASP LLM Top 10, plus classic AppSec (SQLi, weak crypto, TLS, CORS…).
- Multi-language discovery: TypeScript, JavaScript, Python, C#, Java, Go, Rust, YAML, JSON, Dockerfile, Terraform.
- Manifest coverage: common dependency files such as
package.json,package-lock.json,requirements.txt,pyproject.toml,poetry.lock,Cargo.toml,Cargo.lock,go.mod,pom.xml, and Gradle build files are discoverable for scanning. - Multiple report formats: console, JSON, SARIF (GitHub Code Scanning), interactive HTML.
- Built-in local LLM mode: lightweight managed local model analysis with automatic fallback to deterministic scanning when local runtime support is unavailable.
- Workflow artifacts:
reviewandproposecommands wrap canonical scan results with explanation, remediation, fixability, proposal, and validation metadata. - Bounded remediation workflow: dry-run proposals for a small safe allowlist, local branch/apply staging with
--apply, and draft GitHub PR / GitLab MR metadata with--open-pr. - Plugin SDK: ship custom rules and scanners as npm packages.
- CI-friendly: configurable severity threshold + exit codes.
- Fast: bounded-concurrency work pool designed for large repositories.
CLI usage
ai-security-scan [path] [options]
ai-security-scan scan [path] [options] # alias (handy in Docker)
ai-security-scan review [path] [options]
ai-security-scan propose [path] [options]
Arguments:
path Path to scan (file or directory) [default: "."]
Options:
-p, --path <path> Path to scan (overrides positional argument)
-r, --report <formats> console | json | sarif | html (comma-separated)
-o, --output <file> Output file/dir for file-based reports
-s, --severity <level> Minimum severity to report (low|medium|high|critical)
--fail-on <level> Exit non-zero at/above this severity [default: high]
--fail-on-sources Sources that can fail the scan (comma-separated)
--deterministic-gate Fail only on non-LLM findings
--scan-scope Whole-scan scope before analysis (full|changed-files)
--baseline <file> Path to a baseline file
--ignore-file <file> Path to a suppression file
--exclude <globs> Extra ignore globs (comma-separated)
--include <globs> Restrict to these globs (comma-separated)
--no-inline-suppressions Disable inline aiss-disable suppressions
-c, --concurrency <n> Number of concurrent workers
--config <file> Path to a config file
--plugins <modules> Plugin module paths (comma-separated)
--no-color Disable colored output
-v, --verbose Verbose logging
-q, --quiet Suppress diagnostic logging
-V, --version Print version
-h, --help Show helpWorkflow-specific flags
review supports:
--artifact-output <file>--explain-with <deterministic|hybrid|llm>
propose supports:
--artifact-input <file>--artifact-output <file>--applyto create a local branch, apply validated edits, and stage touched files--open-prto generate or create a draft GitHub PR / GitLab MR--provider <github|gitlab>
Exit codes
| Code | Meaning |
| ---- | ---------------------------------------------------------- |
| 0 | No findings at or above the --fail-on threshold. |
| 1 | Findings at or above the threshold were detected. |
| 2 | Scanner error (bad config, missing path, provider error…). |
--severity controls what appears in reports. --fail-on and --fail-on-sources control what can block the process.
Configuration file
Drop a .ai-security-scan.json at the repo root (see .ai-security-scan.example.json). CLI flags override file values.
{
"report": ["console", "sarif"],
"output": "reports",
"failOn": "HIGH",
"deterministicGate": true,
"scanScope": "full",
"exclude": ["**/fixtures/**"],
"workflow": {
"review": {
"artifactOutput": ".ai-security-review.json",
"explainWith": "deterministic"
},
"propose": {
"artifactInput": ".ai-security-review.json",
"artifactOutput": ".ai-security-propose.json",
"dryRun": true,
"openPr": false,
"provider": "github"
}
}
}The following directories are always ignored: node_modules, .git, dist, build, coverage, bin, obj, vendor (plus a few others). Add more via exclude.
Workflow config notes:
workflow.review.explainWithdefaults todeterministicworkflow.propose.dryRundefaults totrueworkflow.propose.providerdefaults togithub--applyflips propose out of dry-run mode- CLI flags still override config-file values
Remote draft creation tokens:
- GitHub:
GH_TOKENorGITHUB_TOKEN - GitLab:
GITLAB_TOKENorGITLAB_PRIVATE_TOKEN
Review / propose workflows
The workflow surface is intentionally layered on top of the scan engine:
scandetects and reports findingsreviewadds explanation, remediation guidance, and fixability metadataproposeadds bounded patch proposals and dry-run validation results
Safety model:
reviewis advisory onlyproposeis dry-run by default- only a small safe allowlist currently generates concrete patch proposals
--applyis required before any local branch mutation happens--open-pr/--provider gitlabgenerate draft remote metadata in dry-run mode and only create a remote PR/MR when combined with--apply
Examples:
# Deterministic review artifact
ai-security-scan review . --artifact-output .ai-security-review.json
# Hybrid review enrichment (falls back cleanly when LLM is unavailable)
ai-security-scan review . --explain-with hybrid --artifact-output .ai-security-review.json
# Dry-run proposal artifact
ai-security-scan propose . --artifact-output .ai-security-propose.json
# Build a local proposal branch and stage validated edits
ai-security-scan propose . --artifact-output .ai-security-propose.json --apply
# Dry-run GitHub draft PR metadata
ai-security-scan propose . --artifact-output .ai-security-propose.json --open-pr
# Dry-run GitLab draft MR metadata
ai-security-scan propose . --artifact-output .ai-security-propose.json --open-pr --provider gitlabCurrent fixable MVP subset:
llm05/remote-code-trust- literal TLS verification disables such as
rejectUnauthorized: false,verify = False, andInsecureSkipVerify: true
All other findings remain advisory until broader fixers are added.
Baselines and suppressions
Use these features to keep known or accepted findings visible in history without letting them stay active in current CI results.
- Baseline = accepted existing findings stored in a JSON file
- Suppression = explicit waiver through
.ai-security-scan-ignoreor inline comments - Baseline/suppression filtering happens before fail evaluation and before report visibility filtering
Baseline file format
Pass a baseline file with:
ai-security-scan . --baseline baseline.jsonSupported JSON shapes:
[
{ "id": "secret/openai-api-key", "file": "src/app.ts", "line": 12 },
{ "id": "ast/prompt-injection-concat", "file": "src/chat.ts" }
]or:
{
"findings": [{ "id": "secret/openai-api-key", "file": "bad.ts", "line": 1 }]
}Rules:
- each entry must include
idandfile lineis optional and 1-based- if
lineis omitted, the entry matches that finding id anywhere in the file - for multi-line findings, a matching line may fall anywhere within the finding span
Ignore file syntax
By default, the scanner looks for .ai-security-scan-ignore in the scan base directory.
Each non-empty, non-comment line must be:
<finding-id> <path>[:line]Example:
# Suppress one specific finding line
secret/openai-api-key src/app.ts:12
# Suppress this finding id anywhere in the file
ast/prompt-injection-concat src/chat.tsIf you set --ignore-file <file>, that file must exist. The default .ai-security-scan-ignore file is optional.
Inline suppressions
Supported directives:
aiss-disable-next-lineaiss-disable-lineaiss-disable
Examples:
// aiss-disable-next-line secret/openai-api-key
const key = 'sk-...';
// aiss-disable-next-line: secret/openai-api-key, secret/generic-assignment
const key = process.env.OPENAI_API_KEY;
const key = secret; // aiss-disable secret/openai-api-key
const prompt = userInput; // aiss-disable-line ast/prompt-injection-concat
// aiss-disable-next-line
dangerousCall();Behavior:
- on a comment-only line, bare
aiss-disableapplies to the next line - on a code line, bare
aiss-disableapplies to the same line - multiple ids may be separated by spaces or commas
- if no ids are provided, all findings on the target line are suppressed
Supported comment styles:
//or/*: TypeScript, JavaScript, C#, Java, Go, Rust#: Python, YAML, TOML, Dockerfile, Terraform<!--: XML
Precedence
The scanner applies filters in this order:
- ignore-file and inline suppressions
- baseline entries
- remaining findings are reported and considered for exit status
If both a suppression and a baseline match the same finding, it is counted as suppressed.
Path resolution
baselineandignoreFilepaths are resolved relative to the scan base directory- for directory scans, that base is the scanned directory
- for single-file scans, that base is the file's parent directory
- matcher paths are normalized to scanner-relative POSIX paths
Examples:
# Directory scan: matcher paths are relative to ./repo
ai-security-scan ./repo --baseline baseline.json
# Single-file scan: matcher path should usually be "app.ts"
ai-security-scan ./repo/src/app.ts --baseline baseline.jsonConfigured baseline files must exist. If --baseline points to a missing file, the scan fails.
LLM analysis (Layer 3)
The package includes a built-in lightweight local LLM flow for deeper security analysis.
# Default package behavior
ai-security-scan .The built-in local LLM mode uses a fixed lightweight code model and the following defaults:
- local runs:
suspicious - CI runs:
changed-files
That changed-files setting is LLM-only. If you want to scope the entire scan before AST, regex, plugins, and LLM, use:
ai-security-scan . --scan-scope changed-filesThat keeps local model analysis lightweight enough for most projects.
Behind the scenes, the CLI starts a temporary ollama/ollama container when local runtime support is available, runs the LLM analysis, and then stops the container when the scan completes.
Requirements for the built-in local LLM mode:
- the machine or CI runner must have Docker available
- the runner must be allowed to start containers
- the first run may spend time pulling the model image and model weights
- if Docker is unavailable, the scan still runs with AST + regex detection only
Free local model path
Current fixed lightweight model:
qwen2.5-coder:3bYou do not need to pass a provider or model flag to use it.
If you want to disable the built-in local LLM during testing or controlled environments, set:
AI_SECURITY_SCAN_DISABLE_LLM=trueRecommended CI workflows
Deterministic blocking gate
AI_SECURITY_SCAN_DISABLE_LLM=true npx [email protected] . --fail-on highor
npx [email protected] . --deterministic-gate --fail-on highFor PR-focused runs, you can also scope the whole scan to changed files:
npx [email protected] . --deterministic-gate --scan-scope changed-files --fail-on highBroader advisory scan with local LLM findings still reported
npx [email protected] . --report console,sarif --output results.sarif --fail-on high --fail-on-sources ast,regex,pluginThat keeps LLM findings visible in reports while preventing them from blocking the build.
When --scan-scope changed-files cannot resolve git context safely, the scanner falls back to a full scan.
Programmatic API
import { runScan, resolveConfig, shouldFail } from 'ai-security-scan';
const config = resolveConfig({ path: './src', failOn: 'HIGH' });
const result = await runScan(config);
console.log(result.stats);
if (shouldFail(result, config)) process.exit(1);Workflow APIs are also available:
import {
loadWorkflowArtifact,
runProposeWorkflow,
runReviewWorkflow,
saveWorkflowArtifact,
} from 'ai-security-scan';
const reviewArtifact = await runReviewWorkflow(result, {
toolVersion: 'dev',
explainWith: 'deterministic',
llmConfig: config.llm,
});
await saveWorkflowArtifact('.ai-security-review.json', reviewArtifact);
const loaded = await loadWorkflowArtifact('.ai-security-review.json');
const proposeArtifact = await runProposeWorkflow(
{ artifact: loaded },
{
toolVersion: 'dev',
rootPath: config.path,
scanConfig: config,
},
);Plugin SDK
Build custom rules/scanners and publish them as npm packages.
import { definePlugin, type RegexRule } from 'ai-security-scan/sdk';
const rule: RegexRule = {
kind: 'regex',
id: 'acme/no-todo-secrets',
title: 'TODO near secret',
description: 'A TODO comment references a secret.',
severity: 'MEDIUM',
category: 'Custom',
recommendation: 'Resolve the TODO and remove the secret.',
pattern: /TODO.*secret/gi,
};
export default definePlugin({ name: 'acme-rules', rules: [rule] });Use it:
ai-security-scan . --plugins ./path/to/plugin.jsSee examples/plugins/custom-plugin.ts.
CI/CD integration
Ready-to-use templates live in examples/ci:
- GitHub Actions (with SARIF upload to the Security tab)
- GitHub Actions + local Ollama (free local model path)
- GitHub Actions + auto Ollama (advisory local-model job)
- GitLab CI (deterministic blocking scan)
- GitLab CI + auto Ollama (auto-managed lightweight container)
- Azure DevOps
- Jenkins
Minimal GitHub Actions step:
- name: Security Scan
env:
AI_SECURITY_SCAN_DISABLE_LLM: 'true'
run: npx [email protected] . --fail-on highMinimal GitLab step:
ai_security_scan:
stage: test
image: node:20-alpine
variables:
AI_SECURITY_SCAN_DISABLE_LLM: 'true'
script:
- npx [email protected] . --fail-on highWorkflow artifacts can also be generated in CI and uploaded as build artifacts for later review:
npx ai-security-scan review . --artifact-output .ai-security-review.json
npx ai-security-scan propose . --artifact-output .ai-security-propose.jsonRemote draft creation permissions/tokens:
- GitHub draft PR creation needs a token with repo contents + pull request write access
- GitLab draft MR creation needs
GITLAB_TOKENorGITLAB_PRIVATE_TOKEN
Architecture
src/
├─ cli.ts # executable entrypoint
├─ cli/ # CLI-only helpers: options, config merge, report output
├─ app/
│ ├─ scan/ # scan orchestration and pipeline composition
│ └─ workflow/ # review/propose orchestration, artifacts, validation
├─ index.ts # public programmatic API
├─ core/ # types, severity, config (Zod), finding utils
├─ discovery/ # recursive file discovery + language detection
├─ engine/
│ ├─ astEngine.ts # Layer 1 — ts-morph AST analysis
│ ├─ regexEngine.ts # Layer 2 — regex rule execution
│ ├─ concurrency.ts # bounded work pool
│ └─ scanEngine.ts # public engine wrapper
├─ rules/
│ ├─ ast/ # AST rule definitions
│ ├─ registry.ts # rule registry + enable/disable
│ └─ packs/ # prompt-injection, rag, agent, secrets, mcp, owasp-llm, appsec
├─ llm/
│ ├─ provider.ts # LLMProvider interface + Zod schema + parser
│ ├─ providers/ # openai, azure, anthropic, ollama, local
│ ├─ factory.ts # provider construction from config
│ └─ llmEngine.ts # Layer 3 — chunking + aggregation
├─ plugins/ # plugin manager + public SDK
├─ reporters/ # console, json, sarif, html
├─ vcs/ # local git prep + GitHub/GitLab draft adapter layer
└─ utils/ # logger, text/line indexingDesign principles: clean architecture, SOLID, dependency injection (providers/reporters/plugins are swappable), and a single canonical SecurityFinding model shared across every layer. ScanResult remains the canonical detection contract; workflow artifacts wrap it with explanation, remediation, fixability, proposal, validation, and draft PR/MR metadata.
Contributor-facing architecture details live in docs/architecture.md.
Security finding model
interface SecurityFinding {
id: string;
title: string;
description: string;
severity: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
file: string;
line: number;
category: string;
recommendation: string;
source: 'regex' | 'ast' | 'llm' | 'plugin';
owaspLlm?: string; // e.g. "LLM01"
cwe?: string; // e.g. "CWE-94"
confidence?: number;
// …plus column/endLine/snippet/tags
}Development
npm install
npm run dev -- examples/vulnerable-sample # run from source via tsx
npm run build # bundle with tsup
npm run lint
npm run typecheck
npm test
npm run test:coverageBun is supported too:
bun run src/cli.ts .andbun run build.
Contributing
Contributor workflow and OpenSpec guidance live in CONTRIBUTING.md.
- current behavior specs live under
openspec/specs - proposed cross-cutting changes live under
openspec/changes - release automation details live in
RELEASING.md - user-facing changes should include a Changesets entry via
npm run changeset
Versioning
This project follows Semantic Versioning. Rule additions that surface new findings are treated as minor releases; breaking CLI/SDK changes bump the major. See ROADMAP.md.
License
⚠️ This tool reduces risk but does not guarantee the absence of vulnerabilities. Use it as one layer of a defense-in-depth program.
