When implementing OpenTelemetry (OTel) in a modern web application, the frontend is often the first place developers look to enrich their tracing data. It is incredibly convenient to grab the current user’s ID, role, and email directly from your browser’s Redux store or React context and attach them to outbound OTel spans.
However, this convenience introduces a severe architectural flaw: it implicitly trusts the client.
In observability and application security, relying on frontend state to populate security-critical Application Performance Monitoring (APM) audit logs leaves your pipeline wide open to manipulation. Here is why enriching OTel spans directly from the client is a vulnerability, and how to properly secure your tracing architecture.
The Danger of the “Convenient” Client
Imagine your frontend otelTracing.ts file automatically pulls user.role from Redux and adds it to every span sent to your observability platform.
To a malicious actor, local application data is trivial to manipulate. By altering their local Redux state using browser dev tools, an attacker can spoof their identity or escalate their perceived privileges within your logs. If they trigger administrative endpoints, the backend APM data store will ingest that spoofed identity data.
When your security team later audits the logs to investigate an incident, they will see an “admin” performing the actions, not the actual user. Your audit logs become unreliable, masking the true origin of the breach.
The Fix: Server-Side Single Source of Truth
To establish a trusted actor context—especially for administrative paths—you must extract identity data from a verified, server-side session or token. The server must be the single source of truth for who is interacting with the system.
Instead of trusting the payload from the client, intercept the request during its backend lifecycle after your authentication middleware has verified the user’s JSON Web Token (JWT) or session cookie.
Here is how you enforce this in a standard Node.js (Express) environment:
const { trace } = require('@opentelemetry/api');
// This middleware runs AFTER your auth verification (e.g., Passport, express-jwt)
function secureOtelUserContext(req, res, next) {
// req.user is securely populated ONLY if the server validated the JWT
if (req.user) {
// Retrieve the active span created by the OTel HTTP instrumentation
const span = trace.getActiveSpan();
if (span) {
// Inject standard semantic conventions safely
span.setAttribute('enduser.id', req.user.id);
span.setAttribute('enduser.role', req.user.role);
}
}
next();
}
app.use(verifyJwtMiddleware);
app.use(secureOtelUserContext);By extracting the claims directly from the verified token, it becomes impossible for a user to spoof their identity in your tracing backend, regardless of what their browser sends.
The Role of traceparent Headers
If your frontend is passing the W3C traceparent header to the backend, it is crucial to understand its actual purpose.
The traceparent header (e.g., 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01) exists solely to link the frontend client span to the backend server span. It tells the server to stitch the distributed trace together. It does not validate identity.
Your backend’s auto-instrumentation will read this header automatically. Your job is to allow that trace linkage to happen, while explicitly ignoring any enduser attributes sent in the client’s payload.

