Security Considerations for Cross-Service Baggage in OpenTelemetry

Security Considerations for Cross-Service Baggage in OpenTelemetry

What can go wrong when small notes travel with every request through every service, and the practical defenses that keep them safe, cheap, and trustworthy.

Imagine a parcel that passes through twenty pairs of hands on its way to a customer. Now imagine that anyone, at any step, can stick a small note on the parcel, and that every later handler reads the notes and sometimes acts on them. Most of the time this is handy: “fragile”, “deliver before noon”, “customer is on the premium plan”. But what if a stranger stuck a note on the parcel before it ever entered your warehouse? What if one note said “customer’s home address and phone number”, and the parcel was then handed to an outside courier who photographs every label?

This is the situation with baggage in OpenTelemetry. Baggage is a way of attaching small pieces of information to a request so that every service it touches can read them. It is useful, and it is also a quiet source of security trouble, because it travels automatically, it is not checked for honesty, and it is easy to forget that it is there. This guide explains, step by step and in plain words, what the risks are and how to build a system where baggage stays helpful and harmless.

1What Baggage Is, and Why Security Matters

Before talking about danger, we need to know exactly what we are protecting.

The simple definition

Baggage is a set of name and value pairs that travels along with a request as it moves from service to service. For example, the pair customer.tier with the value premium can be set once at the front door. Every service that handles the request afterward can read it, without having to ask a database or call another service. Baggage is part of OpenTelemetry’s idea of context, the bundle of facts that follows a request through a system, which also carries the trace identifiers that link steps together.

Analogy

Think of the paper tag tied to a suitcase at an airport. The tag is attached at check-in, and every worker along the way, from the loader to the transfer desk, can read it. Baggage in OpenTelemetry works the same way: a tag tied on at the start and read by everyone along the route. And just like a real suitcase tag, anyone who sees the suitcase can read the tag.

Why teams like it

Baggage solves a real problem. Suppose a request starts at a web server and passes through ten services. Only the first knows which experiment group the user belongs to. Without baggage, each of the other nine would need to look it up, or every developer would need to pass the value through every function call and every message by hand. With baggage, the first service sets it once and the framework carries it along. Common uses include experiment groups, region names, request priorities, plan tiers, and flags that help with debugging.

Why it needs special care

Four properties make baggage riskier than most other request data.

Property 1

It travels automatically

Once set, it is attached to outgoing calls by the instrumentation, often without the developer thinking about it again.

Property 2

It goes everywhere

Every service, proxy, and sometimes every outside partner on the route receives it.

Property 3

It is not checked

There is no built-in way to prove that a value is honest or that it was not changed on the way.

Property 4

It is plain text

The pairs are written in readable form in headers or message fields. Secure connections protect the wire, but not the places where headers get logged or displayed.

0
built-in integrity checks on baggage values
0
built-in encryption of baggage values
Every hop
where an attached entry can be read by default
i
Tip

A good mental rule to keep through this whole guide: baggage is a postcard, not a sealed letter. Anyone who handles it can read it, anyone who writes on it can change it, and it should carry only things you would be comfortable having read aloud.

2How Baggage Travels

To defend a road, you must know where it goes. Here is the route baggage takes.

Inside one service: the context

Within a single running program, baggage lives in the context, a small container that is passed along with the current piece of work. Code can read entries from it, and can create a new version with extra entries. The baggage itself is never changed in place. Adding an entry produces a fresh copy, so earlier holders of the old copy are not surprised.

Between services: the propagator

When a service makes a call to another service, a component called a propagator writes the baggage into the outgoing message. For web calls, it appears as a header named baggage. The header is a list of entries separated by commas, each written as a name, an equals sign, and a value, with optional extra properties after a semicolon. Values are percent-encoded so that special characters survive the trip. On the receiving side, a matching propagator reads the header and rebuilds the baggage in the new service’s context.

Propagators are usually switched on for you. In many OpenTelemetry setups, the default configuration propagates both the trace context and baggage. Instrumentation libraries for web servers, web clients, remote procedure calls, and message systems call the propagators automatically. So a developer can set an entry in one place and, without writing any more code, see it appear in dozens of services.

flowchart LR
  CL["Client"] -->|"baggage header"| GW["Edge gateway"]
  GW -->|"baggage header"| A["Service A"]
  A -->|"baggage header"| B["Service B"]
  A -->|"baggage header"| C["Service C"]
  A -->|"message attributes"| Q["Message queue"]
  Q --> W["Background worker"]
  B -->|"baggage header"| EXT["Outside partner API"]
    
Figure 1: Baggage follows the request to every service, to message queues, and even to outside partners, unless something stops it.

Figure 1 shows how far the tags can travel. The client sends a request to the edge gateway. The gateway forwards it to Service A, which calls B and C, puts a message on a queue for a background worker, and finally B contacts an outside partner. At every arrow, the baggage may be written out again. Notice that two of those arrows, to the queue and to the outside partner, leave the part of the system you fully control.

Other carriers: remote procedure calls and messages

Web headers are only one carrier. Remote procedure call systems carry baggage in call metadata, which works much like headers. Message systems carry it in message headers or attributes, so it is stored with the message. Some environments, such as serverless functions or batch schedulers, pass context through event fields or environment settings. The names and shapes differ, but the idea is the same everywhere: a small set of text entries rides alongside the real data, written by one side and read by the other. Whenever you meet a new carrier, ask the same three questions. Who can write to it? Who can read it? How long does it stay?

Limits on size

The standard that defines the baggage header sets minimum limits that every implementation should support, in the range of about sixty-four entries, a few thousand bytes for each entry, and roughly eight thousand bytes in total. Implementations may drop entries that go over their limits. Web servers and proxies also have their own caps on the total size of headers, commonly somewhere between eight and sixteen thousand bytes, though this varies. Check the current specification and your own infrastructure for exact numbers. These limits matter for security, as Chapter 7 shows.

!
Watch out

Because propagation is automatic, the person who adds an entry is rarely the person who knows everywhere it will go. Any security review of baggage must look at the whole route, not just the service that sets the value.

3Baggage Is Not Telemetry, and Where It Ends Up

One of the most common confusions is thinking that baggage automatically becomes part of traces, logs, and metrics. It does not, but it often gets there anyway.

On its own, baggage is only context. It is not written onto spans, log lines, or metric points. This is good news, because it means that putting an entry in baggage does not by itself send the value to your tracing vendor. But there are several ways it ends up in places you may not expect.

PlaceHow baggage gets thereWho can then see it
Network headers between servicesPropagators write it on every outgoing callEvery service, proxy, and anyone who can read headers
Proxy and gateway access logsLogs configured to record all headers or the baggage headerOperators, support staff, log analysis tools
Span attributesA baggage span processor, or custom code, copies entries onto spansEveryone with access to the tracing system
Log linesLogging libraries that add context fields automaticallyEveryone with access to the logs
Metric labelsCustom code or views that use baggage as a labelEveryone with access to the metrics, plus the bill
Message queues and stored messagesMessaging instrumentation copies baggage into message headersAnyone who can read or replay the queue, for as long as messages stay
Outside partnersOutgoing calls to partner services carry the headerThe partner and everything the partner does with its logs
Browser toolsBrowser instrumentation that attaches baggage to requestsThe user of the browser, in developer tools

Two surfaces to protect

It helps to picture two surfaces. The wire surface is everything that moves between systems: headers, message fields, and the logs of devices in between. The telemetry surface is everything that is stored for later: spans, logs, and metrics that received a copy of a baggage value. A good defense watches both. Closing one door while leaving the other open gives only the feeling of safety.

Analogy

A letter can be read while it is in the post, and it can be read again after it is filed in a cabinet. Protecting the envelope is not enough if the filing cabinet is open to the whole office. Baggage has both the post and the cabinet.

i
Tip

Make a short list for your system of every place that reads baggage and every place that copies it. If nobody can produce that list, you have found your first task.

4A Plain-Words Threat Model

A threat model is simply an honest answer to three questions: what are we protecting, who might hurt it, and how?

