From e0856e448cb394bd471870c3166dd8c7dc0ae0a3 Mon Sep 17 00:00:00 2001 From: Klaus Agnoletti Date: Fri, 29 May 2026 17:18:11 +0200 Subject: [PATCH 1/2] Security: stop leaking API credentials in Recon tools IpinfoClient.ts sent the ipinfo.io token as a ?token= URL query parameter on both the single and batch lookups; URL query strings are routinely captured in proxy logs, server access logs, and browser/agent history. Move the token to an Authorization: Bearer header. SubdomainEnum.ts passed the ProjectDiscovery key as 'chaos -key ', exposing it in the process argument list (visible to any local process via ps/procfs). chaos already reads PDCP_API_KEY from the environment, so pass it via env instead. --- Packs/Security/src/Recon/Tools/IpinfoClient.ts | 11 ++++++++--- Packs/Security/src/Recon/Tools/SubdomainEnum.ts | 4 +++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/Packs/Security/src/Recon/Tools/IpinfoClient.ts b/Packs/Security/src/Recon/Tools/IpinfoClient.ts index 36fd633d1b..e34cbd2055 100755 --- a/Packs/Security/src/Recon/Tools/IpinfoClient.ts +++ b/Packs/Security/src/Recon/Tools/IpinfoClient.ts @@ -83,10 +83,14 @@ export class IPInfoClient { // Rate limiting await this.rateLimit(); - const url = `${this.baseUrl}/${ip}/json?token=${this.apiKey}`; + // Send the token via Authorization header, not the URL query string + // (URL params leak into proxy/server logs). + const url = `${this.baseUrl}/${ip}/json`; try { - const response = await fetch(url); + const response = await fetch(url, { + headers: { Authorization: `Bearer ${this.apiKey}` }, + }); if (!response.ok) { if (response.status === 429) { @@ -135,13 +139,14 @@ export class IPInfoClient { // Rate limiting await this.rateLimit(); - const url = `${this.baseUrl}/batch?token=${this.apiKey}`; + const url = `${this.baseUrl}/batch`; try { const response = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json", + Authorization: `Bearer ${this.apiKey}`, }, body: JSON.stringify(uncachedIPs), }); diff --git a/Packs/Security/src/Recon/Tools/SubdomainEnum.ts b/Packs/Security/src/Recon/Tools/SubdomainEnum.ts index bfe0723895..ec6730c09c 100755 --- a/Packs/Security/src/Recon/Tools/SubdomainEnum.ts +++ b/Packs/Security/src/Recon/Tools/SubdomainEnum.ts @@ -47,7 +47,9 @@ async function runChaos(domain: string): Promise { return []; } try { - const result = await $`chaos -key ${key} -d ${domain} -silent`.text(); + // chaos reads PDCP_API_KEY from the environment; passing -key would expose + // the credential in the process argument list (visible via ps/proc). + const result = await $`chaos -d ${domain} -silent`.env({ ...process.env, PDCP_API_KEY: key }).text(); return result.trim().split("\n").filter(Boolean); } catch { console.error("[chaos] Failed"); From a27aa242a95d5acf31be9889bc153ef3df3e5023 Mon Sep 17 00:00:00 2001 From: Klaus Agnoletti Date: Fri, 29 May 2026 17:20:53 +0200 Subject: [PATCH 2/2] docs(security): clarify with_server.py shell=True trust model Document why shell=True is intentional here (operator-supplied local --server command at the caller's own shell privilege, needs cd/&&). No behavior change; adds a # nosec note so scanners stop flagging a non-vulnerability. --- .../src/WebAssessment/WebappScripts/with_server.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Packs/Security/src/WebAssessment/WebappScripts/with_server.py b/Packs/Security/src/WebAssessment/WebappScripts/with_server.py index 431f2eba16..9d9ad65c4b 100755 --- a/Packs/Security/src/WebAssessment/WebappScripts/with_server.py +++ b/Packs/Security/src/WebAssessment/WebappScripts/with_server.py @@ -65,10 +65,13 @@ def main(): for i, server in enumerate(servers): print(f"Starting server {i+1}/{len(servers)}: {server['cmd']}") - # Use shell=True to support commands with cd and && + # shell=True is intentional: this is a local developer helper and `server['cmd']` + # is the operator's own --server argument (needs cd/&& support). The command is + # already operator-controlled at the same trust level as the shell invoking this + # script, so there is no privilege boundary to cross. Do NOT pass untrusted input here. process = subprocess.Popen( server['cmd'], - shell=True, + shell=True, # nosec B602 - operator-supplied local command, see note above stdout=subprocess.PIPE, stderr=subprocess.PIPE )