{"id":51038295,"url":"https://github.com/akhilesharora/herkos","last_synced_at":"2026-06-22T08:32:28.194Z","repository":{"id":365569987,"uuid":"1272719485","full_name":"akhilesharora/herkos","owner":"akhilesharora","description":"Local-first MCP egress broker for AI coding agents: deny-by-default tool gate, signed offline-verifiable audit log, and the SpanGate dual-use binding.","archived":false,"fork":false,"pushed_at":"2026-06-17T22:26:06.000Z","size":431,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-06-18T00:14:41.667Z","etag":null,"topics":["ai-agents","ai-security","audit-log","claude-code","go","golang","llm","mcp","model-context-protocol","reference-implementation"],"latest_commit_sha":null,"homepage":"https://akhilesharora.github.io/herkos/","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/akhilesharora.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","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-17T21:53:24.000Z","updated_at":"2026-06-17T22:30:40.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/akhilesharora/herkos","commit_stats":null,"previous_names":["akhilesharora/herkos"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/akhilesharora/herkos","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/akhilesharora%2Fherkos","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/akhilesharora%2Fherkos/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/akhilesharora%2Fherkos/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/akhilesharora%2Fherkos/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/akhilesharora","download_url":"https://codeload.github.com/akhilesharora/herkos/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/akhilesharora%2Fherkos/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34641636,"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-22T02:00:06.391Z","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":["ai-agents","ai-security","audit-log","claude-code","go","golang","llm","mcp","model-context-protocol","reference-implementation"],"created_at":"2026-06-22T08:32:26.463Z","updated_at":"2026-06-22T08:32:28.188Z","avatar_url":"https://github.com/akhilesharora.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Herkos\n\n[![ci](https://github.com/akhilesharora/herkos/actions/workflows/ci.yml/badge.svg)](https://github.com/akhilesharora/herkos/actions/workflows/ci.yml) · [![codecov](https://codecov.io/gh/akhilesharora/herkos/graph/badge.svg)](https://codecov.io/gh/akhilesharora/herkos) · [![Go Report Card](https://goreportcard.com/badge/github.com/akhilesharora/herkos)](https://goreportcard.com/report/github.com/akhilesharora/herkos) · [Live site](https://akhilesharora.github.io/herkos/) · Apache-2.0\n\nA local-first, pure-Go in-path MCP egress broker. Herkos sits between an AI agent and the MCP servers it calls, gates which tool calls reach upstream on the wire, and writes a signed, offline-verifiable audit log of every brokered call. It is built around **SpanGate**: the minimal code context an agent needs to answer a query is exactly the set it should be allowed to send back out. It is a working utility you can read, run, and verify.\n\n## SpanGate\nFor each query, Herkos's local tree-sitter code graph emits a minimal set of `(file, line-range)` spans. That same set is both the model's context (fewer tokens) and the egress allowlist (deny-by-default). The dual-use is structural: a single `core.Binding` is the only value both the serve path and the egress authorizer read, so \"the context set and the egress set are identical\" is a type invariant, not a convention two code paths politely agree to. Every answer ships a signed Merkle receipt, verifiable offline by a third party with only the public key.\n\n![SpanGate: a query produces one core.Binding span set that is both the model's context and the egress allowlist, and every answer ships a signed, offline-verifiable receipt](docs/diagrams/dual-use-binding.svg)\n\nThe live broker (`herkos serve`) enforces that same set on the wire:\n\n![The broker: the agent's tool calls pass through herkos serve, a deny-by-default tool gate plus content tripwire, to the upstream MCP server, with every brokered call written to a signed, hash-chained, context-bound audit log](docs/diagrams/broker-data-path.svg)\n\n## Install\n```\ngo install github.com/akhilesharora/herkos/cmd/herkos@latest\n```\nBuilding from source (or `make build`) needs Go 1.25+ and a C toolchain: the `herkos index` code-graph step uses tree-sitter through cgo. Prebuilt binaries on the [releases page](https://github.com/akhilesharora/herkos/releases) are CGO-off and portable, so everything works except `herkos index`; build from source if you want the SpanGate index. `serve --isolate` is Linux-only.\n\n## Use it with your agent\nHerkos presents as an MCP server to whatever launches it and forwards to the real one, so it works in front of any MCP client with no client-specific code. Wrapping a server is one config change: point the client's `command` at `herkos serve` and pass the real command after `--`.\n\nFirst, generate the local signing key (stays on your machine, `0600`):\n```\nherkos keygen\n```\n\nThe fastest path is to broker everything you already have. `register --all` walks a config and wraps every local stdio server in place:\n```\nherkos register --all --config .mcp.json\n```\nIt launches each server once to discover the tools it exposes, then pins exactly those tools as the server's allowlist, so a tool added by a later upstream update is denied by default. Remote servers and entries already brokered through Herkos are skipped.\n\n`scan` and `register` work across any client by config **shape**, not by client name. They auto-detect both JSON launch-config shapes, the `mcpServers` object and the `servers` object, so the same two commands cover Claude Code, Cursor, VS Code, GitHub Copilot, Cline, Windsurf, and anything else using either shape. Codex stores its servers as TOML in `~/.codex/config.toml`; that one is the same one-line wrap, hand-edited for now, since `register` does not rewrite TOML yet.\n\nWrap a single server in place instead of the whole config:\n```\nherkos register --config .mcp.json --server github --allow-tool get_issue --allow-tool list_issues\n```\n\nOr skip the config and broker an upstream server directly. The agent's MCP client launches `herkos serve ...`; everything after `--` is the upstream server command:\n```\nherkos serve --allow-tool read_file --allow-tool list_dir -- npx -y @some/mcp-server\n```\nA `tools/call` to any tool you did not `--allow-tool` is blocked in-path and answered with a JSON-RPC error; the agent's session keeps running.\n\n## What works\nThe pure-Go SpanGate core (SELECT -\u003e Binding -\u003e canonicalize -\u003e pool -\u003e signed receipt, with the dual-use leak provably blocked), the tree-sitter parser (Go/TS/Python), the on-disk index, the CLI, and the live in-path MCP broker (`herkos serve`, MCP newline-framed and verified end to end over real stdio framing against a subprocess) all work and are tested under the race detector, fuzzed, and gated on a clean-checkout build. The run against a third-party MCP server (`@modelcontextprotocol/server-everything`) is a manual repro, in the [reproduce-it-yourself](#reproduce-it-yourself) steps below, not in CI.\n\nEnforcement is described plainly, because a security tool that hides its gaps is worse than none:\n\n- The broker's **default egress guard is tool-name only**. It gates which `tools/call` reach the upstream, not payload bytes or other methods.\n- Pinning a served set (`--served-span` with `--index`) adds a **content tripwire** that blocks tool-call arguments carrying repo lines from outside the set, after normalizing case and whitespace so a reflow or recase still trips. This is a userspace heuristic that base64, paraphrase, or token rewrite still defeat. It is not an airtight boundary.\n- `serve --receipts \u003cdir\u003e` keeps a **signed, hash-chained audit log** of every brokered tool call, fail-closed: an audit-write failure stops the session rather than letting an unlogged call through. `herkos verify` detects any edit, reorder, or mid-drop offline with only the public key, and reports a truncated log (one missing its signed close) as incomplete. With a served set pinned, the opening record commits a fingerprint of that served context, so the receipt proves which context-egress binding was in force.\n- `serve --isolate` runs a server in a **kernel network namespace with no route out** (unprivileged, Linux), so a server that only needs stdio to Herkos cannot open its own socket to any host. The transformation-resistant, full per-destination egress seal (eBPF host allowlisting) is **not built**.\n\nThe signed receipt is the one durable, distinctive piece, and it works today.\n\n## Reproduce it yourself\nBroker a real MCP server, deny a tool in-path, and verify the signed receipt offline. `echo` is allowed, any other `tools/call` is blocked in-path:\n```\nherkos keygen\nherkos serve --allow-tool echo --receipts /tmp/r -- npx -y @modelcontextprotocol/server-everything\nherkos verify --file /tmp/r/\u003csession\u003e.jsonl --pubkey \u003cpublic-key\u003e   # VERIFIED ... cleanly closed\n```\nEditing a record, dropping the sealed last line, or using a different public key all make `herkos verify` fail (INCOMPLETE for a chopped log, a hard failure otherwise).\n\nArm the content tripwire by building an index and pinning the spans the model may see. A tool-call argument carrying a repo line from outside `auth.go:1-40` is then blocked on a normalized match, and the served set is bound into the receipt:\n```\nherkos index .\nherkos serve --allow-tool read_file --index .herkos/index --served-span auth.go:1-40 \\\n  --receipts /tmp/r -- npx -y @some/mcp-server\n# the receipt's opening record commits a fingerprint of the served set; flipping it fails verify\n```\n\nAdd `--isolate` to cut the server's own network (Linux). On shutdown the log is sealed and its tip hash printed, so a later truncation is detectable:\n```\nherkos serve --allow-tool read_file --receipts ~/.herkos/audit --isolate -- npx -y @some/mcp-server\nherkos verify --file ~/.herkos/audit/\u003csession\u003e.jsonl --pubkey \u003chex\u003e   # VERIFIED, or INCOMPLETE if chopped\n```\n\n## Where it stands\nMarket and threat analysis (see [`CASE-STUDIES.md`](docs/CASE-STUDIES.md) for worked examples against real 2025 MCP incidents) puts three of the four pillars in commodity territory:\n\n- **Native host tool allow/deny.** Claude Code, Cursor, and VS Code Copilot ship deny-by-default MCP allowlists and OS-level network sandboxes, enterprise-managed. The in-path tool-name broker lives in their lane.\n- **Signed offline-verifiable receipts.** Open-source **Pipelock** is a strict superset: Ed25519 hash chain covering MCP stdio *and* HTTP, with a non-Go verifier. Herkos has one format and one verifier. Do not read Herkos as doing more here than Pipelock does.\n- **Admission scanning.** Snyk's / Invariant's `mcp-scan` detects tool-poisoning by content analysis with no baseline; Herkos's `scan` only flags tool-description drift against a trusted baseline, so it misses first-seen poisoning.\n\nThe threat shape is honest about reach too: the marquee MCP attacks (GitHub toxic-agent-flow, `postmark-mcp`) ride *approved* tools or leak server-side. An in-path broker cannot prevent them. What it delivers is **harm-reduction and forensics**, a tighter blast radius and a verifiable record of what happened, not prevention.\n\nThat is the scope, not a verdict. Inside it, one piece is distinct: **context-derived egress as a type invariant**, the SpanGate dual-use binding done in a shipping product. It is distinct in a shipping product, not new as an idea: the concept is anticipated in the research (CaMeL, OCELOT, NeuroTaint), and the enforcement today is the defeatable content tripwire, not an airtight boundary. The honest account of why it differs from IFC and signed-receipt work, and exactly where it holds and does not, is in [`DUAL-USE-BINDING.md`](docs/DUAL-USE-BINDING.md). The field map, on three axes against Pipelock, capgate, mcp-spine, mcp-scan, and srt, is in [`COMPARISON.md`](docs/COMPARISON.md).\n\n## Develop\n```\nmake build         # go build ./...\nmake race          # go test ./... -race\nmake lint          # golangci-lint run\nmake check         # build + vet + race + lint\nmake verify-clean  # build + vet + race the committed code, not the working tree\n```\n\n## Write-up\nThe honest account of why it was built, why prevention is not achievable, and where it does work is in [WRITEUP.md](docs/WRITEUP.md).\n\n## Security\nReport a vulnerability through [SECURITY.md](SECURITY.md). Herkos ships its own bypass list, so what it does not catch is written down in the [case studies](docs/CASE-STUDIES.md) and the security model, not hidden.\n\n## License\nApache-2.0.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fakhilesharora%2Fherkos","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fakhilesharora%2Fherkos","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fakhilesharora%2Fherkos/lists"}