Guard · Security

Read the security case, not the score

AI Security lists the open security cases in scope and opens each one on its Security section: its class, the detector version that fired, the affected assets and the exploit evidence. A conventional code defect, a runtime AI attack and an assistant or toolchain attack stay distinct, what no detector reads is named as not covered, and a containment request reads pending until the desktop confirms it. The Security tab's scans of the checkout sit beside it.

For: the engineer about to push, and the owner who decides which paths and permission rules a repository may not change quietly

What it does, and why it helps

The AI Security page is the open security cases on this machine, overdue first and then by severity, under the scope bar (the repository applies to the whole page; environment, service and time narrow the case list only) and the readiness line that says how old the prepared read is. A case opens in the case dialog on its Security section. Classification reads the category a person recorded against a versioned taxonomy, with the detector version and who recorded it; until then it reads Not recorded against a versioned AI security taxonomy. This case carries its detector's class only until a person classifies it. The Classify form offers four categories: conventional vulnerability, runtime AI attack, assistant or toolchain attack, and not a security incident, and it requires the detector version. Affected assets, Exploit evidence (as pointers) and Containment options read Not recorded when nothing was recorded, and a missing exploit record says absence of evidence is not proof nothing happened. Every case also carries Evidence coverage, the same seven families as on every other page.

Gaps are named rather than counted. The exposure inventory says what it does not read, for example Not covered, so not counted: MCP drift, and ends A gap is not zero exposure. The OWASP coverage matrix, with its Not covered because column, sits under the Reference disclosure after the evidence, because it describes what RepoOps can detect, not what was observed. No sentence on the page claims a runtime AI detector is watching when none is.

Containment is approved per repository and confirmed per leg. On the hosted case an owner presses Approve containment of <repository>; until then the Containment row reads that a status write to contained is refused. The hosted containment queue then shows each request's legs (Run token: revoked or not revoked; Branch: blocked or not blocked) and reads Revocation requested, not yet confirmed contained until the desktop reports every leg, then Contained: every leg completed or Not contained. A request no desktop has picked up for over an hour says so. On the desktop the Containment row reads whether a runtime HOLD is attached to the case; there is no containment apply control there.

The Security tab is one parent with subtabs. The parent renders the repository's own .claude/brain/security.md when it has one, and a placeholder that says what the tab is for when it does not. Four subtabs do the scanning. Guardrails reads every tracked and untracked-not-ignored file and runs seven checks: a .env file tracked by git, a vendor-shaped key or private-key block in a tracked file, the same in a file about to be committed, a server bound to the all-interfaces address, a wildcard or reflected CORS origin, a .env file that .gitignore does not cover, and a key-shaped string in a test or example file. Vulnerabilities runs npm audit over the repository root and each immediate directory with its own package.json. File integrity diffs the working tree against the branch the repository tracks, scoped to a watchlist of sensitive paths, and offers a diff and a revert per file. Permissions reads .claude/settings.json and .claude/settings.local.json and shows the resolved mode, every allow, ask and deny rule, the hooks, and the risk findings behind a posture score.

A scan that could not run says so. Guardrails paints Unknown, not clean: when the scan read no files or git failed, and a clean result reads that none of these checks fired, not that the app is secure. The other subtabs of the same parent (Investigations, Readiness, Session signals, AI incidents, Case, MCP security, Agent code scan, Security + Cost, Egress policy, Memory Integrity) each have their own guide. The AI Security page links its own supporting tools: Investigations, Session signals and Agent code scan.

The pain. A key lands in a file, the next git add commits it, and the first person to notice is outside the company. A dependency advisory sits in a workspace nobody audits. A protected file changes on a branch and the review does not look there.

The point of view. Read the checkout, not the story about it. Every finding should name the file and the line so the fix is a command, and a scan that did not run should never paint a clean banner.

What gets easier. Deciding what to fix before a push. Each guardrail finding lists its evidence rows with the secret masked to its first four and last two characters and the exact command. Each protected-path change shows its diff. Each permission finding names the rule and the replacement.

