{"id":51119717,"url":"https://github.com/brittonhayes/vala","last_synced_at":"2026-06-25T01:00:48.442Z","repository":{"id":362157573,"uuid":"1257642809","full_name":"brittonhayes/vala","owner":"brittonhayes","description":"A threat hunter that lives in your terminal with memories in Notion.","archived":false,"fork":false,"pushed_at":"2026-06-11T02:26:34.000Z","size":458,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-11T04:13:16.356Z","etag":null,"topics":["anthropic","claude","detection-as-code","detection-engineering","golang","incident-response","llm-agent","security","siem","sigma"],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/brittonhayes.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-02T21:52:23.000Z","updated_at":"2026-06-11T02:26:38.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/brittonhayes/vala","commit_stats":null,"previous_names":["brittonhayes/harness"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/brittonhayes/vala","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brittonhayes%2Fvala","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brittonhayes%2Fvala/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brittonhayes%2Fvala/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brittonhayes%2Fvala/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/brittonhayes","download_url":"https://codeload.github.com/brittonhayes/vala/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brittonhayes%2Fvala/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34755063,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-24T02:00:07.484Z","response_time":106,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["anthropic","claude","detection-as-code","detection-engineering","golang","incident-response","llm-agent","security","siem","sigma"],"created_at":"2026-06-25T01:00:47.601Z","updated_at":"2026-06-25T01:00:48.433Z","avatar_url":"https://github.com/brittonhayes.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ch1 align=\"center\"\u003evala\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://pkg.go.dev/github.com/brittonhayes/vala\"\u003e\u003cimg src=\"https://pkg.go.dev/badge/github.com/brittonhayes/vala.svg\" alt=\"Go Reference\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/brittonhayes/vala/actions/workflows/ci.yml\"\u003e\u003cimg src=\"https://github.com/brittonhayes/vala/actions/workflows/ci.yml/badge.svg\" alt=\"Build Status\"\u003e\u003c/a\u003e\n  \u003ca href=\"./LICENSE\"\u003e\u003cimg src=\"https://img.shields.io/badge/license-MIT-green.svg\" alt=\"License\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003eA threat hunter that lives in your terminal. One binary, one loop: scope a hypothesis, hunt it down, reach a verdict, ship the detection.\u003c/p\u003e\n\n```\n  vala  security detection \u0026 response · claude-opus-4-8\n  type a request · /help for commands · \"exit\" to quit\n  ────────────────────────────────────────────────────\n  › a CISA advisory says GuardDuty is being disabled — hunt the last 24h\n  › confirmed. author and link a Sigma detection for DeleteDetector\n```\n\nPoint it at your data lake, describe the work, and walk away. Every hunt, every\nfinding, every detection lands in a Notion-backed brain so the next hunt builds\non the last. It is **provider-agnostic** — run it on Anthropic's Claude, OpenAI,\nGoogle Gemini, OpenRouter, or a local model — and needs **no external detection\ntoolchain**: Sigma rules are validated and unit-tested offline, inside the binary.\n\n## Install\n\n```sh\ngo install github.com/brittonhayes/vala/cmd/vala@latest\nvala            # first run opens guided setup automatically\n```\n\nThe first time you run `vala` and it isn't fully wired up, it opens a guided\nsetup that walks you through the three things it needs — a **model provider**, a\n**brain** (where findings persist), and the **evidence sources** it hunts in\n(Scanner, Wiz, or any MCP server). There's nothing separate to find; vala knows\nwhen you're not set up and helps you finish. Run `vala setup` anytime to add a\nsource or change a choice.\n\nFor Anthropic you can **log in with your Claude Pro/Max subscription** — vala\nopens your browser, you paste back the one-time code, and no raw API key is ever\nentered or stored. Prefer a key? Paste one instead, or skip setup entirely: a key\nalready in your environment (e.g. `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`) is\npicked up automatically. To set up just the provider, `vala connect` jumps\nstraight to the provider picker.\n\n\u003cdetails\u003e\n\u003csummary\u003eBuild from source\u003c/summary\u003e\n\n```sh\ngit clone https://github.com/brittonhayes/vala \u0026\u0026 cd vala\ngo build -o vala ./cmd/vala\n```\n\n\u003c/details\u003e\n\n## Quickstart\n\nFire up the session and start talking:\n\n```sh\nvala\n```\n\nThat's the whole surface — one interactive session. A few slash commands steer\nthe conversation itself:\n\n| Command | What it does |\n| --- | --- |\n| `/help` | List the commands. |\n| `/connect [provider]` | Choose or switch the model provider mid-session; lists providers when bare. |\n| `/mode [id]` | List modes, or switch the active specialization live; lists modes when bare. |\n| `/clear` | Wipe the context and transcript, keep the banner. |\n| `/compact [focus]` | Summarize the session into a tight recap and keep going; `focus` steers it. |\n\nType `/` and the commands appear inline — a fuzzy autocomplete that filters by\nname and description as you type, `↑`/`↓` to pick, `Tab` to fill in, `Enter` to\nrun. No need to memorize them.\n\nNeed a one-shot for CI or a cron? Same toolbox, no TTY:\n\n```sh\nvala run \"validate and test every rule in my detections directory, report failures\"\n```\n\n\u003e [!TIP]\n\u003e Common flags: `--model \u003cid\u003e`, `--permission ask|auto`, `--mode hunt|detect`, `--yes`.\n\u003e vala also auto-compacts as a turn approaches the context window (80% by\n\u003e default) so long sessions never run out of room — tune it with\n\u003e `context_window` / `auto_compact_threshold`, or set either to `0` to turn it off.\n\n## Choose your provider\n\nvala talks to whatever model backend you point it at. One binary, any provider:\n\n```sh\nvala connect              # guided picker (masked key entry)\nvala connect openai       # jump straight to a provider\nvala connect ollama       # point at a local server — no key needed\n```\n\n| Provider | Models | Auth |\n| --- | --- | --- |\n| `anthropic` | Claude (Opus, Sonnet, Haiku) | `ANTHROPIC_API_KEY` |\n| `openai` | GPT-5, GPT-4.1, o4-mini | `OPENAI_API_KEY` |\n| `google` | Gemini 2.5 Pro/Flash | `GEMINI_API_KEY` |\n| `openrouter` | any model, one key | `OPENROUTER_API_KEY` |\n| `groq` · `deepseek` · `xai` | Llama, DeepSeek, Grok | provider key |\n| `ollama` · `lmstudio` | local models | none (local server) |\n\nUnder the hood there are just two wire protocols — Anthropic Messages and OpenAI\nChat Completions — so every OpenAI-compatible endpoint (including local servers\nand private gateways) works by pointing at a base URL. Switch providers live in\na session with `/connect`; the conversation carries over. Define a custom\nOpenAI-compatible provider under `providers` in `.vala.json`:\n\n```json\n{\n  \"provider\": \"mygateway\",\n  \"model\": \"my-model\",\n  \"providers\": {\n    \"mygateway\": { \"base_url\": \"https://gateway.internal/v1\", \"api_key_env\": \"GATEWAY_KEY\" }\n  }\n}\n```\n\nCredentials live in `~/.config/vala/auth.json` (mode `0600`), never in the\nproject config; environment variables always take precedence.\n\n## Give it a brain\n\nBy default vala runs in ephemeral, in-memory mode — fine for a quick look,\nforgotten the moment you quit. Give it a persistent brain and your hunts, intel,\nevidence, and detections compound across sessions. Just run vala — on first\nlaunch it detects what isn't set up and opens a guided setup where you pick a\nbrain (re-run it any time with `vala setup`):\n\n```sh\nvala            # first launch opens setup automatically\nvala setup      # re-run to add, change, or repair a piece\n```\n\n- **On-disk brain** — durable, no account. Writes the brain to a single JSON file\n  (`.vala/brain.json` by default) and records the path in `.vala.json`; narrative\n  hunt pages land as readable Markdown beside it. The zero-dependency path —\n  nothing to log into, and the whole brain is a portable, version-controllable\n  artifact.\n- **Notion brain** — shared with your team. Provisions a single **Vala Brain**\n  database with one data source per store (hunts, evidence, intel, detections,\n  backlog, memory, coverage) under a Notion page you choose, and writes the\n  data-source IDs into `.vala.json`. If a store ever goes missing, re-running\n  setup repairs it in place rather than leaving the brain half-broken.\n\n\u003e [!NOTE]\n\u003e The Notion path needs the [Notion CLI](https://github.com/makenotion/ntn);\n\u003e setup runs `ntn login` for you when you aren't authenticated yet. The on-disk\n\u003e path needs nothing. Until a brain is configured, vala reminds you on startup\n\u003e that it's in memory-only mode (silence it with `--no-init-prompt`).\n\n## Give it context\n\nvala opens every session with standing context about your environment, from two\nplaces:\n\n**`VALA.md`** is what you write by hand — a plain Markdown file vala reads into\nevery session: crown-jewel systems, where each log source lives, what \"normal\"\nlooks like, detection naming conventions, prior incidents. Setup drops a\ncommented starter in your project; fill in what matters.\n\n```\nyour-repo/VALA.md        ← project context, version-controlled with the team\n~/.config/vala/VALA.md   ← personal context, merged in first\n```\n\n**Shared memory** is what vala learns as it hunts. When the agent discovers a\ndurable fact — \"auth logs live in Okta\", \"svc-deploy rotates keys nightly\" — it\ncalls `remember`, which writes that fact to the brain stamped with who learned\nit. Point a whole team at the same Notion brain and memory becomes **multiplayer**:\none hunter's discovery primes everyone's next session. With a local brain it's\nstill yours across sessions; ephemeral mode forgets it on exit.\n\nUnlike query results and file contents — which vala treats as untrusted data —\nboth VALA.md and team memory are operator-authored, so vala trusts them as\nguidance.\n\n## Modes \u0026 skills\n\nvala runs in a **mode** — a specialization that swaps the system prompt, the\ntools the agent sees, and a set of bundled **skills**. Think of it as the same\nagent and toolbox, focused for the job at hand:\n\n| Mode | What it's for |\n| --- | --- |\n| `hunt` *(default)* | The full eight-stage hunt loop below: hypothesis → evidence → verdict → detection. |\n| `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. |\n\nPick a mode at startup (`--mode detect`, `VALA_MODE=detect`, or `\"mode\": \"detect\"`\nin config) or switch live with `/mode detect`; the conversation carries over.\n\n**Skills** are Claude-Code-style capability packs — a `SKILL.md` (frontmatter +\nmarkdown) that a mode bundles. The system prompt lists each active skill by name\nand one-line description; the agent loads the full body on demand with the\n`skill` tool (progressive disclosure, so long playbooks cost tokens only when\nneeded). Drop your own in `.vala/skills/\u003cname\u003e/SKILL.md` (project) or\n`~/.config/vala/skills/` (user); a project skill overrides a built-in of the same\nname. See [`docs/SPEC-0014`](docs/SPEC-0014-modes-and-skills.md).\n\n## The hunt loop\n\nvala runs a single eight-stage loop, shaped after the frameworks every hunt team\nknows — Sqrrl's Hunting Loop, Splunk PEAK, TaHiTI. The loop and its rationale are\nspecified in [`docs/SPEC-0001`](docs/SPEC-0001-overview-and-hunt-loop.md); the\nfull specification set — the grounding truth for what vala offers — lives in\n[`docs/`](docs/README.md).\n\n**1 · Scope \u0026 prioritize.** Choose what to hunt — weighting detection coverage\ngaps first, then active threat intel, then what matters in this environment.\n`recall` (including the `coverage` scope) reads the brain back so settled ground\nisn't re-hunted; `queue_hunt` parks triggers on a prioritized backlog.\n\n**2 · Form hypothesis.** State it with ABLE — the testable adversary\n**B**ehavior and the data-source **L**ocation where it'd show up — and pick a\nhunt type: hypothesis-driven, baseline, or model-assisted. `open_hunt` opens it.\n\n**3 · Plan \u0026 validate data.** `validate_data` confirms the telemetry exists\nbefore you query. A failed check is recorded as a visibility gap — never a\nsilent skip — and is an actionable outcome in its own right.\n\n**4–5 · Execute \u0026 deep-dive.** Investigate over read-only data sources, spoken\nvia the [Model Context Protocol](https://modelcontextprotocol.io). Point it at a\n[Scanner](https://scanner.dev) data lake and it discovers the indexes, queries\nthem, baselines normal, and records each fact as an immutable Finding (and each\nindicator, TTP, or actor as a first-class artifact).\n\n**6 · Document \u0026 decide.** `store_hunt` writes the narrative page with a verdict\n— **Confirmed**, **Refuted**, or **Inconclusive** — and a detection-output tier\ndecision. Every declarative finding must cite a Finding ID, or the page bounces.\n\n**7 · Convert to detection.** vala picks the highest-fidelity output the finding\nsupports: a Sigma rule (tiers 1–2), a recurring hunt (tier 3), a playbook (tier\n4), or a justified no-build (tier 5). A low-value rule is worse than none.\n\n**8 · Feed back.** `update_coverage` records the technique's coverage state so\nthe next hunt aims where coverage is weakest, and follow-on hypotheses are\nqueued. The autonomy at each stage scales with the Hunting Maturity Model level\n([`docs/SPEC-0013`](docs/SPEC-0013-maturity-and-autonomy.md)).\n\n## Detections\n\nA confirmed hunt's output is a [Sigma](https://sigmahq.io) rule — vendor-neutral\ndetection-as-code that compiles to most SIEM backends. vala writes it to your\n`detections_dir` (default `detections`) and leaves deployment to your pipeline.\nIt ships none of its own.\n\nTwo optional, schema-valid fields make each rule stand on its own:\n\n- **`runbook:`** — inline response guidance (`triage`, `investigate`, `contain`,\n  `escalate`, `references`).\n- **`tests:`** — `{name, event, match}` cases vala runs through its offline\n  engine to prove the rule's logic.\n\n```yaml\ndetection:\n  selection:\n    eventName: ConsoleLogin\n    userIdentity.type: Root\n  condition: selection\ntests:\n  - name: root console login fires\n    event: { eventName: ConsoleLogin, userIdentity.type: Root }\n    match: true\n  - name: iam user login is ignored\n    event: { eventName: ConsoleLogin, userIdentity.type: IAMUser }\n    match: false\n```\n\nThe offline engine in [`internal/detect`](internal/detect) checks rules against\nthe official Sigma schema and supports the common modifiers (`contains`,\n`startswith`, `endswith`, `all`, `re`, `cidr`, `lt|lte|gt|gte`), `*`/`?`\nwildcards, dotted field lookups, and `1 of` / `all of` quantifiers. The\nreference rules under\n[`internal/reference/sigma/`](internal/reference/sigma) are complete examples.\n\n## Configuration\n\nSettings layer lowest-priority first: built-in defaults →\n`~/.config/vala/config.json` → `./.vala.json` → environment variables\n(`VALA_PROVIDER`, `VALA_MODEL`, `VALA_PERMISSION`, `VALA_CONTEXT_WINDOW`,\n`VALA_AUTO_COMPACT_THRESHOLD`, `SCANNER_MCP_URL`, `SCANNER_API_KEY`, and each\nprovider's own key env such as `ANTHROPIC_API_KEY` / `OPENAI_API_KEY`).\n\n```json\n{\n  \"provider\": \"anthropic\",\n  \"model\": \"claude-opus-4-8\",\n  \"mode\": \"hunt\",\n  \"permission\": \"ask\",\n  \"detections_dir\": \"detections\",\n  \"context_window\": 200000,\n  \"auto_compact_threshold\": 0.8,\n  \"mcp\": [\n    { \"name\": \"notion\", \"url\": \"https://mcp.notion.com/mcp\", \"oauth\": true },\n    { \"name\": \"scanner\", \"url\": \"https://\u003cyour\u003e.scanner.dev/mcp\", \"api_key_env\": \"SCANNER_API_KEY\" },\n    { \"name\": \"wiz\", \"url\": \"https://mcp.app.wiz.io/\", \"oauth\": true }\n  ],\n  \"brain_file\": \"\",\n  \"notion\": {\n    \"evidence\": \"\", \"hunts\": \"\", \"intel\": \"\", \"detections\": \"\", \"backlog\": \"\", \"memory\": \"\"\n  }\n}\n```\n\n**Notion search and evidence sources.** The reserved MCP server named `notion`\nbelongs to the brain: it powers `recall` with Notion MCP search and is not\nexposed as a hunting evidence tool. Guided setup configures it as part of the\nNotion brain flow, defaulting to Notion's hosted production MCP URL while letting\nyou provide your own Notion MCP-compatible URL.\n\nEvery other MCP server under `mcp` is evidence — this is what vala actually\nhunts in. The fastest way to connect one is the guided setup,\nwhich runs automatically the first time vala detects it isn't fully wired up, and\non demand any time:\n\n```sh\nvala setup     # connect a provider, a brain, and evidence sources\n```\n\nThe wizard ships curated presets for [Scanner](https://scanner.dev) (a hosted\ndata lake over HTTPS with an API key) and [Wiz](https://www.wiz.io) (the Security\nGraph, which you sign into in your browser), plus a custom-HTTP option for any MCP\nserver. It validates each connection live — you see `✓ N tools` before you leave\n— and writes the entry to `.vala.json`. You can also hand-edit the `mcp` array:\neach server is connected at startup over its `transport` (`http`, the default, or\n`stdio`), its tools discovered, and the read-only ones handed to the agent.\nSecrets are never persisted to `.vala.json`:\n\n- a bearer-token server reads its key from `api_key_env`;\n- an `\"oauth\": true` server (e.g. Wiz) signs in through the browser on first use\n  and caches its token under `~/.config/vala/mcp-auth.json` (mode `0600`),\n  refreshing it silently on later launches;\n- a `stdio` server reads the variables named in `env` and passes them to the\n  subprocess.\n\nAs a shortcut, `SCANNER_MCP_URL` (plus `SCANNER_API_KEY`) registers Scanner with\nno config file. The session banner shows which sources connected; with none, vala\nreasons only over local files.\n\n**Notion IDs.** The `notion` values are the parent `database` plus one\ndata-source ID per store — `vala setup` fills them in for you. To wire them by\nhand, resolve an ID with\n`ntn datasources resolve \u003cid\u003e` and match each property name to the keys vala\nwrites (`hunt_id`, `status`, `started_at`, relations like `hunts`/`detections`).\nLeave them empty to stay local.\n\n\u003e [!WARNING]\n\u003e `--permission` controls interactivity: `ask` (default) keeps you hands-on and\n\u003e prompts before strategic writes; `auto` is hands-off and lets vala add backlog\n\u003e items, hunts, intel, coverage, and links as it works. Reach for `auto` only\n\u003e when you trust the run.\n\n## Development\n\n```sh\ngo build ./...\ngo vet ./...\ngo test ./...\n```\n\nCI runs build, vet, `go test -race`, and a `gofmt` check on every push and pull\nrequest. The architecture follows\n[charmbracelet/crush](https://github.com/charmbracelet/crush) — one `Tool` type\nplus one embedded `.md` description per tool, a permission gate, sessions. The\nregistry in [`internal/tools/toolbox.go`](internal/tools/toolbox.go) is the\nsingle extension point.\n\n## License\n\n[MIT](LICENSE) © Britton Hayes\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbrittonhayes%2Fvala","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbrittonhayes%2Fvala","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbrittonhayes%2Fvala/lists"}