Open specification for security teams.
Trust requires verification. This document details the exact implementation of the loopback proxy, cryptographic policy signing, SIEM pipelines, and the system threat model. It is built for the engineers responsible for evaluating this control.
Distributed Enforcement, Centralized Policy.
The system operates across three core runtimes separated by explicit trust boundaries: the endpoint proxy, the local daemon, and the single-tenant control plane.
Adapted from docs/JUDGE_AUTHORITY.md and deploy/README.md.
What happens on every request.
OPENAI_BASE_URL / ANTHROPIC_BASE_URL pointed at the proxy's vendor paths — via VS Code's environmentVariableCollection, scoped to the terminals VS Code spawns. Never HTTP_PROXY, never system-wide, and never overriding a value the developer already set. The agent's SDK reads the override and dials 127.0.0.1:11435 exactly as it would the vendor endpoint. The proxy is loopback-bound by default; widening requires explicit operator config (bind_host / TETHER_PROXY_BIND_HOST) and emits a named startup warning. Optional, for CONNECT-tier host visibility: an operator can point an agent's process HTTP_PROXY at the proxy — process-scoped, with NO_PROXY covering loopback and Overwatch, never system-wide.Tier 0 — deterministic floor
proxy_core.go:114-117verdict: BLOCKED.CloudJudgeCanBlock and CloudJudgeEnabled are true, the judge runs in-band with a default 800 ms timeout. On a BLOCKED verdict, the proxy returns 403. On timeout or empty response, the verdict converts to BLOCKED with reason "cloud judge (sync): timed out — failing closed." A network adversary cannot defeat blocking by delaying judge calls. This is the 2026-06 hardening.Tier 2 — async judge, advisory
proxy_core.go:183-188judgeReview record with promptDigest.sha256, a 200-character preview, and a verdict an operator can promote to the Tier 0 floor for future requests. The customer for Tier 2 is the operator console and the SIEM, not the developer.Receipt on every response
docs/ATTESTATION.mdX-Tether-Policy-Version: <version> on every blocked 403 and every forwarded response. The corresponding event in your SIEM carries the same policyVersion. An auditor pulls GET /api/v1/policy/versions/<version>, retrieves the signed bundle, and runs an offline Ed25519 verification against the controls in force at decision time.Overwatch signs. The proxy verifies.
Replay is blocked by a monotonic version.
The control plane signs {schemaVersion, version, controls, publishedAt} at publish with Ed25519. Each proxy verifies before applying — TOFU-pinned per-tenant public key, fail-closed on mismatch. A monotonic version blocks replay of an older bundle.
Policy poll interval is 10 seconds; ACKs include {hostname, version, appliedAt} so Overwatch shows real fleet-adoption state. A fleet converges on a new policy within that 10-second bound.
// policy_client.go:185-188
if !verifyEd25519(payload, sig, key) {
// discard; keep last verified or block egress
return ErrPolicyUnverified
}
// monotonic version blocks replay
if incoming.Version <= current.Version {
return ErrStalePolicy
}
Customer-supplied model.
Hardware-adaptive selection. Sampled by default.
The proxy probes hardware on startup and selects from a catalog of judge profiles across four tiers — metal, cuda, cpu-modern, cpu-low. Two runtimes are supported: local Ollama (per-vendor model overrides) and a cloud OpenAI-compatible endpoint the customer configures.
Async by default at a 3% sample rate, hard-capped at 10%. Mandatory escalations fire on novel-agent, novel-destination, and large-body. Sync-blocking is opt-in via cloud_judge_can_block with a default 800 ms timeout.
The judge is customer-supplied: cloud calls bill from the customer's own model vendor, Tether bills nothing. Local Ollama keeps every decision on-device — the on-prem and air-gap path.
tier := probeHardware() // metal|cuda|cpu-modern|cpu-low
profile := catalog[tier]
sampleRate := min(cfg.SampleRate, 0.10) // def 0.03
if novelAgent || novelDest || largeBody {
escalate() // mandatory review
}
if cfg.CloudJudgeCanBlock {
runSync(profile, 800*ms) // fail-closed
}
The exact routes, formats, and signatures.
Every event fans out to all registered SIEM adapters in parallel, with a shared 5-attempt exponential-backoff retry. Identity and posture ride first-class adapters that mirror the receiver's own schema.
Splunk HEC
siem-adapters.js:267-282POST → <splunk-host>/services/collector/event. Token held write-only in your Splunk instance. Per-event JSON envelope with policyVersion, identity, agent, host, verdict, deterministic match, judge verdict. Retry: 5 attempts, exponential backoff up to ~5.5 min.Microsoft Sentinel
siem-adapters.js:290-312POST → Log Analytics Data Collector API, SharedKey HMAC-SHA256 against api-version=2016-04-01. Workspace ID + key per adapter; no app registration. Custom log type per event family so Sentinel rules target Tether's stream directly.Syslog RFC 5424
siem-adapters.js:333-355UDP datagram → <syslog-host>:514 via Node's dgram. UDP only in v1. Air-gap-clean: no internet dependency. Pair with on-prem Overwatch and local Ollama for a true air-gap configuration.Outbound webhooks (HMAC-SHA256)
webhook-store.js:174-240POST → <your-receiver> · X-Tether-Signature: sha256=<hex>. HMAC of timestamp + "." + body; constant-time comparison on your side. Per-webhook filters on eventTypes and verdicts. Secret write-only at registration, redacted on list/show./api/v1/auth/sso/{start,callback,scim}. Directory-sync is a push-model Jackson webhook (X-DSync-Signature HMAC-SHA256), not a SCIM2 endpoint. user.created / user.updated / user.deleted map to roles via group name — SCIM_GROUP_ADMIN / SCIM_GROUP_OPERATOR / SCIM_GROUP_VIEWER env vars. Every provision and deprovision lands in the audit trail.GET /api/v1/devices/posture/export?format=tether | okta | entra | csv, with ?compliantOnly= and ?since= filters. The Okta envelope plugs into Device Trust attestation (attestations.tether namespace). The Entra shape mirrors GET /deviceManagement/managedDevices under extensions.aiSecurityPosture. CSV is RFC 4180-quoted.JIT access requests + operator console
workspace/main.go:349-402 overwatch/api/server.js mcp.go:401-426request mode, the daemon pauses the action and posts to POST /api/v1/access-requests; the operator decides in /access-requests. Default 5-minute TTL per PILOT.md; behavior on expiry is configurable per axis (block / allow / coach). Cooperative MCP agents that speak the tether_may_i_* surface escalate the same way — needs_approval rides the same JIT queue.Append-only policy audit
PILOT.mdGET /api/v1/policy/audit · file $TETHER_DATA_DIR/policy-audit.jsonl. Append-only JSONL of every policy publish, revert, and adoption ACK — actor, reason, before-hash, after-hash. SQLite-backed durable stores are post-GA; today the file lives in your GCS bucket or EFS file system.The chain your auditor runs
without trusting Tether.
Ed25519 policy signing proves which bundle Overwatch signed. Per-request attestation closes the gap from which bundle exists to which bundle decided this specific request.
The proxy sets X-Tether-Policy-Version: <version> on every blocked 403 and every forwarded response. Every AI_PROXY_REQUEST event and every judge review carries policyVersion. An auditor with the event ID does four things:
- 1.Pull the signed bundle:
GET /api/v1/policy/versions/<version>. - 2.Pull the public key:
GET /api/v1/policy/public-key. ConfirmkeyIdmatches. - 3.Run Ed25519 verification offline:
openssl pkeyutl -verify .... - 4.Read the
controlsobject in the verified payload. That is the policy in force at decision time.
Chain: event.policyVersion → signed bundle → Ed25519 verify → controls. docs/ATTESTATION.md
The four steps collapse into one call. GET /api/v1/attestation/<eventId> returns a single artifact — the decision, the signed policy bundle that made it, and the tenant's Ed25519 public key. tools/verify-attestation.js is a standalone verifier with zero dependencies; it re-checks the artifact offline: the Ed25519 signature, the binding of the decision to its policy version, and the exact controls in force. It also prints controls.judge.model — the judge model tag is pinned inside the signed bundle, so a verified decision attests which model was in force. Version drift cannot silently change enforcement.
Every decision also records how the agent was identified — explicit, header-heuristic, or unattributed — in the event, in the judge record, and in the X-Tether-Agent-Source response header. The audit distinguishes trustworthy attribution from a header guess.
One limit. In-memory events do not survive Overwatch restart unless your SIEM adapter forwards them. The persistent audit trail is policy-audit.jsonl for policy publish actions; per-request event durability depends on SIEM forwarding. Tamper-evident per-version bundle retention (append-only WORM) is a documented gap for deployments requiring full forensic retention.
{
"success": true,
"version": {
"version": 42,
"publishedAt": "2026-06-07T18:29:31Z",
"signedPayload": "<canonical-JSON>",
"signature": "<base64>",
"keyId": "tether-pol-...",
"signatureAlgorithm": "Ed25519"
}
}
$ openssl pkeyutl -verify ...
Signature Verified Successfully
// or the one-call path
$ curl -s overwatch/api/v1/attestation/<eventId>
// → decision + signed bundle + tenant public key
$ node tools/verify-attestation.js
Ed25519 signature verified · policy binding OK
controls.judge.model: "<pinned-model-tag>"
What the proxy sees,
and what it does not.
- CONNECT destination classification. The proxy sees the host on the CONNECT tunnel and can block unapproved AI vendors and off-allowlist marketplace installs at the host level.
- Configured-upstream content inspection. When an agent supports a base-URL override (Claude Code, Codex CLI, OpenAI-SDK consumers, Continue, aider), Tether reads the full prompt, runs the deterministic floor, and optionally invokes the judge before forwarding.
- Identity attribution. Every decision carries the developer's SCIM identity, the agent label, the host, and the policy version.
- TLS-tunneled body inspection. The Connect proxy does not perform TLS interception. No root CA is installed on the developer machine. Workspace-tier TLS termination is reserved for the future managed VSCodium build.
- Workspace daemon enforcement. Clipboard, downloads, external links, screen capture, and coach mode are advisory today. The daemon returns the right decision and emits the audit; the IDE-side enforcement seam ships with the managed VSCodium artifact.
- Tools the override doesn't reach. An agent spawned outside VS Code's terminals doesn't inherit the injected base-URL override, and an extension or CLI that binds its own endpoint bypasses the proxy either way — as does a tool that ignores the process-scoped
HTTP_PROXYin the advanced setup. Tether governs the AI-egress lane for the tools your developers are authorized to use. - MCP servers that hardcode their own endpoint. MCP-bound traffic that never inherits the base-URL override does not pass through the loopback proxy. Recommendation: allowlist MCP endpoints at the host-classification layer; monitor for novel destinations. Cooperative MCP agents that speak
tether_may_i_*stay in scope — warden/cmd/proxy/mcp.go.
policy-audit.jsonl + per-request attestation produce auditable evidence."What about Cursor?"
Coverage depends on whether the tool honors a base-URL override. Three tiers; honest about each.
| Tool | Architecture | Best tier today | What we see | Honest gap |
|---|---|---|---|---|
| Claude Code | Anthropic CLI, Node.js | Tier B (configured-upstream) | Full prompt + response stream, tool-use blocks, file attachments | MCP servers need their own coverage |
| OpenAI Codex CLI | OpenAI CLI | Tier B | Full prompt, response, tool calls | Non-OpenAI provider configs need local prefix |
| Aider | Open-source Python CLI | Tier B | Full prompt, response, every file attached, every diff applied | None at Tier B |
| Zed | Native Rust editor | Tier B | Full prompt, response, file context | Custom provider added after install drops to Tier A until re-sync |
| VS Code + Continue / Copilot | VS Code extension | Tier A on Copilot · Tier B on Continue | Copilot: vendor + identity. Continue: full content | Copilot's prompt path is opaque — Tier A is the honest claim |
| Cursor | Closed-source VS Code fork | Tier B on BYOK · Tier A on default flow | BYOK: full content. Default: vendor + identity | Default flow on unmanaged devices: prompt content not visible |
| Browser-only (ChatGPT.com) | Web app | — | Network destination via your existing CASB | Not in Tether's scope. Browser extension on roadmap; not shipping today |
Walk the code with us.
Bring this brief to a 30-minute review. We open the repo on the line numbers above; you decide whether the claim holds.