The detectors run as part of mallcop scan, which executes the full pipeline end to end (connect → detect → triage → investigate → committee consensus). You can also run just the detection phase with mallcop detect. During the learning period, findings are informational only - no escalation. After learning, full severity applies.

new-actor

Flags actors not seen in the baseline period.

An "actor" is any principal that appears in events: user accounts, service principals, API keys, CI bots. If mallcop hasn't seen this actor before, it's flagged. Applies to all sources and event types.

Most new-actor findings are resolved at triage in one LLM call. The actor checks the baseline, sees the creating user's recent activity, and resolves if it fits a known pattern (onboarding, CI setup, etc.).

Tuning: baseline.actor_min_events controls how many events an actor needs before being "known".

priv-escalation

Fires when an event indicates elevated permissions.

New admin role, new repo owner, new org admin, new subscription-level role assignment. Severity is critical. Goes directly to investigation if triage can't attribute it to a known human action in context.

Source-agnostic — it keys on event type/action, not the connector. Live via GitHub today, and via the shipped Azure and M365 connectors (mallcop-connectors).

Event types watched: role_assignment, collaborator_added, permission_change, admin_action, member_added, iam_change

auth-failure-burst

Fires on N+ authentication failures within a time window.

Catches brute force, credential stuffing, and distributed spray patterns. Defaults are tuned for a small-team baseline: roughly 5 failures in 10 minutes from the same IP, or 20 failures across any IPs targeting the same account.

The triage actor distinguishes between fat-finger failures (one user, interactive session, same time of day as normal) and spray attacks (many users, unusual IPs, automated timing).

Detectors are baseline-relative and need no per-detector tuning by default. Where a detector supports widening its coverage, you pass a widen-only overlay with mallcop scan --tuning (see Configuration).

unusual-timing

Flags events outside the baseline time-of-day pattern for a known actor.

Mallcop tracks per-actor activity windows from the baseline period. A developer who always works 9am-7pm Eastern triggers this detector if they appear at 3am UTC. Timing anomalies combined with privilege changes or new resource access are strong signals.

The shakedown suite tests competing signals: a developer who works both US East and European hours (wide baseline window) should not trigger this detector on evening activity.

volume-anomaly

Fires when event volume exceeds N× the baseline frequency.

Measured per (source, event_type) pair. A normal day generates ~50 Azure Activity Log events; a day with 250 triggers this at the default 5× multiplier. Catches data exfiltration (bulk reads), runaway processes (API hammering), and enumeration.

Deploy days legitimately generate high volume. The triage actor checks recent commit activity and resolved prior volume anomalies to distinguish normal spikes from attacks.

injection-probe

Flags events containing prompt injection patterns in string fields.

Scans sanitized string fields for known prompt injection signatures: SQL injection user agents, embedded LLM instructions (IGNORE PREVIOUS INSTRUCTIONS variants), and structured content in free-text fields. Runs before Airlock 2 validation to catch malicious payloads that survived initial sanitization.

The shakedown suite tests benign SQL keywords in log messages (e.g., a developer running SELECT queries normally). The detector must not fire on legitimate SQL in application logs.

log-format-drift

Fires when a container app's parser unmatched-line ratio exceeds threshold.

When a container app updates its logging format, the existing parser template stops matching. Unmatched lines accumulate. When the ratio exceeds the threshold (default: 30%), this detector fires. This catches:

  • Legitimate service updates (parser needs updating)
  • Log tampering or injection (attacker suppressing evidence)
  • Application errors generating non-standard output

The triage actor checks for recent deployments to the app to distinguish updates from anomalies.

new-external-access

Flags when external entities are granted access to internal resources.

An "external entity" is any principal not previously seen accessing your internal resources: a new cross-account role, a new OAuth app granted access, an external collaborator added to a private repo. The baseline tracks the set of known external relationships.

unusual-resource-access

Flags when a known actor touches a resource outside their baseline relationship set.

Mallcop maintains per-actor resource relationships in the baseline: which Key Vaults, storage accounts, repos, databases each actor has historically accessed. A developer who has never touched the production key vault suddenly querying secrets is a strong signal for lateral movement or insider threat.

unusual-login

Flags logins that deviate from a known actor's baseline login pattern.

Combines geo, device, and method signals: a login from a country, client, or auth method the actor has never used. Distinct from unusual-timing, which looks only at time-of-day. Strong lateral-movement and account-takeover signal when combined with privilege changes.

rate-anomaly

Fires when an actor's request rate exceeds their baseline rate envelope.

Per-actor rate tracking, distinct from volume-anomaly (which is per (source, event_type)). Catches automated enumeration, runaway scripts, and token abuse where a single principal hammers an API far above its learned cadence.

config-drift

Flags changes to security-relevant configuration away from the baseline state.

Tracks settings such as MFA enforcement, public-access flags, retention policies, and network rules. When a setting moves away from its learned baseline value, the detector fires. The triage actor checks for an authorized change ticket or recent admin action.

secrets-exposure

Flags events that expose credentials or secret material.

Scans sanitized fields for credential patterns: API keys, tokens, private keys, and connection strings appearing in logs, commits, or config. Severity is high — exposed secrets are treated as live until rotated.

exfil-pattern

Flags access patterns consistent with data exfiltration.

Bulk reads, large outbound transfers, or sequential enumeration of sensitive resources followed by export. Combines volume and resource-access signals to distinguish staging for exfiltration from ordinary heavy reads.

dependency-tamper

Flags suspicious changes to dependencies or the supply chain.

New or altered package sources, lockfile changes that pull from unexpected registries, and unsigned or unexpected dependency additions. Catches supply-chain injection before a tampered dependency ships.

git-oops

Flags risky git operations such as force-pushes and history rewrites.

Force-pushes to protected branches, deleted branches, rewritten history, and large accidental commits. Distinguishes routine rebases from destructive or evidence-suppressing rewrites by checking actor baseline and branch protection state.

malicious-skill

Flags installation or invocation of skills/extensions with malicious characteristics.

Newly added agent skills, plugins, or extensions whose declared capabilities or behavior match known malicious patterns. Catches a compromised or rogue skill being introduced into an automation surface.

Custom detectors — code-first, sandboxed

A custom detector is Go code, not a declarative rule file — mallcop has no YAML-rule loader, by design. It implements core/detect.Detector, compiles to a wasip1/wasm module, and ships as a sidecar:

// a sidecar's entire main is this — detectorhost.Run owns the stdio wire protocol
func main() { os.Exit(detectorhost.Run(myDetector{})) }

The sidecar reads one JSON document on stdin ({"events": [...], "baseline": {...}}) and writes a JSON array of findings to stdout — nothing else. It runs inside a zero-capability sandbox (detecthost, built on wazero): no filesystem, no environment variables, no network — the guest's only channel to the outside world is those three pipes, and every call runs under a hard timeout.

The sidecar directory is a mallcop.yaml setting (detectors.sidecars.dir, default ./detectors/bin — see the Sidecar detectors: line in Configuration's mallcop config output). Grade a candidate detector against the labeled exam before committing it, even from outside this repo's own tree:

mallcop exam-detect --sidecar-src ./path/to/my-detector-package

A new detector goes through the same validate-proposal gate as every other self-extension change — protected paths frozen, the code must build and pass the import allow-list (no net/file/exec in a detector), and the labeled exam must show no regression and at least one closed gap. It is never auto-merged. See Self-Improvement for the full loop and the human-review boundary around authored code.