按类型开发指南

防护规则包(rule_pack)开发指南

规则包是数据,不是代码:用节点已有的 WAF / CC / ACL / 挑战规则模型写规则,主控发布前做冲突检测,节点由核心引擎解释执行。

← 全部开发指南

快速开始

  1. 在卖家站完成入驻,登记发布者公钥(插件签名工具页有步骤)。
  2. 卖家站“我的插件”新建插件,类型选 rule_pack,插件 ID 为“你的命名空间.名称”(如 acme.dev.sqli-extra)。
  3. 按下面的格式写规则包源文件 rulepack.src.json:pack_id 必须是 dev/<命名空间>/<名称>,layer 为 developer,version 与插件版本相同;再用“本地调试”里的编译器输出规范字节 rulepack.json。
  4. 把 rulepack.json 的 SHA-256 写进 manifest 的 spec.entrypoint.sha256,签名 manifest,上传版本。
{
  "schema": "cdnca.rule-pack/v1",
  "pack_id": "dev/acme.dev/sqli-extra",
  "version": "1.0.0",
  "layer": "developer",
  "engine": "waf_v2",
  "engine_min_version": 1,
  "name": "SQL 注入加强",
  "description": "参数 SQL 注入检测与后台路径拦截",
  "params_schema": {
    "action": {"type": "string", "enum": ["log", "block"], "bind": {"target": "waf.action", "rules": ["r010"]}}
  },
  "exports": ["r010", "r020"],
  "rules": [
    {"id": "r010", "name": "参数注入", "waf": {"match": {"inspect": [{"target": "args", "operator": "detect", "values": ["sqli"]}]}, "action": "block", "flow": "stop"}},
    {"id": "r020", "name": "后台路径", "waf": {"match": {"path": {"kind": "prefix", "value": "/wp-admin"}}, "action": "block", "flow": "stop"}}
  ],
  "defaults": {"action": "block"}
}

签名与公钥登记见插件签名工具页。 签名工具与步骤 →

manifest 示例与字段

{
  "api_version": "marketplace.cdnca.io/v1",
  "kind": "Plugin",
  "metadata": {
    "id": "acme.dev.sqli-extra",
    "version": "1.0.0",
    "name": {"zh-CN": "SQL 注入加强", "en-US": "SQLi extra"},
    "description": {"zh-CN": "参数注入检测与后台路径拦截"},
    "publisher": {"id": "acme.dev", "name": "Acme", "support_url": "https://acme.example/support"}
  },
  "spec": {
    "categories": ["security_rules"],
    "compatibility": {"control_api": ">=1.0.35 <2.0.0"},
    "entrypoint": {"type": "rule_pack", "path": "rulepack.json", "sha256": "<rulepack.json 的 SHA-256>"},
    "permissions": ["security.rule_packs.provide"],
    "configuration_schema": {"path": "schemas/config.schema.json", "sha256": "<配置 Schema 的 SHA-256>"},
    "supply_chain": {"sbom_sha256": "<SBOM 的 SHA-256>", "provenance_sha256": "<构建来源证明的 SHA-256>"}
  }
}
字段要求
spec.entrypoint.type固定 rule_pack;rule_pack 插件只能上传 rule_pack 版本
spec.entrypoint.path规则包文件在包内的相对路径,不能以 / 开头、不能含 ..,不能与配置 Schema 同名,不能是 manifest.json
spec.entrypoint.sha256规则包文件(上传的 content)的小写十六进制 SHA-256
spec.permissions恰好一个:security.rule_packs.provide
spec.provides不能出现
metadata.version纯 major.minor.patch,数据包不允许预发布或构建后缀
spec.categories1 个以上,取值 dns_provider、notification_provider、payment_provider、analytics_exporter、automation_connector、security_rules、theme、ui_page
spec.supply_chainsbom_sha256、provenance_sha256 两个 SHA-256;授权端同步目录时要求必须有
integrity由 cdnca-plugin sign-manifest 生成,不要手写

协议与接口

格式 cdnca.rule-pack/v1,字段严格(未知字段、重复键、尾随内容都拒绝),文件最大 1 MiB。

字段说明
enginewaf_v2、acl、cc_v2、challenge、modsecurity 之一(每个规则包只用一种引擎;modsecurity 规则是 CRS 规则集选择)
engine_min_version不低于规则实际需要的版本、不高于编译器支持的最高版本(waf_v2 / acl / modsecurity 为 1,cc_v2 / challenge 为 6)
rules[]1–256 条(cc_v2 / challenge 最多 128);id 匹配 ^[a-z0-9][a-z0-9._-]{0,63}$ 且不重复,按 id 顺序执行;每条规则恰好带本引擎的一个规则体(waf、cc 或 crs),waf / cc 字段是节点规则模型本身,rule_id、priority、host、port 由编译器按站点分配,包内留空
exports可以被站点单独开关的规则 id(最多 256)
params_schema最多 32 个参数,名称 ^[a-z][a-z0-9_]{0,31}$;type 为 integer / string / boolean,可带 minimum、maximum、enum;bind.target 指向规则字段,bind.rules 为空表示全部规则
defaults参数默认值

