Technical Architecture

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.

DEVELOPER MACHINE CUSTOMER CLOUD ACCOUNT — GCP OR AWS Coding agent Claude Code · Cursor · Copilot · Codex CLI Aider · Zed · VS Code extension BASE_URL override → 127.0.0.1:11435 Connect proxy · loopback only 127.0.0.1:11435 — kardianos service · Go TIER 0 deterministic blocklist · always blocks TIER 1 sync judge · fail-closed on timeout TIER 2 async judge · advisory · cannot block Workspace daemon 127.0.0.1:11436 — clipboard / downloads / external links Today: advisory. Enforcing when managed VSCodium ships. Upstream AI vendor api.anthropic.com · api.openai.com · ... Overwatch — single-tenant in your cloud Cloud Run (GCP) or ECS Fargate (AWS) — Node.js / Express POLICY-STORE · Ed25519-signed bundles · versioned · audited EVENTS · access-requests · judge-reviews · agents · devices SSO (Jackson SAML/OIDC) · SCIM webhook · posture export Judge — customer-supplied Local Ollama (4 hardware tiers) OR cloud (OpenAI-compatible) Default sample rate 3% · max 10% Your security stack Splunk HEC · Sentinel LA · syslog UDP Okta / Entra · Jackson SCIM Tether is an additive control TETHER_DATA_DIR policy.json (versioned bundles) · policy-audit.jsonl (append-only) Lives in your VPC. No Tether-hosted data transit. X-Tether-Policy-Version: 42 — on every response · auditor verifies offline against signed bundle policy poll · 10s Ed25519 verify judge call
Scroll the diagram sideways to read it →

Adapted from docs/JUDGE_AUTHORITY.md and deploy/README.md.

What happens on every request.

01

Agent dials the proxy

main.go:98-103 docs/PROXY_ENV_SCOPING.md
By default, the VS Code extension injects vendor base-URL overrides — 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.
02

Tier 0 — deterministic floor

proxy_core.go:114-117
Compiled regex over the embedded floor plus operator-promoted patterns. If a pattern matches, the response is HTTP 403 with the reason text and the forward path is not reached. Tier 0 cannot be opted out of at runtime. The decision is reachable in the audit log under verdict: BLOCKED.
03

Tier 1 — sync judge (opt-in), fail-closed

proxy_core.go:130 proxy_core.go:158-181
Configurable; sync-block opt-in at deploy time. When both 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.
04

Tier 2 — async judge, advisory

proxy_core.go:183-188
When sync-mode is off, the judge runs in a goroutine after the response has already been forwarded. It cannot rewrite, block, or retract the response — by invariant. What it can do: produce a judgeReview 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.
05

Receipt on every response

docs/ATTESTATION.md
The proxy sets X-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.

"The judge said ALLOWED" is not the same as
"the request was allowed."

The framing depends on the tier. The three tiers exist in the code as distinct branches — not a single configurable knob.

TIER 0 — DETERMINISTIC FLOOR

Always blocks. No opt-out.

Operator opt-out? No.

Compiled regex over the embedded floor plus operator-promoted patterns. The forward path is not reached on a match. This is the only enforcement layer a regulated customer can rely on to block by policy alone — no model, no network, no opinion.

blocklist.go:138
TIER 1 — SYNC JUDGE

Blocks when enabled. Fails closed on timeout.

Configurable; sync-block opt-in at deploy time.

The judge runs in-band with a default 800 ms timeout. When enabled, it blocks in-band and fails closed on timeout — the verdict converts to BLOCKED on timeout or empty result. If CloudJudgeCanBlock is left false, the judge runs as Tier 2 advisory.

JUDGE_AUTHORITY.md
TIER 2 — ASYNC JUDGE

Records. Cannot block. By invariant.

Operator opt-in? No effect on the current request.

Runs in a goroutine after the response is forwarded. Produces a judgeReview the operator can promote to the Tier 0 floor — affecting future requests, not the one being judged. The async path has no handle on the response that already went out. By construction.

proxy_core.go:245

For known patterns, Tier 0 is the strong guarantee. For novel content, Tier 1 blocks if the operator opted in. For TLS-tunneled traffic that does not route through the configured plaintext upstream, the proxy classifies the destination host but does not read the body — see Scope & threat model below.

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.

// fail-closed on signature mismatch
// 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.