When it helps. Before a push, after an agent session, and when a teammate or a contractor has a branch open against paths that carry money, credentials, migrations or the repository brain.

Its limits. The seven guardrail checks are the high-frequency footguns, not an audit: cloud configuration, IAM and git history are out of scope, and the fixes point at the command rather than run it. npm audit covers npm advisories only and needs a lockfile per surface. File integrity is visibility plus a local revert; the enforcement that protected paths cannot land without review is branch protection and CODEOWNERS on the forge, which the tab does not check. The permission posture reads two files and cannot see what a user-level settings file adds.

Understand it in 30 seconds

30.1 s, captions on. Narration: Microsoft Zira Desktop (provisional voice; an approved narration source is pending).Transcript
Read the narration
  1. 0:00 A score says little. Which asset, which rule, which session?
  2. 0:06 A code defect, a runtime AI attack and a toolchain attack stay separate.
  3. 0:11 Each names its detector.
  4. 0:13 The case lists the affected assets and the exploit evidence.
  5. 0:17 What no detector reads is named, not counted as clean.
  6. 0:23 An owner approves containment for this repository.
  7. 0:26 Until the desktop confirms it, it reads pending.

Synthetic example. Read the guide

Where to find it

Where to find it

  • Desktop: localhost:4000. It has no sidebar row: open it from the palette, or go straight to its address.
  • Hosted: repoops.ai/team/security, from AI Security in the sidebar.
  • Keyboard: ⌘ K, then type “Security”.

When to use it

A key in a file that is not ignored

Situation. An agent session wrote a helper with a real Anthropic key inline. The file is new and .gitignore does not cover it.

What you do. Open Security, then Guardrails. Press Re-scan. Read the High finding A secret is about to be committed, its evidence row (path, line, the kind of key, the masked value) and the fix text.

What you see. The pills read the counts by severity. The finding carries up to twenty-five evidence rows; the value shows its first four and last two characters. The freshness line shows when the scan ran and whether it was cached.

What it establishes. The key is moved into a .env that .gitignore covers before the commit. The scan is read-only, so nothing changed until you edited the file; a re-scan shows the finding gone. A gone finding is not proof the key was never pushed elsewhere.

A protected path changed on a branch

Situation. The File integrity banner reports changes to protected paths against origin/main, and one row under auth reads modified.

What you do. Press View diff on the row. If the change was not meant, press Revert to main and confirm the dialog, which says the local file is overwritten, local changes are discarded, and a file absent from the branch is deleted.

What you see. The category header shows the globs it matched. The diff is capped at 24000 bytes and says so when truncated. After a revert the tab re-scans and the row is gone, or the revert refuses with a reason: a path off the watchlist, a path that escapes the repository, or a file committed on this branch that a working-tree restore cannot undo.

What it establishes. The working tree matches the tracked branch for that file. The revert is a git checkout of the branch copy; it does not touch commits, and RepoOps keeps no copy of what it overwrote.

A repository that grants itself bypass

Situation. A teammate committed .claude/settings.json with permissions.defaultMode set to bypassPermissions.

What you do. Open Security, then Permissions. Read the mode card, its source line (project or local), and the error finding under Risk findings.

What you see. The score reads 100 minus 40 for each error, 20 for each warn and 5 for each info. The finding says the repository runs with no permission prompts and names the fix: remove the mode and grant scoped allow rules.

What it establishes. The posture is a read of two files, so the fix is an edit to them and a reload of the tab. The score is a count of findings weighted by severity, not a measure of safety.

Before you start

Supported versions
RepoOps desktop v0.3.1, the release this guide was read against. Vulnerabilities needs npm on the machine and a package-lock.json or npm-shrinkwrap.json in each surface it should audit. File integrity needs the tracked branch fetched locally, which the mirror does on sync.
Where it runs
Local: the AI Security page, and the Security tab and its subtabs. Hosted: /team/security leads with Risk with context, the open security cases a machine published, under three figures (Open findings, Needs a decision, Published postures) and an aside, Coverage you can inspect. Each finding opens on the page, and below the list sit the AI Security cells: what each finding touches and which detectors abstained. The specialist readers are one closed All tools list in the aside. The security.md from the published brain snapshot sits in a collapsed Published security posture section below. The posture panel takes the repository only, and says the scope does not narrow it by environment, service or time. The hosted page never receives the working-tree scan, a diff or a secret value. Containment approval sits on the hosted case's Fix section, and the containment queue on the hosted incidents page.
Permissions
The scans run for whoever opens the desktop app on that machine. Recording a status on a finding takes the same-origin gate. On repoops.ai the page is session-authed and scoped to the repositories your membership allows; the extra sensitive paths are set by a member who can manage the team.
Connections
A repository registered in repos.config.json with a remote and a branch. Nothing else for the four scans. For the hosted page, the device bound to a team and the Brain snapshot and AI incident cases toggles on in Settings.
Plan
The scans have no plan gate; the pricing capability map has no row for them. Seeing the hosted page follows the hosted dashboard tiers.

Configure it

  1. Open Security and press Re-scan on Guardrails.

    The first open paints the last cached result, then refreshes. Re-scan forces a fresh read of the working tree (?fresh=1). The result is cached per repository in memory until the next Re-scan or restart; there is no timer.

  2. Read Vulnerabilities and pick a gate policy.

    The first open starts an audit in the background; the tab polls and says Scanning… results will appear here automatically. Gate policy has three buttons: Info only, Block prod high/critical, Block all high/critical. The choice lives in this browser (default prod) and drives the Copy YAML and Copy command snippets; it blocks nothing on its own.

  3. Check the watchlist on File integrity.

    Six default categories: secrets (.env*), payments, phi/health, migrations, auth, ledger/brain (.claude/brain/**). Each category header shows the globs the scan used. Harden this repo builds a CODEOWNERS body from the same list, with the owner read from the remote or a placeholder when none resolved.

  4. Add the team's extra paths on repoops.ai, if you run a team.

    On /team/overview, Extra sensitive paths to monitor takes one glob per line and stores them as the org-custom category. A bound desktop pulls the team policy every six hours and merges the globs into its watchlist; the scan, the diff and the revert all read the merged list.

  5. Make the permission posture explicit.

    Permissions reads .claude/settings.json and .claude/settings.local.json, local overriding project. Add a permissions block with scoped allow rules and a deny for secret files; the tab refreshes on reload. Autonomy zones are shown read-only there and change only in the repository's intent spec.

  6. Let findings become cases, if you want the AI Security page to fill.

    The session-signals tick writes durable findings every fifteen minutes by default. With REPOOPS_AIR_OPEN_SCHEDULE=1 in the data directory's .env, a finding at or above REPOOPS_AIR_CASE_MIN_SEVERITY (default high) opens a case, and the AI Security page lists it. Until then the page says Nothing to open on this lens.

  7. Classify each case before you act on it.

    Open the case, read Classification, then use the Classify form: a category, the detector version (required), the affected assets and the containment choices, comma separated. Record classification writes it with your name and the time. A case left unclassified keeps only its detector's family, and says so.

  8. Approve containment, then wait for the legs.

    On the hosted case an owner approves containment for the repository; the decision contains the case on that approval and no other. The request is pending until the desktop reports each leg. Read Revocation requested, not yet confirmed contained as not contained yet.

SettingWhereA sensible choiceWhy it matters
repos.<id>.branchrepos.config.jsonthe branch pull requests merge intoFile integrity diffs the working tree against <remote>/<branch>; with no value the base is origin/main. A base not fetched locally reads as a scan error with a sync hint, never as clean.
watchlist (team policy)repoops.ai, /team/overview, Extra sensitive paths to monitorthe globs a review must see, such as infra/** or **/*.pemMerged into the six default categories on every bound desktop; the scan, the diff and the revert read one list, so a team path is revertable the moment it is flagged.
vuln-gate-policyVulnerabilities subtab, Gate policy, stored in this browserBlock prod high/critical (the default)Decision support only: it colors the verdict for the current scan and fills the CI snippet. A surface whose production-only audit failed reads unverified for the prod gate.
REPOOPS_VULN_SCAN_CRON_OFFthe data directory's .env (read in server.mjs; not listed in .env.example)unsetEvery hour the server refreshes the one repository whose audit is stalest and older than the age below. 1 turns that off; the tab still audits on open.
REPOOPS_VULN_SCAN_MAX_AGE_HOURSthe data directory's .env (read in server.mjs; not listed in .env.example)24 (the default)How old a scan may be before the hourly refresh replaces it. A repository nobody has scanned comes first.
REPOOPS_SIGNALS_SCAN_INTERVAL_MSthe data directory's .envunset (15 minutes)The tick that writes durable findings from the session-signals scan. 0 disables it, and then no finding is written and no security case can open from one.
REPOOPS_AIR_OPEN_SCHEDULE, REPOOPS_AIR_CASE_MIN_SEVERITYthe data directory's .env1 to open cases; high (the default floor)A finding at or above the floor becomes a case on the AI Security page. Any value other than the string 1 leaves case opening off.
permissions.defaultMode, allow, ask, deny.claude/settings.json and .claude/settings.local.json in the repositoryno bypassPermissions in repo settings; scoped Bash(...) allows; a Read(.env) denyThese two files are the whole input to the posture. bypassPermissions in either is an error finding; Bash, Bash(*) or * in allow is a warn; no deny rule is an info.
Brain snapshot, AI incident casesSettings, What this machine publishes to your teamoff until the team should see security.md and the open casesThe first carries the repository's security.md to /team/security; the second carries the case rows the hosted list reads. Both are off by default and need a bound device.
ⓘ
To stop or undo
The four scans have no switch; they run when their subtab opens and never on a timer, except the hourly dependency refresh (REPOOPS_VULN_SCAN_CRON_OFF=1 stops it) and the findings tick (REPOOPS_SIGNALS_SCAN_INTERVAL_MS=0 stops it). Nothing they write changes the repository. The one mutating button is Revert to main, behind a confirm dialog; it restores the branch copy and cannot be undone for uncommitted work. Turn off the two publish toggles to stop the hosted page receiving anything new.

What you should see

A clean checkout

Configuration. A registered repository with a lockfile at the root, no team policy, the default watchlist, a settings.json with scoped allows and a secret deny.

Expect. Guardrails reads No footguns found across N files scanned. Vulnerabilities reads No known vulnerabilities per surface. File integrity reads No changes to protected paths vs origin/main. Permissions reads No risk findings, and the score is 100 only when no family, including the MCP and autonomy checks, added a finding.

Verify. The Guardrails banner names the file count it read; a count of 0 reads Unknown, not clean instead. The Vulnerabilities freshness line shows the scan time and vuln-scans.json in the data directory holds the result across a restart.

A scan that could not run

Configuration. A repository whose tracked branch was never fetched, or a surface with no lockfile, or a git command that failed.

Expect. File integrity reads Couldn't scan with the base ref and a hint to sync the repository first. Vulnerabilities marks the surface Not scanned with the npm error and audits the rest. Guardrails paints Unknown, not clean: with the git error.

Verify. The Guardrails response carries ok:false, clean:false and scanned null. No banner on any of the three reads clean for a scan that read nothing.

Findings that became cases

Configuration. REPOOPS_AIR_OPEN_SCHEDULE=1, a session-signals detector that fired at high, the AI incident cases toggle on.

Expect. The AI Security page lists the case, overdue first, then by severity, then most recently seen, and opens it in the case dialog on its Security section: Classification, Affected assets, Exploit evidence, Containment options, a Classify form with four categories, and the Containment row.

Verify. The count line reads N of M, overdue first, then by severity. On repoops.ai, /team/security lists the same case under Risk with context with its severity, its repository and its attribution band, and Inspect opens the case sections in place. The hosted Security section has no Containment options row, and its Containment row carries the approval text instead of the HOLD text.

Data and cost

What is captured
Guardrails, File integrity and Permissions keep their last result in server memory per repository (an LRU of 64 entries, gone on restart). Vulnerabilities persists the last audit per repository to vuln-scans.json in the data directory. Durable findings from the session-signals scan are appended to the repository brain's security-findings/YYYY-MM.jsonl with a redacted, capped excerpt; a status change appends a superseding row rather than editing one.
Who can see it
Local by default. With Brain snapshot on, the repository's security.md travels with the snapshot to the team's brain store and renders on /team/security. With AI incident cases on, the case rows go to the team. The working-tree findings, the diffs, the npm advisories and the permission rules are read on the desktop and never published by this feature.
How long it is kept
Findings are read over 24 months by the routes and 6 months by the default index. No retention knob shortens either. node scripts/security-findings-forget.mjs --apply gzips months older than the newest six in place and nothing is deleted; an archived month is still read. In-memory scan results last until the next Re-scan or restart; vuln-scans.json is rewritten on each audit.
What leaves the machine
The guardrail, integrity and permission scans read files and run git on this machine and send nothing. npm audit sends the dependency list to the npm registry, which is the one network call the four scans make. A bound desktop fetches the team policy over HTTPS on the device token every six hours. The two publish toggles above are the only RepoOps egress.
What it costs
No model call. The scans are regex, git and npm on the local machine. The Secrets in images section on Guardrails runs OCR locally with tesseract.js and downloads the recognizer on its first run. The standard installer leaves tesseract.js out to stay small; there the section says OCR not included in this build and every image reads as unknown, not clean.

When the result differs

SymptomLikely causeNext action
Guardrails reads Unknown, not clean: the scan read no files.git ls-files returned nothing, or every candidate was binary, over 512 KiB, or a lockfile.Confirm the repository path in repos.config.json is a git checkout, then Re-scan.
File integrity reads Couldn't scan and names origin/main.The tracked branch is not fetched locally.Sync the repository so the mirror fetches the branch, then Re-scan. With another branch named in repos.config.json, the banner names that one.
A surface reads Not scanned on Vulnerabilities.No package-lock.json or npm-shrinkwrap.json there, npm timed out after 90 seconds, or the registry was unreachable.Run npm install in that surface to create the lockfile, or check the network, then Re-scan. The other surfaces still report.
Revert to main refuses with committed on this branch but absent from the base.The file was added in a commit on this branch, so a working-tree restore cannot remove it.Revert the commit with git. The tab only restores the branch copy of a file the base already holds, or deletes an untracked one.
Revert refuses with not on the sensitive-path watchlist.The path matched no default category and no team glob when the revert ran.Re-scan so the tab and the revert read the same merged list; a cached scan from before a policy change can list a path the current list does not.
Permissions asks you to pick a repo.The subtab was opened without ?repo=<id>.Open it from the dashboard rail so the repository id travels with the iframe.
The AI Security page reads Nothing to open on this lens.No case of class security is open: REPOOPS_AIR_OPEN_SCHEDULE is not 1, no detector fired at the floor, no run is held, or the finding was suppressed.Check the findings on Session signals and the two case flags in the data directory's .env. An empty queue with the schedule off is a missing source, not a clean bill.
The containment queue reads Revocation requested, not yet confirmed contained.The request was approved and queued, and no desktop has reported every leg yet. After an hour with no report the queue says the daemon that performs the revoke may be off or unlinked.Treat the run as not contained. Check that the bound desktop and its capture daemon are running; Retry containment re-queues the request.
Classification reads Not recorded against a versioned AI security taxonomy.Nobody has classified the case; it carries only the detector's family.Record the class with the Classify form, including the detector version.
/team/security reads No security snapshot yet.No bound desktop has published a brain snapshot that includes security.md.Turn on Brain snapshot in the desktop app's Settings and wait for the next publish. The hosted posture is a parse of that file, not a scan.
Disable
Not provided as a switch. Close the subtab and nothing scans; the hourly dependency refresh stops with REPOOPS_VULN_SCAN_CRON_OFF=1 and the findings tick with REPOOPS_SIGNALS_SCAN_INTERVAL_MS=0. Turning off Brain snapshot and AI incident cases in Settings stops new hosted data; what was already published stays until replaced.
Roll back
Not provided. Revert to main overwrites the working-tree file with the branch copy, or deletes an untracked one, and RepoOps keeps no copy of what it replaced; the confirm dialog says so. Recover a reverted change from git only if it was committed or stashed first.
Revoke access
The scans use no credential. Device binding: disconnect in the desktop app forgets the binding and revokes the device token on the server, which also stops the team policy fetch and both publishers. A repository token in repos.config.json is not used by this feature.
Delete
Not provided. No route deletes a finding; the files are the repository brain's security-findings/YYYY-MM.jsonl, and the forget script archives months without deleting them. vuln-scans.json in the data directory holds the last audit per repository and is rewritten, never cleared, by the app. The in-memory results clear on restart.

Maintenance evidence

Feature id
security (spine leaf security)
Owner
AI security program: Security guardrails, the Vulnerabilities tab, Ledger integrity Layer C, Permissions posture P1, the AIR findings store (WS1), and the pillar landing tabs (LDG-0681, LDG-0688). Guide: LDG-0717.
Supported product version
RepoOps main at 509e8e8ad (after v0.3.2)
Last verified
2026-09-26, read again against origin/main at 509e8e8ad for the AI Security page (public/lib/pillar-landing.js, public/lib/scope-bar.js, public/lib/page-readiness.js), the Security section and Classify form (public/lib/case-sections.js, website/components/case-investigation-forms.tsx), the exposure inventory and the Reference disclosure (lib/home-content.mjs), containment approval (website/components/case-economics-forms.tsx) and the containment queue (website/app/team/(home)/agent-incidents/page.tsx). First read 2026-09-15 at 52366bb6d; labels read from the served tab source (public/security-guardrails.html, public/vulnerabilities.html, public/file-integrity.html, public/permissions.html, public/ai-security.html with public/lib/pillar-landing.js and public/lib/case-sections.js) and the hosted page source; not opened on a running instance.
Example fixtures
No fixture file; the inputs are inline in lib/security-guardrails.test.mjs (the seven checks, prose skips, the self-scan), lib/file-integrity.test.mjs (a temp git repository: scan, diff, revert, the watchlist refusal, the missing base), lib/vuln-scan.test.mjs (the audit parser, the job states, the stale pick), lib/permissions-posture.test.mjs and lib/routes/permissions.test.mjs (the findings and the 404), lib/security-findings.test.mjs (the store), and public/lib/pillar-landing.test.mjs (the queue ranking).
Source references
lib/canonical-spine.json, lib/canonical-tabs.mjs, lib/security-guardrails.mjs, lib/routes/security-guardrails.mjs, lib/vuln-scan.mjs, lib/routes/vulnerabilities.mjs, lib/file-integrity.mjs, lib/routes/file-integrity.mjs, lib/policy.mjs, lib/permissions-posture.mjs, lib/routes/permissions.mjs, lib/security-findings.mjs, lib/routes/security-findings.mjs, lib/session-signals-alert.mjs, lib/today.mjs, server.mjs, public/security-guardrails.html, public/vulnerabilities.html, public/file-integrity.html, public/permissions.html, public/ai-security.html, public/lib/pillar-landing.js, public/lib/case-sections.js, website/app/team/(home)/security/page.tsx, website/app/team/(home)/overview/page.tsx, lib/publish-catalog.mjs, lib/home-content.mjs, website/components/case-investigation-forms.tsx, website/components/case-economics-forms.tsx, website/app/team/(home)/agent-incidents/page.tsx, public/lib/scope-bar.js, public/lib/page-readiness.js
Documentation review
Revised 2026-09-26 by the authoring agent against the code (LDG-0988); independent review not yet recorded.
Video review
Narrated story rendered and published 2026-09-26 (render c39d78a77cb0, LDG-0988), beside the desktop navigation with the six pages as they ship. Six frames, the captions and the transcript were reviewed by the authoring agent, not an independent reviewer; the audio was not listened to by a person. It is a labeled synthetic explanation of a case and a pending containment request, not a recording of a production containment.

Last updated