@devtune/ai-traffic 0.10.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cjs/index.js CHANGED
@@ -4,7 +4,7 @@
4
4
  * Exposes middleware helpers, route wrappers, and registry matching utilities.
5
5
  */
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.FALLBACK_AI_REFERRER_REGISTRY_ENTRIES = exports.detectAiReferrer = exports.detectAiCrawler = exports.parseAiBotRegistryPayload = exports.matchAiBotRegistryEntry = exports.compileAiBotRegistryEntries = exports.FALLBACK_AI_BOT_REGISTRY_ENTRIES = exports.resolveClientIp = exports.detectTrustedProxy = exports.SDK_VERSION_HEADER = exports.SDK_VERSION = exports.withDevTuneAiTrafficRoute = exports.mightBeAiBotUserAgent = exports.createDevTuneAiTrafficMiddleware = exports.createDevTuneAiTraffic = exports.DevTuneAiTrafficClient = exports.DEFAULT_REGISTRY_URL = exports.DEFAULT_INGEST_ENDPOINT = void 0;
7
+ exports.FALLBACK_AI_REFERRER_REGISTRY_ENTRIES = exports.detectAiReferrer = exports.detectAiCrawler = exports.parseAiBotRegistryPayload = exports.matchAiBotRegistryEntry = exports.compileAiBotRegistryEntries = exports.FALLBACK_AI_BOT_REGISTRY_ENTRIES = exports.resolveClientIp = exports.detectTrustedProxy = exports.WEB_BOT_AUTH_FORWARDED_HEADERS = exports.SDK_VERSION_HEADER = exports.SDK_VERSION = exports.withDevTuneAiTrafficRoute = exports.mightBeAiBotUserAgent = exports.createDevTuneAiTrafficMiddleware = exports.createDevTuneAiTraffic = exports.DevTuneAiTrafficClient = exports.DEFAULT_REGISTRY_URL = exports.DEFAULT_INGEST_ENDPOINT = void 0;
8
8
  var client_js_1 = require("./client.js");
9
9
  Object.defineProperty(exports, "DEFAULT_INGEST_ENDPOINT", { enumerable: true, get: function () { return client_js_1.DEFAULT_INGEST_ENDPOINT; } });
10
10
  Object.defineProperty(exports, "DEFAULT_REGISTRY_URL", { enumerable: true, get: function () { return client_js_1.DEFAULT_REGISTRY_URL; } });
