AI News HubLIVE
In-site rewrite6 min read

Show HN: A Firewall for AI agents with auditing

Trajeckt is a runtime enforcement gateway for AI agents that blocks multi-step exploits deterministicly in ~1.6ms by enforcing sealed pre-session commitments. It catches data exfiltration sequences that per-action security checks miss.

SourceHacker News AIAuthor: beebeeVB

Notifications You must be signed in to change notification settings

Fork 0

Star 2

BranchesTags

Open more actions menu

Folders and files

NameName

Last commit message

Last commit date

Latest commit

History

118 Commits

118 Commits

.github

.github

benches

benches

configs

configs

deployments

deployments

doc

doc

docs

docs

examples

examples

frontend/.vite/deps_temp_c018749a

frontend/.vite/deps_temp_c018749a

scripts

scripts

sdk-python

sdk-python

sdk

sdk

src

src

tests

tests

vendor

vendor

.gitignore

.gitignore

AGENTS.md

AGENTS.md

CHANGELOG.md

CHANGELOG.md

CODE_REVIEW.md

CODE_REVIEW.md

Cargo.lock

Cargo.lock

Cargo.toml

Cargo.toml

DOCKER.md

DOCKER.md

LICENSE

LICENSE

Makefile

Makefile

README.md

README.md

SECURITY.md

SECURITY.md

baseline_traj.txt

baseline_traj.txt

baseline_trajeckt.txt

baseline_trajeckt.txt

deployment_test_output.md

deployment_test_output.md

docker-compose.yml

docker-compose.yml

named_suites.txt

named_suites.txt

Repository files navigation

A runtime enforcement gateway for AI agents. It blocks multi-step exploits that every per-action security check misses — deterministically, in ~1.6ms, outside the agent's reach.

Reading a database is allowed. Sending an email is allowed. Doing them in that order is data exfiltration. Every authorization system on the market checks one action at a time, so the sequence walks right through. trajeckt checks each call against the whole trajectory the agent has accumulated and the data flowing through it — so the exfiltration is blocked at the step that completes it, even though that step looks legal on its own.

Run it

The fastest way to see the real gateway enforce is Docker. It brings up the enforcement gateway plus a mock MCP upstream, with the traj compiler bundled in the image so sealed-commitment enforcement is live out of the box — no hand-authored config.

docker-compose up --build

Then confirm enforcement is live and drive a session:

curl -s http://localhost:7777/healthz | jq

{"status":"ok",...,"commitment_capable":true}

Point any MCP-speaking agent at http://localhost:7777 instead of your real MCP server. A full smoke test — sensitive read allowed, then the external write that completes an exfiltration blocked with HTTP 403 — is in DOCKER.md, and scripts/smoke_test.sh runs it end to end and asserts both outcomes.

The first build takes a few minutes (Rust, compiling the gateway and the compiler from source). After that the stack starts in seconds.

No Docker? See the core idea in one command

If you just want to see the central insight with zero dependencies — no Docker, no server, no keys, no network — run the standalone example:

cargo run --example demo

It runs the same three-step agent twice. First the way the industry does it today: each action evaluated in isolation, all three allowed, customer records gone. Then through trajeckt's enforcement:

TRAJEKTORYD (J_t causal enforcement) ─────────────────────────────────────────── Agent A: read_database → ALLOW Agent B: summarize → ALLOW Agent C: send_email_external → BLOCK J_t causal path detected: d_customer_records → summarize → d_summary → external_sink Reason: sensitive data reached forbidden sink

Result: exfiltration blocked before execution.

Same agent, same tools, same legal-looking calls. trajeckt tracks where the data came from and refuses to let tainted data reach a forbidden sink — no matter how many clean-looking steps sit in between. (This standalone example exercises the heuristic safety floor; the Docker path above runs the full sealed-commitment engine.)

How it actually works

Before the agent runs, its authorized trajectory is declared in a small spec, compiled into a graph, and sealed with an HMAC. Once sealed, that graph is the authority. Every tool call is checked against the current reachable frontier of the sealed graph, fail-closed — an action not in the graph is hard-refused, and a session with no installed graph is refused before any evaluation runs. The agent's context window is treated as compromised; enforcement holds state the agent can't reach.

→ Full architecture and the sealed-commitment flow below.

A runtime enforcement gateway for AI agents that enforces sealed pre-session commitments: the agent's authorized trajectory is declared and sealed before execution starts, and the gateway enforces deterministically fail-closed against that sealed graph for every tool call in the session.

What it does

Most agent governance treats each tool call independently: a policy engine sees (agent, tool, arguments) and returns allow or deny. That framing structurally cannot see violations that live in the ordering and data-flow between actions.

trajeckt addresses this at two levels:

Primary: sealed commitment enforcement. Before any tool call is allowed, the gateway requires a sealed CompiledGraph (Gτ) — a cryptographically signed, operator-approved declaration of exactly which tools the agent may call, in which order, and to which data sinks. Once sealed, the graph is the authority. Every tool call is checked against the current reachable frontier of the sealed graph (Type V enforcement), provenance constraints (Type II), and taint propagation. An action not in the sealed graph is hard-refused. A session with no installed graph is hard-refused before any evaluation runs.

Safety floor: heuristic sequence detector. When an operator explicitly opts out of commitment enforcement (allow_uncommitted: true in the policy), the gateway falls back to coarse behavioral-pattern heuristics: exfiltration sequences (ReadSensitive → ExternalWrite) and command-and-control chains (ShellExec → NetworkEgress) are detected and blocked. This is a last-resort net, not the product. It catches known-bad patterns when running without a sealed graph; it does not detect violations that stay within the heuristic's blind spots.

Quickstart

Zero-config committed mode

trajectoryd up --upstream http://your-mcp-server

Only --upstream is required. trajectoryd up always runs in committed mode:

auto_commitment=on — the gateway seals a Gτ from the tools/list handshake before any tool call is allowed. The sealed graph is not heuristic mode; it is commitment graph enforcement derived automatically from the declared tool set.

require_commitment=on — any session that reaches tools/call without an installed graph is hard-refused with an explicit block reason, not silently downgraded to heuristics. Point your MCP-speaking agent at http://localhost:7777 instead of your real MCP server. The gateway prints the listen address and a curl /healthz verify command on startup.

Docker

docker-compose up

Then point your MCP-speaking agent at http://localhost:7777. The bundled fake-mcp-server is the upstream by default. See DOCKER.md for details.

From source

cargo build --release ./target/release/trajectoryd up \ --upstream http://your-mcp-server

Or with an explicit policy file for corpus, TLS, and budget settings:

./target/release/trajectoryd up \ --upstream http://your-mcp-server \ --policy configs/trajectory-policy.yaml.example

Advanced: hand-authored commitments

Zero-config derives a permissive graph covering all declared tools. Operators who need to pre-declare exact tool scopes, hard budget caps, or ordered task phases can hand-author a commitment:

  1. Start the gateway

docker-compose up --build

The example policy at configs/trajectory-policy.yaml.example needs one key set to match what you use with traj commit:

corpus: signing_key: "trajeckt-demo-v1-32byte-key12345" # must match --key below

  1. Author and seal a commitment with traj commit

Install the traj compiler:

cd /path/to/traj && cargo build --release export TRAJ_BIN=/path/to/traj/target/release/traj

Seal a policy from the MCP tool list and a task description:

DEMO_KEY_HEX=$(python3 -c "print('trajeckt-demo-v1-32byte-key12345'.encode().hex())")

$TRAJ_BIN commit \ --tools-list mcp_tools_list.json \ --task "Read customer tickets and write internal case notes" \ --budget money=0,calls=20,secs=60 \ --out policy.sealed.json \ --key "$DEMO_KEY_HEX" \ --yes

mcp_tools_list.json is the tools/list response from your MCP server. traj commit narrows the tool list to the task-relevant tools, derives scope automatically, and seals the graph.

For advanced use (hand-authored BoundaryCommitment JSON with explicit read_scope, write_scope, data_sinks, and budget):

$TRAJ_BIN commit-boundary commitment.json \ --registry registry.json \ --key "$DEMO_KEY_HEX" \ -o policy.sealed.json

  1. Install the sealed graph

python -m trajeckt.install \ --gateway http://localhost:7777 \ --graph policy.sealed.json

Or from Python:

from trajeckt.install import installed_session

session_id = installed_session( gateway_url="http://localhost:7777", sealed_graph_path="policy.sealed.json", ) print(session_id) # pass this to make_http_wrapper below

installed_session() generates a UUID v4 session ID, installs the graph, and returns the ID so callers pass it to make_http_wrapper without a copy-paste step.

  1. Wrap your agent with the same session_id

from trajeckt_langgraph import make_http_wrapper from langgraph.prebuilt import ToolNode

tool_node = ToolNode( tools, wrap_tool_call=make_http_wrapper( "http://localhost:7777", session_id=session_id, # from installed_session() above ), )

session_id must match the one returned by installed_session(). A mismatched ID means the gateway has no sealed graph for that session, which now hard-refuses all tool calls — the request is blocked with "no commitment installed" rather than silently falling back to heuristics. This is intentional: a mismatch is always a configuration error.

Runnable end-to-end example

examples/quickstart_enforced_agent.py threads one session_id through the entire install → enforce loop and exits non-zero if either the allowed call is blocked or the unsafe call is allowed (doubles as CI):

python3 examples/quickstart_enforced_agent.py \ --gateway http://localhost:7777

Commitment posture and opt-out

require_commitment_before_tools auto_commitment behavior

true (default) true (default) Zero-config committed: commitment auto-installed on tools/list, then enforced. A session that reaches tools/call without one is hard-blocked.

true false Manual-commit: operator installs graphs with traj commit --install; sessions without a graph are blocked loudly.

false any Heuristic floor mode. Requires allow_uncommitted: true in the policy to start; otherwise the gateway refuses to start. Prints a WARN to stderr at startup even when the flag is set.

require_commitment_before_tools defaults to true when the field is absent from the policy YAML. trajectoryd up hardcodes both flags on regardless of the YAML file.

To explicitly opt into heuristic floor mode, add to your policy YAML:

require_commitment_before_tools: false allow_uncommitted: true

Or pass --allow-uncommitted to the gateway subcommand. The gateway will start with a WARN.

Verifying enforcement with /healthz

curl -s http://localhost:7777/healthz | jq

Response:

{ "status": "ok", "signing_key_source": "file", "auto_commitment": true, "commitment_capable": true }

field meaning

status "ok" (liveness) or "ready" (readiness, from /readyz)

signing_key_source "env" — from TRAJECTORYD_SIGNING_KEY or env:VAR "file" — from ~/.trajeckt/key or file:/path or inline policy "generated" — freshly generated; key not yet on disk

auto_commitment whether the gateway auto-installs commitments on tools/list

commitment_capable true if the traj binary is resolvable right now; false means auto-commitment WILL fail — install the binary or set TRAJ_BIN

commitment_capable: false does not prevent the gateway from starting, but every tools/list response will fail to install a

[truncated for AI cost control]

Show HN: A Firewall for AI agents with auditing | AI News Hub