What we are protecting

  • Private information about customers, staff, and the company itself.
  • Correct decisions, such as who may do what and how much each customer pays.
  • Availability, meaning that systems stay up and fast.
  • Budget, since telemetry that grows without limit costs money.
  • Trust in the data, so dashboards and investigations are not misled.

Who might hurt it

Actor

Outside attacker

Anyone on the internet who can send a request, and therefore a baggage header.

Actor

Curious or careless insider

A developer or operator who adds sensitive values, or reads values they should not.

Actor

Compromised service

A service that an attacker has taken over and that can now change the baggage for everything downstream.

Actor

Partner or vendor

An outside party that receives your requests and may store or mishandle what they carry.

Actor

Honest mistake

No villain at all, just a shared library with a setting that nobody understood.

The trust zones

flowchart LR
  subgraph U["Untrusted zone"]
    C["Browsers and apps"]
    P["Partner systems"]
  end
  subgraph E["Edge zone"]
    G["Edge gateway"]
  end
  subgraph I["Internal zone"]
    S1["Service A"]
    S2["Service B"]
    Q["Message queue"]
  end
  subgraph X["Outside vendors"]
    V["Payment and analytics APIs"]
  end
  C -->|"baggage may be forged"| G
  P -->|"baggage may be forged"| G
  G -->|"cleaned baggage"| S1
  S1 --> S2
  S1 --> Q
  S2 -->|"baggage may leak"| V
    
Figure 2: Baggage crosses trust boundaries in both directions. Entering from outside, it may be forged. Leaving to outside, it may leak.

The picture in Figure 2 shows the most important idea in this guide. There are boundaries, and every boundary needs a guard. Incoming baggage from outside is untrusted input. Outgoing baggage to outside is a possible information leak. Inside, even trusted services can be wrong or compromised, so a little caution is still wise.

Three short attack stories

The free upgrade. A curious customer opens the browser’s developer tools and sees that a request includes a baggage entry about their plan. They change the value from “basic” to “premium” and resend the request. A service downstream sees “premium” and unlocks a feature. No password was stolen, and no bug in the usual sense was found. The system simply believed a note.

The loud neighbor. An attacker discovers that a baggage entry called “debug” makes services write detailed logs. They send it on every request. Log volume grows tenfold, storage fills, and the logging system slows down. Meanwhile, the extra detail also writes values from other customers’ requests into logs that a wider group can read.

The quiet leak. No attacker at all. A team adds a customer reference to baggage for tracing. A year later, a new feature calls an analytics vendor through a library that uses the same web client. For months the vendor receives the reference with every call. Nobody notices until an audit asks which outside companies hold customer references.

These stories share a pattern: the harm came from ordinary features working as designed, in places the designers had not pictured.

Six kinds of trouble in plain words

Kind of troubleWhat it meansBaggage example
PretendingSomeone claims to be something they are notA client sends a baggage entry saying it is a premium customer
TamperingData is changed on its wayA compromised service rewrites the tenant entry
DenyingActions cannot be traced to a personBaggage used as the only record of who did something
LeakingPrivate data reaches the wrong eyesAn email address sent to a partner API
OverloadingSystems slow or fail under too much dataAn enormous baggage header repeated on every internal call
OverreachingSomeone gains more power than they should haveA baggage flag that switches on an admin path

The next chapters take these one at a time, and add a few that are special to baggage, such as injection into other systems, noisy telemetry, and data that leaks between requests.

5Threat: Leakage of Sensitive Information

The most common baggage problem is not a clever attack. It is a helpful developer putting the wrong thing in the bag.

A made-up story

Imagine a company called Harbor Foods, a name invented for this guide. A support engineer wants to see the email address of the affected customer in every service’s logs, to speed up investigations. So the login service sets a baggage entry user.email. A week later, the checkout service calls an outside shipping partner. The instrumentation attaches the baggage header, as it does to every outgoing call. The partner’s servers now receive the customer’s email address with every request, whether or not the partner has any right to it, and the partner’s access logs keep it for ninety days. Nobody at Harbor Foods did anything on purpose. The leak was the sum of three reasonable decisions.

The ways leaks happen

  • Outbound calls to third parties. Payment gateways, mapping services, analytics tools, and shipping partners are all just “downstream calls” to the instrumentation.
  • Infrastructure logs. Load balancers, gateways, and web servers can record headers. Anything in a header may end up in a log that many people can search.
  • Telemetry copies. When baggage is copied onto spans and log lines, the value is stored in the observability system, often with broad access and long retention.
  • Error pages and debugging tools. Debug output and error reports sometimes print all headers.
  • Browsers. If a browser application attaches baggage to requests, then the user, and anyone with access to their device, can read it. Browser applications also need the server to explicitly allow the baggage header across origins, and that permission should not be given to every site.
Analogy

Writing a private fact in baggage is like writing it on a postcard that will be handed from person to person around a whole city. Even if every person is honest, the card will be seen by many, and one of them may photograph it or leave it on a bus.

Giving support teams what they need, safely

The usual reason people put private values in baggage is a good one: they want to find a customer’s requests quickly. There are safer ways to give support that power. Carry a short, random request reference that identifies the request but says nothing about the person. Store the link between that reference and the customer in a protected system, with access limited to support staff and a record of who looked. Support then searches by reference, and an engineer who reads a trace or a log sees only a meaningless code. If the reference leaks, little is lost, because it cannot be used without the protected link.

What counts as sensitive

Kind of valueExamplesShould it be in baggage?
SecretsPasswords, access tokens, session cookies, API keysNever
Direct personal dataNames, email addresses, phone numbers, home addresses, government IDsNever
Indirect personal dataAccount numbers, device IDs, precise locationsAvoid. If needed, use an opaque, short-lived stand-in
Business-sensitive factsPrices, discounts, internal risk scores, fraud flagsAvoid. Look them up inside where needed
Internal structureServer names, internal URLs, database namesAvoid, because it helps attackers map your system
Harmless hintsRegion code, experiment group, coarse plan tier, request priorityYes, within size limits
!
Watch out

Even “harmless” values can be sensitive in combination. A region, a plan tier, and an experiment group may be harmless alone but together point to a small, identifiable group of people. Keep the list short and coarse.

6Threat: Forged Baggage and Misplaced Trust

If anyone can write on the tag, nobody should believe the tag when something important is at stake.

The core problem

The baggage header arrives from the network. Whoever sent the request decides what it says. There is no signature, no proof of origin, and no check that the entry matches reality. A service that reads customer.tier=premium has learned only that somebody claimed the customer is premium. If that somebody was a browser or a script, the claim is worthless.

Attacks that follow

  • Privilege claims. An attacker sends an entry such as an admin flag or a tenant name, hoping a service will treat it as true.
  • Free upgrades. An entry that changes pricing, limits, or features is a gift to anyone who guesses its name.
  • Cost and load tricks. A “debug” or “verbose” flag that switches on expensive behavior lets an outsider make your systems work harder, or write more logs, at will.
  • Skipping safeguards. A flag like “skip cache” or “skip checks”, added for convenience in testing, may still be honored in production.
  • Tampering from inside. A compromised internal service can rewrite entries for everyone downstream, and the downstream services cannot tell.
  • Guessable names. Attackers can try common names, since baggage names are visible in documentation, code, and traffic.
Analogy

Imagine a nightclub where the doorperson lets in anyone who has a sticker saying “VIP” on their coat. Anyone can buy stickers at the corner shop. The sticker is useful for the staff to find regular guests quickly, but it must never be the only thing that opens the door.

The rule: hints, not decisions

The safe way to think is that baggage can carry hints, never decisions. A hint helps a service do something a little smarter, such as choosing which experiment text to show. A decision is something that must be right for the system to be secure or correct, such as who is allowed to see which data, or how much to charge. Decisions must be made from facts the service can verify: an authenticated identity, a verified token, or a record in a trusted database.

