@koda-sl/baker-cli 0.204.0 → 0.209.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.
Files changed (27) hide show
  1. package/README.md +56 -3
  2. package/dist/{chunk-UZYVSPCA.js → chunk-3E33AGNR.js} +2 -2
  3. package/dist/{chunk-SVHFT7AN.js → chunk-3WODSPNZ.js} +4 -4
  4. package/dist/{chunk-2R2DEPG7.js → chunk-CRXSLYEX.js} +3 -3
  5. package/dist/{chunk-5MPIOGRO.js → chunk-E2WYWHJJ.js} +370 -221
  6. package/dist/chunk-E2WYWHJJ.js.map +1 -0
  7. package/dist/{chunk-KHHD26LZ.js → chunk-FUSTYLP5.js} +3 -3
  8. package/dist/{chunk-6NZG2TCM.js → chunk-IRNM3T6B.js} +6 -1
  9. package/dist/{chunk-6NZG2TCM.js.map → chunk-IRNM3T6B.js.map} +1 -1
  10. package/dist/cli.js +732 -121
  11. package/dist/cli.js.map +1 -1
  12. package/dist/client-CF6ZORCO.js +15 -0
  13. package/dist/engine/index.js +3 -3
  14. package/dist/{env-FF7ZB5W5.js → env-7SCX7HAH.js} +4 -2
  15. package/dist/{output-YGPWST77.js → output-4YIOSWOG.js} +5 -5
  16. package/dist/{shared-CRWB5B7P.js → shared-EBF5DEDJ.js} +6 -6
  17. package/package.json +1 -1
  18. package/dist/chunk-5MPIOGRO.js.map +0 -1
  19. package/dist/client-MP7BKFKM.js +0 -15
  20. /package/dist/{chunk-UZYVSPCA.js.map → chunk-3E33AGNR.js.map} +0 -0
  21. /package/dist/{chunk-SVHFT7AN.js.map → chunk-3WODSPNZ.js.map} +0 -0
  22. /package/dist/{chunk-2R2DEPG7.js.map → chunk-CRXSLYEX.js.map} +0 -0
  23. /package/dist/{chunk-KHHD26LZ.js.map → chunk-FUSTYLP5.js.map} +0 -0
  24. /package/dist/{client-MP7BKFKM.js.map → client-CF6ZORCO.js.map} +0 -0
  25. /package/dist/{env-FF7ZB5W5.js.map → env-7SCX7HAH.js.map} +0 -0
  26. /package/dist/{output-YGPWST77.js.map → output-4YIOSWOG.js.map} +0 -0
  27. /package/dist/{shared-CRWB5B7P.js.map → shared-EBF5DEDJ.js.map} +0 -0
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  ApiError,
3
3
  apiGet
4
- } from "./chunk-2R2DEPG7.js";
4
+ } from "./chunk-CRXSLYEX.js";
5
5
  import {
6
6
  getEnv
7
- } from "./chunk-6NZG2TCM.js";
7
+ } from "./chunk-IRNM3T6B.js";
8
8
 
9
9
  // src/error-handler.ts
