01Why OrthoSec
Generic SAST wasn't built for AI risk. It doesn't know that model output is untrusted, that a system prompt is a trust boundary, or that an agent tool that shells out is one injection away from RCE. OrthoSec is built around exactly those facts.
Zero false positives, by design
Dataflow taint analysis — not line-proximity regex. It fires only when the actual untrusted data reaches the actual sink. Benchmark-gated at 100% precision.
Intra → interproc → cross-module
Traces taint across function calls and import boundaries (including re-export chains), respecting sanitizers and trust boundaries.
Executive briefing on top
An optional model layer explains business impact and drafts fixes — grounded on the deterministic findings, never the source of one.
02Quickstart
The core scanner needs no API key and no configuration.
# install (add language extras you use) pip install "orthosec[intel,ts,go,java,kotlin,csharp,ruby,php,rust]" # scan a local path — writes a self-contained HTML report orthosec scan ./my-ai-app # scan across cores, gate CI on HIGH+, emit SARIF orthosec scan . --jobs auto --fail-on high --sarif orthosec.sarif # clone & scan a remote or private repo (credentials stay off argv/logs) orthosec scan owner/repo --git-token-stdin
03One scan, four lenses
The same findings, re-framed for the reader. Pass --profile,
or flip it live in the HTML report.
Fix it
File, line, taint path, and the concrete remediation.
Triage it
OWASP LLM + MITRE ATLAS mapping, severity, confidence tier.
Report it
Business risk, posture score, annualized loss exposure bands.
Ship it
What blocks release and what's acceptable, in plain language.
04What it detects
Full OWASP LLM Top-10 (2025) coverage — 11 built-in detectors, plus an optional deterministic Semgrep engine for general code-security rules.
| Detector | OWASP | Catches |
|---|---|---|
prompt-hardening | LLM01 / 07 | Untrusted input concatenated into prompts; secrets in system prompts |
secrets | LLM02 | Hardcoded provider / model API keys |
unsafe-model-load | LLM03 / 04 | pickle / torch.load / unsafe deserialization; unpinned model fetches |
dependency-audit | LLM03 | Unpinned / untrusted-source AI deps; known CVEs via OSV.dev |
data-poisoning | LLM04 | Fine-tuning / training on untrusted data |
output-handling | LLM05 | Model output into eval / shell / SQL / HTML sinks |
tool-exposure | LLM06 | Over-privileged agent tools with no confirmation gate |
prompt-leakage | LLM07 | System prompt written to logs / stdout |
rag-trust | LLM08 | Untrusted content ingested into a retrieval corpus |
misinformation | LLM09 | Ungrounded output in a high-stakes domain (advisory) |
unbounded-consumption | LLM10 | Uncapped LLM calls; unbounded agent loops (denial-of-wallet) |
05Nine languages, same discipline
Python uses the stdlib ast; every other language uses
tree-sitter (JS also esprima). Each is an optional extra — without it, that language falls back
to regex automatically, never a crash.
AST = precise dataflow ·
pattern = scoped pattern-match.
| Language | LLM01 | LLM05 | LLM06 | LLM10 |
|---|---|---|---|---|
Python built-in | AST | AST | AST | AST |
TypeScript / JSX [ts] | AST | AST | pattern | AST |
JavaScript [js] | — | AST | pattern | AST |
Go [go] | AST | AST | pattern | AST |
Java [java] | AST | AST | pattern | pattern |
Kotlin [kotlin] | AST | AST | pattern | pattern |
C# / .NET [csharp] | AST | AST | pattern | pattern |
Ruby [ruby] | AST | AST | pattern | pattern |
PHP [php] | AST | AST | pattern | pattern |
Rust [rust] | AST | AST | pattern | pattern |
The AST paths (LLM01 untrusted→prompt, LLM05 output→sink) trace taint intra-function → interprocedural → cross-module, framework-aware of LangChain, LlamaIndex, OpenAI, Anthropic, Flask/FastAPI/Django and each language's popular SDKs.
06The trust model
Models make OrthoSec smarter without making it a guesser. The rule is directional:
Deterministic is the arbiter
The 0-FP deterministic engine is the floor. Optional model passes corroborate
(raise to confirmed) or discover extra advisory issues —
deduped and excluded from the score/gate. A model never removes or invents a finding.
Strongest first, re-scan verified
remediate --auto tries a deterministic codemod, then a local model, then a cloud
model — and re-scans after each. A fix that doesn't verify is reverted, not shipped.
07CLI & configuration
| Command | What it does |
|---|---|
scan <path | url | owner/repo> | Scan a local path or clone & scan a remote/private repo. --jobs, --profile, --fail-on, --sarif/--json/--html, --diff, --baseline |
ask <path> "<question>" | Grounded Q&A over the findings (needs a model backend) |
remediate <path> --auto | Verify-gated auto-fix cascade (deterministic → local → cloud) |
watch / schedule | Continuous or cron-scheduled scans with report output |
Key environment variables
| Variable | Effect |
|---|---|
ORTHOSEC_JOBS | Worker processes (same as --jobs). Findings are byte-identical to a serial scan; ~4× on large trees |
ORTHOSEC_SEMGREP=1 | Enable the bundled deterministic Semgrep engine (opt-in, off by default) |
ORTHOSEC_OSV=1 | Enrich pinned AI/ML deps with known CVEs from OSV.dev |
ORTHOSEC_CONFIDENCE=1 | Model corroboration → confidence tiers (needs a model backend) |
ORTHOSEC_DISCOVER=1 | Model-led discovery of extra advisory findings (excluded from the gate) |
ANTHROPIC_API_KEY · ORTHOSEC_LOCAL_MODEL_URL | Enable the executive briefing / Q&A / auto-fix (cloud or a local OpenAI-compatible model) |
08CI / CD
Gate pull requests on AI-security risk. Drop in the GitHub Action, or run the CLI / Docker anywhere.
# .github/workflows/orthosec.yml — scan changed files, upload SARIF
- uses: cloudivian-org/OrthoSec@main
with:
profile: appsec
fail-on: high
diff-ref: ${{ github.event.pull_request.base.sha }}
# any CI, or locally — via the published container
docker run --rm -v "$PWD:/scan" ghcr.io/cloudivian-org/orthosec \
scan /scan --sarif /scan/orthosec.sarif --fail-on high
--write-baseline) and gate on new findings only, so day-one noise never blocks CI.09Private & remote repos
Scan a repo you don't have checked out. The host (GitHub / GitLab / Bitbucket) is auto-detected, and credentials are handled safely — passed via an askpass helper and child-process env, never on the command line or in logs.
# token read from stdin, never argv orthosec scan owner/private-repo --git-token-stdin --branch main # or use your SSH agent / git credential helper transparently orthosec scan git@github.com:owner/private-repo.git