跳转到内容
本站为社区维护的非官方中文翻译,与 Cloudflare, Inc. 无关联。本站不提供账户登录,请勿在任何非官方站点输入您的 Cloudflare 账户信息。官方文档:developers.cloudflare.com

脱敏 JSON 字段

最后更新 查看 MarkdownAgent 设置

在将 JSON 请求发送到应用程序之前,将顶层 customerId 字段替换为 [REDACTED]。将此模板用于具有已知模式以及未压缩 UTF-8 JSON 正文的特定端点。

这是基于字段的转换。它不会在任意文本或文件中检测敏感数据,也不会使用数据丢失防护(DLP)匹配结果。

Gateway 在将原始请求体交给 Worker 之前对其进行检查。DLP Block 决定可以中断 Worker,因此此脚本不能使用脱敏来绕过请求侧 DLP 检查。

Gateway 在转发之前不会再次扫描修改后的请求。

配置 Worker

在 Worker 项目中,将 @cloudflare/zt-types ↗ 2026.10.1 或更高版本安装为开发依赖项:

npm i -D @cloudflare/zt-types

保留 src/index.ts 中 GatewayMiddlewareContext 和 GatewayMiddlewareNext 的 import type 声明。它们描述 Gateway 上下文和延续函数,并会在 TypeScript 编译时被移除。从 cloudflare:workers 导入运行时 WorkerEntrypoint 类。

将此脚本用作 Worker 项目中的 src/index.ts。它不需要绑定。

src/index.jsjs
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) }),
		);
	}
}
src/index.tsts
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 的端点。它替换一个顶层字段,不会脱敏嵌套字段、查询参数或其他标头。

这篇文档对您有帮助吗?