{% extends "base.html" %} {% block title %}Documentation · ai-protect{% endblock %} {% block autorefresh %}window.AI_PROTECT_NO_AUTOREFRESH = true;{% endblock %} {% block content %}

Documentation

Step-by-step setup for every configurable knob. The ? bubbles on /settings link directly into the relevant section below.

Locale & time

Timezone

Any IANA timezone name (e.g. America/New_York, Europe/London, Asia/Tokyo). The dropdown shows 28 common ones; the free-text override accepts any of the 600+ IANA names. Wrong names are rejected with a clear error at save time.

Date format

Python strftime tokens. Defaults to %Y-%m-%d %H:%M:%S %Z.

  • %Y-%m-%d %H:%M:%S %Z2026-05-24 14:32:10 EDT (default)
  • %a %b %d %I:%M %pSat May 24 02:32 PM
  • %Y-%m-%dT%H:%M:%S%z2026-05-24T14:32:10-0400 (ISO 8601)

The format applies everywhere — /feeds last-fetch column, /history rows, /intel fetched-at column, finding evidence timestamps.

Paths & storage

Findings file

Append-only JSONL where every finding lands. Must be outside /tmp — that path is wiped on reboot and the May 2026 scans were lost that way. Default ~/.ai-protect/findings.jsonl. The UI launcher's --findings CLI flag overrides this setting at startup.

Manifests directory

Directory holding YAML manifest files. Leave blank to use pipeline/manifests/ relative to the install. The UI's --manifests-dir CLI flag overrides this at startup.

Source cache directory

Where the GitHub provider stores persistent clones when the clone strategy is set to cached. Default ~/.ai-protect/src-cache/; one subdir per <owner>/<repo>. Safe to delete at any time — the next scan will re-clone.

Source providers

A source provider materializes a manifest's code into a local directory before adapter dispatch. Two providers ship today:

  • local — read manifest.source_paths from the filesystem (the original behavior). No-op materialization, no cleanup.
  • github — clone the repo declared on the manifest before each scan. Supports public / private repos, PAT or GitHub App auth, github.com or GHES, per-scan-clone or persistent cache.

Set the global default on /settings, then optionally override per-manifest in the manifest YAML or via /manifests/<name>/edit.

GitHub base URL

For github.com: leave as https://github.com. For GitHub Enterprise Server: set to your GHES hostname (e.g. https://github.acme.com). The provider derives the API URL automatically (github.com → api.github.com; GHES → <ghes>/api/v3).

Repo visibility

Public repos clone without auth. Private repos require either a PAT or an installed GitHub App with read access. Switching to private reveals the auth method picker.

Authentication method

PAT — quick to set up; one token covers every repo the human user can see. Best for single-operator or homelab installs.
GitHub App — installable on an org or per-repo, fine-grained permissions, short-lived installation tokens minted on demand (auto-rotated; no human token to babysit). Recommended for org-wide deployments. More setup but no rotation churn afterward.

Create a fine-grained PAT

  1. Sign in to GitHub. Top-right avatar → Settings.
  2. Left rail bottom → Developer settings.
  3. Personal access tokensFine-grained tokensGenerate new token.
  4. Name it (e.g. ai-protect-scanner), set an expiration (90 days is reasonable).
  5. Resource owner: your user (or your org if the token can act on org repos).
  6. Repository access: select Only select repositories and pick the ones ai-protect should scan. (Avoid All repositories — narrower scope is safer.)
  7. Repository permissionsContents: Read-only. (Metadata is auto-included as read.) Leave every other permission untouched.
  8. Generate token. Copy the value immediately — GitHub shows it only once.
  9. Paste into the Personal access token field on /settings. Save.

Classic (unscoped) PATs work but are over-privileged for this use case — prefer fine-grained.

