/ projects / carl

CARL

Offline SOC knowledge base that captures what lives in analysts' heads

HTML JavaScript Python FastAPI
500+ knowledge entries 8 dispatch engines 11 alert playbooks

Built into it

  • Three-phase routing engine
  • KQL-ready alert playbooks
  • Investigation primitives library
CARL

Problem

SOC tribal knowledge lives in people, not documentation. When an analyst hits a phishing alert at 2 AM, the response steps exist — but only in the heads of senior analysts who are off-shift. Onboarding runbooks go stale fast and miss the edge cases experienced analysts handle by instinct.

Approach

CARL (Contextual Alert Response Library) is a deterministic, offline knowledge base with no LLM dependency. It runs entirely in the browser on the analyst workstation, never makes an external call, and produces the same output for the same query every time. Alert-type lookups return structured playbooks with step-by-step investigation paths. The store holds 500+ entries — alert playbooks, investigation primitives, MITRE ATT&CK techniques, LOLBin databases, file-path masquerading patterns — resolved through eight dispatch engines and a keyword scoring layer, not a language model.

Here’s what the playbook surface looks like for a phishing-cred-harvest alert against a synthetic contoso.com tenant — click or arrow-key through the four steps:

// investigation · phishing → cred-harvest synthetic data · contoso.com

Six-hour window surrounding the click. Successful auths first, risk attribute carried.

SigninLogs
| where TimeGenerated between (datetime('2026-04-21T14:00:00Z') ..
                               datetime('2026-04-21T20:00:00Z'))
| where UserPrincipalName == '[email protected]'
| project TimeGenerated, IPAddress, ResultType, RiskLevelDuringSignIn
| sort by TimeGenerated desc
› Returned 12 sign-ins · 3 from new IPs

Any auto-forward / delete-on-receive rule created after the suspicious event.

AuditLogs
| where TimeGenerated > ago(6h)
| where OperationName == 'New-InboxRule'
| where TargetResources[0].userPrincipalName == '[email protected]'
| project TimeGenerated, RuleName=Result,
          Conditions=AdditionalDetails
› Returned 1 rule · auto-forward to 10.0.4.12

New or updated security-info entries on the affected account.

AuditLogs
| where TimeGenerated > ago(24h)
| where OperationName in (
    'Add a security info to user',
    'Update a security info from user')
| where TargetResources[0].userPrincipalName == '[email protected]'
| project TimeGenerated, OperationName, IPAddress
› Returned 2 events · same IP as Step 01

Sender pattern from the staging domain across the contoso.com tenant.

EmailEvents
| where Timestamp > ago(72h)
| where SenderFromAddress endswith '@phish-staging.example.com'
| where RecipientEmailAddress endswith '@contoso.com'
| summarize Count=count(),
            Recipients=dcount(RecipientEmailAddress)
› Returned 47 messages · 9 recipients

Running offline in the browser means it works behind strict network controls and nothing leaves the machine. Determinism means the same query always returns the same answer — which is the property that holds up in an audit conversation.

Outcome

Analysts work through unfamiliar alert types without waiting for a senior to come online. New-hire ramp shortens because the implicit response patterns are now explicit, searchable, and versioned in git.

What’s next

An asset-inventory CSV import pipeline is in progress. Once it lands, lookups can return environment-specific network ranges and escalation contacts instead of generic placeholders.