// judge_selector.go:76-93 (paraphrased)
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.

POST → <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-312
POST → 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-355
UDP 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-240
POST → <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.

SSO + SCIM via Jackson

jackson.js jackson.js:205-291
BoxyHQ Jackson handles SAML 2.0 / OIDC at /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.

Device posture export — four formats

posture-export.js:79-101 :111-135 :145-172 :177-212
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-426
When a workspace axis (clipboard, downloads, external links, screen capture) is set to request 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.md
GET /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. 1.Pull the signed bundle: GET /api/v1/policy/versions/<version>.
  2. 2.Pull the public key: GET /api/v1/policy/public-key. Confirm keyId matches.
  3. 3.Run Ed25519 verification offline: openssl pkeyutl -verify ....
  4. 4.Read the controls object 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.

overwatch API /api/v1/attestation/:eventId tools/verify-attestation.js

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.

$ curl -s overwatch/api/v1/policy/versions/42
{
  "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.

In scope
  • 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.
Out of scope today
  • 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_PROXY in 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.
Threat model — in scope
Inadvertent data exfil
Developer carelessness
Developers pasting proprietary code, credentials, or PII into AI completions. Tier 0 floor + sync judge catch known patterns and fail-closed-on-timeout configurations.
Covered   For known patterns that route through the proxy.
Shadow AI tools
Unapproved coding agents
Extensions or CLIs that make AI API calls outside the approved toolchain. The proxy classifies the destination host on the CONNECT tunnel; unapproved vendors are blockable.
Covered   For tools that route through the proxy — the base-URL override, or process-scoped HTTP_PROXY in the advanced setup.
Policy tampering
Local config changes
A developer modifying, downgrading, or replaying an older policy. Ed25519 verification with monotonic version blocks both tampering and replay.
Covered
Compliance evidence gaps
Audit readiness
Inability to demonstrate technical controls. Append-only policy-audit.jsonl + per-request attestation produce auditable evidence.
Covered
Threat model — out of scope
Server-side AI traffic
Cloud workloads
Agent-to-agent calls made from CI/CD pipelines or server-side processes — not developer endpoints. Tether is an endpoint product.
Out of scope
Browser-based AI
claude.ai · chat.openai.com
AI tools accessed directly in the browser — no local CLI or extension involved. Today this is your CASB's domain; Tether's browser-extension coverage is on the roadmap.
Partial
Tools that bypass the proxy
Rogue extensions, custom TLS stacks
An agent spawned outside VS Code's terminals doesn't inherit the base-URL override; an extension that binds its own socket or hardcodes an endpoint bypasses the loopback proxy — so does one that ignores the process-scoped HTTP_PROXY in the advanced setup. Socket-level egress provenance binding is not in the codebase.
Partial   Mitigation: managed VSCodium for high-control environments.
Motivated technical insider
Adversary with full physical access
A developer with deep technical knowledge motivated to circumvent controls (kernel-level interception, custom binaries). Tether raises the cost; it is not a deterrent against this profile.
Partial

"What about Cursor?"

Coverage depends on whether the tool honors a base-URL override. Three tiers; honest about each.

Per-tool AI agent coverage: architecture, best Tether tier today, what Tether sees, and the honest gap.
Tool Architecture Best tier today What we see Honest gap
Claude CodeAnthropic CLI, Node.jsTier B (configured-upstream)Full prompt + response stream, tool-use blocks, file attachmentsMCP servers need their own coverage
OpenAI Codex CLIOpenAI CLITier BFull prompt, response, tool callsNon-OpenAI provider configs need local prefix
AiderOpen-source Python CLITier BFull prompt, response, every file attached, every diff appliedNone at Tier B
ZedNative Rust editorTier BFull prompt, response, file contextCustom provider added after install drops to Tier A until re-sync
VS Code + Continue / CopilotVS Code extensionTier A on Copilot · Tier B on ContinueCopilot: vendor + identity. Continue: full contentCopilot's prompt path is opaque — Tier A is the honest claim
CursorClosed-source VS Code forkTier B on BYOK · Tier A on default flowBYOK: full content. Default: vendor + identityDefault flow on unmanaged devices: prompt content not visible
Browser-only (ChatGPT.com)Web appNetwork destination via your existing CASBNot 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.

Book a 30-min walkthrough ↗