Create a GitHub App

  1. GitHub Settings → Developer settings → GitHub Apps → New GitHub App (or under an org's settings if the App should live on an org).
  2. Name: e.g. ai-protect-scanner. Homepage URL: anything (required field, not used).
  3. Webhook: uncheck "Active" — we don't use webhooks (yet).
  4. Repository permissions → Contents: Read-only. Leave everything else as No access.
  5. Where can this GitHub App be installed?: Only on this account (or "Any account" if you'll share it).
  6. Create GitHub App.
  7. On the created App's page, note the App ID at the top.
  8. Scroll to Private keysGenerate a private key. A .pem file downloads. Move it to a stable path (e.g. ~/.ai-protect/github-app.pem) and chmod 600.

App private key file

Absolute path to the PEM file from the previous step. chmod 600 is required — the file holds the App's signing key.

App installation ID

  1. From the App's settings page → left rail → Install App.
  2. Pick the user or organization to install on; choose which repositories.
  3. After installing, navigate to Settings → Integrations → Applications → Installed GitHub Apps (or for an org: https://github.com/organizations/<org>/settings/installations).
  4. Click Configure next to your App. The URL bar will show .../installations/<INSTALLATION_ID> — that numeric id is what to paste into App installation ID on /settings.

Clone strategy

per_scan — fresh shallow clone (--depth 1) to a temp dir for each scan; deleted after. No persistent state. Slower on the 2nd scan of the same repo because the clone repeats. Best when disk is constrained or you want clean isolation.
cached — clone once into the source cache dir; on subsequent scans do git fetch + checkout. Much faster on the 2nd+ scan of the same repo. The cache lives at the path configured under Source cache directory.

Default ref

Branch, tag, or commit SHA to scan when the manifest doesn't specify its own. Most repos: main (or master for legacy repos). Tags: v1.2.0. SHAs (7+ hex chars): the provider detects these and uses an explicit checkout step.

Clone depth

Number of recent commits to fetch. 1 (shallow) is fastest and sufficient for every SAST / dependency / IaC adapter we ship. Set 0 for full history — needed only if an adapter you wire in does git blame or commit-range analysis.

Manifest fields (the /manifests form)

An app manifest declares one AI workload (an agent, an assistant, an automation runner — anything the pipeline scans). The four Risk tiering dimensions feed the classifier in pipeline/core/tiering.py: each one scores 1-4 (riskier is higher); the sum + a few forced rules determine Tier 1-4, which in turn decides which adapters run at which SDLC stage.

Name

Identifier — letters (mixed case allowed), digits, dashes, underscores; must start with a letter. This becomes the YAML filename and the app_name on every finding, so keep it stable: renaming requires the App aliases field below to preserve resolution history.

Two names that differ only by case (MyApp vs myapp) are refused on save — they'd resolve to the same file on case-insensitive filesystems (macOS HFS+, Windows NTFS). String comparisons elsewhere in the pipeline (dashboard filter, change-store lookup, intel cross-ref, auto-resolve) are case-sensitive, so pick one canonical form per app and stick to it.

Owner email · On-call email

Accountable owner gets findings notifications; on-call is paged when blocking gates trip during a release. If on-call is blank it defaults to the owner.

Description

Free-text: what the app does + the security shape (where data flows, which surfaces accept user input, what irreversible actions are possible). Also tokenized by the intel_match adapter to find CVE feeds that mention products the app uses.

Data sensitivity (Risk tier dimension 1 of 4)

The most-sensitive data class the app touches. PHI handling forces Tier 1 regardless of every other dimension (HIPAA / HITRUST baseline).

  • all — the app spans multiple classes (e.g. PHI + PII). The classifier resolves "all" to the most-restrictive value (phi) so tiering is conservative.
  • phi — protected health information (patient names, MRN, DOB, diagnoses, labs, etc.) → score 4 / 4. Forces Tier 1.
  • pii — personally-identifiable information that isn't PHI (employee records, customer contact info) → 3 / 4.
  • financial — payment, billing, banking data → 3 / 4.
  • confidential — internal company data not in the categories above (roadmaps, code, internal docs) → 2 / 4.
  • public — only public-domain or marketing content → 1 / 4.

Decision impact (Risk tier dimension 2 of 4)

What the worst possible model output could cause. clinical_influence forces Tier 1 (FDA SaMD reclassification risk).

  • all → resolves to irreversible.
  • irreversible — model output can trigger an action that can't be undone (sent message, posted comment, deleted record) → 4 / 4.
  • clinical_influence — output may shape a clinical decision (diagnosis, dosage, treatment selection) → 4 / 4. Forces Tier 1.
  • automated_action — output triggers a downstream action that has effects but is recoverable (ticket created, email drafted) → 3 / 4.
  • advisory — output is shown to a human who decides whether to act on it (summary, recommendation, draft text) → 1 / 4.

Integration footprint (Risk tier dimension 3 of 4)

How much the app reaches into other systems.

  • all → resolves to external_action.
  • external_action — calls third-party APIs that affect external state (post to Slack, push to GitHub, call a partner's API) → 4 / 4.
  • agent_tool_use — model invokes tools / MCP servers internally; tools may call out → 3 / 4.
  • write_back — writes to a system of record (EHR, CRM, the company DB) → 3 / 4.
  • read_only — reads from systems but never writes; pure inference → 1 / 4.

User population (Risk tier dimension 4 of 4)

Who can talk to the app. external + non-advisory forces Tier 1 (an outside user driving consequential output is the highest-blast-radius shape).

  • all → resolves to external.
  • external — public internet, customers, partners → 4 / 4.
  • enterprise — anyone with a company SSO login → 2 / 4.
  • team — a specific named team / org unit → 2 / 4.
  • single_user — only the app's owner uses it (CLI tools, personal automation) → 1 / 4.

Model endpoints

Every model the app sends prompts to. Each row:

  • name — short label you use to refer to this endpoint elsewhere.
  • provideranthropic / openai / azure / internal / etc.
  • model — the model id (e.g. claude-sonnet-4-6, gpt-4o).
  • auth env — the environment variable name holding the API key (e.g. ANTHROPIC_API_KEY). The pipeline reads from os.environ; this manifest field declares which var to read.
  • via gateway — model traffic flows through your sanctioned gateway (Bedrock, a homegrown proxy, etc.) rather than direct provider calls. PHI handling REQUIRES this to be true.
  • BAA covered — the provider has signed a Business Associate Agreement covering this app. PHI handling REQUIRES this to be true.

MCP servers

MCP (Model Context Protocol) servers the app's model can invoke as tools. Each row:

  • name — the MCP server's identifier.
  • tier (1-4) — risk tier of the MCP itself. The app's tier inherits the most-restrictive MCP tier — so a Tier 4 app that calls a Tier 1 MCP is auto-classified Tier 1.
  • data_scopephi / pii / internal / public — what the MCP touches.
  • actions — comma-separated allowed tool names. Anything outside this list is a scope violation.
  • side_effectsread_only / mutating / irreversible.
  • third_party — checked if the MCP is operated by an external vendor (additional review burden).

Surfaces

Attacker-controlled inputs the app exposes:

  • user chat — interactive chat input (human types prompts).
  • email intake — emails the app ingests for processing.
  • document ingest — file uploads, RAG corpora the user can add to.
  • webhook — HTTP webhooks invoked by external systems.
  • voice — speech-to-text ingress.

The more surfaces are checked, the larger the prompt-injection attack surface — adapters like garak + pyrit probe these.

Target environment

  • Base URL — primary HTTP endpoint the app exposes. DAST scans (ZAP, nuclei, sqlmap) target this.
  • API URL — separate API endpoint if it differs from the base URL.
  • Test user token env var — env-var name holding a credential for a low-privilege test user. When set, mcp_scope performs a live probe to confirm forbidden actions are actually refused (vs static-only checks otherwise).
  • Allow mutation — required true for any adapter that modifies state (Burp active, ZAP active, sqlmap, atomic, caldera, metasploit, ride). Default off.
  • Network allowed zones — CIDR list that pre-approves internal-IP DAST scans for this app. The DAST URL safety guard refuses RFC1918/loopback/etc. by default; CIDRs listed here lift the refusal for this manifest's target.

Expected actions · Expected data scopes

Ground-truth allow-list of what the app's MCPs / tools should be exposing. Anything an MCP exposes outside the expected_actions list is flagged as a scope violation by mcp_scope. Same idea for data scopes.

Threat model path

Path to a signed-off threat-model YAML artifact. Required for Tier 1 / 2 at the design stage; the threat_model_check adapter fails the gate if missing. See pipeline/fixtures/clinical_assistant_threat_model.yml for the shape.

Guardrails path

Path to a NeMo Guardrails config (input + output rails). Required for Tier 1 / 2; guardrails adapter fails the gate if missing. See pipeline/fixtures/clinical_assistant_guardrails.yml for the shape.

Source paths

One path per line. Every SAST / SCA / IaC adapter scans these. Used by Source-provider local (the default). For GitHub-clone provider, leave blank — the orchestrator materializes the repo and sets this automatically.

Source excludes

One pattern per line. Filters findings post-hoc: /abs/prefix (absolute prefix match), *.pyc (glob), or __pycache__ (substring of path). Common defaults are already skipped by most adapters; use this for app-specific excludes (e.g. vendored deps, generated code).

App aliases

Previous names this manifest inherits resolution history from, one per line. When a manifest is renamed (old_namenew_name), list old_name here so the dashboard treats this app's same logical findings (same adapter + category + title) as resolved when the predecessor's Change has been applied. Re-keys fingerprints at view time; no data is modified on disk.

DAST defaults

Live-target (DAST) scans — safety first

Every DAST scan sends real HTTP requests to a chosen target. The settings below apply universally; per-manifest overrides live on the manifest's target block. Hard-denied IPs (cloud metadata endpoints like 169.254.169.254 and equivalents) can never be overridden, even by an internal-scan toggle.

Max requests per second (per adapter)

Soft cap that's translated into per-adapter CLI flags:

  • nuclei -rate-limit <n>
  • ZAP attack-strength / threadsPerHost (best-effort)
  • sqlmap --threads (when active mode is allowed)

Adapters that don't expose a rate flag fall back to their own defaults; the per-adapter timebox below is the universal backstop.

Max concurrent requests (per adapter)

Concurrency hint for adapters with a parallel-worker model (nuclei -concurrency, ZAP threadsPerHost). Keep low (≤10) when scanning preprod systems that don't have production rate-limiting in place.

Per-adapter hard timebox (seconds)

Maximum wall-clock duration for any DAST adapter subprocess. Default 30 minutes. This is the universal stop-gap that catches adapters that ignore --rate-limit flags. Set 0 to disable, but you almost certainly don't want to.

Require scope prefix for crawlers

When checked, crawler-class adapters (ZAP spider, katana via recon, owasp_noir) refuse to launch against a bare origin — i.e., a URL whose path is / or empty. The operator must supply a scope prefix like https://target.example.com/myapp/ so the crawl can't escape into unrelated content (vendor blogs, marketing pages, admin panels).

Recommended on. Disable only when scanning a single-purpose host where the entire HTTP surface is in-scope.

Hard-denied IP ranges (always refused): cloud metadata endpoints (169.254.169.254 and equivalents), URLs with embedded credentials (user:pass@host), and non-http(s) schemes. Default-denied (overridable with the per-scan Allow internal-network scan typed confirmation or per-manifest target.network_allowed_zones): RFC1918, loopback, link-local, IPv6 ULA / link-local, multicast, documentation ranges, CGNAT, benchmark, reserved.

Remediation behavior

Auto-resolve on re-scan

The single biggest difference between an audit checklist and a working assurance pipeline: findings should close themselves when the scanner can no longer reproduce them. Manually clicking "applied" on dozens of fixed CVEs defeats the point of automation.

When the toggle is on, every scan compares fingerprints emitted by its status=ok adapters against fingerprints those same adapters emitted in past runs. Any prior fingerprint that does NOT re-appear is written as a Change with state applied and strategy auto_resolve_absent — the dashboard's resolved-stripping logic then hides it from the active view.

Guards (each prevents a class of false positive):

  • Adapter scope — only adapters whose status this scan was ok can auto-resolve their own fingerprints. If garak was unavailable (tool not installed), garak findings stay open — absence isn't evidence when the adapter didn't look.
  • Stage scope — a build-stage scan can't auto-resolve preprod findings. We can only speak to what THIS run covered.
  • Honor revert — if you manually reverted a Change for a fingerprint, that's a "no, leave it open" signal. The fingerprint will not auto-resolve again on subsequent absent re-scans until you re-propose it through the normal workflow.
  • Skip already-resolved — fingerprints whose latest Change is already in applied / validated / deployed are left alone (no duplicate Change records).

Provenance: each auto-resolution writes a Change with actor="auto-resolve", strategy="auto_resolve_absent", a summary noting the scan id and adapter, plus a scan.auto_resolved event on /history so it's auditable.

To disable, uncheck the setting on /settings → Remediation behavior. Use cases for disabling: stochastic adapter pipelines where you don't trust single-run absence as evidence, or audit environments where every closure needs a human signature.

Intel feeds defaults

Default polling interval (seconds)

How often the background poller refetches each newly-added feed. Minimum 60s (enforced server-side). 3600 (1h) is reasonable for most CVE feeds; 86400 (daily) is fine for the long-tail tag-feeds. Per-feed override available on /feeds → Edit.

intel_match emission floor

The intel_match detection adapter capable of emitting at any severity from info to high (capped — never critical on intel alone since matches are unverified token overlap). This floor drops anything below it. KEV-listed matches always emit regardless, because active exploitation in the wild is significant even when unverified.

Disable KEV ratchet

The orchestrator's enrichment hook bumps any scanner finding whose CVE appears on CISA KEV up to CRITICAL severity. Check this box to opt out — useful in test environments where the bump confuses downstream policy gates. Default: ratchet on.

{% endblock %}