Use of the valueSafe in baggage?Better source
Choose experiment wordingYes, a wrong value only changes a labelBaggage is fine
Add a tag to dashboardsYes, if sanitizedBaggage is fine, with an allow list
Decide who may see dataNoVerified identity token checked by the service
Select the customer’s tenantNoTenant taken from the authenticated session at the edge
Set prices or limitsNoA trusted lookup by authenticated ID
Enable debug or skip safeguardsNoOperator controls with their own authentication

Stale and replayed claims

Even an honest value can become false. A request captured today can be replayed tomorrow by anyone who saw it, carrying yesterday’s entries. A stored message may carry a plan tier that has changed. Because baggage has no timestamp and no expiry, a service cannot tell how old a claim is. Another reason to keep decisions out of baggage: the world changes, and the tag does not.

Making honest values more believable

The edge gateway should remove any baggage coming from untrusted clients and, where needed, set fresh values itself from facts it has verified. That way, inside the system, an entry that was set by the gateway can be trusted somewhat more than an entry that arrived from outside, because the outside entries were never allowed through. Where a value must be trusted by several services, the safest design is not baggage at all, but a signed token that carries the claim and that every service verifies.

7Threat: Size and Amplification

A single large header is a nuisance. The same header repeated across a hundred internal calls is an attack.

Why baggage can be used as a megaphone

An incoming request becomes many outgoing requests as it passes through a system. A page that looks simple to a user may cause dozens of internal calls. Each internal call carries the baggage header. If an attacker sends a large baggage header once, your own systems will copy it onto every call, multiplying its size across your internal network, your proxies, and your logs. The attacker spends a little. You spend a lot.

flowchart TD
  ATK["One request with an 8 KB baggage header"] --> GW["Edge gateway"]
  GW --> A["Service A"]
  A --> L1["40 calls, each carrying 8 KB"]
  L1 --> L2["Each call causes 3 more calls, 120 in total, each carrying 8 KB"]
    
Figure 3: One large header turns into more than a megabyte of internal traffic when copied onto every downstream call.

A worked example

StepCalculationResult
First level calls40 calls times 8 KB320 KB
Second level calls40 calls times 3 follow-up calls is 120 calls, times 8 KB960 KB
Extra traffic per attacker request320 KB plus 960 KBAbout 1.25 MB
Attacker rate200 requests per second from a small botnet200 per second
Extra internal traffic1.25 MB times 200About 250 MB per second, around 2 gigabits per second

The numbers are invented for teaching, but the shape is real. A small stream of cheap requests creates a heavy stream of internal traffic, plus extra work for parsing headers, writing logs, and, if the value is copied to telemetry, storing it. The attacker needs no special skill.

Trim, reject, or pass through?

When the edge meets an oversized or odd header, it has three choices. It can trim it, keeping only the allowed entries that fit. It can reject the whole request with an error. Or it can pass it through unchanged. Passing through is the unsafe choice and should be used almost never. Trimming is friendly to real users, because an honest client with one extra entry still gets served. Rejecting is better for clearly malformed or hostile input, because it gives the sender a clear signal and saves work. A common mix is to trim ordinary excess and reject headers that cannot be parsed or that exceed a much higher hard limit. Whatever you choose, count each outcome.

Other size problems

  • Rejections. If the total headers grow past a proxy’s cap, requests may fail with errors. An oversized baggage header can therefore break real users’ requests, not just the attacker’s.
  • Silent dropping. Implementations may silently drop entries above their limits, so important entries could disappear without any message.
  • Memory. Many small entries cost parsing time and memory at every hop.
  • Growth by accident. Even without attackers, teams may add entries over years until the header is big and nobody knows why.

The defense: strict, small limits at the edge

Choose your own limits, much smaller than the standard’s maximums, and enforce them where requests enter. A sample policy might allow no more than ten entries, no more than sixty-four characters per name and value, and no more than five hundred bytes in total. Anything over the limit is either trimmed to the allowed entries or removed whole. Then internal services enforce the same limits again when they add entries, so that a growing system cannot creep upward. With the policy above, the same attack from the worked example would add at most five hundred bytes per call instead of eight thousand, a sixteen-fold reduction, and would be easy to notice in monitoring.

i
Tip

Treat the baggage budget like a luggage allowance on a flight. A small, strict allowance forces everyone to think about what is truly worth carrying.

8Threat: Injection Into Downstream Systems

A baggage value is text that someone else chose. When your code pastes that text into something else, the writer gets a say in what happens.

Analogy

Picture a restaurant where the waiter copies each guest’s note straight into the recipe card that the cook follows. If a guest writes “add salt”, fine. If a guest writes “and also give everyone at the next table a free dinner”, the cook may follow that too. Pasting untrusted text into instructions is how injection attacks work, and baggage values are untrusted text.

Where baggage values often get pasted

DestinationWhat an attacker triesThe defense
Log linesIncludes line breaks to create fake log entries that look real, or hides evidenceRemove control characters and line breaks, and log values in a structured field, not pasted into a sentence
Database queriesIncludes query fragments to read or change dataAlways use parameterized queries and never build queries by joining text
Commands and scriptsIncludes shell symbols to run extra commandsNever place baggage values in commands. If unavoidable, pass them as separate, escaped arguments
Web addresses and redirectsPoints users or services to an attacker’s siteAllow only known destinations, never build addresses from raw values
Outgoing headersIncludes line breaks to add or change other headersReject values with control characters, and use libraries that encode safely
Web pages and admin toolsIncludes script code that runs in an operator’s browserEscape values when displaying them, in dashboards, trace viewers, and error pages
File pathsIncludes path tricks to reach other filesNever build file paths from values, or restrict them to a fixed list
Templates and expressionsIncludes expression syntax that the engine evaluatesPass values as data, never as part of the template

Decode first, then validate

Baggage values are percent-encoded on the wire. A harmless-looking string such as “%0A” decodes into a line break. This means that a check done on the encoded text may miss what the decoded text contains. The right order is to decode once, then validate the decoded text against a strict pattern, and never decode twice.

Log forging in plain words

Logs are often read as a story, one line at a time. If a baggage value contains a line break followed by text that looks like a log line, the log now contains a line that nobody’s code wrote. An attacker can use this to invent events, such as a successful login, or to bury real events under noise. The remedy is simple. Write values into structured fields, so that the logging system stores them as data, and strip control characters before they get anywhere near a log.

Prefer small, fixed shapes

The safest values are ones that can be checked against a short list: “region equals one of five known codes”, “tier equals one of four known names”. Convert the incoming text into a fixed type, such as an enumeration, at the moment of reading. If the text does not match, treat the value as missing. This turns free text, which is dangerous, into a choice from a menu, which is not.

i
Tip

Give your teams one small shared helper for reading baggage. The helper returns typed, validated values and refuses everything else. When all reads go through it, a whole class of mistakes disappears at once.

9Threat: Telemetry Poisoning and Cardinality Explosions

Copying baggage onto spans, logs, and metrics is useful, and it hands an outsider the pen that writes in your observability tools.

Why teams copy baggage into telemetry

Baggage is a convenient way to label telemetry with business facts. If every span carries the plan tier, then engineers can ask “are premium customers slower?” Many SDKs offer a baggage span processor that copies entries onto every span. The idea is good. The risk is in which entries, and who chose their values.

Cardinality: the explosion of unique values

Cardinality means the number of different values that a label can take. Metric systems create a separate time series for every combination of label values. A label with five possible values is cheap. A label that an outsider can set to anything creates a new series for every request.

Take an example. Say five services each produce twenty metrics. If baggage copies a label that could take one million different values, the number of series could grow to five times twenty times one million, or one hundred million series. No metrics backend is built for that. Costs jump, queries slow, and the system may refuse more data or fall over. An attacker who knows that a label is copied can do this on purpose by sending random values. The same thing can also happen by accident when a developer adds a user ID to baggage.

Finding the attribute that is exploding

When costs or slowness appear, the cause is usually a single attribute. Ask your telemetry system for the number of distinct values per attribute over the last hour, sorted from highest to lowest. An attribute with thousands of values per hour that you expected to have five is the culprit. Trace it back to its source: a baggage copy rule, a library setting, or a new code change. Then remove the rule and add a limit so the problem cannot return quietly.

