A threat hunter that lives in your terminal. One binary, one loop: scope a hypothesis, hunt it down, reach a verdict, ship the detection.
vala security detection & response · claude-opus-4-8
type a request · /help for commands · "exit" to quit
────────────────────────────────────────────────────
› a CISA advisory says GuardDuty is being disabled — hunt the last 24h
› confirmed. author and link a Sigma detection for DeleteDetector
Point it at your data lake, describe the work, and walk away. Every hunt, every finding, every detection lands in a Notion-backed brain so the next hunt builds on the last. It is provider-agnostic — run it on Anthropic's Claude, OpenAI, Google Gemini, OpenRouter, or a local model — and needs no external detection toolchain: Sigma rules are validated and unit-tested offline, inside the binary.
go install github.com/brittonhayes/vala/cmd/vala@latest
vala # first run opens guided setup automaticallyThe first time you run vala and it isn't fully wired up, it opens a guided
setup that walks you through the three things it needs — a model provider, a
brain (where findings persist), and the evidence sources it hunts in
(Scanner, Wiz, or any MCP server). There's nothing separate to find; vala knows
when you're not set up and helps you finish. Run vala setup anytime to add a
source or change a choice.
For Anthropic you can log in with your Claude Pro/Max subscription — vala
opens your browser, you paste back the one-time code, and no raw API key is ever
entered or stored. Prefer a key? Paste one instead, or skip setup entirely: a key
already in your environment (e.g. ANTHROPIC_API_KEY, OPENAI_API_KEY) is
picked up automatically. To set up just the provider, vala connect jumps
straight to the provider picker.
Build from source
git clone https://github.com/brittonhayes/vala && cd vala
go build -o vala ./cmd/valaFire up the session and start talking:
valaThat's the whole surface — one interactive session. A few slash commands steer the conversation itself:
| Command | What it does |
|---|---|
/help |
List the commands. |
/connect [provider] |
Choose or switch the model provider mid-session; lists providers when bare. |
/mode [id] |
List modes, or switch the active specialization live; lists modes when bare. |
/clear |
Wipe the context and transcript, keep the banner. |
/compact [focus] |
Summarize the session into a tight recap and keep going; focus steers it. |
Type / and the commands appear inline — a fuzzy autocomplete that filters by
name and description as you type, ↑/↓ to pick, Tab to fill in, Enter to
run. No need to memorize them.
Need a one-shot for CI or a cron? Same toolbox, no TTY:
vala run "validate and test every rule in my detections directory, report failures"Tip
Common flags: --model <id>, --permission ask|auto, --mode hunt|detect, --yes.
vala also auto-compacts as a turn approaches the context window (80% by
default) so long sessions never run out of room — tune it with
context_window / auto_compact_threshold, or set either to 0 to turn it off.
vala talks to whatever model backend you point it at. One binary, any provider:
vala connect # guided picker (masked key entry)
vala connect openai # jump straight to a provider
vala connect ollama # point at a local server — no key needed| Provider | Models | Auth |
|---|---|---|
anthropic |
Claude (Opus, Sonnet, Haiku) | ANTHROPIC_API_KEY |
openai |
GPT-5, GPT-4.1, o4-mini | OPENAI_API_KEY |
google |
Gemini 2.5 Pro/Flash | GEMINI_API_KEY |
openrouter |
any model, one key | OPENROUTER_API_KEY |
groq · deepseek · xai |
Llama, DeepSeek, Grok | provider key |
ollama · lmstudio |
local models | none (local server) |
Under the hood there are just two wire protocols — Anthropic Messages and OpenAI
Chat Completions — so every OpenAI-compatible endpoint (including local servers
and private gateways) works by pointing at a base URL. Switch providers live in
a session with /connect; the conversation carries over. Define a custom
OpenAI-compatible provider under providers in .vala.json:
{
"provider": "mygateway",
"model": "my-model",
"providers": {
"mygateway": { "base_url": "https://gateway.internal/v1", "api_key_env": "GATEWAY_KEY" }
}
}Credentials live in ~/.config/vala/auth.json (mode 0600), never in the
project config; environment variables always take precedence.
By default vala runs in ephemeral, in-memory mode — fine for a quick look,
forgotten the moment you quit. Give it a persistent brain and your hunts, intel,
evidence, and detections compound across sessions. Just run vala — on first
launch it detects what isn't set up and opens a guided setup where you pick a
brain (re-run it any time with vala setup):
vala # first launch opens setup automatically
vala setup # re-run to add, change, or repair a piece- On-disk brain — durable, no account. Writes the brain to a single JSON file
(
.vala/brain.jsonby default) and records the path in.vala.json; narrative hunt pages land as readable Markdown beside it. The zero-dependency path — nothing to log into, and the whole brain is a portable, version-controllable artifact. - Notion brain — shared with your team. Provisions a single Vala Brain
database with one data source per store (hunts, evidence, intel, detections,
backlog, memory, coverage) under a Notion page you choose, and writes the
data-source IDs into
.vala.json. If a store ever goes missing, re-running setup repairs it in place rather than leaving the brain half-broken.
Note
The Notion path needs the Notion CLI;
setup runs ntn login for you when you aren't authenticated yet. The on-disk
path needs nothing. Until a brain is configured, vala reminds you on startup
that it's in memory-only mode (silence it with --no-init-prompt).
vala opens every session with standing context about your environment, from two places:
VALA.md is what you write by hand — a plain Markdown file vala reads into
every session: crown-jewel systems, where each log source lives, what "normal"
looks like, detection naming conventions, prior incidents. Setup drops a
commented starter in your project; fill in what matters.
your-repo/VALA.md ← project context, version-controlled with the team
~/.config/vala/VALA.md ← personal context, merged in first
Shared memory is what vala learns as it hunts. When the agent discovers a
durable fact — "auth logs live in Okta", "svc-deploy rotates keys nightly" — it
calls remember, which writes that fact to the brain stamped with who learned
it. Point a whole team at the same Notion brain and memory becomes multiplayer:
one hunter's discovery primes everyone's next session. With a local brain it's
still yours across sessions; ephemeral mode forgets it on exit.
Unlike query results and file contents — which vala treats as untrusted data — both VALA.md and team memory are operator-authored, so vala trusts them as guidance.
vala runs in a mode — a specialization that swaps the system prompt, the tools the agent sees, and a set of bundled skills. Think of it as the same agent and toolbox, focused for the job at hand:
| Mode | What it's for |
|---|---|
hunt (default) |
The full eight-stage hunt loop below: hypothesis → evidence → verdict → detection. |
detect |
Detection Engineering — author, refine, and test Sigma rules directly, without running a full hunt. Drops the hunt-lifecycle tools, centers the detection toolkit, and bundles the sigma-authoring skill. |
Pick a mode at startup (--mode detect, VALA_MODE=detect, or "mode": "detect"
in config) or switch live with /mode detect; the conversation carries over.
Skills are Claude-Code-style capability packs — a SKILL.md (frontmatter +
markdown) that a mode bundles. The system prompt lists each active skill by name
and one-line description; the agent loads the full body on demand with the
skill tool (progressive disclosure, so long playbooks cost tokens only when
needed). Drop your own in .vala/skills/<name>/SKILL.md (project) or
~/.config/vala/skills/ (user); a project skill overrides a built-in of the same
name. See docs/SPEC-0014.
vala runs a single eight-stage loop, shaped after the frameworks every hunt team
knows — Sqrrl's Hunting Loop, Splunk PEAK, TaHiTI. The loop and its rationale are
specified in docs/SPEC-0001; the
full specification set — the grounding truth for what vala offers — lives in
docs/.
1 · Scope & prioritize. Choose what to hunt — weighting detection coverage
gaps first, then active threat intel, then what matters in this environment.
recall (including the coverage scope) reads the brain back so settled ground
isn't re-hunted; queue_hunt parks triggers on a prioritized backlog.
2 · Form hypothesis. State it with ABLE — the testable adversary
Behavior and the data-source Location where it'd show up — and pick a
hunt type: hypothesis-driven, baseline, or model-assisted. open_hunt opens it.
3 · Plan & validate data. validate_data confirms the telemetry exists
before you query. A failed check is recorded as a visibility gap — never a
silent skip — and is an actionable outcome in its own right.
4–5 · Execute & deep-dive. Investigate over read-only data sources, spoken via the Model Context Protocol. Point it at a Scanner data lake and it discovers the indexes, queries them, baselines normal, and records each fact as an immutable Finding (and each indicator, TTP, or actor as a first-class artifact).
6 · Document & decide. store_hunt writes the narrative page with a verdict
— Confirmed, Refuted, or Inconclusive — and a detection-output tier
decision. Every declarative finding must cite a Finding ID, or the page bounces.
7 · Convert to detection. vala picks the highest-fidelity output the finding supports: a Sigma rule (tiers 1–2), a recurring hunt (tier 3), a playbook (tier 4), or a justified no-build (tier 5). A low-value rule is worse than none.
8 · Feed back. update_coverage records the technique's coverage state so
the next hunt aims where coverage is weakest, and follow-on hypotheses are
queued. The autonomy at each stage scales with the Hunting Maturity Model level
(docs/SPEC-0013).
A confirmed hunt's output is a Sigma rule — vendor-neutral
detection-as-code that compiles to most SIEM backends. vala writes it to your
detections_dir (default detections) and leaves deployment to your pipeline.
It ships none of its own.
Two optional, schema-valid fields make each rule stand on its own:
runbook:— inline response guidance (triage,investigate,contain,escalate,references).tests:—{name, event, match}cases vala runs through its offline engine to prove the rule's logic.
detection:
selection:
eventName: ConsoleLogin
userIdentity.type: Root
condition: selection
tests:
- name: root console login fires
event: { eventName: ConsoleLogin, userIdentity.type: Root }
match: true
- name: iam user login is ignored
event: { eventName: ConsoleLogin, userIdentity.type: IAMUser }
match: falseThe offline engine in internal/detect checks rules against
the official Sigma schema and supports the common modifiers (contains,
startswith, endswith, all, re, cidr, lt|lte|gt|gte), */?
wildcards, dotted field lookups, and 1 of / all of quantifiers. The
reference rules under
internal/reference/sigma/ are complete examples.
Settings layer lowest-priority first: built-in defaults →
~/.config/vala/config.json → ./.vala.json → environment variables
(VALA_PROVIDER, VALA_MODEL, VALA_PERMISSION, VALA_CONTEXT_WINDOW,
VALA_AUTO_COMPACT_THRESHOLD, SCANNER_MCP_URL, SCANNER_API_KEY, and each
provider's own key env such as ANTHROPIC_API_KEY / OPENAI_API_KEY).
{
"provider": "anthropic",
"model": "claude-opus-4-8",
"mode": "hunt",
"permission": "ask",
"detections_dir": "detections",
"context_window": 200000,
"auto_compact_threshold": 0.8,
"mcp": [
{ "name": "notion", "url": "https://mcp.notion.com/mcp", "oauth": true },
{ "name": "scanner", "url": "https://<your>.scanner.dev/mcp", "api_key_env": "SCANNER_API_KEY" },
{ "name": "wiz", "url": "https://mcp.app.wiz.io/", "oauth": true }
],
"brain_file": "",
"notion": {
"evidence": "", "hunts": "", "intel": "", "detections": "", "backlog": "", "memory": ""
}
}Notion search and evidence sources. The reserved MCP server named notion
belongs to the brain: it powers recall with Notion MCP search and is not
exposed as a hunting evidence tool. Guided setup configures it as part of the
Notion brain flow, defaulting to Notion's hosted production MCP URL while letting
you provide your own Notion MCP-compatible URL.
Every other MCP server under mcp is evidence — this is what vala actually
hunts in. The fastest way to connect one is the guided setup,
which runs automatically the first time vala detects it isn't fully wired up, and
on demand any time:
vala setup # connect a provider, a brain, and evidence sourcesThe wizard ships curated presets for Scanner (a hosted
data lake over HTTPS with an API key) and Wiz (the Security
Graph, which you sign into in your browser), plus a custom-HTTP option for any MCP
server. It validates each connection live — you see ✓ N tools before you leave
— and writes the entry to .vala.json. You can also hand-edit the mcp array:
each server is connected at startup over its transport (http, the default, or
stdio), its tools discovered, and the read-only ones handed to the agent.
Secrets are never persisted to .vala.json:
- a bearer-token server reads its key from
api_key_env; - an
"oauth": trueserver (e.g. Wiz) signs in through the browser on first use and caches its token under~/.config/vala/mcp-auth.json(mode0600), refreshing it silently on later launches; - a
stdioserver reads the variables named inenvand passes them to the subprocess.
As a shortcut, SCANNER_MCP_URL (plus SCANNER_API_KEY) registers Scanner with
no config file. The session banner shows which sources connected; with none, vala
reasons only over local files.
Notion IDs. The notion values are the parent database plus one
data-source ID per store — vala setup fills them in for you. To wire them by
hand, resolve an ID with
ntn datasources resolve <id> and match each property name to the keys vala
writes (hunt_id, status, started_at, relations like hunts/detections).
Leave them empty to stay local.
Warning
--permission controls interactivity: ask (default) keeps you hands-on and
prompts before strategic writes; auto is hands-off and lets vala add backlog
items, hunts, intel, coverage, and links as it works. Reach for auto only
when you trust the run.
go build ./...
go vet ./...
go test ./...CI runs build, vet, go test -race, and a gofmt check on every push and pull
request. The architecture follows
charmbracelet/crush — one Tool type
plus one embedded .md description per tool, a permission gate, sessions. The
registry in internal/tools/toolbox.go is the
single extension point.
MIT © Britton Hayes