Agent Safety14 min read

Zero-Trust Ingest: Why You Should Retire Your Static DSNs

Static write-only telemetry endpoints are an attack runway for agentic systems. Replace them with an ephemeral JWT gate in front of your collector.

This is Part 1 of Hardened Agentic Stack — a trust-boundary series for high-privilege agents. We start where many teams still leave the door open: telemetry ingest.

The “Oh No” moment

In June 2026, Nutrient disclosed a security incident that nearly turned their observability stack into an attack runway. An attacker:

  1. Found a public Sentry DSN in the JavaScript bundle.
  2. Submitted a fake bug report containing a malicious “runbook.”
  3. Buried an npx command designed to exfiltrate secrets.
  4. Relied on an AI coding agent that read the Sentry feed as part of automated incident response.

No credentials. No repo access. Just a static public write endpoint — and an agent that treated attacker-controlled text as instructions.

That is the pattern this series exists to kill. Soft controls (“the model should be careful”) are not a trust boundary. Hard gates are.

For the long-form version of Nutrient plus the full open-source LGTMP replacement stack, see Why Serverless Isn’t a Mistake…. This essay extracts the ingest lesson and turns it into the first control of a hardened agentic deployment.

The problem: static DSNs are write-only trust

A classic DSN looks harmless:

https://[email protected]/789

It is intentionally public. Browser SDKs cannot hold server secrets, so vendors made ingest write-only and accepted that anyone who can read your bundle can also write events.

For years, the blast radius was mostly spam or reconnaissance. Agents change the economics:

  • Observability feeds become instruction channels.
  • Fake issues, fake logs, and fake traces become prompt-injection payloads.
  • “Write-only” stops meaning “low risk” once something autonomous reads and acts.

The architectural failure is not “Sentry exists.” Sentry behaved as documented. The failure is treating unauthenticated, attacker-writable telemetry as trusted input for tools that can execute.

Rule for this series: logs are not instructions. Ingest that anyone can poison is not a side channel — it is a primary attack surface.

ClawQL context: why ingest is Step Zero

ClawQL agents (local, edge, or gateway-backed) routinely:

  • Read issues, traces, and logs while diagnosing failures.
  • Call tools that can change real systems.
  • Carry memory across sessions.

That combination means a poisoned observability event is not a dashboard inconvenience. It is a potential tool-invocation precursor.

Before you argue about sandboxes, eBPF kill-switches, or prompt scrubbing (later posts in this series), close the cheap path: nobody outside your auth boundary should be able to write into the feeds your agents read.

Companion implementation material lives in DevSecOps-boilerplate. Canonical module references:

The infrastructure fix: ephemeral JWT gate

Replace the permanent writable URL with a token-gated edge proxy.

Old flow vs new flow

Flow Path Who can write
Legacy Client → Collector (static DSN) Anyone who scrapes the bundle
Hardened Client → Auth → short-lived JWT → Worker gate → private collector Only holders of a live, scoped JWT

The Worker (Cloudflare or equivalent) is not “your new Sentry.” It is a zero-trust chokepoint: validate, rate-limit, optionally sanitize, then forward — or drop.

JWT shape

Issue short-lived tokens from a backend that already knows the user/session. Typical claims:

{
  "sub": "session_abc123",
  "project": "frontend-prod",
  "origin": "https://app.example.com",
  "scope": ["telemetry:write"],
  "iat": 1717600000,
  "exp": 1717603600
}

Anyone can decode the payload. Nobody can forge a valid signature without your private signing key. Expiry is the control DSN never had: compromise windows shrink from “until we rotate the global secret” to “until this token dies.”

Recommended defaults for browser RUM / SDK ingest:

  • TTL 5–60 minutes
  • Bind origin / project claims and enforce them in the Worker
  • Rotate signing keys on a schedule; keep verify keys overlapping during rollout
  • Prefer asymmetric verify in the Worker so the edge never holds the minting key

Request path

  1. Page/session start: backend mints a JWT after authenticating the session.
  2. SDK init: point Faro / OTel / custom telemetry at the Worker URL; attach Authorization: Bearer <jwt> (or an equivalent header your gate expects).
  3. Capture: events hit the Worker, not Alloy/Sentry/OTLP directly.
  4. Validate: signature, exp, claims, content-type, body size, rate limits.
  5. Forward or drop: valid traffic goes to a private collector endpoint; failures return a boring 204 with no error detail attackers can iterate on.

Sketch for the gate (TypeScript-shaped, abbreviated):

export default {
  async fetch(req: Request, env: Env): Promise<Response> {
    if (req.method !== 'POST') return new Response(null, { status: 204 });

    const token = bearer(req);
    if (!token) return new Response(null, { status: 204 });

    let claims: Claims;
    try {
      claims = await verifyJwt(token, env.JWT_PUBLIC_JWKS);
    } catch {
      return new Response(null, { status: 204 });
    }

    if (claims.exp * 1000 < Date.now()) return new Response(null, { status: 204 });
    if (!allowOrigin(req.headers.get('Origin'), claims.origin)) {
      return new Response(null, { status: 204 });
    }
    if (!(await rateLimit(env, claims.sub))) return new Response(null, { status: 204 });

    const body = await req.arrayBuffer();
    if (body.byteLength > env.MAX_BODY_BYTES) return new Response(null, { status: 204 });

    // Optional: strip known injection markers before storage
    const cleaned = scrubControlSequences(body);

    await fetch(env.PRIVATE_COLLECTOR_URL, {
      method: 'POST',
      headers: {
        'content-type': req.headers.get('content-type') ?? 'application/json',
        'x-project': claims.project,
        'x-session': claims.sub,
      },
      body: cleaned,
    });

    return new Response(null, { status: 204 });
  },
};

Sanitization at the gate

Closing unauthenticated write is necessary, not sufficient. Payloads that clear auth can still carry control text later modules will scrub deeper. At ingest, keep it cheap and deterministic:

  • Reject unexpected content types and oversized bodies.
  • Strip common instruction-marker patterns from string fields destined for agent-readable stores.
  • Prefer fail closed to the collector rather than “best effort keep the event.”

Deeper dual-model scrubbing and ATR/Panguard enforcement come later in the series. Here the job is: no anonymous poison into the pipe.

Architecture pattern: Ephemeral Proxy

Name the pattern so the rest of the series can point at it.

Ephemeral Proxy = mint short-lived, scoped credentials → validate at an edge gate → forward only into private infrastructure.

Properties that matter:

  • No ambient write trust. Knowing the URL is not enough.
  • Scoped claims. A frontend token should not grant agent-admin telemetry scopes.
  • Boring failures. Do not teach attackers your validation order.
  • Composable. The same gate idea applies to OTLP, Faro, custom webhooks, and agent-side diagnostic uploads.

This is also why serverless is the right shape for the gate despite broader “serverless was a mistake” discourse: the gate is ephemeral, globally distributed, and paid per invocation — not a stateful control plane.

What this covers — and what it does not

Threat Outcome with Ephemeral Proxy
Stranger scraped a DSN and injects fake issues Blocked — no valid JWT
Stolen long-lived ingest key reused for months Dramatically reduced — keys are short-lived session tickets
Malicious logged-in user copies their own JWT Still possible until expiry — detect via residual signals
Prompt injection via a legitimate but hostile document the agent opens Not solved here — later posts

Residual authenticated abuse needs visibility: unmatched client errors vs server traces, volume spikes, and agent tool-use anomalies (Parts 8–10). Part 1 removes the free anonymous runway.

Safety check

You are “trusted enough” on ingest when all of the following are true:

  • Browser and public clients cannot reach Alloy / Sentry / OTLP without a live JWT.
  • Minting requires an authenticated session (or equivalent machine identity for non-browser agents).
  • Worker verifies signature, expiry, and binding claims on every write.
  • Private collector endpoints are network-private (VPC, tunnel, or mTLS) — not “security through an obscure hostname.”
  • Agents that read telemetry assume the feed can still contain hostile content from authenticated writers — ingest hardening is layer one, not the whole stack.

What’s next

Part 2 moves from “how telemetry gets in” to “how agents hold secrets at all”: Secret Rotation Patterns for Local/Edge Agents — bootstrap tickets, Vault/Cloudflare exchange, and why long-lived .env values do not belong on a high-privilege agent host.

About the author

Daniel Smith builds ClawQL, an agent operating system for token-efficient discovery and execution over APIs — with observability, hardened tool boundaries, and production routing for LLM workloads. He writes here about the systems problems behind shipping agents.