Other kinds of poisoning

  • Skewed experiments. If an experiment group comes from baggage, an outsider can send fake groups, and your analysis counts them.
  • Wrong cost reports. If cost labels come from baggage, a service can claim another team’s name and shift the bill.
  • Misleading dashboards. Fake values can hide real problems, or create false alarms.
  • Indexing load. Tracing backends that index attributes can be slowed by endless new attribute values or huge ones.
  • Hidden messages. Long values can smuggle text, such as links, into tools that display attributes to staff.

Defenses

DefenseHow it helps
Copy only allow-listed keysNew or unexpected names are never copied, whoever invented them
Map values to a fixed setUnknown values become “other”, so cardinality stays bounded
Never use baggage as a metric label unless it has a small fixed set of valuesPrevents series explosions
Hash or bucket identifiers for traces onlyAllows correlation without storing raw values, and keeps them out of metrics
Set attribute count and length limits in the SDKCaps the harm from huge values
Remove unknown attributes in the CollectorA second line of defense if an application copies too much
Watch the number of distinct valuesEarly warning when something starts to grow
!
Watch out

The most dangerous setting is “copy all baggage onto every span”. It is easy to turn on and gives an outsider a way to put free text into every trace you store. Use a named list of allowed keys instead.

10Threat: Context Bleed Between Requests and Tenants

Sometimes the danger is not an attacker at all. It is a note that was left behind and read by the wrong person.

Analogy

In a hotel, the housekeeper must clear the room completely between guests. If the previous guest’s sticky notes stay on the desk, the next guest reads private details. Context bleed is a housekeeping failure: the previous request’s baggage is still in the room when the next request arrives.

How the context follows the work

The current context is usually tied to the current thread or task. Frameworks switch the context on when a request begins and off when it ends. That works well as long as each piece of work runs entirely on its own. Trouble starts when work jumps between threads, is delayed, or is reused.

Common causes

  • Thread pools and reused workers. If a worker picks up a task and the context from an earlier task is still attached, the new task runs with the wrong baggage.
  • Scopes that never close. Code that makes a context current but forgets to detach it in all paths, especially on errors, leaves it behind.
  • Async callbacks and futures. A callback may run later, in another thread, with either no context or a stale one, depending on how the framework carries it.
  • Global or shared variables. A context stored in a shared place, such as a cache or a singleton, is visible to every request.
  • Background jobs started from requests. A job that inherits the context of the request that started it keeps that baggage for its whole life, which may be hours.
  • Connection reuse. Rarely, code stores request details on a long-lived connection object.

What it looks like

A support agent searching logs for customer A sees log lines labeled with customer B’s tenant. A background job for tenant A writes a record that is tagged for tenant B. A premium flag from a previous request switches on features for the next customer. These bugs are hard to find because they appear only under load, when threads are busy and reused.

Defenses

  • Always close scopes. Use the language’s safe pattern so that the scope is released even when errors occur.
  • Use context-aware executors. Wrap thread pools and schedulers with the helpers that carry context explicitly, and test them.
  • Start clean. At the beginning of each incoming request, and at the start of each background job, begin with a fresh context.
  • Keep context out of shared state. Never store baggage or context in global places.
  • Do not use baggage for tenant identity. Tenant isolation should come from verified identity, so that a bleed cannot cause a cross-tenant leak of real data.
  • Test under concurrency. Run tests that fire many parallel requests with different baggage values, and assert that each request only ever sees its own.

11Threat: Queues, Retries, and Data That Lives Too Long

A web call is over in a moment. A message in a queue can sit for days, and baggage sits with it.

flowchart LR
  P["Producer service"] -->|"baggage copied into message headers"| Q["Queue stores messages for days"]
  Q --> C1["Consumer reads soon"]
  Q --> DLQ["Dead letter queue read by operators"]
  Q --> MIR["Mirror to analytics or a partner"]
  Q --> C2["Consumer reads much later with stale values"]
    
Figure 4: When baggage rides on messages, it is stored, retried, mirrored, and read long after the request has ended.

What changes with messages

Messaging instrumentation usually copies the context, including baggage, into the message’s headers or attributes. That turns a short-lived tag into stored data. It is now written to disk, copied to replicas, backed up, and visible to everyone who can read the queue. It may also be moved to places you did not plan.

An example of the slow burn

Suppose an order service puts a message on a queue and a shipping service reads it two days later because of a backlog. The message carries baggage from the original request, including a flag that said “expedited” at the time. The customer cancelled the expedited option an hour later, but the flag lives on. The shipping service honors it and the company pays for faster delivery nobody wanted. Nothing was hacked. The system simply believed an old note.

Specific risks

  • Stale claims. A message created when a customer was on a premium plan may be processed after the plan ended, and the baggage still says premium.
  • Replays and retries. Failed messages are retried and sent to dead-letter queues, where operators or tools may inspect them and see every baggage entry.
  • Mirroring and exports. Messages are often copied to analytics systems, other regions, and partners, taking baggage with them.
  • Restored backups. Restoring data into a test environment may bring production baggage with it.
  • Batches. A consumer that reads fifty messages at once has fifty different baggage sets. Which one applies to the batch? Using any single one is a mistake.
  • Limits. Messaging systems cap how many attributes a message can carry. Baggage may squeeze out other, more important attributes.

Defenses

  • Decide on purpose. By default, do not copy baggage into messages. Allow only specific keys, on specific topics, where there is a clear need.
  • Prefer explicit fields. If the consumer needs a fact, put it in the message body with the same access controls as the rest of the message, and have the consumer re-check anything that matters.
  • Treat stored baggage as old. Never use values from messages to make decisions without re-verification.
  • Use links for batches. When a consumer processes many messages together, record links to each message’s trace, rather than adopting one parent context and its baggage.
  • Keep retention short for queues and dead-letter queues, and restrict who may read them.
  • Scrub before copying data out. When messages go to analytics, partners, or test systems, remove baggage first.

12Privacy and Compliance

Baggage is data about people and businesses. The rules that apply to other data apply to it too.

Privacy laws and company policies are written about personal data, which means any information that can identify a person directly or indirectly. They rarely care which technical mechanism carries the data. If baggage includes a customer number, a device ID, a precise location, or anything that could be linked back to a person, then baggage is carrying personal data, and the usual duties apply. This guide cannot give legal advice, and rules differ by country and industry, so involve your privacy and legal teams. The principles below are the common ground.

PrincipleWhat it meansWhat it means for baggage
MinimizeCollect and share only what is neededCarry no personal data unless truly necessary, and the least detail possible
Limit the purposeUse data only for the reason it was collectedA flag set for experiments should not be copied into marketing logs
Limit the audienceShare only with those who need itNo automatic sharing with partners, strip at the exit
Limit the timeKeep data no longer than neededShort retention in queues, logs, and traces that hold copies
Know where it isKeep an inventory of data flowsA registry of baggage keys and where each one goes
Honor requestsSupport access and deletion requestsIf a person’s data is in telemetry, you must be able to find and remove it
Respect bordersSome data may not leave certain regionsBaggage carried to a service in another region is a transfer

Retention and deletion in practice

People may ask a company to delete the personal data it holds about them. If baggage values have been copied into logs and traces, deletion becomes a search through many systems, each with its own storage and backups. The easy cure is prevention: if no personal data was ever stored there, there is nothing to delete. If some was stored, shorter retention in telemetry stores limits how long the problem lasts, and a registry of where each key was copied tells you where to look.

The simplest path

The easiest way to comply is to keep personal data out of baggage completely. If a service really needs to know something about the user, it can look it up from a trusted store using the verified identity, with all the access control and record keeping that store already has. This keeps baggage in the clear-cut category of “harmless hints”, and keeps your list of personal data flows short and easy to explain.

i
Tip

If someone asks, “Does our tracing system hold personal data?”, you want the answer to be a quick, confident “no, and here is how we make sure”, not a week of searching.