@@ -16,6 +16,8 @@ Object.defineProperty(exports, "withDevTuneAiTrafficRoute", { enumerable: true,
16
16
  var version_js_1 = require("./version.js");
17
17
  Object.defineProperty(exports, "SDK_VERSION", { enumerable: true, get: function () { return version_js_1.SDK_VERSION; } });
18
18
  Object.defineProperty(exports, "SDK_VERSION_HEADER", { enumerable: true, get: function () { return version_js_1.SDK_VERSION_HEADER; } });
19
+ var web_bot_auth_js_1 = require("./web-bot-auth.js");
20
+ Object.defineProperty(exports, "WEB_BOT_AUTH_FORWARDED_HEADERS", { enumerable: true, get: function () { return web_bot_auth_js_1.WEB_BOT_AUTH_FORWARDED_HEADERS; } });
19
21
  var client_ip_js_1 = require("./client-ip.js");
20
22
  Object.defineProperty(exports, "detectTrustedProxy", { enumerable: true, get: function () { return client_ip_js_1.detectTrustedProxy; } });
21
23
  Object.defineProperty(exports, "resolveClientIp", { enumerable: true, get: function () { return client_ip_js_1.resolveClientIp; } });
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH,yCAgBqB;AAfnB,oHAAA,uBAAuB,OAAA;AACvB,iHAAA,oBAAoB,OAAA;AACpB,mHAAA,sBAAsB,OAAA;AACtB,mHAAA,sBAAsB,OAAA;AACtB,6HAAA,gCAAgC,OAAA;AAChC,kHAAA,qBAAqB,OAAA;AACrB,sHAAA,yBAAyB,OAAA;AAW3B,2CAA+D;AAAtD,yGAAA,WAAW,OAAA;AAAE,gHAAA,kBAAkB,OAAA;AAExC,+CAIwB;AAHtB,kHAAA,kBAAkB,OAAA;AAClB,+GAAA,eAAe,OAAA;AAIjB,6CASuB;AARrB,+HAAA,gCAAgC,OAAA;AAChC,0HAAA,2BAA2B,OAAA;AAC3B,sHAAA,uBAAuB,OAAA;AACvB,wHAAA,yBAAyB,OAAA;AAO3B,+CAUwB;AATtB,+GAAA,eAAe,OAAA;AACf,gHAAA,gBAAgB,OAAA;AAChB,qIAAA,qCAAqC,OAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAEH,yCAgBqB;AAfnB,oHAAA,uBAAuB,OAAA;AACvB,iHAAA,oBAAoB,OAAA;AACpB,mHAAA,sBAAsB,OAAA;AACtB,mHAAA,sBAAsB,OAAA;AACtB,6HAAA,gCAAgC,OAAA;AAChC,kHAAA,qBAAqB,OAAA;AACrB,sHAAA,yBAAyB,OAAA;AAW3B,2CAA+D;AAAtD,yGAAA,WAAW,OAAA;AAAE,gHAAA,kBAAkB,OAAA;AAExC,qDAAoG;AAA3F,iIAAA,8BAA8B,OAAA;AAEvC,+CAIwB;AAHtB,kHAAA,kBAAkB,OAAA;AAClB,+GAAA,eAAe,OAAA;AAIjB,6CASuB;AARrB,+HAAA,gCAAgC,OAAA;AAChC,0HAAA,2BAA2B,OAAA;AAC3B,sHAAA,uBAAuB,OAAA;AACvB,wHAAA,yBAAyB,OAAA;AAO3B,+CAUwB;AATtB,+GAAA,eAAe,OAAA;AACf,gHAAA,gBAAgB,OAAA;AAChB,qIAAA,qCAAqC,OAAA"}
package/dist/cjs/node.js CHANGED
@@ -14,13 +14,17 @@ function createDevTuneAiTrafficNodeHandler(options) {
14
14
  headers: createHeadersLike(request),
15
15
  url: createRequestUrl(request, origin),
16
16
  ip: request.socket?.remoteAddress ?? null,
17
+ method: request.method,
18
+ targetUri: createTargetUri(request),
17
19
  };
18
20
  client.refreshRegistryIfStale();
19
21
  if (!client.mightTrackRequest(requestLike)) {
20
22
  return;
21
23
  }
24
+ // Recorded after the response, so keep the arrival time a signature is checked against.
25
+ const receivedAt = client.signedArrivalTime(requestLike);
22
26
  response.once("finish", () => {
23
- client.trackRequest(requestLike, undefined, response.statusCode);
27
+ client.trackRequest(requestLike, undefined, response.statusCode, receivedAt);
24
28
  });
25
29
  };
26
30
  }
@@ -29,6 +33,9 @@ function createHeadersLike(request) {
29
33
  get(name) {
30
34
  return getHeader(request, name);
31
35
  },
36
+ getCombined(name) {
37
+ return ownCombinedHeaderValue(request.headers, name);
38
+ },
32
39
  };
33
40
  }
34
41
  function createRequestUrl(request, configuredOrigin) {
@@ -40,11 +47,30 @@ function createRequestUrl(request, configuredOrigin) {
40
47
  return requestPath;
41
48
  }
42
49
  }
