在将 JSON 请求发送到应用程序之前,将顶层 customerId 字段替换为 [REDACTED]。将此模板用于具有已知模式以及未压缩 UTF-8 JSON 正文的特定端点。
这是基于字段的转换。它不会在任意文本或文件中检测敏感数据,也不会使用数据丢失防护(DLP)匹配结果。
Gateway 在将原始请求体交给 Worker 之前对其进行检查。DLP Block 决定可以中断 Worker,因此此脚本不能使用脱敏来绕过请求侧 DLP 检查。
Gateway 在转发之前不会再次扫描修改后的请求。
在 Worker 项目中,将 @cloudflare/zt-types ↗ 2026.10.1 或更高版本安装为开发依赖项:
npm i -D @cloudflare/zt-typesyarn add -D @cloudflare/zt-typespnpm add -D @cloudflare/zt-typesbun add -d @cloudflare/zt-types保留 src/index.ts 中 GatewayMiddlewareContext 和 GatewayMiddlewareNext 的 import type 声明。它们描述 Gateway 上下文和延续函数,并会在 TypeScript 编译时被移除。从 cloudflare:workers 导入运行时 WorkerEntrypoint 类。
将此脚本用作 Worker 项目中的 src/index.ts。它不需要绑定。
import { WorkerEntrypoint } from "cloudflare:workers";
const LIMIT = 256 * 1024;
export class GatewayMiddleware extends WorkerEntrypoint {
async handle(request, context, next) {
if (request.method !== "POST") return next(request);
const type = request.headers.get("content-type") ?? "";
if (
!/^application\/json\s*(?:;|$)/i.test(type) ||
request.headers.has("content-encoding")
) {
return new Response("Expected uncompressed JSON", { status: 415 });
}
if (!request.body)
return new Response("Expected a JSON object", { status: 400 });
const reader = request.body.getReader();
const decoder = new TextDecoder("utf-8", { fatal: true, ignoreBOM: false });
let size = 0;
let text = "";
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
size += value.byteLength;
if (size > LIMIT) {
void reader.cancel().catch(() => {});
return new Response("Request body too large", { status: 413 });
}
text += decoder.decode(value, { stream: true });
}
text += decoder.decode();
} catch {
void reader.cancel().catch(() => {});
return new Response("Invalid request body", { status: 400 });
} finally {
reader.releaseLock();
}
let object;
try {
object = JSON.parse(text);
if (!object || typeof object !== "object" || Array.isArray(object))
throw new Error();
} catch {
return new Response("Expected a JSON object", { status: 400 });
}
if (Object.hasOwn(object, "customerId")) object.customerId = "[REDACTED]";
const headers = new Headers(request.headers);
for (const name of [
"content-length",
"content-md5",
"digest",
"content-digest",
"repr-digest",
]) {
headers.delete(name);
}
return next(
new Request(request, { headers, body: JSON.stringify(object) }),
);
}
}import { WorkerEntrypoint } from "cloudflare:workers";
import type {
GatewayMiddlewareContext,
GatewayMiddlewareNext,
} from "@cloudflare/zt-types";
const LIMIT = 256 * 1024;
export class GatewayMiddleware extends WorkerEntrypoint {
async handle(
request: Request,
context: GatewayMiddlewareContext,
next: GatewayMiddlewareNext,
): Promise<Response> {
if (request.method !== "POST") return next(request);
const type = request.headers.get("content-type") ?? "";
if (
!/^application\/json\s*(?:;|$)/i.test(type) ||
request.headers.has("content-encoding")
) {
return new Response("Expected uncompressed JSON", { status: 415 });
}
if (!request.body)
return new Response("Expected a JSON object", { status: 400 });
const reader = request.body.getReader();
const decoder = new TextDecoder("utf-8", { fatal: true, ignoreBOM: false });
let size = 0;
let text = "";
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
size += value.byteLength;
if (size > LIMIT) {
void reader.cancel().catch(() => {});
return new Response("Request body too large", { status: 413 });
}
text += decoder.decode(value, { stream: true });
}
text += decoder.decode();
} catch {
void reader.cancel().catch(() => {});
return new Response("Invalid request body", { status: 400 });
} finally {
reader.releaseLock();
}
let object: Record<string, unknown>;
try {
object = JSON.parse(text);
if (!object || typeof object !== "object" || Array.isArray(object))
throw new Error();
} catch {
return new Response("Expected a JSON object", { status: 400 });
}
if (Object.hasOwn(object, "customerId")) object.customerId = "[REDACTED]";
const headers = new Headers(request.headers);
for (const name of [
"content-length",
"content-md5",
"digest",
"content-digest",
"repr-digest",
]) {
headers.delete(name);
}
return next(
new Request(request, { headers, body: JSON.stringify(object) }),
);
}
}读取器在流式传输时统计字节,并拒绝大于 256 KiB 的正文。无效 JSON、压缩正文和不支持的内容类型会收到明确错误。脚本会重建已消费的正文并保留原始请求 URL。
部署 Worker,并为受控 API 主机和路径创建 Forward 策略,HTTP Method 为 POST,并限定到你的测试用户组。
通过 Gateway 发送此 JSON 正文:
{
"customerId": "test-customer",
"message": "Test request"
}在源站确认 customerId 为 [REDACTED],且 message 未更改。验证无效 JSON 正文返回 400,超过大小限制的正文返回 413,且这两个请求都不会到达源站。
该转换会更改 JSON 序列化,并可能使已签名的有效负载或应用程序校验和失效。仅将其用于接受重写 JSON 的端点。它替换一个顶层字段,不会脱敏嵌套字段、查询参数或其他标头。