13What May Go Into Baggage

Instead of reacting to every threat one by one, ask four simple questions before any entry is allowed.

flowchart TD
  Q1{"Would it hurt if a stranger read it"} -->|"yes"| N1["Do not put it in baggage"]
  Q1 -->|"no"| Q2{"Will any decision depend on it being true"}
  Q2 -->|"yes"| N2["Use a verified token or a trusted lookup"]
  Q2 -->|"no"| Q3{"Is it small, with a fixed set of values"}
  Q3 -->|"no"| N3["Use a short opaque ID or leave it out"]
  Q3 -->|"yes"| Q4{"Must it reach outside partners"}
  Q4 -->|"yes"| N4["Send it as an explicit field in the partner interface"]
  Q4 -->|"no"| OK["Allowed in baggage. Add it to the registry"]
    
Figure 5: A four-question check that every proposed baggage entry must pass.

Reading the flow

First question: would it hurt if a stranger read it? Secrets, personal data, and internal structure fail at once. Second: will any decision depend on it being true? If yes, baggage is the wrong tool, because it cannot be trusted. Third: is it small and from a fixed set? That keeps size and cardinality under control, and makes validation easy. Fourth: must it reach outside partners? If so, do not rely on automatic header propagation. Send it deliberately, as a documented field that the partner agrees to receive.

The gray areas

Some entries are neither clearly safe nor clearly dangerous. A coarse location such as a country is usually fine, but a city or postcode may identify a small group. A plan tier is fine, but a plan tier plus an exact signup date may point to one person. An experiment cohort is fine, but a cohort name that reveals a product not yet announced is a business secret. When in doubt, move up one level of coarseness, or leave the entry out. It is far easier to add an entry later than to withdraw one that has already spread.

Good and bad entries

Proposed entryVerdictWhy
Region code with five possible valuesAllowedSmall, fixed, harmless
Experiment cohort nameAllowedSmall, fixed set, only a hint
Request priority: low, normal, highAllowed with careMust not let outsiders jump the queue; set at the edge
User email addressRefusedPersonal data, fails the first question
Session tokenRefusedSecret
Tenant ID used to pick the databaseRefusedA decision depends on it; use verified identity
Free text “support note”RefusedUnbounded, injectable, possibly sensitive
Debug mode switchRefusedOutsiders could switch on expensive behavior

14Defense at the Entrance: The Edge Gateway

The cheapest place to stop forged or oversized baggage is the door, before it enters a thousand hallways.

flowchart LR
  NET["Internet"] --> WAF["Web application firewall with header size rules"]
  WAF --> EDGE["Edge gateway: strip, allow list, set trusted values"]
  EDGE --> MESH["Internal services with a validation helper"]
  MESH --> EGR["Egress proxy: remove baggage for outside hosts"]
  EGR --> OUT["Outside partners"]
  MESH --> OBS["Telemetry with allow listed baggage copies"]
  MESH --> MQ["Queues with allow listed baggage only"]
    
Figure 6: Defense in layers. Each boundary has a guard, and each store of copies has an allow list.

What the gateway should do

ActionPlain-words purpose
Strip by defaultRemove the baggage header from every request that comes from an untrusted source. Most real clients have no legitimate reason to send it
Allow-list for known partnersIf a trusted partner must send some entries, accept only the named keys, from that partner’s verified identity
Limit size and countCap the number of entries and bytes, much lower than the standard’s maximum
Validate formReject malformed headers, bad characters, and values that do not match the expected pattern
Set trusted valuesAdd entries yourself from verified facts, such as the region or the plan tier looked up from the authenticated identity
Count and reportKeep counters of what was stripped or rejected, without recording the values themselves
Handle browsers carefullyDo not allow arbitrary websites to send the baggage header across origins, and consider whether your own web app needs to send it at all

Where to do it

The same job can be done by a reverse proxy, an API gateway, a service mesh ingress, a content delivery network’s edge functions, or a small piece of gateway code. Most of these can remove or overwrite request headers by rule. Choose the layer that already sits in front of all your traffic, so there is no side door. If different entry points exist, such as a public gateway and a partner gateway, each needs its own policy, because the trust level differs.

Partner traffic needs its own door

Some businesses exchange requests with partners who legitimately need to send or receive a few context entries, for example a shared correlation hint. Treat such partners as a separate trust zone with a separate entrance, not as the general public. Identify them strongly, for example with client certificates. Write down the exact keys that may be accepted from each partner, and the exact keys that may be sent to each partner. Reject everything else. Review the list on a schedule, because partnerships and their needs change.

Do the same for the trace context

The same reasoning protects the trace identifiers and the sampling flag that travel beside baggage. Outsiders who can set those can also force expensive sampling or confuse traces. A gateway that starts fresh trace context for untrusted callers closes both holes with one rule.

!
Watch out

Be sure the stripping happens before any instrumentation reads the header. If a service reads the baggage first and the rule that removes it runs afterward, the damage is already done in that service’s context.

15Defense Inside: Validation and Safe Use

The edge is the first guard, not the only one. Services should still be careful with what they read and what they write.

Why inside defenses still matter

Edge rules can have gaps. A side entrance may exist, a partner may be allowed some entries, an internal service may be compromised, or a rule may fail to deploy. If every internal service trusts every entry just because “the edge checks everything”, then one gap becomes a full failure. Layered defense means each service keeps its own small guard.

One shared helper for every read and write

The most useful single control is a small shared library, owned by one team, which all services use instead of calling the raw baggage functions. It does the careful work in one place.

Reading

Typed and validated

Returns a value only if the key is on the allow list and the value fits the expected shape, such as one of five region codes. Otherwise it returns “missing”.

Writing

Budgeted

Refuses to add keys that are not in the registry, and refuses when the total size would pass the limit.

Safety

Cleaned

Removes control characters and truncates long values before they can reach logs or other systems.

Visibility

Counted

Counts rejected reads and writes by key name, giving monitoring something to watch.

Make the safe way the easy way

  • Lint rules. A rule in the build that flags direct use of the raw baggage functions outside the shared helper, with a short explanation of what to use instead.
  • Namespaced keys. Names start with a short owner prefix, so that two teams cannot accidentally use the same key for different meanings, and so that readers know whom to ask.
  • Code review checklists. Any change that adds a new key or a new copy of baggage into telemetry gets a security and privacy look.
  • Known caller identity. When services authenticate to each other, for example with mutual certificates from a service mesh, a service knows who sent a request. Entries then become “claims made by service X”, which is somewhat more reliable than anonymous text, though still not proof of truth.

A review checklist for a new key

  • Does the purpose fit in one sentence, and does someone own the key?
  • Does it pass the four questions of Chapter 13?
  • What is the exact set of allowed values, or the exact pattern and maximum length?
  • Who sets it, and who reads it, and what does each reader do with it?
  • Will it reach any outside party, any queue, or any stored telemetry?
  • What happens if the value is missing, wrong, or hostile? The answer should be “nothing important”.
  • When will the owner review whether the key is still needed?

Who is allowed to set what

Key kindWho may set itWhat others do with it
Edge-set keys, such as region and plan tierOnly the edge gatewayRead as hints. Drop any copy that arrives from a source other than the edge
Team keys, such as experiment cohortThe owning team’s serviceRead within the allowed purpose only
Anything elseNo oneIgnored and counted as unknown
i
Tip

Services that do not need baggage at all can turn the baggage propagator off in their configuration, so they neither read nor forward it. Be careful, though: if such a service sits in the middle of a call chain, downstream services that need the entries will no longer receive them.

16Defense at the Exit: Egress Control

The last guard stands at the door that leads out, making sure nothing leaves that should stay inside.

The main rule

Do not send baggage to outside parties unless there is a written reason. Outside parties include payment gateways, mapping and messaging providers, analytics tools, advertising networks, and every software-as-a-service product that your code calls. They did not ask for your internal hints, they may log them, and you may be unable to make them delete them later.

