Skip to content

Security: agentloopr/Triage

Security

SECURITY.md

Security

Scope

This is a reference implementation, not a maintained service — see the README. There is no patch SLA and no guarantee a report gets fixed on any timeline. What is guaranteed:

  • every push to main, and every pull request, runs a full-history gitleaks scan and an internal-identifier guard in CI (.github/workflows/ci.yml, secrets job). main is protected and requires those checks. The metrics branch is written by a bot and is not covered by that job — it is constrained instead by an allowlist in traffic.yml that refuses to publish anything but aggregate counts

What happens to untrusted text before it reaches a model

Every externally-authored input is screened at the prompt boundary — the source (transcript, channel log, email thread, GitHub activity, Drive comments), the board snapshot, comment history, retrieved documents, and every tool result:

  • Secrets are redacted unconditionally. A key pasted into a Slack channel or a card description never leaves the process. This is not hypothetical for chat sources.
  • Injection patterns are detected and the text is framed as data, not dropped — removing the matched line destroys the evidence the categorization pass needs to match a card.
  • The alert names the rule, the length and a digest — never the matched text. A security log that copies the content it flagged is a second store of the thing worth protecting.

This is not a sandbox. The pattern list is regexes; a rephrased attack walks past it. What bounds the damage is structural and downstream: every write passes the gates, and Pass 2b re-derives the categorization blind.

In the default configuration, the writer is deterministic and the guarantee is absolute:

A successful injection can mislead a categorization. It cannot author a write.

There is no code path from a model turn to a mutation, because no write tool exists for one to reach.

With BOARD_AGENT_WRITES on, the guarantee narrows, and the narrower version is the one to hold us to:

A successful injection cannot author a write the deterministic gates would not already have approved.

That mode hands the board agent write tools, so a model does reach the tracker. Every write it originates is rebuilt into a manifest item and re-run through the same gates the pipeline's own answer faced — routing, roster, evidence, duplicate, critical — and a write those gates refuse becomes a hold. What an injection could still do is steer a write that passes every gate: a plausible card, on a real list, for a real person. What it cannot do is rotate a credential, reach an off-roster assignee, or invent a list. The gate list in ARCHITECTURE.md is the actual boundary, and it is worth reading before turning the flag on rather than after.

The flag is off by default, and the paragraph above is the reason it is a flag rather than the default.

Which commands touch a live service

Eleven, not the four an earlier version of this file claimed — that version predated scripts/, and the count drifted again the moment those shipped without this table being updated. Whatever this number says next, verify it against package.json's scripts block rather than trusting it on its own:

command network credentials writes?
npm run board tracker tracker token no — read-only
npm run pull source + model provider + tracker source, model, tracker only with --write; plans otherwise
npm run poll source(s) + model provider + tracker source, model, tracker yes, by default (cron shouldn't silently only-plan); --dry-run plans instead
npm run serve inbound HTTP + source + model provider + tracker GITHUB_WEBHOOK_SECRET, SLACK_SIGNING_SECRET, source, model, tracker yes — a verified delivery triggers a live write, same as poll
npm run answer tracker tracker token yes — --approve executes a held write
npm run record model provider model key writes cassettes to disk, not to a tracker
npm run record:regression model provider model key writes cassettes to disk, not to a tracker
npm run cost:deepseek model provider (DeepSeek) DEEPSEEK_API_KEY no — tallies token usage only
npm run cost:claude model provider (Anthropic) ANTHROPIC_API_KEY no — tallies token usage only
npm run smoke:tracker tracker tracker token no — read-only, apply() is never called
npm run smoke:tracker:write tracker tracker token, plus --list/--team/--member yes — creates a real task/issue, exercises setStatus/setAssignees/addComment, then deletes what it created. Point it only at a workspace you know is disposable

npm run serve is the one command that listens rather than only calling out, and it is the only row above with an inbound network surface. It speaks plain HTTP — no TLS termination — and accepts a request from anyone who can reach the port; the two signing secrets are what stand between that and a forged delivery triggering a real write. See LIMITATIONS.md for what deploying it actually requires (a reverse proxy for TLS, process supervision, and the rest).

Everything else — demo, test, eval, lint, typecheck — runs with no network and no key. That is enforced in CI rather than asserted here: the build job has no secrets in its environment and still runs all five demo modes and the eval.

Grant the minimum scope each one needs; a tracker token that can only read is enough for npm run board or npm run smoke:tracker, and npm run smoke:tracker:write should only ever hold a token scoped to a disposable workspace, never a production one.

Reporting a vulnerability

Open a private security advisory on this repo, or email security@agentloopr.com. Include the scenario or command that reproduces it. Please don't open a public issue for something exploitable — everything else (a bug that isn't a vulnerability) is fine as a normal issue.

What's out of scope

  • The agent layer's prompts producing an unwanted category or proposal — that's a quality question, not a security one. The structural guarantee (read-only tools, every proposal re-gated, never un-holds) is what's load-bearing; see AGENTS.md.
  • Findings that require a maintainer to have wired their own live credentials insecurely — the .env.example file and every doc that touches configuration says these are placeholders.

There aren't any published security advisories