aicheck
Find exposed self-hosted AI services — in CI, or continuously across your estate.
One engine, four doors (pip / GitHub Action / Docker / site):
aicheck <target>— CI feeder: fail the build if a PR ships an unauthenticated AI serviceaicheck inventory— local continuous inventory: multi-host, stable finding IDs, drift (new/fixed/still_open), no phone-home
Live-probes Ollama, n8n, vLLM, Langfuse, Open WebUI, ComfyUI, Ray, Dify, Qdrant, AnythingLLM, Jupyter, Gradio, Langflow, Flowise, Chroma, Weaviate, Redis consoles, MCP servers and more — grades A–F, SARIF on by default in CI, plain-English fix cards. From unauth.dev.
Install from the GitHub Marketplace,
or follow the steps below. Maintainer listing notes:
docs/marketplace.md.
Add to your repo (60 seconds)
- Copy
examples/github-action.ymlto.github/workflows/aicheck.yml(or use the minimal snippet below). - Point
targetat the host your job starts (oftenlocalhost+ aservices:block). - Ensure the job has
permissions: security-events: writeso SARIF lands in Security → Code scanning.
name: ai-stack-exposure
on: [pull_request]
permissions:
contents: read
security-events: write
jobs:
aicheck:
runs-on: ubuntu-latest
steps:
- uses: unauthdev/aicheck-scan@v1
with:
target: localhost
Pin @v1 for floating majors, or @v1.1.1 for an exact release. More examples:
examples/.
Why live probing
This is not a config linter. The action starts from what actually answers: it runs the same read-only GET probes the unauth.dev scanner runs, against the real service in your job. If Ollama responds unauthenticated on 11434, that's ground truth — no guessing from compose files, near-zero false positives.
It answers one question: "did this PR ship an AI service with no auth?" It does not prove internet reachability (your firewall/proxy is invisible from CI) — that's what post-deploy monitoring is for.
One engine, four doors
| door | install / use | when |
|---|---|---|
| pip CLI | pip install aicheck-scan → aicheck your-host |
check any machine, right now |
| GitHub Action | uses: unauthdev/aicheck-scan@v1 |
every PR, in the build |
| Docker | docker run ghcr.io/unauthdev/aicheck:v1 your-host --allow-private |
GitLab, Bitbucket, Azure, Jenkins, bare CI |
| site scanner | unauth.dev | zero-install, from the internet's side |
Same engine, same severity model, same grade — pick the door that fits.
Usage (fail the PR on exposure)
name: ai-stack-exposure
on: [pull_request]
permissions:
contents: read
security-events: write # SARIF → code scanning (default on)
jobs:
aicheck:
runs-on: ubuntu-latest
services:
ollama:
image: ollama/ollama:latest
ports: ["11434:11434"]
steps:
- uses: unauthdev/aicheck-scan@v1
with:
target: localhost
fail-grade: C # D or F fails the build
Full copy-paste: examples/github-action.yml.
A default Ollama container fails — that's the point. Fix it (the annotation
links the fix card), watch it go green.
What you get on the run page:
aicheck — grade F
Your PR ships 2 exposed AI services — anyone who can reach them can use them.
severity service finding fix CRITICAL Ollama API exposed without authentication fix card HIGH n8n settings endpoint readable without authentication fix card
Inputs
| Input | Default | Meaning |
|---|---|---|
target |
(required) | Host to probe. No port — well-known AI-service ports are probed. |
fail-grade |
F |
Fail if the grade is this or worse. F = only critical exposure fails; C = anything above clean fails. |
Note: fail-grade: A fails the build even on a clean scan; it exists to
smoke-test the wiring on first install.
| services | (all 17) | Comma-separated product filter, e.g. ollama,n8n. |
| upload-sarif | true | Upload results to code scanning. Set false to skip (no security-events permission needed then). |
Outputs
| Output | Meaning |
|---|---|
grade |
A (clean), C, D, or F (critical exposure). |
Install (local CLI)
pip install aicheck-scan
# CI / single host (same as the Action)
aicheck example.com
aicheck scan localhost --allow-private --fail-grade F
# Local estate inventory (air-gapped; nothing phones home)
aicheck inventory --targets targets.yaml --state-dir ./state --allow-private
# CSV / flow-log JSONL / CIDRs + webhook to YOUR endpoint on new findings
aicheck inventory --targets hosts.jsonl --state-dir ./state --allow-private \
--webhook https://hooks.example.internal/aicheck --webhook-on new
Probe contract: docs/PROBES.md.
Targets: YAML / CSV / JSONL examples under examples/.
The package installs the aicheck console command — same engine the Action and
the Docker image run.
Paranoid path — pin by hash, don't trust the index:
pip download aicheck-scan --no-deps -d /tmp/aicheck
pip install --require-hashes aicheck-scan \
--hash sha256:<hash from the release notes>
Hashes are in the release notes for each version. Details and verification: docs/trust.md.
Air-gapped / offline
The only call the engine makes beyond your target is an optional weekly PyPI version check. To run fully offline — air-gapped networks, locked-down runners — disable it either way:
aicheck example.com --no-version-check # per run
export AICHECK_NO_VERSION_CHECK=1 # per environment
With that off (and --dry-run to prove it), nothing is dialed except the
hosts you name. Inventory mode is offline by design.
Auditability
the engine is dependency-light Python (httpx + pyyaml). don't trust us: run
--dry-run, run it behind a proxy, or read it — the core is an afternoon's
audit. full trust page: docs/trust.md.
Privacy / supply chain
- Runs entirely on your runner. Probe traffic is read-only GETs to your
target. The only other dial is an optional weekly PyPI version check
(opt out:
--no-version-check/AICHECK_NO_VERSION_CHECK=1) — see docs/trust.md. No telemetry to unauth.dev. - No credentials needed. No Docker socket. No privileged mode.
- What it probes: well-known metadata endpoints only (version, tags, settings). No logins, no POSTs to your services, no exploit verification.
GitLab CI
The engine is a plain CLI — GitLab support is config, not code. The one-liner (preferred, uses the published image):
aicheck:
image: ghcr.io/unauthdev/aicheck:v1
services:
- name: ollama/ollama:latest
alias: ollama
variables:
TARGET: ollama # the service alias
script:
- python -m aicheck.scan "$TARGET" --allow-private --fail-grade F
The full version — one scan, SARIF artifact, pipeline fails on grade — with the source pinned to the v1 tag (never track main):
aicheck:
image: python:3.11-slim
services:
- name: ollama/ollama:latest
alias: ollama
variables:
TARGET: ollama # the service alias — or localhost with a before_script install
before_script:
- pip install --quiet httpx pyyaml
- git clone --depth 1 --branch v1.1.5 https://github.com/unauthdev/aicheck-scan.git /aicheck
script:
- cd /aicheck
- python -m aicheck.scan "$TARGET" --allow-private --format json --fail-grade F > "$CI_PROJECT_DIR/aicheck.json" || code=$?
- test -s "$CI_PROJECT_DIR/aicheck.json" && python -m aicheck.render "$CI_PROJECT_DIR/aicheck.json" --format sarif --redact > "$CI_PROJECT_DIR/aicheck.sarif" || true
- test -s "$CI_PROJECT_DIR/aicheck.json" && python -m aicheck.render "$CI_PROJECT_DIR/aicheck.json" --format text || true
- exit ${code:-0}
artifacts:
when: always
reports:
sarif: aicheck.sarif # vulnerability report + MR security widget (GitLab Ultimate)
paths:
- aicheck.sarif
expire_in: 30 days
On Free/Premium the findings print in the job log and the pipeline still fails on grade — the SARIF dashboards (pipeline Security tab, vulnerability report, MR widget) need Ultimate.
Any CI with Docker
The same ghcr.io/unauthdev/aicheck:v1 image works on Bitbucket Pipelines,
Azure DevOps, Jenkins, and bare CI runners — anywhere that can run a
container.
CLI
The same engine runs standalone — install it from PyPI (see Install above):
pip install aicheck-scan
aicheck localhost --allow-private
aicheck example.com --format sarif --fail-grade C
Exit codes: 0 pass, 1 grade at or worse than --fail-grade, 2 target
error. Without --allow-private, only public IPs/hostnames resolve (the CLI
guards against scanning internal infrastructure by accident).
Two flags expose the trust surface before and during a scan:
aicheck example.com --dry-run # print every request it would send — no sockets, no DNS
aicheck example.com --verbose # log each dialed connection (with pinned IP) to stderr
License
MIT — see LICENSE. Fix cards and grading by
unauth.dev; findings link to the public fix library at
unauth.dev/fixes/. Security reports: SECURITY.md.
Metadata
Release files for aicheck-scan 1.2.4
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| aicheck_scan-1.2.4.tar.gz | 70.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| aicheck_scan-1.2.4-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 169.3 kB
Release files / aicheck_scan-1.2.4.tar.gz
| Download URL | aicheck_scan-1.2.4.tar.gz |
|---|---|
| Size | 70.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
2f8b50a16b5f1233b26a2b3fffe1d5aa7e4d773831c1e21b9941009a6bf3bbdf
|
|
BLAKE2b-256 checksum How to use checksums |
fd384d4a12e1c6e5a501cfbea8e5ff7cf5b2c178f07ea6087609006b4752c3e7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 5, 2026.
Transparency logRelease files / aicheck_scan-1.2.4-py3-none-any.whl
| Download URL | aicheck_scan-1.2.4-py3-none-any.whl |
|---|---|
| Size | 99.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
3dad337ea530cce33d185c24a74c32227d8d3597f6bdf921dedbca069f739ade
|
|
BLAKE2b-256 checksum How to use checksums |
9d1c34e9b0a553909247d77d3e125ea24271a02766a3a8a2af965b164a7ba35a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 5, 2026.
Transparency log