Skip to content

brittonhayes/vala

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

41 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

vala

Go Reference Build Status License

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.

Install

go install github.com/brittonhayes/vala/cmd/vala@latest
vala            # first run opens guided setup automatically

The 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/vala

Quickstart

Fire up the session and start talking:

vala

That'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.

Choose your provider

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.

Give it a brain

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.json by 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).

Give it context

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.

Modes & skills

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.

The hunt loop

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).

Detections

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: false

The 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.

Configuration

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 sources

The 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": true server (e.g. Wiz) signs in through the browser on first use and caches its token under ~/.config/vala/mcp-auth.json (mode 0600), refreshing it silently on later launches;
  • a stdio server reads the variables named in env and 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.

Development

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.

License

MIT © Britton Hayes

About

A threat hunter that lives in your terminal with memories in Notion.

Topics

Resources

License

Stars

2 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages