@domandigital/synthetic

中文文档
0.2.2发布于 2026/11/02Apache-2.0TypeScriptESM + CJS零依赖构建来源已验证近 30 天下载 5140

多曼数字客户端表格的已签名的合成提交协议:签名和验证,再播放后卫,收件人锁定,Turnstile配置自查,信标和清洗收据. 只有网络加密 .

中文使用文档
文档目录10 节
  1. 安装
  2. 在窗体处理器中
  3. 旋转
  4. 签名(dd-checks)
  5. 线条格式
  6. 收件人锁定
  7. 旋转配置自查
  8. 灯塔和清理收据
  9. 让信头远离哨兵和日志
  10. 发展

@ doman数字/合成

多曼数字客户端表格的被签名的合成提交协议. 数字 检查( E)dd-checks)通过客户的真实形式提交真正的询问 调度器; Turnstile 屏蔽合成浏览器, 所以处理器 接受一个已签名的跳过请求 只有转动的符号。 。 。 .

网络加密, 没有依赖性: 它运行在 Cloudflare Workers, Node 22+ 和 dd- checks 中的浏览器边签名助手。 两次测试 节点和工人.

协议在DOM-487上的"DD Checks: plan"文档中指定, 页:1 这个包是作为代码的这个部分.

安装

pnpm add @domandigital/synthetic

在窗体处理器中

import {
  verifyRequest, keysFromEnv, turnstileConfigCheck, assertCanaryRecipient,
  syntheticReport, sendBeacon, upstashReplayGuard,
} from "@domandigital/synthetic";

export async function POST(request: Request) {
  const verified = await verifyRequest(request, {
    keys: keysFromEnv(env),                 // empty => synthetic path off
    host: "www.example.co.uk",              // the public host
    path: "/api/enquiry",
    contentTypes: ["application/json"],
    replayGuard: upstashReplayGuard({ url: env.UPSTASH_REDIS_REST_URL, token: env.UPSTASH_REDIS_REST_TOKEN }),
  });

  if (!verified.ok && verified.rejected) {
    // Handle as an ordinary request (Turnstile still applies) and tell dd-checks.
    ctx.waitUntil(sendBeacon(reporter, { form: "enquiry", code: "synthetic_rejected", reason: verified.reason }));
  }

  const synthetic = verified.ok ? verified.synthetic : null; // frozen; the only source of truth
  // 1. same-origin  2. schema validation
  // 3. synthetic ? turnstileConfigCheck({...}) : verify the Turnstile token
  // 4. the real rate limiter, identifier `synthetic:<form>` when synthetic
  // 5. full mode only: DB write (record_origin='synthetic', synthetic_run_id, purge_after),
  //    staff email redirected to canaryAddress("s", runId); submitter email to
  //    canaryAddress("u", runId) on the daily run only. assertCanaryRecipient() first.
  // 6. skip SMS, CRM, marketing lists, trackLead; report them as "skipped".
  // 7. respond with { ..., synthetic: syntheticReport(synthetic, stages) } only when synthetic
}

规则代码为您执行:

  • 默认关闭. 无 DD_SYNTHETIC_SECRET (和 DD_SYNTHETIC_KID每个请求都是普通的.
  • 签名前的形状 . 方法,主机,路径和内容类型在机构被散列前进行检查.
  • 被拒绝的请求是普通请求从来没有失败. verify 从来没有扔过.
  • 需要回放守卫 并在签名后运行,所以一个被伪造的请求不能燃烧一个真实运行的id.
  • 文体和查询从不读取 页:1 synthetic编辑 SyntheticContext 仅由已核实信头构建并被冻结.

重放守护选项 : upstashReplayGuard (单位:千美元)SET synth:<runId> NX EX 300),或 您自己的功能相对于一个独特的制约 synthetic_run_id数字 :

replayGuard: async (runId) => {
  try { await db.insert(syntheticRuns).values({ syntheticRunId: runId }); return true; }
  catch (e) { if (isUniqueViolation(e)) return false; throw e; }
}

旋转

一个网站最多持有两把钥匙: DD_SYNTHETIC_SECRET + 键 DD_SYNTHETIC_KID,以及 DD_SYNTHETIC_SECRET_NEXT + 键 DD_SYNTHETIC_KID_NEXT设置新密钥为 _NEXT 和一个新孩子, 切换平台,推广它, 然后删除 旧的 一个身份不明的孩子被拒绝了 秘诀为32个随机字节,基数64url:

node -e "console.log(require('node:crypto').randomBytes(32).toString('base64url'))"

签名(dd-checks)

const { headers, runId } = await sign({
  key: { kid: "example-2026-10", secret },
  url: "https://www.example.co.uk/api/enquiry",
  contentType: "application/json",
  body: rawBodyString,
  client: "example", form: "enquiry", mode: "probe",
});

runId 16 个字符 [a-z2-7]生成新鲜的,每一次来自 CSPRNG(英语:CSPRNG). 重放非曲直 然后进入金丝雀地址 所以每跑一次 是独一无二的 客户端的无一能键不能去除一个.

线条格式