预算:单个规则包最多 256 条规则、64 个正则、4096 个代价单位(默认参数和每个参数边界值都要满足);一个站点所有挂载合计最多 1024 条、256 个正则、16384 单位。正则最长 512 字节,重复次数上限 256。

规则包只能匹配并拦截、挑战、限速或封禁,不能改写请求、回源或响应,也不能做任何计算。站点管理员在主控把规则包挂到站点,用户层例外先于开发者层规则执行。

打包与上传字段

表单字段必填内容与校验
manifest是签名后的 manifest,1 字节到 256 KiB;身份必须是本插件 ID、本命名空间,integrity.key_id 必须是该命名空间 active 的公钥并验签通过
configuration_schema是配置 JSON Schema 文件,1 字节到 64 KiB;SHA-256 必须等于 spec.configuration_schema.sha256
changelog_md否更新说明 Markdown,最多 65536 字节
channel否stable(默认)或 beta
auto_publish否true / false:审核通过后自动上架
content是规则包文件本身,1 字节到 8 MiB,SHA-256 等于 spec.entrypoint.sha256;必须是编译器的规范字节(见本地调试)
package:<platform>不能有rule_pack 版本不带可执行包

接口:POST /seller/v1/plugins/{id}/versions,Content-Type: multipart/form-data,必须带一个 1–128 字符的 Idempotency-Key 请求头;同一 key 重试返回同一结果。请求体默认上限 200 MiB。除上表与本类型专属字段外,出现任何其他字段名都返回 400 invalid_upload。

同一插件的版本号不能重复,且必须高于任何已审核过的版本(已通过、已上架、已隐藏或已下架)。上传成功后版本进入“预检中”,卖家站版本详情显示预检报告。

审核规则

上传后审核 worker 先跑“校验”阶段(manifest、入口、权限、本类型专属检查),出现任何拒绝项就不再扫描;再跑“扫描”阶段。结论:有拒绝项 → 需修改(修好后点“重新提交”,每个版本最多 10 次扫描);有隔离项 → 待人工审核;都没有 → 视下列条件直接通过或进入人工审核。

  • 首个版本(该插件还没有任何已审核版本)
  • 权限与上一个已审核版本不同(增加或减少)
  • 高风险类型:module、engine_module、provider
  • 入驻未满 90 天的卖家(市场默认值,运营可调)
  • 扫描连续 3 次出错

满足任一条即进入人工审核。完整规则表(规则 ID、级别、拒绝或隔离、是否试运行)见审核规则页。

  • rulepack.compile_failed / rulepack.budget_exceeded:编译失败或超预算(拒绝)。
  • rulepack.layer_invalid:pack_id 不是 dev/<你的命名空间>/<名称> 或 layer 不是 developer(拒绝)。
  • rulepack.version_mismatch:规则包 version 与插件版本不同(拒绝)。
  • rulepack.not_canonical:上传的不是规范字节(拒绝)。
  • rulepack.gate_failed:单独解析不通过发布质量门(拒绝)。
  • entrypoint.data_pack_permissions / entrypoint.data_pack_version / entrypoint.packages_present / entrypoint.content_mismatch:权限、版本号、多带包或文件摘要不符(拒绝)。

本类型相关的规则见审核规则清单: metadata.* entrypoint.* permissions.* rulepack.*

版本与兼容

metadata.version 是规范 SemVer(不带前导 v),每次上传必须比已审核版本高;同一版本号不能重复上传。

spec.compatibility.control_api 是 1–8 个以空格分隔的比较式(>=、<=、=、>、<,后接规范 SemVer),总长不超过 128;全部满足才算兼容。不满足的主控不能安装或升级到该版本,已安装的保持最近兼容版本。

"compatibility": {"control_api": ">=1.0.35 <2.0.0"}

新版本增加权限时,客户管理员在主控里要重新确认;权限变化的版本一律人工审核。

规则包 version 必须等于插件版本。节点按 engine_min_version 判断能否执行;需要 JA4、IP 情报、CRS 模块或 multipart 检测的规则,节点没有对应能力时主控发布前会提示。

本地调试

平台审核、主控和节点用的是同一个编译器 libs/rulepack。规范字节就是 Compile 后的 Canonical:把它写成 rulepack.json 再算 SHA-256。在仓库根目录这样编译你写的 rulepack.src.json,输出规范字节 rulepack.json:

mkdir -p libs/rulepack/cmd/rpc && cat > libs/rulepack/cmd/rpc/main.go <<'EOF'
package main

import (
	"fmt"
	"os"

	"github.com/cdnca/cdnca/libs/rulepack"
)

func main() {
	raw, _ := os.ReadFile(os.Args[1])
	c, err := rulepack.Compile(raw)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
	os.WriteFile(os.Args[2], c.Canonical, 0o644)
	fmt.Println(c.SHA256, c.Cost.Rules, c.Cost.Regexes, c.Cost.Units)
}
EOF
GOWORK=off go -C libs/rulepack run ./cmd/rpc "$PWD/rulepack.src.json" "$PWD/rulepack.json"

输出依次是规范字节的 SHA-256(填进 spec.entrypoint.sha256)与规则数、正则数、代价单位。编译报错时的信息与审核报告里 rulepack.compile_failed 的 message 相同。