Where leaks hide

  • Vendor libraries. A payment company’s client library uses an ordinary web client, which the instrumentation wraps, so the baggage header goes along.
  • Webhooks and callbacks. Calls from your system to a customer’s server carry the header too.
  • Redirects. A redirect that moves a call from your domain to another may carry headers across.
  • Health checks and probes that call external monitoring services.

Ways to stop it

ControlHow it worksStrengthWeakness
Filtering propagatorA small wrapper that writes only allow-listed keys, or none, when the destination is not an internal hostClose to the code, works anywhere the code runsMust be adopted by every service
Per-client configurationClients that call outside hosts are created without the propagator, or with instrumentation settings that skip themSimple for known clientsEasy to forget for new clients
Egress proxyAll outbound traffic goes through a proxy that removes the baggage header for any destination not on an internal listOne central rule, catches every libraryNeeds good network design so that there is no bypass
Mesh egress gatewayA service mesh sends outside calls through a controlled exit that applies header rulesFits clusters already using a meshAdds components to run
Domain allow listNetwork controls that allow calls only to approved outside namesLimits where anything can goDoes not itself remove headers

Using two of these together is wise. The proxy catches what the code forgets, and the code-level control protects when a service runs in a place without the proxy, such as a developer’s laptop or a new environment.

When a partner needs data

If a partner truly needs a fact that you currently keep in baggage, give it to them on purpose. Add a documented field to the partner interface, send only that field, describe it in the contract, and agree on how long they keep it. That turns an accidental leak into a deliberate, reviewable data share.

!
Watch out

Outbound filtering that checks the destination by name can be fooled by internal names that point outside, such as a company domain that is really a vendor’s server. Keep the list of internal destinations exact and review it regularly.

17Controlling Baggage Inside Telemetry

Once a value is copied from baggage into a stored span, log, or metric, it lives as long as that data does. Choose carefully what to copy.

Copy with intent

Instead of “copy everything”, define a short list of baggage keys that are useful for analysis and safe to store. Only those are copied, to spans and logs. Everything else is left behind in the context. Each key on the list should have an owner and a reason in the registry.

Label the origin

A useful habit is to place copied values under a clear name prefix, so that anyone reading a trace can tell at a glance that the value came from baggage and was not measured or verified by the service. For example, a name such as baggage.region tells an analyst to treat the value as a hint. This simple labeling prevents the mistake of treating a forged flag as a fact in an investigation.

Where each signal stands

SignalMay baggage be copied?Conditions
SpansYes, for allow-listed keysShort values, fixed sets, origin prefix, attribute limits set
LogsYes, for allow-listed keysSanitized values, no personal data, structured fields
MetricsOnly for enumerations with a small fixed setNever for identifiers or free text, because cardinality explodes
Long-term archivesAvoidRetention rules must match the most sensitive value stored

Second line of defense: the Collector

Even when applications are careful, mistakes happen. The OpenTelemetry Collector can act as a safety net. Rules can remove attributes whose names are not on the approved list, mask values that match patterns such as email addresses, or delete anything that carries the origin prefix but is not registered. SDKs also have limits for the length of attribute values and the number of attributes per span, which can be set through configuration, such as OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT. Using both the SDK limits and the Collector rules gives two layers.

Access and retention

Telemetry stores are searched by many engineers. If baggage-derived values are stored there, apply the same access controls and retention rules as to any other data of that sensitivity. Review who can read raw traces and logs, and remove broad access that was granted just because “it is only debugging data”.

Analogy

Think of a school notice board. Before pinning a note to it, you decide who will read it and for how long it will stay up. Copying baggage into telemetry is pinning notes on a public board, so the same questions apply.

18Governance: The Baggage Registry

Rules written on paper protect nothing unless someone owns them. A registry is the simplest tool for ownership.

What a registry is

A baggage registry is a list, kept in version control, of every key allowed in your systems. Each key has a row that answers the questions a security reviewer would ask. The registry also feeds the code: allow lists in the edge gateway, the shared helper, the telemetry copier, and the egress filter are all generated from it, so that there is one source of truth.

FieldQuestion it answers
NameWhat is the key called, with its owner prefix?
OwnerWhich team answers questions and reviews changes?
PurposeWhy does this exist, in one sentence?
Allowed valuesWhat are the exact allowed values or the pattern?
Data classIs it public, internal, or sensitive?
Who sets itThe edge only, or named services?
Who reads itWhich services use it, and for what?
May cross to outside partiesYes or no, and under what agreement?
Copied to telemetryWhich signals, if any?
Crosses queuesWhich topics, if any?
Review dateWhen must the owner confirm it is still needed?
flowchart LR
  A["Proposal with purpose and data class"] --> B["Security and privacy review"]
  B -->|"approved"| C["Added to the registry and allow lists"]
  B -->|"rejected"| X["Use another design"]
  C --> D["Used in services and monitored"]
  D --> E["Periodic review"]
  E -->|"still needed"| D
  E -->|"unused"| F["Retired and removed"]
    
Figure 7: The life of a baggage key, from proposal to retirement.

The lifecycle in practice

A team that wants a new key opens a short proposal that fills in the registry row. Security and privacy reviewers check it with the four questions from Chapter 13. If approved, the registry change is merged, and generated allow lists roll out to the edge, the helper, and the telemetry rules. Monitoring then shows how often the key is seen. Every quarter, owners confirm their keys. Keys that nobody reads are retired, and the retirement is made real by removing them from the allow lists and the code. This habit keeps the baggage small, which is itself a security measure.

i
Tip

Start with an honest inventory. Spend one day listing every baggage key that exists today by scanning code and sampling traffic at the gateway. Most companies are surprised by the number of forgotten keys.

19Testing the Defenses

A guard who has never been tested is only a guess. These checks turn the guess into evidence.

TestHow it worksPass condition
Validator unit testsFeed the shared helper good values, bad values, and edge casesOnly valid, allow-listed values are returned
Malformed header testsSend random, broken, and extreme headers: missing equals signs, repeated keys, huge counts, strange characters, line breaks, double encodingNo crashes, no passing of bad content, bounded time and memory
Forgery tests at the edgeFrom outside, send forged flags such as premium or adminThe flags never reach internal services
Oversize testsSend headers at and above the limitsRejected or trimmed, consistently, with counters raised
Injection testsPlace attack text in values and watch logs, queries, and pagesText appears only as harmless data
Leak tests with marker valuesSet a unique, harmless-looking marker in baggage, then search logs, spans, queues, and outbound traffic for itThe marker appears only in approved places
Concurrency testsRun many parallel requests with different valuesEach request sees only its own
Egress testsMake calls to a test server standing in for an outside partnerNo baggage header arrives
Registry consistency testsCompare code, allow lists, and registry in the buildNo key exists in one place and not in the others

What to automate

Manual checks fade away. Put the cheap tests into the build, so that every change runs them: validator tests, malformed header tests, registry consistency tests, and a check that the egress rule still removes the header. Run the heavier tests, such as marker searches across all stores and the concurrency tests under load, on a regular schedule, for example every night or every week, and send failures to the owning team. Keep the exercises with human attackers for the times when you add major features or change infrastructure.

The marker trick

The leak test deserves special attention. It uses a “canary” marker, a unique value such as a random string that cannot be confused with real data. You attach it to a test request and then search everything you can reach: gateway logs, application logs, tracing storage, queue contents, and the traffic that arrives at a stand-in for an outside server. Wherever the marker appears, a path exists. Compare the results with your registry. Any place that is not allowed is a finding. Run this regularly, not just once, because every deployment may change the paths.

Practice real incidents

At least once or twice a year, run a short exercise in which one team plays an attacker who tries to use baggage to gain a premium feature, leak data to a partner, or cause an overload. The second team defends using only monitoring and runbooks. These exercises reveal gaps in dashboards and in understanding that automated tests cannot.

20Monitoring and Responding

Defenses need eyes. Monitoring tells you when they are tested, when they fail, and when someone is probing them.

What to measure