标题 : X-DD-Synth: v1, (中文(简体) ), X-DD-Synth-Kid, (中文(简体) ), -Ts (unix 秒),(unix seconds),(英语:, -Run, (中文(简体) ), -Mode (单位:千美元)probe 或者说 full, (中文(简体) ), -Client, (中文(简体) ), -Form, (中文(简体) ), -Sig: v1=<hex>。 。 。 .

签名是HMAC-SHA256 上面有32字节的秘密:

DD-SYNTH-V1\n<kid>\n<ts>\n<runId>\n<mode>\n<client>\n<form>\nPOST\n<host>\n<path>\n<content-type>\n<hex sha256(raw body)>

host 小写,并仅在 URL 有端口时包含端口. path 已经 查询字符串已删除. content-type 信头与发送的完全相同(a) 多段边界是它的一部分). 窗口:±120 s. 比较:恒定时间.

测试向量 发表于 test-vectors.json (单位:千美元)@domandigital/synthetic/test-vectors.json- 一个测试秘密,每个案例 输入、词条、身体散列和预期签名。 他们 以 node:crypto 以 scripts/gen-vectors.mjs,独立于 这个包裹的代码。 任何其他执行( Python 检查器、 Go 服务) 必须复制它们.

收件人锁定

assertCanaryRecipient(submitterEmail);   // throws SyntheticRecipientError

仅 @canary.domandigital.co.uk 通过,作为一个简单的地址。 显示名称, 列表、子域和外观类似域都失败。 在一切发生之前就叫它 发送.

旋转配置自查

turnstileConfigCheck({ siteKey, secretKey, expectedHostname, publicHost }) 在经核实的合成请求中替换符号。 它回来了 { ok, outcome },与 outcome 其中一个:

结果含义
ok存在密钥, 主机名右侧, 被网站验证接受的秘密
missing_keys键或 TURNSTILE_EXPECTED_HOSTNAME 未设置
hostname_mismatchTURNSTILE_EXPECTED_HOSTNAME已正常化的不是公共主机(在客户端网站被忽略了几个星期的失败)
test_key_on_productiona 生产主机上的 Cloudflare 测试键
invalid-input-secret校对:soup
siteverify_unexpected网站验证回答其他问题(例如 success 用于生产时的假币)
siteverify_unreachable网络出错、 超时或非 JSON ; 不描述配置

Hostnames 比较小写与方案,端口,路径和后继点已删除; www. 因为它是一个不同的宿主。 显示主机接受测试 键,因为运行浏览器的行程使用 Cloudflare 总是通过键.

灯塔和清理收据

const reporter = { key, client: "example", waitUntil: ctx.waitUntil.bind(ctx) };
sendBeacon(reporter, { form: "enquiry", code: "accepted" });
sendPurgeReceipt(reporter, { form: "enquiry", deleted: 24, remaining: 0 });

两者均用网站的钥匙签名(-Mode: beacon 编号 : receipt),员额改为 https://checks.domandigital.co.uk/beacon 财务报告和审定财务报表 /purge-receipt,为 火和遗忘,和 永不抛出或拒绝:一个糟糕的争论被放弃了. A 类 信标只持有代码(accepted, (中文(简体) ), hostname_mismatch, (中文(简体) ), invalid-input-secret, (中文(简体) ), misconfigured, (中文(简体) ), service_unavailable, (中文(简体) ), delivery_failed, (中文(简体) ), captcha_failed, (中文(简体) ), synthetic_rejected 加一个 reason, (中文(简体) ), 也从来不是个人资料.

让信头远离哨兵和日志

Sentry.init({ beforeSend: scrubSentryEvent, beforeSendTransaction: scrubSentryEvent });
logger.info(scrubText(headerDump));
const safe = scrubHeaders(request.headers);

发展

pnpm --filter @domandigital/synthetic test    # Node, then workerd

版本与发布记录

@domandigital/synthetic 共发布 5 个版本,首次发布于 2026/10/08;过去 12 个月发布了 5 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.2.2latest2026/11/0211 天前
0.2.12026/10/1925 天前
0.2.02026/10/101 个月前
0.1.02026/10/081 个月前
0.0.0-stage2026/10/081 个月前

关于 @domandigital/synthetic 的常见问题

@domandigital/synthetic 是什么?

多曼数字客户端表格的已签名的合成提交协议:签名和验证,再播放后卫,收件人锁定,Turnstile配置自查,信标和清洗收据. 只有网络加密 .

如何安装 @domandigital/synthetic?

在项目目录运行 npm install @domandigital/synthetic,或使用 pnpm add / yarn add / bun add。

@domandigital/synthetic 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@domandigital/synthetic 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@domandigital/synthetic 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22.12。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@domandigital/synthetic 可以商用吗?

@domandigital/synthetic 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@domandigital/synthetic 的下载量有多少?

根据 npm 官方下载统计,@domandigital/synthetic 在 2026/10/13 – 2026/11/11 这 30 天内被下载 5,140 次,日均约 171 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@domandigital/synthetic 还在维护吗?

最新版本 0.2.2 发布于 2026/11/02(11 天前);至今共发布 5 个版本,其中过去 12 个月发布了 5 个;首次发布于 2026/10/08。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。