fix(security): strip Azure HTTP headers from thread config to prevent IP leak
The LangGraph API server injects all x-* request headers into thread.config.configurable, which gets persisted and exposed via POST /threads/search. This leaked Azure-injected headers (x-forwarded-for, x-client-ip, x-arr-ssl, etc.) to any user. Adds a Hono middleware (http.app) that strips sensitive infrastructure headers at the HTTP layer before they reach the LangGraph API routes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
569e157f25
commit
60735de884
@@ -6,6 +6,9 @@
|
||||
"ui": {
|
||||
"agent": "./src/agent-uis/index.tsx"
|
||||
},
|
||||
"http": {
|
||||
"app": "./src/agent/http.ts:app"
|
||||
},
|
||||
"env": ".env",
|
||||
"dependencies": ["."]
|
||||
}
|
||||
|
||||
@@ -84,6 +84,7 @@
|
||||
"eslint-plugin-react-hooks": "^5.0.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.18",
|
||||
"globals": "^15.14.0",
|
||||
"hono": "^4.12.12",
|
||||
"prettier": "^3.5.2",
|
||||
"tailwind-scrollbar": "^4.0.1",
|
||||
"tailwindcss": "^4.0.6",
|
||||
|
||||
Generated
+13
-10
@@ -201,6 +201,9 @@ importers:
|
||||
globals:
|
||||
specifier: ^15.14.0
|
||||
version: 15.15.0
|
||||
hono:
|
||||
specifier: ^4.12.12
|
||||
version: 4.12.12
|
||||
prettier:
|
||||
specifier: ^3.5.2
|
||||
version: 3.5.2
|
||||
@@ -2397,8 +2400,8 @@ packages:
|
||||
highlightjs-vue@1.0.0:
|
||||
resolution: {integrity: sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==}
|
||||
|
||||
hono@4.7.2:
|
||||
resolution: {integrity: sha512-8V5XxoOF6SI12jkHkzX/6aLBMU5GEF5g387EjVSQipS0DlxWgWGSMeEayY3CRBjtTUQYwLHx9JYouWqKzy2Vng==}
|
||||
hono@4.12.12:
|
||||
resolution: {integrity: sha512-p1JfQMKaceuCbpJKAPKVqyqviZdS0eUxH9v82oWo1kb9xjQ5wA6iP3FNVAPDFlz5/p7d45lO+BpSk1tuSZMF4Q==}
|
||||
engines: {node: '>=16.9.0'}
|
||||
|
||||
html-url-attributes@3.0.1:
|
||||
@@ -4519,13 +4522,13 @@ snapshots:
|
||||
|
||||
'@google/generative-ai@0.21.0': {}
|
||||
|
||||
'@hono/node-server@1.13.8(hono@4.7.2)':
|
||||
'@hono/node-server@1.13.8(hono@4.12.12)':
|
||||
dependencies:
|
||||
hono: 4.7.2
|
||||
hono: 4.12.12
|
||||
|
||||
'@hono/zod-validator@0.2.2(hono@4.7.2)(zod@3.24.2)':
|
||||
'@hono/zod-validator@0.2.2(hono@4.12.12)(zod@3.24.2)':
|
||||
dependencies:
|
||||
hono: 4.7.2
|
||||
hono: 4.12.12
|
||||
zod: 3.24.2
|
||||
|
||||
'@humanfs/core@0.19.1': {}
|
||||
@@ -4613,8 +4616,8 @@ snapshots:
|
||||
'@langchain/langgraph-api@0.0.30(@langchain/core@0.3.45(openai@4.95.1(zod@3.24.2)))(@langchain/langgraph-checkpoint@0.0.17(@langchain/core@0.3.45(openai@4.95.1(zod@3.24.2))))(@langchain/langgraph-sdk@0.0.73(@langchain/core@0.3.45(openai@4.95.1(zod@3.24.2)))(react@19.0.0))(@langchain/langgraph@0.2.64(@langchain/core@0.3.45(openai@4.95.1(zod@3.24.2)))(react@19.0.0)(zod-to-json-schema@3.24.3(zod@3.24.2)))(openai@4.95.1(zod@3.24.2))(typescript@5.7.3)':
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.26.2
|
||||
'@hono/node-server': 1.13.8(hono@4.7.2)
|
||||
'@hono/zod-validator': 0.2.2(hono@4.7.2)(zod@3.24.2)
|
||||
'@hono/node-server': 1.13.8(hono@4.12.12)
|
||||
'@hono/zod-validator': 0.2.2(hono@4.12.12)(zod@3.24.2)
|
||||
'@langchain/core': 0.3.45(openai@4.95.1(zod@3.24.2))
|
||||
'@langchain/langgraph': 0.2.64(@langchain/core@0.3.45(openai@4.95.1(zod@3.24.2)))(react@19.0.0)(zod-to-json-schema@3.24.3(zod@3.24.2))
|
||||
'@langchain/langgraph-checkpoint': 0.0.17(@langchain/core@0.3.45(openai@4.95.1(zod@3.24.2)))
|
||||
@@ -4624,7 +4627,7 @@ snapshots:
|
||||
dedent: 1.5.3
|
||||
dotenv: 16.4.7
|
||||
exit-hook: 4.0.0
|
||||
hono: 4.7.2
|
||||
hono: 4.12.12
|
||||
langsmith: 0.2.15(openai@4.95.1(zod@3.24.2))
|
||||
open: 10.1.0
|
||||
semver: 7.7.1
|
||||
@@ -6308,7 +6311,7 @@ snapshots:
|
||||
|
||||
highlightjs-vue@1.0.0: {}
|
||||
|
||||
hono@4.7.2: {}
|
||||
hono@4.12.12: {}
|
||||
|
||||
html-url-attributes@3.0.1: {}
|
||||
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* Custom HTTP middleware for LangGraph API server.
|
||||
*
|
||||
* Strips sensitive HTTP headers (injected by Azure Front Door / App Service)
|
||||
* before they reach the LangGraph API routes. Without this, headers like
|
||||
* x-forwarded-for, x-client-ip, x-arr-ssl etc. get persisted into
|
||||
* thread.config.configurable and are exposed via POST /threads/search.
|
||||
*
|
||||
* Registered in langgraph.json as: { "http": { "app": "./src/agent/http.ts:app" } }
|
||||
*/
|
||||
import { Hono } from "hono";
|
||||
|
||||
/**
|
||||
* Headers injected by Azure infrastructure that must NOT leak into
|
||||
* config.configurable. The LangGraph API server copies every `x-*`
|
||||
* header (except x-api-key / x-tenant-id / x-service-key) into the
|
||||
* run config, so we strip them at the HTTP layer.
|
||||
*/
|
||||
const SENSITIVE_HEADER_PREFIXES = [
|
||||
"x-forwarded-",
|
||||
"x-client-",
|
||||
"x-arr-",
|
||||
"x-waws-",
|
||||
"x-original-",
|
||||
"x-site-",
|
||||
"x-azure-",
|
||||
"x-ms-",
|
||||
"x-appservice-",
|
||||
"x-liveupgrade",
|
||||
] as const;
|
||||
|
||||
const SENSITIVE_HEADER_EXACT = new Set([
|
||||
"x-client-ip",
|
||||
"x-client-port",
|
||||
"x-forwarded-for",
|
||||
"x-forwarded-host",
|
||||
"x-forwarded-proto",
|
||||
"x-forwarded-port",
|
||||
"x-forwarded-scheme",
|
||||
"x-forwarded-tlsversion",
|
||||
"x-arr-ssl",
|
||||
"x-arr-log-id",
|
||||
"x-waws-unencoded-url",
|
||||
"x-original-url",
|
||||
"x-original-host",
|
||||
"x-site-deployment-id",
|
||||
"x-azure-requestchainv2",
|
||||
"x-azure-socketip",
|
||||
"x-azure-ref",
|
||||
"x-azure-fdid",
|
||||
"x-azure-clientip",
|
||||
"x-real-ip",
|
||||
"client-ip",
|
||||
"disguised-host",
|
||||
"was-default-hostname",
|
||||
"max-forwards",
|
||||
"user-agent",
|
||||
]);
|
||||
|
||||
function isSensitiveHeader(key: string): boolean {
|
||||
const lower = key.toLowerCase();
|
||||
if (SENSITIVE_HEADER_EXACT.has(lower)) return true;
|
||||
for (const prefix of SENSITIVE_HEADER_PREFIXES) {
|
||||
if (lower.startsWith(prefix)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export const app = new Hono();
|
||||
|
||||
app.use("*", async (c, next) => {
|
||||
// Collect header names to delete (can't mutate while iterating)
|
||||
const toDelete: string[] = [];
|
||||
for (const [key] of c.req.raw.headers.entries()) {
|
||||
if (isSensitiveHeader(key)) {
|
||||
toDelete.push(key);
|
||||
}
|
||||
}
|
||||
for (const key of toDelete) {
|
||||
c.req.raw.headers.delete(key);
|
||||
}
|
||||
await next();
|
||||
});
|
||||
Reference in New Issue
Block a user