PlaceSignal to watchWhat a change means
Edge gatewayRequests with baggage, stripped, rejected, oversize, malformedA jump signals scanning or a misconfigured client
Internal servicesDistribution of baggage size and entry count, unknown keys seen, rejected reads and writes by keyGrowth hints at creep or a new unreviewed key
Telemetry storesDistinct values per copied attribute, attribute length, series countsRapid growth hints at cardinality abuse
EgressOutbound requests to outside hosts that still carry the baggage headerShould be zero. Any count is an incident or a bypass
QueuesMessages carrying baggage, by topicNew topics carrying baggage need review
ErrorsRequests refused because headers were too largeReal users may be harmed by oversize baggage

Notice that most of these are counts and shapes, not values. Monitoring should never store the baggage values themselves, because that would copy the very data you are trying to protect into another system.

A simple dashboard

One page is enough to start. At the top, put one big number: baggage headers seen on traffic leaving to outside hosts, which should be zero. Below that, show a small chart of requests stripped or rejected at the edge, a chart of the 50th and 99th percentile of baggage size against the limit, and a table of unknown keys seen in the last day. Add one chart for the number of distinct values in each copied telemetry attribute. When someone asks how safe your baggage use is, this page answers in half a minute.

Alerts worth having

  • Any baggage header seen on traffic leaving to outside hosts.
  • A sudden rise in stripped or rejected baggage at the edge.
  • The 99th percentile of baggage size creeping toward the limit.
  • An unknown key appearing in more than a handful of requests.
  • A copied attribute whose number of distinct values grows faster than expected.

When something goes wrong

1

Contain

Tighten the edge rule to strip everything, switch off copying to telemetry, and block outbound baggage at the egress proxy. These are quick switches prepared in advance.

2

Find the scope

Use the registry and monitoring to learn which keys, which services, which stores, and which time period were involved.

3

Clean up

Remove or mask the affected data in logs, traces, and queues. If an outside party received data, contact them under your agreement and ask for deletion.

4

Tell the right people

If personal data was involved, your privacy and legal teams decide what notices are required, and when.

5

Learn and fix the cause

Add a test that would have caught the problem, and a rule that makes the unsafe way impossible, not just discouraged.

21Deployment and Cloud

Principles become real when mapped to the building blocks you actually run. Here is a map for a typical AWS-style environment. Always check current product features before relying on them.

NeedTypical building blockNote
Strip or rewrite inbound headersA reverse proxy or service mesh ingress; API gateway request mapping; edge functions on a content delivery networkChoose the layer that all traffic passes through. A plain load balancer may not offer arbitrary header removal
Limit header size and flood abusive clientsA web application firewall with size and rate rulesAdd as a coarse first filter. Fine-grained validation stays in the gateway
Control outbound callsEgress proxy in a controlled subnet, plus domain allow lists in a network firewallMake the proxy the only route out, so there is no bypass
Know the caller’s identityMutual TLS through a mesh, or signed service credentialsGives baggage entries a known sender, not proof of truth
Protect messages at restEncryption with managed keys, short retention, restricted read permissions on queues and dead-letter queuesMessage systems also cap attributes, so baggage must compete for room
Control who reads logs and tracesFine-grained permissions, short retention, separate stores for sensitive logsReview roles that have broad read access
Telemetry safety netCollector rules to remove unapproved attributes and mask patternsTest them as carefully as application code
Configuration as codePropagator settings, limits, and allow lists in version control, rolled out graduallyRegistry changes generate configuration for all layers

Roll out in a safe order

1

Measure first

Turn on counting at the edge and at the exit, without changing any behavior. Learn what baggage exists today.

2

Strip untrusted inbound baggage

This is the highest benefit and the lowest risk, because almost no legitimate outside client depends on it.

3

Block outbound leaks

Start with the egress proxy removing baggage for outside hosts. Watch for partners that really depended on it.

4

Adopt the registry and helper

Migrate services one by one, with the lint rule catching direct use.

5

Clean up telemetry

Replace “copy all” with allow-listed copies, and remove old attributes with sensitive values from storage according to policy.

22Advantages, Disadvantages, Trade-offs, and Alternatives

After all these risks, a fair question arises: is baggage worth using at all?

What baggage gives you

  • Context available everywhere, without extra lookups.
  • No need to change every function and message format to pass a hint along.
  • Consistent behavior across languages and frameworks.
  • A way to label telemetry with business meaning.
  • Very low cost for small, well-chosen entries.

What it costs in risk

  • Automatic spread to places you did not choose.
  • No proof that values are honest or unchanged.
  • Plain-text exposure in logs, queues, and partner systems.
  • Amplification of size across internal calls.
  • Easy to over-use, since adding an entry takes one line.

The alternatives, side by side

ApproachHow it worksTrustCost and effortBest for
Baggage with controlsSmall hints in a header, with edge, helper, and egress guardsLow. Hints onlyLow once the controls existExperiment cohorts, region hints, debug labels
Signed tokenA tamper-evident token that each service verifiesHighMedium. Key management and token sizeIdentity, tenant, and permissions
Lookup by identifierPass a short opaque ID, and each service asks a trusted store for factsHigh, as high as the storeMedium. Extra calls and cachingCustomer attributes, plan details
Explicit parametersPut the value in the request or message body as a normal fieldAs high as normal input validationMedium. Interface changesAnything a partner or a single next service needs
Request context storeSave request facts in a fast shared cache keyed by trace or request IDHigh, with access controlHigher. A new component to runLarger or more sensitive context

How to decide in a meeting

When a team proposes a new baggage entry, run through a quick decision. If the value is secret or personal, the answer is no. If a decision depends on it, use a token or a lookup. If it must reach a partner, use an explicit field. If it is a small hint with a fixed set of values and has an owner, accept it and add it to the registry. This takes five minutes, and it stops the slow growth of risky entries that are only visible years later.

Trade-offs to discuss with your team

  • Convenience versus control. Open, automatic baggage is the easiest to use and the hardest to control. A registry and helper add friction and add safety.
  • Reach versus exposure. The wider the entries travel, the more services benefit and the more places can leak them.
  • Hints versus certainty. Baggage is cheap because it is unverified. Verification costs effort, and belongs in other tools.
  • One central policy versus team freedom. A central registry slows new keys down a little and prevents the slow growth of unreviewed ones.

23Design Patterns and Anti-patterns

Habits that work for many teams, and the traps that catch many teams.

Patterns that work

Pattern

Strip at the door

Remove untrusted inbound baggage at the edge, and set fresh trusted values from verified facts.

Pattern

Allow lists everywhere

Named keys only, at the edge, in the helper, in telemetry copies, in queues, and at the exit.

Pattern

Hints, not decisions

No permission, price, or tenant choice depends on a baggage value.

Pattern

One shared helper

Every read and write passes through one validated, counted, budgeted library.

Pattern

Origin prefix on copies

Telemetry attributes copied from baggage carry a visible prefix, reminding readers that the value is a hint.

Pattern

Default-deny egress

No baggage leaves toward outside parties unless a written exception exists.

Pattern

Registry as code

One list of keys generates all allow lists and tests, with owners and review dates.

Pattern

Marker-based leak tests

A unique canary value is followed through every store and every outbound path, on a schedule.

Anti-patterns to avoid

ANTI-PATTERN 01Avoid
What people do

Put the user’s email or name into baggage “so that support can see it in every log”.

Why it hurts

The value is sent to every service and every outside partner, and is stored in every place that copies it. A convenience for one team becomes a data-protection problem for the whole company.

Do this instead

Carry a short, opaque request or customer reference, and let authorized support tools look up details from a protected store.

ANTI-PATTERN 02Avoid
What people do

Use a baggage entry to choose the tenant, the user role, or a price.

Why it hurts

Anyone who can send a header, or who can change it somewhere along the path, can claim any tenant or role. This is a direct path to data exposure or free goods.

Do this instead

Derive tenant and role from the verified identity at each service, using a signed token or a trusted lookup.

ANTI-PATTERN 03Avoid
What people do

Switch on “copy all baggage to every span” and every log line.

Why it hurts