43
- function getRequestOrigin(request) {
50
+ /**
51
+ * The URL a signature covers, as the request line and the raw host (`X-Forwarded-Host`, then `Host`) carried
52
+ * it. Never parsed, so a signed `https://EXAMPLE.com:443/guide` is forwarded
53
+ * byte for byte. A configured `origin` never replaces the host the agent
54
+ * signed. An absolute-form request target is already the whole URL. Null
55
+ * when there is no host to build it from.
56
+ */
57
+ function createTargetUri(request) {
58
+ const requestPath = request.url ?? "/";
59
+ // An absolute-form target is already the whole URL; any other non-path
60
+ // form (`*`, authority-form) is not a URL a signature can cover.
61
+ if (!requestPath.startsWith("/")) {
62
+ return /^https?:\/\//iu.test(requestPath) ? requestPath : null;
63
+ }
64
+ const host = getRequestHost(request);
65
+ return host ? `${getRequestOrigin(request, host)}${requestPath}` : null;
66
+ }
67
+ function getRequestOrigin(request, host = getRequestHost(request) || "localhost") {
44
68
  const forwardedProtocol = getForwardedHeader(request, "x-forwarded-proto");
45
69
  const protocol = forwardedProtocol ?? (request.socket?.encrypted ? "https" : "http");
46
- const host = getForwardedHeader(request, "x-forwarded-host") ?? getHeader(request, "host");
47
- return `${protocol}://${host || "localhost"}`;
70
+ return `${protocol}://${host}`;
71
+ }
72
+ function getRequestHost(request) {
73
+ return getForwardedHeader(request, "x-forwarded-host") ?? getHeader(request, "host");
48
74
  }
49
75
  function getHeader(request, name) {
50
76
  return headerValue(request.headers[name.toLowerCase()]);
@@ -59,4 +85,27 @@ function headerValue(value) {
59
85
  }
60
86
  return value?.[0] ?? null;
61
87
  }
88
+ /**
89
+ * A covered field's value, read only as an own property: the names come from
90
+ * a client's `Signature-Input`, so `constructor` or `__proto__` must not
91
+ * reach an inherited value.
92
+ */
93
+ function ownCombinedHeaderValue(headers, name) {
94
+ const key = name.toLowerCase();
95
+ return Object.prototype.hasOwnProperty.call(headers, key)
96
+ ? combinedHeaderValue(headers[key])
97
+ : null;
98
+ }
99
+ /**
100
+ * Repeated values combine with ", " as RFC 9110 field combination does. Only
101
+ * a string or an array of strings is a value; anything else is absent.
102
+ */
103
+ function combinedHeaderValue(value) {
104
+ if (typeof value === "string") {
105
+ return value;
106
+ }
107
+ return Array.isArray(value) && value.length > 0 && value.every((item) => typeof item === "string")
108
+ ? value.join(", ")
109
+ : null;
110
+ }
62
111
  //# sourceMappingURL=node.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"node.js","sourceRoot":"","sources":["../../../node/src/node.ts"],"names":[],"mappings":";AAAA;;;GAGG;;AA4BH,8EAwBC;AAlDD,oDAI6B;AAsB7B,SAAgB,iCAAiC,CAAC,OAAoC;IACpF,MAAM,EAAE,MAAM,EAAE,GAAG,aAAa,EAAE,GAAG,OAAO,CAAC;IAC7C,MAAM,MAAM,GAAG,IAAA,mCAAsB,EAAC,aAAa,CAAC,CAAC;IAErD,OAAO,SAAS,2BAA2B,CACzC,OAAoC,EACpC,QAAsC;QAEtC,MAAM,WAAW,GAAG;YAClB,OAAO,EAAE,iBAAiB,CAAC,OAAO,CAAC;YACnC,GAAG,EAAE,gBAAgB,CAAC,OAAO,EAAE,MAAM,CAAC;YACtC,EAAE,EAAE,OAAO,CAAC,MAAM,EAAE,aAAa,IAAI,IAAI;SAC1C,CAAC;QAEF,MAAM,CAAC,sBAAsB,EAAE,CAAC;QAEhC,IAAI,CAAC,MAAM,CAAC,iBAAiB,CAAC,WAAW,CAAC,EAAE,CAAC;YAC3C,OAAO;QACT,CAAC;QAED,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,EAAE;YAC3B,MAAM,CAAC,YAAY,CAAC,WAAW,EAAE,SAAS,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC;QACnE,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;AACJ,CAAC;AAED,SAAS,iBAAiB,CAAC,OAAoC;IAC7D,OAAO;QACL,GAAG,CAAC,IAAI;YACN,OAAO,SAAS,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QAClC,CAAC;KACF,CAAC;AACJ,CAAC;AAED,SAAS,gBAAgB,CAAC,OAAoC,EAAE,gBAAyB;IACvF,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC;IAEvC,IAAI,CAAC;QACH,OAAO,IAAI,GAAG,CAAC,WAAW,EAAE,gBAAgB,IAAI,gBAAgB,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;IACxF,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,WAAW,CAAC;IACrB,CAAC;AACH,CAAC;AAED,SAAS,gBAAgB,CAAC,OAAoC;IAC5D,MAAM,iBAAiB,GAAG,kBAAkB,CAAC,OAAO,EAAE,mBAAmB,CAAC,CAAC;IAC3E,MAAM,QAAQ,GAAG,iBAAiB,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IACrF,MAAM,IAAI,GAAG,kBAAkB,CAAC,OAAO,EAAE,kBAAkB,CAAC,IAAI,SAAS,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAE3F,OAAO,GAAG,QAAQ,MAAM,IAAI,IAAI,WAAW,EAAE,CAAC;AAChD,CAAC;AAED,SAAS,SAAS,CAAC,OAAoC,EAAE,IAAY;IACnE,OAAO,WAAW,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;AAC1D,CAAC;AAED,SAAS,kBAAkB,CAAC,OAAoC,EAAE,IAAY;IAC5E,MAAM,KAAK,GAAG,SAAS,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IAEvC,OAAO,KAAK,EAAE,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,IAAI,CAAC;AACjD,CAAC;AAED,SAAS,WAAW,CAAC,KAAkB;IACrC,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;AAC5B,CAAC"}
1
+ {"version":3,"file":"node.js","sourceRoot":"","sources":["../../../node/src/node.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,159 @@
1
+ "use strict";
2
+ /**
3
+ * ABOUTME: Vercel OIDC credentials for ingest, used instead of an ingest key on Vercel.
4
+ * Filters the per-request token header and exchanges it for DevTune's audience with a cache.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.VercelOidcTokenExchanger = exports.VercelOidcExchangeError = exports.TOKEN_EXPIRY_MARGIN_MS = exports.VERCEL_TOKEN_EXCHANGE_URL = exports.DEVTUNE_INGEST_AUDIENCE = exports.DEVTUNE_PROJECT_HEADER = exports.VERCEL_OIDC_TOKEN_HEADER = void 0;
8
+ exports.isDevTuneProjectId = isDevTuneProjectId;
9
+ exports.readVercelOidcTokenExpiry = readVercelOidcTokenExpiry;
10
+ /** Vercel sets this on every request to a deployment that has OIDC enabled. */
11
+ exports.VERCEL_OIDC_TOKEN_HEADER = "x-vercel-oidc-token";
12
+ /** Tells the ingest route which DevTune project's Vercel link to check the token against. */
13
+ exports.DEVTUNE_PROJECT_HEADER = "x-devtune-project";
14
+ exports.DEVTUNE_INGEST_AUDIENCE = "https://devtune.ai/ai-traffic-ingest";
15
+ exports.VERCEL_TOKEN_EXCHANGE_URL = "https://oidc.vercel.com/~token";
16
+ const VERCEL_OIDC_ISSUER = "https://oidc.vercel.com";
17
+ const MAX_TOKEN_LENGTH = 8_192;
18
+ const TOKEN_EXCHANGE_TIMEOUT_MS = 5_000;
19
+ const BASE64URL_SEGMENT = /^[A-Za-z0-9_-]+$/u;
20
+ const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu;
21
+ /** Tokens this close to expiry are neither captured, exchanged nor served from the cache. */
22
+ exports.TOKEN_EXPIRY_MARGIN_MS = 60_000;
23
+ function isDevTuneProjectId(value) {
24
+ return UUID_PATTERN.test(value);
25
+ }
26
+ /**
27
+ * Returns the token's expiry in milliseconds when the value looks like a live
28
+ * Vercel OIDC token, or null for anything else.
29
+ *
30
+ * The signature is not checked: the SDK has no way to, and the ingest route
31
+ * verifies it. This filter only keeps a client-supplied junk header, a token
32
+ * from another issuer or an expired token from replacing a good one.
33
+ */
34
+ function readVercelOidcTokenExpiry(value, nowMs) {
35
+ if (value.length > MAX_TOKEN_LENGTH) {
36
+ return null;
37
+ }
38
+ const segments = value.split(".");
39
+ if (segments.length !== 3 || !segments.every((segment) => BASE64URL_SEGMENT.test(segment))) {
40
+ return null;
41
+ }
42
+ const claims = decodeJsonSegment(segments[1] ?? "");
43
+ if (!claims ||
44
+ typeof claims.iss !== "string" ||
45
+ (claims.iss !== VERCEL_OIDC_ISSUER && !claims.iss.startsWith(`${VERCEL_OIDC_ISSUER}/`)) ||
46
+ typeof claims.owner_id !== "string" ||
47
+ typeof claims.project_id !== "string" ||
48
+ typeof claims.exp !== "number" ||
49
+ !Number.isFinite(claims.exp)) {
50
+ return null;
51
+ }
52
+ const expiresAtMs = claims.exp * 1000;
53
+ return expiresAtMs - nowMs > exports.TOKEN_EXPIRY_MARGIN_MS ? expiresAtMs : null;
54
+ }
55
+ /**
56
+ * Thrown when Vercel answers an exchange but not with a usable token. Carries
57
+ * a fixed `kind` and the status, so callers can report the failure without
58
+ * reading any message text.
59
+ */
60
+ class VercelOidcExchangeError extends Error {
61
+ kind;
62
+ status;
63
+ constructor(kind, status) {
64
+ super(kind === "exchange_status"
65
+ ? `Vercel OIDC token exchange failed with status ${status}`
66
+ : "Vercel OIDC token exchange returned an unusable response");
67
+ this.kind = kind;
68
+ this.status = status;
69
+ this.name = "VercelOidcExchangeError";
70
+ }
71
+ }
72
+ exports.VercelOidcExchangeError = VercelOidcExchangeError;
73
+ /**
74
+ * Exchanges a deployment's Vercel OIDC token for one scoped to DevTune's
75
+ * ingest audience.
76
+ *
77
+ * Results are cached in memory by source token until their expiry, less the
78
+ * margin, and expired entries are dropped on every call, so the steady state
79
+ * is one entry per live source token. Callers that need the same token at once
80
+ * share one request. Vercel's answers are reported as a
81
+ * `VercelOidcExchangeError`; anything the fetch itself throws is passed on.
82
+ */
83
+ class VercelOidcTokenExchanger {
84
+ fetchImpl;
85
+ nowMs;
86
+ cache = new Map();
87
+ inFlight = new Map();
88
+ constructor(fetchImpl, nowMs) {
89
+ this.fetchImpl = fetchImpl;
90
+ this.nowMs = nowMs;
91
+ }
92
+ async exchange(sourceToken) {
93
+ const nowMs = this.nowMs();
94
+ for (const [key, entry] of this.cache) {
95
+ if (entry.usableUntilMs <= nowMs) {
96
+ this.cache.delete(key);
97
+ }
98
+ }
99
+ const cached = this.cache.get(sourceToken);
100
+ if (cached) {
101
+ return cached.token;
102
+ }
103
+ const pending = this.inFlight.get(sourceToken);
104
+ if (pending) {
105
+ return pending;
106
+ }
107
+ const request = this.requestExchange(sourceToken).finally(() => {
108
+ this.inFlight.delete(sourceToken);
109
+ });
110
+ this.inFlight.set(sourceToken, request);
111
+ return request;
112
+ }
113
+ async requestExchange(sourceToken) {
114
+ const response = await this.fetchImpl(exports.VERCEL_TOKEN_EXCHANGE_URL, {
115
+ body: JSON.stringify({ token: sourceToken, aud: exports.DEVTUNE_INGEST_AUDIENCE }),
116
+ headers: {
117
+ accept: "application/json",
118
+ "content-type": "application/json",
119
+ },
120
+ method: "POST",
121
+ signal: AbortSignal.timeout(TOKEN_EXCHANGE_TIMEOUT_MS),
122
+ });
123
+ if (!response.ok) {
124
+ throw new VercelOidcExchangeError("exchange_status", response.status);
125
+ }
126
+ const payload = await response.json().catch(() => null);
127
+ if (!isRecord(payload) ||
128
+ typeof payload.token !== "string" ||
129
+ payload.token.length === 0 ||
130
+ typeof payload.expiry !== "number" ||
131
+ !Number.isFinite(payload.expiry)) {
132
+ throw new VercelOidcExchangeError("exchange_response");
133
+ }
134
+ const usableUntilMs = payload.expiry * 1000 - exports.TOKEN_EXPIRY_MARGIN_MS;
135
+ // A token that is already inside the margin would only be rejected at ingest.
136
+ if (usableUntilMs <= this.nowMs()) {
137
+ throw new VercelOidcExchangeError("exchange_response");
138
+ }
139
+ this.cache.set(sourceToken, { token: payload.token, usableUntilMs });
140
+ return payload.token;
141
+ }
142
+ }
143
+ exports.VercelOidcTokenExchanger = VercelOidcTokenExchanger;
144
+ function decodeJsonSegment(segment) {
145
+ try {
146
+ const base64 = segment.replace(/-/gu, "+").replace(/_/gu, "/");
147
+ const binary = atob(base64.padEnd(Math.ceil(base64.length / 4) * 4, "="));
148
+ const bytes = Uint8Array.from(binary, (character) => character.charCodeAt(0));
149
+ const value = JSON.parse(new TextDecoder().decode(bytes));
150
+ return isRecord(value) ? value : null;
151
+ }
152
+ catch {
153
+ return null;
154
+ }
155
+ }
156
+ function isRecord(value) {
157
+ return typeof value === "object" && value !== null;
158
+ }
159
+ //# sourceMappingURL=vercel-oidc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vercel-oidc.js","sourceRoot":"","sources":["../../src/vercel-oidc.ts"],"names":[],"mappings":"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"}
@@ -6,7 +6,7 @@
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
7
  exports.SDK_VERSION_HEADER = exports.SDK_VERSION = void 0;
8
8
  /** Must equal `version` in package.json; a unit test and the release check enforce it. */
9
- exports.SDK_VERSION = "0.10.0";
9
+ exports.SDK_VERSION = "0.12.0";
10
10
  /** Sent on every ingest and registry request. Carries the version only, no visitor data. */
11
11
  exports.SDK_VERSION_HEADER = "x-devtune-sdk-version";
12
12
  //# sourceMappingURL=version.js.map
@@ -0,0 +1,238 @@
1
+ "use strict";
2
+ /**
3
+ * ABOUTME: Collects the Web Bot Auth signature material DevTune needs to verify a signed agent request.
4
+ * Forwards the signature headers verbatim with the request line and covered fields; never verifies, logs or stores them.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.WEB_BOT_AUTH_FORWARDED_HEADERS = void 0;
8
+ exports.readWebBotAuth = readWebBotAuth;
9
+ exports.hasWebBotAuthHeaders = hasWebBotAuthHeaders;
10
+ const MAX_SIGNATURE_LENGTH = 4096;
11
+ const MAX_SIGNATURE_INPUT_LENGTH = 4096;
12
+ const MAX_SIGNATURE_AGENT_LENGTH = 2048;
13
+ const MAX_METHOD_LENGTH = 16;
14
+ const MAX_TARGET_URI_LENGTH = 8192;
15
+ const MAX_HEADER_VALUE_LENGTH = 4096;
16
+ const SIGNATURE_HEADERS = new Set(["signature", "signature-input", "signature-agent"]);
17
+ /**
18
+ * Derived components that depend on the path, query or scheme, which a Fetch
19
+ * runtime may have rewritten: it normalises the path, and behind a
20
+ * TLS-terminating proxy its `request.url` can be `http:` for an `https:`
21
+ * request.
22
+ */
23
+ const FETCH_REWRITTEN_COMPONENTS = new Set([
24
+ "@path",
25
+ "@query",
26
+ "@request-target",
27
+ "@scheme",
28
+ "@target-uri",
29
+ ]);
30
+ /**
31
+ * The only header fields ever forwarded with a signature: non-sensitive
32
+ * request headers. A request whose signature covers any other field, besides
33
+ * the three signature headers, forwards no signature material at all, since
34
+ * an unknown field may carry a credential and dropping just that field would
35
+ * leave a signature that can never verify. Short enough to stay within
36
+ * ingest's limits of 16 fields and 128-character names.
37
+ */
38
+ exports.WEB_BOT_AUTH_FORWARDED_HEADERS = [
39
+ "accept",
40
+ "accept-encoding",
41
+ "accept-language",
42
+ "cache-control",
43
+ "content-length",
44
+ "content-type",
45
+ "date",
46
+ "from",
47
+ "host",
48
+ "user-agent",
49
+ ];
50
+ const FORWARDED_HEADERS = new Set(exports.WEB_BOT_AUTH_FORWARDED_HEADERS);
51
+ /** RFC 9110 field-value characters: visible ASCII, SP and HTAB. */
52
+ const FIELD_VALUE = /^[\t\x20-\x7e]*$/u;
53
+ /** RFC 9110 method token. */
54
+ const METHOD = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/u;
55
+ /** An absolute URI as sent on the wire: visible ASCII, no whitespace. */
56
+ const TARGET_URI = /^[\x21-\x7e]+$/u;
57
+ /**
58
+ * Reads the material for a request that carries all three of `Signature`,
59
+ * `Signature-Input` and `Signature-Agent`, or returns null.
60
+ *
61
+ * Also returns null when anything would exceed ingest's limits or is not a
62
+ * value the site could have received, rather than truncating: a truncated
63
+ * signature can never verify, and the event is then sent exactly as an
64
+ * unsigned one would be.
65
+ *
66
+ * `targetUri` must carry the authority and target the agent sent. A target
67
+ * that differs from the event's `url` only makes DevTune's check
68
+ * inconclusive, but a rewritten one that happens to match `url` turns a
69
+ * genuine signature into a failed one.
70
+ *
71
+ * A target with a query, even a bare `?`, forwards nothing. The query can
72
+ * carry a reset token, an OAuth code or search terms, and DevTune could not
73
+ * verify the signature anyway: the event's `url` never has a query, so the
74
+ * target would not match it.
75
+ */
76
+ function readWebBotAuth(request) {
77
+ if (!hasWebBotAuthHeaders(request)) {
78
+ return null;
79
+ }
80
+ const signature = request.headers.get("signature");
81
+ const signatureInput = request.headers.get("signature-input");
82
+ const signatureAgent = request.headers.get("signature-agent");
83
+ const { method } = request;
84
+ const targetUri = request.targetUri === undefined ? readFetchTargetUri(request) : request.targetUri;
85
+ if (targetUri === null ||
86
+ targetUri.includes("?") ||
87
+ !isFieldValue(signature, MAX_SIGNATURE_LENGTH) ||
88
+ !isFieldValue(signatureInput, MAX_SIGNATURE_INPUT_LENGTH) ||
89
+ !isFieldValue(signatureAgent, MAX_SIGNATURE_AGENT_LENGTH) ||
90
+ !method ||
91
+ method.length > MAX_METHOD_LENGTH ||
92
+ !METHOD.test(method) ||
93
+ targetUri.length > MAX_TARGET_URI_LENGTH ||
94
+ !TARGET_URI.test(targetUri)) {
95
+ return null;
96
+ }
97
+ const components = readCoveredComponents(signatureInput);
98
+ // A Fetch runtime has already normalised the path (`/a/../guide` arrives as
99
+ // `/guide`) and may report a proxied `https:` request as `http:`. The raw
100
+ // forms are gone, so a signature over them would be checked against a
101
+ // rewritten base and fail. Signatures over `@authority` and
102
+ // `signature-agent` alone depend on neither and still forward.
103
+ if (request.targetUri === undefined &&
104
+ components.some((component) => FETCH_REWRITTEN_COMPONENTS.has(component))) {
105
+ return null;
106
+ }
107
+ const coveredNames = components.filter((component) => !component.startsWith("@") && !SIGNATURE_HEADERS.has(component));
108
+ if (coveredNames.some((name) => !FORWARDED_HEADERS.has(name))) {
109
+ return null;
110
+ }
111
+ const headers = new Map();
112
+ for (const name of coveredNames) {
113
+ if (headers.has(name)) {
114
+ continue;
115
+ }
116
+ const value = request.headers.getCombined
117
+ ? request.headers.getCombined(name)
118
+ : request.headers.get(name);
119
+ if (value === null) {
120
+ continue;
121
+ }
122
+ if (!isFieldValue(value, MAX_HEADER_VALUE_LENGTH, true)) {
123
+ return null;
124
+ }
125
+ headers.set(name, value);
126
+ }
127
+ return {
128
+ signature,
129
+ signatureInput,
130
+ signatureAgent,
131
+ method,
132
+ targetUri,
133
+ ...(headers.size === 0 ? {} : { headers: Object.fromEntries(headers) }),
134
+ };
135
+ }
136
+ /**
137
+ * The signed target for a Fetch-compatible runtime, which hands over
138
+ * `request.url` already normalised: `https://DOCS.example.com:443/guide`
139
+ * arrives as `https://docs.example.com/guide`. The authority is put back
140
+ * from the raw header the runtime built `url` from, `X-Forwarded-Host` or
141
+ * `Host`, and falls back to the raw `Host` when neither matches, so it is
142
+ * never a rewritten one. The scheme and path still come from `url`, which is
143
+ * why a signature covering a component that depends on them is not
144
+ * forwarded from here. Null without a `Host` header, or when `url` has a
145
+ * query, which `url.search` would hide when it is a bare `?`.
146
+ */
147
+ function readFetchTargetUri(request) {
148
+ const host = request.headers.get("host");
149
+ let url;
150
+ try {
151
+ url = new URL(request.url);
152
+ }
153
+ catch {
154
+ return null;
155
+ }
156
+ if (!host || request.url.split("#", 1)[0]?.includes("?")) {
157
+ return null;
158
+ }
159
+ const forwardedHost = request.headers.get("x-forwarded-host")?.split(",", 1)[0]?.trim();
160
+ const authority = [forwardedHost, host].find((candidate) => candidate && normalizeHost(url.protocol, candidate) === url.host) ?? host;
161
+ return `${url.protocol}//${authority}${url.pathname}`;
162
+ }
163
+ function normalizeHost(protocol, host) {
164
+ try {
165
+ return new URL(`${protocol}//${host}`).host;
166
+ }
167
+ catch {
168
+ return null;
169
+ }
170
+ }
171
+ /**
172
+ * Whether a request carries all three Web Bot Auth signature headers, the
173
+ * only requests whose arrival time or signature material is worth reading.
174
+ */
175
+ function hasWebBotAuthHeaders(request) {
176
+ return (request.headers.get("signature") !== null &&
177
+ request.headers.get("signature-input") !== null &&
178
+ request.headers.get("signature-agent") !== null);
179
+ }
180
+ /**
181
+ * Component identifiers a `Signature-Input` covers, across every signature it
182
+ * labels: header field names and derived components such as `@path`. This is
183
+ * just enough of RFC 8941 to find the strings that start an inner-list item:
184
+ * quoted strings are skipped whole, so a parameter value such as `key="a"` or
185
+ * `keyid="x (y)"` is never mistaken for one. Anything odd in the input is left
186
+ * for the caller or DevTune's parser to reject.
187
+ */
188
+ function readCoveredComponents(signatureInput) {
189
+ const names = [];
190
+ let inInnerList = false;
191
+ let previous = "";
192
+ for (let index = 0; index < signatureInput.length; index += 1) {
193
+ const char = signatureInput[index];
194
+ if (char === '"') {
195
+ const end = findStringEnd(signatureInput, index + 1);
196
+ if (end === -1) {
197
+ break;
198
+ }
199
+ if (inInnerList && (previous === "(" || previous === " ")) {
200
+ names.push(signatureInput.slice(index + 1, end).replace(/\\(["\\])/gu, "$1"));
201
+ }
202
+ index = end;
203
+ previous = '"';
204
+ continue;
205
+ }
206
+ if (char === "(") {
207
+ inInnerList = true;
208
+ }
209
+ else if (char === ")") {
210
+ inInnerList = false;
211
+ }
212
+ previous = char ?? "";
213
+ }
214
+ return names;
215
+ }
216
+ /** Index of the quote closing an sf-string that opened before `start`, or -1. */
217
+ function findStringEnd(value, start) {
218
+ for (let index = start; index < value.length; index += 1) {
219
+ if (value[index] === "\\") {
220
+ index += 1;
221
+ }
222
+ else if (value[index] === '"') {
223
+ return index;
224
+ }
225
+ }
226
+ return -1;
227
+ }
228
+ /**
229
+ * Also rejects a non-string, which a hand-written `HeadersLike` can return
230
+ * for a repeated field and which ingest's schema would refuse.
231
+ */
232
+ function isFieldValue(value, maxLength, allowEmpty = false) {
233
+ return (typeof value === "string" &&
234
+ (allowEmpty || value.length > 0) &&
235
+ value.length <= maxLength &&
236
+ FIELD_VALUE.test(value));
237
+ }
238
+ //# sourceMappingURL=web-bot-auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web-bot-auth.js","sourceRoot":"","sources":["../../src/web-bot-auth.ts"],"names":[],"mappings":"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"}