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:
- Found a public Sentry DSN in the JavaScript bundle.
- Submitted a fake bug report containing a malicious “runbook.”
- Buried an
npxcommand designed to exfiltrate secrets. - 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/projectclaims 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
- Page/session start: backend mints a JWT after authenticating the session.
- SDK init: point Faro / OTel / custom telemetry at the Worker URL; attach
Authorization: Bearer <jwt>(or an equivalent header your gate expects). - Capture: events hit the Worker, not Alloy/Sentry/OTLP directly.
- Validate: signature,
exp, claims, content-type, body size, rate limits. - Forward or drop: valid traffic goes to a private collector endpoint; failures return a boring
204with 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.