Agentic, scanner-grounded code review for the whole pull request — application code, infrastructure, and the AI/LLM/agent code most review tools don't understand yet.
acr reviews only the lines a PR changes, runs high-precision built-in rules plus the open-source scanners you already trust, merges duplicates, and (optionally) hands every finding to an LLM agent that reads the surrounding code to confirm it, dismiss false positives and write the fix. The output is one PR comment, inline annotations, SARIF for GitHub code scanning, and an exit code that can gate the merge.
See a live AI review → A real pull request with deliberately insecure AI, Kubernetes and Docker code, reviewed by this tool's GitHub Action with Claude. The reviewer confirmed 11 of 12 findings, dismissed the one planted false positive, and wrote a fix for each. The PR stays open as a showcase and will not be merged.
$ acr review --base origin/main
## Agentic code review · ❌ Changes requested
🔴 1 critical · 🟠 3 high · 🟡 2 medium · 🔵 1 low
| 🔴 | critical | `agent.py:12` | LLM output used as code, shell, SQL or HTML (LLM05) | ACR-AI-002 |
| 🟠 | high | `.github/workflows/x.yml:9` | Untrusted event data interpolated into a shell script | ACR-GHA-002 |
| 🟠 | high | `k8s/trainer.yaml:14` | Dangerous Linux capability added | ACR-K8S-004 |
- The CI/CD attack surface as it is attacked. Secrets posted off GitHub from a workflow step (the GhostAction shape), the checkout token shipped inside a workflow artifact (ArtiPACKED), event data written into
GITHUB_ENV, dependencies pulled from a moving git ref, actions andCOPY --fromimages that are not pinned. - Lock files, not only manifests. Pinned versions in
uv.lock, Poetry, Pipfile and npm locks and pip files are checked: a CA bundle that still trusts compromised roots, packages over plain HTTP or with TLS checks off, an extra index open to dependency confusion, git dependencies with no integrity hash or pinned to a branch. - Go as thoroughly as Python. SQL built with
fmt.Sprintf,sh -chanded a built command,InsecureSkipVerify, redirects and file paths taken from the request, servers with no header timeout, world-writable files. - The bugs that ship in ML code. Model and dataset archives extracted without a path filter, temp files picked by name, HTTP calls that can wait forever,
math/randbehind a token, a private key compared withbig.Int. - The whole PR, not one language. Real PRs change Python and the Dockerfile, the Compose file, the Helm chart and a workflow. One review covers all of it.
- AI/agent code is a first-class target. Rules mapped to the OWASP Top 10 for LLM Applications: prompt injection, LLM output reaching
exec/SQL/HTML,trust_remote_code, unbounded token spend, provider keys in the browser — plus the new AI supply chain: unpinned or auto-approved MCP servers, tokens in MCP configs, agent skills that pipe scripts into a shell, read credentials or hide instructions, agent instruction files (AGENTS.md,CLAUDE.md,.cursorrules) carrying hidden instructions, agent plugins whose marketplace entry is not pinned to a commit or whose hooks fetch and run code, and coding agents (Claude Code, Codex, Gemini CLI) launched with their permission checks off. - Grounded, not guessed. Deterministic rules and real scanners decide what and where; the agent only verifies and explains. Every finding has an exact file and line.
- Adoptable on legacy code. Only changed lines are reviewed, so turning it on doesn't drown every PR in old debt.
- Works without any LLM. The deterministic layer is useful on its own; the agent is opt-in.
flowchart LR
PR[Pull request diff] --> D[Changed files and lines]
D --> C{Classify}
C -->|app code| R1[App rules]
C -->|AI / agent code| R2[OWASP LLM + MCP + skill rules]
C -->|K8s, Docker, Terraform, Actions| R3[Infra rules]
D --> S[Optional scanners<br/>gitleaks · semgrep · trivy · checkov<br/>hadolint · actionlint · kube-linter]
R1 & R2 & R3 & S --> M[Keep changed lines only<br/>merge cross-tool duplicates]
M --> A[Optional AI agent<br/>confirm · dismiss · explain · fix]
A --> O[PR comment · inline annotations · SARIF · exit code]
pip install git+https://github.com/sunilmehta-si/agentic-code-review-tool
cd your-repo
acr init # write a starter .acr.yml
acr review # working tree vs HEAD
acr review --staged # only what you are about to commit
acr review --base origin/main # what a PR would change (merge-base)
acr review --all # audit every tracked file
acr review --format sarif -o acr.sarif # for GitHub code scanning
acr review --format github # annotations in a GitHub Actions log
acr review --format gitlab -o gl-code-quality-report.json # GitLab merge request Code Quality
acr review --format junit -o acr.xml # Jenkins, Azure DevOps, CircleCI, Bitbucket test reports
git diff origin/main... | acr review --diff-file - # review any unified diff
acr rules # list built-in and custom rules (--format markdown|json)
acr classify Dockerfile k8s/app.yaml # which file kinds and rules apply to a file| Option | Default | Meaning |
|---|---|---|
--fail-on {critical,high,medium,low,never} |
high |
Exit 1 if a finding at or above this severity remains |
--no-scanners |
off | Built-in rules only |
--scanners semgrep,trivy |
all installed | Run a subset of external scanners |
--exclude 'docs/*' |
— | Skip paths (repeatable) |
--include 'services/api/*' |
everything | Only review matching paths (repeatable); include: in .acr.yml |
--sarif, --json, --markdown PATH |
— | Write extra report formats from the same run (no re-scan) |
--baseline FILE |
.acr-baseline.json if present |
Suppress findings accepted with acr baseline |
--min-severity medium |
show everything | Leave out findings below this level; min_severity: in .acr.yml |
--timings |
off | Print how long the rules and each scanner took |
Suppress a deliberate, reviewed line with an inline comment. acr:ignore silences every rule on that line;
acr:ignore[ACR-APP-004, semgrep:*] silences only the listed rules (ids or globs), so anything else on the line
is still reported. Markers work for built-in rules and external scanner findings alike. Add until=2026-12-31 to make a
suppression lapse on that date; after it, the finding is reported again with a note that its exemption expired.
# .acr.yml (command-line flags win over this file)
fail_on: high
exclude: ["docs/*", "*.generated.go"]
disable: ["ACR-AI-007"] # rule ids or globs, e.g. "hadolint:*"
severity:
ACR-K8S-009: medium # per-rule severity overrides
"hadolint:*": low # globs work too; an exact id wins
scanners: [semgrep, trivy]
ai: claude # none | claude | openaiUnknown or invalid settings stop the run with a clear error instead of being silently ignored.
Encode your own conventions as line patterns. They run on changed lines like built-in rules and honour acr:ignore,
disable, severity overrides and the baseline:
custom_rules:
- id: ORG-001 # your prefix; ACR- is reserved
title: Internal service called over plain HTTP
pattern: 'http://[a-z0-9-]+\.internal\b'
severity: high # critical | high | medium | low | info (default medium)
files: ["*.py", "services/*"] # optional globs (default: every file)
category: app-security # optional, shapes the "By area" summary
fix: Call the https:// endpoint through the service mesh.acr rules lists them next to the built-in rules and acr explain ORG-001 shows the pattern and fix. A broken pattern
or an unknown key stops the review with a message naming the rule.
Adopting on an existing codebase: run acr baseline once and commit .acr-baseline.json. Known findings are suppressed,
matched by rule and line content so they survive code moving around, and only new problems fail pull requests.
acr baseline --check fails when baselined findings have since been fixed, and acr baseline --prune removes those
entries without accepting anything new, so the baseline only ever shrinks.
examples/insecure-app is a small project with deliberate mistakes in every area. Running
acr review --root examples/insecure-app --all --no-scanners --fail-on neverreports 88 findings across 17 files (8 critical, 40 high, 35 medium, 5 low). One finding from each rule family:
| Severity | Location | Issue | Rule | |
|---|---|---|---|---|
| 🔴 | critical | .github/workflows/triage.yml:12 |
Privileged workflow checks out untrusted PR code | ACR-GHA-001 |
| 🔴 | critical | .mcp.json:6 |
Credential stored in plain text in an MCP config | ACR-MCP-002 |
| 🔴 | critical | agent.py:24 |
LLM output used as code, shell, SQL or HTML (LLM05 Improper Output Handling) | ACR-AI-002 |
| 🔴 | critical | infra/main.tf:7 |
Sensitive port open to the whole internet | ACR-TF-001 |
| 🔴 | critical | k8s/trainer.yaml:18 |
Privileged container or privilege escalation allowed | ACR-K8S-001 |
| 🔴 | critical | skills/deploy-helper/SKILL.md:8 |
Agent skill or rules file reads credentials or sends data out | ACR-SKILL-002 |
| 🟠 | high | .claude/settings.json:3 |
Coding agent run with its permission checks or sandbox turned off (LLM06 Excessive Agency) | ACR-AI-010 |
| 🟠 | high | Dockerfile:2 |
Secret baked into the image via ENV or ARG | ACR-DOCKER-005 |
| 🟠 | high | agent.py:24 |
Dynamic code execution | ACR-APP-003 |
| 🟠 | high | app.py:6 |
Hardcoded password or token | ACR-SEC-002 |
The full run is checked in CI by tests/test_examples.py, so a rule that stops firing fails the build.
docker build -t acr .
docker run --rm -v "$PWD:/src" acr review --base origin/mainThe image runs as a non-root user and pins its base image by digest.
# .pre-commit-config.yaml
repos:
- repo: https://github.com/sunilmehta-si/agentic-code-review-tool
rev: v0.5.0
hooks:
- id: acrThe hook runs the built-in rules only (fast, offline) with --staged, so it reviews exactly what is being committed and
ignores unstaged edits. CI can run the full review with scanners and the AI agent.
# .github/workflows/code-review.yml
name: code-review
on: pull_request
permissions:
contents: read
pull-requests: write # inline review comments
security-events: write # SARIF upload to code scanning
jobs:
review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false
- uses: sunilmehta-si/agentic-code-review-tool@v0.5.0 # or pin the release commit SHA
with:
fail-on: high
ai: claude # or: none, openai
anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }}
- uses: github/codeql-action/upload-sarif@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
if: always()
with:
sarif_file: acr.sarifThe action writes the summary to the job page, posts inline comments on the PR, shows findings as annotations on the
changed files, uploads SARIF to the Security tab, and fails the check only after all of that is published. Annotations
need no write permission, so pull requests from forks still get line-level feedback (annotations: "false" turns them off).
Re-runs on new pushes don't repeat inline comments that were already posted, and transient GitHub API errors are retried.
| Input | Default | Meaning |
|---|---|---|
fail-on |
high |
Severity that fails the check (never to report only) |
ai, model |
none |
claude or openai to verify findings with an agent |
scanners |
all installed | Comma-separated subset, or none for built-in rules only |
post-review, annotations |
true |
PR review comment with inline comments; diff annotations |
sarif-file |
acr.sarif |
SARIF path for code scanning (empty to skip) |
Outputs: findings, blocking (counts) and report-file (the JSON report, which includes the gate decision).
Use fetch-depth: 0 so the PR diff is exact; on a shallow clone acr falls back to a direct diff and says so.
Live example: the demo pull request. Every inline comment there was written by the Claude reviewer.
With --ai, an LLM agent verifies every deterministic finding before you see it. It reads the surrounding code with three read-only, repository-confined tools (read_file, search_repo, list_changed_files), then marks each finding confirmed, false positive or uncertain, explains it in plain language and writes a concrete fix. It may add up to five issues of its own, but only on lines the PR changes.
pip install "agentic-code-review-tool[claude] @ git+https://github.com/sunilmehta-si/agentic-code-review-tool"
export ANTHROPIC_API_KEY=... # or: ant auth login
acr review --base origin/main --ai claude
# Any OpenAI-compatible endpoint: OpenAI, vLLM, Ollama, LM Studio, LiteLLM
export ACR_OPENAI_BASE_URL=http://localhost:11434/v1 ACR_MODEL=qwen2.5-coder:32b
acr review --base origin/main --ai openai| Setting | Default | Notes |
|---|---|---|
--ai claude |
model claude-opus-5-5, effort high |
Official Anthropic SDK; cached system prompt; structured JSON verdicts; server-side fallback on safety refusals (ACR_CLAUDE_FALLBACKS=0 to disable) |
--ai openai |
ACR_MODEL required |
Standard-library HTTP only, so it works air-gapped with a local model |
--model, ACR_MODEL, ACR_EFFORT |
— | Override model and effort |
--max-steps |
12 | Upper bound on agent tool calls |
--ai-max-findings |
50 | Only the most severe findings are sent to the model, bounding cost on large changes |
--ai-dry-run |
— | Print the redacted request the model would receive, without calling it |
The agent is treated as untrusted too. Repository content is passed as data, the prompt tells the model not to follow instructions found in code, the tools cannot write, execute or leave the repository, and new findings off the changed lines are discarded.
Credentials never reach the model provider: the tools refuse files such as .env, private keys and *.tfstate, and
provider keys, private-key blocks and hardcoded passwords in the diff or tool output are replaced with [REDACTED]. If the AI step fails for any reason (no key, rate limit, refusal), the deterministic review is still published and the report says why.
Every rule is run over real repositories before it ships (Argo CD, Kubeflow Pipelines, KServe, MLflow, flytekit and
others, with scripts/sweep.py), and the false positives that turn up are fixed first: tests and fixtures are not
deployments, the name of a secret is not the secret, a pull request's base branch is not attacker input. The changelog
says how each sweep moved the counts.
127 rules, generated from the code (acr rules; acr explain <id> for details):
| Area | Rule | Severity | What it catches |
|---|---|---|---|
| Secrets | ACR-K8S-011 |
high | Kubernetes Secret with real values committed to git |
| Secrets | ACR-K8S-018 |
high | Credential stored in a ConfigMap |
| Secrets | ACR-MCP-002 |
critical | Credential stored in plain text in an MCP config |
| Secrets | ACR-SEC-001 |
critical | Provider credential committed to source |
| Secrets | ACR-SEC-002 |
high | Hardcoded password or token |
| Application code | ACR-APP-001 |
high | SQL built with string formatting |
| Application code | ACR-APP-002 |
high | Shell command built from a string |
| Application code | ACR-APP-003 |
high | Dynamic code execution |
| Application code | ACR-APP-004 |
high | Unsafe deserialisation |
| Application code | ACR-APP-005 |
medium | TLS certificate verification disabled |
| Application code | ACR-APP-006 |
medium | Debug mode enabled in application code |
| Application code | ACR-APP-007 |
low | Weak hash used for security |
| Application code | ACR-APP-008 |
critical | JWT accepted without verifying its signature or expiry |
| Application code | ACR-APP-009 |
high | CORS allows any origin together with credentials |
| Application code | ACR-APP-010 |
medium | Predictable random numbers used for a secret value |
| Application code | ACR-APP-011 |
high | XML parser resolves external entities (XXE) |
| Application code | ACR-APP-012 |
medium | Redirect to a URL taken straight from the request (open redirect) |
| Application code | ACR-APP-013 |
high | File path taken straight from the request (path traversal) |
| Application code | ACR-APP-014 |
medium | Deprecated SSL/TLS protocol version allowed |
| Application code | ACR-APP-015 |
medium | Session or auth cookie without Secure or HttpOnly |
| Application code | ACR-APP-016 |
medium | Regular expression built from request input (ReDoS) |
| Application code | ACR-APP-017 |
medium | Django accepts any Host header (ALLOWED_HOSTS wildcard) |
| Application code | ACR-APP-018 |
medium | CSRF protection turned off |
| Application code | ACR-APP-019 |
medium | ECDSA private key handled through big.Int arithmetic on its D field |
| Application code | ACR-APP-020 |
low | Go HTTP request sent without a timeout |
| Application code | ACR-APP-021 |
low | Python HTTP request sent without a timeout |
| Application code | ACR-APP-022 |
medium | Archive extracted without a path filter (zip slip) |
| Application code | ACR-APP-023 |
medium | Temporary file created by name, open to a race (mktemp) |
| Application code | ACR-APP-024 |
medium | Go HTTP server with no request timeouts |
| Application code | ACR-APP-025 |
medium | File or directory created world-writable |
| Application code | ACR-APP-026 |
high | Authorisation checked with assert |
| AI / LLM / agent code | ACR-AI-001 |
high | User input concatenated into a system prompt (LLM01 Prompt Injection) |
| AI / LLM / agent code | ACR-AI-002 |
critical | LLM output used as code, shell, SQL or HTML (LLM05 Improper Output Handling) |
| AI / LLM / agent code | ACR-AI-003 |
high | Agent given dangerous tools without guardrails (LLM06 Excessive Agency) |
| AI / LLM / agent code | ACR-AI-004 |
high | Untrusted model code or weights loaded (LLM03 Supply Chain) |
| AI / LLM / agent code | ACR-AI-005 |
medium | LLM call without an output-token limit (LLM10 Unbounded Consumption) |
| AI / LLM / agent code | ACR-AI-006 |
critical | Provider API key exposed to the browser (LLM02 Sensitive Information Disclosure) |
| AI / LLM / agent code | ACR-AI-007 |
low | Full prompts or model responses written to logs (LLM02 Sensitive Information Disclosure) |
| AI / LLM / agent code | ACR-AI-008 |
high | Secret or credential placed in a system prompt (LLM07 System Prompt Leakage) |
| AI / LLM / agent code | ACR-AI-009 |
medium | Agent loop has no step or iteration limit (LLM10 Unbounded Consumption) |
| AI / LLM / agent code | ACR-AI-010 |
high | Coding agent run with its permission checks or sandbox turned off (LLM06 Excessive Agency) |
| AI / LLM / agent code | ACR-AI-011 |
high | Agent settings pre-approve any shell command or risky ones (LLM06 Excessive Agency) |
| AI / LLM / agent code | ACR-AI-012 |
high | Model server started with remote code execution enabled (LLM03 Supply Chain) |
| AI / LLM / agent code | ACR-AI-013 |
medium | Model demo published on a public share link |
| AI / LLM / agent code | ACR-AI-014 |
medium | Agent settings auto-start every MCP server in the repository |
| AI / LLM / agent code | ACR-AI-015 |
high | Agent settings open the whole disk or home directory to the agent |
| AI / LLM / agent code | ACR-AI-016 |
high | Agent plugin marketplace entry not pinned to a commit (LLM03 Supply Chain) |
| AI / LLM / agent code | ACR-AI-017 |
critical | Agent plugin hook runs a remote script or reads credentials (LLM03 Supply Chain) |
| AI / LLM / agent code | ACR-MCP-001 |
high | MCP server installed from an unpinned package |
| AI / LLM / agent code | ACR-MCP-003 |
high | MCP server over plain HTTP or with broad host access |
| AI / LLM / agent code | ACR-MCP-004 |
medium | MCP tools run without asking the user |
| AI / LLM / agent code | ACR-SKILL-001 |
high | Agent skill or rules file tells the agent to run a remote script |
| AI / LLM / agent code | ACR-SKILL-002 |
critical | Agent skill or rules file reads credentials or sends data out |
| AI / LLM / agent code | ACR-SKILL-003 |
high | Agent skill or rules file contains hidden or override instructions |
| AI / LLM / agent code | ACR-SKILL-004 |
medium | Agent skill pre-approves unrestricted shell access |
| Kubernetes | ACR-K8S-001 |
critical | Privileged container or privilege escalation allowed |
| Kubernetes | ACR-K8S-002 |
high | Pod shares the host's network, PID or IPC namespace |
| Kubernetes | ACR-K8S-003 |
high | hostPath volume mounts the node's filesystem |
| Kubernetes | ACR-K8S-004 |
high | Dangerous Linux capability added |
| Kubernetes | ACR-K8S-005 |
medium | Container image not pinned (latest or no tag) |
| Kubernetes | ACR-K8S-006 |
high | Container runs as root (UID 0) |
| Kubernetes | ACR-K8S-007 |
medium | Container has no CPU/memory limits |
| Kubernetes | ACR-K8S-008 |
medium | GPU requested without a matching limit |
| Kubernetes | ACR-K8S-009 |
low | Pod does not require a non-root user |
| Kubernetes | ACR-K8S-010 |
high | RBAC grants wildcard permissions or cluster-admin |
| Kubernetes | ACR-K8S-012 |
high | Seccomp or AppArmor confinement turned off |
| Kubernetes | ACR-K8S-013 |
medium | Container binds a port directly on the node (hostPort) |
| Kubernetes | ACR-K8S-014 |
high | Container sees the host's unmasked /proc (procMount: Unmasked) |
| Kubernetes | ACR-K8S-015 |
medium | Ingress injects raw NGINX configuration through a snippet annotation |
| Kubernetes | ACR-K8S-016 |
medium | Namespace switches Pod Security Admission to privileged |
| Kubernetes | ACR-K8S-017 |
high | Unauthenticated service exposed outside the cluster |
| Kubernetes | ACR-K8S-019 |
medium | NetworkPolicy allows all ingress traffic |
| Kubernetes | ACR-K8S-020 |
high | RBAC grants a privilege-escalation path |
| Kubernetes | ACR-K8S-021 |
low | Container keeps its default capabilities or may escalate privileges |
| Containers (Dockerfile, Compose) | ACR-COMPOSE-001 |
critical | Compose service runs privileged or shares a host namespace |
| Containers (Dockerfile, Compose) | ACR-COMPOSE-002 |
high | Compose service mounts the Docker socket or a sensitive host path |
| Containers (Dockerfile, Compose) | ACR-COMPOSE-003 |
medium | Compose image not pinned (latest or no tag) |
| Containers (Dockerfile, Compose) | ACR-COMPOSE-004 |
high | Compose publishes a database or unauthenticated port on all interfaces |
| Containers (Dockerfile, Compose) | ACR-DOCKER-001 |
medium | Base image not pinned (latest or no tag) |
| Containers (Dockerfile, Compose) | ACR-DOCKER-002 |
medium | Image runs as root (no USER instruction) |
| Containers (Dockerfile, Compose) | ACR-DOCKER-003 |
high | Remote script piped straight into a shell |
| Containers (Dockerfile, Compose) | ACR-DOCKER-004 |
low | ADD fetches a remote URL |
| Containers (Dockerfile, Compose) | ACR-DOCKER-005 |
high | Secret baked into the image via ENV or ARG |
| Containers (Dockerfile, Compose) | ACR-DOCKER-006 |
low | Package manager cache or sudo left in the image |
| Containers (Dockerfile, Compose) | ACR-DOCKER-007 |
medium | World-writable permissions set in the image |
| Containers (Dockerfile, Compose) | ACR-DOCKER-008 |
high | TLS or package signature checks disabled during the build |
| Containers (Dockerfile, Compose) | ACR-DOCKER-009 |
high | Credential file copied into the image |
| Containers (Dockerfile, Compose) | ACR-DOCKER-010 |
medium | SSH server installed or exposed in the image |
| Containers (Dockerfile, Compose) | ACR-DOCKER-011 |
medium | Stage copies from an external image not pinned (latest or no tag) |
| Containers (Dockerfile, Compose) | ACR-DOCKER-012 |
medium | Package installed into the image at whatever version is latest |
| GitHub Actions | ACR-GHA-001 |
critical | Privileged workflow checks out untrusted PR code |
| GitHub Actions | ACR-GHA-002 |
high | Untrusted event data interpolated into a shell script |
| GitHub Actions | ACR-GHA-003 |
medium | Third-party action not pinned to a commit SHA |
| GitHub Actions | ACR-GHA-004 |
medium | Workflow token has broad or default permissions |
| GitHub Actions | ACR-GHA-005 |
high | Secret printed to the workflow log |
| GitHub Actions | ACR-GHA-006 |
low | Deprecated workflow command |
| GitHub Actions | ACR-GHA-007 |
medium | Cloud OIDC token granted to every job in the workflow |
| GitHub Actions | ACR-GHA-008 |
medium | Every repository secret handed to an external reusable workflow |
| GitHub Actions | ACR-GHA-009 |
medium | Self-hosted runner used by a pull-request workflow |
| GitHub Actions | ACR-GHA-010 |
high | Insecure workflow commands re-enabled (ACTIONS_ALLOW_UNSECURE_COMMANDS) |
| GitHub Actions | ACR-GHA-011 |
high | Every secret serialised into one value with toJSON(secrets) |
| GitHub Actions | ACR-GHA-012 |
high | Workflow pipes a remote script straight into a shell |
| GitHub Actions | ACR-GHA-013 |
medium | Container action not pinned to an image digest |
| GitHub Actions | ACR-GHA-014 |
critical | Secret sent to an external host from a workflow step |
| GitHub Actions | ACR-GHA-015 |
high | Untrusted event data written into GITHUB_ENV or GITHUB_PATH |
| GitHub Actions | ACR-GHA-017 |
high | Checkout credentials uploaded in a workflow artifact |
| GitHub Actions | ACR-GHA-018 |
high | Action from an owner that looks like a well-known one (typosquat) |
| Terraform | ACR-TF-001 |
critical | Sensitive port open to the whole internet |
| Terraform | ACR-TF-002 |
high | Storage or data service exposed publicly |
| Terraform | ACR-TF-003 |
high | Encryption or TLS weakened |
| Terraform | ACR-TF-004 |
high | Wildcard or owner-level IAM permission |
| Terraform | ACR-TF-005 |
medium | Kubernetes control plane reachable from the internet or legacy auth on |
| Terraform | ACR-TF-006 |
low | Module source not pinned to a version |
| Terraform | ACR-TF-007 |
medium | Database can be destroyed without protection or a final backup |
| Terraform | ACR-TF-008 |
medium | Instance metadata reachable without session tokens (credential theft via SSRF) |
| Terraform | ACR-TF-009 |
high | Resource policy allows any principal ("*") without conditions |
| Terraform | ACR-TF-010 |
medium | Encryption key rotation turned off |
| Terraform | ACR-TF-011 |
medium | Cloud audit logging turned off |
| Terraform | ACR-TF-012 |
low | Bucket uses per-object ACLs instead of uniform access |
| Terraform | ACR-TF-013 |
high | Serverless function exposed on a public URL without authentication |
| Terraform | ACR-TF-014 |
medium | Secrets and keys can be permanently deleted at once |
| Dependencies | ACR-DEP-001 |
high | certifi pinned before 2026.10.18, which still trusts the compromised emSign ECC root |
| Dependencies | ACR-DEP-002 |
high | Packages fetched over plain HTTP or from a host exempted from TLS checks |
| Dependencies | ACR-DEP-003 |
medium | Extra package index next to PyPI, open to dependency confusion |
| Dependencies | ACR-DEP-004 |
low | npm lock installs a package from git with no integrity hash |
| Dependencies | ACR-DEP-005 |
medium | pip requirement installed from a moving git ref |
| Dependencies | ACR-GHA-016 |
medium | Dependency installed from a moving git ref in a workflow |
Installed scanners run automatically on the file types they understand; missing ones are skipped and listed in the report.
| Scanner | Covers |
|---|---|
| Gitleaks | Secrets in any file |
| Semgrep | SAST for application code (ACR_SEMGREP_CONFIG, default p/default) |
| Trivy | Vulnerable dependencies and IaC misconfiguration |
| Checkov | Terraform, Kubernetes, Dockerfile, GitHub Actions |
| hadolint | Dockerfiles |
| actionlint | GitHub Actions workflows |
| kube-linter | Kubernetes manifests |
When two tools report the same issue on the same line, the report shows it once and credits both.
| Part | State |
|---|---|
| Diff-aware engine, 109 built-in rules, 7 scanner adapters (run in parallel) | ✅ Done, unit-tested |
| Markdown / JSON / SARIF reports, CLI with severity gate | ✅ Done, unit-tested |
| AI review agent (Claude + OpenAI-compatible) with read-only tools | ✅ Done, unit-tested with a fake model |
| Inline PR comments and composite GitHub Action (single run) | ✅ Done, unit-tested; first live run pending |
.acr.yml config, baseline, acr explain, pre-commit hook, container image |
✅ Done (v0.2.0) |
Scoped suppressions, --staged, GitHub annotations, acr init, coding-agent rules |
✅ Done (v0.3.0) |
Custom rules, --include, acr classify, SARIF fingerprints, secret redaction for the agent |
✅ Done (v0.4.0) |
GitLab and JUnit reports, --diff-file, expiring suppressions, baseline check/prune, Compose and agent-instruction files |
✅ Done (v0.5.0) |
How the pipeline is ordered and why: docs/architecture.md. Setups for GitLab, Jenkins, Azure DevOps, CircleCI, Bitbucket and other systems: docs/ci-integrations.md.
Inspired by the hybrid "deterministic pipeline + agent" approach popularised by Open Code Review; this project is an independent implementation focused on security, infrastructure and AI/agent code, with no code shared.
Other open-source infrastructure work by Sunil Mehta:
- k8s-inference-routing-benchmark: How much the router in front of LLM servers matters, measured: a Kubernetes Service against the llm-d Router's scorers on identical chat and RAG traces, with the picker's estimate of each pod's cache against the pods' real contents.
- kubernetes-secure-mlops-pipeline: A model's path from training to serving with every artifact signed, attested and admitted by policy, drilled in CI; reviewed by this tool on every pull request.
- k8s-gpu-scheduler-benchmark: Benchmarks Kubernetes GPU schedulers (default, Kueue, Volcano, KAI) in simulation and on real clusters.
- ai-devsecops-cicd: Reference DevSecOps pipeline for LLM and AI-agent services: AI security tests, SBOM/AI-BOM, signing, admission policy.
- llm-inference-platform: GPU-backed LLM serving with a streaming gateway, monitoring and reproducible benchmarks.
- jev-inference-router: Observable decision routing for local GPU inference, evaluated against rule-based and classifier baselines.
MIT © Sunil Mehta