From 3fb5bad834cf9551a42c5e6e9d079f4b47895919 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 19 Aug 2026 12:08:24 +0000 Subject: [PATCH] fix: never forward credential headers to the target website The keep_headers=true parameter copied every inbound header onto the outbound request, including the Authorization header that callers use to authenticate to this Actor. Any caller who authenticated with an Authorization header and scraped a URL they did not control leaked their Apify API token to that URL's operator. Because Apify tokens are account-scoped, the impact reached well beyond this Actor. README.md already documented that the Authorization header is stripped in this mode; the implementation never did it. The sibling spb-/ant- prefixed forwarding path in the same function does filter headers (cookie, set-cookie, host), so the mechanism existed but was not applied here. Strip authorization and proxy-authorization in the keep_headers path, and update the README to state the guarantee it now actually keeps. Headers explicitly opted in per-request via the spb-/ant- prefixes are unchanged: those carry a value the caller chose to send to the target. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Lphs4iiGsBBumxe9uezJ9B --- README.md | 2 +- src/const.ts | 7 +++++++ src/utils.ts | 5 ++++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e707234..a3cf2fe 100644 --- a/README.md +++ b/README.md @@ -161,7 +161,7 @@ SuperScraper API supports most of the API parameters of [ScraperAPI](https://doc | `premium` | Use residential proxies to fetch the web content, in order to reduce the probability of being blocked. Can be either `true` or `false`, default is `false`. This is equivalent to setting ScrapingBee's `premium_proxy`. | | `ultra_premium` | Same as `premium`. | | `country_code` | Use IP addresses that are geolocated in the specified country by specifying its [2-letter ISO code](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2#Officially_assigned_code_elements). When using code other than `US`, `premium_proxy` must be set to `true`. This is equivalent to setting ScrapingAnt's `proxy_country`. | -| `keep_headers` | If `true`, then all headers sent to this Actor will be forwarded to the target website. The `Authorization` header will be removed. | +| `keep_headers` | If `true`, then all headers sent to this Actor will be forwarded to the target website. The `Authorization` and `Proxy-Authorization` headers are never forwarded, so your Apify API token is not exposed to the target website. | | `device_type` | Can be either `desktop` (default) or `mobile`. This is equivalent to setting ScrapingBees's `device`. | | `binary_target` | Specify whether the target is a file. Can be `true` or `false`, default is `false`. Currently only supported when JS rendering is set to `false` via the `render_js`, `browser`, or `render` parameters. | diff --git a/src/const.ts b/src/const.ts index bae7925..509d699 100644 --- a/src/const.ts +++ b/src/const.ts @@ -19,3 +19,10 @@ export const VALID_RESOURCES = [ 'manifest', 'other', ]; + +// Headers that authenticate the caller to this Actor. Forwarding them to the scraped +// website would hand the caller's Apify API token to that website's operator. +export const CREDENTIAL_HEADERS = [ + 'authorization', + 'proxy-authorization', +]; diff --git a/src/utils.ts b/src/utils.ts index 62b85a8..46cd180 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -10,7 +10,7 @@ import { EquivalentParameters, ScrapingBee, ScraperApi, ScrapingAnt } from './pa import { UserInputError } from './errors.js'; import { validateAndTransformExtractRules } from './extract_rules_utils.js'; import { parseAndValidateInstructions } from './instructions_utils.js'; -import { Label, VALID_RESOURCES } from './const.js'; +import { CREDENTIAL_HEADERS, Label, VALID_RESOURCES } from './const.js'; const transformTimeMeasuresToRelative = (timeMeasures: TimeMeasure[]): TimeMeasure[] => { const firstMeasure = timeMeasures[0].time; @@ -268,6 +268,9 @@ export function createRequestForCrawler(params: ParsedUrlQuery, req: IncomingMes const reqHeaders = req.headers; const headersToForward: Record = {}; for (const [key, val] of Object.entries(reqHeaders)) { + if (CREDENTIAL_HEADERS.includes(key.toLowerCase())) { + continue; + } if (Array.isArray(val)) { continue; }