10
10
  var REQUEST_CONNECTION_PLATFORM = {
@@ -295,4 +295,4 @@ export {
295
295
  writeAdsOutput,
296
296
  resolveCustomerId
297
297
  };
298
- //# sourceMappingURL=chunk-KHHD26LZ.js.map
298
+ //# sourceMappingURL=chunk-FUSTYLP5.js.map
@@ -318,6 +318,10 @@ function captureBudgetMs() {
318
318
  function captureProxyCredentials() {
319
319
  return readProxyCredentials(process.env);
320
320
  }
321
+ function captureVimeoCookie() {
322
+ const raw = process.env.VIMEO_COOKIE?.trim();
323
+ return raw ? raw : void 0;
324
+ }
321
325
  function childEnvWith(extra) {
322
326
  return { ...process.env, ...extra };
323
327
  }
@@ -337,6 +341,7 @@ export {
337
341
  resolveChatId,
338
342
  captureBudgetMs,
339
343
  captureProxyCredentials,
344
+ captureVimeoCookie,
340
345
  childEnvWith
341
346
  };
342
- //# sourceMappingURL=chunk-6NZG2TCM.js.map
347
+ //# sourceMappingURL=chunk-IRNM3T6B.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../proxy/src/challenge.ts","../../proxy/src/tiers.ts","../../proxy/src/credentials.ts","../../proxy/src/escalate.ts","../../proxy/src/privateAddress.ts","../../proxy/src/publicAddress.ts","../../proxy/src/route.ts","../../proxy/src/ladder.ts","../src/env.ts"],"sourcesContent":["/**\n * Was that a page, or the wall in front of it?\n *\n * A bot filter answers 200. The body parses, it has a title, and every check\n * downstream — `response.ok`, the status, the content type — says the read\n * succeeded. So a challenge is the one block that cannot be seen from the\n * status line, and the only place it is visible is the bytes.\n *\n * This lives in `@baker/proxy` rather than beside any one caller because the\n * escalation ladder is only coherent if every surface agrees on what \"blocked\"\n * means: the rung that gets climbed is chosen from this answer, and a detector\n * that differs per caller would mean the same wall costs money on one surface\n * and is filed as content on another.\n *\n * Pure and dependency-free, so it stays reachable from Convex's V8 runtime\n * through the package barrel.\n */\n\n/**\n * Phrases that only appear on a block or challenge page.\n *\n * Deliberately specific — \"access denied\" alone would match a page *about*\n * access control, so each phrase is one a real marketing page has no reason to\n * use as its title.\n */\nconst CHALLENGE_PHRASES = [\n \"just a moment\",\n \"attention required\",\n \"verify you are human\",\n \"checking your browser\",\n \"enable javascript and cookies to continue\",\n \"unusual traffic\",\n \"access denied\",\n \"you have been blocked\",\n \"request unsuccessful\",\n \"are you a robot\",\n \"security check\",\n \"ddos protection\",\n \"captcha\",\n];\n\n/** Vendors whose block pages carry a fingerprint even when the title does not. */\nconst CHALLENGE_MARKERS = [\"cf-browser-verification\", \"cf_chl_\", \"px-captcha\", \"_incapsula_\", \"distil_r_captcha\"];\n\n/**\n * How long a response may be and still be judged by its *wording*.\n *\n * Phrases like \"captcha\" or \"access denied\" are ordinary English that a real\n * article can legitimately contain, so matching them anywhere would refuse\n * pages we read perfectly well. Length is the precision guard: a page that is\n * mostly content is content.\n */\nexport const CHALLENGE_LENGTH_CEILING = 2_000;\n\n/**\n * Whether these bytes are a bot wall rather than the thing that was asked for.\n *\n * Reads only what the caller already paid for. Nothing here issues a request —\n * asking the host again would deepen the very block this is detecting.\n *\n * The two lists are searched differently, and that asymmetry is the point.\n * Phrases are judged only on a short response, for the precision reason above.\n * Vendor markers are fingerprints nothing but the vendor emits, so they are\n * searched at any length — which is what makes a real interstitial detectable\n * at all. Cloudflare's is 5–15 KB of inlined script wrapped around one visible\n * sentence, so gating markers behind the same length ceiling as the phrases put\n * every genuine large wall in a dead band where neither list could reach it.\n */\nexport function isChallengeBody(content: string, title?: string | null): boolean {\n if (content.length <= CHALLENGE_LENGTH_CEILING) {\n const haystack = `${title ?? \"\"}\\n${content}`.toLowerCase();\n if (CHALLENGE_PHRASES.some((phrase) => haystack.includes(phrase))) return true;\n }\n\n const whole = content.toLowerCase();\n return CHALLENGE_MARKERS.some((marker) => whole.includes(marker));\n}\n","/**\n * The two Oxylabs products, and everything that differs between them.\n *\n * They are NOT credential-swappable. Endpoint, username prefix, geo token and\n * sticky-session mechanism all differ, so \"same URL, other username/password\"\n * fails — and it fails as a 407, which Chromium reports as a plain network\n * error. Before this module existed there was no code path that could tell that\n * apart from the site refusing us, so the failure would have been recorded\n * against the page rather than against our own configuration.\n *\n * That asymmetry is the reason this table exists in exactly one place. Every\n * caller builds its route through `buildProxyRoute`; nobody concatenates an\n * Oxylabs URL by hand.\n */\n\nexport type ProxyTier = \"datacenter\" | \"residential\";\n\nexport interface ProxyTierSpec {\n readonly tier: ProxyTier;\n readonly host: string;\n readonly port: number;\n /** Oxylabs requires this in front of the account name. The two differ. */\n readonly usernamePrefix: \"user-\" | \"customer-\";\n /** Country targeting token. `-country-` on datacenter, `-cc-` on residential. */\n readonly countryKey: \"-country-\" | \"-cc-\";\n /**\n * Sticky-session token, or null when the product has none.\n *\n * Datacenter does stickiness by *port* (8001 for IP #1, 8002 for #2), not by\n * username. We do not implement that: a capture is a single page load, so a\n * reusable IP buys nothing and the knob would only add a way to get it wrong.\n */\n readonly sessionKey: \"-sessid-\" | null;\n}\n\nexport const PROXY_TIERS: Readonly<Record<ProxyTier, ProxyTierSpec>> = {\n datacenter: {\n tier: \"datacenter\",\n host: \"dc.oxylabs.io\",\n port: 8000,\n usernamePrefix: \"user-\",\n countryKey: \"-country-\",\n sessionKey: null,\n },\n residential: {\n tier: \"residential\",\n host: \"pr.oxylabs.io\",\n port: 7777,\n usernamePrefix: \"customer-\",\n countryKey: \"-cc-\",\n sessionKey: \"-sessid-\",\n },\n};\n\n/**\n * Cheapest useful route first, and the ladder never skips a rung.\n *\n * Residential costs materially more than datacenter, so it is only ever reached\n * because datacenter was tried and refused — never as a first guess and never\n * speculatively.\n */\nexport const ESCALATION_ORDER = [\"datacenter\", \"residential\"] as const satisfies readonly ProxyTier[];\n\n/** Every prefix this module knows, used to spot a credential in the wrong slot. */\nexport const KNOWN_USERNAME_PREFIXES = [\"user-\", \"customer-\"] as const;\n","import type { ProxyTier } from \"./tiers.ts\";\nimport { ESCALATION_ORDER } from \"./tiers.ts\";\n\n/**\n * The four environment variable names, in one place.\n *\n * Convex holds these and hands them to the sandbox; the CLI reads them back\n * out. A typo on either side is a *silent* no-op — the capability appears to\n * ship, every fetch quietly takes the direct route, and nothing fails. That is\n * the worst failure mode available to this feature, so the names are a shared\n * constant with a contract test rather than eight string literals spread across\n * two packages.\n */\nexport const PROXY_ENV_VARS = {\n datacenter: { username: \"OXYLABS_DATACENTER_USERNAME\", password: \"OXYLABS_DATACENTER_PASSWORD\" },\n residential: { username: \"OXYLABS_RESIDENTIAL_USERNAME\", password: \"OXYLABS_RESIDENTIAL_PASSWORD\" },\n} as const satisfies Record<ProxyTier, { username: string; password: string }>;\n\nexport interface TierCredentials {\n readonly username: string;\n readonly password: string;\n}\n\nexport type ProxyCredentials = Partial<Readonly<Record<ProxyTier, TierCredentials>>>;\n\n/**\n * Read whichever tiers are fully configured.\n *\n * Takes the env record rather than reading `process.env` itself, so the whole\n * package stays pure and the Convex and CLI sides can be tested identically.\n *\n * Half a pair is not a credential. A username with no password would build a\n * route that authenticates as nobody, and Oxylabs answers that with the same\n * 407 as a wrong password — so it would look like a broken proxy rather than an\n * unconfigured one. Dropping it here means `configuredTiers` tells the truth.\n */\nexport function readProxyCredentials(env: Record<string, string | undefined>): ProxyCredentials {\n const credentials: { -readonly [K in ProxyTier]?: TierCredentials } = {};\n for (const tier of ESCALATION_ORDER) {\n const username = env[PROXY_ENV_VARS[tier].username]?.trim();\n const password = env[PROXY_ENV_VARS[tier].password]?.trim();\n if (username && password) credentials[tier] = { username, password };\n }\n return credentials;\n}\n\n/**\n * The inverse of `readProxyCredentials`: credentials back out as env vars.\n *\n * The writing side used to be a loop inside one Convex function, which is how\n * the ladder came to exist everywhere and work in one place. That loop ran only\n * for `baker landing inspiration scrape`, so every other CLI fetch surface in a\n * Session — `images download`, canvas `ingest`, the two scaffolders, yt-dlp —\n * read four variables nobody had set and went direct, silently, exactly as\n * `PROXY_ENV_VARS` warns.\n *\n * Pairing it with the reader here is the point: the round trip is a test, so the\n * two sides cannot drift into agreeing about three variables out of four.\n */\nexport function proxyCredentialEnvs(credentials: ProxyCredentials): Record<string, string> {\n const envs: Record<string, string> = {};\n for (const tier of ESCALATION_ORDER) {\n const held = credentials[tier];\n if (!held) continue;\n envs[PROXY_ENV_VARS[tier].username] = held.username;\n envs[PROXY_ENV_VARS[tier].password] = held.password;\n }\n return envs;\n}\n\n/**\n * Which tiers this process could actually reach, cheapest first.\n *\n * There is no ceiling to apply. A deployment holding both pairs may climb to\n * residential, full stop — the brake is the credential itself. Removing\n * `OXYLABS_RESIDENTIAL_PASSWORD` takes effect on the next restart, needs no\n * deploy, and withholds the tier by the same mechanism a ceiling would have,\n * so the valve that used to sit here only added a second way to say it. It was\n * also never wired into ad-dna's task definition, which is the highest-volume\n * egress in the repo — a control that could not be turned where it mattered\n * most.\n */\nexport function configuredTiers(credentials: ProxyCredentials): ProxyTier[] {\n return ESCALATION_ORDER.filter((tier) => credentials[tier] !== undefined);\n}\n\n/** Whether there is any proxy to escalate to at all. */\nexport function proxyEscalationConfigured(credentials: ProxyCredentials): boolean {\n return configuredTiers(credentials).length > 0;\n}\n","/**\n * When is a failure worth spending a more expensive route on?\n *\n * The ladder only ever climbs on evidence that *who we are* was the problem. A\n * page that 404s, a domain that does not resolve, a certificate that will not\n * negotiate and a site that timed out all fail identically from every IP on\n * earth, so retrying them through a metered exit buys a second identical\n * failure and a bill. That asymmetry — cheap to be wrong in one direction,\n * expensive in the other — is why this is a closed allow-list rather than\n * \"escalate unless we recognise the error\".\n *\n * Shared deliberately: the capture engine, the image fetchers and the ad-media\n * downloaders all have to agree on what \"blocked\" means, or the corpus and the\n * bill disagree about the same event.\n */\n\n/** What one attempt observed, normalised across Playwright and `fetch`. */\nexport interface BlockSignal {\n /** Main-document / response status, when there was one. */\n readonly status?: number | null;\n /** Chromium `net::` name or a Node error code, when the attempt threw one. */\n readonly netError?: string | null;\n /** A challenge body was detected behind an otherwise successful response. */\n readonly challenge?: boolean;\n /** The attempt ran out of time rather than being refused. */\n readonly timedOut?: boolean;\n}\n\n/** \"We don't like *you*\" — a different exit IP is a different you. */\nconst ESCALATABLE_STATUSES = new Set([403, 429, 451]);\n\n/**\n * A TCP reset on ClientHello is how a WAF null-routes a datacenter range.\n *\n * Today all three of these collapse into one \"site didn't respond\" message and\n * are filed non-retryable, which is precisely why this class of block has been\n * invisible: it looks identical to a site that is genuinely down.\n */\nconst ESCALATABLE_NET_ERRORS = new Set([\"ERR_CONNECTION_RESET\", \"ERR_CONNECTION_CLOSED\", \"ERR_EMPTY_RESPONSE\"]);\n\n/**\n * Failures that belong to our route, not to the site.\n *\n * A 407 cannot come from an origin server — only something speaking proxy\n * produces one — so this needs no \"was this attempt proxied\" flag to be safe.\n */\nconst PROXY_NET_ERRORS = new Set([\n \"ERR_TUNNEL_CONNECTION_FAILED\",\n \"ERR_PROXY_CONNECTION_FAILED\",\n \"ERR_PROXY_AUTH_REQUESTED\",\n \"ERR_PROXY_CERTIFICATE_INVALID\",\n \"ERR_UNEXPECTED_PROXY_AUTH\",\n \"ERR_MANDATORY_PROXY_CONFIGURATION_FAILED\",\n \"ERR_HTTPS_PROXY_TUNNEL_RESPONSE_REDIRECT\",\n]);\n\nexport function isProxyFailure(signal: BlockSignal): boolean {\n if (signal.status === 407) return true;\n return signal.netError ? PROXY_NET_ERRORS.has(signal.netError) : false;\n}\n\nexport function shouldEscalate(signal: BlockSignal): boolean {\n // Our own broken route never advances the ladder — it degrades it. Climbing\n // here would spend a more expensive rung reproducing our misconfiguration.\n if (isProxyFailure(signal)) return false;\n if (signal.timedOut) return false;\n if (signal.challenge) return true;\n if (signal.status != null && ESCALATABLE_STATUSES.has(signal.status)) return true;\n return signal.netError ? ESCALATABLE_NET_ERRORS.has(signal.netError) : false;\n}\n","/**\n * The same question as `refuseNonPublicUrl`, asked of an address instead of a name.\n *\n * A URL carries a hostname, and a hostname is not an address. `localtest.me`\n * and `127.0.0.1.nip.io` are public names, with public DNS records, that\n * resolve to `127.0.0.1` — and nothing stops an attacker pointing a domain they\n * own at `169.254.169.254`. Proven against a real loopback server: the hostname\n * guard passed `http://localtest.me:4321/secret.txt` straight through, the bytes\n * were downloaded, and only the image parser complained afterwards.\n *\n * IPv6 is parsed rather than pattern-matched, and that is the second lesson\n * here. The first version tested prefixes with regexes and unwrapped an\n * IPv4-mapped address only when it still had a dot in it — but a URL parser\n * normalises `::ffff:127.0.0.1` to `::ffff:7f00:1`, so the dot is gone by the\n * time anyone looks. `[::ffff:169.254.169.254]` therefore read as public and the\n * ladder returned the metadata body with `outcome: \"ok\"`. One address has many\n * spellings; comparing spellings is the wrong operation.\n *\n * Anything unparseable is treated as private. This is a safety gate, and the\n * only safe reading of \"I do not know what this is\" is \"do not fetch it\".\n */\n\n/** Every IPv4 range that is not routable on the public internet. */\nfunction isPrivateIpv4Octets(octets: readonly number[]): boolean {\n const [a, b] = octets as [number, number, number, number];\n if (a === 0) return true; // \"this network\" — 0.0.0.0/8\n if (a === 10) return true; // private\n if (a === 127) return true; // loopback\n if (a === 169 && b === 254) return true; // link-local, and the cloud metadata address\n if (a === 172 && b >= 16 && b <= 31) return true; // private /12 — NOT all of 172.*\n if (a === 192 && b === 168) return true; // private\n if (a === 100 && b >= 64 && b <= 127) return true; // carrier-grade NAT\n if (a >= 224) return true; // multicast and reserved\n return false;\n}\n\n/** Dotted-quad to octets, or `null` when it is not one. */\nfunction parseIpv4(value: string): number[] | null {\n const parts = value.split(\".\");\n if (parts.length !== 4) return null;\n const octets = parts.map((part) => (/^\\d{1,3}$/.test(part) ? Number(part) : Number.NaN));\n if (octets.some((octet) => !Number.isInteger(octet) || octet < 0 || octet > 255)) return null;\n return octets;\n}\n\n/**\n * One side of a `::` to its 16-bit groups, or `null` if any part is not one.\n *\n * A trailing dotted-quad stands for the final two groups and is only legal in\n * last position — that is the spelling `::ffff:127.0.0.1` uses before a URL\n * parser rewrites it to `::ffff:7f00:1`.\n */\nfunction expandGroups(part: string): number[] | null {\n if (part === \"\") return [];\n const chunks = part.split(\":\");\n const groups: number[] = [];\n for (let index = 0; index < chunks.length; index++) {\n const chunk = chunks[index] as string;\n if (chunk.includes(\".\")) {\n if (index !== chunks.length - 1) return null;\n const octets = parseIpv4(chunk);\n if (!octets) return null;\n groups.push(((octets[0] as number) << 8) | (octets[1] as number));\n groups.push(((octets[2] as number) << 8) | (octets[3] as number));\n continue;\n }\n if (!/^[0-9a-f]{1,4}$/.test(chunk)) return null;\n groups.push(Number.parseInt(chunk, 16));\n }\n return groups;\n}\n\n/**\n * IPv6 to its eight 16-bit groups, or `null` when it is not one.\n *\n * Handles the `::` elision and a trailing dotted-quad, which together are why\n * one address has so many spellings.\n */\nfunction parseIpv6(value: string): number[] | null {\n const halves = value.split(\"::\");\n if (halves.length > 2) return null;\n\n const head = expandGroups(halves[0] as string);\n const tail = halves.length === 2 ? expandGroups(halves[1] as string) : [];\n if (head === null || tail === null) return null;\n\n if (halves.length === 1) return head.length === 8 ? head : null;\n const missing = 8 - head.length - tail.length;\n if (missing < 1) return null;\n return [...head, ...new Array<number>(missing).fill(0), ...tail];\n}\n\nexport function isPrivateAddress(ip: string): boolean {\n const address = ip.trim().toLowerCase();\n if (!address) return true;\n\n const v4 = parseIpv4(address);\n if (v4) return isPrivateIpv4Octets(v4);\n\n const v6 = parseIpv6(address);\n // Not an address we can read. A safety gate may not guess.\n if (!v6) return true;\n\n const [g0, g1, g2, g3, g4, g5, g6, g7] = v6 as [number, number, number, number, number, number, number, number];\n\n // `::` (unspecified) and `::1` (loopback).\n if (g0 === 0 && g1 === 0 && g2 === 0 && g3 === 0 && g4 === 0 && g5 === 0 && g6 === 0) return true;\n\n // IPv4-mapped (`::ffff:a.b.c.d`) and IPv4-translated (`::ffff:0:a.b.c.d`).\n // The whole point: by the time this is read the tail is hex, not dotted.\n const mappedV4 = g0 === 0 && g1 === 0 && g2 === 0 && g3 === 0;\n if (mappedV4 && g4 === 0 && g5 === 0xffff) {\n return isPrivateIpv4Octets([g6 >> 8, g6 & 0xff, g7 >> 8, g7 & 0xff]);\n }\n if (mappedV4 && g4 === 0xffff && g5 === 0) {\n return isPrivateIpv4Octets([g6 >> 8, g6 & 0xff, g7 >> 8, g7 & 0xff]);\n }\n\n if ((g0 & 0xfe00) === 0xfc00) return true; // fc00::/7 unique-local\n if ((g0 & 0xffc0) === 0xfe80) return true; // fe80::/10 link-local\n return false;\n}\n","/**\n * Addresses that must never be fetched, and must never be proxied.\n *\n * Lifted from `convex/landingLibrary/utils.ts`, where these three regexes were\n * the only copy in the repo — reachable from Convex and from nowhere else. That\n * was survivable while the capture ran one hop after the check. It is not\n * survivable now: the CLI can be invoked directly, two Convex paths enqueue\n * without re-validating, and a proxy adds a rung where \"which network am I on\"\n * stops being rhetorical.\n *\n * Two distinct reasons live here, and both point the same way:\n *\n * - **There is nothing to read.** A private address only resolves from inside\n * the network asking, so a capture of one produces a screenshot of an error.\n * - **There is something to read, and we must not.** `169.254.169.254` is the\n * cloud metadata endpoint that every capture runner has a route to.\n *\n * The rule for the proxy is *refuse*, not *bypass*. Bypassing would leave a\n * request that still happens, just unproxied; refusing means there is no\n * request to route. That is a stronger guarantee and a simpler one to check.\n */\n\nimport { isPrivateAddress } from \"./privateAddress.ts\";\n\n/**\n * Names — not addresses. Anything that looks like an address goes to\n * `isPrivateAddress`, which parses it instead of matching its spelling.\n */\nconst PRIVATE_HOST = /^(localhost|.*\\.localhost|.*\\.local)$/;\n/** Where a Session's own preview is served while the page is being built. */\nconst SANDBOX_HOST = /\\.e2b\\.(app|dev)$/;\n\n/**\n * A bracketed IPv6 literal, unwrapped.\n *\n * `new URL(\"http://[::1]/\").hostname` keeps the brackets, so a hostname check\n * that does not strip them is comparing against a string no address will ever\n * equal. The previous version tried to absorb that with `\\[?::1\\]?` and caught\n * exactly one address out of the whole of IPv6 — `[::ffff:169.254.169.254]`\n * sailed through and the ladder returned the metadata body with `outcome: \"ok\"`.\n */\nfunction unwrapIpLiteral(host: string): string | null {\n if (host.startsWith(\"[\") && host.endsWith(\"]\")) return host.slice(1, -1);\n // A bare v4 literal, or a v6 one that arrived without brackets.\n if (/^[0-9.]+$/.test(host) || host.includes(\":\")) return host;\n return null;\n}\n\nexport function isPrivateHostname(hostname: string): boolean {\n const host = hostname.trim().toLowerCase();\n if (!host) return true;\n if (PRIVATE_HOST.test(host) || SANDBOX_HOST.test(host)) return true;\n\n // Every address form — v4, v6, bracketed, mapped, elided — is decided by one\n // parser. Spelling an address in a regex is what let four of them through.\n const literal = unwrapIpLiteral(host);\n return literal === null ? false : isPrivateAddress(literal);\n}\n\n/**\n * The same rule again, in the only notation a browser's proxy settings speak.\n *\n * Chromium — and so Playwright and `agent-browser` — matches a bypass entry\n * against the hostname and understands one wildcard and no CIDR at all. The\n * ranges above therefore cannot be handed over as regexes; `172.16/12` has to\n * become sixteen entries, and there is no way to express \"any private address\".\n *\n * That makes this a second copy of one fact, which is worth stating plainly:\n * the day it disagrees with `isPrivateHostname` is the day a Session's own\n * preview at `localhost:4321` goes out through a metered exit and comes back\n * refused. Its test pins the correspondence.\n *\n * Note the direction reverses here, and deliberately. Everywhere else the rule\n * for a private address is *refuse*, because a request that still happens is\n * not a guarantee. A browser the agent drives has to reach `localhost:4321` —\n * that is its main job — so here the private address is the legitimate traffic\n * and the list says \"go direct\", not \"do not go\".\n */\nexport const BROWSER_PROXY_BYPASS = [\n \"localhost\",\n \"*.localhost\",\n \"127.0.0.1\",\n \"0.0.0.0\",\n \"::1\",\n \"*.local\",\n // A Session's own preview, which is served from the sandbox's public host —\n // public in DNS, ours in every sense that matters here.\n \"*.e2b.app\",\n \"*.e2b.dev\",\n \"10.*\",\n \"192.168.*\",\n \"169.254.*\",\n ...Array.from({ length: 16 }, (_, i) => `172.${16 + i}.*`),\n].join(\",\");\n\nexport type NonPublicReason = \"not_public\" | \"unparseable\";\n\n/**\n * `null` when the URL is safe to fetch; a reason when it must be refused.\n *\n * Anything that is not plain http(s) is refused too. A capture is handed URLs\n * from agents and from stored rows, and `file:` reaching a browser we launched\n * is a local file read wearing a URL.\n */\nexport function refuseNonPublicUrl(url: string): NonPublicReason | null {\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n return \"unparseable\";\n }\n if (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") return \"not_public\";\n return isPrivateHostname(parsed.hostname) ? \"not_public\" : null;\n}\n","import type { ProxyCredentials } from \"./credentials.ts\";\nimport type { ProxyTier, ProxyTierSpec } from \"./tiers.ts\";\nimport { KNOWN_USERNAME_PREFIXES, PROXY_TIERS } from \"./tiers.ts\";\n\n/**\n * How one attempt reaches the internet.\n *\n * Credentials stay in separate fields instead of being embedded in the URL:\n * Playwright's `proxy` option wants them that way, undici's `ProxyAgent` takes\n * them as a header we build once, and — the real reason — a password that never\n * exists as a substring of a URL cannot be leaked by anything that logs a URL.\n */\nexport type ProxyRoute =\n | { readonly kind: \"direct\" }\n | {\n readonly kind: \"proxy\";\n readonly tier: ProxyTier;\n /** e.g. `http://dc.oxylabs.io:8000` */\n readonly server: string;\n readonly username: string;\n readonly password: string;\n };\n\nexport const DIRECT_ROUTE: ProxyRoute = { kind: \"direct\" };\n\nexport interface RouteOptions {\n /** ISO-3166 alpha-2, upper-cased here. Omit for \"wherever\" — the default. */\n readonly country?: string;\n /** Sticky-session id. Ignored by a tier whose product has no session syntax. */\n readonly session?: string;\n}\n\nexport type RouteProblem = \"missing\" | \"wrong_prefix\";\n\nexport type RouteResult = { ok: true; route: ProxyRoute } | { ok: false; problem: RouteProblem };\n\n/**\n * Put the account name into the shape this specific product expects.\n *\n * Three cases, and the third is the one worth being strict about:\n *\n * 1. Bare (`acme`) — someone pasted the account name. Prepend the prefix.\n * 2. Already correct (`user-acme` in the datacenter slot) — someone pasted the\n * full Oxylabs username. Leave it alone.\n * 3. Carrying the OTHER product's prefix (`customer-acme` in the datacenter\n * slot) — refuse.\n *\n * Case 3 must not be \"helpfully\" rewritten. A `customer-` value in the\n * datacenter slot means the residential credentials were pasted into the wrong\n * variable, and rewriting the prefix would authenticate a residential account\n * against the datacenter endpoint. That either 407s — wasting the cheap rung\n * for no reason — or it works, and bills residential rates from the tier whose\n * entire purpose is to be the cheap one. Refusing is the only answer that\n * cannot silently cost money.\n */\nexport function proxyUsername(\n spec: ProxyTierSpec,\n rawUsername: string,\n options?: RouteOptions,\n): { ok: true; username: string } | { ok: false; problem: \"wrong_prefix\" } {\n const raw = rawUsername.trim();\n const foreignPrefix = KNOWN_USERNAME_PREFIXES.find(\n (prefix) => prefix !== spec.usernamePrefix && raw.startsWith(prefix),\n );\n if (foreignPrefix) return { ok: false, problem: \"wrong_prefix\" };\n\n let username = raw.startsWith(spec.usernamePrefix) ? raw : `${spec.usernamePrefix}${raw}`;\n // Geo before session — Oxylabs reads the username left to right and rejects\n // the pair in the other order.\n if (options?.country) username += `${spec.countryKey}${options.country.toUpperCase()}`;\n if (options?.session && spec.sessionKey) username += `${spec.sessionKey}${options.session}`;\n return { ok: true, username };\n}\n\n/** Build the route for one tier, or say why there isn't one. */\nexport function buildProxyRoute(tier: ProxyTier, credentials: ProxyCredentials, options?: RouteOptions): RouteResult {\n const held = credentials[tier];\n if (!held) return { ok: false, problem: \"missing\" };\n\n const spec = PROXY_TIERS[tier];\n const username = proxyUsername(spec, held.username, options);\n if (!username.ok) return username;\n\n return {\n ok: true,\n route: {\n kind: \"proxy\",\n tier,\n server: `http://${spec.host}:${spec.port}`,\n username: username.username,\n password: held.password,\n },\n };\n}\n","import type { ProxyCredentials } from \"./credentials.ts\";\nimport { configuredTiers } from \"./credentials.ts\";\nimport { refuseNonPublicUrl } from \"./publicAddress.ts\";\nimport type { ProxyRoute, RouteOptions } from \"./route.ts\";\nimport { buildProxyRoute, DIRECT_ROUTE } from \"./route.ts\";\n\n/**\n * Every route this URL is allowed to be attempted on, in the order to try them.\n *\n * Direct is always first and always present: an unblocked page must cost\n * nothing, and a deployment with no credentials has to behave exactly as it did\n * before any of this existed.\n *\n * A tier whose credentials are malformed is *skipped*, not fatal. One\n * mis-pasted variable should cost the use of that rung, not the whole ladder —\n * the alternative is that a typo in the cheap tier silently disables the\n * expensive one that would have worked.\n */\nexport function plannedRoutes(url: string, credentials: ProxyCredentials, options?: RouteOptions): ProxyRoute[] {\n // A private address is refused upstream; if one reaches here anyway it must\n // not become a billed request, and there is no exit node on earth from which\n // `localhost` means us.\n if (refuseNonPublicUrl(url) !== null) return [DIRECT_ROUTE];\n\n const routes: ProxyRoute[] = [DIRECT_ROUTE];\n for (const tier of configuredTiers(credentials)) {\n const built = buildProxyRoute(tier, credentials, options);\n if (built.ok) routes.push(built.route);\n }\n return routes;\n}\n","import type { ProxyCredentials } from \"@baker/proxy\";\nimport { readProxyCredentials } from \"@baker/proxy\";\nimport { createEnv } from \"@t3-oss/env-core\";\nimport { z } from \"zod\";\n\ntype Env = {\n BAKER_API_KEY: string;\n BAKER_API_URL: string;\n BAKER_CHAT_ID?: string;\n BAKER_ACTING_USER_ID?: string;\n BAKER_GOOGLE_ADS_CUSTOMER_ID?: string;\n BAKER_GA4_PROPERTY_ID?: string;\n BAKER_GSC_SITE_URL?: string;\n BAKER_X_ADS_ACCOUNT_ID?: string;\n BAKER_META_AD_ACCOUNT_ID?: string;\n BAKER_LINKEDIN_AD_ACCOUNT_ID?: string;\n};\n\nlet cached: Env | undefined;\n\nexport function getEnv(): Env {\n if (!cached) {\n cached = createEnv({\n server: {\n BAKER_API_KEY: z.string().startsWith(\"bk_\", \"API key must start with 'bk_'\"),\n BAKER_API_URL: z.url(\"BAKER_API_URL must be a valid URL\"),\n BAKER_CHAT_ID: z.string().optional(),\n BAKER_ACTING_USER_ID: z.string().optional(),\n BAKER_GOOGLE_ADS_CUSTOMER_ID: z\n .string()\n .regex(/^\\d{10}$/)\n .optional(),\n BAKER_GA4_PROPERTY_ID: z.string().optional(),\n BAKER_GSC_SITE_URL: z.string().optional(),\n BAKER_X_ADS_ACCOUNT_ID: z\n .string()\n .regex(/^[a-z0-9]+$/, \"X Ads account ID must be a base36 string\")\n .optional(),\n BAKER_META_AD_ACCOUNT_ID: z.string().optional(),\n BAKER_LINKEDIN_AD_ACCOUNT_ID: z\n .string()\n .regex(/^\\d+$/, \"LinkedIn ad account ID must be the numeric portion of urn:li:sponsoredAccount:N\")\n .optional(),\n },\n runtimeEnv: process.env,\n });\n }\n return cached;\n}\n\n// Read directly (not via the validated schema): debug logging must work even when\n// the required BAKER_API_KEY/BAKER_API_URL are missing or malformed.\nexport function debugLogSetting(): string | undefined {\n const raw = process.env.BAKER_DEBUG_LOG?.trim();\n return raw ? raw : undefined;\n}\n\nexport function requireChatId(): string {\n const env = getEnv();\n if (!env.BAKER_CHAT_ID) {\n throw new Error(\n \"BAKER_CHAT_ID is not set. This command stages changes against a chat — run it from a chat-attached environment.\",\n );\n }\n return env.BAKER_CHAT_ID;\n}\n\n/**\n * Which chat a *read* addresses. A `--chat` value names another chat in the same company — how an\n * earlier chat's staged changes are recovered verbatim instead of rebuilt from a summary — and with\n * no flag it is this session's own chat.\n *\n * Read paths only. Staging, amending and discarding keep calling `requireChatId`, so one session can\n * never edit another's draft; the backend enforces the same thing twice over, since every mutating\n * internal requires the draft be `active` and every route checks the chat belongs to the caller's\n * company.\n */\nexport function resolveChatId(chat?: unknown): string {\n return typeof chat === \"string\" && chat.length > 0 ? chat : requireChatId();\n}\n\n/**\n * Wall clock the whole capture may spend, when the caller bounded it.\n *\n * Read from the environment rather than taken as a flag, deliberately. A\n * `--budget-ms` argument would land in the command schema, and the schema is\n * what the agent reads — one question away from \"what is the budget for?\", and\n * from there the escalation ladder stops being invisible to it.\n *\n * Kept out of `getEnv()` because that validator is for a chat-attached run; the\n * capture engine also runs where none of those variables exist.\n */\nexport function captureBudgetMs(): number | null {\n const raw = Number(process.env.BAKER_CAPTURE_BUDGET_MS);\n return Number.isFinite(raw) && raw > 0 ? raw : null;\n}\n\n/**\n * Egress-proxy credentials for a capture, or none at all.\n *\n * These arrive as sandbox *command* env, never as arguments — a credential in\n * argv is readable by any `ps` sharing the sandbox. With none set,\n * `plannedRoutes` yields the direct route alone and a capture behaves exactly\n * as it did before the ladder existed.\n *\n * Whichever pairs arrive are the rungs this process may climb; there is nothing\n * to filter. A tier the deployment withholds is simply absent from this env, so\n * a ceiling read on this side could only ever disagree with the one place that\n * decides.\n */\nexport function captureProxyCredentials(): ProxyCredentials {\n return readProxyCredentials(process.env);\n}\n\n/**\n * This process's environment plus `extra`, for handing to a child.\n *\n * Lives here because `process.env` reads belong in this file — but it earns its\n * place beyond the lint rule: `spawn`'s `env` *replaces* the environment rather\n * than extending it, so a caller that passes only its additions silently strips\n * `PATH` and the child fails to start for a reason that looks nothing like the\n * cause.\n */\nexport function childEnvWith(extra: Record<string, string>): NodeJS.ProcessEnv {\n return { ...process.env, ...extra };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../proxy/src/challenge.ts","../../proxy/src/tiers.ts","../../proxy/src/credentials.ts","../../proxy/src/escalate.ts","../../proxy/src/privateAddress.ts","../../proxy/src/publicAddress.ts","../../proxy/src/route.ts","../../proxy/src/ladder.ts","../src/env.ts"],"sourcesContent":["/**\n * Was that a page, or the wall in front of it?\n *\n * A bot filter answers 200. The body parses, it has a title, and every check\n * downstream — `response.ok`, the status, the content type — says the read\n * succeeded. So a challenge is the one block that cannot be seen from the\n * status line, and the only place it is visible is the bytes.\n *\n * This lives in `@baker/proxy` rather than beside any one caller because the\n * escalation ladder is only coherent if every surface agrees on what \"blocked\"\n * means: the rung that gets climbed is chosen from this answer, and a detector\n * that differs per caller would mean the same wall costs money on one surface\n * and is filed as content on another.\n *\n * Pure and dependency-free, so it stays reachable from Convex's V8 runtime\n * through the package barrel.\n */\n\n/**\n * Phrases that only appear on a block or challenge page.\n *\n * Deliberately specific — \"access denied\" alone would match a page *about*\n * access control, so each phrase is one a real marketing page has no reason to\n * use as its title.\n */\nconst CHALLENGE_PHRASES = [\n \"just a moment\",\n \"attention required\",\n \"verify you are human\",\n \"checking your browser\",\n \"enable javascript and cookies to continue\",\n \"unusual traffic\",\n \"access denied\",\n \"you have been blocked\",\n \"request unsuccessful\",\n \"are you a robot\",\n \"security check\",\n \"ddos protection\",\n \"captcha\",\n];\n\n/** Vendors whose block pages carry a fingerprint even when the title does not. */\nconst CHALLENGE_MARKERS = [\"cf-browser-verification\", \"cf_chl_\", \"px-captcha\", \"_incapsula_\", \"distil_r_captcha\"];\n\n/**\n * How long a response may be and still be judged by its *wording*.\n *\n * Phrases like \"captcha\" or \"access denied\" are ordinary English that a real\n * article can legitimately contain, so matching them anywhere would refuse\n * pages we read perfectly well. Length is the precision guard: a page that is\n * mostly content is content.\n */\nexport const CHALLENGE_LENGTH_CEILING = 2_000;\n\n/**\n * Whether these bytes are a bot wall rather than the thing that was asked for.\n *\n * Reads only what the caller already paid for. Nothing here issues a request —\n * asking the host again would deepen the very block this is detecting.\n *\n * The two lists are searched differently, and that asymmetry is the point.\n * Phrases are judged only on a short response, for the precision reason above.\n * Vendor markers are fingerprints nothing but the vendor emits, so they are\n * searched at any length — which is what makes a real interstitial detectable\n * at all. Cloudflare's is 5–15 KB of inlined script wrapped around one visible\n * sentence, so gating markers behind the same length ceiling as the phrases put\n * every genuine large wall in a dead band where neither list could reach it.\n */\nexport function isChallengeBody(content: string, title?: string | null): boolean {\n if (content.length <= CHALLENGE_LENGTH_CEILING) {\n const haystack = `${title ?? \"\"}\\n${content}`.toLowerCase();\n if (CHALLENGE_PHRASES.some((phrase) => haystack.includes(phrase))) return true;\n }\n\n const whole = content.toLowerCase();\n return CHALLENGE_MARKERS.some((marker) => whole.includes(marker));\n}\n","/**\n * The two Oxylabs products, and everything that differs between them.\n *\n * They are NOT credential-swappable. Endpoint, username prefix, geo token and\n * sticky-session mechanism all differ, so \"same URL, other username/password\"\n * fails — and it fails as a 407, which Chromium reports as a plain network\n * error. Before this module existed there was no code path that could tell that\n * apart from the site refusing us, so the failure would have been recorded\n * against the page rather than against our own configuration.\n *\n * That asymmetry is the reason this table exists in exactly one place. Every\n * caller builds its route through `buildProxyRoute`; nobody concatenates an\n * Oxylabs URL by hand.\n */\n\nexport type ProxyTier = \"datacenter\" | \"residential\";\n\nexport interface ProxyTierSpec {\n readonly tier: ProxyTier;\n readonly host: string;\n readonly port: number;\n /** Oxylabs requires this in front of the account name. The two differ. */\n readonly usernamePrefix: \"user-\" | \"customer-\";\n /** Country targeting token. `-country-` on datacenter, `-cc-` on residential. */\n readonly countryKey: \"-country-\" | \"-cc-\";\n /**\n * Sticky-session token, or null when the product has none.\n *\n * Datacenter does stickiness by *port* (8001 for IP #1, 8002 for #2), not by\n * username. We do not implement that: a capture is a single page load, so a\n * reusable IP buys nothing and the knob would only add a way to get it wrong.\n */\n readonly sessionKey: \"-sessid-\" | null;\n}\n\nexport const PROXY_TIERS: Readonly<Record<ProxyTier, ProxyTierSpec>> = {\n datacenter: {\n tier: \"datacenter\",\n host: \"dc.oxylabs.io\",\n port: 8000,\n usernamePrefix: \"user-\",\n countryKey: \"-country-\",\n sessionKey: null,\n },\n residential: {\n tier: \"residential\",\n host: \"pr.oxylabs.io\",\n port: 7777,\n usernamePrefix: \"customer-\",\n countryKey: \"-cc-\",\n sessionKey: \"-sessid-\",\n },\n};\n\n/**\n * Cheapest useful route first, and the ladder never skips a rung.\n *\n * Residential costs materially more than datacenter, so it is only ever reached\n * because datacenter was tried and refused — never as a first guess and never\n * speculatively.\n */\nexport const ESCALATION_ORDER = [\"datacenter\", \"residential\"] as const satisfies readonly ProxyTier[];\n\n/** Every prefix this module knows, used to spot a credential in the wrong slot. */\nexport const KNOWN_USERNAME_PREFIXES = [\"user-\", \"customer-\"] as const;\n","import type { ProxyTier } from \"./tiers.ts\";\nimport { ESCALATION_ORDER } from \"./tiers.ts\";\n\n/**\n * The four environment variable names, in one place.\n *\n * Convex holds these and hands them to the sandbox; the CLI reads them back\n * out. A typo on either side is a *silent* no-op — the capability appears to\n * ship, every fetch quietly takes the direct route, and nothing fails. That is\n * the worst failure mode available to this feature, so the names are a shared\n * constant with a contract test rather than eight string literals spread across\n * two packages.\n */\nexport const PROXY_ENV_VARS = {\n datacenter: { username: \"OXYLABS_DATACENTER_USERNAME\", password: \"OXYLABS_DATACENTER_PASSWORD\" },\n residential: { username: \"OXYLABS_RESIDENTIAL_USERNAME\", password: \"OXYLABS_RESIDENTIAL_PASSWORD\" },\n} as const satisfies Record<ProxyTier, { username: string; password: string }>;\n\nexport interface TierCredentials {\n readonly username: string;\n readonly password: string;\n}\n\nexport type ProxyCredentials = Partial<Readonly<Record<ProxyTier, TierCredentials>>>;\n\n/**\n * Read whichever tiers are fully configured.\n *\n * Takes the env record rather than reading `process.env` itself, so the whole\n * package stays pure and the Convex and CLI sides can be tested identically.\n *\n * Half a pair is not a credential. A username with no password would build a\n * route that authenticates as nobody, and Oxylabs answers that with the same\n * 407 as a wrong password — so it would look like a broken proxy rather than an\n * unconfigured one. Dropping it here means `configuredTiers` tells the truth.\n */\nexport function readProxyCredentials(env: Record<string, string | undefined>): ProxyCredentials {\n const credentials: { -readonly [K in ProxyTier]?: TierCredentials } = {};\n for (const tier of ESCALATION_ORDER) {\n const username = env[PROXY_ENV_VARS[tier].username]?.trim();\n const password = env[PROXY_ENV_VARS[tier].password]?.trim();\n if (username && password) credentials[tier] = { username, password };\n }\n return credentials;\n}\n\n/**\n * The inverse of `readProxyCredentials`: credentials back out as env vars.\n *\n * The writing side used to be a loop inside one Convex function, which is how\n * the ladder came to exist everywhere and work in one place. That loop ran only\n * for `baker landing inspiration scrape`, so every other CLI fetch surface in a\n * Session — `images download`, canvas `ingest`, the two scaffolders, yt-dlp —\n * read four variables nobody had set and went direct, silently, exactly as\n * `PROXY_ENV_VARS` warns.\n *\n * Pairing it with the reader here is the point: the round trip is a test, so the\n * two sides cannot drift into agreeing about three variables out of four.\n */\nexport function proxyCredentialEnvs(credentials: ProxyCredentials): Record<string, string> {\n const envs: Record<string, string> = {};\n for (const tier of ESCALATION_ORDER) {\n const held = credentials[tier];\n if (!held) continue;\n envs[PROXY_ENV_VARS[tier].username] = held.username;\n envs[PROXY_ENV_VARS[tier].password] = held.password;\n }\n return envs;\n}\n\n/**\n * Which tiers this process could actually reach, cheapest first.\n *\n * There is no ceiling to apply. A deployment holding both pairs may climb to\n * residential, full stop — the brake is the credential itself. Removing\n * `OXYLABS_RESIDENTIAL_PASSWORD` takes effect on the next restart, needs no\n * deploy, and withholds the tier by the same mechanism a ceiling would have,\n * so the valve that used to sit here only added a second way to say it. It was\n * also never wired into ad-dna's task definition, which is the highest-volume\n * egress in the repo — a control that could not be turned where it mattered\n * most.\n */\nexport function configuredTiers(credentials: ProxyCredentials): ProxyTier[] {\n return ESCALATION_ORDER.filter((tier) => credentials[tier] !== undefined);\n}\n\n/** Whether there is any proxy to escalate to at all. */\nexport function proxyEscalationConfigured(credentials: ProxyCredentials): boolean {\n return configuredTiers(credentials).length > 0;\n}\n","/**\n * When is a failure worth spending a more expensive route on?\n *\n * The ladder only ever climbs on evidence that *who we are* was the problem. A\n * page that 404s, a domain that does not resolve, a certificate that will not\n * negotiate and a site that timed out all fail identically from every IP on\n * earth, so retrying them through a metered exit buys a second identical\n * failure and a bill. That asymmetry — cheap to be wrong in one direction,\n * expensive in the other — is why this is a closed allow-list rather than\n * \"escalate unless we recognise the error\".\n *\n * Shared deliberately: the capture engine, the image fetchers and the ad-media\n * downloaders all have to agree on what \"blocked\" means, or the corpus and the\n * bill disagree about the same event.\n */\n\n/** What one attempt observed, normalised across Playwright and `fetch`. */\nexport interface BlockSignal {\n /** Main-document / response status, when there was one. */\n readonly status?: number | null;\n /** Chromium `net::` name or a Node error code, when the attempt threw one. */\n readonly netError?: string | null;\n /** A challenge body was detected behind an otherwise successful response. */\n readonly challenge?: boolean;\n /** The attempt ran out of time rather than being refused. */\n readonly timedOut?: boolean;\n}\n\n/** \"We don't like *you*\" — a different exit IP is a different you. */\nconst ESCALATABLE_STATUSES = new Set([403, 429, 451]);\n\n/**\n * A TCP reset on ClientHello is how a WAF null-routes a datacenter range.\n *\n * Today all three of these collapse into one \"site didn't respond\" message and\n * are filed non-retryable, which is precisely why this class of block has been\n * invisible: it looks identical to a site that is genuinely down.\n */\nconst ESCALATABLE_NET_ERRORS = new Set([\"ERR_CONNECTION_RESET\", \"ERR_CONNECTION_CLOSED\", \"ERR_EMPTY_RESPONSE\"]);\n\n/**\n * Failures that belong to our route, not to the site.\n *\n * A 407 cannot come from an origin server — only something speaking proxy\n * produces one — so this needs no \"was this attempt proxied\" flag to be safe.\n */\nconst PROXY_NET_ERRORS = new Set([\n \"ERR_TUNNEL_CONNECTION_FAILED\",\n \"ERR_PROXY_CONNECTION_FAILED\",\n \"ERR_PROXY_AUTH_REQUESTED\",\n \"ERR_PROXY_CERTIFICATE_INVALID\",\n \"ERR_UNEXPECTED_PROXY_AUTH\",\n \"ERR_MANDATORY_PROXY_CONFIGURATION_FAILED\",\n \"ERR_HTTPS_PROXY_TUNNEL_RESPONSE_REDIRECT\",\n]);\n\nexport function isProxyFailure(signal: BlockSignal): boolean {\n if (signal.status === 407) return true;\n return signal.netError ? PROXY_NET_ERRORS.has(signal.netError) : false;\n}\n\nexport function shouldEscalate(signal: BlockSignal): boolean {\n // Our own broken route never advances the ladder — it degrades it. Climbing\n // here would spend a more expensive rung reproducing our misconfiguration.\n if (isProxyFailure(signal)) return false;\n if (signal.timedOut) return false;\n if (signal.challenge) return true;\n if (signal.status != null && ESCALATABLE_STATUSES.has(signal.status)) return true;\n return signal.netError ? ESCALATABLE_NET_ERRORS.has(signal.netError) : false;\n}\n","/**\n * The same question as `refuseNonPublicUrl`, asked of an address instead of a name.\n *\n * A URL carries a hostname, and a hostname is not an address. `localtest.me`\n * and `127.0.0.1.nip.io` are public names, with public DNS records, that\n * resolve to `127.0.0.1` — and nothing stops an attacker pointing a domain they\n * own at `169.254.169.254`. Proven against a real loopback server: the hostname\n * guard passed `http://localtest.me:4321/secret.txt` straight through, the bytes\n * were downloaded, and only the image parser complained afterwards.\n *\n * IPv6 is parsed rather than pattern-matched, and that is the second lesson\n * here. The first version tested prefixes with regexes and unwrapped an\n * IPv4-mapped address only when it still had a dot in it — but a URL parser\n * normalises `::ffff:127.0.0.1` to `::ffff:7f00:1`, so the dot is gone by the\n * time anyone looks. `[::ffff:169.254.169.254]` therefore read as public and the\n * ladder returned the metadata body with `outcome: \"ok\"`. One address has many\n * spellings; comparing spellings is the wrong operation.\n *\n * Anything unparseable is treated as private. This is a safety gate, and the\n * only safe reading of \"I do not know what this is\" is \"do not fetch it\".\n */\n\n/** Every IPv4 range that is not routable on the public internet. */\nfunction isPrivateIpv4Octets(octets: readonly number[]): boolean {\n const [a, b] = octets as [number, number, number, number];\n if (a === 0) return true; // \"this network\" — 0.0.0.0/8\n if (a === 10) return true; // private\n if (a === 127) return true; // loopback\n if (a === 169 && b === 254) return true; // link-local, and the cloud metadata address\n if (a === 172 && b >= 16 && b <= 31) return true; // private /12 — NOT all of 172.*\n if (a === 192 && b === 168) return true; // private\n if (a === 100 && b >= 64 && b <= 127) return true; // carrier-grade NAT\n if (a >= 224) return true; // multicast and reserved\n return false;\n}\n\n/** Dotted-quad to octets, or `null` when it is not one. */\nfunction parseIpv4(value: string): number[] | null {\n const parts = value.split(\".\");\n if (parts.length !== 4) return null;\n const octets = parts.map((part) => (/^\\d{1,3}$/.test(part) ? Number(part) : Number.NaN));\n if (octets.some((octet) => !Number.isInteger(octet) || octet < 0 || octet > 255)) return null;\n return octets;\n}\n\n/**\n * One side of a `::` to its 16-bit groups, or `null` if any part is not one.\n *\n * A trailing dotted-quad stands for the final two groups and is only legal in\n * last position — that is the spelling `::ffff:127.0.0.1` uses before a URL\n * parser rewrites it to `::ffff:7f00:1`.\n */\nfunction expandGroups(part: string): number[] | null {\n if (part === \"\") return [];\n const chunks = part.split(\":\");\n const groups: number[] = [];\n for (let index = 0; index < chunks.length; index++) {\n const chunk = chunks[index] as string;\n if (chunk.includes(\".\")) {\n if (index !== chunks.length - 1) return null;\n const octets = parseIpv4(chunk);\n if (!octets) return null;\n groups.push(((octets[0] as number) << 8) | (octets[1] as number));\n groups.push(((octets[2] as number) << 8) | (octets[3] as number));\n continue;\n }\n if (!/^[0-9a-f]{1,4}$/.test(chunk)) return null;\n groups.push(Number.parseInt(chunk, 16));\n }\n return groups;\n}\n\n/**\n * IPv6 to its eight 16-bit groups, or `null` when it is not one.\n *\n * Handles the `::` elision and a trailing dotted-quad, which together are why\n * one address has so many spellings.\n */\nfunction parseIpv6(value: string): number[] | null {\n const halves = value.split(\"::\");\n if (halves.length > 2) return null;\n\n const head = expandGroups(halves[0] as string);\n const tail = halves.length === 2 ? expandGroups(halves[1] as string) : [];\n if (head === null || tail === null) return null;\n\n if (halves.length === 1) return head.length === 8 ? head : null;\n const missing = 8 - head.length - tail.length;\n if (missing < 1) return null;\n return [...head, ...new Array<number>(missing).fill(0), ...tail];\n}\n\nexport function isPrivateAddress(ip: string): boolean {\n const address = ip.trim().toLowerCase();\n if (!address) return true;\n\n const v4 = parseIpv4(address);\n if (v4) return isPrivateIpv4Octets(v4);\n\n const v6 = parseIpv6(address);\n // Not an address we can read. A safety gate may not guess.\n if (!v6) return true;\n\n const [g0, g1, g2, g3, g4, g5, g6, g7] = v6 as [number, number, number, number, number, number, number, number];\n\n // `::` (unspecified) and `::1` (loopback).\n if (g0 === 0 && g1 === 0 && g2 === 0 && g3 === 0 && g4 === 0 && g5 === 0 && g6 === 0) return true;\n\n // IPv4-mapped (`::ffff:a.b.c.d`) and IPv4-translated (`::ffff:0:a.b.c.d`).\n // The whole point: by the time this is read the tail is hex, not dotted.\n const mappedV4 = g0 === 0 && g1 === 0 && g2 === 0 && g3 === 0;\n if (mappedV4 && g4 === 0 && g5 === 0xffff) {\n return isPrivateIpv4Octets([g6 >> 8, g6 & 0xff, g7 >> 8, g7 & 0xff]);\n }\n if (mappedV4 && g4 === 0xffff && g5 === 0) {\n return isPrivateIpv4Octets([g6 >> 8, g6 & 0xff, g7 >> 8, g7 & 0xff]);\n }\n\n if ((g0 & 0xfe00) === 0xfc00) return true; // fc00::/7 unique-local\n if ((g0 & 0xffc0) === 0xfe80) return true; // fe80::/10 link-local\n return false;\n}\n","/**\n * Addresses that must never be fetched, and must never be proxied.\n *\n * Lifted from `convex/landingLibrary/utils.ts`, where these three regexes were\n * the only copy in the repo — reachable from Convex and from nowhere else. That\n * was survivable while the capture ran one hop after the check. It is not\n * survivable now: the CLI can be invoked directly, two Convex paths enqueue\n * without re-validating, and a proxy adds a rung where \"which network am I on\"\n * stops being rhetorical.\n *\n * Two distinct reasons live here, and both point the same way:\n *\n * - **There is nothing to read.** A private address only resolves from inside\n * the network asking, so a capture of one produces a screenshot of an error.\n * - **There is something to read, and we must not.** `169.254.169.254` is the\n * cloud metadata endpoint that every capture runner has a route to.\n *\n * The rule for the proxy is *refuse*, not *bypass*. Bypassing would leave a\n * request that still happens, just unproxied; refusing means there is no\n * request to route. That is a stronger guarantee and a simpler one to check.\n */\n\nimport { isPrivateAddress } from \"./privateAddress.ts\";\n\n/**\n * Names — not addresses. Anything that looks like an address goes to\n * `isPrivateAddress`, which parses it instead of matching its spelling.\n */\nconst PRIVATE_HOST = /^(localhost|.*\\.localhost|.*\\.local)$/;\n/** Where a Session's own preview is served while the page is being built. */\nconst SANDBOX_HOST = /\\.e2b\\.(app|dev)$/;\n\n/**\n * A bracketed IPv6 literal, unwrapped.\n *\n * `new URL(\"http://[::1]/\").hostname` keeps the brackets, so a hostname check\n * that does not strip them is comparing against a string no address will ever\n * equal. The previous version tried to absorb that with `\\[?::1\\]?` and caught\n * exactly one address out of the whole of IPv6 — `[::ffff:169.254.169.254]`\n * sailed through and the ladder returned the metadata body with `outcome: \"ok\"`.\n */\nfunction unwrapIpLiteral(host: string): string | null {\n if (host.startsWith(\"[\") && host.endsWith(\"]\")) return host.slice(1, -1);\n // A bare v4 literal, or a v6 one that arrived without brackets.\n if (/^[0-9.]+$/.test(host) || host.includes(\":\")) return host;\n return null;\n}\n\nexport function isPrivateHostname(hostname: string): boolean {\n const host = hostname.trim().toLowerCase();\n if (!host) return true;\n if (PRIVATE_HOST.test(host) || SANDBOX_HOST.test(host)) return true;\n\n // Every address form — v4, v6, bracketed, mapped, elided — is decided by one\n // parser. Spelling an address in a regex is what let four of them through.\n const literal = unwrapIpLiteral(host);\n return literal === null ? false : isPrivateAddress(literal);\n}\n\n/**\n * The same rule again, in the only notation a browser's proxy settings speak.\n *\n * Chromium — and so Playwright and `agent-browser` — matches a bypass entry\n * against the hostname and understands one wildcard and no CIDR at all. The\n * ranges above therefore cannot be handed over as regexes; `172.16/12` has to\n * become sixteen entries, and there is no way to express \"any private address\".\n *\n * That makes this a second copy of one fact, which is worth stating plainly:\n * the day it disagrees with `isPrivateHostname` is the day a Session's own\n * preview at `localhost:4321` goes out through a metered exit and comes back\n * refused. Its test pins the correspondence.\n *\n * Note the direction reverses here, and deliberately. Everywhere else the rule\n * for a private address is *refuse*, because a request that still happens is\n * not a guarantee. A browser the agent drives has to reach `localhost:4321` —\n * that is its main job — so here the private address is the legitimate traffic\n * and the list says \"go direct\", not \"do not go\".\n */\nexport const BROWSER_PROXY_BYPASS = [\n \"localhost\",\n \"*.localhost\",\n \"127.0.0.1\",\n \"0.0.0.0\",\n \"::1\",\n \"*.local\",\n // A Session's own preview, which is served from the sandbox's public host —\n // public in DNS, ours in every sense that matters here.\n \"*.e2b.app\",\n \"*.e2b.dev\",\n \"10.*\",\n \"192.168.*\",\n \"169.254.*\",\n ...Array.from({ length: 16 }, (_, i) => `172.${16 + i}.*`),\n].join(\",\");\n\nexport type NonPublicReason = \"not_public\" | \"unparseable\";\n\n/**\n * `null` when the URL is safe to fetch; a reason when it must be refused.\n *\n * Anything that is not plain http(s) is refused too. A capture is handed URLs\n * from agents and from stored rows, and `file:` reaching a browser we launched\n * is a local file read wearing a URL.\n */\nexport function refuseNonPublicUrl(url: string): NonPublicReason | null {\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n return \"unparseable\";\n }\n if (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") return \"not_public\";\n return isPrivateHostname(parsed.hostname) ? \"not_public\" : null;\n}\n","import type { ProxyCredentials } from \"./credentials.ts\";\nimport type { ProxyTier, ProxyTierSpec } from \"./tiers.ts\";\nimport { KNOWN_USERNAME_PREFIXES, PROXY_TIERS } from \"./tiers.ts\";\n\n/**\n * How one attempt reaches the internet.\n *\n * Credentials stay in separate fields instead of being embedded in the URL:\n * Playwright's `proxy` option wants them that way, undici's `ProxyAgent` takes\n * them as a header we build once, and — the real reason — a password that never\n * exists as a substring of a URL cannot be leaked by anything that logs a URL.\n */\nexport type ProxyRoute =\n | { readonly kind: \"direct\" }\n | {\n readonly kind: \"proxy\";\n readonly tier: ProxyTier;\n /** e.g. `http://dc.oxylabs.io:8000` */\n readonly server: string;\n readonly username: string;\n readonly password: string;\n };\n\nexport const DIRECT_ROUTE: ProxyRoute = { kind: \"direct\" };\n\nexport interface RouteOptions {\n /** ISO-3166 alpha-2, upper-cased here. Omit for \"wherever\" — the default. */\n readonly country?: string;\n /** Sticky-session id. Ignored by a tier whose product has no session syntax. */\n readonly session?: string;\n}\n\nexport type RouteProblem = \"missing\" | \"wrong_prefix\";\n\nexport type RouteResult = { ok: true; route: ProxyRoute } | { ok: false; problem: RouteProblem };\n\n/**\n * Put the account name into the shape this specific product expects.\n *\n * Three cases, and the third is the one worth being strict about:\n *\n * 1. Bare (`acme`) — someone pasted the account name. Prepend the prefix.\n * 2. Already correct (`user-acme` in the datacenter slot) — someone pasted the\n * full Oxylabs username. Leave it alone.\n * 3. Carrying the OTHER product's prefix (`customer-acme` in the datacenter\n * slot) — refuse.\n *\n * Case 3 must not be \"helpfully\" rewritten. A `customer-` value in the\n * datacenter slot means the residential credentials were pasted into the wrong\n * variable, and rewriting the prefix would authenticate a residential account\n * against the datacenter endpoint. That either 407s — wasting the cheap rung\n * for no reason — or it works, and bills residential rates from the tier whose\n * entire purpose is to be the cheap one. Refusing is the only answer that\n * cannot silently cost money.\n */\nexport function proxyUsername(\n spec: ProxyTierSpec,\n rawUsername: string,\n options?: RouteOptions,\n): { ok: true; username: string } | { ok: false; problem: \"wrong_prefix\" } {\n const raw = rawUsername.trim();\n const foreignPrefix = KNOWN_USERNAME_PREFIXES.find(\n (prefix) => prefix !== spec.usernamePrefix && raw.startsWith(prefix),\n );\n if (foreignPrefix) return { ok: false, problem: \"wrong_prefix\" };\n\n let username = raw.startsWith(spec.usernamePrefix) ? raw : `${spec.usernamePrefix}${raw}`;\n // Geo before session — Oxylabs reads the username left to right and rejects\n // the pair in the other order.\n if (options?.country) username += `${spec.countryKey}${options.country.toUpperCase()}`;\n if (options?.session && spec.sessionKey) username += `${spec.sessionKey}${options.session}`;\n return { ok: true, username };\n}\n\n/** Build the route for one tier, or say why there isn't one. */\nexport function buildProxyRoute(tier: ProxyTier, credentials: ProxyCredentials, options?: RouteOptions): RouteResult {\n const held = credentials[tier];\n if (!held) return { ok: false, problem: \"missing\" };\n\n const spec = PROXY_TIERS[tier];\n const username = proxyUsername(spec, held.username, options);\n if (!username.ok) return username;\n\n return {\n ok: true,\n route: {\n kind: \"proxy\",\n tier,\n server: `http://${spec.host}:${spec.port}`,\n username: username.username,\n password: held.password,\n },\n };\n}\n","import type { ProxyCredentials } from \"./credentials.ts\";\nimport { configuredTiers } from \"./credentials.ts\";\nimport { refuseNonPublicUrl } from \"./publicAddress.ts\";\nimport type { ProxyRoute, RouteOptions } from \"./route.ts\";\nimport { buildProxyRoute, DIRECT_ROUTE } from \"./route.ts\";\n\n/**\n * Every route this URL is allowed to be attempted on, in the order to try them.\n *\n * Direct is always first and always present: an unblocked page must cost\n * nothing, and a deployment with no credentials has to behave exactly as it did\n * before any of this existed.\n *\n * A tier whose credentials are malformed is *skipped*, not fatal. One\n * mis-pasted variable should cost the use of that rung, not the whole ladder —\n * the alternative is that a typo in the cheap tier silently disables the\n * expensive one that would have worked.\n */\nexport function plannedRoutes(url: string, credentials: ProxyCredentials, options?: RouteOptions): ProxyRoute[] {\n // A private address is refused upstream; if one reaches here anyway it must\n // not become a billed request, and there is no exit node on earth from which\n // `localhost` means us.\n if (refuseNonPublicUrl(url) !== null) return [DIRECT_ROUTE];\n\n const routes: ProxyRoute[] = [DIRECT_ROUTE];\n for (const tier of configuredTiers(credentials)) {\n const built = buildProxyRoute(tier, credentials, options);\n if (built.ok) routes.push(built.route);\n }\n return routes;\n}\n","import type { ProxyCredentials } from \"@baker/proxy\";\nimport { readProxyCredentials } from \"@baker/proxy\";\nimport { createEnv } from \"@t3-oss/env-core\";\nimport { z } from \"zod\";\n\ntype Env = {\n BAKER_API_KEY: string;\n BAKER_API_URL: string;\n BAKER_CHAT_ID?: string;\n BAKER_ACTING_USER_ID?: string;\n BAKER_GOOGLE_ADS_CUSTOMER_ID?: string;\n BAKER_GA4_PROPERTY_ID?: string;\n BAKER_GSC_SITE_URL?: string;\n BAKER_X_ADS_ACCOUNT_ID?: string;\n BAKER_META_AD_ACCOUNT_ID?: string;\n BAKER_LINKEDIN_AD_ACCOUNT_ID?: string;\n};\n\nlet cached: Env | undefined;\n\nexport function getEnv(): Env {\n if (!cached) {\n cached = createEnv({\n server: {\n BAKER_API_KEY: z.string().startsWith(\"bk_\", \"API key must start with 'bk_'\"),\n BAKER_API_URL: z.url(\"BAKER_API_URL must be a valid URL\"),\n BAKER_CHAT_ID: z.string().optional(),\n BAKER_ACTING_USER_ID: z.string().optional(),\n BAKER_GOOGLE_ADS_CUSTOMER_ID: z\n .string()\n .regex(/^\\d{10}$/)\n .optional(),\n BAKER_GA4_PROPERTY_ID: z.string().optional(),\n BAKER_GSC_SITE_URL: z.string().optional(),\n BAKER_X_ADS_ACCOUNT_ID: z\n .string()\n .regex(/^[a-z0-9]+$/, \"X Ads account ID must be a base36 string\")\n .optional(),\n BAKER_META_AD_ACCOUNT_ID: z.string().optional(),\n BAKER_LINKEDIN_AD_ACCOUNT_ID: z\n .string()\n .regex(/^\\d+$/, \"LinkedIn ad account ID must be the numeric portion of urn:li:sponsoredAccount:N\")\n .optional(),\n },\n runtimeEnv: process.env,\n });\n }\n return cached;\n}\n\n// Read directly (not via the validated schema): debug logging must work even when\n// the required BAKER_API_KEY/BAKER_API_URL are missing or malformed.\nexport function debugLogSetting(): string | undefined {\n const raw = process.env.BAKER_DEBUG_LOG?.trim();\n return raw ? raw : undefined;\n}\n\nexport function requireChatId(): string {\n const env = getEnv();\n if (!env.BAKER_CHAT_ID) {\n throw new Error(\n \"BAKER_CHAT_ID is not set. This command stages changes against a chat — run it from a chat-attached environment.\",\n );\n }\n return env.BAKER_CHAT_ID;\n}\n\n/**\n * Which chat a *read* addresses. A `--chat` value names another chat in the same company — how an\n * earlier chat's staged changes are recovered verbatim instead of rebuilt from a summary — and with\n * no flag it is this session's own chat.\n *\n * Read paths only. Staging, amending and discarding keep calling `requireChatId`, so one session can\n * never edit another's draft; the backend enforces the same thing twice over, since every mutating\n * internal requires the draft be `active` and every route checks the chat belongs to the caller's\n * company.\n */\nexport function resolveChatId(chat?: unknown): string {\n return typeof chat === \"string\" && chat.length > 0 ? chat : requireChatId();\n}\n\n/**\n * Wall clock the whole capture may spend, when the caller bounded it.\n *\n * Read from the environment rather than taken as a flag, deliberately. A\n * `--budget-ms` argument would land in the command schema, and the schema is\n * what the agent reads — one question away from \"what is the budget for?\", and\n * from there the escalation ladder stops being invisible to it.\n *\n * Kept out of `getEnv()` because that validator is for a chat-attached run; the\n * capture engine also runs where none of those variables exist.\n */\nexport function captureBudgetMs(): number | null {\n const raw = Number(process.env.BAKER_CAPTURE_BUDGET_MS);\n return Number.isFinite(raw) && raw > 0 ? raw : null;\n}\n\n/**\n * Egress-proxy credentials for a capture, or none at all.\n *\n * These arrive as sandbox *command* env, never as arguments — a credential in\n * argv is readable by any `ps` sharing the sandbox. With none set,\n * `plannedRoutes` yields the direct route alone and a capture behaves exactly\n * as it did before the ladder existed.\n *\n * Whichever pairs arrive are the rungs this process may climb; there is nothing\n * to filter. A tier the deployment withholds is simply absent from this env, so\n * a ceiling read on this side could only ever disagree with the one place that\n * decides.\n */\nexport function captureProxyCredentials(): ProxyCredentials {\n return readProxyCredentials(process.env);\n}\n\n/**\n * The Vimeo session cookie the deployment supplied, or undefined.\n *\n * Absent is the normal, supported state: a deployment that has not configured\n * one gets the same \"download it and upload the file\" refusal Vimeo gave before\n * this existed. Read here with the other env, so nothing else reaches into\n * `process.env` for it.\n */\nexport function captureVimeoCookie(): string | undefined {\n const raw = process.env.VIMEO_COOKIE?.trim();\n return raw ? raw : undefined;\n}\n\n/**\n * This process's environment plus `extra`, for handing to a child.\n *\n * Lives here because `process.env` reads belong in this file — but it earns its\n * place beyond the lint rule: `spawn`'s `env` *replaces* the environment rather\n * than extending it, so a caller that passes only its additions silently strips\n * `PATH` and the child fails to start for a reason that looks nothing like the\n * cause.\n */\nexport function childEnvWith(extra: Record<string, string>): NodeJS.ProcessEnv {\n return { ...process.env, ...extra };\n}\n"],"mappings":"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","names":[]}