Most security tools send you alerts. Mallcop sends you answers.
Here's what happens between mallcop scan and "all clear."
Mallcop generated 15 findings this week. 12 were resolved by AI triage. Routine activity by known actors doing known things. 2 were investigated deeper and resolved with cited evidence. 1 was escalated to you with a full investigation report written to the git store.
Every security tool finds things. What you actually want is to not spend your afternoon chasing them. The AI runs each one down, cites its evidence, and either closes the case or hands you a briefing you can act on in under a minute.
No AI involved at this stage. Just rules compared against what's normal.
Is someone doing something they shouldn't?
Did your team leave a door open?
Is something messing with mallcop itself, or showing up outside your walls?
One command runs the whole thing. mallcop scan connects, detects, triages,
investigates, and runs committee consensus end to end, then writes findings and
resolutions to your git store. Detection is rule-based and free; the AI cascade runs only
on what the detectors flag. mallcop init sets up config first; mallcop detect
is available to run the detection phase on its own.
Someone new shows up in your GitHub org.
The new-actor detector fires because this principal isn't in the baseline.
Rule-based comparison, no LLM, no cost. Finding generated with severity warn.
The triage agent runs the structured triage playbook. The finding, triggering events, and baseline data are pre-loaded into context. The agent enters a reasoning loop with a bounded iteration budget:
# Triage agent reasoning loop 1. Calls check-baseline → actor not known, 0 prior events 2. Calls search-events → finds org invite sent by repo owner 3 hours ago 3. Applies the 4-question test: A. Routine? No — first appearance. B. Legitimate trigger? Yes — org invite by admin at 14:22 UTC. C. Credential theft? No — invite chain is intact, timing matches. D. Access expansion? No — read-only collaborator. 4. Resolves with confidence 4, citing invite event ID and timestamp. Result: resolved — "New collaborator added via org invite (event evt-2026-03-11-0042) by admin@org 3h prior. Read-only access."
Same finding, but no invite found. Activity at 3am. Unusual IP range. Triage confidence is 2. A hard runtime rule says confidence ≤ 2 means escalate. The agent doesn't guess. It says "I'm not sure" and passes to investigation.
The investigation agent has more tools (search-findings, baseline-stats,
annotate-finding) and a larger iteration budget than triage. It digs into the
full event history before deciding.
# Investigation agent deep dive 1. Calls search-events → no invite, no PR, no onboarding workflow 2. Calls check-baseline → actor IP not in any known range 3. Calls search-findings → no prior findings for this actor 4. Applies credential theft test: "If credentials were stolen, would this look identical?" → Cannot distinguish. 5. Annotates finding with full reasoning chain 6. Escalates with confidence 2, citing missing provenance Result: escalated — "No upstream trigger found. 3am activity, unknown IP range, no org invite or PR. Recommend: verify identity through independent channel."
Findings and resolutions are written to the git-native store you control. One auditable history of every event, finding, and ruling. Escalated findings carry their full investigation notes, so you read reasoning, not just an alert. Wire your own notifier on top of the git store if you want a push to chat or email.
When you rule on a finding (mallcop feedback <id> dismiss --store ./store,
or "Silence this" from the hosted mallcop.app chat), that ruling is written back
to the store as a suppress directive. The next mallcop scan reads it and stops
re-flagging that class of finding. Same loop, whether you run it from the CLI or the browser.
Notice the AI never saw the raw GitHub API response. Events are sanitized at ingest:
control characters stripped, strings length-capped. User-controlled fields (commit messages,
branch names, PR titles) are wrapped in [USER_DATA_BEGIN]...[USER_DATA_END] markers.
Tool results are re-sanitized before reaching the LLM.
If someone put "IGNORE PREVIOUS INSTRUCTIONS" in a commit message, it arrives wrapped in markers. The AI sees it as data to analyze, not instructions to follow.
Agents interact with your data through a controlled set of tools. Each tool has explicit permissions. Triage is read-only by design.
| Tool | What it tells the agent | Available to | Permission |
|---|---|---|---|
| check-baseline | Is this actor known? Frequency, relationships, typical hours | triage, investigate | read |
| read-events | Events for this finding, enriched with local time context | triage, investigate | read |
| search-events | Full-text search across event history — find upstream triggers | triage, investigate | read |
| search-findings | Historical findings — has this pattern appeared before? | investigate | read |
| baseline-stats | Statistical summary of baseline for an actor | investigate | read |
| annotate-finding | Document reasoning before resolving | investigate | write |
| resolve-finding | Mark finding resolved/escalated with confidence score | triage, investigate | write* |
* Triage's resolve-finding is constrained by policy: it cannot resolve privilege escalation or access boundary findings regardless of model output. Enforced at runtime, not by prompting.
Beyond the scan, mallcop ships a tool-calling analyst you can question in plain English.
mallcop investigate reads your events, findings, baseline, and rules to answer
things like "who was added to the org this week?" or "explain this finding," and it cites the
evidence behind every answer. On the managed rail the same analyst runs from a web chat console,
inside your own GitHub Actions, so your data and inference key never leave your repo.
Triage is not a vibe check. It's a structured protocol with hard constraints the AI can't override. These are real excerpts from the playbooks that run in production.
From the triage agent's playbook:
## Step 3: Analyze Answer these questions using the data from steps 1-2: A. Is this action routine for this actor? "[Actor] has done [action] [N] times. This is [routine/new]." B. Is there a legitimate trigger? "Events show [trigger/no trigger]: [detail]." C. Could a stolen credential produce this exact pattern? "[Yes/No] because [specific factor — IP/location, timing, user-agent]." D. Does this expand access or privileges? "[Yes/No]." ## Step 4: Decide - If A=routine AND B=trigger AND C=distinguishable AND D=no → RESOLVE - Privilege changes → always ESCALATE (non-negotiable) - Log format drift → always ESCALATE - Resolution requires positive evidence — "actor is known" alone is not enough - Otherwise → ESCALATE
From the investigation agent's playbook:
## Pre-Resolution Checklist Before calling resolve-finding — whether resolving OR escalating — run these 5 checks. They apply in both directions. 1. EVIDENCE — Am I citing specific fields, timestamps, or baseline entries? If I can't point to it, I'm guessing. This applies to escalations too: cite what's anomalous, not just "it looks wrong." 2. ADVERSARY — Could an attacker produce this exact pattern? What would distinguish legitimate from compromised? Automation names, user-agent strings, and correlation IDs can all be spoofed. 3. DISCONFIRM — What evidence would contradict my conclusion? Did I check for it, or just not look? If resolving, did I check for anomalous signals I might be overlooking? If escalating, did I check whether the baseline explains the activity? 4. BOUNDARY — Does this action expand who or what has access to the environment? If yes, treat as privilege-level. 5. BLAST RADIUS — If I'm wrong, what's the worst case? A false escalation wastes analyst time. A missed breach loses the org.
The investigation agent works from structured domain reasoning baked into its playbook. Here's the kind of analysis it applies.
These are three distinct events that often get conflated. A grant is when an actor receives a permission (role attachment, policy change, group membership). A use is when the actor exercises that permission. Escalation is when the result exceeds what was explicitly intended — an actor converts a limited permission into broader access, often by chaining grants across multiple resources or principals. Seeing a grant event in isolation tells you almost nothing. The question is whether the use that followed was proportionate to the grant, and whether the grant itself was within the actor's pre-existing authority. Key check: did the actor who issued the grant have the authority to do so? An actor who can grant a permission they do not themselves hold is a classic privilege escalation pattern.
The "known actor" trap: GitHub admins are known actors by definition —
they appear frequently in the audit log. The credential theft test must
focus on behavioral deviation (new action types, new targets, new timing),
not on whether the actor is known.
This domain reasoning is built into the cascade. There is no plugin to install or configure. The same playbooks run on every scan, and any user-controlled text the agent reads is sanitized at the airlock first (see the security model).
The headline differentiator: mallcop doesn't trust a single AI verdict to close a case.
| Layer | What it is | AI involved? |
|---|---|---|
| Hard constraints | Code rules — priv-escalation, log drift, injection always escalate | No |
| Learned rules | Your confirmed rulings become deterministic rules the next scan reads | No |
| AI triage | Quick investigation, resolve the obvious by the 4-question test | Yes |
| AI investigation | Deeper dig with more tools and iterations when triage can't be sure | Yes |
| Committee consensus | Independent re-votes on anything it would resolve benign | Yes |
| Escalated to human | Any single escalate vote wins — the finding comes to you | — |
When the cascade would resolve a finding as benign, mallcop doesn't just accept the answer. It runs independent re-votes on that finding. If any one of them votes to escalate, the finding goes to a human. Closing a case takes unanimity, so a single dissent is enough to keep it open. This is the mechanism that catches the cases a single AI pass would get wrong.
mallcop scan has run against a real GitHub org, pulled real events, and flagged a
new actor (collaborator_added). The consensus cascade resolved it benign with cited
provenance: "established relationship via onboarding contributions; no role grants."
A real connector, real events, and the reasoning behind the verdict, all written to the git store.
The consensus cascade decides what to do with the findings it has. A second loop safely grows what mallcop can find in the first place.
Every scan that force-escalates an action it couldn't map, or misses something it should have
caught, becomes feedstock. Mallcop mines it (mallcop collect), proposes a
narrowly-scoped fix, and puts that fix through a $0, no-inference gate
(mallcop validate-proposal) before it lands. Safe config widens apply
automatically; a brand-new authored detector only ever lands after a human reviews it. The gate
freezes the committee itself. The loop can widen what mallcop sees, but it can never edit
how mallcop votes.
Mallcop fixes its own gaps two ways, and both leave proof behind:
A real example, for free. Mallcop grades its own detection against a set of real scenarios, and
one kept slipping past: a genuine AWS privilege grant (PowerUserAccess) the rules
missed, because the name has no obvious word like "admin." One added rule fixes it, and mallcop
checks on its own that the near-identical but harmless read-only version still doesn't cry wolf.
$ mallcop exam-detect # grade detection against real scenarios FAIL PE-08-aws-poweruser-grant must_fire: priv-escalation emitted: (none) # the blind spot: missed it exam-detect: 17 labeled (13 passed, 4 failed), 41 unlabeled (skipped) $ mallcop exam-detect --tuning detectors/tuning.yaml # after one added rule PASS PE-08-aws-poweruser-grant must_fire: priv-escalation emitted: priv-escalation # now caught exam-detect: 17 labeled (14 passed, 3 failed), 41 unlabeled (skipped)
This is not a diagram. Mallcop recently wrote a brand-new detector end to end: opencode (GLM-5.2) authored it in a sandbox, it passed every check (it caught one more real case and broke nothing), and a human opened it as pull request #157. Never merged on its own.
File and GitHub are built into the core binary. Six cloud sources ship today as standalone
binaries in the mallcop-connectors
repo. They emit event JSONL you pipe into mallcop scan --events. The list below is
a starting point, not a fixed catalog: a connector is AI-authored code, so a source that isn't
here yet is a request away, not a roadmap item you wait on.
--connector github --github-org <org>--events <file.jsonl>Shipped standalone binaries. Each emits event JSONL for mallcop scan --events.
Not yet ported. Planned standalone connectors.
Bring your own inference key and mallcop costs you $0. You pay only your model provider. Or skip key management: managed inference on the donut rail is live today, pay-as-you-go or by monthly subscription.
| BYOK — live | Managed — live | |
|---|---|---|
| Price | $0 + your API costs | Donut packs from $5, or $4.99/mo+ |
| Models | Your choice (any API key) | Optimized routing by sovereignty tier |
| Key management | You manage API keys | We handle it |
| Connectors | File + GitHub built in; AWS, Azure, GCP, M365, Okta via mallcop-connectors | Same |
| Events | Unlimited | Unlimited |
| Status | Available now | Checkout + metering live now (Polar) |