Outsiders can write free text into your telemetry, cardinality can explode, and sensitive values are stored for as long as the telemetry lives.

Do this instead

Copy a short allow-listed set of keys, with an origin prefix and value limits.

ANTI-PATTERN 04Avoid
What people do

Leave default propagation on for every outgoing call, including outside partners, and never check.

Why it hurts

Entries leak to outside parties by default, silently, forever.

Do this instead

Put an egress proxy or filtering propagator in place, and count what still leaves.

24Best Practices and Common Mistakes

A short list of habits to adopt, and the slips that appear in review after review.

Best practices

  • Treat baggage as untrusted input at every boundary. It is a postcard written by strangers.
  • Keep it tiny. A few short, coarse entries with fixed sets of values.
  • Never carry secrets or personal data. Use opaque references and trusted lookups.
  • Strip at the edge and filter at the exit. The two boundaries matter most.
  • Make decisions from verified facts and use baggage only for hints.
  • Maintain a registry with owners, purposes, and review dates.
  • Copy to telemetry with an allow list and never copy to metrics unless the values form a small fixed set.
  • Test and monitor continuously. Marker tests, malformed input tests, size distributions, and egress counters.

Common mistakes and quick fixes

MistakeWhat you will noticeFix
Trusting incoming baggage from the internetStrange values from clients, forged flags in logsStrip or allow-list at the edge, set trusted values there
Sending baggage to partners by accidentPartner support asks why they see your internal labelsEgress proxy and filtering propagator, plus counters
No size limitsRequests failing with header errors, rising internal trafficSet small limits at the edge and in the helper
Copying baggage to metric labelsSeries counts and bills jumpRemove those labels, allow only small fixed sets
Pasting values into logs, queries, or pagesOdd log lines, security scan findingsValidate and encode, use parameterized queries
Leaving scopes open in thread poolsWrong tenant labels under loadClose scopes safely, use context-aware executors, start clean
Putting baggage on messages by defaultOld values seen long after, operators see sensitive entriesAllow only named keys on named topics, shorten retention
No owner for keysNobody knows what a key is for, so nobody dares remove itRegistry with owners and review dates
Allowing the baggage header from every website in a browser appUsers or third-party pages can inject entriesRestrict allowed origins, avoid sending baggage from browsers
i
Tip

The single most effective week of work is usually the first one: measure what baggage exists, strip it at the edge, and block it at the exit. Everything after that is refinement.

25Real-World and Industry Examples

The guidance in this guide matches what standards bodies, tool makers, and infrastructure defaults already tell us, plus two clearly labeled made-up stories.

The OpenTelemetry documentation: a direct warning

OpenTelemetry’s own explanation of baggage includes a security note. It points out that because baggage is propagated automatically, sensitive items can reach unintended parties such as outside APIs, and that there are no built-in integrity checks, so values you read may not be yours. The approach in this guide turns those two warnings into concrete controls at the edge, in the services, and at the exit. Read the current documentation yourself, as wording and guidance may change.

The W3C Baggage specification: limits and privacy notes

The specification that defines the baggage header sets minimum limits for the number and size of entries, and discusses privacy and security concerns, including the risk of sensitive data traveling beyond its intended audience. The limits are floors that implementations must support, not recommendations, which is why this guide suggests setting your own much smaller limits.

OpenTracing and Jaeger: baggage with restrictions

OpenTracing, one of the projects that merged into OpenTelemetry, already had baggage and described its cost, since every hop carries every entry. Jaeger’s client libraries offered a way to restrict baggage to approved keys and to cap value sizes, configured centrally. That is the same idea as the registry and allow lists described here, which shows that the problem is old and the remedy is well understood.

Everyday infrastructure: header size limits

Common web servers and runtimes cap header sizes by default, typically around eight thousand bytes for a single header line in popular servers, with some runtimes allowing more. A baggage header that grows close to such a cap can cause ordinary requests to fail. Because defaults differ between products and versions, check your own stack, and choose baggage limits that sit comfortably below the smallest cap on the path.

Proxies and gateways: header removal as a standard feature

Widely used proxies and gateways, such as Envoy and NGINX, can remove, replace, or add request headers by rule. Many API gateway products offer similar mapping features. This is why edge stripping and egress filtering are practical today without writing new software.

Harbor Foods (made up for teaching)

Harbor Foods, the imaginary company from Chapter 5, ran a leak test with a marker value after the shipping partner incident. The marker showed up in the partner’s request logs, in a queue read by a data-science team, and in a debugging dashboard. The team stripped baggage at the edge, added an egress proxy, replaced the email entry with an opaque reference, and added a registry. A second marker test, a month later, found the value only in the places the registry allowed. Treat the story as a picture of the method, not as a promise of results.

A cardinality scare (made up for teaching)

Imagine a video service in which a developer enabled “copy all baggage to metric labels” while testing. A traffic scanner then sent random values in the baggage header at a steady pace. Within hours, the number of metric series grew until the backend slowed and dashboards timed out. The fix was to remove the copy rule, strip untrusted baggage at the edge, and allow only small fixed-set labels. The lesson is that an outsider can use your own settings against you.

26Frequently Asked Questions

Short answers to the questions that come up most often.

Q1Is baggage secure by default?
No. It is plain text, carries no proof of origin, and is propagated automatically. Safety comes from the controls you add around it.
Q2Does a secure connection solve the problem?
It protects the data while it moves between two systems. It does not stop forged values from outside, does not stop logging of headers, does not protect messages stored in queues, and does not stop partners from reading what you send them.
Q3Should we stop using baggage?
Not necessarily. For small, coarse hints it is a useful tool. Stop using it for secrets, personal data, and anything that decides who can do what.
Q4Can baggage values be signed or encrypted?
It is possible, but it adds key management, expiry, and replay concerns, and increases size. For claims that must be trusted, a standard signed token that every service verifies is usually a cleaner choice than inventing a signing scheme for baggage.
Q5Does baggage show up in my traces automatically?
No. It becomes part of stored telemetry only if some code, such as a baggage span processor or a logging integration, copies it. Decide what gets copied, on purpose.
Q6How can I find out what baggage we send today?
Count and sample at the edge and at the egress proxy, scan the code for uses of the baggage functions, and run marker tests to see where a value travels.
Q7What about browser applications?
Avoid sending baggage from browsers when you can. Anything set there is visible to the user and can be changed by them. If you need it, allow the header only for your own origins and treat what arrives as untrusted.
Q8What limits should we choose?
Start small, for example around ten entries and a few hundred bytes in total, then adjust based on your registry and measurements. Keep them well below the limits of your proxies and servers.
Q9Can baggage be used to influence sampling?
It can, but because outsiders may forge it, it can also be used to force expensive sampling. If you do use it, let only the edge set that value, and ignore it from any other source.
Q10Do the same ideas apply to messaging and remote procedure calls?
Yes. The carrier changes, such as call metadata or message attributes, but the threats are the same: forged input, leaks, size, injection, and long life. The controls follow the same pattern.
Q11Who should own baggage security?
Usually the platform or observability team owns the shared helper, the registry, and the edge and egress rules, while security and privacy teams review new keys and run tests. Each key still has a named owning team.

27Summary and Key Takeaways

Key Takeaways

  • Baggage is a postcard. It is plain text, unverified, and automatically sent to every hop, so carry only small, coarse, harmless hints.
  • Guard both boundaries. Strip or allow-list inbound baggage at the edge, and block or filter it on the way to outside parties.
  • Hints, never decisions. Permissions, tenants, and prices must come from verified identity and trusted lookups.
  • Limit size and shape. Small caps on entries and bytes stop amplification, and fixed sets of values stop injection and cardinality explosions.
  • Mind the copies. Telemetry, logs, and queues store baggage for a long time, so copy only allow-listed keys, label their origin, and keep metrics free of free-form values.
  • Keep the room clean. Close scopes, use context-aware executors, and start each request and job with a fresh context so values never bleed between requests or tenants.
  • Govern, test, and watch. A registry with owners, marker-based leak tests, and counters at the edge and exit keep the whole design honest over time.