├── .github └── workflows │ ├── auto-publish.yml │ └── tests.yml ├── .gitignore ├── .pr-preview.json ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── abstract ├── baggage-overview.md ├── common-abstract.md └── common-sotd.md ├── baggage ├── HTTP_HEADER_FORMAT.md ├── HTTP_HEADER_FORMAT_RATIONALE.md ├── README.md ├── acknowledgments.md ├── privacy.md └── security.md ├── index.html ├── reports └── cr-2024-implementations.md ├── test ├── baggage │ ├── __init__.py │ └── baggage.py └── test_baggage.py └── w3c.json /.github/workflows/auto-publish.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | pull_request: {} 4 | push: 5 | branches: [main] 6 | jobs: 7 | main: 8 | name: Build, Validate and Deploy 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - uses: w3c/spec-prod@v2 13 | with: 14 | W3C_ECHIDNA_TOKEN: ${{ secrets.W3C_TR_TOKEN }} 15 | W3C_WG_DECISION_URL: https://docs.google.com/document/d/12AAIbnkt_AH1YjZwUwxirX_7WA7aWOSXpDweCgfqIiQ/edit#heading=h.jc5tpbau2p7z 16 | W3C_BUILD_OVERRIDE: | 17 | shortName: baggage 18 | specStatus: WD 19 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Self Test 2 | on: 3 | push: 4 | pull_request: 5 | 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | name: Run the python tests 10 | steps: 11 | - uses: actions/checkout@v3 12 | - name: Setup python 13 | uses: actions/setup-python@v3 14 | with: 15 | python-version: 3.11.2 16 | architecture: x64 17 | - run: cd test && python test_baggage.py 18 | shell: bash -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IntelliJ IDEA 2 | .idea 3 | *.iml 4 | 5 | # Eclipse 6 | .classpath 7 | .project 8 | .settings 9 | bin 10 | 11 | # NetBeans 12 | /.nb-gradle 13 | /.nb-gradle-properties 14 | 15 | # OS X 16 | .DS_Store 17 | 18 | # Emacs 19 | *~ 20 | \#*\# 21 | 22 | # Vim 23 | .swp 24 | 25 | # Byte-compiled / optimized / DLL files 26 | __pycache__/ 27 | *.py[cod] 28 | *$py.class 29 | 30 | # Distribution / packaging 31 | .Python 32 | build/ 33 | develop-eggs/ 34 | dist/ 35 | downloads/ 36 | eggs/ 37 | .eggs/ 38 | lib/ 39 | lib64/ 40 | parts/ 41 | sdist/ 42 | var/ 43 | wheels/ 44 | share/python-wheels/ 45 | *.egg-info/ 46 | .installed.cfg 47 | *.egg 48 | MANIFEST 49 | 50 | # PyInstaller 51 | # Usually these files are written by a python script from a template 52 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 53 | *.manifest 54 | *.spec 55 | 56 | # Installer logs 57 | pip-log.txt 58 | pip-delete-this-directory.txt 59 | 60 | # Unit test / coverage reports 61 | htmlcov/ 62 | .tox/ 63 | .nox/ 64 | .coverage 65 | .coverage.* 66 | .cache 67 | nosetests.xml 68 | coverage.xml 69 | *.cover 70 | *.py,cover 71 | .hypothesis/ 72 | .pytest_cache/ 73 | cover/ 74 | 75 | # Environments 76 | .env 77 | .venv 78 | env/ 79 | venv/ 80 | ENV/ 81 | env.bak/ 82 | venv.bak/ 83 | -------------------------------------------------------------------------------- /.pr-preview.json: -------------------------------------------------------------------------------- 1 | { 2 | "src_file": "index.html", 3 | "type": "respec" 4 | } 5 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | All documentation, code and communication under this repository are covered by the [W3C Code of Ethics and Professional Conduct](https://www.w3.org/Consortium/cepc/). 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing into baggage 2 | 3 | This repository is being used for work in the [W3C Distributed Tracing Working Group](https://www.w3.org/2018/distributed-tracing/). 4 | 5 | Contributions to this repository are intended to become part of Recommendation-track documents governed by the 6 | [W3C Patent Policy](https://www.w3.org/Consortium/Patent-Policy/) and 7 | [Software and Document License](https://www.w3.org/Consortium/Legal/copyright-software). To make substantive contributions to specifications, you must either participate 8 | in the relevant W3C Working Group or make a non-member patent licensing commitment. 9 | 10 | If you are not the sole contributor to a contribution (pull request), please identify all 11 | contributors in the pull request comment. 12 | 13 | To add a contributor (other than yourself, that's automatic), mark them one per line as follows: 14 | 15 | ``` bash 16 | +@github_username 17 | ``` 18 | 19 | If you added a contributor by mistake, you can remove them in a comment with: 20 | 21 | ``` bash 22 | -@github_username 23 | ``` 24 | 25 | If you are making a pull request on behalf of someone else but you had no part in designing the 26 | feature, you can remove yourself with the above syntax. 27 | 28 | ## Editing report 29 | 30 | This repository follows the best practices from https://w3c.github.io/ 31 | 32 | - Report is generated by [ReSpec](https://github.com/w3c/respec/wiki) 33 | - To view report locally - start the local server like `python -m SimpleHTTPServer` to avoid cross-site file loading issue 34 | - You can preview report from your branch or fork `http://htmlpreview.github.io/?https://github.com//baggage/blob//index.html`. It is useful to review PR suggestions. 35 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | All Reports in this Repository are licensed by contributors 2 | under the 3 | [W3C Software and Document License](https://www.w3.org/Consortium/Legal/copyright-software). 4 | 5 | All tests in this Repository are licensed by contributors to be distributed under the 6 | [W3C 3-clause BSD License](https://www.w3.org/Consortium/Legal/2008/03-bsd-license.html). 7 | 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Baggage Specification 2 | 3 | This repository is associated with the [baggage](https://w3c.github.io/baggage/) specification. 4 | 5 | Status of the report is 6 | [First Public Working Draft](https://www.w3.org/2017/Process-20170301/#first-wd). 7 | 8 | See rationale 9 | [document](baggage/HTTP_HEADER_FORMAT_RATIONALE.md) for 10 | clarifications. 11 | 12 | ## Team Communication 13 | 14 | See 15 | [communication](https://github.com/w3c/distributed-tracing-wg#team-communication) 16 | 17 | We appreciate feedback and contributions. Please make sure to read 18 | rationale documents when you have a question about particular decision 19 | made in specification. 20 | 21 | ## Goal 22 | 23 | This specification defines propagation of baggage for events correlation 24 | beyond the request identification that is covered by [trace 25 | context](https://w3c.github.io/trace-context/) specification. Our goal 26 | is to share this with the community so that various tracing and 27 | diagnostics products can operate together. 28 | 29 | ## Known Implementations 30 | 31 | OpenTelemetry provides an implementation of the baggage specification. You can find details at [OpenTelemetry API](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/baggage/api.md). OpenTelemetry SDK ships a BaggagePropagator and enables it by default. For example, the .NET version of it is [here](https://github.com/open-telemetry/opentelemetry-dotnet/blob/5ddf9a486e755c53ab73debf87286a934fcbbb51/src/OpenTelemetry.Api/Context/Propagation/BaggagePropagator.cs) and it is documented [here](https://github.com/open-telemetry/opentelemetry-dotnet/blob/main/src/OpenTelemetry.Api/README.md#baggage-api). 32 | 33 | Another system that supports the baggage concept is .NET. It supports it as part of the [System.Diagnostics.Activity class](https://github.com/dotnet/corefx/blob/master/src/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/Activity.cs) and it is documented [here](https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.activity?view=net-5.0). However, it is not a strict reference implementation: for example, it doesn't enforce the same character set or limits. 34 | 35 | ## Why are we doing this 36 | 37 | See 38 | [Why](https://github.com/w3c/distributed-tracing-wg#why-are-we-doing-this) 39 | 40 | ## Contributing 41 | 42 | See [Contributing.md](CONTRIBUTING.md) for details. 43 | -------------------------------------------------------------------------------- /abstract/baggage-overview.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | The `baggage` header represents a set of user-defined properties associated with a distributed request. Libraries and platforms SHOULD propagate this header. 4 | -------------------------------------------------------------------------------- /abstract/common-abstract.md: -------------------------------------------------------------------------------- 1 | This specification defines a standard for representing and propagating a set of application-defined properties associated with a distributed request or workflow execution. 2 | 3 | This is independent of the [Trace Context](https://www.w3.org/TR/trace-context/) specification. Baggage can be used regardless of whether Distributed Tracing is used. This specification standardizes representation and propagation of application-defined properties. In contrast, Trace Context specification standardizes representation and propagation of the metadata needed to enable Distributed Tracing scenarios. 4 | 5 | The current version of the Baggage specification is targeted for implementations by applications and services, including web applications running within a browser. Web Browsers or User Agents are not currently in scope as a target implementation. -------------------------------------------------------------------------------- /abstract/common-sotd.md: -------------------------------------------------------------------------------- 1 | During the Candidate Recommendation stage, the Working Group will audit: 2 | * the [OpenTelemetry implementations of Baggage](https://opentelemetry.io/docs/specs/otel/baggage/api/) to make sure they are following the W3C Baggage spec. 3 | * a sample of [OpenTelemetry's baggage test suites](https://github.com/open-telemetry/opentelemetry-specification/blob/main/spec-compliance-matrix.md#baggage) to verify they are testing the standard. 4 | -------------------------------------------------------------------------------- /baggage/HTTP_HEADER_FORMAT.md: -------------------------------------------------------------------------------- 1 | # Baggage HTTP Header Format 2 | 3 | The `baggage` header is used to propagate user-supplied key-value pairs through a distributed request. 4 | A received header MAY be altered to change or add information and it SHOULD be passed on to all downstream requests. 5 | 6 | Multiple `baggage` headers are allowed. Values can be combined in a single header according to [RFC 7230](https://tools.ietf.org/html/rfc7230#page-24). 7 | 8 | ## Header Name 9 | 10 | Header name: `baggage` 11 | 12 | In order to increase interoperability across multiple protocols and encourage successful integration, 13 | implementations SHOULD keep the header name lowercase. 14 | 15 | ## Header Encoding 16 | 17 | This header is a [[UTF-8]] encoded [[UNICODE]] string, however it uses only code points from the Basic Latin Unicode Block which are encoded identically in both Unicode and [[ASCII]]. 18 | 19 | ## Header Content 20 | 21 | This section uses the Augmented Backus-Naur Form (ABNF) notation of [[!RFC5234]]. 22 | 23 | ### Definition 24 | 25 | ```ABNF 26 | baggage-string = list-member 0*179( OWS "," OWS list-member ) 27 | list-member = key OWS "=" OWS value *( OWS ";" OWS property ) 28 | property = key OWS "=" OWS value 29 | property =/ key OWS 30 | key = token ; as defined in RFC 7230, Section 3.2.6 31 | value = *baggage-octet 32 | baggage-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E 33 | ; US-ASCII characters excluding CTLs, 34 | ; whitespace, DQUOTE, comma, semicolon, 35 | ; and backslash 36 | OWS = *( SP / HTAB ) ; optional white space, as defined in RFC 7230, Section 3.2.3 37 | ``` 38 | 39 | `token` is defined in [[RFC7230]], Section 3.2.6: https://tools.ietf.org/html/rfc7230#section-3.2.6 40 | 41 | The definition of `OWS` is taken from [[RFC7230]], Section 3.2.3: https://tools.ietf.org/html/rfc7230#section-3.2.3 42 | 43 | #### baggage-string 44 | 45 | List of `list-member`s with optional properties attached. 46 | Uniqueness of keys between multiple `list-member`s in a `baggage-string` is not guaranteed. 47 | The order of duplicate entries SHOULD be preserved when mutating the list. 48 | Producers SHOULD try to produce a `baggage-string` without any `list-member`s which duplicate the `key` of another list member. 49 | 50 | #### key 51 | 52 | A `token` which identifies a `value` in the `baggage`. `token` is defined in [RFC7230, Section 3.2.6](https://tools.ietf.org/html/rfc7230#section-3.2.6). 53 | Leading and trailing whitespaces (`OWS`) are allowed and are not considered to be a part of the key. 54 | 55 | 60 | 61 | #### value 62 | 63 | A string which contains a value identified by the `key`. 64 | Any code points outside of the `baggage-octet` range MUST be percent-encoded. 65 | The percent code point (`U+0025`) MUST be percent-encoded. 66 | Code points which are not required to be percent-encoded MAY be percent-encoded. 67 | Percent-encoding is defined in [[RFC3986]], Section 2.1: https://datatracker.ietf.org/doc/html/rfc3986#section-2.1. 68 | 69 | When decoding the value, percent-encoded octet sequences that do not match the UTF-8 encoding scheme MUST be replaced with the replacement code point (`U+FFFD`). 70 | 71 | Leading and trailing whitespaces (`OWS`) are allowed and are not considered to be a part of the value. 72 | 73 | Note, `value` MAY contain any number of the equal sign (`U+003D`) code points. Parsers 74 | MUST NOT assume that the equal sign is only used to separate `key` and `value`. 75 | 76 | 81 | 82 | #### property 83 | 84 | Additional metadata MAY be appended to values in the form of property set, represented as semi-colon `;` delimited list of keys and/or key-value pairs, e.g. `;k1=v1;k2;k3=v3`. 85 | Property keys and values are given no specific meaning by this specification. 86 | Leading and trailing `OWS` is allowed and is not considered to be a part of the property key or value. 87 | 88 | ### Limits 89 | 90 | A platform MUST propagate all `list-member`s including any `list-member`s added by the platform whenever both of these conditions are met: 91 | 92 | - **Condition 1:** The resulting `baggage-string` contains 64 `list-member`s or less. 93 | - **Condition 2:** The resulting `baggage-string` is of size 8192 bytes or less. 94 | 95 | If either of the above conditions is not met, a platform MAY drop `list-member`s until both conditions are met. 96 | The selection of which `list-member`s to drop and their order is unspecified and left to the implementer. 97 | Note that the above limits are _minimum_ requirements to comply with the specification. 98 | An implementor or platform MAY define higher limits and SHOULD propagate as much baggage information as is reasonable within their requirements. 99 | If a platform cannot propagate all baggage, it MUST NOT propagate any partial `list-member`s. 100 | If there are multiple `baggage` headers, all limits apply to the combination of all `baggage` headers and not each header individually. 101 | 102 | ### Example 103 | 104 | The following example header contains 3 `list-member`s. 105 | The `baggage-string` contained in the header contains 86 bytes. 106 | 82 bytes come from the `list-member`s and 4 bytes come from commas and optional whitespace. 107 | 108 | ``` 109 | baggage: key1=value1;property1;property2, key2 = value2, key3=value3; propertyKey=propertyValue 110 | ``` 111 | 112 | - `key1=value1;property1;property2` 113 | - 31 bytes 114 | - `key2 = value2` 115 | - 13 bytes 116 | - `key3=value3; propertyKey=propertyValue` 117 | - 38 bytes 118 | 119 | ## Examples of HTTP headers 120 | 121 | Assume we want to propagate these entries: `userId="alice"`, `serverNode="DF 28"`, `isProduction=false`, 122 | 123 | Single header: 124 | 125 | ``` 126 | baggage: userId=alice,serverNode=DF%2028,isProduction=false 127 | ``` 128 | 129 | Here is one more example where values with characters outside of the `baggage-octet` range are percent-encoded. Consider the entry: `userId="Amélie"`, `serverNode="DF 28"`, `isProduction=false`: 130 | 131 | ``` 132 | baggage: userId=Am%C3%A9lie,serverNode=DF%2028,isProduction=false 133 | ``` 134 | 135 | Baggage might be split into multiple headers: 136 | 137 | ``` 138 | baggage: userId=alice 139 | baggage: serverNode=DF%2028,isProduction=false 140 | ``` 141 | 142 | Values and names might begin and end with spaces: 143 | 144 | ``` 145 | baggage: userId = alice 146 | baggage: serverNode = DF%2028, isProduction = false 147 | ``` 148 | 149 | ### Example use case 150 | 151 | For example, if all of your data needs to be sent to a single node, you could propagate a property indicating that. 152 | 153 | ``` 154 | baggage: serverNode=DF%2028 155 | ``` 156 | 157 | For example, if you need to annotate logs with some request-specific information, you could propagate a property using the baggage header. 158 | 159 | ``` 160 | baggage: userId=alice 161 | ``` 162 | 163 | For example, if you have non-production requests that flow through the same services as production requests. 164 | 165 | ``` 166 | baggage: isProduction=false 167 | ``` 168 | # Mutating baggage 169 | A system receiving a `baggage` request header SHOULD send it to outgoing requests. 170 | A system MAY mutate the value of this header before passing it on. 171 | 172 | Because baggage entry keys, values, and metadata are not specified here, producers and consumers MAY agree on any set of mutation rules that don't violate the specification. For example, keys may be deduplicated by keeping the first entry, keeping the last entry, or concatenating values together. 173 | 174 | The following mutations are allowed: 175 | 176 | * **Add a new key/value pair.** A key/value pair MAY be added. 177 | * **Update an existing value.** The value for any given key MAY be updated. 178 | * **Delete a key/value pair.** Any key/value pair MAY be deleted. 179 | * **Deduplicating the list.** Duplicate keys MAY be removed. 180 | 181 | If a system receiving or updating a `baggage` request header determines that the number of baggage entries exceeds the limit defined in the limits section above, it MAY drop or truncate certain baggage entries in any order chosen by the implementation. 182 | 183 | If a system determines that the value of a baggage entry is not in the format defined in this specification, it MAY remove that entry before propagating the baggage header as part of outgoing requests. 184 | -------------------------------------------------------------------------------- /baggage/HTTP_HEADER_FORMAT_RATIONALE.md: -------------------------------------------------------------------------------- 1 | # Baggage Header Format Rationale 2 | 3 | This document provides a rationale for the decisions made for the `baggage` header format. 4 | 5 | ## General considerations 6 | 7 | - It should be human-readable. Cryptic headers would hide the fact of potential information disclosure. 8 | - It should be appendable (comma-separated) https://tools.ietf.org/html/rfc7230#page-24 so nodes 9 | can add baggage properties without parsing existing headers. 10 | - Keys are a single word in ASCII, and values should be a short string in UTF-8 or a derivative of a URL. 11 | 12 | ## Why a single header? 13 | 14 | Another option would be to use prefixed headers, such as `trace-context-X`, where `X` is a propagated 15 | field name. That could reduce the size of the data, particularly in http/2, where header 16 | compression can apply. 17 | 18 | Generally speaking, a `baggage` header may be split into multiple headers, and 19 | compression may be at the same ballpark as repeating values are converted into a single value 20 | in HPAC's dynamic collection. That said, there was no profiling made to make this decision. 21 | 22 | The approach with multiple headers has the following problems: 23 | - Name values limitation is much more pressing when the context name is used a part of a header 24 | name. 25 | - The comma-separated format similar to the proposed still needs to be supported in every individual header which makes parsing harder. 26 | - A single header is more comfortable to configure for tracing by many app servers, and makes CORS whitelisting more straightforward. 27 | 28 | ## Why not Vary-style? 29 | 30 | The [Vary](https://tools.ietf.org/html/rfc7231#section-7.1.4) approach is another alternative, 31 | where a fixed header is used to enumerate the list of other headers that actually contain the data. 32 | which could be used to accomplish the same. For example, `baggage: x-department; x-user-id; 33 | ttl=1` could tell the propagation to look at and forward the parent ID header, but only to the 34 | next hop. This has an advantage of HTTP header compression (hpack) and also weave-in with legacy 35 | tracing headers. 36 | 37 | Vary approach may be implemented as a new "header reference" value type `ref`. 38 | `baggage: x-b3-parentid;type=ref;ttl=1` if proven needed. 39 | 40 | ## Trimming of spaces 41 | 42 | The header should be human-readable and editable. Thus spaces are allowed before and after the comma, equal sign, and semicolon separators. It makes manual editing of headers less error-prone. It also allows better visual separation of fields when value modified manually. 43 | 44 | ## Why not use Structured Field Values for HTTP? 45 | 46 | We had several discussions about using [Structured Field Values for HTTP](https://datatracker.ietf.org/doc/html/rfc8941) and finally decided to go with this explicit definition of encoding. The rationale is to reduce complexity as we only need a small subset of the functionality for Baggage. For example, the closest match to this in Structured Field Values format is a dictionary, but it allows many features that adds complexity to Baggage (e.g., recursive inner lists with items of different types). While it may be possible to specify various restrictions to "subtract" functionality from RFC 8941, we believe it will make things more complex as we will end up in a much worse place - neither adhering to RFC 8941 nor having a simple specification. Further, we are not tied only to HTTP. In the future, we will expand the specification to other protocols and we want to retain a simpler encoding scheme. 47 | 48 | ## Lowercase header name 49 | 50 | In order to maximize the portability of the `baggage` header it is intentionally specified as a single lowercase word. 51 | While HTTP header names are case-insensitive, the `baggage` header is meant to be propagated over other protocols that have 52 | different constraints. Representing the header as a single lowercase word maximizes the use and reduces the chance of error 53 | when using the header in non-HTTP scenarios. 54 | 55 | ## Case sensitivity of keys 56 | 57 | There are few considerations why the names should be case sensitive: 58 | - some keys may be a URL query string parameters which are case sensitive 59 | - forcing lower case decreases readability of the names written in camel case 60 | 61 | ## Value format 62 | While the semantics of header values are specific to the producer and consumer of a key/value pair, we 63 | concluded that string values should be encoded at the producer and decoded at the consumer and that the specification should define a mechanism for that. 64 | 65 | URL encoding is a low-overhead way to encode Unicode characters for non-Latin characters in the values. URL encoding keeps a single word in Latin unchanged and accessible. 66 | 67 | 68 | ## Limits 69 | 70 | Lower limits ensure a minimum usefulness for users, while preventing processing, storage, and analysis of the header and its values from being overly burdensome. 71 | The minimum overall size and number of list members is given so that a user may reliably determine if a header value will be propagated, even through a platform with limited resources. 72 | -------------------------------------------------------------------------------- /baggage/README.md: -------------------------------------------------------------------------------- 1 | # Baggage Header 2 | 3 | Applications and libraries can use baggage to attach user-defined properties as part of a distributed request. A framework can automatically propagate this data to downstream services as part of the distributed context. The benefit of the cross-cutting nature of this context propagation means that it doesn't require any changes in the participating services (e.g., to change API signatures) to propagate these parameters. There are two main categories of use cases of baggage: 4 | 5 | 1. Use cases that enable better _observability_ of the system. 6 | 2. Use cases that enable better _control_ of the system. 7 | 8 | ## Example use cases 9 | The below are a few example use cases for baggage: 10 | 11 | ### Labeling synthetic traffic 12 | Baggage can be used to tag synthetic requests, such as those from blackbox monitoring tools or load/stress testing tools, e.g. `isProduction=false`. This can help services distinguish between production requests and synthetic requests. For example, this can be used by a system to partition error rate metrics to have separate time series for production and synthetics. This can enable configuring alert thresholds independently for production and synthetics. 13 | 14 | In addition, services can use baggage to customize behavior for synthetic requests. For example, a service can choose to redirect synthetic requests to a different cluster. 15 | 16 | ### Attributing resource usage to lines of business 17 | Baggage can be used to attribute infrastructure spend to a line of business. While higher level services will likely have dedicated services for each line of business, lower layers such as storage or messaging might be shared across multiple lines of businesses. A client or an upstream service can use baggage to attach information on the line of business associated with that request, e.g. `lob=youtube`. Downstream services can use this to attribute operations (e.g., number of storage operations) to a specific line of business. 18 | 19 | ### Traffic prioritization / Quality of Service 20 | Baggage can be used by services to prioritize requests to provide better QoS. In a system, there can be different types of requests with different priorities: e.g., in ride sharing app, request for making a trip is more important than adding a location to favorites. As this request propagates through the system, this information can be lost in the lower shared layers. To solve this, we can assign tiers to different requests and pass it as part of baggage. In case of a traffic spike, the services can prioritize based on it. 21 | 22 | ### Carrying routing instructions for requests during manual testing 23 | Baggage can be used to specify routing instructions for requests to control the behavior for manual testing. For example, a routing instruction could be: "when the request reaches service X, forward it to this specific service instance that I am running and debugging". 24 | 25 | ### Carrying fault injection instructions for chaos engineering 26 | Baggage can be used to deliver fault injection instructions to services for chaos engineering. For example, an instruction could be to increase the latency of a specific service call or to fail a call. Using the baggage mechanism enables expressing and propagating targeted and non-trivial fault instructions to various components in the context of specific requests. 27 | 28 | ## HTTP Format 29 | The HTTP format is defined [here](HTTP_HEADER_FORMAT.md) and the rationale is defined 30 | [here](HTTP_HEADER_FORMAT_RATIONALE.md). 31 | 32 | ## Binary Format 33 | TODO: add link here 34 | -------------------------------------------------------------------------------- /baggage/acknowledgments.md: -------------------------------------------------------------------------------- 1 | ## Acknowledgments 2 | 3 | Thanks to Armin Ruech, Jonathan Mace, Philippe Le Hegaret, Bastian Krol, and Reiley Yang for their contributions to this work. 4 | -------------------------------------------------------------------------------- /baggage/privacy.md: -------------------------------------------------------------------------------- 1 | # Privacy Considerations 2 | 3 | Requirements to propagate headers to downstream services, as well as storing values of these headers, open up potential privacy concerns. 4 | Using proprietary ways of context propagation, vendors and application developers could always encode information that contains user identifiable data. 5 | This standard makes it possible for systems to operate on a known, standardized header to restrict propagation of sensitive data in the baggage when crossing trust boundaries. 6 | 7 | Systems MUST assess the risk of header abuse. This section provides some considerations and initial assessment of the risk associated with storing and propagating this header. Systems may choose to inspect and remove sensitive information from the fields before processing or propagating the received data. All mutations should, however, conform to the list of mutations defined in this specification. 8 | 9 | ## Privacy of the baggage header 10 | 11 | The main purpose of this header is to provide additional system-specific information to other systems within the same trust-boundary. 12 | The `baggage` header may contain any value in any of the keys. 13 | As such, the `baggage` header can contain user-identifiable data, however no key or its value or properties is given semantic meaning by this specification. 14 | Applications using baggage should be aware that the keys and values can be propagated to other systems. Hence, they should remove any private information that they don't want to be propagated to other systems. -------------------------------------------------------------------------------- /baggage/security.md: -------------------------------------------------------------------------------- 1 | # Security Considerations 2 | 3 | Systems relying on the `baggage` headers should also follow all best practices for parsing potentially malicious data, including checking for header length and content of header values. 4 | These practices help to avoid buffer overflow, HTML injection, and other types of attacks. 5 | 6 | ## Information Exposure 7 | As mentioned in the privacy section, `baggage` may carry sensitive information. 8 | Application owners should either ensure that no proprietary or confidential information is stored in `baggage`, or they should ensure that `baggage` isn't present in requests that cross trust-boundaries. 9 | 10 | 11 | ## Other Risks 12 | Application owners need to make sure to test all code paths leading to the sending of the `baggage` header. For example, in web applications written in JavaScript, it is typical to make cross-origin requests. If one of these code paths leads to `baggage` headers being sent by cross-origin calls that are restricted using `Access-Control-Allow-Headers` [[FETCH]], it may fail. 13 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Propagation format for distributed context: Baggage 7 | 8 | 66 | 67 | 68 | 69 |
70 |
71 | 72 |
73 | 74 |
75 | 76 |
77 |
78 |
79 | 80 |
81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /reports/cr-2024-implementations.md: -------------------------------------------------------------------------------- 1 | During the Candidate Recommendation stage, the Working Group will audit: 2 | * the [OpenTelemetry implementations of Baggage](https://opentelemetry.io/docs/specs/otel/baggage/api/) to make sure they are following the W3C Baggage spec. 3 | * a sample of [OpenTelemetry's baggage test suites](https://github.com/open-telemetry/opentelemetry-specification/blob/main/spec-compliance-matrix.md#baggage) to verify they are testing the standard. 4 | 5 | # Implementations 6 | 7 | # Tests 8 | -------------------------------------------------------------------------------- /test/baggage/__init__.py: -------------------------------------------------------------------------------- 1 | from .baggage import Baggage, BaggageEntry, BaggageEntryProperty 2 | 3 | __all__ = ( 4 | 'Baggage', 5 | 'BaggageEntry', 6 | 'BaggageEntryProperty', 7 | ) 8 | -------------------------------------------------------------------------------- /test/baggage/baggage.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import re 4 | from urllib.parse import quote, unquote 5 | 6 | 7 | class Baggage(object): 8 | '''baggage regular expression reference implementation''' 9 | _DELIMITER_FORMAT_RE = re.compile('[ \t]*,[ \t]*') 10 | entries: list[BaggageEntry] = [] 11 | 12 | def __init__(self, entries: list[BaggageEntry] | None = None): 13 | if entries is not None: 14 | self.entries = entries 15 | 16 | @classmethod 17 | def from_string(cls, value: str) -> Baggage: 18 | '''parse a valid baggage string into a Baggage class''' 19 | if not isinstance(value, str): 20 | raise ValueError('value must be a string') 21 | 22 | # list-member 0*179( OWS "," OWS list-member ) 23 | value = re.split(Baggage._DELIMITER_FORMAT_RE, value) 24 | return Baggage([BaggageEntry.from_string(s) for s in value]) 25 | 26 | def to_string(self) -> str: 27 | ''' 28 | Serialize a Baggage class into an HTML header string 29 | 30 | Only the first 180 entries will be serialized even if more than 180 entries exist in the list. 31 | Entries will only be included until the limit of 8192 bytes is reached. 32 | Entries which serialize longer than the 4096 byte limit per entry are skipped. 33 | ''' 34 | out = "" 35 | for i, entry in enumerate(self.entries): 36 | entry_str = entry.to_string() 37 | 38 | # Prepend delimiter on all but the first entry 39 | if i > 0: 40 | out += "," 41 | out += entry_str 42 | 43 | return out 44 | 45 | 46 | class BaggageEntry(object): 47 | '''baggage entry class for baggage reference implementation''' 48 | # token = 1*tchar 49 | # tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA 50 | _KEY_FORMAT = r'[a-zA-Z0-9!#$%&\'*+\-.\^_`|~]+' 51 | # baggage-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E 52 | # value = *baggage-octet 53 | _VALUE_FORMAT = r'[\x23-\x2b\x2d-\x3a\x3c-\x5b\x5d-\x7e\x21]*' 54 | 55 | # list-member = key OWS "=" OWS value *( OWS ";" OWS property ) 56 | _DELIMITER_FORMAT_RE = re.compile(r'[ \t]*;[ \t]*') 57 | _KV_FORMAT_RE = re.compile( 58 | r'^(%s)[ \t]*=[ \t]*(%s)$' % (_KEY_FORMAT, _VALUE_FORMAT)) 59 | _KEY_RE = re.compile(r'^%s$' % _KEY_FORMAT) 60 | 61 | def __init__(self, key: str, value: str, properties: list[BaggageEntryProperty] = None): 62 | self.key = key 63 | self.value = value 64 | self.properties = properties or [] 65 | 66 | @classmethod 67 | def from_string(cls, value: str) -> BaggageEntry: 68 | '''parse a single baggage entry into a BaggageEntry class''' 69 | if not isinstance(value, str): 70 | raise ValueError('value must be a string') 71 | 72 | split_strs = re.split(BaggageEntry._DELIMITER_FORMAT_RE, value) 73 | kv = split_strs[0] 74 | property_strs = split_strs[1:] 75 | 76 | kv_match = re.match(BaggageEntry._KV_FORMAT_RE, kv.rstrip(" \t")) 77 | 78 | if kv_match is None: 79 | raise ValueError('failed to parse baggage entry key-value pair \'%s\'' % kv) 80 | 81 | key, value = kv_match[1], kv_match[2] 82 | 83 | properties = [] 84 | 85 | for s in property_strs: 86 | key_match = re.match(BaggageEntry._KEY_RE, s) 87 | kv_match = re.match(BaggageEntry._KV_FORMAT_RE, s) 88 | if key_match is not None: 89 | properties.append(BaggageEntryProperty(s)) 90 | elif kv_match is not None: 91 | properties.append(BaggageEntryProperty( 92 | kv_match[1], kv_match[2])) 93 | else: 94 | raise ValueError('property %s could not be parsed') 95 | 96 | return cls(key, unquote(value), properties) 97 | 98 | def to_string(self) -> str: 99 | '''serialize a BaggageEntry class into a string''' 100 | s = "%s=%s" % (self.key, quote(self.value)) 101 | for prop in self.properties: 102 | s += ";%s" % prop.to_string() 103 | return s 104 | 105 | 106 | class BaggageEntryProperty(object): 107 | def __init__(self, key: str, value: str = None) -> None: 108 | self.key = key 109 | self.value = value 110 | 111 | def to_string(self): 112 | if self.value is None: 113 | return self.key 114 | 115 | return '%s=%s' % (self.key, self.value) 116 | -------------------------------------------------------------------------------- /test/test_baggage.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import urllib.parse 3 | 4 | from baggage import Baggage, BaggageEntry 5 | 6 | 7 | class BaggageTest(unittest.TestCase): 8 | def test_ctor_default(self): 9 | baggage = Baggage() 10 | self.assertEqual(baggage.entries, []) 11 | 12 | def test_parse_simple(self): 13 | baggage = Baggage().from_string("SomeKey=SomeValue") 14 | self.assertEqual(len(baggage.entries), 1) 15 | entry = baggage.entries[0] 16 | self.assertEqual(entry.key, "SomeKey") 17 | self.assertEqual(entry.value, "SomeValue") 18 | self.assertEqual(len(entry.properties), 0) 19 | 20 | def test_parse_multiple(self): 21 | baggage = Baggage().from_string( 22 | "SomeKey=SomeValue;SomeProp,SomeKey2=SomeValue2;ValueProp=PropVal") 23 | self.assertEqual(len(baggage.entries), 2) 24 | entry1 = baggage.entries[0] 25 | entry2 = baggage.entries[1] 26 | 27 | self.assertEqual(entry1.key, "SomeKey") 28 | self.assertEqual(entry1.value, "SomeValue") 29 | self.assertEqual(len(entry1.properties), 1) 30 | self.assertEqual(entry1.properties[0].key, "SomeProp") 31 | self.assertEqual(entry1.properties[0].value, None) 32 | 33 | self.assertEqual(entry2.key, "SomeKey2") 34 | self.assertEqual(entry2.value, "SomeValue2") 35 | self.assertEqual(len(entry2.properties), 1) 36 | self.assertEqual(entry2.properties[0].key, "ValueProp") 37 | self.assertEqual(entry2.properties[0].value, "PropVal") 38 | 39 | def test_parse_multiple_ows(self): 40 | baggage = Baggage().from_string( 41 | "SomeKey \t = \t SomeValue \t ; \t SomeProp \t , \t SomeKey2 \t = \t SomeValue2 \t ; \t ValueProp \t = \t PropVal") 42 | self.assertEqual(len(baggage.entries), 2) 43 | entry1 = baggage.entries[0] 44 | entry2 = baggage.entries[1] 45 | 46 | self.assertEqual(entry1.key, "SomeKey") 47 | self.assertEqual(entry1.value, "SomeValue") 48 | self.assertEqual(len(entry1.properties), 1) 49 | self.assertEqual(entry1.properties[0].key, "SomeProp") 50 | self.assertEqual(entry1.properties[0].value, None) 51 | 52 | self.assertEqual(entry2.key, "SomeKey2") 53 | self.assertEqual(entry2.value, "SomeValue2") 54 | self.assertEqual(len(entry2.properties), 1) 55 | self.assertEqual(entry2.properties[0].key, "ValueProp") 56 | self.assertEqual(entry2.properties[0].value, "PropVal") 57 | 58 | def test_parse_multiple_kv_property(self): 59 | baggage = Baggage().from_string( 60 | "SomeKey=SomeValue;SomePropKey=SomePropValue,SomeKey2=SomeValue2;SomePropKey2=SomePropValue2") 61 | self.assertEqual(len(baggage.entries), 2) 62 | entry1 = baggage.entries[0] 63 | entry2 = baggage.entries[1] 64 | self.assertEqual(entry1.key, "SomeKey") 65 | self.assertEqual(entry1.value, "SomeValue") 66 | self.assertEqual(entry1.properties[0].key, "SomePropKey") 67 | self.assertEqual(entry1.properties[0].value, "SomePropValue") 68 | self.assertEqual(entry2.key, "SomeKey2") 69 | self.assertEqual(entry2.value, "SomeValue2") 70 | self.assertEqual(entry2.properties[0].key, "SomePropKey2") 71 | self.assertEqual(entry2.properties[0].value, "SomePropValue2") 72 | 73 | def test_parse_multiple_kv_property_ows(self): 74 | baggage = Baggage().from_string( 75 | "SomeKey \t = \t SomeValue \t ; \t SomePropKey=SomePropValue \t , \t SomeKey2 \t = \t SomeValue2 \t ; \t SomePropKey2 \t = \t SomePropValue2") 76 | self.assertEqual(len(baggage.entries), 2) 77 | entry1 = baggage.entries[0] 78 | entry2 = baggage.entries[1] 79 | self.assertEqual(entry1.key, "SomeKey") 80 | self.assertEqual(entry1.value, "SomeValue") 81 | self.assertEqual(entry1.properties[0].key, "SomePropKey") 82 | self.assertEqual(entry1.properties[0].value, "SomePropValue") 83 | self.assertEqual(entry2.key, "SomeKey2") 84 | self.assertEqual(entry2.value, "SomeValue2") 85 | self.assertEqual(entry2.properties[0].key, "SomePropKey2") 86 | self.assertEqual(entry2.properties[0].value, "SomePropValue2") 87 | 88 | 89 | class BaggageEntryTest(unittest.TestCase): 90 | def test_ctor_default(self): 91 | entry = BaggageEntry("SomeKey", "SomeValue") 92 | self.assertEqual(entry.key, "SomeKey") 93 | self.assertEqual(entry.value, "SomeValue") 94 | self.assertEqual(len(entry.properties), 0) 95 | 96 | def test_parse_simple(self): 97 | entry = BaggageEntry.from_string("SomeKey=SomeValue") 98 | self.assertEqual(entry.key, "SomeKey") 99 | self.assertEqual(entry.value, "SomeValue") 100 | self.assertEqual(len(entry.properties), 0) 101 | 102 | def test_parse_multiple_equals(self): 103 | entry = BaggageEntry.from_string("SomeKey=SomeValue=equals") 104 | self.assertEqual(entry.key, "SomeKey") 105 | self.assertEqual(entry.value, "SomeValue=equals") 106 | self.assertEqual(len(entry.properties), 0) 107 | 108 | def test_parse_percent_encoded(self): 109 | value = "\t \"\';=asdf!@#$%^&*()" 110 | encoded_value = urllib.parse.quote(value) 111 | entry = BaggageEntry.from_string("SomeKey=%s" % (encoded_value)) 112 | self.assertEqual(entry.key, "SomeKey") 113 | self.assertEqual(entry.value, value) 114 | self.assertEqual(entry.to_string( 115 | ), "SomeKey=%09%20%22%27%3B%3Dasdf%21%40%23%24%25%5E%26%2A%28%29") 116 | 117 | def test_parse_property(self): 118 | entry = BaggageEntry.from_string("SomeKey=SomeValue;SomeProp") 119 | self.assertEqual(entry.key, "SomeKey") 120 | self.assertEqual(entry.value, "SomeValue") 121 | self.assertEqual(len(entry.properties), 1) 122 | self.assertEqual(entry.properties[0].key, "SomeProp") 123 | 124 | def test_parse_multi_property(self): 125 | entry = BaggageEntry.from_string( 126 | "SomeKey=SomeValue;SomeProp;SecondProp=PropValue") 127 | self.assertEqual(entry.key, "SomeKey") 128 | self.assertEqual(entry.value, "SomeValue") 129 | self.assertEqual(len(entry.properties), 2) 130 | self.assertEqual(entry.properties[0].key, "SomeProp") 131 | self.assertEqual(entry.properties[0].value, None) 132 | self.assertEqual(entry.properties[1].key, "SecondProp") 133 | self.assertEqual(entry.properties[1].value, 'PropValue') 134 | 135 | def test_parse_kv_property(self): 136 | entry = BaggageEntry.from_string( 137 | "SomeKey=SomeValue;SomePropKey=SomePropValue") 138 | self.assertEqual(entry.key, "SomeKey") 139 | self.assertEqual(entry.value, "SomeValue") 140 | self.assertEqual(len(entry.properties), 1) 141 | self.assertEqual(entry.properties[0].key, "SomePropKey") 142 | self.assertEqual(entry.properties[0].value, "SomePropValue") 143 | 144 | def test_parse_simple_ows(self): 145 | entry = BaggageEntry.from_string("SomeKey \t = \t SomeValue \t ") 146 | self.assertEqual(entry.key, "SomeKey") 147 | self.assertEqual(entry.value, "SomeValue") 148 | self.assertEqual(len(entry.properties), 0) 149 | 150 | def test_parse_percent_encoded_ows(self): 151 | value = "\t \"\';=asdf!@#$%^&*()" 152 | encoded_value = urllib.parse.quote(value) 153 | entry = BaggageEntry.from_string( 154 | "SomeKey \t = \t %s \t " % (encoded_value)) 155 | self.assertEqual(entry.key, "SomeKey") 156 | self.assertEqual(entry.value, value) 157 | 158 | def test_parse_property_ows(self): 159 | entry = BaggageEntry.from_string( 160 | "SomeKey \t = \t SomeValue \t ; \t SomeProp") 161 | self.assertEqual(entry.key, "SomeKey") 162 | self.assertEqual(entry.value, "SomeValue") 163 | self.assertEqual(len(entry.properties), 1) 164 | self.assertEqual(entry.properties[0].key, "SomeProp") 165 | 166 | def test_parse_multi_property_ows(self): 167 | entry = BaggageEntry.from_string( 168 | "SomeKey \t = \t SomeValue \t ; \t SomeProp \t ; \t SecondProp \t = \t PropValue") 169 | self.assertEqual(entry.key, "SomeKey") 170 | self.assertEqual(entry.value, "SomeValue") 171 | self.assertEqual(len(entry.properties), 2) 172 | self.assertEqual(entry.properties[0].key, "SomeProp") 173 | self.assertEqual(entry.properties[0].value, None) 174 | self.assertEqual(entry.properties[1].key, "SecondProp") 175 | self.assertEqual(entry.properties[1].value, 'PropValue') 176 | 177 | def test_parse_kv_property_ows(self): 178 | entry = BaggageEntry.from_string( 179 | "SomeKey \t = \t SomeValue \t ; \t SomePropKey \t = \t SomePropValue") 180 | self.assertEqual(entry.key, "SomeKey") 181 | self.assertEqual(entry.value, "SomeValue") 182 | self.assertEqual(len(entry.properties), 1) 183 | self.assertEqual(entry.properties[0].key, "SomePropKey") 184 | self.assertEqual(entry.properties[0].value, "SomePropValue") 185 | 186 | 187 | class LimitsTest(unittest.TestCase): 188 | def test_serialize_at_least_64(self): 189 | '''A platform MUST propagate all list-members up to at least 64 list-members including any list-members added by the platform.''' 190 | baggage = Baggage([BaggageEntry("key%s" % x, "value") 191 | for x in range(64)]) 192 | baggage_str = baggage.to_string() 193 | entry_strs = baggage_str.split(",") 194 | self.assertEqual(len(entry_strs), 64) 195 | 196 | def test_serialize_long_entry(self): 197 | '''A platform MUST propagate all list-members including any list-members added by the platform if the resulting baggage-string would be 8192 bytes or less.''' 198 | long_value = '0123456789' * 819 199 | baggage = Baggage([BaggageEntry("a", long_value)]) 200 | # a 1 character 201 | # = 1 character 202 | # 0123456789 10 characters * 819 = 8190 characters 203 | # total 8192 characters 204 | baggage_str = baggage.to_string() 205 | self.assertEqual(len(baggage_str), 8192) 206 | 207 | def test_serialize_many_entries(self): 208 | # 512 entries with 15 bytes + 1 trailing comma 209 | baggage = Baggage( 210 | [BaggageEntry("{:03d}".format(x), '0123456789a') for x in range(512)]) 211 | 212 | # last entry is 16 bytes 213 | baggage_str = baggage.to_string() + 'b' 214 | self.assertEqual(len(baggage_str), 8192) 215 | 216 | if __name__ == '__main__': 217 | unittest.main() 218 | -------------------------------------------------------------------------------- /w3c.json: -------------------------------------------------------------------------------- 1 | { 2 | "group": "wg/distributed-tracing" 3 | , "contacts": ["plehegar"] 4 | , "policy": "open" 5 | , "repo-type": "rec-track" 6 | } 7 | --------------------------------------------------------------------------------