diff --git a/actions/setup/js/send_otlp_span.cjs b/actions/setup/js/send_otlp_span.cjs index 9a49523e6f1..433fb5ecd2f 100644 --- a/actions/setup/js/send_otlp_span.cjs +++ b/actions/setup/js/send_otlp_span.cjs @@ -873,6 +873,27 @@ function parseOTLPHeaders(raw) { return result; } +const EMPTY_OTLP_AUTHORIZATION_SCHEMES = new Set([ + "api-key", + "apikey", + "basic", + "bearer", + "concealed", + "digest", + "dpop", + "dsn", + "gnap", + "hoba", + "mutual", + "negotiate", + "oauth", + "privatetoken", + "scram-sha-1", + "scram-sha-256", + "sentry", + "vapid", +]); + /** * @param {string} raw * @returns {boolean} @@ -881,7 +902,7 @@ function hasEmptyOTLPAuthorizationHeader(raw) { const headers = parseOTLPHeaders(raw); return Object.entries(headers).some(([key, value]) => { const normalizedKey = key.toLowerCase(); - return (normalizedKey === "authorization" || normalizedKey === "x-sentry-auth") && value === ""; + return (normalizedKey === "authorization" || normalizedKey === "x-sentry-auth") && (value === "" || EMPTY_OTLP_AUTHORIZATION_SCHEMES.has(value.toLowerCase())); }); } diff --git a/actions/setup/js/send_otlp_span.test.cjs b/actions/setup/js/send_otlp_span.test.cjs index a229b31fc24..b1289d83472 100644 --- a/actions/setup/js/send_otlp_span.test.cjs +++ b/actions/setup/js/send_otlp_span.test.cjs @@ -6705,6 +6705,17 @@ describe("parseOTLPEndpoints", () => { expect(parseOTLPEndpoints()).toEqual([]); }); + it.each(["Authorization=ApiKey", "Authorization=Bearer", "Authorization=Digest%20%20", "x-sentry-auth=ApiKey", "x-sentry-auth=DSN"])("drops an endpoint when its %s header has an auth scheme without credentials", headers => { + process.env.GH_AW_OTLP_ENDPOINTS = JSON.stringify([{ url: "https://traces.example.com:4317", headers }]); + expect(parseOTLPEndpoints()).toEqual([]); + }); + + it("keeps an endpoint when its authorization header has credentials", () => { + const endpoint = { url: "https://traces.example.com:4317", headers: "Authorization=ApiKey secret-token" }; + process.env.GH_AW_OTLP_ENDPOINTS = JSON.stringify([endpoint]); + expect(parseOTLPEndpoints()).toEqual([endpoint]); + }); + it("keeps an endpoint when an unrelated header is empty", () => { process.env.GH_AW_OTLP_ENDPOINTS = JSON.stringify([{ url: "https://traces.example.com:4317", headers: "X-Tenant=" }]); expect(parseOTLPEndpoints()).toEqual([{ url: "https://traces.example.com:4317", headers: "X-Tenant=" }]); diff --git a/docs/src/content/docs/reference/open-telemetry.mdx b/docs/src/content/docs/reference/open-telemetry.mdx index d817b1d52f4..18b264e6add 100644 --- a/docs/src/content/docs/reference/open-telemetry.mdx +++ b/docs/src/content/docs/reference/open-telemetry.mdx @@ -34,6 +34,8 @@ observability: Once configured, gh-aw exports built-in workflow spans such as setup and conclusion events to the configured OTLP backend. +Use the bare secret expression for authorization headers, as shown above, rather than adding an authentication scheme prefix. For Sentry endpoints, gh-aw automatically rewrites `Authorization` to `x-sentry-auth`, so no prefix is needed. + ### Organization-wide defaults When a workflow does not configure `observability.otlp` (in its own frontmatter or through an import), the compiler falls back to a default OTLP configuration read from the GitHub Actions environment: