diff --git a/scripts/shots/core/en/security.json b/scripts/shots/core/en/security.json index 70af8a0b..70eccd25 100644 --- a/scripts/shots/core/en/security.json +++ b/scripts/shots/core/en/security.json @@ -1,5 +1,32 @@ { "content": { + "kinds": [ + { + "name": "Hidden characters", + "name_zh": "隐藏字符", + "slug": "invisible" + }, + { + "name": "Instruction override", + "name_zh": "指令覆盖", + "slug": "injection" + }, + { + "name": "Identity and prompts", + "name_zh": "身份与提示词", + "slug": "persona" + }, + { + "name": "Chinese phrasing", + "name_zh": "中文说法", + "slug": "chinese" + }, + { + "name": "Custom", + "name_zh": "自定义", + "slug": "custom" + } + ], "mode": "observe", "rules": [ { @@ -16,12 +43,14 @@ ] }, "name": "Unicode tag characters", + "name_zh": "Unicode 标签字符", "on_by_default": true, "replacement": "***", "scope": [ "request" ], - "why": "Entirely invisible in an editor, yet carried into the model's context as they are, so they can hide a whole instruction." + "why": "Entirely invisible in an editor, yet carried into the model's context as they are, so they can hide a whole instruction.", + "why_zh": "在编辑器中完全不可见,但会原样进入模型上下文,可用于隐藏整段指令。" }, { "action": "strip", @@ -38,12 +67,14 @@ ] }, "name": "Bidirectional controls", + "name_zh": "双向控制符", "on_by_default": true, "replacement": "***", "scope": [ "request" ], - "why": "They can make the order shown on screen differ from the actual order of the characters." + "why": "They can make the order shown on screen differ from the actual order of the characters.", + "why_zh": "可使屏幕上的显示顺序与实际字符顺序不一致。" }, { "action": "strip", @@ -61,12 +92,14 @@ ] }, "name": "Zero-width characters", + "name_zh": "零宽字符", "on_by_default": false, "replacement": "***", "scope": [ "request" ], - "why": "Invisible in an editor, and read by the model. Emoji, Persian and other ordinary writing use them too." + "why": "Invisible in an editor, and read by the model. Emoji, Persian and other ordinary writing use them too.", + "why_zh": "在编辑器中不可见,但会被模型读到。表情符号、波斯文等正常文字也会用到。" }, { "action": "strip", @@ -84,12 +117,14 @@ ] }, "name": "Private-use characters", + "name_zh": "私用区字符", "on_by_default": false, "replacement": "***", "scope": [ "request" ], - "why": "They have no standard meaning. Some icon fonts use them." + "why": "They have no standard meaning. Some icon fonts use them.", + "why_zh": "没有标准含义。部分图标字体会用到。" }, { "action": "block", @@ -103,6 +138,7 @@ "text": "ignore previous instructions" }, "name": "Ignore previous instructions", + "name_zh": "要求忽略先前的指令", "on_by_default": true, "replacement": "***", "scope": [ @@ -121,6 +157,7 @@ "text": "ignore all previous" }, "name": "Ignore all previous", + "name_zh": "要求忽略之前的全部内容", "on_by_default": true, "replacement": "***", "scope": [ @@ -139,6 +176,7 @@ "text": "disregard your instructions" }, "name": "Disregard your instructions", + "name_zh": "要求无视指令", "on_by_default": true, "replacement": "***", "scope": [ @@ -157,6 +195,7 @@ "text": "jailbreak" }, "name": "Jailbreak", + "name_zh": "越狱", "on_by_default": false, "replacement": "***", "scope": [ @@ -175,6 +214,7 @@ "text": " dan " }, "name": "DAN", + "name_zh": "DAN", "on_by_default": false, "replacement": "***", "scope": [ @@ -193,6 +233,7 @@ "text": "developer mode" }, "name": "Developer mode", + "name_zh": "开发者模式", "on_by_default": false, "replacement": "***", "scope": [ @@ -211,6 +252,7 @@ "text": "you are now" }, "name": "Persona manipulation", + "name_zh": "重新设定身份", "on_by_default": false, "replacement": "***", "scope": [ @@ -229,6 +271,7 @@ "text": "new persona" }, "name": "New persona", + "name_zh": "新的人设", "on_by_default": false, "replacement": "***", "scope": [ @@ -247,6 +290,7 @@ "text": "act as" }, "name": "Act as", + "name_zh": "要求扮演角色", "on_by_default": false, "replacement": "***", "scope": [ @@ -265,6 +309,7 @@ "text": "pretend to be" }, "name": "Pretend to be", + "name_zh": "要求假装身份", "on_by_default": false, "replacement": "***", "scope": [ @@ -283,6 +328,7 @@ "text": "system prompt" }, "name": "System prompt extraction", + "name_zh": "索取系统提示词", "on_by_default": false, "replacement": "***", "scope": [ @@ -301,6 +347,7 @@ "text": "reveal your instructions" }, "name": "Reveal instructions", + "name_zh": "要求透露指令", "on_by_default": false, "replacement": "***", "scope": [ @@ -319,6 +366,7 @@ "text": "what are your rules" }, "name": "What are your rules", + "name_zh": "询问规则", "on_by_default": false, "replacement": "***", "scope": [ @@ -337,6 +385,7 @@ "pattern": "[A-Za-z0-9+/=]{50,}" }, "name": "Base64 smuggling", + "name_zh": "长串 Base64", "on_by_default": false, "replacement": "***", "scope": [ @@ -355,6 +404,7 @@ "text": "忽略之前" }, "name": "Ignore previous instructions (Chinese)", + "name_zh": "要求忽略先前的指令(中文)", "on_by_default": false, "replacement": "***", "scope": [ @@ -373,6 +423,7 @@ "text": "忘记你" }, "name": "Forget your instructions (Chinese)", + "name_zh": "要求忘记指令(中文)", "on_by_default": false, "replacement": "***", "scope": [ @@ -391,6 +442,7 @@ "text": "不要遵循" }, "name": "Do not follow (Chinese)", + "name_zh": "要求不要遵循(中文)", "on_by_default": false, "replacement": "***", "scope": [ @@ -409,6 +461,7 @@ "text": "现在你是" }, "name": "You are now (Chinese)", + "name_zh": "重新设定身份(中文)", "on_by_default": false, "replacement": "***", "scope": [ @@ -427,6 +480,7 @@ "text": "扮演" }, "name": "Role-play (Chinese)", + "name_zh": "要求扮演角色(中文)", "on_by_default": false, "replacement": "***", "scope": [ @@ -445,6 +499,7 @@ "text": "透露你的" }, "name": "Reveal your instructions (Chinese)", + "name_zh": "要求透露指令(中文)", "on_by_default": false, "replacement": "***", "scope": [ @@ -463,6 +518,7 @@ "text": "系统提示词" }, "name": "System prompt (Chinese)", + "name_zh": "系统提示词(中文)", "on_by_default": false, "replacement": "***", "scope": [ @@ -481,6 +537,7 @@ "text": "越狱" }, "name": "Jailbreak (Chinese)", + "name_zh": "越狱(中文)", "on_by_default": false, "replacement": "***", "scope": [ @@ -490,6 +547,18 @@ ] }, "inspect_tools": { + "kinds": [ + { + "name": "Built-in", + "name_zh": "内置", + "slug": "command" + }, + { + "name": "Custom", + "name_zh": "自定义", + "slug": "custom" + } + ], "mode": "enforce", "rules": [ { @@ -504,8 +573,10 @@ "pattern": "(?i)(curl|wget)[^\\n|]*\\|\\s*(sudo\\s+)?(ba|z|d)?sh" }, "name": "Download and run", + "name_zh": "下载即执行", "on_by_default": true, - "why": "Downloads and runs it straight away; what runs is decided remotely and cannot be read first" + "why": "Downloads and runs it straight away; what runs is decided remotely and cannot be read first", + "why_zh": "下载后直接执行,执行的内容由远端决定且无法预先查看" }, { "action": "cut", @@ -519,8 +590,10 @@ "pattern": "(?i)base64\\s+(-d|--decode)[^\\n|]*\\|\\s*(ba)?sh" }, "name": "Decode and run", + "name_zh": "解码后执行", "on_by_default": true, - "why": "Hides what will run inside base64" + "why": "Hides what will run inside base64", + "why_zh": "将要执行的内容隐藏在 base64 编码中" }, { "action": "cut", @@ -534,8 +607,10 @@ "pattern": "(?i)(env|printenv|cat\\s+[^\\n]{0,60}\\.env)[^\\n]{0,200}(curl|wget|nc\\s)" }, "name": "Send out environment variables", + "name_zh": "外发环境变量", "on_by_default": true, - "why": "Sends the environment, which usually holds keys, somewhere else" + "why": "Sends the environment, which usually holds keys, somewhere else", + "why_zh": "将环境变量(通常包含密钥)发送到外部" }, { "action": "cut", @@ -550,8 +625,10 @@ "pattern": "(\\.ssh/id_|\\.aws/credentials|\\.netrc|/\\.env)[^\\n]{0,30}(发|传|上报|打印|输出|贴出|读出|(?i:send|upload|post|print|paste|exfil))" }, "name": "Send out a credential file", + "name_zh": "外发凭据文件", "on_by_default": true, - "why": "Asks the model to send the contents of a credential file somewhere" + "why": "Asks the model to send the contents of a credential file somewhere", + "why_zh": "要求模型将凭据文件的内容发送出去" }, { "action": "cut", @@ -566,8 +643,10 @@ "pattern": "(发送|上传|贴出|打印|输出|读取|(?i:send|upload|post|print|read))[^\\n]{0,30}(\\.ssh/id_|\\.aws/credentials|\\.netrc|/\\.env)" }, "name": "Send out a credential file (verb first)", + "name_zh": "外发凭据文件(动词在前)", "on_by_default": true, - "why": "Asks the model to send the contents of a credential file somewhere" + "why": "Asks the model to send the contents of a credential file somewhere", + "why_zh": "要求模型将凭据文件的内容发送出去" }, { "action": "cut", @@ -581,8 +660,10 @@ "pattern": "(?i)(cat|cp|scp|curl)[^\\n]{0,200}(\\.ssh/id_|\\.aws/credentials|\\.netrc)" }, "name": "Read a private key or cloud credential", + "name_zh": "读取私钥或云凭据", "on_by_default": true, - "why": "Reads a private key or a cloud credential" + "why": "Reads a private key or a cloud credential", + "why_zh": "读取私钥或云服务凭据" }, { "action": "cut", @@ -596,8 +677,10 @@ "kind": "builtin" }, "name": "Send a credential to an unknown host", + "name_zh": "凭据发往陌生主机", "on_by_default": true, - "why": "Sends a credential to a host that is neither local nor the credential's own provider" + "why": "Sends a credential to a host that is neither local nor the credential's own provider", + "why_zh": "将凭据发往既非本机、也不是该凭据服务商的主机" }, { "action": "cut", @@ -611,8 +694,10 @@ "kind": "builtin" }, "name": "Read or change ThinkWatch's own data", + "name_zh": "读写 ThinkWatch 自己的数据", "on_by_default": true, - "why": "Reads or changes ThinkWatch's data directory, which holds every upstream key in plain text and the settings of these protections" + "why": "Reads or changes ThinkWatch's data directory, which holds every upstream key in plain text and the settings of these protections", + "why_zh": "读写 ThinkWatch 的数据目录,其中以明文保存全部上游密钥和这几项防护的设置" }, { "action": "cut", @@ -627,8 +712,10 @@ "pattern": "(>>?|tee|cp|mv|ln\\s+-s)[^\\n]{0,200}(\\.zshrc|\\.bashrc|\\.bash_profile|\\.profile|\\.zprofile|LaunchAgents|LaunchDaemons|\\.git/hooks|crontab)" }, "name": "Write a startup item", + "name_zh": "写入启动项", "on_by_default": true, - "why": "Writes somewhere that runs at login or whenever a terminal opens" + "why": "Writes somewhere that runs at login or whenever a terminal opens", + "why_zh": "写入开机或打开终端时自动执行的位置" }, { "action": "cut", @@ -643,8 +730,10 @@ "pattern": "crontab\\s+(-r\\b|-\\s*($|[\"';&|)])|[^\\s-][^\\n]{0,40})" }, "name": "Install a scheduled job", + "name_zh": "安装定时任务", "on_by_default": true, - "why": "Installs a scheduled job, or deletes every existing one" + "why": "Installs a scheduled job, or deletes every existing one", + "why_zh": "安装定时任务,或删除全部现有定时任务" }, { "action": "record", @@ -659,8 +748,10 @@ "pattern": "rm\\s+(-[a-zA-Z]*\\s+)*-[a-zA-Z]*[rf][a-zA-Z]*\\s+(/|~|\\$HOME)(\\s|$|[\"';&|)])" }, "name": "Delete home or root", + "name_zh": "删除主目录或根目录", "on_by_default": true, - "why": "Deletes the whole home directory or the root" + "why": "Deletes the whole home directory or the root", + "why_zh": "删除整个主目录或根目录" }, { "action": "record", @@ -675,8 +766,10 @@ "pattern": "chmod\\s+(-R\\s+)?777" }, "name": "World-writable permissions", + "name_zh": "开放全部写权限", "on_by_default": true, - "why": "Makes a file writable by everyone" + "why": "Makes a file writable by everyone", + "why_zh": "将文件权限设为所有人可写" }, { "action": "record", @@ -690,21 +783,144 @@ "kind": "builtin" }, "name": "Upload a local file to an external host", + "name_zh": "上传本地文件到外部主机", "on_by_default": true, - "why": "Uploads the contents of a local file to an external host" + "why": "Uploads the contents of a local file to an external host", + "why_zh": "将本地文件的内容上传到外部主机" } ] }, "redact": { + "kinds": [ + { + "name": "Saved credentials", + "name_zh": "保存的密钥", + "slug": "saved" + }, + { + "name": "AI services", + "name_zh": "AI 服务", + "slug": "ai-services" + }, + { + "name": "Code hosting and publishing", + "name_zh": "代码托管与软件发布", + "slug": "dev-platforms" + }, + { + "name": "Cloud and infrastructure", + "name_zh": "云服务与基础设施", + "slug": "cloud" + }, + { + "name": "Payments, messaging and collaboration", + "name_zh": "支付、通信与协作", + "slug": "business" + }, + { + "name": "Private keys", + "name_zh": "私钥", + "slug": "private-keys" + }, + { + "name": "JWTs", + "name_zh": "JWT", + "slug": "jwt" + }, + { + "name": "Connection strings", + "name_zh": "连接串", + "slug": "conn-strings" + }, + { + "name": "Credentials by position", + "name_zh": "固定位置的凭据", + "slug": "in-context" + }, + { + "name": "Cryptocurrency", + "name_zh": "加密货币", + "slug": "crypto" + }, + { + "name": "ID documents", + "name_zh": "证件号码", + "slug": "id-documents" + }, + { + "name": "Foreign ID numbers", + "name_zh": "境外证件号码", + "slug": "foreign-ids" + }, + { + "name": "Financial accounts", + "name_zh": "金融账户", + "slug": "financial" + }, + { + "name": "Contact details", + "name_zh": "联系方式", + "slug": "contact" + }, + { + "name": "Addresses and locations", + "name_zh": "住址与位置", + "slug": "location" + }, + { + "name": "Vehicles", + "name_zh": "车辆", + "slug": "vehicle" + }, + { + "name": "Device and network identifiers", + "name_zh": "设备与网络标识", + "slug": "device" + }, + { + "name": "Personal information by field name", + "name_zh": "带字段名的个人信息", + "slug": "labeled-personal" + }, + { + "name": "Internal addresses", + "name_zh": "内网地址", + "slug": "internal" + }, + { + "name": "Custom", + "name_zh": "自定义", + "slug": "custom" + } + ], "mode": "enforce", "rules": [ { + "about": "Recognized by value, whatever the format: credentials saved in ThinkWatch, namely upstream keys and header values, gateway keys, proxy passwords, the remote control key, and sync and backup credentials. Values shorter than 12 characters are not matched", + "about_zh": "按值识别 ThinkWatch 中保存的密钥,与格式无关:上游密钥与请求头中的值、网关密钥、代理密码、远程控制密钥、同步与备份凭据。不足 12 个字符的值不识别", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "thinkwatch-saved-secret", + "kind": "saved", + "label": "SECRET", + "matcher": { + "kind": "saved" + }, + "name": "Credentials saved in ThinkWatch", + "name_zh": "ThinkWatch 中保存的密钥", + "on_by_default": true + }, + { + "about": "Starts with `sk-ant-`, followed by at least 20 characters", + "about_zh": "`sk-ant-` 开头,其后至少 20 个字符", "action": "placeholder", "custom": false, "default_action": "placeholder", "enabled": true, "id": "anthropic-api-key", - "kind": "api-keys", + "kind": "ai-services", "label": "SECRET", "matcher": { "kind": "prefix", @@ -712,15 +928,18 @@ "prefix": "sk-ant-" }, "name": "Anthropic API key", + "name_zh": "Anthropic API 密钥", "on_by_default": true }, { + "about": "Starts with `sk-proj-`, followed by at least 20 characters", + "about_zh": "`sk-proj-` 开头,其后至少 20 个字符", "action": "placeholder", "custom": false, "default_action": "placeholder", "enabled": true, "id": "openai-project-key", - "kind": "api-keys", + "kind": "ai-services", "label": "SECRET", "matcher": { "kind": "prefix", @@ -728,383 +947,2217 @@ "prefix": "sk-proj-" }, "name": "OpenAI project key", + "name_zh": "OpenAI 项目密钥", "on_by_default": true }, { + "about": "Starts with `sk-`, at least 40 characters, with both letters and digits", + "about_zh": "`sk-` 开头,全长至少 40 个字符,同时含字母和数字", "action": "placeholder", "custom": false, "default_action": "placeholder", "enabled": true, "id": "openai-api-key", - "kind": "api-keys", + "kind": "ai-services", "label": "SECRET", "matcher": { "kind": "openai-legacy", "min_len": 40 }, "name": "OpenAI API key", + "name_zh": "OpenAI API 密钥", "on_by_default": true }, { + "about": "Starts with `sk-svcacct-` or `sk-admin-`, followed by at least 58 characters", + "about_zh": "`sk-svcacct-`、`sk-admin-` 开头,其后至少 58 个字符", "action": "placeholder", "custom": false, "default_action": "placeholder", "enabled": true, - "id": "github-personal-token", - "kind": "api-keys", + "id": "openai-service-key", + "kind": "ai-services", "label": "SECRET", "matcher": { - "kind": "prefix", - "min_tail": 30, - "prefix": "ghp_" + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 58, + "prefix": "sk-svcacct-", + "tail": "any" + }, + { + "min_tail": 58, + "prefix": "sk-admin-", + "tail": "any" + } + ] }, - "name": "GitHub personal access token", + "name": "OpenAI service account or admin key", + "name_zh": "OpenAI 服务账号与管理密钥", "on_by_default": true }, { + "about": "Starts with `sk-or-v1-`, followed by at least 64 hex characters", + "about_zh": "`sk-or-v1-` 开头,其后至少 64 个十六进制字符", "action": "placeholder", "custom": false, "default_action": "placeholder", "enabled": true, - "id": "github-oauth-token", - "kind": "api-keys", + "id": "openrouter-key", + "kind": "ai-services", "label": "SECRET", "matcher": { - "kind": "prefix", - "min_tail": 30, - "prefix": "gho_" + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 64, + "prefix": "sk-or-v1-", + "tail": "hex" + } + ] }, - "name": "GitHub OAuth token", + "name": "OpenRouter API key", + "name_zh": "OpenRouter API 密钥", "on_by_default": true }, { + "about": "Starts with `xai-`, followed by at least 70 letters or digits", + "about_zh": "`xai-` 开头,其后至少 70 个字母或数字", "action": "placeholder", "custom": false, "default_action": "placeholder", "enabled": true, - "id": "github-server-token", - "kind": "api-keys", + "id": "xai-key", + "kind": "ai-services", "label": "SECRET", "matcher": { - "kind": "prefix", - "min_tail": 30, - "prefix": "ghs_" + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 70, + "prefix": "xai-", + "tail": "alnum" + } + ] }, - "name": "GitHub server token", + "name": "xAI API key", + "name_zh": "xAI API 密钥", "on_by_default": true }, { + "about": "Starts with `gsk_`, followed by at least 52 letters or digits", + "about_zh": "`gsk_` 开头,其后至少 52 个字母或数字", "action": "placeholder", "custom": false, "default_action": "placeholder", "enabled": true, - "id": "github-user-token", - "kind": "api-keys", + "id": "groq-key", + "kind": "ai-services", "label": "SECRET", "matcher": { - "kind": "prefix", - "min_tail": 30, - "prefix": "ghu_" + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 52, + "prefix": "gsk_", + "tail": "alnum" + } + ] }, - "name": "GitHub user token", + "name": "Groq API key", + "name_zh": "Groq API 密钥", "on_by_default": true }, { + "about": "Starts with `hf_` or `api_org_`, followed by at least 34 letters or digits", + "about_zh": "`hf_`、`api_org_` 开头,其后至少 34 个字母或数字", "action": "placeholder", "custom": false, "default_action": "placeholder", "enabled": true, - "id": "github-fine-grained-token", - "kind": "api-keys", + "id": "huggingface-token", + "kind": "ai-services", "label": "SECRET", "matcher": { - "kind": "prefix", - "min_tail": 30, - "prefix": "github_pat_" + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 34, + "prefix": "hf_", + "tail": "alnum" + }, + { + "min_tail": 34, + "prefix": "api_org_", + "tail": "alnum" + } + ] }, - "name": "GitHub fine-grained token", + "name": "Hugging Face token", + "name_zh": "Hugging Face 令牌", "on_by_default": true }, { + "about": "Starts with `r8_`, followed by at least 37 letters or digits", + "about_zh": "`r8_` 开头,其后至少 37 个字母或数字", "action": "placeholder", "custom": false, "default_action": "placeholder", "enabled": true, - "id": "slack-bot-token", - "kind": "api-keys", + "id": "replicate-token", + "kind": "ai-services", "label": "SECRET", "matcher": { - "kind": "prefix", - "min_tail": 20, - "prefix": "xoxb-" + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 37, + "prefix": "r8_", + "tail": "alnum" + } + ] }, - "name": "Slack bot token", + "name": "Replicate token", + "name_zh": "Replicate 令牌", "on_by_default": true }, { + "about": "Starts with `pplx-`, followed by at least 48 letters or digits", + "about_zh": "`pplx-` 开头,其后至少 48 个字母或数字", "action": "placeholder", "custom": false, "default_action": "placeholder", "enabled": true, - "id": "slack-user-token", - "kind": "api-keys", + "id": "perplexity-key", + "kind": "ai-services", "label": "SECRET", "matcher": { - "kind": "prefix", - "min_tail": 20, - "prefix": "xoxp-" + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 48, + "prefix": "pplx-", + "tail": "alnum" + } + ] }, - "name": "Slack user token", + "name": "Perplexity API key", + "name_zh": "Perplexity API 密钥", "on_by_default": true }, { + "about": "Starts with `ABSK`, followed by at least 100 characters", + "about_zh": "`ABSK` 开头,其后至少 100 个字符", "action": "placeholder", "custom": false, "default_action": "placeholder", "enabled": true, - "id": "slack-app-token", - "kind": "api-keys", + "id": "bedrock-api-key", + "kind": "ai-services", "label": "SECRET", "matcher": { - "kind": "prefix", - "min_tail": 20, - "prefix": "xoxa-" + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 100, + "prefix": "ABSK", + "tail": "any" + } + ] }, - "name": "Slack app token", + "name": "Amazon Bedrock API key", + "name_zh": "Amazon Bedrock API 密钥", "on_by_default": true }, { + "about": "Starts with `fw_`, followed by at least 20 letters or digits", + "about_zh": "`fw_` 开头,其后至少 20 个字母或数字", "action": "placeholder", "custom": false, "default_action": "placeholder", "enabled": true, - "id": "aws-access-key-id", - "kind": "api-keys", + "id": "fireworks-key", + "kind": "ai-services", "label": "SECRET", "matcher": { - "kind": "prefix", - "min_tail": 12, - "prefix": "AKIA" + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 20, + "prefix": "fw_", + "tail": "alnum" + } + ] }, - "name": "AWS access key ID", + "name": "Fireworks AI API key", + "name_zh": "Fireworks AI API 密钥", "on_by_default": true }, { + "about": "Starts with `csk-`, followed by at least 48 letters or digits", + "about_zh": "`csk-` 开头,其后至少 48 个字母或数字", "action": "placeholder", "custom": false, "default_action": "placeholder", "enabled": true, - "id": "aws-temporary-key-id", - "kind": "api-keys", + "id": "cerebras-key", + "kind": "ai-services", "label": "SECRET", "matcher": { - "kind": "prefix", - "min_tail": 12, - "prefix": "ASIA" + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 48, + "prefix": "csk-", + "tail": "alnum" + } + ] }, - "name": "AWS temporary access key ID", + "name": "Cerebras API key", + "name_zh": "Cerebras API 密钥", "on_by_default": true }, { + "about": "Starts with `lsv2_pt_` or `lsv2_sk_`, followed by at least 43 characters", + "about_zh": "`lsv2_pt_`、`lsv2_sk_` 开头,其后至少 43 个字符", "action": "placeholder", "custom": false, "default_action": "placeholder", "enabled": true, - "id": "google-api-key", - "kind": "api-keys", + "id": "langsmith-key", + "kind": "ai-services", "label": "SECRET", "matcher": { - "kind": "prefix", - "min_tail": 30, - "prefix": "AIza" + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 43, + "prefix": "lsv2_pt_", + "tail": "any" + }, + { + "min_tail": 43, + "prefix": "lsv2_sk_", + "tail": "any" + } + ] }, - "name": "Google API key", + "name": "LangSmith API key", + "name_zh": "LangSmith API 密钥", "on_by_default": true }, { + "about": "Starts with `sk-lf-`, followed by at least 36 characters", + "about_zh": "`sk-lf-` 开头,其后至少 36 个字符", "action": "placeholder", "custom": false, "default_action": "placeholder", "enabled": true, - "id": "google-oauth-token", - "kind": "api-keys", + "id": "langfuse-secret-key", + "kind": "ai-services", "label": "SECRET", "matcher": { - "kind": "prefix", - "min_tail": 20, - "prefix": "ya29." + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 36, + "prefix": "sk-lf-", + "tail": "any" + } + ] }, - "name": "Google OAuth token", + "name": "Langfuse secret key", + "name_zh": "Langfuse 私密密钥", "on_by_default": true }, { + "about": "Starts with `pcsk_`, followed by at least 69 characters", + "about_zh": "`pcsk_` 开头,其后至少 69 个字符", "action": "placeholder", "custom": false, "default_action": "placeholder", "enabled": true, - "id": "gitlab-token", - "kind": "api-keys", + "id": "pinecone-key", + "kind": "ai-services", "label": "SECRET", "matcher": { - "kind": "prefix", - "min_tail": 15, - "prefix": "glpat-" + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 69, + "prefix": "pcsk_", + "tail": "any" + } + ] }, - "name": "GitLab token", + "name": "Pinecone API key", + "name_zh": "Pinecone API 密钥", "on_by_default": true }, { + "about": "Starts with `tvly-`, followed by at least 32 characters", + "about_zh": "`tvly-` 开头,其后至少 32 个字符", "action": "placeholder", "custom": false, "default_action": "placeholder", "enabled": true, - "id": "stripe-live-key", - "kind": "api-keys", + "id": "tavily-key", + "kind": "ai-services", "label": "SECRET", "matcher": { - "kind": "prefix", - "min_tail": 20, - "prefix": "sk_live_" + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 32, + "prefix": "tvly-", + "tail": "any" + } + ] }, - "name": "Stripe live key", + "name": "Tavily API key", + "name_zh": "Tavily API 密钥", "on_by_default": true }, { + "about": "Starts with `sk-api-`, followed by at least 100 characters", + "about_zh": "`sk-api-` 开头,其后至少 100 个字符", "action": "placeholder", "custom": false, "default_action": "placeholder", "enabled": true, - "id": "stripe-restricted-key", - "kind": "api-keys", + "id": "minimax-key", + "kind": "ai-services", "label": "SECRET", "matcher": { - "kind": "prefix", - "min_tail": 20, - "prefix": "rk_live_" + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 100, + "prefix": "sk-api-", + "tail": "any" + } + ] }, - "name": "Stripe restricted key", + "name": "MiniMax API key", + "name_zh": "MiniMax API 密钥", "on_by_default": true }, { + "about": "32 hex characters, `.` and 16 letters or digits, run together", + "about_zh": "32 个十六进制字符、`.` 与 16 个字母或数字相连", "action": "placeholder", "custom": false, "default_action": "placeholder", "enabled": true, - "id": "npm-token", - "kind": "api-keys", + "id": "zhipu-api-key", + "kind": "ai-services", "label": "SECRET", "matcher": { - "kind": "prefix", - "min_tail": 30, - "prefix": "npm_" + "check": "zhipu-api-key", + "kind": "builtin" }, - "name": "npm token", + "name": "Zhipu API key", + "name_zh": "智谱 API 密钥", "on_by_default": true }, { + "about": "Starts with `bce-v3/ALTAK-`, followed by 21 letters or digits, `/` and 40 hex characters, as a whole", + "about_zh": "`bce-v3/ALTAK-` 开头,其后为 21 个字母或数字、`/` 与 40 个十六进制字符,整段", "action": "placeholder", "custom": false, "default_action": "placeholder", "enabled": true, - "id": "digitalocean-token", - "kind": "api-keys", + "id": "qianfan-api-key", + "kind": "ai-services", "label": "SECRET", "matcher": { - "kind": "prefix", - "min_tail": 30, - "prefix": "dop_v1_" + "check": "qianfan-api-key", + "kind": "builtin" }, - "name": "DigitalOcean token", + "name": "Baidu Qianfan API key", + "name_zh": "百度千帆 API 密钥", "on_by_default": true }, { + "about": "Starts with `sk-`, followed by exactly 32 letters and digits (both present) or exactly 48 lowercase letters", + "about_zh": "`sk-` 开头,其后恰为 32 个字母和数字(两者都有),或恰为 48 个小写字母", "action": "placeholder", "custom": false, "default_action": "placeholder", "enabled": true, - "id": "sendgrid-key", - "kind": "api-keys", + "id": "sk-compat-key", + "kind": "ai-services", "label": "SECRET", "matcher": { - "kind": "prefix", - "min_tail": 30, - "prefix": "SG." + "check": "sk-compat-key", + "kind": "builtin" }, - "name": "SendGrid key", + "name": "OpenAI-compatible API key", + "name_zh": "OpenAI 兼容格式 API 密钥", "on_by_default": true }, { + "about": "Starts with `ghp_`, followed by at least 30 characters", + "about_zh": "`ghp_` 开头,其后至少 30 个字符", "action": "placeholder", "custom": false, "default_action": "placeholder", "enabled": true, - "id": "private-key", - "kind": "private-keys", + "id": "github-personal-token", + "kind": "dev-platforms", "label": "SECRET", "matcher": { - "kind": "pem" + "kind": "prefix", + "min_tail": 30, + "prefix": "ghp_" }, - "name": "Private key", + "name": "GitHub personal access token", + "name_zh": "GitHub 个人令牌", "on_by_default": true }, { + "about": "Starts with `gho_`, followed by at least 30 characters", + "about_zh": "`gho_` 开头,其后至少 30 个字符", "action": "placeholder", "custom": false, "default_action": "placeholder", "enabled": true, - "id": "jwt", - "kind": "jwt", + "id": "github-oauth-token", + "kind": "dev-platforms", "label": "SECRET", "matcher": { - "kind": "jwt" + "kind": "prefix", + "min_tail": 30, + "prefix": "gho_" }, - "name": "JWT", + "name": "GitHub OAuth token", + "name_zh": "GitHub OAuth 令牌", "on_by_default": true }, { + "about": "Starts with `ghs_`, followed by at least 30 characters", + "about_zh": "`ghs_` 开头,其后至少 30 个字符", "action": "placeholder", "custom": false, "default_action": "placeholder", "enabled": true, - "id": "conn-string-password", - "kind": "conn-strings", + "id": "github-server-token", + "kind": "dev-platforms", "label": "SECRET", "matcher": { - "kind": "conn-string" + "kind": "prefix", + "min_tail": 30, + "prefix": "ghs_" }, - "name": "Connection string password", + "name": "GitHub server token", + "name_zh": "GitHub 服务器令牌", "on_by_default": true }, { + "about": "Starts with `ghu_`, followed by at least 30 characters", + "about_zh": "`ghu_` 开头,其后至少 30 个字符", "action": "placeholder", "custom": false, "default_action": "placeholder", "enabled": true, - "id": "cn-resident-id", - "kind": "personal", - "label": "ID_NUMBER", + "id": "github-user-token", + "kind": "dev-platforms", + "label": "SECRET", "matcher": { - "born_since": 1900, - "kind": "cn-resident-id" + "kind": "prefix", + "min_tail": 30, + "prefix": "ghu_" }, - "name": "Chinese resident ID number", + "name": "GitHub user token", + "name_zh": "GitHub 用户令牌", "on_by_default": true }, { + "about": "Starts with `github_pat_`, followed by at least 30 characters", + "about_zh": "`github_pat_` 开头,其后至少 30 个字符", "action": "placeholder", "custom": false, "default_action": "placeholder", "enabled": true, - "id": "bank-card", - "kind": "personal", - "label": "CARD_NUMBER", + "id": "github-fine-grained-token", + "kind": "dev-platforms", + "label": "SECRET", "matcher": { - "kind": "bank-card", - "networks": [ - { - "lengths": [ - 16, - 17, + "kind": "prefix", + "min_tail": 30, + "prefix": "github_pat_" + }, + "name": "GitHub fine-grained token", + "name_zh": "GitHub 细粒度令牌", + "on_by_default": true + }, + { + "about": "Starts with `glpat-`, followed by at least 15 characters", + "about_zh": "`glpat-` 开头,其后至少 15 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "gitlab-token", + "kind": "dev-platforms", + "label": "SECRET", + "matcher": { + "kind": "prefix", + "min_tail": 15, + "prefix": "glpat-" + }, + "name": "GitLab token", + "name_zh": "GitLab 令牌", + "on_by_default": true + }, + { + "about": "Starts with `npm_`, followed by at least 30 characters", + "about_zh": "`npm_` 开头,其后至少 30 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "npm-token", + "kind": "dev-platforms", + "label": "SECRET", + "matcher": { + "kind": "prefix", + "min_tail": 30, + "prefix": "npm_" + }, + "name": "npm token", + "name_zh": "npm 令牌", + "on_by_default": true + }, + { + "about": "Starts with `ghr_`, followed by at least 36 letters or digits", + "about_zh": "`ghr_` 开头,其后至少 36 个字母或数字", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "github-refresh-token", + "kind": "dev-platforms", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 36, + "prefix": "ghr_", + "tail": "alnum" + } + ] + }, + "name": "GitHub refresh token", + "name_zh": "GitHub 刷新令牌", + "on_by_default": true + }, + { + "about": "Starts with `glptt-`, `gldt-`, `glrt-`, `gloas-`, `glcbt-`, `glft-`, `glsoat-`, `glimt-` or `glagent-`, followed by at least 20 characters", + "about_zh": "`glptt-`、`gldt-`、`glrt-`、`gloas-`、`glcbt-`、`glft-`、`glsoat-`、`glimt-`、`glagent-` 开头,其后至少 20 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "gitlab-other-token", + "kind": "dev-platforms", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 20, + "prefix": "glptt-", + "tail": "any" + }, + { + "min_tail": 20, + "prefix": "gldt-", + "tail": "any" + }, + { + "min_tail": 20, + "prefix": "glrt-", + "tail": "any" + }, + { + "min_tail": 20, + "prefix": "gloas-", + "tail": "any" + }, + { + "min_tail": 20, + "prefix": "glcbt-", + "tail": "any" + }, + { + "min_tail": 20, + "prefix": "glft-", + "tail": "any" + }, + { + "min_tail": 20, + "prefix": "glsoat-", + "tail": "any" + }, + { + "min_tail": 20, + "prefix": "glimt-", + "tail": "any" + }, + { + "min_tail": 20, + "prefix": "glagent-", + "tail": "any" + } + ] + }, + "name": "Other GitLab token", + "name_zh": "GitLab 其他令牌", + "on_by_default": true + }, + { + "about": "Starts with `pypi-AgEIcHlwaS5vcmc`, followed by at least 50 characters", + "about_zh": "`pypi-AgEIcHlwaS5vcmc` 开头,其后至少 50 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "pypi-token", + "kind": "dev-platforms", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 50, + "prefix": "pypi-AgEIcHlwaS5vcmc", + "tail": "any" + } + ] + }, + "name": "PyPI token", + "name_zh": "PyPI 令牌", + "on_by_default": true + }, + { + "about": "Starts with `rubygems_`, followed by at least 48 hex characters", + "about_zh": "`rubygems_` 开头,其后至少 48 个十六进制字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "rubygems-token", + "kind": "dev-platforms", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 48, + "prefix": "rubygems_", + "tail": "hex" + } + ] + }, + "name": "RubyGems token", + "name_zh": "RubyGems 令牌", + "on_by_default": true + }, + { + "about": "Starts with `CLOJARS_`, followed by at least 60 letters or digits", + "about_zh": "`CLOJARS_` 开头,其后至少 60 个字母或数字", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "clojars-token", + "kind": "dev-platforms", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 60, + "prefix": "CLOJARS_", + "tail": "alnum" + } + ] + }, + "name": "Clojars token", + "name_zh": "Clojars 令牌", + "on_by_default": true + }, + { + "about": "Starts with `dckr_pat_`, followed by at least 27 characters; starts with `dckr_oat_`, followed by at least 32 characters", + "about_zh": "`dckr_pat_` 开头,其后至少 27 个字符;`dckr_oat_` 开头,其后至少 32 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "docker-hub-token", + "kind": "dev-platforms", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 27, + "prefix": "dckr_pat_", + "tail": "any" + }, + { + "min_tail": 32, + "prefix": "dckr_oat_", + "tail": "any" + } + ] + }, + "name": "Docker Hub token", + "name_zh": "Docker Hub 令牌", + "on_by_default": true + }, + { + "about": "Starts with `AKCp`, followed by at least 69 letters or digits", + "about_zh": "`AKCp` 开头,其后至少 69 个字母或数字", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "artifactory-key", + "kind": "dev-platforms", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 69, + "prefix": "AKCp", + "tail": "alnum" + } + ] + }, + "name": "JFrog Artifactory key", + "name_zh": "JFrog Artifactory 密钥", + "on_by_default": true + }, + { + "about": "Starts with `dapi`, followed by at least 32 hex characters", + "about_zh": "`dapi` 开头,其后至少 32 个十六进制字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "databricks-token", + "kind": "dev-platforms", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 32, + "prefix": "dapi", + "tail": "hex" + } + ] + }, + "name": "Databricks token", + "name_zh": "Databricks 令牌", + "on_by_default": true + }, + { + "about": "Starts with `sntrys_`, followed by at least 40 characters; starts with `sntryu_`, followed by at least 64 hex characters", + "about_zh": "`sntrys_` 开头,其后至少 40 个字符;`sntryu_` 开头,其后至少 64 个十六进制字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "sentry-token", + "kind": "dev-platforms", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 40, + "prefix": "sntrys_", + "tail": "any" + }, + { + "min_tail": 64, + "prefix": "sntryu_", + "tail": "hex" + } + ] + }, + "name": "Sentry token", + "name_zh": "Sentry 令牌", + "on_by_default": true + }, + { + "about": "Starts with `glsa_`, followed by at least 41 characters; starts with `glc_`, followed by at least 32 characters", + "about_zh": "`glsa_` 开头,其后至少 41 个字符;`glc_` 开头,其后至少 32 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "grafana-token", + "kind": "dev-platforms", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 41, + "prefix": "glsa_", + "tail": "any" + }, + { + "min_tail": 32, + "prefix": "glc_", + "tail": "any" + } + ] + }, + "name": "Grafana token", + "name_zh": "Grafana 令牌", + "on_by_default": true + }, + { + "about": "Starts with `PMAK-`, followed by at least 59 characters", + "about_zh": "`PMAK-` 开头,其后至少 59 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "postman-key", + "kind": "dev-platforms", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 59, + "prefix": "PMAK-", + "tail": "any" + } + ] + }, + "name": "Postman API key", + "name_zh": "Postman API 密钥", + "on_by_default": true + }, + { + "about": "Starts with `AKIA`, followed by at least 12 characters", + "about_zh": "`AKIA` 开头,其后至少 12 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "aws-access-key-id", + "kind": "cloud", + "label": "SECRET", + "matcher": { + "kind": "prefix", + "min_tail": 12, + "prefix": "AKIA" + }, + "name": "AWS access key ID", + "name_zh": "AWS 访问密钥 ID", + "on_by_default": true + }, + { + "about": "Starts with `ASIA`, followed by at least 12 characters", + "about_zh": "`ASIA` 开头,其后至少 12 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "aws-temporary-key-id", + "kind": "cloud", + "label": "SECRET", + "matcher": { + "kind": "prefix", + "min_tail": 12, + "prefix": "ASIA" + }, + "name": "AWS temporary access key ID", + "name_zh": "AWS 临时访问密钥 ID", + "on_by_default": true + }, + { + "about": "Starts with `AIza`, followed by at least 30 characters", + "about_zh": "`AIza` 开头,其后至少 30 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "google-api-key", + "kind": "cloud", + "label": "SECRET", + "matcher": { + "kind": "prefix", + "min_tail": 30, + "prefix": "AIza" + }, + "name": "Google API key", + "name_zh": "Google API 密钥", + "on_by_default": true + }, + { + "about": "Starts with `ya29.`, followed by at least 20 characters", + "about_zh": "`ya29.` 开头,其后至少 20 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "google-oauth-token", + "kind": "cloud", + "label": "SECRET", + "matcher": { + "kind": "prefix", + "min_tail": 20, + "prefix": "ya29." + }, + "name": "Google OAuth token", + "name_zh": "Google OAuth 令牌", + "on_by_default": true + }, + { + "about": "Starts with `dop_v1_`, followed by at least 30 characters", + "about_zh": "`dop_v1_` 开头,其后至少 30 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "digitalocean-token", + "kind": "cloud", + "label": "SECRET", + "matcher": { + "kind": "prefix", + "min_tail": 30, + "prefix": "dop_v1_" + }, + "name": "DigitalOcean token", + "name_zh": "DigitalOcean 令牌", + "on_by_default": true + }, + { + "about": "Starts with `ABIA` or `ACCA`, followed by at least 16 letters or digits", + "about_zh": "`ABIA`、`ACCA` 开头,其后至少 16 个字母或数字", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "aws-other-key-id", + "kind": "cloud", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 16, + "prefix": "ABIA", + "tail": "alnum" + }, + { + "min_tail": 16, + "prefix": "ACCA", + "tail": "alnum" + } + ] + }, + "name": "Other AWS access key ID", + "name_zh": "AWS 其他访问密钥 ID", + "on_by_default": true + }, + { + "about": "Starts with `LTAI`, followed by at least 12 letters or digits", + "about_zh": "`LTAI` 开头,其后至少 12 个字母或数字", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "alibaba-access-key-id", + "kind": "cloud", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 12, + "prefix": "LTAI", + "tail": "alnum" + } + ] + }, + "name": "Alibaba Cloud AccessKey ID", + "name_zh": "阿里云 AccessKey ID", + "on_by_default": true + }, + { + "about": "Starts with `AKID`, followed by at least 32 letters or digits", + "about_zh": "`AKID` 开头,其后至少 32 个字母或数字", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "tencent-secret-id", + "kind": "cloud", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 32, + "prefix": "AKID", + "tail": "alnum" + } + ] + }, + "name": "Tencent Cloud SecretId", + "name_zh": "腾讯云 SecretId", + "on_by_default": true + }, + { + "about": "Starts with `AKLT` or `AKTP`, followed by at least 40 characters", + "about_zh": "`AKLT`、`AKTP` 开头,其后至少 40 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "volcengine-access-key-id", + "kind": "cloud", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 40, + "prefix": "AKLT", + "tail": "any" + }, + { + "min_tail": 40, + "prefix": "AKTP", + "tail": "any" + } + ] + }, + "name": "Volcengine AccessKey ID", + "name_zh": "火山引擎 AccessKey ID", + "on_by_default": true + }, + { + "about": "Three characters, a digit and `Q~`, followed by 31 to 34 characters", + "about_zh": "3 个字符、1 位数字与 `Q~`,其后 31 至 34 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "azure-ad-client-secret", + "kind": "cloud", + "label": "SECRET", + "matcher": { + "check": "azure-ad-client-secret", + "kind": "builtin" + }, + "name": "Azure AD client secret", + "name_zh": "Azure AD 客户端密码", + "on_by_default": true + }, + { + "about": "Starts with `doo_v1_` or `dor_v1_`, followed by at least 64 hex characters", + "about_zh": "`doo_v1_`、`dor_v1_` 开头,其后至少 64 个十六进制字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "digitalocean-other-token", + "kind": "cloud", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 64, + "prefix": "doo_v1_", + "tail": "hex" + }, + { + "min_tail": 64, + "prefix": "dor_v1_", + "tail": "hex" + } + ] + }, + "name": "Other DigitalOcean token", + "name_zh": "DigitalOcean 其他令牌", + "on_by_default": true + }, + { + "about": "Starts with `v1.0-`, followed by at least 171 characters", + "about_zh": "`v1.0-` 开头,其后至少 171 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "cloudflare-origin-ca-key", + "kind": "cloud", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 171, + "prefix": "v1.0-", + "tail": "any" + } + ] + }, + "name": "Cloudflare Origin CA key", + "name_zh": "Cloudflare Origin CA 密钥", + "on_by_default": true + }, + { + "about": "Starts with `hvs.`, followed by at least 90 characters; starts with `hvb.`, followed by at least 138 characters", + "about_zh": "`hvs.` 开头,其后至少 90 个字符;`hvb.` 开头,其后至少 138 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "vault-token", + "kind": "cloud", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 90, + "prefix": "hvs.", + "tail": "any" + }, + { + "min_tail": 138, + "prefix": "hvb.", + "tail": "any" + } + ] + }, + "name": "HashiCorp Vault token", + "name_zh": "HashiCorp Vault 令牌", + "on_by_default": true + }, + { + "about": "14 letters or digits and `.atlasv1.`, followed by 60 to 70 characters", + "about_zh": "14 个字母或数字与 `.atlasv1.`,其后 60 至 70 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "terraform-token", + "kind": "cloud", + "label": "SECRET", + "matcher": { + "check": "terraform-token", + "kind": "builtin" + }, + "name": "Terraform Cloud token", + "name_zh": "Terraform Cloud 令牌", + "on_by_default": true + }, + { + "about": "Starts with `dp.pt.`, followed by at least 43 letters or digits", + "about_zh": "`dp.pt.` 开头,其后至少 43 个字母或数字", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "doppler-token", + "kind": "cloud", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 43, + "prefix": "dp.pt.", + "tail": "alnum" + } + ] + }, + "name": "Doppler token", + "name_zh": "Doppler 令牌", + "on_by_default": true + }, + { + "about": "Starts with `pul-`, followed by at least 40 hex characters", + "about_zh": "`pul-` 开头,其后至少 40 个十六进制字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "pulumi-token", + "kind": "cloud", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 40, + "prefix": "pul-", + "tail": "hex" + } + ] + }, + "name": "Pulumi token", + "name_zh": "Pulumi 令牌", + "on_by_default": true + }, + { + "about": "Starts with `HRKU-AA`, followed by at least 58 characters", + "about_zh": "`HRKU-AA` 开头,其后至少 58 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "heroku-key", + "kind": "cloud", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 58, + "prefix": "HRKU-AA", + "tail": "any" + } + ] + }, + "name": "Heroku API key", + "name_zh": "Heroku API 密钥", + "on_by_default": true + }, + { + "about": "Starts with `fo1_`, followed by at least 43 characters", + "about_zh": "`fo1_` 开头,其后至少 43 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "flyio-token", + "kind": "cloud", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 43, + "prefix": "fo1_", + "tail": "any" + } + ] + }, + "name": "Fly.io token", + "name_zh": "Fly.io 令牌", + "on_by_default": true + }, + { + "about": "Starts with `sbp_`, followed by at least 40 letters or digits; starts with `sb_secret_`, followed by at least 31 characters", + "about_zh": "`sbp_` 开头,其后至少 40 个字母或数字;`sb_secret_` 开头,其后至少 31 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "supabase-key", + "kind": "cloud", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 40, + "prefix": "sbp_", + "tail": "alnum" + }, + { + "min_tail": 31, + "prefix": "sb_secret_", + "tail": "any" + } + ] + }, + "name": "Supabase key", + "name_zh": "Supabase 密钥", + "on_by_default": true + }, + { + "about": "Starts with `AGE-SECRET-KEY-1`, followed by at least 58 letters or digits", + "about_zh": "`AGE-SECRET-KEY-1` 开头,其后至少 58 个字母或数字", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "age-secret-key", + "kind": "cloud", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 58, + "prefix": "AGE-SECRET-KEY-1", + "tail": "alnum" + } + ] + }, + "name": "age secret key", + "name_zh": "age 私钥", + "on_by_default": true + }, + { + "about": "Starts with `ops_eyJ`, followed by at least 200 characters", + "about_zh": "`ops_eyJ` 开头,其后至少 200 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "onepassword-token", + "kind": "cloud", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 200, + "prefix": "ops_eyJ", + "tail": "any" + } + ] + }, + "name": "1Password service account token", + "name_zh": "1Password 服务账号令牌", + "on_by_default": true + }, + { + "about": "Starts with `xoxb-`, followed by at least 20 characters", + "about_zh": "`xoxb-` 开头,其后至少 20 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "slack-bot-token", + "kind": "business", + "label": "SECRET", + "matcher": { + "kind": "prefix", + "min_tail": 20, + "prefix": "xoxb-" + }, + "name": "Slack bot token", + "name_zh": "Slack 机器人令牌", + "on_by_default": true + }, + { + "about": "Starts with `xoxp-`, followed by at least 20 characters", + "about_zh": "`xoxp-` 开头,其后至少 20 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "slack-user-token", + "kind": "business", + "label": "SECRET", + "matcher": { + "kind": "prefix", + "min_tail": 20, + "prefix": "xoxp-" + }, + "name": "Slack user token", + "name_zh": "Slack 用户令牌", + "on_by_default": true + }, + { + "about": "Starts with `xoxa-`, followed by at least 20 characters", + "about_zh": "`xoxa-` 开头,其后至少 20 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "slack-app-token", + "kind": "business", + "label": "SECRET", + "matcher": { + "kind": "prefix", + "min_tail": 20, + "prefix": "xoxa-" + }, + "name": "Slack app token", + "name_zh": "Slack 应用令牌", + "on_by_default": true + }, + { + "about": "Starts with `sk_live_`, followed by at least 20 characters", + "about_zh": "`sk_live_` 开头,其后至少 20 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "stripe-live-key", + "kind": "business", + "label": "SECRET", + "matcher": { + "kind": "prefix", + "min_tail": 20, + "prefix": "sk_live_" + }, + "name": "Stripe live key", + "name_zh": "Stripe 生产密钥", + "on_by_default": true + }, + { + "about": "Starts with `rk_live_`, followed by at least 20 characters", + "about_zh": "`rk_live_` 开头,其后至少 20 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "stripe-restricted-key", + "kind": "business", + "label": "SECRET", + "matcher": { + "kind": "prefix", + "min_tail": 20, + "prefix": "rk_live_" + }, + "name": "Stripe restricted key", + "name_zh": "Stripe 受限密钥", + "on_by_default": true + }, + { + "about": "Starts with `SG.`, followed by at least 30 characters", + "about_zh": "`SG.` 开头,其后至少 30 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "sendgrid-key", + "kind": "business", + "label": "SECRET", + "matcher": { + "kind": "prefix", + "min_tail": 30, + "prefix": "SG." + }, + "name": "SendGrid key", + "name_zh": "SendGrid 密钥", + "on_by_default": true + }, + { + "about": "Starts with `whsec_`, followed by at least 32 characters", + "about_zh": "`whsec_` 开头,其后至少 32 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "stripe-webhook-secret", + "kind": "business", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 32, + "prefix": "whsec_", + "tail": "any" + } + ] + }, + "name": "Stripe webhook signing secret", + "name_zh": "Stripe Webhook 签名密钥", + "on_by_default": true + }, + { + "about": "Starts with `shpat_`, `shpca_`, `shppa_` or `shpss_`, followed by at least 32 hex characters", + "about_zh": "`shpat_`、`shpca_`、`shppa_`、`shpss_` 开头,其后至少 32 个十六进制字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "shopify-token", + "kind": "business", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 32, + "prefix": "shpat_", + "tail": "hex" + }, + { + "min_tail": 32, + "prefix": "shpca_", + "tail": "hex" + }, + { + "min_tail": 32, + "prefix": "shppa_", + "tail": "hex" + }, + { + "min_tail": 32, + "prefix": "shpss_", + "tail": "hex" + } + ] + }, + "name": "Shopify token", + "name_zh": "Shopify 令牌", + "on_by_default": true + }, + { + "about": "Starts with `SK`, followed by at least 32 hex characters", + "about_zh": "`SK` 开头,其后至少 32 个十六进制字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "twilio-api-key", + "kind": "business", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 32, + "prefix": "SK", + "tail": "hex" + } + ] + }, + "name": "Twilio API key", + "name_zh": "Twilio API 密钥", + "on_by_default": true + }, + { + "about": "Starts with `xkeysib-`, followed by at least 81 characters", + "about_zh": "`xkeysib-` 开头,其后至少 81 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "brevo-key", + "kind": "business", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 81, + "prefix": "xkeysib-", + "tail": "any" + } + ] + }, + "name": "Brevo API key", + "name_zh": "Brevo API 密钥", + "on_by_default": true + }, + { + "about": "Starts with `xapp-`, `xoxe-`, `xoxe.xoxp-`, `xoxe.xoxb-` or `xoxr-`, followed by at least 20 characters", + "about_zh": "`xapp-`、`xoxe-`、`xoxe.xoxp-`、`xoxe.xoxb-`、`xoxr-` 开头,其后至少 20 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "slack-other-token", + "kind": "business", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 20, + "prefix": "xapp-", + "tail": "any" + }, + { + "min_tail": 20, + "prefix": "xoxe-", + "tail": "any" + }, + { + "min_tail": 20, + "prefix": "xoxe.xoxp-", + "tail": "any" + }, + { + "min_tail": 20, + "prefix": "xoxe.xoxb-", + "tail": "any" + }, + { + "min_tail": 20, + "prefix": "xoxr-", + "tail": "any" + } + ] + }, + "name": "Other Slack token", + "name_zh": "Slack 其他令牌", + "on_by_default": true + }, + { + "about": "Starts with `lin_api_`, followed by at least 40 letters or digits", + "about_zh": "`lin_api_` 开头,其后至少 40 个字母或数字", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "linear-key", + "kind": "business", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 40, + "prefix": "lin_api_", + "tail": "alnum" + } + ] + }, + "name": "Linear API key", + "name_zh": "Linear API 密钥", + "on_by_default": true + }, + { + "about": "Starts with `ntn_`, followed by at least 46 letters or digits", + "about_zh": "`ntn_` 开头,其后至少 46 个字母或数字", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "notion-token", + "kind": "business", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 46, + "prefix": "ntn_", + "tail": "alnum" + } + ] + }, + "name": "Notion token", + "name_zh": "Notion 令牌", + "on_by_default": true + }, + { + "about": "Starts with `ATATT3`, followed by at least 150 characters", + "about_zh": "`ATATT3` 开头,其后至少 150 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "atlassian-token", + "kind": "business", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 150, + "prefix": "ATATT3", + "tail": "any" + } + ] + }, + "name": "Atlassian API token", + "name_zh": "Atlassian API 令牌", + "on_by_default": true + }, + { + "about": "From `-----BEGIN … PRIVATE KEY-----` to its END, as a whole", + "about_zh": "`-----BEGIN … PRIVATE KEY-----` 至对应的 END,整段", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "private-key", + "kind": "private-keys", + "label": "SECRET", + "matcher": { + "kind": "pem" + }, + "name": "Private key", + "name_zh": "私钥", + "on_by_default": true + }, + { + "about": "Three base64url parts; the first decodes to text containing `\"alg\"`", + "about_zh": "三段 base64url,首段解码后含 `\"alg\"`", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "jwt", + "kind": "jwt", + "label": "SECRET", + "matcher": { + "kind": "jwt" + }, + "name": "JWT", + "name_zh": "JWT", + "on_by_default": true + }, + { + "about": "The password in `scheme://user:password@host`; only the password is replaced", + "about_zh": "`协议://用户:口令@主机` 中的口令;只换口令", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "conn-string-password", + "kind": "conn-strings", + "label": "SECRET", + "matcher": { + "kind": "conn-string" + }, + "name": "Connection string password", + "name_zh": "连接串口令", + "on_by_default": true + }, + { + "about": "The value of an `Authorization`, `X-Api-Key`, `Api-Key` or `X-Goog-Api-Key` header, in curl `-H`, raw HTTP or code; only the value is replaced", + "about_zh": "`Authorization`、`X-Api-Key`、`Api-Key`、`X-Goog-Api-Key` 请求头的值,包括 curl 的 `-H`、HTTP 原文与代码中的写法;只换值", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "auth-header-credential", + "kind": "in-context", + "label": "SECRET", + "matcher": { + "check": "auth-header-credential", + "kind": "builtin" + }, + "name": "Credential in a request header", + "name_zh": "请求头中的凭据", + "on_by_default": true + }, + { + "about": "Passwords on a command line: `curl -u user:password`, `--user`, `mysql -ppassword`, `PGPASSWORD=… psql`; only the password is replaced", + "about_zh": "命令行中的口令:`curl -u 用户:口令`、`--user`、`mysql -p口令`、`PGPASSWORD=… psql`;只换口令", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "command-line-password", + "kind": "in-context", + "label": "SECRET", + "matcher": { + "check": "command-line-password", + "kind": "builtin" + }, + "name": "Password on a command line", + "name_zh": "命令行中的口令", + "on_by_default": true + }, + { + "about": "The value of the URL query parameter `access_token`, `api_key`, `apikey`, `client_secret`, `sig`, `X-Amz-Signature` or `X-Goog-Signature`; only the parameter value is replaced", + "about_zh": "URL 查询参数 `access_token`、`api_key`、`apikey`、`client_secret`、`sig`、`X-Amz-Signature`、`X-Goog-Signature` 的值;只换参数值", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "url-token", + "kind": "in-context", + "label": "SECRET", + "matcher": { + "check": "url-token", + "kind": "builtin" + }, + "name": "Token in a URL", + "name_zh": "URL 中的令牌", + "on_by_default": true + }, + { + "about": "The token in a Feishu / Lark, DingTalk, WeCom, Slack, Discord or Microsoft Teams bot webhook URL; only the token is replaced", + "about_zh": "飞书 / Lark、钉钉、企业微信、Slack、Discord、Microsoft Teams 机器人 Webhook 地址中的令牌;只换令牌段", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "bot-webhook", + "kind": "in-context", + "label": "SECRET", + "matcher": { + "check": "bot-webhook", + "kind": "builtin" + }, + "name": "Bot webhook", + "name_zh": "机器人 Webhook", + "on_by_default": true + }, + { + "about": "8 to 10 digits and `:AA`, followed by 33 characters", + "about_zh": "8 至 10 位数字与 `:AA`,其后 33 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "telegram-bot-token", + "kind": "in-context", + "label": "SECRET", + "matcher": { + "check": "telegram-bot-token", + "kind": "builtin" + }, + "name": "Telegram bot token", + "name_zh": "Telegram 机器人令牌", + "on_by_default": true + }, + { + "about": "Assignments in `.env`, YAML, TOML, INI or JSON whose variable name ends in `SECRET`, `SECRET_KEY`, `PASSWORD`, `PASSWD`, `PWD`, `TOKEN`, `API_KEY`, `APIKEY`, `ACCESS_KEY`, `PRIVATE_KEY` or `CLIENT_SECRET` (ignoring case) and whose value is a literal of at least 8 characters; only the value is replaced. Expressions, variable references and placeholders do not count", + "about_zh": "`.env`、YAML、TOML、INI、JSON 中变量名以 `SECRET`、`SECRET_KEY`、`PASSWORD`、`PASSWD`、`PWD`、`TOKEN`、`API_KEY`、`APIKEY`、`ACCESS_KEY`、`PRIVATE_KEY`、`CLIENT_SECRET` 结尾(不区分大小写)、值为至少 8 个字符的字面量的赋值;只换值,表达式、变量引用和占位写法不算", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "config-assignment", + "kind": "in-context", + "label": "SECRET", + "matcher": { + "kind": "assignment", + "suffixes": [ + "SECRET", + "SECRET_KEY", + "PASSWORD", + "PASSWD", + "PWD", + "TOKEN", + "API_KEY", + "APIKEY", + "ACCESS_KEY", + "PRIVATE_KEY", + "CLIENT_SECRET" + ] + }, + "name": "Secret assigned in a config file", + "name_zh": "配置文件中的密钥赋值", + "on_by_default": true + }, + { + "about": "12, 15, 18, 21 or 24 consecutive words from the BIP39 English or Simplified Chinese word list that pass the checksum; numbering allowed, replaced as a whole", + "about_zh": "连续 12、15、18、21 或 24 个 BIP39 英文或简体中文词表中的词,并通过校验和;可带编号,整段", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "crypto-mnemonic", + "kind": "crypto", + "label": "SECRET", + "matcher": { + "check": "crypto-mnemonic", + "kind": "builtin" + }, + "name": "Recovery phrase", + "name_zh": "助记词", + "on_by_default": true + }, + { + "about": "A WIF private key of 51 or 52 characters that passes Base58Check with a valid version byte", + "about_zh": "51 或 52 个字符的 WIF 私钥:Base58Check 校验通过,版本字节对得上", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "crypto-wif-key", + "kind": "crypto", + "label": "SECRET", + "matcher": { + "check": "crypto-wif-key", + "kind": "builtin" + }, + "name": "WIF private key", + "name_zh": "WIF 私钥", + "on_by_default": true + }, + { + "about": "Starts with `xprv`, `tprv`, `yprv` or `zprv` and passes Base58Check", + "about_zh": "`xprv`、`tprv`、`yprv`、`zprv` 开头,Base58Check 校验通过", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "crypto-extended-key", + "kind": "crypto", + "label": "SECRET", + "matcher": { + "check": "crypto-extended-key", + "kind": "builtin" + }, + "name": "Extended private key", + "name_zh": "扩展私钥", + "on_by_default": true + }, + { + "about": "An 18-character resident ID number, including residence permits for Hong Kong, Macau and Taiwan residents and foreign permanent residence ID cards, whose region code, birth date since 1900 and check character all check out", + "about_zh": "18 位居民身份证号(含港澳台居民居住证、外国人永久居留身份证):地区码、1900 年以来的出生日期和校验码都对得上", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "cn-resident-id", + "kind": "id-documents", + "label": "ID_NUMBER", + "matcher": { + "born_since": 1900, + "kind": "cn-resident-id" + }, + "name": "Chinese resident ID number", + "name_zh": "居民身份证号", + "on_by_default": true + }, + { + "about": "A 15-digit legacy resident ID number with a valid region code and 6-digit birth date, followed by a 3-digit sequence", + "about_zh": "15 位旧身份证号:地区码、6 位出生日期都对得上,其后 3 位顺序码", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": false, + "id": "cn-resident-id-15", + "kind": "id-documents", + "label": "ID_NUMBER", + "matcher": { + "check": "cn-resident-id-15", + "kind": "builtin" + }, + "name": "15-digit legacy resident ID number", + "name_zh": "15 位旧身份证号", + "on_by_default": false + }, + { + "about": "Two lines of 44 characters starting with `P`, with all four check digits and the composite check digit correct; replaced as a whole", + "about_zh": "两行各 44 个字符、以 `P` 开头,四处校验位与综合校验位都对得上;整段", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "passport-mrz", + "kind": "id-documents", + "label": "PASSPORT", + "matcher": { + "check": "passport-mrz", + "kind": "builtin" + }, + "name": "Passport machine-readable zone", + "name_zh": "护照机读区", + "on_by_default": true + }, + { + "about": "Starts with `E`, or `E` and a letter, followed by 7 or 8 digits, with “护照” or “passport” in the 20 characters before it", + "about_zh": "`E` 或 `E` 加一个字母开头、其后 7 至 8 位数字,且前 20 个字符内出现「护照」或「passport」", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": false, + "id": "cn-passport", + "kind": "id-documents", + "label": "PASSPORT", + "matcher": { + "check": "cn-passport", + "kind": "builtin" + }, + "name": "Chinese passport number", + "name_zh": "中国护照号", + "on_by_default": false + }, + { + "about": "Hong Kong, Macau and Taiwan ID numbers: Hong Kong and Taiwan ones with a correct check character; Macau ones only when the document is named just before", + "about_zh": "香港、澳门、台湾身份证号:香港、台湾的校验码对得上;澳门的须前文写明证件名称", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": false, + "id": "hk-macau-taiwan-id", + "kind": "id-documents", + "label": "ID_NUMBER", + "matcher": { + "check": "hk-macau-taiwan-id", + "kind": "builtin" + }, + "name": "Hong Kong, Macau or Taiwan ID number", + "name_zh": "港澳台身份证号", + "on_by_default": false + }, + { + "about": "Mainland travel permits for Hong Kong and Macau residents (`H` or `M` and 8 digits) and for Taiwan residents (8 digits), with “通行证”, “回乡证” or “台胞证” in the 20 characters before it", + "about_zh": "港澳居民来往内地通行证(`H` 或 `M` 加 8 位数字)与台湾居民来往大陆通行证(8 位数字),且前 20 个字符内出现「通行证」「回乡证」或「台胞证」", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": false, + "id": "mainland-travel-permit", + "kind": "id-documents", + "label": "ID_NUMBER", + "matcher": { + "check": "mainland-travel-permit", + "kind": "builtin" + }, + "name": "Mainland travel permit number", + "name_zh": "港澳台往来内地通行证号", + "on_by_default": false + }, + { + "about": "A US Social Security number written as `ddd-dd-dddd`, excluding area, group and serial numbers never issued", + "about_zh": "`ddd-dd-dddd` 写法的美国社会安全号,不会分配的区号、组号与序号除外", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": false, + "id": "us-ssn", + "kind": "foreign-ids", + "label": "ID_NUMBER", + "matcher": { + "check": "us-ssn", + "kind": "builtin" + }, + "name": "US Social Security number", + "name_zh": "美国社会安全号", + "on_by_default": false + }, + { + "about": "A UK National Insurance number: 2 letters, 6 digits and one of A to D, excluding letter pairs never issued", + "about_zh": "2 个字母、6 位数字与 A 至 D 之一组成的英国国民保险号,不会分配的字母组合除外", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": false, + "id": "uk-nino", + "kind": "foreign-ids", + "label": "ID_NUMBER", + "matcher": { + "check": "uk-nino", + "kind": "builtin" + }, + "name": "UK National Insurance number", + "name_zh": "英国国民保险号", + "on_by_default": false + }, + { + "about": "Starts with S, T, F or G, followed by 7 digits and a correct check letter", + "about_zh": "S、T、F、G 之一开头,7 位数字加校验字母,校验字母对得上", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": false, + "id": "sg-nric", + "kind": "foreign-ids", + "label": "ID_NUMBER", + "matcher": { + "check": "sg-nric", + "kind": "builtin" + }, + "name": "Singapore NRIC / FIN", + "name_zh": "新加坡 NRIC / FIN", + "on_by_default": false + }, + { + "about": "A 6-digit birth date, `-` and 7 digits, with a valid date and an 8th digit from 1 to 8", + "about_zh": "6 位出生日期、`-` 与 7 位数字,日期有效、第 8 位为 1 至 8", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": false, + "id": "kr-rrn", + "kind": "foreign-ids", + "label": "ID_NUMBER", + "matcher": { + "check": "kr-rrn", + "kind": "builtin" + }, + "name": "Korean resident registration number", + "name_zh": "韩国居民登录号", + "on_by_default": false + }, + { + "about": "12 digits with a correct check digit, with “マイナンバー”, “個人番号” or “My Number” in the 20 characters before it", + "about_zh": "12 位数字,校验位对得上,且前 20 个字符内出现「マイナンバー」「個人番号」或「My Number」", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": false, + "id": "jp-my-number", + "kind": "foreign-ids", + "label": "ID_NUMBER", + "matcher": { + "check": "jp-my-number", + "kind": "builtin" + }, + "name": "Japanese My Number", + "name_zh": "日本个人番号", + "on_by_default": false + }, + { + "about": "12 digits (optionally in groups of four), the first from 2 to 9, passing the Verhoeff check", + "about_zh": "12 位数字(可四位一组),首位为 2 至 9,Verhoeff 校验通过", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": false, + "id": "in-aadhaar", + "kind": "foreign-ids", + "label": "ID_NUMBER", + "matcher": { + "check": "in-aadhaar", + "kind": "builtin" + }, + "name": "Indian Aadhaar number", + "name_zh": "印度 Aadhaar 号码", + "on_by_default": false + }, + { + "about": "A Brazilian CPF written as `ddd.ddd.ddd-dd`, with both check digits correct", + "about_zh": "`ddd.ddd.ddd-dd` 写法的巴西 CPF,两位校验码对得上", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": false, + "id": "br-cpf", + "kind": "foreign-ids", + "label": "ID_NUMBER", + "matcher": { + "check": "br-cpf", + "kind": "builtin" + }, + "name": "Brazilian CPF", + "name_zh": "巴西 CPF", + "on_by_default": false + }, + { + "about": "A UnionPay, Visa, Mastercard, American Express, JCB, Discover or Diners Club card number whose prefix and length match and that passes the Luhn check; public test card numbers excepted", + "about_zh": "银联、Visa、Mastercard、American Express、JCB、Discover、Diners Club 的卡号:号段、位数对得上并通过 Luhn 校验;公开的测试卡号除外", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "bank-card", + "kind": "financial", + "label": "CARD_NUMBER", + "matcher": { + "kind": "bank-card", + "networks": [ + { + "lengths": [ + 16, + 17, 18, 19 ], @@ -1216,37 +3269,360 @@ ] }, "name": "Bank card number", + "name_zh": "银行卡号", + "on_by_default": true + }, + { + "about": "A country code, two check digits and the account number length set for that country, passing MOD 97-10; written in one run or in groups of four", + "about_zh": "国家代码、两位校验码与该国规定位数的账号,MOD 97-10 校验通过;连写或四位一组", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "iban", + "kind": "financial", + "label": "ACCOUNT_NUMBER", + "matcher": { + "check": "iban", + "kind": "builtin" + }, + "name": "IBAN", + "name_zh": "国际银行账号(IBAN)", "on_by_default": true }, { + "about": "Email addresses: `name@domain`", + "about_zh": "邮箱地址:`名称@域名`", "action": "placeholder", "custom": false, "default_action": "placeholder", "enabled": false, "id": "email", - "kind": "personal", + "kind": "contact", "label": "EMAIL", "matcher": { "kind": "email" }, "name": "Email address", + "name_zh": "邮箱地址", "on_by_default": false }, { + "about": "Chinese mainland mobile numbers: 11 digits starting with `1`, the second 3 to 9, not run together with other digits; a `+86`, `0086` or `86-` prefix and 3-4-4 groups separated by spaces or hyphens are allowed", + "about_zh": "中国大陆手机号:`1` 开头的 11 位数字,第二位为 3 到 9,前后不紧挨其他数字;可带 `+86`、`0086`、`86-` 前缀,可按 3-4-4 以空格或连字符分隔", "action": "placeholder", "custom": false, "default_action": "placeholder", "enabled": false, "id": "cn-mobile-phone", - "kind": "personal", + "kind": "contact", "label": "PHONE", "matcher": { "kind": "cn-mobile-phone" }, "name": "Chinese mainland mobile number", + "name_zh": "中国大陆手机号", + "on_by_default": false + }, + { + "about": "A 2- or 3-digit area code starting with `0` and a 7- or 8-digit number, with a valid area code and brackets or a hyphen (`010-12345678`, `(0755)1234567`)", + "about_zh": "`0` 开头的 2 至 3 位区号加 7 至 8 位号码,区号有效,须带括号或连字符(`010-12345678`、`(0755)1234567`)", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": false, + "id": "cn-landline", + "kind": "contact", + "label": "PHONE", + "matcher": { + "check": "cn-landline", + "kind": "builtin" + }, + "name": "Chinese mainland landline number", + "name_zh": "中国大陆固定电话", + "on_by_default": false + }, + { + "about": "Starts with `+`, with a valid country code and the length set for that country; spaces and hyphens allowed", + "about_zh": "`+` 开头,国家码有效、位数与该国规定一致;可带空格与连字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": false, + "id": "intl-phone", + "kind": "contact", + "label": "PHONE", + "matcher": { + "check": "intl-phone", + "kind": "builtin" + }, + "name": "International phone number", + "name_zh": "国际电话号码", + "on_by_default": false + }, + { + "about": "A Chinese mainland street address: administrative area names, a street, road, lane or village, and a house number; addresses that stop at the district do not count", + "about_zh": "行政区划名称、街道路巷村之一与门牌号(号、栋、单元、室)组成的中国大陆详细地址;只到区县的不算", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": false, + "id": "cn-address", + "kind": "location", + "label": "ADDRESS", + "matcher": { + "check": "cn-address", + "kind": "builtin" + }, + "name": "Chinese mainland street address", + "name_zh": "中国大陆详细地址", + "on_by_default": false + }, + { + "about": "A latitude and longitude pair within range, each with at least 5 decimal places", + "about_zh": "成对出现、在取值范围内、小数不少于 5 位的纬度与经度", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": false, + "id": "geo-coordinates", + "kind": "location", + "label": "LOCATION", + "matcher": { + "check": "geo-coordinates", + "kind": "builtin" + }, + "name": "Precise coordinates", + "name_zh": "精确坐标", + "on_by_default": false + }, + { + "about": "A province abbreviation, a letter and 5 letters or digits (6 for new-energy vehicles), without I or O", + "about_zh": "省份简称、字母与 5 位字母或数字(新能源车为 6 位),不含 I、O", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": false, + "id": "cn-license-plate", + "kind": "vehicle", + "label": "PLATE", + "matcher": { + "check": "cn-license-plate", + "kind": "builtin" + }, + "name": "Chinese mainland license plate", + "name_zh": "中国大陆车牌号", + "on_by_default": false + }, + { + "about": "17 characters without I, O or Q, with a correct check character in position 9", + "about_zh": "17 个字符、不含 I、O、Q,第 9 位校验码对得上", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": false, + "id": "vin", + "kind": "vehicle", + "label": "VIN", + "matcher": { + "check": "vin", + "kind": "builtin" + }, + "name": "Vehicle identification number (VIN)", + "name_zh": "车辆识别代号(VIN)", + "on_by_default": false + }, + { + "about": "15 digits passing the Luhn check, with “IMEI” in the 20 characters before it", + "about_zh": "15 位数字,Luhn 校验通过,且前 20 个字符内出现「IMEI」", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": false, + "id": "imei", + "kind": "device", + "label": "DEVICE_ID", + "matcher": { + "check": "imei", + "kind": "builtin" + }, + "name": "IMEI", + "name_zh": "IMEI", + "on_by_default": false + }, + { + "about": "6 pairs of hex characters separated by `:` or `-`; all-zero, broadcast and multicast addresses excepted", + "about_zh": "6 组两位十六进制字符,以 `:` 或 `-` 分隔;全零、广播与组播地址除外", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": false, + "id": "mac-address", + "kind": "device", + "label": "DEVICE_ID", + "matcher": { + "check": "mac-address", + "kind": "builtin" + }, + "name": "MAC address", + "name_zh": "MAC 地址", + "on_by_default": false + }, + { + "about": "Public IPv4 and IPv6 addresses; private, loopback, link-local, carrier-grade NAT, documentation and multicast addresses excepted", + "about_zh": "公网 IPv4、IPv6 地址;私有、回环、链路本地、运营商级 NAT、文档示例与组播地址除外", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": false, + "id": "public-ip", + "kind": "device", + "label": "IP", + "matcher": { + "check": "public-ip", + "kind": "builtin" + }, + "name": "Public IP address", + "name_zh": "公网 IP 地址", + "on_by_default": false + }, + { + "about": "The value of a field named `姓名`, `真实姓名`, `联系人`, `收件人`, `收货人`, `real_name`, `full_name`, `legal_name`, `first_name`, `last_name`, `given_name`, `family_name` or `name`, written as `name: value`, `\"name\": \"value\"` or under a table header; only the value is replaced", + "about_zh": "字段名为 `姓名`、`真实姓名`、`联系人`、`收件人`、`收货人`、`real_name`、`full_name`、`legal_name`、`first_name`、`last_name`、`given_name`、`family_name`、`name` 的值,包括 `字段名: 值`、`\"字段名\": \"值\"` 与表格表头的写法;只换值", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": false, + "id": "labeled-name", + "kind": "labeled-personal", + "label": "NAME", + "matcher": { + "kind": "labeled", + "names": [ + "姓名", + "真实姓名", + "联系人", + "收件人", + "收货人", + "real_name", + "full_name", + "legal_name", + "first_name", + "last_name", + "given_name", + "family_name", + "name" + ] + }, + "name": "Name by field name", + "name_zh": "带字段名的姓名", + "on_by_default": false + }, + { + "about": "The value of a field named `住址`, `家庭住址`, `收货地址`, `联系地址`, `home_address`, `shipping_address`, `residential_address` or `address`, written as `name: value`, `\"name\": \"value\"` or under a table header; only the value is replaced", + "about_zh": "字段名为 `住址`、`家庭住址`、`收货地址`、`联系地址`、`home_address`、`shipping_address`、`residential_address`、`address` 的值,包括 `字段名: 值`、`\"字段名\": \"值\"` 与表格表头的写法;只换值", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": false, + "id": "labeled-address", + "kind": "labeled-personal", + "label": "ADDRESS", + "matcher": { + "kind": "labeled", + "names": [ + "住址", + "家庭住址", + "收货地址", + "联系地址", + "home_address", + "shipping_address", + "residential_address", + "address" + ] + }, + "name": "Address by field name", + "name_zh": "带字段名的住址", + "on_by_default": false + }, + { + "about": "The value of a field named `出生日期`, `生日`, `birthday`, `birth_date`, `date_of_birth` or `dob`, written as `name: value`, `\"name\": \"value\"` or under a table header; only the value is replaced", + "about_zh": "字段名为 `出生日期`、`生日`、`birthday`、`birth_date`、`date_of_birth`、`dob` 的值,包括 `字段名: 值`、`\"字段名\": \"值\"` 与表格表头的写法;只换值", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": false, + "id": "labeled-birth-date", + "kind": "labeled-personal", + "label": "BIRTH_DATE", + "matcher": { + "kind": "labeled", + "names": [ + "出生日期", + "生日", + "birthday", + "birth_date", + "date_of_birth", + "dob" + ] + }, + "name": "Birth date by field name", + "name_zh": "带字段名的出生日期", + "on_by_default": false + }, + { + "about": "The value of a field named `银行账号`, `开户账号`, `银行卡号`, `account_number`, `bank_account` or `bank_account_number`, written as `name: value`, `\"name\": \"value\"` or under a table header; only the value is replaced", + "about_zh": "字段名为 `银行账号`、`开户账号`、`银行卡号`、`account_number`、`bank_account`、`bank_account_number` 的值,包括 `字段名: 值`、`\"字段名\": \"值\"` 与表格表头的写法;只换值", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": false, + "id": "labeled-account", + "kind": "labeled-personal", + "label": "ACCOUNT_NUMBER", + "matcher": { + "kind": "labeled", + "names": [ + "银行账号", + "开户账号", + "银行卡号", + "account_number", + "bank_account", + "bank_account_number" + ] + }, + "name": "Bank account by field name", + "name_zh": "带字段名的银行账号", + "on_by_default": false + }, + { + "about": "The value of a field named `病历号`, `住院号`, `诊断`, `病史`, `medical_record_number` or `diagnosis`, written as `name: value`, `\"name\": \"value\"` or under a table header; only the value is replaced", + "about_zh": "字段名为 `病历号`、`住院号`、`诊断`、`病史`、`medical_record_number`、`diagnosis` 的值,包括 `字段名: 值`、`\"字段名\": \"值\"` 与表格表头的写法;只换值", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": false, + "id": "labeled-health", + "kind": "labeled-personal", + "label": "HEALTH", + "matcher": { + "kind": "labeled", + "names": [ + "病历号", + "住院号", + "诊断", + "病史", + "medical_record_number", + "diagnosis" + ] + }, + "name": "Health information by field name", + "name_zh": "带字段名的健康信息", "on_by_default": false }, { + "about": "Addresses starting with `10.`, `172.16–31.`, `192.168.` or `100.64–127.`, and IPv6 addresses in `fc00::/7`; not 127.0.0.1", + "about_zh": "`10.`、`172.16–31.`、`192.168.`、`100.64–127.` 开头的地址与 `fc00::/7` 内的 IPv6 地址,不含 127.0.0.1", "action": "placeholder", "custom": false, "default_action": "placeholder", @@ -1258,9 +3634,12 @@ "kind": "private-ip" }, "name": "Internal IP address", + "name_zh": "内网地址", "on_by_default": false }, { + "about": "Domains ending in `.local`, `.internal`, `.lan`, `.corp`, `.home.arpa` or `.intranet`", + "about_zh": "以 `.local`、`.internal`、`.lan`、`.corp`、`.home.arpa`、`.intranet` 结尾的域名", "action": "placeholder", "custom": false, "default_action": "placeholder", @@ -1273,10 +3652,14 @@ "suffixes": [ ".local", ".internal", - ".lan" + ".lan", + ".corp", + ".home.arpa", + ".intranet" ] }, "name": "Internal domain", + "name_zh": "内部域名", "on_by_default": false }, { diff --git a/scripts/shots/core/en/status.json b/scripts/shots/core/en/status.json index c1adadb0..2e9c7bab 100644 --- a/scripts/shots/core/en/status.json +++ b/scripts/shots/core/en/status.json @@ -1,5 +1,5 @@ { - "api_version": 55, + "api_version": 56, "clients": 4, "config_path": "", "draining": 0, @@ -16,5 +16,5 @@ "reachable": [] }, "uptime_secs": 0, - "version": "0.84.0" + "version": "0.85.0" } diff --git a/scripts/shots/core/zh/security.json b/scripts/shots/core/zh/security.json index ed58177c..55214944 100644 --- a/scripts/shots/core/zh/security.json +++ b/scripts/shots/core/zh/security.json @@ -1,5 +1,32 @@ { "content": { + "kinds": [ + { + "name": "Hidden characters", + "name_zh": "隐藏字符", + "slug": "invisible" + }, + { + "name": "Instruction override", + "name_zh": "指令覆盖", + "slug": "injection" + }, + { + "name": "Identity and prompts", + "name_zh": "身份与提示词", + "slug": "persona" + }, + { + "name": "Chinese phrasing", + "name_zh": "中文说法", + "slug": "chinese" + }, + { + "name": "Custom", + "name_zh": "自定义", + "slug": "custom" + } + ], "mode": "observe", "rules": [ { @@ -16,12 +43,14 @@ ] }, "name": "Unicode tag characters", + "name_zh": "Unicode 标签字符", "on_by_default": true, "replacement": "***", "scope": [ "request" ], - "why": "Entirely invisible in an editor, yet carried into the model's context as they are, so they can hide a whole instruction." + "why": "Entirely invisible in an editor, yet carried into the model's context as they are, so they can hide a whole instruction.", + "why_zh": "在编辑器中完全不可见,但会原样进入模型上下文,可用于隐藏整段指令。" }, { "action": "strip", @@ -38,12 +67,14 @@ ] }, "name": "Bidirectional controls", + "name_zh": "双向控制符", "on_by_default": true, "replacement": "***", "scope": [ "request" ], - "why": "They can make the order shown on screen differ from the actual order of the characters." + "why": "They can make the order shown on screen differ from the actual order of the characters.", + "why_zh": "可使屏幕上的显示顺序与实际字符顺序不一致。" }, { "action": "strip", @@ -61,12 +92,14 @@ ] }, "name": "Zero-width characters", + "name_zh": "零宽字符", "on_by_default": false, "replacement": "***", "scope": [ "request" ], - "why": "Invisible in an editor, and read by the model. Emoji, Persian and other ordinary writing use them too." + "why": "Invisible in an editor, and read by the model. Emoji, Persian and other ordinary writing use them too.", + "why_zh": "在编辑器中不可见,但会被模型读到。表情符号、波斯文等正常文字也会用到。" }, { "action": "strip", @@ -84,12 +117,14 @@ ] }, "name": "Private-use characters", + "name_zh": "私用区字符", "on_by_default": false, "replacement": "***", "scope": [ "request" ], - "why": "They have no standard meaning. Some icon fonts use them." + "why": "They have no standard meaning. Some icon fonts use them.", + "why_zh": "没有标准含义。部分图标字体会用到。" }, { "action": "block", @@ -103,6 +138,7 @@ "text": "ignore previous instructions" }, "name": "Ignore previous instructions", + "name_zh": "要求忽略先前的指令", "on_by_default": true, "replacement": "***", "scope": [ @@ -121,6 +157,7 @@ "text": "ignore all previous" }, "name": "Ignore all previous", + "name_zh": "要求忽略之前的全部内容", "on_by_default": true, "replacement": "***", "scope": [ @@ -139,6 +176,7 @@ "text": "disregard your instructions" }, "name": "Disregard your instructions", + "name_zh": "要求无视指令", "on_by_default": true, "replacement": "***", "scope": [ @@ -157,6 +195,7 @@ "text": "jailbreak" }, "name": "Jailbreak", + "name_zh": "越狱", "on_by_default": false, "replacement": "***", "scope": [ @@ -175,6 +214,7 @@ "text": " dan " }, "name": "DAN", + "name_zh": "DAN", "on_by_default": false, "replacement": "***", "scope": [ @@ -193,6 +233,7 @@ "text": "developer mode" }, "name": "Developer mode", + "name_zh": "开发者模式", "on_by_default": false, "replacement": "***", "scope": [ @@ -211,6 +252,7 @@ "text": "you are now" }, "name": "Persona manipulation", + "name_zh": "重新设定身份", "on_by_default": false, "replacement": "***", "scope": [ @@ -229,6 +271,7 @@ "text": "new persona" }, "name": "New persona", + "name_zh": "新的人设", "on_by_default": false, "replacement": "***", "scope": [ @@ -247,6 +290,7 @@ "text": "act as" }, "name": "Act as", + "name_zh": "要求扮演角色", "on_by_default": false, "replacement": "***", "scope": [ @@ -265,6 +309,7 @@ "text": "pretend to be" }, "name": "Pretend to be", + "name_zh": "要求假装身份", "on_by_default": false, "replacement": "***", "scope": [ @@ -283,6 +328,7 @@ "text": "system prompt" }, "name": "System prompt extraction", + "name_zh": "索取系统提示词", "on_by_default": false, "replacement": "***", "scope": [ @@ -301,6 +347,7 @@ "text": "reveal your instructions" }, "name": "Reveal instructions", + "name_zh": "要求透露指令", "on_by_default": false, "replacement": "***", "scope": [ @@ -319,6 +366,7 @@ "text": "what are your rules" }, "name": "What are your rules", + "name_zh": "询问规则", "on_by_default": false, "replacement": "***", "scope": [ @@ -337,6 +385,7 @@ "pattern": "[A-Za-z0-9+/=]{50,}" }, "name": "Base64 smuggling", + "name_zh": "长串 Base64", "on_by_default": false, "replacement": "***", "scope": [ @@ -355,6 +404,7 @@ "text": "忽略之前" }, "name": "Ignore previous instructions (Chinese)", + "name_zh": "要求忽略先前的指令(中文)", "on_by_default": false, "replacement": "***", "scope": [ @@ -373,6 +423,7 @@ "text": "忘记你" }, "name": "Forget your instructions (Chinese)", + "name_zh": "要求忘记指令(中文)", "on_by_default": false, "replacement": "***", "scope": [ @@ -391,6 +442,7 @@ "text": "不要遵循" }, "name": "Do not follow (Chinese)", + "name_zh": "要求不要遵循(中文)", "on_by_default": false, "replacement": "***", "scope": [ @@ -409,6 +461,7 @@ "text": "现在你是" }, "name": "You are now (Chinese)", + "name_zh": "重新设定身份(中文)", "on_by_default": false, "replacement": "***", "scope": [ @@ -427,6 +480,7 @@ "text": "扮演" }, "name": "Role-play (Chinese)", + "name_zh": "要求扮演角色(中文)", "on_by_default": false, "replacement": "***", "scope": [ @@ -445,6 +499,7 @@ "text": "透露你的" }, "name": "Reveal your instructions (Chinese)", + "name_zh": "要求透露指令(中文)", "on_by_default": false, "replacement": "***", "scope": [ @@ -463,6 +518,7 @@ "text": "系统提示词" }, "name": "System prompt (Chinese)", + "name_zh": "系统提示词(中文)", "on_by_default": false, "replacement": "***", "scope": [ @@ -481,6 +537,7 @@ "text": "越狱" }, "name": "Jailbreak (Chinese)", + "name_zh": "越狱(中文)", "on_by_default": false, "replacement": "***", "scope": [ @@ -490,6 +547,18 @@ ] }, "inspect_tools": { + "kinds": [ + { + "name": "Built-in", + "name_zh": "内置", + "slug": "command" + }, + { + "name": "Custom", + "name_zh": "自定义", + "slug": "custom" + } + ], "mode": "enforce", "rules": [ { @@ -504,8 +573,10 @@ "pattern": "(?i)(curl|wget)[^\\n|]*\\|\\s*(sudo\\s+)?(ba|z|d)?sh" }, "name": "Download and run", + "name_zh": "下载即执行", "on_by_default": true, - "why": "Downloads and runs it straight away; what runs is decided remotely and cannot be read first" + "why": "Downloads and runs it straight away; what runs is decided remotely and cannot be read first", + "why_zh": "下载后直接执行,执行的内容由远端决定且无法预先查看" }, { "action": "cut", @@ -519,8 +590,10 @@ "pattern": "(?i)base64\\s+(-d|--decode)[^\\n|]*\\|\\s*(ba)?sh" }, "name": "Decode and run", + "name_zh": "解码后执行", "on_by_default": true, - "why": "Hides what will run inside base64" + "why": "Hides what will run inside base64", + "why_zh": "将要执行的内容隐藏在 base64 编码中" }, { "action": "cut", @@ -534,8 +607,10 @@ "pattern": "(?i)(env|printenv|cat\\s+[^\\n]{0,60}\\.env)[^\\n]{0,200}(curl|wget|nc\\s)" }, "name": "Send out environment variables", + "name_zh": "外发环境变量", "on_by_default": true, - "why": "Sends the environment, which usually holds keys, somewhere else" + "why": "Sends the environment, which usually holds keys, somewhere else", + "why_zh": "将环境变量(通常包含密钥)发送到外部" }, { "action": "cut", @@ -550,8 +625,10 @@ "pattern": "(\\.ssh/id_|\\.aws/credentials|\\.netrc|/\\.env)[^\\n]{0,30}(发|传|上报|打印|输出|贴出|读出|(?i:send|upload|post|print|paste|exfil))" }, "name": "Send out a credential file", + "name_zh": "外发凭据文件", "on_by_default": true, - "why": "Asks the model to send the contents of a credential file somewhere" + "why": "Asks the model to send the contents of a credential file somewhere", + "why_zh": "要求模型将凭据文件的内容发送出去" }, { "action": "cut", @@ -566,8 +643,10 @@ "pattern": "(发送|上传|贴出|打印|输出|读取|(?i:send|upload|post|print|read))[^\\n]{0,30}(\\.ssh/id_|\\.aws/credentials|\\.netrc|/\\.env)" }, "name": "Send out a credential file (verb first)", + "name_zh": "外发凭据文件(动词在前)", "on_by_default": true, - "why": "Asks the model to send the contents of a credential file somewhere" + "why": "Asks the model to send the contents of a credential file somewhere", + "why_zh": "要求模型将凭据文件的内容发送出去" }, { "action": "cut", @@ -581,8 +660,10 @@ "pattern": "(?i)(cat|cp|scp|curl)[^\\n]{0,200}(\\.ssh/id_|\\.aws/credentials|\\.netrc)" }, "name": "Read a private key or cloud credential", + "name_zh": "读取私钥或云凭据", "on_by_default": true, - "why": "Reads a private key or a cloud credential" + "why": "Reads a private key or a cloud credential", + "why_zh": "读取私钥或云服务凭据" }, { "action": "cut", @@ -596,8 +677,10 @@ "kind": "builtin" }, "name": "Send a credential to an unknown host", + "name_zh": "凭据发往陌生主机", "on_by_default": true, - "why": "Sends a credential to a host that is neither local nor the credential's own provider" + "why": "Sends a credential to a host that is neither local nor the credential's own provider", + "why_zh": "将凭据发往既非本机、也不是该凭据服务商的主机" }, { "action": "cut", @@ -611,8 +694,10 @@ "kind": "builtin" }, "name": "Read or change ThinkWatch's own data", + "name_zh": "读写 ThinkWatch 自己的数据", "on_by_default": true, - "why": "Reads or changes ThinkWatch's data directory, which holds every upstream key in plain text and the settings of these protections" + "why": "Reads or changes ThinkWatch's data directory, which holds every upstream key in plain text and the settings of these protections", + "why_zh": "读写 ThinkWatch 的数据目录,其中以明文保存全部上游密钥和这几项防护的设置" }, { "action": "cut", @@ -627,8 +712,10 @@ "pattern": "(>>?|tee|cp|mv|ln\\s+-s)[^\\n]{0,200}(\\.zshrc|\\.bashrc|\\.bash_profile|\\.profile|\\.zprofile|LaunchAgents|LaunchDaemons|\\.git/hooks|crontab)" }, "name": "Write a startup item", + "name_zh": "写入启动项", "on_by_default": true, - "why": "Writes somewhere that runs at login or whenever a terminal opens" + "why": "Writes somewhere that runs at login or whenever a terminal opens", + "why_zh": "写入开机或打开终端时自动执行的位置" }, { "action": "cut", @@ -643,8 +730,10 @@ "pattern": "crontab\\s+(-r\\b|-\\s*($|[\"';&|)])|[^\\s-][^\\n]{0,40})" }, "name": "Install a scheduled job", + "name_zh": "安装定时任务", "on_by_default": true, - "why": "Installs a scheduled job, or deletes every existing one" + "why": "Installs a scheduled job, or deletes every existing one", + "why_zh": "安装定时任务,或删除全部现有定时任务" }, { "action": "record", @@ -659,8 +748,10 @@ "pattern": "rm\\s+(-[a-zA-Z]*\\s+)*-[a-zA-Z]*[rf][a-zA-Z]*\\s+(/|~|\\$HOME)(\\s|$|[\"';&|)])" }, "name": "Delete home or root", + "name_zh": "删除主目录或根目录", "on_by_default": true, - "why": "Deletes the whole home directory or the root" + "why": "Deletes the whole home directory or the root", + "why_zh": "删除整个主目录或根目录" }, { "action": "record", @@ -675,8 +766,10 @@ "pattern": "chmod\\s+(-R\\s+)?777" }, "name": "World-writable permissions", + "name_zh": "开放全部写权限", "on_by_default": true, - "why": "Makes a file writable by everyone" + "why": "Makes a file writable by everyone", + "why_zh": "将文件权限设为所有人可写" }, { "action": "record", @@ -690,21 +783,144 @@ "kind": "builtin" }, "name": "Upload a local file to an external host", + "name_zh": "上传本地文件到外部主机", "on_by_default": true, - "why": "Uploads the contents of a local file to an external host" + "why": "Uploads the contents of a local file to an external host", + "why_zh": "将本地文件的内容上传到外部主机" } ] }, "redact": { + "kinds": [ + { + "name": "Saved credentials", + "name_zh": "保存的密钥", + "slug": "saved" + }, + { + "name": "AI services", + "name_zh": "AI 服务", + "slug": "ai-services" + }, + { + "name": "Code hosting and publishing", + "name_zh": "代码托管与软件发布", + "slug": "dev-platforms" + }, + { + "name": "Cloud and infrastructure", + "name_zh": "云服务与基础设施", + "slug": "cloud" + }, + { + "name": "Payments, messaging and collaboration", + "name_zh": "支付、通信与协作", + "slug": "business" + }, + { + "name": "Private keys", + "name_zh": "私钥", + "slug": "private-keys" + }, + { + "name": "JWTs", + "name_zh": "JWT", + "slug": "jwt" + }, + { + "name": "Connection strings", + "name_zh": "连接串", + "slug": "conn-strings" + }, + { + "name": "Credentials by position", + "name_zh": "固定位置的凭据", + "slug": "in-context" + }, + { + "name": "Cryptocurrency", + "name_zh": "加密货币", + "slug": "crypto" + }, + { + "name": "ID documents", + "name_zh": "证件号码", + "slug": "id-documents" + }, + { + "name": "Foreign ID numbers", + "name_zh": "境外证件号码", + "slug": "foreign-ids" + }, + { + "name": "Financial accounts", + "name_zh": "金融账户", + "slug": "financial" + }, + { + "name": "Contact details", + "name_zh": "联系方式", + "slug": "contact" + }, + { + "name": "Addresses and locations", + "name_zh": "住址与位置", + "slug": "location" + }, + { + "name": "Vehicles", + "name_zh": "车辆", + "slug": "vehicle" + }, + { + "name": "Device and network identifiers", + "name_zh": "设备与网络标识", + "slug": "device" + }, + { + "name": "Personal information by field name", + "name_zh": "带字段名的个人信息", + "slug": "labeled-personal" + }, + { + "name": "Internal addresses", + "name_zh": "内网地址", + "slug": "internal" + }, + { + "name": "Custom", + "name_zh": "自定义", + "slug": "custom" + } + ], "mode": "enforce", "rules": [ { + "about": "Recognized by value, whatever the format: credentials saved in ThinkWatch, namely upstream keys and header values, gateway keys, proxy passwords, the remote control key, and sync and backup credentials. Values shorter than 12 characters are not matched", + "about_zh": "按值识别 ThinkWatch 中保存的密钥,与格式无关:上游密钥与请求头中的值、网关密钥、代理密码、远程控制密钥、同步与备份凭据。不足 12 个字符的值不识别", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "thinkwatch-saved-secret", + "kind": "saved", + "label": "SECRET", + "matcher": { + "kind": "saved" + }, + "name": "Credentials saved in ThinkWatch", + "name_zh": "ThinkWatch 中保存的密钥", + "on_by_default": true + }, + { + "about": "Starts with `sk-ant-`, followed by at least 20 characters", + "about_zh": "`sk-ant-` 开头,其后至少 20 个字符", "action": "placeholder", "custom": false, "default_action": "placeholder", "enabled": true, "id": "anthropic-api-key", - "kind": "api-keys", + "kind": "ai-services", "label": "SECRET", "matcher": { "kind": "prefix", @@ -712,15 +928,18 @@ "prefix": "sk-ant-" }, "name": "Anthropic API key", + "name_zh": "Anthropic API 密钥", "on_by_default": true }, { + "about": "Starts with `sk-proj-`, followed by at least 20 characters", + "about_zh": "`sk-proj-` 开头,其后至少 20 个字符", "action": "placeholder", "custom": false, "default_action": "placeholder", "enabled": true, "id": "openai-project-key", - "kind": "api-keys", + "kind": "ai-services", "label": "SECRET", "matcher": { "kind": "prefix", @@ -728,383 +947,2217 @@ "prefix": "sk-proj-" }, "name": "OpenAI project key", + "name_zh": "OpenAI 项目密钥", "on_by_default": true }, { + "about": "Starts with `sk-`, at least 40 characters, with both letters and digits", + "about_zh": "`sk-` 开头,全长至少 40 个字符,同时含字母和数字", "action": "placeholder", "custom": false, "default_action": "placeholder", "enabled": true, "id": "openai-api-key", - "kind": "api-keys", + "kind": "ai-services", "label": "SECRET", "matcher": { "kind": "openai-legacy", "min_len": 40 }, "name": "OpenAI API key", + "name_zh": "OpenAI API 密钥", "on_by_default": true }, { + "about": "Starts with `sk-svcacct-` or `sk-admin-`, followed by at least 58 characters", + "about_zh": "`sk-svcacct-`、`sk-admin-` 开头,其后至少 58 个字符", "action": "placeholder", "custom": false, "default_action": "placeholder", "enabled": true, - "id": "github-personal-token", - "kind": "api-keys", + "id": "openai-service-key", + "kind": "ai-services", "label": "SECRET", "matcher": { - "kind": "prefix", - "min_tail": 30, - "prefix": "ghp_" + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 58, + "prefix": "sk-svcacct-", + "tail": "any" + }, + { + "min_tail": 58, + "prefix": "sk-admin-", + "tail": "any" + } + ] }, - "name": "GitHub personal access token", + "name": "OpenAI service account or admin key", + "name_zh": "OpenAI 服务账号与管理密钥", "on_by_default": true }, { + "about": "Starts with `sk-or-v1-`, followed by at least 64 hex characters", + "about_zh": "`sk-or-v1-` 开头,其后至少 64 个十六进制字符", "action": "placeholder", "custom": false, "default_action": "placeholder", "enabled": true, - "id": "github-oauth-token", - "kind": "api-keys", + "id": "openrouter-key", + "kind": "ai-services", "label": "SECRET", "matcher": { - "kind": "prefix", - "min_tail": 30, - "prefix": "gho_" + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 64, + "prefix": "sk-or-v1-", + "tail": "hex" + } + ] }, - "name": "GitHub OAuth token", + "name": "OpenRouter API key", + "name_zh": "OpenRouter API 密钥", "on_by_default": true }, { + "about": "Starts with `xai-`, followed by at least 70 letters or digits", + "about_zh": "`xai-` 开头,其后至少 70 个字母或数字", "action": "placeholder", "custom": false, "default_action": "placeholder", "enabled": true, - "id": "github-server-token", - "kind": "api-keys", + "id": "xai-key", + "kind": "ai-services", "label": "SECRET", "matcher": { - "kind": "prefix", - "min_tail": 30, - "prefix": "ghs_" + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 70, + "prefix": "xai-", + "tail": "alnum" + } + ] }, - "name": "GitHub server token", + "name": "xAI API key", + "name_zh": "xAI API 密钥", "on_by_default": true }, { + "about": "Starts with `gsk_`, followed by at least 52 letters or digits", + "about_zh": "`gsk_` 开头,其后至少 52 个字母或数字", "action": "placeholder", "custom": false, "default_action": "placeholder", "enabled": true, - "id": "github-user-token", - "kind": "api-keys", + "id": "groq-key", + "kind": "ai-services", "label": "SECRET", "matcher": { - "kind": "prefix", - "min_tail": 30, - "prefix": "ghu_" + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 52, + "prefix": "gsk_", + "tail": "alnum" + } + ] }, - "name": "GitHub user token", + "name": "Groq API key", + "name_zh": "Groq API 密钥", "on_by_default": true }, { + "about": "Starts with `hf_` or `api_org_`, followed by at least 34 letters or digits", + "about_zh": "`hf_`、`api_org_` 开头,其后至少 34 个字母或数字", "action": "placeholder", "custom": false, "default_action": "placeholder", "enabled": true, - "id": "github-fine-grained-token", - "kind": "api-keys", + "id": "huggingface-token", + "kind": "ai-services", "label": "SECRET", "matcher": { - "kind": "prefix", - "min_tail": 30, - "prefix": "github_pat_" + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 34, + "prefix": "hf_", + "tail": "alnum" + }, + { + "min_tail": 34, + "prefix": "api_org_", + "tail": "alnum" + } + ] }, - "name": "GitHub fine-grained token", + "name": "Hugging Face token", + "name_zh": "Hugging Face 令牌", "on_by_default": true }, { + "about": "Starts with `r8_`, followed by at least 37 letters or digits", + "about_zh": "`r8_` 开头,其后至少 37 个字母或数字", "action": "placeholder", "custom": false, "default_action": "placeholder", "enabled": true, - "id": "slack-bot-token", - "kind": "api-keys", + "id": "replicate-token", + "kind": "ai-services", "label": "SECRET", "matcher": { - "kind": "prefix", - "min_tail": 20, - "prefix": "xoxb-" + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 37, + "prefix": "r8_", + "tail": "alnum" + } + ] }, - "name": "Slack bot token", + "name": "Replicate token", + "name_zh": "Replicate 令牌", "on_by_default": true }, { + "about": "Starts with `pplx-`, followed by at least 48 letters or digits", + "about_zh": "`pplx-` 开头,其后至少 48 个字母或数字", "action": "placeholder", "custom": false, "default_action": "placeholder", "enabled": true, - "id": "slack-user-token", - "kind": "api-keys", + "id": "perplexity-key", + "kind": "ai-services", "label": "SECRET", "matcher": { - "kind": "prefix", - "min_tail": 20, - "prefix": "xoxp-" + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 48, + "prefix": "pplx-", + "tail": "alnum" + } + ] }, - "name": "Slack user token", + "name": "Perplexity API key", + "name_zh": "Perplexity API 密钥", "on_by_default": true }, { + "about": "Starts with `ABSK`, followed by at least 100 characters", + "about_zh": "`ABSK` 开头,其后至少 100 个字符", "action": "placeholder", "custom": false, "default_action": "placeholder", "enabled": true, - "id": "slack-app-token", - "kind": "api-keys", + "id": "bedrock-api-key", + "kind": "ai-services", "label": "SECRET", "matcher": { - "kind": "prefix", - "min_tail": 20, - "prefix": "xoxa-" + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 100, + "prefix": "ABSK", + "tail": "any" + } + ] }, - "name": "Slack app token", + "name": "Amazon Bedrock API key", + "name_zh": "Amazon Bedrock API 密钥", "on_by_default": true }, { + "about": "Starts with `fw_`, followed by at least 20 letters or digits", + "about_zh": "`fw_` 开头,其后至少 20 个字母或数字", "action": "placeholder", "custom": false, "default_action": "placeholder", "enabled": true, - "id": "aws-access-key-id", - "kind": "api-keys", + "id": "fireworks-key", + "kind": "ai-services", "label": "SECRET", "matcher": { - "kind": "prefix", - "min_tail": 12, - "prefix": "AKIA" + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 20, + "prefix": "fw_", + "tail": "alnum" + } + ] }, - "name": "AWS access key ID", + "name": "Fireworks AI API key", + "name_zh": "Fireworks AI API 密钥", "on_by_default": true }, { + "about": "Starts with `csk-`, followed by at least 48 letters or digits", + "about_zh": "`csk-` 开头,其后至少 48 个字母或数字", "action": "placeholder", "custom": false, "default_action": "placeholder", "enabled": true, - "id": "aws-temporary-key-id", - "kind": "api-keys", + "id": "cerebras-key", + "kind": "ai-services", "label": "SECRET", "matcher": { - "kind": "prefix", - "min_tail": 12, - "prefix": "ASIA" + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 48, + "prefix": "csk-", + "tail": "alnum" + } + ] }, - "name": "AWS temporary access key ID", + "name": "Cerebras API key", + "name_zh": "Cerebras API 密钥", "on_by_default": true }, { + "about": "Starts with `lsv2_pt_` or `lsv2_sk_`, followed by at least 43 characters", + "about_zh": "`lsv2_pt_`、`lsv2_sk_` 开头,其后至少 43 个字符", "action": "placeholder", "custom": false, "default_action": "placeholder", "enabled": true, - "id": "google-api-key", - "kind": "api-keys", + "id": "langsmith-key", + "kind": "ai-services", "label": "SECRET", "matcher": { - "kind": "prefix", - "min_tail": 30, - "prefix": "AIza" + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 43, + "prefix": "lsv2_pt_", + "tail": "any" + }, + { + "min_tail": 43, + "prefix": "lsv2_sk_", + "tail": "any" + } + ] }, - "name": "Google API key", + "name": "LangSmith API key", + "name_zh": "LangSmith API 密钥", "on_by_default": true }, { + "about": "Starts with `sk-lf-`, followed by at least 36 characters", + "about_zh": "`sk-lf-` 开头,其后至少 36 个字符", "action": "placeholder", "custom": false, "default_action": "placeholder", "enabled": true, - "id": "google-oauth-token", - "kind": "api-keys", + "id": "langfuse-secret-key", + "kind": "ai-services", "label": "SECRET", "matcher": { - "kind": "prefix", - "min_tail": 20, - "prefix": "ya29." + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 36, + "prefix": "sk-lf-", + "tail": "any" + } + ] }, - "name": "Google OAuth token", + "name": "Langfuse secret key", + "name_zh": "Langfuse 私密密钥", "on_by_default": true }, { + "about": "Starts with `pcsk_`, followed by at least 69 characters", + "about_zh": "`pcsk_` 开头,其后至少 69 个字符", "action": "placeholder", "custom": false, "default_action": "placeholder", "enabled": true, - "id": "gitlab-token", - "kind": "api-keys", + "id": "pinecone-key", + "kind": "ai-services", "label": "SECRET", "matcher": { - "kind": "prefix", - "min_tail": 15, - "prefix": "glpat-" + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 69, + "prefix": "pcsk_", + "tail": "any" + } + ] }, - "name": "GitLab token", + "name": "Pinecone API key", + "name_zh": "Pinecone API 密钥", "on_by_default": true }, { + "about": "Starts with `tvly-`, followed by at least 32 characters", + "about_zh": "`tvly-` 开头,其后至少 32 个字符", "action": "placeholder", "custom": false, "default_action": "placeholder", "enabled": true, - "id": "stripe-live-key", - "kind": "api-keys", + "id": "tavily-key", + "kind": "ai-services", "label": "SECRET", "matcher": { - "kind": "prefix", - "min_tail": 20, - "prefix": "sk_live_" + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 32, + "prefix": "tvly-", + "tail": "any" + } + ] }, - "name": "Stripe live key", + "name": "Tavily API key", + "name_zh": "Tavily API 密钥", "on_by_default": true }, { + "about": "Starts with `sk-api-`, followed by at least 100 characters", + "about_zh": "`sk-api-` 开头,其后至少 100 个字符", "action": "placeholder", "custom": false, "default_action": "placeholder", "enabled": true, - "id": "stripe-restricted-key", - "kind": "api-keys", + "id": "minimax-key", + "kind": "ai-services", "label": "SECRET", "matcher": { - "kind": "prefix", - "min_tail": 20, - "prefix": "rk_live_" + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 100, + "prefix": "sk-api-", + "tail": "any" + } + ] }, - "name": "Stripe restricted key", + "name": "MiniMax API key", + "name_zh": "MiniMax API 密钥", "on_by_default": true }, { + "about": "32 hex characters, `.` and 16 letters or digits, run together", + "about_zh": "32 个十六进制字符、`.` 与 16 个字母或数字相连", "action": "placeholder", "custom": false, "default_action": "placeholder", "enabled": true, - "id": "npm-token", - "kind": "api-keys", + "id": "zhipu-api-key", + "kind": "ai-services", "label": "SECRET", "matcher": { - "kind": "prefix", - "min_tail": 30, - "prefix": "npm_" + "check": "zhipu-api-key", + "kind": "builtin" }, - "name": "npm token", + "name": "Zhipu API key", + "name_zh": "智谱 API 密钥", "on_by_default": true }, { + "about": "Starts with `bce-v3/ALTAK-`, followed by 21 letters or digits, `/` and 40 hex characters, as a whole", + "about_zh": "`bce-v3/ALTAK-` 开头,其后为 21 个字母或数字、`/` 与 40 个十六进制字符,整段", "action": "placeholder", "custom": false, "default_action": "placeholder", "enabled": true, - "id": "digitalocean-token", - "kind": "api-keys", + "id": "qianfan-api-key", + "kind": "ai-services", "label": "SECRET", "matcher": { - "kind": "prefix", - "min_tail": 30, - "prefix": "dop_v1_" + "check": "qianfan-api-key", + "kind": "builtin" }, - "name": "DigitalOcean token", + "name": "Baidu Qianfan API key", + "name_zh": "百度千帆 API 密钥", "on_by_default": true }, { + "about": "Starts with `sk-`, followed by exactly 32 letters and digits (both present) or exactly 48 lowercase letters", + "about_zh": "`sk-` 开头,其后恰为 32 个字母和数字(两者都有),或恰为 48 个小写字母", "action": "placeholder", "custom": false, "default_action": "placeholder", "enabled": true, - "id": "sendgrid-key", - "kind": "api-keys", + "id": "sk-compat-key", + "kind": "ai-services", "label": "SECRET", "matcher": { - "kind": "prefix", - "min_tail": 30, - "prefix": "SG." + "check": "sk-compat-key", + "kind": "builtin" }, - "name": "SendGrid key", + "name": "OpenAI-compatible API key", + "name_zh": "OpenAI 兼容格式 API 密钥", "on_by_default": true }, { + "about": "Starts with `ghp_`, followed by at least 30 characters", + "about_zh": "`ghp_` 开头,其后至少 30 个字符", "action": "placeholder", "custom": false, "default_action": "placeholder", "enabled": true, - "id": "private-key", - "kind": "private-keys", + "id": "github-personal-token", + "kind": "dev-platforms", "label": "SECRET", "matcher": { - "kind": "pem" + "kind": "prefix", + "min_tail": 30, + "prefix": "ghp_" }, - "name": "Private key", + "name": "GitHub personal access token", + "name_zh": "GitHub 个人令牌", "on_by_default": true }, { + "about": "Starts with `gho_`, followed by at least 30 characters", + "about_zh": "`gho_` 开头,其后至少 30 个字符", "action": "placeholder", "custom": false, "default_action": "placeholder", "enabled": true, - "id": "jwt", - "kind": "jwt", + "id": "github-oauth-token", + "kind": "dev-platforms", "label": "SECRET", "matcher": { - "kind": "jwt" + "kind": "prefix", + "min_tail": 30, + "prefix": "gho_" }, - "name": "JWT", + "name": "GitHub OAuth token", + "name_zh": "GitHub OAuth 令牌", "on_by_default": true }, { + "about": "Starts with `ghs_`, followed by at least 30 characters", + "about_zh": "`ghs_` 开头,其后至少 30 个字符", "action": "placeholder", "custom": false, "default_action": "placeholder", "enabled": true, - "id": "conn-string-password", - "kind": "conn-strings", + "id": "github-server-token", + "kind": "dev-platforms", "label": "SECRET", "matcher": { - "kind": "conn-string" + "kind": "prefix", + "min_tail": 30, + "prefix": "ghs_" }, - "name": "Connection string password", + "name": "GitHub server token", + "name_zh": "GitHub 服务器令牌", "on_by_default": true }, { + "about": "Starts with `ghu_`, followed by at least 30 characters", + "about_zh": "`ghu_` 开头,其后至少 30 个字符", "action": "placeholder", "custom": false, "default_action": "placeholder", "enabled": true, - "id": "cn-resident-id", - "kind": "personal", - "label": "ID_NUMBER", + "id": "github-user-token", + "kind": "dev-platforms", + "label": "SECRET", "matcher": { - "born_since": 1900, - "kind": "cn-resident-id" + "kind": "prefix", + "min_tail": 30, + "prefix": "ghu_" }, - "name": "Chinese resident ID number", + "name": "GitHub user token", + "name_zh": "GitHub 用户令牌", "on_by_default": true }, { + "about": "Starts with `github_pat_`, followed by at least 30 characters", + "about_zh": "`github_pat_` 开头,其后至少 30 个字符", "action": "placeholder", "custom": false, "default_action": "placeholder", "enabled": true, - "id": "bank-card", - "kind": "personal", - "label": "CARD_NUMBER", + "id": "github-fine-grained-token", + "kind": "dev-platforms", + "label": "SECRET", "matcher": { - "kind": "bank-card", - "networks": [ - { - "lengths": [ - 16, - 17, + "kind": "prefix", + "min_tail": 30, + "prefix": "github_pat_" + }, + "name": "GitHub fine-grained token", + "name_zh": "GitHub 细粒度令牌", + "on_by_default": true + }, + { + "about": "Starts with `glpat-`, followed by at least 15 characters", + "about_zh": "`glpat-` 开头,其后至少 15 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "gitlab-token", + "kind": "dev-platforms", + "label": "SECRET", + "matcher": { + "kind": "prefix", + "min_tail": 15, + "prefix": "glpat-" + }, + "name": "GitLab token", + "name_zh": "GitLab 令牌", + "on_by_default": true + }, + { + "about": "Starts with `npm_`, followed by at least 30 characters", + "about_zh": "`npm_` 开头,其后至少 30 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "npm-token", + "kind": "dev-platforms", + "label": "SECRET", + "matcher": { + "kind": "prefix", + "min_tail": 30, + "prefix": "npm_" + }, + "name": "npm token", + "name_zh": "npm 令牌", + "on_by_default": true + }, + { + "about": "Starts with `ghr_`, followed by at least 36 letters or digits", + "about_zh": "`ghr_` 开头,其后至少 36 个字母或数字", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "github-refresh-token", + "kind": "dev-platforms", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 36, + "prefix": "ghr_", + "tail": "alnum" + } + ] + }, + "name": "GitHub refresh token", + "name_zh": "GitHub 刷新令牌", + "on_by_default": true + }, + { + "about": "Starts with `glptt-`, `gldt-`, `glrt-`, `gloas-`, `glcbt-`, `glft-`, `glsoat-`, `glimt-` or `glagent-`, followed by at least 20 characters", + "about_zh": "`glptt-`、`gldt-`、`glrt-`、`gloas-`、`glcbt-`、`glft-`、`glsoat-`、`glimt-`、`glagent-` 开头,其后至少 20 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "gitlab-other-token", + "kind": "dev-platforms", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 20, + "prefix": "glptt-", + "tail": "any" + }, + { + "min_tail": 20, + "prefix": "gldt-", + "tail": "any" + }, + { + "min_tail": 20, + "prefix": "glrt-", + "tail": "any" + }, + { + "min_tail": 20, + "prefix": "gloas-", + "tail": "any" + }, + { + "min_tail": 20, + "prefix": "glcbt-", + "tail": "any" + }, + { + "min_tail": 20, + "prefix": "glft-", + "tail": "any" + }, + { + "min_tail": 20, + "prefix": "glsoat-", + "tail": "any" + }, + { + "min_tail": 20, + "prefix": "glimt-", + "tail": "any" + }, + { + "min_tail": 20, + "prefix": "glagent-", + "tail": "any" + } + ] + }, + "name": "Other GitLab token", + "name_zh": "GitLab 其他令牌", + "on_by_default": true + }, + { + "about": "Starts with `pypi-AgEIcHlwaS5vcmc`, followed by at least 50 characters", + "about_zh": "`pypi-AgEIcHlwaS5vcmc` 开头,其后至少 50 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "pypi-token", + "kind": "dev-platforms", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 50, + "prefix": "pypi-AgEIcHlwaS5vcmc", + "tail": "any" + } + ] + }, + "name": "PyPI token", + "name_zh": "PyPI 令牌", + "on_by_default": true + }, + { + "about": "Starts with `rubygems_`, followed by at least 48 hex characters", + "about_zh": "`rubygems_` 开头,其后至少 48 个十六进制字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "rubygems-token", + "kind": "dev-platforms", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 48, + "prefix": "rubygems_", + "tail": "hex" + } + ] + }, + "name": "RubyGems token", + "name_zh": "RubyGems 令牌", + "on_by_default": true + }, + { + "about": "Starts with `CLOJARS_`, followed by at least 60 letters or digits", + "about_zh": "`CLOJARS_` 开头,其后至少 60 个字母或数字", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "clojars-token", + "kind": "dev-platforms", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 60, + "prefix": "CLOJARS_", + "tail": "alnum" + } + ] + }, + "name": "Clojars token", + "name_zh": "Clojars 令牌", + "on_by_default": true + }, + { + "about": "Starts with `dckr_pat_`, followed by at least 27 characters; starts with `dckr_oat_`, followed by at least 32 characters", + "about_zh": "`dckr_pat_` 开头,其后至少 27 个字符;`dckr_oat_` 开头,其后至少 32 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "docker-hub-token", + "kind": "dev-platforms", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 27, + "prefix": "dckr_pat_", + "tail": "any" + }, + { + "min_tail": 32, + "prefix": "dckr_oat_", + "tail": "any" + } + ] + }, + "name": "Docker Hub token", + "name_zh": "Docker Hub 令牌", + "on_by_default": true + }, + { + "about": "Starts with `AKCp`, followed by at least 69 letters or digits", + "about_zh": "`AKCp` 开头,其后至少 69 个字母或数字", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "artifactory-key", + "kind": "dev-platforms", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 69, + "prefix": "AKCp", + "tail": "alnum" + } + ] + }, + "name": "JFrog Artifactory key", + "name_zh": "JFrog Artifactory 密钥", + "on_by_default": true + }, + { + "about": "Starts with `dapi`, followed by at least 32 hex characters", + "about_zh": "`dapi` 开头,其后至少 32 个十六进制字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "databricks-token", + "kind": "dev-platforms", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 32, + "prefix": "dapi", + "tail": "hex" + } + ] + }, + "name": "Databricks token", + "name_zh": "Databricks 令牌", + "on_by_default": true + }, + { + "about": "Starts with `sntrys_`, followed by at least 40 characters; starts with `sntryu_`, followed by at least 64 hex characters", + "about_zh": "`sntrys_` 开头,其后至少 40 个字符;`sntryu_` 开头,其后至少 64 个十六进制字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "sentry-token", + "kind": "dev-platforms", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 40, + "prefix": "sntrys_", + "tail": "any" + }, + { + "min_tail": 64, + "prefix": "sntryu_", + "tail": "hex" + } + ] + }, + "name": "Sentry token", + "name_zh": "Sentry 令牌", + "on_by_default": true + }, + { + "about": "Starts with `glsa_`, followed by at least 41 characters; starts with `glc_`, followed by at least 32 characters", + "about_zh": "`glsa_` 开头,其后至少 41 个字符;`glc_` 开头,其后至少 32 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "grafana-token", + "kind": "dev-platforms", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 41, + "prefix": "glsa_", + "tail": "any" + }, + { + "min_tail": 32, + "prefix": "glc_", + "tail": "any" + } + ] + }, + "name": "Grafana token", + "name_zh": "Grafana 令牌", + "on_by_default": true + }, + { + "about": "Starts with `PMAK-`, followed by at least 59 characters", + "about_zh": "`PMAK-` 开头,其后至少 59 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "postman-key", + "kind": "dev-platforms", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 59, + "prefix": "PMAK-", + "tail": "any" + } + ] + }, + "name": "Postman API key", + "name_zh": "Postman API 密钥", + "on_by_default": true + }, + { + "about": "Starts with `AKIA`, followed by at least 12 characters", + "about_zh": "`AKIA` 开头,其后至少 12 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "aws-access-key-id", + "kind": "cloud", + "label": "SECRET", + "matcher": { + "kind": "prefix", + "min_tail": 12, + "prefix": "AKIA" + }, + "name": "AWS access key ID", + "name_zh": "AWS 访问密钥 ID", + "on_by_default": true + }, + { + "about": "Starts with `ASIA`, followed by at least 12 characters", + "about_zh": "`ASIA` 开头,其后至少 12 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "aws-temporary-key-id", + "kind": "cloud", + "label": "SECRET", + "matcher": { + "kind": "prefix", + "min_tail": 12, + "prefix": "ASIA" + }, + "name": "AWS temporary access key ID", + "name_zh": "AWS 临时访问密钥 ID", + "on_by_default": true + }, + { + "about": "Starts with `AIza`, followed by at least 30 characters", + "about_zh": "`AIza` 开头,其后至少 30 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "google-api-key", + "kind": "cloud", + "label": "SECRET", + "matcher": { + "kind": "prefix", + "min_tail": 30, + "prefix": "AIza" + }, + "name": "Google API key", + "name_zh": "Google API 密钥", + "on_by_default": true + }, + { + "about": "Starts with `ya29.`, followed by at least 20 characters", + "about_zh": "`ya29.` 开头,其后至少 20 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "google-oauth-token", + "kind": "cloud", + "label": "SECRET", + "matcher": { + "kind": "prefix", + "min_tail": 20, + "prefix": "ya29." + }, + "name": "Google OAuth token", + "name_zh": "Google OAuth 令牌", + "on_by_default": true + }, + { + "about": "Starts with `dop_v1_`, followed by at least 30 characters", + "about_zh": "`dop_v1_` 开头,其后至少 30 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "digitalocean-token", + "kind": "cloud", + "label": "SECRET", + "matcher": { + "kind": "prefix", + "min_tail": 30, + "prefix": "dop_v1_" + }, + "name": "DigitalOcean token", + "name_zh": "DigitalOcean 令牌", + "on_by_default": true + }, + { + "about": "Starts with `ABIA` or `ACCA`, followed by at least 16 letters or digits", + "about_zh": "`ABIA`、`ACCA` 开头,其后至少 16 个字母或数字", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "aws-other-key-id", + "kind": "cloud", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 16, + "prefix": "ABIA", + "tail": "alnum" + }, + { + "min_tail": 16, + "prefix": "ACCA", + "tail": "alnum" + } + ] + }, + "name": "Other AWS access key ID", + "name_zh": "AWS 其他访问密钥 ID", + "on_by_default": true + }, + { + "about": "Starts with `LTAI`, followed by at least 12 letters or digits", + "about_zh": "`LTAI` 开头,其后至少 12 个字母或数字", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "alibaba-access-key-id", + "kind": "cloud", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 12, + "prefix": "LTAI", + "tail": "alnum" + } + ] + }, + "name": "Alibaba Cloud AccessKey ID", + "name_zh": "阿里云 AccessKey ID", + "on_by_default": true + }, + { + "about": "Starts with `AKID`, followed by at least 32 letters or digits", + "about_zh": "`AKID` 开头,其后至少 32 个字母或数字", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "tencent-secret-id", + "kind": "cloud", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 32, + "prefix": "AKID", + "tail": "alnum" + } + ] + }, + "name": "Tencent Cloud SecretId", + "name_zh": "腾讯云 SecretId", + "on_by_default": true + }, + { + "about": "Starts with `AKLT` or `AKTP`, followed by at least 40 characters", + "about_zh": "`AKLT`、`AKTP` 开头,其后至少 40 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "volcengine-access-key-id", + "kind": "cloud", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 40, + "prefix": "AKLT", + "tail": "any" + }, + { + "min_tail": 40, + "prefix": "AKTP", + "tail": "any" + } + ] + }, + "name": "Volcengine AccessKey ID", + "name_zh": "火山引擎 AccessKey ID", + "on_by_default": true + }, + { + "about": "Three characters, a digit and `Q~`, followed by 31 to 34 characters", + "about_zh": "3 个字符、1 位数字与 `Q~`,其后 31 至 34 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "azure-ad-client-secret", + "kind": "cloud", + "label": "SECRET", + "matcher": { + "check": "azure-ad-client-secret", + "kind": "builtin" + }, + "name": "Azure AD client secret", + "name_zh": "Azure AD 客户端密码", + "on_by_default": true + }, + { + "about": "Starts with `doo_v1_` or `dor_v1_`, followed by at least 64 hex characters", + "about_zh": "`doo_v1_`、`dor_v1_` 开头,其后至少 64 个十六进制字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "digitalocean-other-token", + "kind": "cloud", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 64, + "prefix": "doo_v1_", + "tail": "hex" + }, + { + "min_tail": 64, + "prefix": "dor_v1_", + "tail": "hex" + } + ] + }, + "name": "Other DigitalOcean token", + "name_zh": "DigitalOcean 其他令牌", + "on_by_default": true + }, + { + "about": "Starts with `v1.0-`, followed by at least 171 characters", + "about_zh": "`v1.0-` 开头,其后至少 171 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "cloudflare-origin-ca-key", + "kind": "cloud", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 171, + "prefix": "v1.0-", + "tail": "any" + } + ] + }, + "name": "Cloudflare Origin CA key", + "name_zh": "Cloudflare Origin CA 密钥", + "on_by_default": true + }, + { + "about": "Starts with `hvs.`, followed by at least 90 characters; starts with `hvb.`, followed by at least 138 characters", + "about_zh": "`hvs.` 开头,其后至少 90 个字符;`hvb.` 开头,其后至少 138 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "vault-token", + "kind": "cloud", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 90, + "prefix": "hvs.", + "tail": "any" + }, + { + "min_tail": 138, + "prefix": "hvb.", + "tail": "any" + } + ] + }, + "name": "HashiCorp Vault token", + "name_zh": "HashiCorp Vault 令牌", + "on_by_default": true + }, + { + "about": "14 letters or digits and `.atlasv1.`, followed by 60 to 70 characters", + "about_zh": "14 个字母或数字与 `.atlasv1.`,其后 60 至 70 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "terraform-token", + "kind": "cloud", + "label": "SECRET", + "matcher": { + "check": "terraform-token", + "kind": "builtin" + }, + "name": "Terraform Cloud token", + "name_zh": "Terraform Cloud 令牌", + "on_by_default": true + }, + { + "about": "Starts with `dp.pt.`, followed by at least 43 letters or digits", + "about_zh": "`dp.pt.` 开头,其后至少 43 个字母或数字", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "doppler-token", + "kind": "cloud", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 43, + "prefix": "dp.pt.", + "tail": "alnum" + } + ] + }, + "name": "Doppler token", + "name_zh": "Doppler 令牌", + "on_by_default": true + }, + { + "about": "Starts with `pul-`, followed by at least 40 hex characters", + "about_zh": "`pul-` 开头,其后至少 40 个十六进制字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "pulumi-token", + "kind": "cloud", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 40, + "prefix": "pul-", + "tail": "hex" + } + ] + }, + "name": "Pulumi token", + "name_zh": "Pulumi 令牌", + "on_by_default": true + }, + { + "about": "Starts with `HRKU-AA`, followed by at least 58 characters", + "about_zh": "`HRKU-AA` 开头,其后至少 58 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "heroku-key", + "kind": "cloud", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 58, + "prefix": "HRKU-AA", + "tail": "any" + } + ] + }, + "name": "Heroku API key", + "name_zh": "Heroku API 密钥", + "on_by_default": true + }, + { + "about": "Starts with `fo1_`, followed by at least 43 characters", + "about_zh": "`fo1_` 开头,其后至少 43 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "flyio-token", + "kind": "cloud", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 43, + "prefix": "fo1_", + "tail": "any" + } + ] + }, + "name": "Fly.io token", + "name_zh": "Fly.io 令牌", + "on_by_default": true + }, + { + "about": "Starts with `sbp_`, followed by at least 40 letters or digits; starts with `sb_secret_`, followed by at least 31 characters", + "about_zh": "`sbp_` 开头,其后至少 40 个字母或数字;`sb_secret_` 开头,其后至少 31 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "supabase-key", + "kind": "cloud", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 40, + "prefix": "sbp_", + "tail": "alnum" + }, + { + "min_tail": 31, + "prefix": "sb_secret_", + "tail": "any" + } + ] + }, + "name": "Supabase key", + "name_zh": "Supabase 密钥", + "on_by_default": true + }, + { + "about": "Starts with `AGE-SECRET-KEY-1`, followed by at least 58 letters or digits", + "about_zh": "`AGE-SECRET-KEY-1` 开头,其后至少 58 个字母或数字", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "age-secret-key", + "kind": "cloud", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 58, + "prefix": "AGE-SECRET-KEY-1", + "tail": "alnum" + } + ] + }, + "name": "age secret key", + "name_zh": "age 私钥", + "on_by_default": true + }, + { + "about": "Starts with `ops_eyJ`, followed by at least 200 characters", + "about_zh": "`ops_eyJ` 开头,其后至少 200 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "onepassword-token", + "kind": "cloud", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 200, + "prefix": "ops_eyJ", + "tail": "any" + } + ] + }, + "name": "1Password service account token", + "name_zh": "1Password 服务账号令牌", + "on_by_default": true + }, + { + "about": "Starts with `xoxb-`, followed by at least 20 characters", + "about_zh": "`xoxb-` 开头,其后至少 20 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "slack-bot-token", + "kind": "business", + "label": "SECRET", + "matcher": { + "kind": "prefix", + "min_tail": 20, + "prefix": "xoxb-" + }, + "name": "Slack bot token", + "name_zh": "Slack 机器人令牌", + "on_by_default": true + }, + { + "about": "Starts with `xoxp-`, followed by at least 20 characters", + "about_zh": "`xoxp-` 开头,其后至少 20 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "slack-user-token", + "kind": "business", + "label": "SECRET", + "matcher": { + "kind": "prefix", + "min_tail": 20, + "prefix": "xoxp-" + }, + "name": "Slack user token", + "name_zh": "Slack 用户令牌", + "on_by_default": true + }, + { + "about": "Starts with `xoxa-`, followed by at least 20 characters", + "about_zh": "`xoxa-` 开头,其后至少 20 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "slack-app-token", + "kind": "business", + "label": "SECRET", + "matcher": { + "kind": "prefix", + "min_tail": 20, + "prefix": "xoxa-" + }, + "name": "Slack app token", + "name_zh": "Slack 应用令牌", + "on_by_default": true + }, + { + "about": "Starts with `sk_live_`, followed by at least 20 characters", + "about_zh": "`sk_live_` 开头,其后至少 20 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "stripe-live-key", + "kind": "business", + "label": "SECRET", + "matcher": { + "kind": "prefix", + "min_tail": 20, + "prefix": "sk_live_" + }, + "name": "Stripe live key", + "name_zh": "Stripe 生产密钥", + "on_by_default": true + }, + { + "about": "Starts with `rk_live_`, followed by at least 20 characters", + "about_zh": "`rk_live_` 开头,其后至少 20 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "stripe-restricted-key", + "kind": "business", + "label": "SECRET", + "matcher": { + "kind": "prefix", + "min_tail": 20, + "prefix": "rk_live_" + }, + "name": "Stripe restricted key", + "name_zh": "Stripe 受限密钥", + "on_by_default": true + }, + { + "about": "Starts with `SG.`, followed by at least 30 characters", + "about_zh": "`SG.` 开头,其后至少 30 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "sendgrid-key", + "kind": "business", + "label": "SECRET", + "matcher": { + "kind": "prefix", + "min_tail": 30, + "prefix": "SG." + }, + "name": "SendGrid key", + "name_zh": "SendGrid 密钥", + "on_by_default": true + }, + { + "about": "Starts with `whsec_`, followed by at least 32 characters", + "about_zh": "`whsec_` 开头,其后至少 32 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "stripe-webhook-secret", + "kind": "business", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 32, + "prefix": "whsec_", + "tail": "any" + } + ] + }, + "name": "Stripe webhook signing secret", + "name_zh": "Stripe Webhook 签名密钥", + "on_by_default": true + }, + { + "about": "Starts with `shpat_`, `shpca_`, `shppa_` or `shpss_`, followed by at least 32 hex characters", + "about_zh": "`shpat_`、`shpca_`、`shppa_`、`shpss_` 开头,其后至少 32 个十六进制字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "shopify-token", + "kind": "business", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 32, + "prefix": "shpat_", + "tail": "hex" + }, + { + "min_tail": 32, + "prefix": "shpca_", + "tail": "hex" + }, + { + "min_tail": 32, + "prefix": "shppa_", + "tail": "hex" + }, + { + "min_tail": 32, + "prefix": "shpss_", + "tail": "hex" + } + ] + }, + "name": "Shopify token", + "name_zh": "Shopify 令牌", + "on_by_default": true + }, + { + "about": "Starts with `SK`, followed by at least 32 hex characters", + "about_zh": "`SK` 开头,其后至少 32 个十六进制字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "twilio-api-key", + "kind": "business", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 32, + "prefix": "SK", + "tail": "hex" + } + ] + }, + "name": "Twilio API key", + "name_zh": "Twilio API 密钥", + "on_by_default": true + }, + { + "about": "Starts with `xkeysib-`, followed by at least 81 characters", + "about_zh": "`xkeysib-` 开头,其后至少 81 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "brevo-key", + "kind": "business", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 81, + "prefix": "xkeysib-", + "tail": "any" + } + ] + }, + "name": "Brevo API key", + "name_zh": "Brevo API 密钥", + "on_by_default": true + }, + { + "about": "Starts with `xapp-`, `xoxe-`, `xoxe.xoxp-`, `xoxe.xoxb-` or `xoxr-`, followed by at least 20 characters", + "about_zh": "`xapp-`、`xoxe-`、`xoxe.xoxp-`、`xoxe.xoxb-`、`xoxr-` 开头,其后至少 20 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "slack-other-token", + "kind": "business", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 20, + "prefix": "xapp-", + "tail": "any" + }, + { + "min_tail": 20, + "prefix": "xoxe-", + "tail": "any" + }, + { + "min_tail": 20, + "prefix": "xoxe.xoxp-", + "tail": "any" + }, + { + "min_tail": 20, + "prefix": "xoxe.xoxb-", + "tail": "any" + }, + { + "min_tail": 20, + "prefix": "xoxr-", + "tail": "any" + } + ] + }, + "name": "Other Slack token", + "name_zh": "Slack 其他令牌", + "on_by_default": true + }, + { + "about": "Starts with `lin_api_`, followed by at least 40 letters or digits", + "about_zh": "`lin_api_` 开头,其后至少 40 个字母或数字", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "linear-key", + "kind": "business", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 40, + "prefix": "lin_api_", + "tail": "alnum" + } + ] + }, + "name": "Linear API key", + "name_zh": "Linear API 密钥", + "on_by_default": true + }, + { + "about": "Starts with `ntn_`, followed by at least 46 letters or digits", + "about_zh": "`ntn_` 开头,其后至少 46 个字母或数字", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "notion-token", + "kind": "business", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 46, + "prefix": "ntn_", + "tail": "alnum" + } + ] + }, + "name": "Notion token", + "name_zh": "Notion 令牌", + "on_by_default": true + }, + { + "about": "Starts with `ATATT3`, followed by at least 150 characters", + "about_zh": "`ATATT3` 开头,其后至少 150 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "atlassian-token", + "kind": "business", + "label": "SECRET", + "matcher": { + "kind": "prefixes", + "prefixes": [ + { + "min_tail": 150, + "prefix": "ATATT3", + "tail": "any" + } + ] + }, + "name": "Atlassian API token", + "name_zh": "Atlassian API 令牌", + "on_by_default": true + }, + { + "about": "From `-----BEGIN … PRIVATE KEY-----` to its END, as a whole", + "about_zh": "`-----BEGIN … PRIVATE KEY-----` 至对应的 END,整段", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "private-key", + "kind": "private-keys", + "label": "SECRET", + "matcher": { + "kind": "pem" + }, + "name": "Private key", + "name_zh": "私钥", + "on_by_default": true + }, + { + "about": "Three base64url parts; the first decodes to text containing `\"alg\"`", + "about_zh": "三段 base64url,首段解码后含 `\"alg\"`", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "jwt", + "kind": "jwt", + "label": "SECRET", + "matcher": { + "kind": "jwt" + }, + "name": "JWT", + "name_zh": "JWT", + "on_by_default": true + }, + { + "about": "The password in `scheme://user:password@host`; only the password is replaced", + "about_zh": "`协议://用户:口令@主机` 中的口令;只换口令", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "conn-string-password", + "kind": "conn-strings", + "label": "SECRET", + "matcher": { + "kind": "conn-string" + }, + "name": "Connection string password", + "name_zh": "连接串口令", + "on_by_default": true + }, + { + "about": "The value of an `Authorization`, `X-Api-Key`, `Api-Key` or `X-Goog-Api-Key` header, in curl `-H`, raw HTTP or code; only the value is replaced", + "about_zh": "`Authorization`、`X-Api-Key`、`Api-Key`、`X-Goog-Api-Key` 请求头的值,包括 curl 的 `-H`、HTTP 原文与代码中的写法;只换值", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "auth-header-credential", + "kind": "in-context", + "label": "SECRET", + "matcher": { + "check": "auth-header-credential", + "kind": "builtin" + }, + "name": "Credential in a request header", + "name_zh": "请求头中的凭据", + "on_by_default": true + }, + { + "about": "Passwords on a command line: `curl -u user:password`, `--user`, `mysql -ppassword`, `PGPASSWORD=… psql`; only the password is replaced", + "about_zh": "命令行中的口令:`curl -u 用户:口令`、`--user`、`mysql -p口令`、`PGPASSWORD=… psql`;只换口令", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "command-line-password", + "kind": "in-context", + "label": "SECRET", + "matcher": { + "check": "command-line-password", + "kind": "builtin" + }, + "name": "Password on a command line", + "name_zh": "命令行中的口令", + "on_by_default": true + }, + { + "about": "The value of the URL query parameter `access_token`, `api_key`, `apikey`, `client_secret`, `sig`, `X-Amz-Signature` or `X-Goog-Signature`; only the parameter value is replaced", + "about_zh": "URL 查询参数 `access_token`、`api_key`、`apikey`、`client_secret`、`sig`、`X-Amz-Signature`、`X-Goog-Signature` 的值;只换参数值", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "url-token", + "kind": "in-context", + "label": "SECRET", + "matcher": { + "check": "url-token", + "kind": "builtin" + }, + "name": "Token in a URL", + "name_zh": "URL 中的令牌", + "on_by_default": true + }, + { + "about": "The token in a Feishu / Lark, DingTalk, WeCom, Slack, Discord or Microsoft Teams bot webhook URL; only the token is replaced", + "about_zh": "飞书 / Lark、钉钉、企业微信、Slack、Discord、Microsoft Teams 机器人 Webhook 地址中的令牌;只换令牌段", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "bot-webhook", + "kind": "in-context", + "label": "SECRET", + "matcher": { + "check": "bot-webhook", + "kind": "builtin" + }, + "name": "Bot webhook", + "name_zh": "机器人 Webhook", + "on_by_default": true + }, + { + "about": "8 to 10 digits and `:AA`, followed by 33 characters", + "about_zh": "8 至 10 位数字与 `:AA`,其后 33 个字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "telegram-bot-token", + "kind": "in-context", + "label": "SECRET", + "matcher": { + "check": "telegram-bot-token", + "kind": "builtin" + }, + "name": "Telegram bot token", + "name_zh": "Telegram 机器人令牌", + "on_by_default": true + }, + { + "about": "Assignments in `.env`, YAML, TOML, INI or JSON whose variable name ends in `SECRET`, `SECRET_KEY`, `PASSWORD`, `PASSWD`, `PWD`, `TOKEN`, `API_KEY`, `APIKEY`, `ACCESS_KEY`, `PRIVATE_KEY` or `CLIENT_SECRET` (ignoring case) and whose value is a literal of at least 8 characters; only the value is replaced. Expressions, variable references and placeholders do not count", + "about_zh": "`.env`、YAML、TOML、INI、JSON 中变量名以 `SECRET`、`SECRET_KEY`、`PASSWORD`、`PASSWD`、`PWD`、`TOKEN`、`API_KEY`、`APIKEY`、`ACCESS_KEY`、`PRIVATE_KEY`、`CLIENT_SECRET` 结尾(不区分大小写)、值为至少 8 个字符的字面量的赋值;只换值,表达式、变量引用和占位写法不算", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "config-assignment", + "kind": "in-context", + "label": "SECRET", + "matcher": { + "kind": "assignment", + "suffixes": [ + "SECRET", + "SECRET_KEY", + "PASSWORD", + "PASSWD", + "PWD", + "TOKEN", + "API_KEY", + "APIKEY", + "ACCESS_KEY", + "PRIVATE_KEY", + "CLIENT_SECRET" + ] + }, + "name": "Secret assigned in a config file", + "name_zh": "配置文件中的密钥赋值", + "on_by_default": true + }, + { + "about": "12, 15, 18, 21 or 24 consecutive words from the BIP39 English or Simplified Chinese word list that pass the checksum; numbering allowed, replaced as a whole", + "about_zh": "连续 12、15、18、21 或 24 个 BIP39 英文或简体中文词表中的词,并通过校验和;可带编号,整段", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "crypto-mnemonic", + "kind": "crypto", + "label": "SECRET", + "matcher": { + "check": "crypto-mnemonic", + "kind": "builtin" + }, + "name": "Recovery phrase", + "name_zh": "助记词", + "on_by_default": true + }, + { + "about": "A WIF private key of 51 or 52 characters that passes Base58Check with a valid version byte", + "about_zh": "51 或 52 个字符的 WIF 私钥:Base58Check 校验通过,版本字节对得上", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "crypto-wif-key", + "kind": "crypto", + "label": "SECRET", + "matcher": { + "check": "crypto-wif-key", + "kind": "builtin" + }, + "name": "WIF private key", + "name_zh": "WIF 私钥", + "on_by_default": true + }, + { + "about": "Starts with `xprv`, `tprv`, `yprv` or `zprv` and passes Base58Check", + "about_zh": "`xprv`、`tprv`、`yprv`、`zprv` 开头,Base58Check 校验通过", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "crypto-extended-key", + "kind": "crypto", + "label": "SECRET", + "matcher": { + "check": "crypto-extended-key", + "kind": "builtin" + }, + "name": "Extended private key", + "name_zh": "扩展私钥", + "on_by_default": true + }, + { + "about": "An 18-character resident ID number, including residence permits for Hong Kong, Macau and Taiwan residents and foreign permanent residence ID cards, whose region code, birth date since 1900 and check character all check out", + "about_zh": "18 位居民身份证号(含港澳台居民居住证、外国人永久居留身份证):地区码、1900 年以来的出生日期和校验码都对得上", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "cn-resident-id", + "kind": "id-documents", + "label": "ID_NUMBER", + "matcher": { + "born_since": 1900, + "kind": "cn-resident-id" + }, + "name": "Chinese resident ID number", + "name_zh": "居民身份证号", + "on_by_default": true + }, + { + "about": "A 15-digit legacy resident ID number with a valid region code and 6-digit birth date, followed by a 3-digit sequence", + "about_zh": "15 位旧身份证号:地区码、6 位出生日期都对得上,其后 3 位顺序码", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": false, + "id": "cn-resident-id-15", + "kind": "id-documents", + "label": "ID_NUMBER", + "matcher": { + "check": "cn-resident-id-15", + "kind": "builtin" + }, + "name": "15-digit legacy resident ID number", + "name_zh": "15 位旧身份证号", + "on_by_default": false + }, + { + "about": "Two lines of 44 characters starting with `P`, with all four check digits and the composite check digit correct; replaced as a whole", + "about_zh": "两行各 44 个字符、以 `P` 开头,四处校验位与综合校验位都对得上;整段", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "passport-mrz", + "kind": "id-documents", + "label": "PASSPORT", + "matcher": { + "check": "passport-mrz", + "kind": "builtin" + }, + "name": "Passport machine-readable zone", + "name_zh": "护照机读区", + "on_by_default": true + }, + { + "about": "Starts with `E`, or `E` and a letter, followed by 7 or 8 digits, with “护照” or “passport” in the 20 characters before it", + "about_zh": "`E` 或 `E` 加一个字母开头、其后 7 至 8 位数字,且前 20 个字符内出现「护照」或「passport」", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": false, + "id": "cn-passport", + "kind": "id-documents", + "label": "PASSPORT", + "matcher": { + "check": "cn-passport", + "kind": "builtin" + }, + "name": "Chinese passport number", + "name_zh": "中国护照号", + "on_by_default": false + }, + { + "about": "Hong Kong, Macau and Taiwan ID numbers: Hong Kong and Taiwan ones with a correct check character; Macau ones only when the document is named just before", + "about_zh": "香港、澳门、台湾身份证号:香港、台湾的校验码对得上;澳门的须前文写明证件名称", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": false, + "id": "hk-macau-taiwan-id", + "kind": "id-documents", + "label": "ID_NUMBER", + "matcher": { + "check": "hk-macau-taiwan-id", + "kind": "builtin" + }, + "name": "Hong Kong, Macau or Taiwan ID number", + "name_zh": "港澳台身份证号", + "on_by_default": false + }, + { + "about": "Mainland travel permits for Hong Kong and Macau residents (`H` or `M` and 8 digits) and for Taiwan residents (8 digits), with “通行证”, “回乡证” or “台胞证” in the 20 characters before it", + "about_zh": "港澳居民来往内地通行证(`H` 或 `M` 加 8 位数字)与台湾居民来往大陆通行证(8 位数字),且前 20 个字符内出现「通行证」「回乡证」或「台胞证」", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": false, + "id": "mainland-travel-permit", + "kind": "id-documents", + "label": "ID_NUMBER", + "matcher": { + "check": "mainland-travel-permit", + "kind": "builtin" + }, + "name": "Mainland travel permit number", + "name_zh": "港澳台往来内地通行证号", + "on_by_default": false + }, + { + "about": "A US Social Security number written as `ddd-dd-dddd`, excluding area, group and serial numbers never issued", + "about_zh": "`ddd-dd-dddd` 写法的美国社会安全号,不会分配的区号、组号与序号除外", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": false, + "id": "us-ssn", + "kind": "foreign-ids", + "label": "ID_NUMBER", + "matcher": { + "check": "us-ssn", + "kind": "builtin" + }, + "name": "US Social Security number", + "name_zh": "美国社会安全号", + "on_by_default": false + }, + { + "about": "A UK National Insurance number: 2 letters, 6 digits and one of A to D, excluding letter pairs never issued", + "about_zh": "2 个字母、6 位数字与 A 至 D 之一组成的英国国民保险号,不会分配的字母组合除外", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": false, + "id": "uk-nino", + "kind": "foreign-ids", + "label": "ID_NUMBER", + "matcher": { + "check": "uk-nino", + "kind": "builtin" + }, + "name": "UK National Insurance number", + "name_zh": "英国国民保险号", + "on_by_default": false + }, + { + "about": "Starts with S, T, F or G, followed by 7 digits and a correct check letter", + "about_zh": "S、T、F、G 之一开头,7 位数字加校验字母,校验字母对得上", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": false, + "id": "sg-nric", + "kind": "foreign-ids", + "label": "ID_NUMBER", + "matcher": { + "check": "sg-nric", + "kind": "builtin" + }, + "name": "Singapore NRIC / FIN", + "name_zh": "新加坡 NRIC / FIN", + "on_by_default": false + }, + { + "about": "A 6-digit birth date, `-` and 7 digits, with a valid date and an 8th digit from 1 to 8", + "about_zh": "6 位出生日期、`-` 与 7 位数字,日期有效、第 8 位为 1 至 8", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": false, + "id": "kr-rrn", + "kind": "foreign-ids", + "label": "ID_NUMBER", + "matcher": { + "check": "kr-rrn", + "kind": "builtin" + }, + "name": "Korean resident registration number", + "name_zh": "韩国居民登录号", + "on_by_default": false + }, + { + "about": "12 digits with a correct check digit, with “マイナンバー”, “個人番号” or “My Number” in the 20 characters before it", + "about_zh": "12 位数字,校验位对得上,且前 20 个字符内出现「マイナンバー」「個人番号」或「My Number」", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": false, + "id": "jp-my-number", + "kind": "foreign-ids", + "label": "ID_NUMBER", + "matcher": { + "check": "jp-my-number", + "kind": "builtin" + }, + "name": "Japanese My Number", + "name_zh": "日本个人番号", + "on_by_default": false + }, + { + "about": "12 digits (optionally in groups of four), the first from 2 to 9, passing the Verhoeff check", + "about_zh": "12 位数字(可四位一组),首位为 2 至 9,Verhoeff 校验通过", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": false, + "id": "in-aadhaar", + "kind": "foreign-ids", + "label": "ID_NUMBER", + "matcher": { + "check": "in-aadhaar", + "kind": "builtin" + }, + "name": "Indian Aadhaar number", + "name_zh": "印度 Aadhaar 号码", + "on_by_default": false + }, + { + "about": "A Brazilian CPF written as `ddd.ddd.ddd-dd`, with both check digits correct", + "about_zh": "`ddd.ddd.ddd-dd` 写法的巴西 CPF,两位校验码对得上", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": false, + "id": "br-cpf", + "kind": "foreign-ids", + "label": "ID_NUMBER", + "matcher": { + "check": "br-cpf", + "kind": "builtin" + }, + "name": "Brazilian CPF", + "name_zh": "巴西 CPF", + "on_by_default": false + }, + { + "about": "A UnionPay, Visa, Mastercard, American Express, JCB, Discover or Diners Club card number whose prefix and length match and that passes the Luhn check; public test card numbers excepted", + "about_zh": "银联、Visa、Mastercard、American Express、JCB、Discover、Diners Club 的卡号:号段、位数对得上并通过 Luhn 校验;公开的测试卡号除外", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "bank-card", + "kind": "financial", + "label": "CARD_NUMBER", + "matcher": { + "kind": "bank-card", + "networks": [ + { + "lengths": [ + 16, + 17, 18, 19 ], @@ -1216,37 +3269,360 @@ ] }, "name": "Bank card number", + "name_zh": "银行卡号", + "on_by_default": true + }, + { + "about": "A country code, two check digits and the account number length set for that country, passing MOD 97-10; written in one run or in groups of four", + "about_zh": "国家代码、两位校验码与该国规定位数的账号,MOD 97-10 校验通过;连写或四位一组", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": true, + "id": "iban", + "kind": "financial", + "label": "ACCOUNT_NUMBER", + "matcher": { + "check": "iban", + "kind": "builtin" + }, + "name": "IBAN", + "name_zh": "国际银行账号(IBAN)", "on_by_default": true }, { + "about": "Email addresses: `name@domain`", + "about_zh": "邮箱地址:`名称@域名`", "action": "placeholder", "custom": false, "default_action": "placeholder", "enabled": false, "id": "email", - "kind": "personal", + "kind": "contact", "label": "EMAIL", "matcher": { "kind": "email" }, "name": "Email address", + "name_zh": "邮箱地址", "on_by_default": false }, { + "about": "Chinese mainland mobile numbers: 11 digits starting with `1`, the second 3 to 9, not run together with other digits; a `+86`, `0086` or `86-` prefix and 3-4-4 groups separated by spaces or hyphens are allowed", + "about_zh": "中国大陆手机号:`1` 开头的 11 位数字,第二位为 3 到 9,前后不紧挨其他数字;可带 `+86`、`0086`、`86-` 前缀,可按 3-4-4 以空格或连字符分隔", "action": "placeholder", "custom": false, "default_action": "placeholder", "enabled": false, "id": "cn-mobile-phone", - "kind": "personal", + "kind": "contact", "label": "PHONE", "matcher": { "kind": "cn-mobile-phone" }, "name": "Chinese mainland mobile number", + "name_zh": "中国大陆手机号", + "on_by_default": false + }, + { + "about": "A 2- or 3-digit area code starting with `0` and a 7- or 8-digit number, with a valid area code and brackets or a hyphen (`010-12345678`, `(0755)1234567`)", + "about_zh": "`0` 开头的 2 至 3 位区号加 7 至 8 位号码,区号有效,须带括号或连字符(`010-12345678`、`(0755)1234567`)", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": false, + "id": "cn-landline", + "kind": "contact", + "label": "PHONE", + "matcher": { + "check": "cn-landline", + "kind": "builtin" + }, + "name": "Chinese mainland landline number", + "name_zh": "中国大陆固定电话", + "on_by_default": false + }, + { + "about": "Starts with `+`, with a valid country code and the length set for that country; spaces and hyphens allowed", + "about_zh": "`+` 开头,国家码有效、位数与该国规定一致;可带空格与连字符", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": false, + "id": "intl-phone", + "kind": "contact", + "label": "PHONE", + "matcher": { + "check": "intl-phone", + "kind": "builtin" + }, + "name": "International phone number", + "name_zh": "国际电话号码", + "on_by_default": false + }, + { + "about": "A Chinese mainland street address: administrative area names, a street, road, lane or village, and a house number; addresses that stop at the district do not count", + "about_zh": "行政区划名称、街道路巷村之一与门牌号(号、栋、单元、室)组成的中国大陆详细地址;只到区县的不算", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": false, + "id": "cn-address", + "kind": "location", + "label": "ADDRESS", + "matcher": { + "check": "cn-address", + "kind": "builtin" + }, + "name": "Chinese mainland street address", + "name_zh": "中国大陆详细地址", + "on_by_default": false + }, + { + "about": "A latitude and longitude pair within range, each with at least 5 decimal places", + "about_zh": "成对出现、在取值范围内、小数不少于 5 位的纬度与经度", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": false, + "id": "geo-coordinates", + "kind": "location", + "label": "LOCATION", + "matcher": { + "check": "geo-coordinates", + "kind": "builtin" + }, + "name": "Precise coordinates", + "name_zh": "精确坐标", + "on_by_default": false + }, + { + "about": "A province abbreviation, a letter and 5 letters or digits (6 for new-energy vehicles), without I or O", + "about_zh": "省份简称、字母与 5 位字母或数字(新能源车为 6 位),不含 I、O", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": false, + "id": "cn-license-plate", + "kind": "vehicle", + "label": "PLATE", + "matcher": { + "check": "cn-license-plate", + "kind": "builtin" + }, + "name": "Chinese mainland license plate", + "name_zh": "中国大陆车牌号", + "on_by_default": false + }, + { + "about": "17 characters without I, O or Q, with a correct check character in position 9", + "about_zh": "17 个字符、不含 I、O、Q,第 9 位校验码对得上", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": false, + "id": "vin", + "kind": "vehicle", + "label": "VIN", + "matcher": { + "check": "vin", + "kind": "builtin" + }, + "name": "Vehicle identification number (VIN)", + "name_zh": "车辆识别代号(VIN)", + "on_by_default": false + }, + { + "about": "15 digits passing the Luhn check, with “IMEI” in the 20 characters before it", + "about_zh": "15 位数字,Luhn 校验通过,且前 20 个字符内出现「IMEI」", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": false, + "id": "imei", + "kind": "device", + "label": "DEVICE_ID", + "matcher": { + "check": "imei", + "kind": "builtin" + }, + "name": "IMEI", + "name_zh": "IMEI", + "on_by_default": false + }, + { + "about": "6 pairs of hex characters separated by `:` or `-`; all-zero, broadcast and multicast addresses excepted", + "about_zh": "6 组两位十六进制字符,以 `:` 或 `-` 分隔;全零、广播与组播地址除外", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": false, + "id": "mac-address", + "kind": "device", + "label": "DEVICE_ID", + "matcher": { + "check": "mac-address", + "kind": "builtin" + }, + "name": "MAC address", + "name_zh": "MAC 地址", + "on_by_default": false + }, + { + "about": "Public IPv4 and IPv6 addresses; private, loopback, link-local, carrier-grade NAT, documentation and multicast addresses excepted", + "about_zh": "公网 IPv4、IPv6 地址;私有、回环、链路本地、运营商级 NAT、文档示例与组播地址除外", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": false, + "id": "public-ip", + "kind": "device", + "label": "IP", + "matcher": { + "check": "public-ip", + "kind": "builtin" + }, + "name": "Public IP address", + "name_zh": "公网 IP 地址", + "on_by_default": false + }, + { + "about": "The value of a field named `姓名`, `真实姓名`, `联系人`, `收件人`, `收货人`, `real_name`, `full_name`, `legal_name`, `first_name`, `last_name`, `given_name`, `family_name` or `name`, written as `name: value`, `\"name\": \"value\"` or under a table header; only the value is replaced", + "about_zh": "字段名为 `姓名`、`真实姓名`、`联系人`、`收件人`、`收货人`、`real_name`、`full_name`、`legal_name`、`first_name`、`last_name`、`given_name`、`family_name`、`name` 的值,包括 `字段名: 值`、`\"字段名\": \"值\"` 与表格表头的写法;只换值", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": false, + "id": "labeled-name", + "kind": "labeled-personal", + "label": "NAME", + "matcher": { + "kind": "labeled", + "names": [ + "姓名", + "真实姓名", + "联系人", + "收件人", + "收货人", + "real_name", + "full_name", + "legal_name", + "first_name", + "last_name", + "given_name", + "family_name", + "name" + ] + }, + "name": "Name by field name", + "name_zh": "带字段名的姓名", + "on_by_default": false + }, + { + "about": "The value of a field named `住址`, `家庭住址`, `收货地址`, `联系地址`, `home_address`, `shipping_address`, `residential_address` or `address`, written as `name: value`, `\"name\": \"value\"` or under a table header; only the value is replaced", + "about_zh": "字段名为 `住址`、`家庭住址`、`收货地址`、`联系地址`、`home_address`、`shipping_address`、`residential_address`、`address` 的值,包括 `字段名: 值`、`\"字段名\": \"值\"` 与表格表头的写法;只换值", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": false, + "id": "labeled-address", + "kind": "labeled-personal", + "label": "ADDRESS", + "matcher": { + "kind": "labeled", + "names": [ + "住址", + "家庭住址", + "收货地址", + "联系地址", + "home_address", + "shipping_address", + "residential_address", + "address" + ] + }, + "name": "Address by field name", + "name_zh": "带字段名的住址", + "on_by_default": false + }, + { + "about": "The value of a field named `出生日期`, `生日`, `birthday`, `birth_date`, `date_of_birth` or `dob`, written as `name: value`, `\"name\": \"value\"` or under a table header; only the value is replaced", + "about_zh": "字段名为 `出生日期`、`生日`、`birthday`、`birth_date`、`date_of_birth`、`dob` 的值,包括 `字段名: 值`、`\"字段名\": \"值\"` 与表格表头的写法;只换值", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": false, + "id": "labeled-birth-date", + "kind": "labeled-personal", + "label": "BIRTH_DATE", + "matcher": { + "kind": "labeled", + "names": [ + "出生日期", + "生日", + "birthday", + "birth_date", + "date_of_birth", + "dob" + ] + }, + "name": "Birth date by field name", + "name_zh": "带字段名的出生日期", + "on_by_default": false + }, + { + "about": "The value of a field named `银行账号`, `开户账号`, `银行卡号`, `account_number`, `bank_account` or `bank_account_number`, written as `name: value`, `\"name\": \"value\"` or under a table header; only the value is replaced", + "about_zh": "字段名为 `银行账号`、`开户账号`、`银行卡号`、`account_number`、`bank_account`、`bank_account_number` 的值,包括 `字段名: 值`、`\"字段名\": \"值\"` 与表格表头的写法;只换值", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": false, + "id": "labeled-account", + "kind": "labeled-personal", + "label": "ACCOUNT_NUMBER", + "matcher": { + "kind": "labeled", + "names": [ + "银行账号", + "开户账号", + "银行卡号", + "account_number", + "bank_account", + "bank_account_number" + ] + }, + "name": "Bank account by field name", + "name_zh": "带字段名的银行账号", + "on_by_default": false + }, + { + "about": "The value of a field named `病历号`, `住院号`, `诊断`, `病史`, `medical_record_number` or `diagnosis`, written as `name: value`, `\"name\": \"value\"` or under a table header; only the value is replaced", + "about_zh": "字段名为 `病历号`、`住院号`、`诊断`、`病史`、`medical_record_number`、`diagnosis` 的值,包括 `字段名: 值`、`\"字段名\": \"值\"` 与表格表头的写法;只换值", + "action": "placeholder", + "custom": false, + "default_action": "placeholder", + "enabled": false, + "id": "labeled-health", + "kind": "labeled-personal", + "label": "HEALTH", + "matcher": { + "kind": "labeled", + "names": [ + "病历号", + "住院号", + "诊断", + "病史", + "medical_record_number", + "diagnosis" + ] + }, + "name": "Health information by field name", + "name_zh": "带字段名的健康信息", "on_by_default": false }, { + "about": "Addresses starting with `10.`, `172.16–31.`, `192.168.` or `100.64–127.`, and IPv6 addresses in `fc00::/7`; not 127.0.0.1", + "about_zh": "`10.`、`172.16–31.`、`192.168.`、`100.64–127.` 开头的地址与 `fc00::/7` 内的 IPv6 地址,不含 127.0.0.1", "action": "placeholder", "custom": false, "default_action": "placeholder", @@ -1258,9 +3634,12 @@ "kind": "private-ip" }, "name": "Internal IP address", + "name_zh": "内网地址", "on_by_default": false }, { + "about": "Domains ending in `.local`, `.internal`, `.lan`, `.corp`, `.home.arpa` or `.intranet`", + "about_zh": "以 `.local`、`.internal`、`.lan`、`.corp`、`.home.arpa`、`.intranet` 结尾的域名", "action": "placeholder", "custom": false, "default_action": "placeholder", @@ -1273,10 +3652,14 @@ "suffixes": [ ".local", ".internal", - ".lan" + ".lan", + ".corp", + ".home.arpa", + ".intranet" ] }, "name": "Internal domain", + "name_zh": "内部域名", "on_by_default": false }, { diff --git a/scripts/shots/core/zh/status.json b/scripts/shots/core/zh/status.json index c1adadb0..2e9c7bab 100644 --- a/scripts/shots/core/zh/status.json +++ b/scripts/shots/core/zh/status.json @@ -1,5 +1,5 @@ { - "api_version": 55, + "api_version": 56, "clients": 4, "config_path": "", "draining": 0, @@ -16,5 +16,5 @@ "reachable": [] }, "uptime_secs": 0, - "version": "0.84.0" + "version": "0.85.0" } diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index c808636c..1ca0a0b7 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -14,7 +14,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "generic-array", ] @@ -82,7 +82,7 @@ dependencies = [ "objc2-foundation", "parking_lot", "percent-encoding", - "windows-sys 0.60.2", + "windows-sys 0.52.0", "wl-clipboard-rs", "x11rb", ] @@ -324,7 +324,7 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ - "digest", + "digest 0.10.7", ] [[package]] @@ -336,6 +336,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + [[package]] name = "block2" version = "0.6.2" @@ -596,7 +605,7 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "inout", "zeroize", ] @@ -629,6 +638,12 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + [[package]] name = "const-random" version = "0.1.18" @@ -767,6 +782,15 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + [[package]] name = "cssparser" version = "0.37.0" @@ -933,11 +957,22 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", - "crypto-common", + "block-buffer 0.10.4", + "crypto-common 0.1.7", "subtle", ] +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.1", + "const-oid", + "crypto-common 0.2.2", +] + [[package]] name = "dirs" version = "4.0.0" @@ -985,7 +1020,7 @@ dependencies = [ "libc", "option-ext", "redox_users 0.5.3", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -1171,7 +1206,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -1848,6 +1883,15 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" +[[package]] +name = "hybrid-array" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17" +dependencies = [ + "typenum", +] + [[package]] name = "hyper" version = "1.11.1" @@ -2690,7 +2734,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -3026,7 +3070,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.45.0", ] [[package]] @@ -3655,7 +3699,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -3711,7 +3755,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -4060,7 +4104,18 @@ checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures 0.2.17", - "digest", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d7069beb7d6ac7b9acd1039986e73443f24234f41074da099d6f994ac9ad19" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.1", + "digest 0.11.3", ] [[package]] @@ -4495,7 +4550,7 @@ dependencies = [ "semver", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "syn 2.0.119", "tauri-utils", "thiserror 2.0.21", @@ -4854,7 +4909,7 @@ dependencies = [ "getrandom 0.4.3", "once_cell", "rustix", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -5383,8 +5438,8 @@ dependencies = [ [[package]] name = "tw-api" -version = "0.84.0" -source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.84.0#dcde84ed1da155303574ad430c0cafade50e3c8b" +version = "0.85.0" +source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.85.0#7134a31380ca2b3784079e3f3e0d2f7c64ec967b" dependencies = [ "serde", "serde_json", @@ -5395,8 +5450,8 @@ dependencies = [ [[package]] name = "tw-dialect" -version = "0.84.0" -source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.84.0#dcde84ed1da155303574ad430c0cafade50e3c8b" +version = "0.85.0" +source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.85.0#7134a31380ca2b3784079e3f3e0d2f7c64ec967b" dependencies = [ "memchr", "serde", @@ -5405,8 +5460,8 @@ dependencies = [ [[package]] name = "tw-guard" -version = "0.84.0" -source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.84.0#dcde84ed1da155303574ad430c0cafade50e3c8b" +version = "0.85.0" +source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.85.0#7134a31380ca2b3784079e3f3e0d2f7c64ec967b" dependencies = [ "aho-corasick", "base64 0.22.1", @@ -5417,6 +5472,7 @@ dependencies = [ "serde", "serde_json", "serde_yaml_ng", + "sha2 0.11.1", "thiserror 2.0.21", "ts-rs", "tw-dialect", @@ -5424,8 +5480,8 @@ dependencies = [ [[package]] name = "tw-link" -version = "0.84.0" -source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.84.0#dcde84ed1da155303574ad430c0cafade50e3c8b" +version = "0.85.0" +source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.85.0#7134a31380ca2b3784079e3f3e0d2f7c64ec967b" dependencies = [ "serde", "serde_json", @@ -5451,8 +5507,8 @@ dependencies = [ [[package]] name = "tw-types" -version = "0.84.0" -source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.84.0#dcde84ed1da155303574ad430c0cafade50e3c8b" +version = "0.85.0" +source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.85.0#7134a31380ca2b3784079e3f3e0d2f7c64ec967b" dependencies = [ "serde", "ts-rs", @@ -5460,8 +5516,8 @@ dependencies = [ [[package]] name = "tw-watch" -version = "0.84.0" -source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.84.0#dcde84ed1da155303574ad430c0cafade50e3c8b" +version = "0.85.0" +source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.85.0#7134a31380ca2b3784079e3f3e0d2f7c64ec967b" dependencies = [ "libc", "notify", @@ -5471,8 +5527,8 @@ dependencies = [ [[package]] name = "tw-yaml" -version = "0.84.0" -source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.84.0#dcde84ed1da155303574ad430c0cafade50e3c8b" +version = "0.85.0" +source = "git+https://github.com/ThinkWatchProject/ThinkWatch-Core.git?tag=v0.85.0#7134a31380ca2b3784079e3f3e0d2f7c64ec967b" dependencies = [ "saphyr-parser", "thiserror 2.0.21", @@ -5520,7 +5576,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "subtle", ] @@ -5936,7 +5992,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -6519,7 +6575,7 @@ dependencies = [ "once_cell", "percent-encoding", "raw-window-handle", - "sha2", + "sha2 0.10.9", "soup3", "tao-macros", "thiserror 2.0.21", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 74ff585e..6b682562 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -26,12 +26,12 @@ license = "MIT" # twcore 二进制必须和这里编译进去的协议镜像来自同一个 core 版本 —— 打包脚本正是 # 从 tw-api 锁到的 tag 去取二进制的(见 scripts/fetch-core.sh)。 [workspace.dependencies] -tw-api = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.84.0" } -tw-types = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.84.0" } -tw-yaml = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.84.0" } -tw-guard = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.84.0" } -tw-watch = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.84.0" } -tw-link = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.84.0" } +tw-api = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.85.0" } +tw-types = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.85.0" } +tw-yaml = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.85.0" } +tw-guard = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.85.0" } +tw-watch = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.85.0" } +tw-link = { git = "https://github.com/ThinkWatchProject/ThinkWatch-Core.git", tag = "v0.85.0" } [lib] name = "thinkwatch_lite_lib" diff --git a/src-tauri/src/core_text.rs b/src-tauri/src/core_text.rs index 79f6aa98..606309fb 100644 --- a/src-tauri/src/core_text.rs +++ b/src-tauri/src/core_text.rs @@ -44,7 +44,7 @@ pub fn text(m: &Msg) -> String { tr!(zh(m).unwrap_or_else(|| m.text.clone()), m.text.clone()) } -/// 词表里的一项(`guard`、`secret_rule`、`content_rule`…),中文。表或项不在是 `None` +/// 词表里的一项(`guard`、`content_rule`…),中文。表或项不在是 `None` pub fn table_zh(table: &str, key: &str) -> Option { self::table().tables.get(table)?.get(key).cloned() } @@ -311,6 +311,31 @@ fn render(src: &str, args: &BTreeMap, tables: &Tables) -> Option mod tests { use super::*; + /// 消息里规则名、理由的中文(`{rule:scan_rule}`、`{rule:rule_why}`、`{rule:content_rule}`) + /// 查的是表里的这三张;**规则的中文本身由 core 写**(tw-guard 的规则目录,安全页显示的 + /// 就是它)。这里钉着两边一字不差:core 改了措辞,消息里不能还是旧的那句 + #[test] + fn rule_tables_match_the_core_catalog() { + let t = &table().tables; + let f = tw_guard::tools::rules::builtin(); + let tools: Vec<_> = f.injection.iter().chain(&f.dangerous).collect(); + let names: HashMap = tools + .iter() + .map(|r| (r.id.clone(), r.name_zh.clone())) + .collect(); + let whys: HashMap = tools + .iter() + .map(|r| (r.id.clone(), r.why_zh.clone())) + .collect(); + let content: HashMap = tw_guard::content::builtins() + .iter() + .map(|b| (b.id.clone(), b.name_zh.clone())) + .collect(); + assert_eq!(t["scan_rule"], names); + assert_eq!(t["rule_why"], whys); + assert_eq!(t["content_rule"], content); + } + /// 表里每一句、每一层场合都写得对:写坏了的那句会悄悄退回英文 #[test] fn every_template_in_the_table_parses() { diff --git a/src-tauri/src/notices/rules.rs b/src-tauri/src/notices/rules.rs index 68398bf5..8563225f 100644 --- a/src-tauri/src/notices/rules.rs +++ b/src-tauri/src/notices/rules.rs @@ -397,7 +397,7 @@ pub fn from_event(ev: &Event) -> Vec { custom, detail, .. - } => watched("inspect_tools", provider, rule, *custom, detail) + } => watched("inspect_tools", provider, rule, *custom, None, detail) .into_iter() .collect(), Event::ContentMatched { @@ -406,7 +406,7 @@ pub fn from_event(ev: &Event) -> Vec { custom, detail, .. - } => watched("content", provider, rule, *custom, detail) + } => watched("content", provider, rule, *custom, None, detail) .into_iter() .collect(), // 一次请求里找到的每个值各一项;同一条规则找到几个值,就是同一条规则命中了几次 @@ -414,7 +414,16 @@ pub fn from_event(ev: &Event) -> Vec { provider, items, .. } => items .iter() - .filter_map(|i| watched("redact", provider, &i.rule, i.custom, &i.detail)) + .filter_map(|i| { + watched( + "redact", + provider, + &i.rule, + i.custom, + i.source.as_ref(), + &i.detail, + ) + }) .collect(), Event::KeyLimitAlert { .. } => key_limit(ev), // **请求失败只进应用内**(概览上原来那条「需要处理的事」挪到这里):多数失败是上游 @@ -860,7 +869,8 @@ fn unpriced(m: &tw_api::UnpricedModel) -> Signal { /// 处理)。**只认 core 说的结局**(`outcome_detail` 是 `recorded`),不自己按档位和动作推。 /// /// 规格(安全页重整 §3):仅记录 = 记入安全日志并发出系统通知。正文**只有规则名和上游**, -/// 不带命中的原文(锁屏上也看得见)。键按规则分(`secwatch:<防护>::<规则>`),所以 +/// 不带命中的原文(锁屏上也看得见)。「ThinkWatch 中保存的密钥」再写明是哪一处保存的 +/// (`source`:上游「DeepSeek」的密钥……),**只有来源,没有值的任何部分**。键按规则分(`secwatch:<防护>::<规则>`),所以 /// **同一条规则 5 分钟里至多弹一次**([`Signal::event`] 的冷却);同时命中的几条规则由总线 /// 合成一条通知([`Signal::merged`],那一行是这条规则在合成通知里的样子)。 fn watched( @@ -868,6 +878,7 @@ fn watched( provider: &str, rule: &str, custom: bool, + source: Option<&tw_api::SavedSource>, detail: &tw_api::SecurityHitDetail, ) -> Option { if !matches!(detail.outcome_detail, tw_api::OutcomeDetail::Recorded {}) { @@ -876,17 +887,37 @@ fn watched( let name = guard_rule_name(guard, rule, custom, &detail.rule_snapshot.name); let what = guard_name(guard); let answer = detail.direction == tw_api::SecurityDirection::Response; - let body = match (answer, crate::i18n::current()) { - (false, crate::i18n::Lang::Zh) => { + let body = match (source, answer, crate::i18n::current()) { + (Some(s), false, crate::i18n::Lang::Zh) => { + let s = saved_source_zh(s); + format!("{what}:经上游「{provider}」的请求含有{s},已记入安全日志。") + } + (Some(s), true, crate::i18n::Lang::Zh) => { + let s = saved_source_zh(s); + format!("{what}:上游「{provider}」的回答含有{s},已记入安全日志。") + } + (Some(s), false, crate::i18n::Lang::En) => { + let s = saved_source_en(s); + format!( + "{what}: a request through upstream “{provider}” contained {s}. It was recorded in the security log." + ) + } + (Some(s), true, crate::i18n::Lang::En) => { + let s = saved_source_en(s); + format!( + "{what}: an answer from upstream “{provider}” contained {s}. It was recorded in the security log." + ) + } + (None, false, crate::i18n::Lang::Zh) => { format!("{what}:经上游「{provider}」的请求命中此规则,已记入安全日志。") } - (true, crate::i18n::Lang::Zh) => { + (None, true, crate::i18n::Lang::Zh) => { format!("{what}:上游「{provider}」的回答命中此规则,已记入安全日志。") } - (false, crate::i18n::Lang::En) => format!( + (None, false, crate::i18n::Lang::En) => format!( "{what}: a request through upstream “{provider}” matched this rule. It was recorded in the security log." ), - (true, crate::i18n::Lang::En) => format!( + (None, true, crate::i18n::Lang::En) => format!( "{what}: an answer from upstream “{provider}” matched this rule. It was recorded in the security log." ), }; @@ -923,58 +954,68 @@ fn guard_name(guard: &str) -> String { ) } -/// 规则的名字,和安全页上的一样:自定义规则就是用户起的名字;内置的中文查词表(和界面同一张), -/// 查不到、或者英文界面,用 core 给的英文名 +/// 保存的密钥是哪一处的,放进句中(「经上游「X」的请求含有上游「DeepSeek」的密钥」)。 +/// 和安全页上写的一样(`savedSourceText`) +fn saved_source_zh(s: &tw_api::SavedSource) -> String { + use tw_api::SavedSourceKind as K; + let n = &s.name; + match s.kind { + K::UpstreamKey => format!("上游「{n}」的密钥"), + K::UpstreamHeader => format!("上游「{n}」请求头中的值"), + K::AccessKey => format!("网关密钥「{n}」"), + K::ProxyPassword => format!("代理「{n}」的密码"), + K::ControlKey => "远程控制密钥".to_string(), + K::SyncCredential => "同步与备份凭据".to_string(), + } +} + +/// 同上,英文,句中的写法(小写开头) +fn saved_source_en(s: &tw_api::SavedSource) -> String { + use tw_api::SavedSourceKind as K; + let n = &s.name; + match s.kind { + K::UpstreamKey => format!("the key of upstream “{n}”"), + K::UpstreamHeader => format!("a header value of upstream “{n}”"), + K::AccessKey => format!("gateway key “{n}”"), + K::ProxyPassword => format!("the password of proxy “{n}”"), + K::ControlKey => "the remote control key".to_string(), + K::SyncCredential => "a sync and backup credential".to_string(), + } +} + +/// 规则的名字,和安全页上的一样:自定义规则就是用户起的名字;内置的取 core 规则目录里的 +/// 中英文名(钉着的这一版 tw-guard,安全页上 core 给的是同一份)。目录里没有的(连着的远程 +/// core 更新、多了规则),用事件里的英文名 fn guard_rule_name(guard: &str, rule: &str, custom: bool, english: &str) -> String { if custom { return rule.to_string(); } let english = if english.is_empty() { rule } else { english }; - match guard { - "inspect_tools" => tool_rule_name(rule), - _ => { - let table = if guard == "redact" { - "secret_rule" - } else { - "content_rule" - }; - tr!( - crate::core_text::table_zh(table, rule).unwrap_or_else(|| english.to_string()), - english.to_string() - ) - } - } + let zh = match guard { + "redact" => tw_guard::redact::rules::builtin(rule).map(|b| b.name_zh.to_string()), + "content" => tw_guard::content::builtin(rule).map(|b| b.name_zh.clone()), + _ => tool_rule(rule).map(|r| r.name_zh.clone()), + }; + tr!( + zh.filter(|z| !z.is_empty()) + .unwrap_or_else(|| english.to_string()), + english.to_string() + ) +} + +/// 一条内置的工具调用规则(危险命令和提示注入两组) +fn tool_rule(id: &str) -> Option<&'static tw_guard::tools::rules::RuleSpec> { + let f = tw_guard::tools::rules::builtin(); + f.dangerous.iter().chain(&f.injection).find(|r| r.id == id) } /// 工具调用规则的名字,和安全页上的一样。**只有内置的这些**:自定义规则的 /// id 就是用户起的名字,原样用。 fn tool_rule_name(rule: &str) -> String { - let (zh, en) = match rule { - "curl-pipe-sh" => ("下载即执行", "Download and run"), - "base64-decode-exec" => ("解码后执行", "Decode and run"), - "exfil-env" => ("外发环境变量", "Send out environment variables"), - "exfil-credentials" => ("外发凭据文件", "Send out a credential file"), - "exfil-credentials-reversed" => ( - "外发凭据文件(动词在前)", - "Send out a credential file (verb first)", - ), - "ssh-key-read" => ("读取私钥或云凭据", "Read a private key or cloud credential"), - "secret-to-unknown-host" => ("凭据发往陌生主机", "Send a credential to an unknown host"), - "thinkwatch-data" => ( - "读写 ThinkWatch 自己的数据", - "Read or change ThinkWatch's own data", - ), - "write-startup-item" => ("写入启动项", "Write a startup item"), - "crontab-install" => ("安装定时任务", "Install a scheduled job"), - "rm-rf-root" => ("删除主目录或根目录", "Delete home or root"), - "chmod-777" => ("开放全部写权限", "World-writable permissions"), - "upload-file-to-host" => ( - "上传本地文件到外部主机", - "Upload a local file to an external host", - ), - other => return other.to_string(), - }; - tr!(zh, en).to_string() + match tool_rule(rule) { + Some(r) => guard_rule_name("inspect_tools", rule, false, &r.name), + None => rule.to_string(), + } } /// 网关本身的状态。**这是唯一的 critical** —— 它一停,所有客户端都在瞎 diff --git a/src-tauri/src/notices/tests.rs b/src-tauri/src/notices/tests.rs index 1b53594c..8666b7f5 100644 --- a/src-tauri/src/notices/tests.rs +++ b/src-tauri/src/notices/tests.rs @@ -955,10 +955,11 @@ fn secrets(provider: &str, items: &[(&str, bool)]) -> tw_api::Event { .map(|(rule, recorded)| tw_api::SecretItem { rule: (*rule).into(), custom: false, - kind: tw_api::SecretKind::Personal, + kind: tw_api::SecretKind::Contact, masked: "z…@example.com".into(), count: 1, replaced: !*recorded, + source: None, detail: hit_detail( tw_api::SecurityDirection::Request, rule, @@ -1019,6 +1020,63 @@ fn a_record_only_hit_of_every_guard_is_told_with_rule_and_upstream_only() { }); } +/// 「ThinkWatch 中保存的密钥」的命中写明是哪一处保存的,**不带值的任何部分**;规则名用 core +/// 规则目录里的中英文名 +#[test] +fn a_saved_secret_hit_names_where_it_is_saved_and_nothing_of_the_value() { + let ev = |source: tw_api::SavedSource| tw_api::Event::SecretsFound { + id: 4, + provider: "relay".into(), + replaced: false, + items: vec![tw_api::SecretItem { + rule: "thinkwatch-saved-secret".into(), + custom: false, + kind: tw_api::SecretKind::Saved, + masked: String::new(), + count: 2, + replaced: false, + source: Some(source), + detail: hit_detail( + tw_api::SecurityDirection::Request, + "thinkwatch-saved-secret", + "Credentials saved in ThinkWatch", + true, + ), + }], + at_ms: T0, + }; + let upstream = tw_api::SavedSource { + kind: tw_api::SavedSourceKind::UpstreamKey, + name: "DeepSeek".into(), + }; + with_lang(Lang::Zh, || { + let s = rules::from_event(&ev(upstream.clone())).remove(0); + assert_eq!(s.title, "安全规则「ThinkWatch 中保存的密钥」命中"); + assert_eq!( + s.body, + "密钥与隐私:经上游「relay」的请求含有上游「DeepSeek」的密钥,已记入安全日志。" + ); + let s = rules::from_event(&ev(tw_api::SavedSource { + kind: tw_api::SavedSourceKind::ControlKey, + name: String::new(), + })) + .remove(0); + assert!(s.body.contains("含有远程控制密钥"), "{}", s.body); + }); + with_lang(Lang::En, || { + let s = rules::from_event(&ev(upstream.clone())).remove(0); + assert_eq!( + s.title, + "Security Rule “Credentials saved in ThinkWatch” Matched" + ); + assert!( + s.body.contains("contained the key of upstream “DeepSeek”"), + "{}", + s.body + ); + }); +} + /// 防护处于「监控」时每条规则都按仅记录处理:「切断」规则命中了也没切,**照「仅记录」说**, /// 不再说「可疑调用、建议在客户端拒绝」 #[test] diff --git a/src/generated/tw-api.ts b/src/generated/tw-api.ts index 5d821901..69aaaad4 100644 --- a/src/generated/tw-api.ts +++ b/src/generated/tw-api.ts @@ -1,6 +1,6 @@ // Generated by tw-api (`tw_api::ts::export_all`). Do not edit by hand. -export const CONTROL_API_VERSION = 55; +export const CONTROL_API_VERSION = 56; /** * 手动中止(`POST /request/{id}/abort`、`POST /sessions/{id}/abort`)叫停了哪几个请求。 @@ -2005,7 +2005,12 @@ mode: GuardMode, /** * 按界面上的顺序:内置的在前,自定义的在后 */ -rules: Array, }; +rules: Array, +/** + * 规则表的分组,按界面上的顺序;`rules` 里每一条的 `kind` 都在这里,最后一组是 + * `custom`(没有自定义规则时也在)。界面的分组名取这里的 `name` / `name_zh` + */ +kinds: Array, }; /** * 一项防护的档位。 @@ -2916,7 +2921,7 @@ base_version?: string | null, }; /** * 一条规则按什么认。**给界面说明用**,界面按类型写成自己的话。 */ -export type Matcher = { "kind": "prefix", prefix: string, min_tail: number, } | { "kind": "openai-legacy", min_len: number, } | { "kind": "pem" } | { "kind": "jwt" } | { "kind": "conn-string" } | { "kind": "private-ip" } | { "kind": "domain-suffix", suffixes: Array, } | { "kind": "cn-resident-id", born_since: number, } | { "kind": "bank-card", networks: Array, } | { "kind": "email" } | { "kind": "cn-mobile-phone" } | { "kind": "regex", pattern: string, +export type Matcher = { "kind": "prefix", prefix: string, min_tail: number, } | { "kind": "openai-legacy", min_len: number, } | { "kind": "pem" } | { "kind": "jwt" } | { "kind": "conn-string" } | { "kind": "private-ip" } | { "kind": "domain-suffix", suffixes: Array, } | { "kind": "cn-resident-id", born_since: number, } | { "kind": "bank-card", networks: Array, } | { "kind": "email" } | { "kind": "cn-mobile-phone" } | { "kind": "prefixes", prefixes: Array, } | { "kind": "saved" } | { "kind": "assignment", suffixes: Array, } | { "kind": "labeled", names: Array, } | { "kind": "regex", pattern: string, /** * 分大小写:工具调用审查的规则(开头写了 `(?i)` 的除外)、写明了要分的出站脱敏和 * 内容过滤的自定义规则。内容过滤的内置规则不分 @@ -3628,6 +3633,15 @@ settings_schema: Array, */ sha256: string, status: PluginStatus, stats: PluginStats, }; +/** + * [`Matcher::Prefixes`] 里的一个前缀。 + */ +export type PrefixView = { prefix: string, min_tail: number, +/** + * `any` / `hex` / `alnum` + */ +tail: string, }; + /** * 一个模型的单价,**每百万 tokens 的美元**,和厂商定价页上印的一样。 * @@ -4764,7 +4778,7 @@ builtin: boolean, */ id: string, /** - * 显示的名字:内置规则的英文名(界面按 id 查自己的名称表),自定义规则就是它的名字 + * 显示的名字:内置规则的英文名(中文名按 id 在规则视图里取 `name_zh`),自定义规则就是它的名字 */ name: string, /** @@ -4929,6 +4943,16 @@ tokens_per_sec: number | null, */ output_tokens: number | null, }; +/** + * 一个保存的凭据是哪一项。`name` 是上游名、访问密钥名、代理名;控制面密钥是空的 + */ +export type SavedSource = { kind: SavedSourceKind, name: string, }; + +/** + * 保存的凭据是哪一种。界面按它说「上游「DeepSeek」的密钥」「代理「公司」的口令」 + */ +export type SavedSourceKind = "upstream-key" | "upstream-header" | "access-key" | "proxy-password" | "control-key" | "sync-credential"; + /** * 一页搜索停在哪儿的原因。 */ @@ -4961,12 +4985,16 @@ replaced: boolean, /** * 每一处在哪儿、当时的规则、具体做了什么(见 [`SecurityHitDetail`]) */ -detail: SecurityHitDetail, }; +detail: SecurityHitDetail, +/** + * 是哪一项保存的凭据(规则 `thinkwatch-saved-secret`:这时 `masked` 是空的)。别的规则没有 + */ +source?: SavedSource | null, }; /** * 出站脱敏找到的东西属于哪一类。 */ -export type SecretKind = "api-keys" | "private-keys" | "jwt" | "conn-strings" | "personal" | "internal" | "custom"; +export type SecretKind = "saved" | "ai-services" | "dev-platforms" | "cloud" | "business" | "private-keys" | "jwt" | "conn-strings" | "in-context" | "crypto" | "id-documents" | "foreign-ids" | "financial" | "contact" | "location" | "vehicle" | "device" | "labeled-personal" | "internal" | "custom"; /** * 各项防护在一段时间里各留下了几条记录。 @@ -5112,7 +5140,11 @@ rule_snapshot: RuleSnapshot, /** * 具体做了什么:换成了哪个占位符、切断的是哪个调用、客户端收到了什么 */ -outcome_detail: OutcomeDetail, }; +outcome_detail: OutcomeDetail, +/** + * 出站脱敏认出的是哪一项保存的凭据(规则 `thinkwatch-saved-secret`)。别的没有 + */ +source?: SavedSource | null, }; /** * 安全日志的一页。**按时间倒序**,`more` 说后面还有没有。 @@ -5159,6 +5191,23 @@ locations: Array, */ more_locations: number, rule_snapshot: RuleSnapshot, outcome_detail: OutcomeDetail, }; +/** + * 规则表的一个分组。 + */ +export type SecurityKindView = { +/** + * 规则的 `kind` 写的就是它(`ai-services`、`command`、`invisible`、`custom`……) + */ +slug: string, +/** + * 英文名 + */ +name: string, +/** + * 中文名 + */ +name_zh: string, }; + /** * 安全日志的一条做了什么。 */ @@ -5199,17 +5248,39 @@ export type SecurityRuleView = { */ id: string, custom: boolean, /** - * 英文名。界面按 id 查自己的名称表,查不到才用它;自定义规则就是名字 + * 英文名;自定义规则就是用户起的名字 */ name: string, /** - * 为什么值得看一眼(英文)。规则名说得清的、自定义规则没有 + * 中文名。内置规则都有;自定义规则没有(名字原样显示,不分语言)。 + * + * **规则的名字和说明只在 core 写一份**(`data/redact.yaml`、`rules.yaml`、 + * `content.yaml`),界面按自己的语言取 `name` 或 `name_zh`,不再自己存名称表 + */ +name_zh?: string, +/** + * 为什么值得看一眼(英文):工具调用审查和内容过滤的内置规则,规则名说不清的才有。 + * 自定义规则没有 */ why?: string, /** - * 类别。出站脱敏:`api-keys` … `personal` / `internal` / `custom`;工具调用审查: - * `command` / `custom`;内容过滤:`invisible` / `injection` / `persona` / `chinese` / - * `custom` + * 同上,中文。`why` 有的它都有 + */ +why_zh?: string, +/** + * 认什么,给人看的一句话(英文):出站脱敏的内置规则都有,别的没有。按位置认的写明只换 + * 哪一段(「only the value is replaced」)。**反引号括起来的一段是代码**(前缀、写法), + * 界面按代码样式显示、不显示反引号本身;反引号总是成对的 + */ +about?: string, +/** + * 同上,中文 + */ +about_zh?: string, +/** + * 分组,[`GuardDetail::kinds`] 里的一个 `slug`。出站脱敏:`saved` / `ai-services` … + * `internal` / `custom`;工具调用审查:`command` / `custom`;内容过滤:`invisible` / + * `injection` / `persona` / `chinese` / `custom` */ kind: string, matcher: Matcher, enabled: boolean, /** @@ -5262,7 +5333,11 @@ excerpt: string, /** * 第三档下做什么 */ -action?: RuleAction | null, }; +action?: RuleAction | null, +/** + * 出站脱敏认出的是一项保存的凭据(`thinkwatch-saved-secret`):是哪一项。这时 `excerpt` 是空的 + */ +source?: SavedSource | null, }; /** * 拿一段文本试一试。 diff --git a/src/i18n/core.i18n.ts b/src/i18n/core.i18n.ts index 9932c168..7122b6af 100644 --- a/src/i18n/core.i18n.ts +++ b/src/i18n/core.i18n.ts @@ -39,29 +39,6 @@ const MESSAGES: Record = CORE_ZH.messages; */ const CONTEXTS: { arg: string; en: string; zh: string }[] = CORE_ZH.contexts; -/** - * 内置内容规则为什么值得看一眼(隐藏字符那一组有)。按规则 id 查,查不到就用 core 的原话。 - * - * **和工具调用规则的那一句(`ruleWhy`)分两张表**:两项的规则 id 是各起各的,同一个 id - * (`you-are-now`)在两边说的不是一件事 - */ -export function contentWhy(rule: string, text: string): string { - if (getLang() === "en") return text; - return CORE_TABLES.content_why?.[rule] ?? text; -} - -/** - * 内置扫描规则命中之后那一句「为什么」。 - * - * **它不是 `Msg`,是一个规则 id 加一句话** —— 工具调用防火墙的事件里带 - * 的就是这两样。内置规则查表说中文;用户自己加的规则查不到,那句话本来 - * 就是他自己写的,原样显示。 - */ -export function ruleWhy(rule: string, text: string): string { - if (getLang() === "en") return text; - return CORE_TABLES.rule_why?.[rule] ?? text; -} - /** * 一句没有码的话,包成 [`Msg`]。 * diff --git a/src/i18n/core.test.ts b/src/i18n/core.test.ts index b6df7942..ce019e3f 100644 --- a/src/i18n/core.test.ts +++ b/src/i18n/core.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from "vitest"; import { setLang } from "./index"; -import { coreText, errorText, plain, ruleWhy } from "./core.i18n"; +import { coreText, errorText, plain } from "./core.i18n"; import CORE_ZH from "./core.zh.json"; import CASE_FILE from "./core.zh.cases.json"; import { compile } from "./template"; @@ -202,24 +202,6 @@ describe("扫描发现:句子由词拼出来", () => { }); }); -describe("工具调用防火墙命中的规则", () => { - it("内置规则说中文", () => { - expect( - inLang("zh", () => ruleWhy("curl-pipe-sh", "Downloads and runs it straight away")), - ).toBe("下载后直接执行,执行的内容由远端决定且无法预先查看"); - }); - - it("用户自己写的规则原样显示", () => { - expect(inLang("zh", () => ruleWhy("我的规则", "我自己写的理由"))).toBe("我自己写的理由"); - }); - - it("英文界面用 core 给的那句", () => { - expect(inLang("en", () => ruleWhy("curl-pipe-sh", "Downloads and runs it"))).toBe( - "Downloads and runs it", - ); - }); -}); - describe("invoke 抛出来的东西", () => { it("控制面的失败是一个带码的对象,按码翻", () => { const e = { diff --git a/src/i18n/core.zh.json b/src/i18n/core.zh.json index 67e669fa..8c83f924 100644 --- a/src/i18n/core.zh.json +++ b/src/i18n/core.zh.json @@ -142,38 +142,6 @@ "thinkwatch-data": "读写 ThinkWatch 自己的数据", "upload-file-to-host": "上传本地文件到外部主机" }, - "secret_rule": { - "anthropic-api-key": "Anthropic API 密钥", - "openai-api-key": "OpenAI API 密钥", - "openai-project-key": "OpenAI 项目密钥", - "github-personal-token": "GitHub 个人令牌", - "github-oauth-token": "GitHub OAuth 令牌", - "github-server-token": "GitHub 服务器令牌", - "github-user-token": "GitHub 用户令牌", - "github-fine-grained-token": "GitHub 细粒度令牌", - "slack-bot-token": "Slack 机器人令牌", - "slack-user-token": "Slack 用户令牌", - "slack-app-token": "Slack 应用令牌", - "aws-access-key-id": "AWS 访问密钥 ID", - "aws-temporary-key-id": "AWS 临时访问密钥 ID", - "google-api-key": "Google API 密钥", - "google-oauth-token": "Google OAuth 令牌", - "gitlab-token": "GitLab 令牌", - "stripe-live-key": "Stripe 生产密钥", - "stripe-restricted-key": "Stripe 受限密钥", - "npm-token": "npm 令牌", - "digitalocean-token": "DigitalOcean 令牌", - "sendgrid-key": "SendGrid 密钥", - "private-key": "私钥", - "jwt": "JWT", - "conn-string-password": "连接串口令", - "cn-resident-id": "居民身份证号", - "bank-card": "银行卡号", - "email": "邮箱地址", - "cn-mobile-phone": "中国大陆手机号", - "internal-ip": "内网地址", - "internal-domain": "内部域名" - }, "content_rule": { "unicode-tags": "Unicode 标签字符", "bidi-controls": "双向控制符", @@ -202,12 +170,6 @@ "zh-system-prompt": "系统提示词(中文)", "zh-jailbreak": "越狱(中文)" }, - "content_why": { - "unicode-tags": "在编辑器中完全不可见,但会原样进入模型上下文,可用于隐藏整段指令。", - "bidi-controls": "可使屏幕上的显示顺序与实际字符顺序不一致。", - "zero-width": "在编辑器中不可见,但会被模型读到。表情符号、波斯文等正常文字也会用到。", - "private-use": "没有标准含义。部分图标字体会用到。" - }, "zai_step": { "authorize_url": "获取授权地址", "wait_authorization": "等待授权", diff --git a/src/labels.i18n.ts b/src/labels.i18n.ts index 017c6e27..837f95d1 100644 --- a/src/labels.i18n.ts +++ b/src/labels.i18n.ts @@ -1,6 +1,4 @@ import { messages } from "@/i18n"; -// 密钥与隐私的规则名和 core 消息的中文是同一张表(Rust 写系统通知时也读它) -import CORE_ZH from "@/i18n/core.zh.json"; /** `labels.ts` 里那些名称表和说法 */ export const labelsText = messages( @@ -127,10 +125,6 @@ export const labelsText = messages( semantics: "语义", }, - // ------------------------------------------------------------ 安全 - /** 密钥与隐私的内置规则名。和系统通知(Rust)共用 core.zh.json 里的同一张表 */ - secrets: CORE_ZH.tables.secret_rule as Record, - // ------------------------------------------------------------ 客户端接管 takesEffect: { immediately: "下一个请求即使用新配置。", @@ -254,38 +248,6 @@ export const labelsText = messages( semantics: "Semantic", }, - secrets: { - "anthropic-api-key": "Anthropic API key", - "openai-api-key": "OpenAI API key", - "openai-project-key": "OpenAI project key", - "github-personal-token": "GitHub personal access token", - "github-oauth-token": "GitHub OAuth token", - "github-server-token": "GitHub server token", - "github-user-token": "GitHub user token", - "github-fine-grained-token": "GitHub fine-grained token", - "slack-bot-token": "Slack bot token", - "slack-user-token": "Slack user token", - "slack-app-token": "Slack app token", - "aws-access-key-id": "AWS access key ID", - "aws-temporary-key-id": "AWS temporary access key ID", - "google-api-key": "Google API key", - "google-oauth-token": "Google OAuth token", - "gitlab-token": "GitLab token", - "stripe-live-key": "Stripe live key", - "stripe-restricted-key": "Stripe restricted key", - "npm-token": "npm token", - "digitalocean-token": "DigitalOcean token", - "sendgrid-key": "SendGrid key", - "private-key": "Private key", - jwt: "JWT", - "conn-string-password": "Connection string password", - "cn-resident-id": "Chinese resident ID number", - "bank-card": "Bank card number", - email: "Email address", - "cn-mobile-phone": "Chinese mainland mobile number", - "internal-ip": "Internal IP address", - "internal-domain": "Internal domain", - }, takesEffect: { immediately: "The next request uses the new configuration.", diff --git a/src/labels.ts b/src/labels.ts index d87b9894..9ebc5562 100644 --- a/src/labels.ts +++ b/src/labels.ts @@ -286,11 +286,6 @@ export function stageLabel(stage: ConfigStage): string { // ---------------------------------------------------------------- 安全 -/** 出站检测和脱敏识别出的凭据种类 */ -export function secretLabel(secret: string): string { - const secrets: Record = textOf(labelsText).secrets; - return secrets[secret] ?? secret; -} // ---------------------------------------------------------------- 客户端接管 diff --git a/src/lib/resource.ts b/src/lib/resource.ts index a19db64e..efdfeda6 100644 --- a/src/lib/resource.ts +++ b/src/lib/resource.ts @@ -322,6 +322,14 @@ export function invalidate(prefix: string) { } } +/** + * 缓存里现有的那一份,不取、不订阅。给渲染时顺带查一下的地方用(规则名按 id 查规则表); + * 要随数据重画的,同时挂上 `useResource`。 + */ +export function peek(key: string): T | undefined { + return cache.get(key)?.data as T | undefined; +} + /** * 不再要的一份数据:没人挂着、也没在取的时候,从缓存里拿掉。 * diff --git a/src/security/GuardTab.tsx b/src/security/GuardTab.tsx index 13be914e..dd7aefab 100644 --- a/src/security/GuardTab.tsx +++ b/src/security/GuardTab.tsx @@ -17,8 +17,8 @@ import { cn } from "@/lib/utils"; import { useText } from "@/i18n"; import { commonText } from "@/i18n/common.i18n"; import { ACTIONS } from "./api"; -import type { Guard, GuardDetail, GuardMode, RuleAction, SecurityRuleView } from "@/types"; -import { MatcherText, modeName, modeTone, ruleWhy, viewName } from "./labels"; +import type { Guard, GuardDetail, GuardMode, RuleAction, SecurityKindView, SecurityRuleView } from "@/types"; +import { kindName, modeName, modeTone, RuleMatchText, ruleWhy, viewName } from "./labels"; import { securityLabelsText } from "./labels.i18n"; import { guardTabText, nowText } from "./GuardTab.i18n"; import { patternOf } from "./RuleDialog"; @@ -180,7 +180,14 @@ export function GuardTab({ - + ); } @@ -224,17 +231,19 @@ function RuleTable({ guard, mode, rules, + kinds, query, actions, }: { guard: Guard; mode: GuardMode; rules: SecurityRuleView[]; + /** 分组和分组名,core 给的(旧的 core 不给) */ + kinds: SecurityKindView[] | undefined; query: string; actions: RuleActions; }) { const t = useText(guardTabText); - const lt = useText(securityLabelsText); const menu = useMenu(actions); const content = guard === "content"; const span = content ? 6 : 5; @@ -242,7 +251,7 @@ function RuleTable({ const present = usePresentList(rules, keyOf); const groups = useMemo(() => { const shown = present.filter( - ({ item: r, presence }) => presence === "exit" || ruleMatches(r, viewName(guard, r), query), + ({ item: r, presence }) => presence === "exit" || ruleMatches(r, viewName(r), query), ); return groupRules(shown, (p) => p.item); }, [present, guard, query]); @@ -277,7 +286,8 @@ function RuleTable({ )} {groups.map((g) => { const live = g.rows.filter((p) => p.presence !== "exit").map((p) => p.item); - const title = (g.custom ? lt.kinds.custom : lt.kinds[g.key]) ?? g.key; + // 分组名由 core 给(`kinds`),按界面语言取 + const title = kindName(kinds, g.key); return ( ) : ( - + )} {guard === "content" && ( diff --git a/src/security/HitDetail.tsx b/src/security/HitDetail.tsx index fd9ee126..13137500 100644 --- a/src/security/HitDetail.tsx +++ b/src/security/HitDetail.tsx @@ -8,6 +8,7 @@ import { clip } from "@/traffic/transcript"; import type { HitLocation, OutcomeDetail, SecurityEventView } from "@/types"; import { replacedOf } from "./rules"; import { Highlight, toneOf, type Mark } from "./Highlight"; +import { useRuleCatalog } from "./catalog"; import { Code, matchingOf, partLabel, snapshotName } from "./labels"; import { securityLabelsText } from "./labels.i18n"; import { hitDetailText } from "./HitDetail.i18n"; @@ -53,6 +54,8 @@ export function HitDetail({ }) { const t = useText(hitDetailText); const lt = useText(securityLabelsText); + // 规则的中文名按 id 到规则目录里查(快照里只有英文名) + useRuleCatalog(); const where = lt.directions[e.direction]; const n = e.locations.length + e.more_locations; const sentModel = e.sent_model && e.sent_model !== e.model ? e.sent_model : null; diff --git a/src/security/RuleDialog.tsx b/src/security/RuleDialog.tsx index 0283db7e..b4f53e67 100644 --- a/src/security/RuleDialog.tsx +++ b/src/security/RuleDialog.tsx @@ -44,7 +44,8 @@ import type { SecurityTestHit, } from "@/types"; import { Highlight, lineOf, type Mark } from "./Highlight"; -import { MatcherText, modeName, ruleName, ruleWhy, viewName } from "./labels"; +import { catalogKinds } from "./catalog"; +import { kindName, modeName, RuleMatchText, ruleName, ruleWhy, viewName } from "./labels"; import { securityLabelsText } from "./labels.i18n"; import { ruleDialogText } from "./RuleDialog.i18n"; import { @@ -711,10 +712,10 @@ export function BuiltinRuleDialog({ - {viewName(guard, rule)} + {viewName(rule)} {t.builtin} - {why || t.category(lt.kinds[rule.kind] ?? rule.kind)} + {why || t.category(kindName(catalogKinds(guard), rule.kind))}
@@ -723,7 +724,7 @@ export function BuiltinRuleDialog({ {written?.match === "regex" ? ( {written.pattern} ) : ( - + )}
diff --git a/src/security/SecurityPage.tsx b/src/security/SecurityPage.tsx index b53a0606..fc143f71 100644 --- a/src/security/SecurityPage.tsx +++ b/src/security/SecurityPage.tsx @@ -179,7 +179,7 @@ export default function SecurityPage({ /** 启用、停用一条规则。可撤销:先拨过去,写完给「撤销」 */ function toggle(guard: Guard, r: SecurityRuleView, enabled: boolean) { - const name = viewName(guard, r); + const name = viewName(r); void undoable({ message: enabled ? t.ruleOn(name) : t.ruleOff(name), apply: () => @@ -206,7 +206,7 @@ export default function SecurityPage({ ), ); void undoable({ - message: t.actionSet(viewName(guard, r), lt.ruleActions[action] ?? action), + message: t.actionSet(viewName(r), lt.ruleActions[action] ?? action), apply: () => detail.mutate((d) => ({ ...d!, @@ -247,7 +247,7 @@ export default function SecurityPage({ guard, editing: null, seed: { - name: viewName(guard, r), + name: viewName(r), pattern: written?.pattern ?? "", match: written?.match, cased: written?.cased, diff --git a/src/security/catalog.ts b/src/security/catalog.ts new file mode 100644 index 00000000..56ba76f7 --- /dev/null +++ b/src/security/catalog.ts @@ -0,0 +1,37 @@ +import { getLang } from "@/i18n"; +import { peek, useResource } from "@/lib/resource"; +import type { Guard, SecurityDetail, SecurityKindView, SecurityRuleView } from "@/types"; +import { api } from "./api"; + +/** + * 规则目录:三项防护的规则表(`GET /security`),和安全页读的是同一份缓存。 + * + * **规则的名字、说明、分组名只在 core 写一份**,中英文各一个字段(`name` / `name_zh`、 + * `why` / `why_zh`、`about` / `about_zh`,分组是 `kinds`)。界面不再自己存名称表。 + * 安全日志、流量页的徽标只带规则 id 和英文名,中文名按 id 到这里查。 + */ +export const CATALOG_KEY = "security-detail"; + +/** + * 用到规则名的地方挂上它:没取过就取一次,取到了重画。安全页之外(流量页的徽标、请求 + * 详情)也要挂,否则那里查不到中文名。 + */ +export function useRuleCatalog(): SecurityDetail | undefined { + return useResource(CATALOG_KEY, () => api.detail()).data; +} + +/** 渲染时查一下:规则表里的这一条。还没取到、或者规则已经没有了是 `undefined` */ +export function catalogRule(guard: string, id: string): SecurityRuleView | undefined { + const d = peek(CATALOG_KEY); + return d?.[guard as Guard]?.rules.find((r) => !r.custom && r.id === id); +} + +/** 一项防护的分组。旧的 core 不给是 `undefined` */ +export function catalogKinds(guard: Guard): SecurityKindView[] | undefined { + return peek(CATALOG_KEY)?.[guard]?.kinds; +} + +/** 按界面语言取:中文界面用中文那一份,没有就退回英文 */ +export function localized(en: string | undefined, zh: string | undefined): string { + return (getLang() === "zh" && zh) || en || ""; +} diff --git a/src/security/export.test.ts b/src/security/export.test.ts index 66d4dcb1..76c080a5 100644 --- a/src/security/export.test.ts +++ b/src/security/export.test.ts @@ -1,11 +1,37 @@ -import { afterEach, describe, expect, it } from "vitest"; +import { afterEach, beforeAll, describe, expect, it } from "vitest"; import { setLang } from "@/i18n"; +import { fetchInto } from "@/lib/resource"; import type { HitLocation, SecurityEventView, SecurityEventsPage, SecurityEventsQuery } from "@/types"; import { csvCell, fetchAll, fileName, locationLine, outcomeLine, toCsv, toJson } from "./export"; +import { CATALOG_KEY } from "./catalog"; import { matchingOf, partLabel, snapshotName } from "./labels"; afterEach(() => setLang("zh")); +// 内置规则的中文名由 core 在规则表里给,按 id 查:放一份规则目录进缓存 +beforeAll(async () => { + const none = { mode: "enforce" as const, rules: [] }; + await fetchInto(CATALOG_KEY, async () => ({ + redact: { + mode: "enforce" as const, + rules: [ + { + id: "anthropic-api-key", + custom: false, + name: "Anthropic API key", + name_zh: "Anthropic API 密钥", + kind: "ai-services", + matcher: { kind: "prefix" as const, prefix: "sk-ant-", min_tail: 20 }, + enabled: true, + on_by_default: true, + }, + ], + }, + inspect_tools: none, + content: none, + })); +}); + const loc = (x: Partial): HitLocation => ({ part: "message", message_index: 3, diff --git a/src/security/export.ts b/src/security/export.ts index 0cfaf46b..157079f1 100644 --- a/src/security/export.ts +++ b/src/security/export.ts @@ -19,7 +19,7 @@ import type { } from "@/types"; import { replacedOf } from "./rules"; import { exportText } from "./export.i18n"; -import { matchingOf, partLabel, snapshotName } from "./labels"; +import { hitValue, matchingOf, partLabel, snapshotName } from "./labels"; import { securityLabelsText } from "./labels.i18n"; import { MAX_PAGE } from "./useSecurityLog"; @@ -152,7 +152,8 @@ export function toCsv(events: SecurityEventView[]): string { s.pattern ?? "", mode ? lt.matching[mode] : "", s.core_version, - e.excerpt, + // 保存的密钥没有值,写来源 + hitValue(e, e.excerpt).text, e.count, e.revealed ?? "", lt.directions[e.direction], diff --git a/src/security/labels.i18n.tsx b/src/security/labels.i18n.tsx index f4c9c274..42dcf246 100644 --- a/src/security/labels.i18n.tsx +++ b/src/security/labels.i18n.tsx @@ -1,22 +1,19 @@ import type { ReactNode } from "react"; import { messages } from "@/i18n"; -// 规则名、防护名和 core 消息的中文是同一张表(Rust 写系统通知时也读它) +// 防护名和 core 消息的中文是同一张表(Rust 写系统通知时也读它) import CORE_ZH from "@/i18n/core.zh.json"; +import type { SavedSourceKind } from "@/types"; /** 句子里要按代码样式画的那一段。怎么画由组件决定,这里只管是哪几个字 */ type Code = (text: string) => ReactNode; -/** 卡组织在中文里的名字。只有银联有通行的中文名,别家照写英文 */ -const CARD_NETWORK_ZH: Record = { UnionPay: "银联" }; - const or = (xs: ReactNode[], sep: ReactNode, last: ReactNode) => xs.flatMap((x, i) => (i === 0 ? [x] : [i === xs.length - 1 ? last : sep, x])); /** - * 各项防护共用的名称:状态、动作、类别、规则名,以及内置规则的匹配判据。 + * 各项防护共用的名称:状态、动作,以及没有 core 写的说明时规则判据的写法。 * - * **规则名只收内置的。**自定义规则的名字就是用户起的,原样显示;表里没有 - * 的,退回 core 给的英文名或 id。 + * **规则的名字、说明和分组名不在这里**:core 在规则表里给出中英文(见 `catalog.ts`)。 * * **三种状态各项一样**:开启、监控、关闭(配置里是 `enforce` / `observe` / `off`)。 */ @@ -54,20 +51,6 @@ export const securityLabelsText = messages( replace: "替换", record: "仅记录", } as Record, - kinds: { - "api-keys": "API 密钥", - "private-keys": "私钥", - jwt: "JWT", - "conn-strings": "连接串", - personal: "个人信息", - internal: "内网地址", - command: "内置", - injection: "指令覆盖", - persona: "身份与提示词", - chinese: "中文说法", - invisible: "隐藏字符", - custom: "自定义", - } as Record, custom: "自定义", /** 命中处在工具结果里(日志的 `tool` 是 `tool_result`) */ toolResult: "工具结果", @@ -104,6 +87,18 @@ export const securityLabelsText = messages( today: "今天", yesterday: "昨天", }, + /** + * 「ThinkWatch 中保存的密钥」命中的是哪一处配置里的值。**只写是哪一处,不写值的任何部分** + * (日志一条的第二行、流量页徽标的提示、导出) + */ + savedSource: { + "upstream-key": (name: string) => `上游「${name}」的密钥`, + "upstream-header": (name: string) => `上游「${name}」请求头中的值`, + "access-key": (name: string) => `网关密钥「${name}」`, + "proxy-password": (name: string) => `代理「${name}」的密码`, + "control-key": () => "远程控制密钥", + "sync-credential": () => "同步与备份凭据", + } as Record string>, /** 日志一条的第二行 */ detail: { /** 出站脱敏:同一个值在一个请求里出现了几次;内容过滤:这条规则命中了几处 */ @@ -113,56 +108,20 @@ export const securityLabelsText = messages( /** 码位规则:命中了几个字符 */ chars: (n: number) => `共 ${n.toLocaleString()} 个字符`, }, - /** 内容过滤的内置规则 */ - contentRules: CORE_ZH.tables.content_rule as Record, - /** - * 内置扫描规则的名字。工具调用审查用其中的危险命令一组;MCP 页的扫描 - * 发现两组都用(提示注入那一组只查客户端配置,不查工具调用)。 - */ - rules: CORE_ZH.tables.scan_rule as Record, matcher: { - prefix: (code: Code, prefix: string, n: number) => ( - <> - {code(prefix)} 开头,其后至少 {n} 个字符 - - ), - openaiLegacy: (code: Code, n: number) => ( - <> - {code("sk-")} 开头,全长至少 {n} 个字符,同时含字母和数字 - - ), - pem: (code: Code) => <>{code("-----BEGIN … PRIVATE KEY-----")} 至对应的 END,整段, - jwt: (code: Code) => <>三段 base64url,首段解码后含 {code('"alg"')}, - connString: (code: Code) => <>{code("协议://用户:口令@主机")} 中的口令,只换口令, - privateIp: (code: Code) => ( - <> - {code("10.")}、{code("172.16–31.")}、{code("192.168.")} 开头的地址,不含 127.0.0.1 - - ), - domainSuffix: (code: Code, suffixes: string[]) => ( - <>以 {or(suffixes.map(code), "、", "、")} 结尾的域名 - ), - cnResidentId: (bornSince: number) => ( - <>18 位居民身份证号:地区码、{bornSince} 年以来的出生日期和校验码都对得上 - ), - /** 卡组织的名字按英文名查,查不到的照写 */ - bankCard: (networks: string[]) => ( - <>{or(networks.map((n) => CARD_NETWORK_ZH[n] ?? n), "、", "、")} 的卡号:号段、位数对得上并通过 Luhn 校验;公开的测试卡号除外 - ), - email: (code: Code) => <>邮箱地址:{code("名称@域名")}, - /** 代码里做的检查(`builtin`),按检查名说它查什么 */ + /** + * 危险命令里代码实现的检查(`builtin`),按检查名说它查什么。密钥与隐私的内置规则不在 + * 这里:它们的说明由 core 给(`about` / `about_zh`) + */ builtin: { "credential-to-network": "凭据发往本机和该凭据的服务商以外的主机", "file-to-network": "本地文件的内容上传到外部主机", "thinkwatch-data": "路径或命令指向 ThinkWatch 的数据目录;只是提到不算", } as Record, + /** 说明缺了时列出的几种写法之间 */ + listSep: "、", /** 没见过的检查名 */ builtinOther: "由内置检查判断", - cnMobilePhone: (code: Code) => ( - <> - 中国大陆手机号:{code("1")} 开头的 11 位数字,第二位为 3 到 9,前后不紧挨其他数字 - - ), // 正则一般区分大小写,不区分的才说;包含两种都说 regex: (code: Code, pattern: string, cased: boolean) => ( <> @@ -210,20 +169,6 @@ export const securityLabelsText = messages( replace: "Replace", record: "Record only", }, - kinds: { - "api-keys": "API keys", - "private-keys": "Private keys", - jwt: "JWTs", - "conn-strings": "Connection strings", - personal: "Personal information", - internal: "Internal addresses", - command: "Built-in", - injection: "Instruction override", - persona: "Identity and prompts", - chinese: "Chinese phrasing", - invisible: "Hidden characters", - custom: "Custom", - }, custom: "Custom", toolResult: "tool result", parts: { @@ -254,103 +199,27 @@ export const securityLabelsText = messages( today: "Today", yesterday: "Yesterday", }, + savedSource: { + "upstream-key": (name: string) => `Key of upstream “${name}”`, + "upstream-header": (name: string) => `Header value of upstream “${name}”`, + "access-key": (name: string) => `Gateway key “${name}”`, + "proxy-password": (name: string) => `Password of proxy “${name}”`, + "control-key": () => "Remote control key", + "sync-credential": () => "Sync and backup credential", + }, detail: { times: (n: number) => `${n} times`, revealed: (text: string) => `hidden text “${text}”`, chars: (n: number) => (n === 1 ? "1 character" : `${n.toLocaleString()} characters`), }, - contentRules: { - "unicode-tags": "Unicode tag characters", - "bidi-controls": "Bidirectional controls", - "zero-width": "Zero-width characters", - "private-use": "Private-use characters", - "ignore-previous-instructions": "Ignore previous instructions", - "ignore-all-previous": "Ignore all previous", - "disregard-your-instructions": "Disregard your instructions", - jailbreak: "Jailbreak", - dan: "DAN", - "developer-mode": "Developer mode", - "you-are-now": "Persona manipulation", - "new-persona": "New persona", - "act-as": "Act as", - "pretend-to-be": "Pretend to be", - "system-prompt": "System prompt extraction", - "reveal-your-instructions": "Reveal instructions", - "what-are-your-rules": "What are your rules", - "base64-wall": "Base64 smuggling", - "zh-ignore-previous": "Ignore previous instructions (Chinese)", - "zh-forget-your": "Forget your instructions (Chinese)", - "zh-do-not-follow": "Do not follow (Chinese)", - "zh-you-are-now": "You are now (Chinese)", - "zh-role-play": "Role-play (Chinese)", - "zh-reveal-your": "Reveal your instructions (Chinese)", - "zh-system-prompt": "System prompt (Chinese)", - "zh-jailbreak": "Jailbreak (Chinese)", - }, - rules: { - "ignore-previous": "Ignore previous instructions", - disregard: "Disregard the system prompt", - "you-are-now": "Reset the model's identity", - "ignore-previous-zh": "Ignore previous instructions (Chinese)", - "new-instructions-zh": "New instructions (Chinese)", - "you-are-now-zh": "Reset the model's identity (Chinese)", - "chat-marker": "Forged chat markers", - "fake-system": "Disguised system text", - "curl-pipe-sh": "Download and run", - "base64-decode-exec": "Decode and run", - "exfil-env": "Send out environment variables", - "exfil-credentials": "Send out a credential file", - "exfil-credentials-reversed": "Send out a credential file (verb first)", - "ssh-key-read": "Read a private key or cloud credential", - "write-startup-item": "Write a startup item", - "crontab-install": "Install a scheduled job", - "rm-rf-root": "Delete home or root", - "chmod-777": "World-writable permissions", - "secret-to-unknown-host": "Send a credential to an unknown host", - "thinkwatch-data": "Read or change ThinkWatch's own data", - "upload-file-to-host": "Upload a local file to an external host", - }, matcher: { - prefix: (code: Code, prefix: string, n: number) => ( - <> - Starts with {code(prefix)}, followed by at least {n} characters - - ), - openaiLegacy: (code: Code, n: number) => ( - <> - Starts with {code("sk-")}, at least {n} characters, with both letters and digits - - ), - pem: (code: Code) => <>From {code("-----BEGIN … PRIVATE KEY-----")} to its END, as a whole, - jwt: (code: Code) => <>Three base64url parts; the first decodes to text containing {code('"alg"')}, - connString: (code: Code) => <>Only the password in {code("scheme://user:password@host")}, - privateIp: (code: Code) => ( - <> - Addresses starting with {code("10.")}, {code("172.16–31.")} or {code("192.168.")}; not - 127.0.0.1 - - ), - domainSuffix: (code: Code, suffixes: string[]) => ( - <>Domains ending in {or(suffixes.map(code), ", ", " or ")} - ), - cnResidentId: (bornSince: number) => ( - <>An 18-character resident ID number whose region code, birth date since {bornSince} and check character all check out - ), - bankCard: (networks: string[]) => ( - <>A {or(networks, ", ", " or ")} card number whose prefix and length match and that passes the Luhn check; public test card numbers excepted - ), - email: (code: Code) => <>Email addresses: {code("name@domain")}, builtin: { "credential-to-network": "A credential sent to a host other than this machine and the credential's provider", "file-to-network": "The contents of a local file uploaded to an external host", "thinkwatch-data": "A path or command that points into ThinkWatch's data directory; a mention does not count", }, + listSep: ", ", builtinOther: "Decided by a built-in check", - cnMobilePhone: (code: Code) => ( - <> - Chinese mainland mobile numbers: 11 digits starting with {code("1")}, the second 3 to 9, not run together with other digits - - ), regex: (code: Code, pattern: string, cased: boolean) => ( <> Regex {code(pattern)} diff --git a/src/security/labels.test.ts b/src/security/labels.test.ts index f810a1c2..e2c1c86f 100644 --- a/src/security/labels.test.ts +++ b/src/security/labels.test.ts @@ -124,13 +124,14 @@ describe("码位规则的命中", () => { }); }); -/** 内置内容规则的说明:中文查表;英文那句以名字开头的,名字去掉(名字在上一行) */ +/** 内置内容规则的说明:中文取 core 给的 why_zh;英文那句以名字开头的,名字去掉(名字在上一行) */ describe("内容规则的说明", () => { const rule = (why: string): SecurityRuleView => ({ id: "bidi-controls", custom: false, name: "Bidirectional controls", why, + why_zh: "可使屏幕上的显示顺序与实际字符顺序不一致。", kind: "invisible", matcher: { kind: "codepoints", ranges: ["U+202A–U+202E", "U+2066–U+2069"] }, enabled: true, @@ -139,7 +140,7 @@ describe("内容规则的说明", () => { default_action: "strip", }); - it("中文查表", () => { + it("中文取 why_zh", () => { expect(ruleWhy("content", rule("Bidirectional controls: they reorder text."))).toBe( "可使屏幕上的显示顺序与实际字符顺序不一致。", ); diff --git a/src/security/labels.tsx b/src/security/labels.tsx index a34ec4b1..1d4d32dc 100644 --- a/src/security/labels.tsx +++ b/src/security/labels.tsx @@ -1,17 +1,19 @@ +import { Fragment } from "react"; import { StatusLabel, type StatusTone } from "@/ui/status-dot"; import { getLang, textOf, useText } from "@/i18n"; -import { contentWhy, ruleWhy as coreRuleWhy } from "@/i18n/core.i18n"; -import { secretLabel } from "@/labels"; import type { ContentMatch, Guard, GuardMode, HitLocation, Matcher, + SavedSource, SecurityEventView, + SecurityKindView, SecurityOutcome, SecurityRuleView, } from "@/types"; +import { catalogRule, localized } from "./catalog"; import { securityLabelsText } from "./labels.i18n"; /** @@ -51,50 +53,48 @@ export function outcomeTone(action: SecurityOutcome): StatusTone { export const OUTCOMES: readonly SecurityOutcome[] = ["cut", "blocked", "stripped", "replaced", "recorded"]; /** - * 一条规则叫什么。 + * 一条规则叫什么:日志、流量页徽标这些只带 id 的地方用。 * - * 出站脱敏的内置规则 id 就是凭据种类(`anthropic-api-key` …),和流量页上 - * 那张名称表是同一张;另两项各查这里的一张表:工具调用审查查扫描规则那张, - * 内容过滤查内容规则那张(隐藏字符那一组也在里面)。 - * **自定义规则的 id 就是用户起的名字**,原样显示。表里都没有的,退回 core - * 给的英文名,再没有就是 id。 + * **名字由 core 给**(规则表里的 `name` / `name_zh`),这里按 id 到规则目录里查(见 + * `catalog.ts`;用到的组件要挂上 `useRuleCatalog`)。**自定义规则的 id 就是用户起的名字**, + * 原样显示。目录还没取到、或者规则已经没有了,退回日志里存的英文名,再没有就是 id。 */ export function ruleName(guard: string, id: string, custom?: boolean, fallback?: string): string { if (custom) return id; - const t = textOf(securityLabelsText); - switch (guard) { - case "redact": { - const name = secretLabel(id); - return name !== id ? name : (fallback ?? id); - } - case "content": - return t.contentRules[id] ?? fallback ?? id; - default: - return t.rules[id] ?? fallback ?? id; - } + const r = catalogRule(guard, id); + return r ? viewName(r) : (fallback ?? id); } -/** 规则列表里的一条叫什么 */ -export function viewName(guard: Guard, r: SecurityRuleView): string { - return ruleName(guard, r.id, r.custom, r.name); +/** 规则列表里的一条叫什么:内置的按界面语言取 `name` / `name_zh`,自定义的就是它的名字 */ +export function viewName(r: SecurityRuleView): string { + return r.custom ? r.name : localized(r.name, r.name_zh); } /** - * 内置规则为什么值得看一眼。中文查词表,查不到就用 core 的原话。 - * - * 内容规则(隐藏字符那一组有)和工具调用规则各查各的表:两边的 id 是各起各的。 + * 内置规则为什么值得看一眼(危险命令、内容过滤有;规则名说得清的没有)。按界面语言取 + * `why` / `why_zh`。 */ export function ruleWhy(guard: Guard, r: SecurityRuleView): string { if (r.custom || !r.why) return ""; - if (guard !== "content") return coreRuleWhy(r.id, r.why); - const why = contentWhy(r.id, r.why); - // 英文那句可能以名字开头(「Bidirectional controls: …」),名字已经在上一行了 + const why = localized(r.why, r.why_zh); + if (guard !== "content" || why !== r.why) return why; + // 内容规则英文那句可能以名字开头(「Bidirectional controls: …」),名字已经在上一行了 const lead = `${r.name}:`.toLowerCase(); if (!why.toLowerCase().startsWith(lead)) return why; const rest = why.slice(lead.length).trimStart(); return rest.charAt(0).toUpperCase() + rest.slice(1); } +/** + * 规则表的分组叫什么:core 给的 `kinds` 里按 `slug` 查。查不到(旧的 core 不给)时 + * 自定义那一组照样叫「自定义」,其余写 slug。 + */ +export function kindName(kinds: readonly SecurityKindView[] | undefined, slug: string): string { + const k = kinds?.find((x) => x.slug === slug); + if (k) return localized(k.name, k.name_zh); + return slug === "custom" ? textOf(securityLabelsText).custom : slug; +} + /** 命中在哪儿:危险命令是哪个工具;内容过滤是「工具结果」,或者「回答」(查的是回答正文) */ export function whereOf(e: SecurityEventView): string | null { const t = textOf(securityLabelsText); @@ -128,7 +128,7 @@ export function partLabel(l: HitLocation): string { /** * 命中那一刻的规则叫什么。**按快照,不按现在的规则表**:规则改了名、删掉了,日志里 - * 还是当时那一条。内置规则的名字照常查词表(快照里的英文名是退路)。 + * 还是当时那一条。内置规则的名字按 id 到规则目录里查(快照里的英文名是退路)。 */ export function snapshotName(e: SecurityEventView): string { const s = e.rule_snapshot; @@ -150,6 +150,21 @@ export function byCodepoints(e: SecurityEventView): boolean { return e.guard === "content" && e.match === "codepoints"; } +/** + * 「ThinkWatch 中保存的密钥」命中的是哪一处配置里的值:上游「DeepSeek」的密钥、网关密钥 + * 「默认」…… **只有来源,没有值**:这条规则报出来的值是空的,连头尾都不留 —— 来源已经 + * 说清楚是哪一把。不认识的来源(更新的 core)退回名字本身。 + */ +export function savedSourceText(s: SavedSource): string { + const f = textOf(securityLabelsText).savedSource[s.kind] as ((name: string) => string) | undefined; + return f ? f(s.name) : s.name; +} + +/** 安全日志一条(或流量页上的一项)的值那一段:保存的密钥写来源,其余是打码后的值 */ +export function hitValue(x: { source?: SavedSource | null }, masked: string): { text: string; mono: boolean } { + return x.source ? { text: savedSourceText(x.source), mono: false } : { text: masked, mono: true }; +} + /** * 一次命中的细节,日志和请求详情里的第二行。 * @@ -167,10 +182,13 @@ export function EventDetail({ e, codepoints = false }: { e: SecurityEventView; c {e.revealed ? t.revealed(e.revealed) : {e.excerpt}} ); + // 保存的密钥没有值可写,写来源;值是空的(更早的记录)就只剩次数 + const v = hitValue(e, e.excerpt); + const times = e.count > 1 ? t.times(e.count) : null; return ( <> - {e.excerpt} - {e.count > 1 && ` · ${t.times(e.count)}`} + {v.text && {v.text}} + {times && (v.text ? ` · ${times}` : times)} ); } @@ -187,32 +205,59 @@ export function Code({ children }: { children: string }) { const code = (s: string) => {s}; -/** 内置规则的匹配判据,写成一句话 */ -export function MatcherText({ m }: { m: Matcher }) { - const t = useText(securityLabelsText).matcher; +/** + * core 写的一句说明,反引号括起来的一段画成代码(反引号本身不显示)。反引号总是成对的; + * 万一落单,最后那一段照普通文字画 + */ +export function CoreText({ text }: { text: string }) { + const parts = text.split("`"); + const paired = parts.length % 2 === 1; + return ( + <> + {parts.map((p, i) => + i % 2 === 1 && (paired || i < parts.length - 1) ? ( + {p} + ) : ( + {i % 2 === 1 ? `\`${p}` : p} + ), + )} + + ); +} + +/** + * 一条规则认什么,写成一句话。 + * + * **密钥与隐私的内置规则由 core 写**(`about` / `about_zh`)。其余的按判据写:自定义 + * 规则和内容规则是正则、包含、码位,危险命令里代码实现的检查按检查名说它查什么。 + */ +export function RuleMatchText({ r }: { r: SecurityRuleView }) { + useText(securityLabelsText); + const about = r.custom ? "" : localized(r.about, r.about_zh); + return about ? : ; +} + +/** 判据里能列出来的写法(前缀、后缀、变量名、字段名):说明缺了的时候至少列出这些 */ +function listed(m: Matcher): string[] { switch (m.kind) { case "prefix": - return t.prefix(code, m.prefix, m.min_tail); - case "openai-legacy": - return t.openaiLegacy(code, m.min_len); - case "pem": - return t.pem(code); - case "jwt": - return t.jwt(code); - case "conn-string": - return t.connString(code); - case "private-ip": - return t.privateIp(code); + return [m.prefix]; + case "prefixes": + return m.prefixes.map((p) => p.prefix); case "domain-suffix": - return t.domainSuffix(code, m.suffixes); - case "cn-resident-id": - return t.cnResidentId(m.born_since); - case "bank-card": - return t.bankCard(m.networks.map((n) => n.name)); - case "email": - return t.email(code); - case "cn-mobile-phone": - return t.cnMobilePhone(code); + case "assignment": + return m.suffixes; + case "labeled": + return m.names; + default: + return []; + } +} + +/** 规则的判据,写成一句话。没有 core 写的说明时用 */ +export function MatcherText({ m }: { m: Matcher }) { + const t = useText(securityLabelsText).matcher; + switch (m.kind) { // 代码里做的检查没有可展示的写法:按检查名说它查什么,不说怎么查 case "builtin": return <>{t.builtin[m.check] ?? t.builtinOther}; @@ -222,6 +267,21 @@ export function MatcherText({ m }: { m: Matcher }) { return t.contains(code, m.text, m.case_sensitive ?? false); case "codepoints": return t.codepoints(code, m.ranges); + default: { + // 内置脱敏规则的说明由 core 给;旧的 core 不给时,列出写法,没有写法的说是内置检查 + const xs = listed(m); + if (xs.length === 0) return <>{t.builtinOther}; + return ( + <> + {xs.map((x, i) => ( + + {i > 0 && t.listSep} + {code(x)} + + ))} + + ); + } } } diff --git a/src/security/marks.ts b/src/security/marks.ts index 4f8e7e2f..230546e7 100644 --- a/src/security/marks.ts +++ b/src/security/marks.ts @@ -26,6 +26,7 @@ export function marksFromEvents( rule: e.rule, custom: e.custom === true, masked: e.excerpt, + source: e.source, count: e.count, replaced: e.action === "replaced", // 和实时事件那一项(`SecretItem.detail`)同一个形状 diff --git a/src/security/redact.test.tsx b/src/security/redact.test.tsx new file mode 100644 index 00000000..a9f3b5d5 --- /dev/null +++ b/src/security/redact.test.tsx @@ -0,0 +1,254 @@ +import { renderToStaticMarkup } from "react-dom/server"; +import { beforeEach, describe, expect, it } from "vitest"; +import { setLang } from "@/i18n"; +import { fetchInto, resetResources } from "@/lib/resource"; +import type { GuardDetail, SavedSource, SecurityDetail, SecurityEventView, SecurityKindView, SecurityRuleView } from "@/types"; +import { CATALOG_KEY } from "./catalog"; +import { toCsv } from "./export"; +import { CoreText, EventDetail, kindName, RuleMatchText, ruleName, ruleWhy, savedSourceText, snapshotName, viewName } from "./labels"; +import { marksFromEvents } from "./marks"; +import { groupRules } from "./rules"; + +/** + * 规则的名字、说明、分组名由 core 给(规则表里的 `name_zh` / `why_zh` / `about` / + * `about_zh`,分组是 `kinds`)。界面只管按语言取、反引号画成代码,以及缺了的时候退回得 + * 体面:中文缺了用英文,规则目录没取到用日志里存的英文名,再没有就是 id。 + */ + +const rule = (x: Partial): SecurityRuleView => ({ + id: "anthropic-api-key", + custom: false, + name: "Anthropic API key", + name_zh: "Anthropic API 密钥", + about: "Starts with `sk-ant-`, followed by at least 20 characters", + about_zh: "`sk-ant-` 开头,其后至少 20 个字符", + kind: "ai-services", + matcher: { kind: "prefix", prefix: "sk-ant-", min_tail: 20 }, + enabled: true, + on_by_default: true, + ...x, +}); + +const KINDS: SecurityKindView[] = [ + { slug: "saved", name: "Saved credentials", name_zh: "保存的密钥" }, + { slug: "ai-services", name: "AI services", name_zh: "AI 服务" }, + { slug: "custom", name: "Custom", name_zh: "自定义" }, +]; + +const guard = (rules: SecurityRuleView[], kinds: SecurityKindView[] = []): GuardDetail => ({ mode: "enforce", rules, kinds }); + +/** 把一份规则目录放进缓存,和安全页取到的一样 */ +async function seed(d: Partial) { + await fetchInto(CATALOG_KEY, async () => ({ + redact: guard([]), + inspect_tools: guard([]), + content: guard([]), + ...d, + })); +} + +const html = (node: React.ReactNode) => renderToStaticMarkup(<>{node}); +const plain = (node: React.ReactNode) => html(node).replace(/<[^>]+>/g, ""); + +beforeEach(() => resetResources()); + +describe("规则名", () => { + it("内置规则按界面语言取 core 给的名字", () => { + expect(viewName(rule({}))).toBe("Anthropic API 密钥"); + setLang("en"); + expect(viewName(rule({}))).toBe("Anthropic API key"); + }); + + it("没有中文名的退回英文名;自定义规则就是它的名字", () => { + expect(viewName(rule({ name_zh: undefined }))).toBe("Anthropic API key"); + expect(viewName(rule({ id: "客户编号", name: "客户编号", custom: true, name_zh: undefined }))).toBe("客户编号"); + }); + + it("只带 id 的地方按 id 到规则目录里查", async () => { + // 目录还没取到:日志里存的英文名,再没有就是 id + expect(ruleName("redact", "anthropic-api-key", false, "Anthropic API key")).toBe("Anthropic API key"); + expect(ruleName("redact", "anthropic-api-key")).toBe("anthropic-api-key"); + await seed({ + redact: guard([rule({})]), + inspect_tools: guard([ + rule({ id: "curl-pipe-sh", name: "Download and run", name_zh: "下载即执行", kind: "command" }), + ]), + }); + expect(ruleName("redact", "anthropic-api-key")).toBe("Anthropic API 密钥"); + expect(ruleName("inspect_tools", "curl-pipe-sh")).toBe("下载即执行"); + // 同一个 id 在别的防护里不算 + expect(ruleName("content", "curl-pipe-sh", false, "Download and run")).toBe("Download and run"); + // 规则已经没有了 + expect(ruleName("redact", "gone-rule", false, "Gone rule")).toBe("Gone rule"); + // 自定义规则原样 + expect(ruleName("redact", "客户编号", true)).toBe("客户编号"); + setLang("en"); + expect(ruleName("redact", "anthropic-api-key")).toBe("Anthropic API key"); + }); + + it("日志一条按快照的 id 查,快照里的英文名是退路", async () => { + const e = hit({ + rule: "anthropic-api-key", + rule_snapshot: { builtin: true, id: "anthropic-api-key", name: "Anthropic API key", core_version: "0.85.0" }, + }); + expect(snapshotName(e)).toBe("Anthropic API key"); + await seed({ redact: guard([rule({})]) }); + expect(snapshotName(e)).toBe("Anthropic API 密钥"); + }); +}); + +describe("规则说明", () => { + it("why:中文界面取 why_zh,缺了退回英文", () => { + const r = rule({ id: "curl-pipe-sh", why: "Runs whatever the remote serves", why_zh: "下载后直接执行" }); + expect(ruleWhy("inspect_tools", r)).toBe("下载后直接执行"); + expect(ruleWhy("inspect_tools", { ...r, why_zh: undefined })).toBe("Runs whatever the remote serves"); + expect(ruleWhy("inspect_tools", { ...r, why: "" })).toBe(""); + setLang("en"); + expect(ruleWhy("inspect_tools", r)).toBe("Runs whatever the remote serves"); + }); + + it("about:按语言取,反引号里的画成代码", () => { + expect(html()).toBe( + html( + <> + sk-ant- + {" 开头,其后至少 20 个字符"} + , + ), + ); + setLang("en"); + expect(plain()).toBe("Starts with sk-ant-, followed by at least 20 characters"); + }); + + it("反引号落单时照普通文字画", () => { + expect(plain()).toBe("a b c `d"); + expect(html().match(/ { + expect(plain()).toBe("sk-ant-"); + const vault = rule({ + about: undefined, + about_zh: undefined, + matcher: { + kind: "prefixes", + prefixes: [ + { prefix: "hvs.", min_tail: 90, tail: "any" }, + { prefix: "hvb.", min_tail: 138, tail: "any" }, + ], + }, + }); + expect(plain()).toBe("hvs.、hvb."); + expect(plain()).toBe( + "由内置检查判断", + ); + }); + + it("中文缺了用英文的 about", () => { + expect(plain()).toBe( + "Starts with sk-ant-, followed by at least 20 characters", + ); + }); + + it("危险命令里代码实现的检查、自定义规则照判据写", () => { + const check = rule({ id: "secret-to-unknown-host", about: undefined, about_zh: undefined, matcher: { kind: "builtin", check: "credential-to-network" } }); + expect(plain()).toBe("凭据发往本机和该凭据的服务商以外的主机"); + const custom = rule({ + id: "客户编号", + name: "客户编号", + custom: true, + about: "ignored", + matcher: { kind: "regex", pattern: "CUST-\\d+", case_sensitive: true }, + }); + expect(plain()).toBe("正则 CUST-\\d+"); + }); +}); + +describe("分组", () => { + it("分组名取 core 给的 kinds", () => { + expect(kindName(KINDS, "ai-services")).toBe("AI 服务"); + setLang("en"); + expect(kindName(KINDS, "ai-services")).toBe("AI services"); + }); + + it("旧的 core 不给 kinds:自定义照样叫自定义,其余写 slug", () => { + expect(kindName(undefined, "custom")).toBe("自定义"); + expect(kindName(undefined, "ai-services")).toBe("ai-services"); + expect(kindName(KINDS, "vehicle")).toBe("vehicle"); + }); + + it("按 core 给的顺序分组,保存的密钥在最前,自定义在最后", () => { + const rows = [ + rule({ id: "thinkwatch-saved-secret", kind: "saved" }), + rule({ id: "anthropic-api-key", kind: "ai-services" }), + rule({ id: "openai-api-key", kind: "ai-services" }), + rule({ id: "客户编号", custom: true, kind: "custom" }), + ]; + expect(groupRules(rows, (r) => r).map((g) => g.key)).toEqual(KINDS.map((k) => k.slug)); + }); +}); + +const hit = (x: Partial): SecurityEventView => ({ + id: 1, + at_ms: 0, + request_id: 7, + guard: "redact", + rule: "thinkwatch-saved-secret", + custom: false, + action: "replaced", + provider: "DeepSeek", + client: "默认", + model: "deepseek-chat", + excerpt: "", + count: 1, + direction: "request", + locations: [], + more_locations: 0, + rule_snapshot: { builtin: true, id: "thinkwatch-saved-secret", name: "Credentials saved in ThinkWatch", core_version: "0.85.0" }, + outcome_detail: { action: "replaced", placeholders: ["<>"] }, + ...x, +}); + +const detail = (e: SecurityEventView) => renderToStaticMarkup().replace(/<[^>]+>/g, ""); + +describe("保存的密钥的来源", () => { + const sources: [SavedSource, string, string][] = [ + [{ kind: "upstream-key", name: "DeepSeek" }, "上游「DeepSeek」的密钥", "Key of upstream “DeepSeek”"], + [{ kind: "upstream-header", name: "Azure" }, "上游「Azure」请求头中的值", "Header value of upstream “Azure”"], + [{ kind: "access-key", name: "默认" }, "网关密钥「默认」", "Gateway key “默认”"], + [{ kind: "proxy-password", name: "公司" }, "代理「公司」的密码", "Password of proxy “公司”"], + [{ kind: "control-key", name: "" }, "远程控制密钥", "Remote control key"], + [{ kind: "sync-credential", name: "" }, "同步与备份凭据", "Sync and backup credential"], + ]; + + it("每种来源都写得出", () => { + for (const [s, zh] of sources) expect(savedSourceText(s)).toBe(zh); + setLang("en"); + for (const [s, , en] of sources) expect(savedSourceText(s)).toBe(en); + }); + + it("不认识的来源退回名字", () => { + expect(savedSourceText({ kind: "new-kind" as SavedSource["kind"], name: "备份" })).toBe("备份"); + }); + + it("日志一条的第二行写来源,不写值", () => { + expect(detail(hit({ source: { kind: "upstream-key", name: "DeepSeek" } }))).toBe("上游「DeepSeek」的密钥"); + expect(detail(hit({ source: { kind: "access-key", name: "默认" }, count: 3 }))).toBe("网关密钥「默认」 · 出现 3 次"); + }); + + it("没有来源也没有值的:只剩次数,不留多余的分隔号", () => { + expect(detail(hit({ count: 2 }))).toBe("出现 2 次"); + expect(detail(hit({}))).toBe(""); + }); + + it("个人信息照常写报出来的值(只留认得出是哪一个的部分)", () => { + expect(detail(hit({ rule: "cn-address", excerpt: "北京市朝阳区…", count: 2 }))).toBe("北京市朝阳区… · 出现 2 次"); + expect(detail(hit({ rule: "iban", excerpt: "…1234" }))).toBe("…1234"); + }); + + it("翻历史拼回的徽标、导出都带着来源", () => { + const e = hit({ source: { kind: "proxy-password", name: "公司" } }); + expect(marksFromEvents([e]).secrets?.items[0]?.source).toEqual({ kind: "proxy-password", name: "公司" }); + expect(toCsv([e])).toContain("代理「公司」的密码"); + }); +}); diff --git a/src/security/rules.ts b/src/security/rules.ts index e0811183..31bc9ff2 100644 --- a/src/security/rules.ts +++ b/src/security/rules.ts @@ -6,7 +6,7 @@ import type { ContentMatch, ContentScope, Guard, OutcomeDetail, RuleAction, Secu /** 规则表里的一组:内置规则按类别各一组,自定义规则在最后一组 */ export interface RuleGroup { - /** 类别(`api-keys`、`invisible`…);工具调用的内置规则是 `command`;自定义的是 `custom` */ + /** 类别(`ai-services`、`invisible`…);工具调用的内置规则是 `command`;自定义的是 `custom` */ key: string; custom: boolean; rows: T[]; @@ -40,7 +40,7 @@ export function groupRules(rows: readonly T[], ruleOf: (row: T) => SecurityRu } /** - * 一条规则写的是什么,拿来检索:正则、要找的文字、码位、前缀、域名后缀。代码里做的检查 + * 一条规则写的是什么,拿来检索:正则、要找的文字、码位、前缀、域名后缀、变量名和字段名。代码里做的检查 * 和没有写法的内置规则(PEM、JWT…)是空的,只按名字找得到 */ export function patternText(r: SecurityRuleView): string { @@ -56,6 +56,12 @@ export function patternText(r: SecurityRuleView): string { return m.prefix; case "domain-suffix": return m.suffixes.join(" "); + case "prefixes": + return m.prefixes.map((p) => p.prefix).join(" "); + case "assignment": + return m.suffixes.join(" "); + case "labeled": + return m.names.join(" "); default: return ""; } diff --git a/src/traffic/RequestTable.tsx b/src/traffic/RequestTable.tsx index ac5486f5..33aee5e8 100644 --- a/src/traffic/RequestTable.tsx +++ b/src/traffic/RequestTable.tsx @@ -17,7 +17,8 @@ import { coreText } from "@/i18n/core.i18n"; import { cn } from "@/lib/utils"; import { compact, latency, money, ms, statusTone, tokens, traffic, when } from "@/format"; import { failureKind, notSentText, translatedText } from "@/labels"; -import { ruleName } from "@/security/labels"; +import { useRuleCatalog } from "@/security/catalog"; +import { ruleName, savedSourceText } from "@/security/labels"; import { notSent } from "@/requestRouting"; import { promptTokens, upstreamText, waitingFor, type Filter, type SortDir, type SortKey } from "@/requestTable"; import { resumeClock } from "@/upstreams/throttle"; @@ -1198,6 +1199,8 @@ function StatusCell({ r }: { r: RequestRow }) { */ function UpstreamCell({ r, via }: { r: RequestRow; via: ViaConfig | null }) { const t = useText(trafficText); + // 徽标提示里的规则名按 id 到规则目录里查(名字由 core 给),取到了重画 + useRuleCatalog(); const sent = notSent(r); const waiting = waitingFor(r); const mark = rowMark(r, via); @@ -1276,7 +1279,10 @@ function UpstreamCell({ r, via }: { r: RequestRow; via: ViaConfig | null }) { // 换掉了是灰的;原样发出去的要看得出来 variant={secrets.replaced ? "secondary" : "warning"} tip={(secrets.replaced ? t.redactedTip : t.secretsTip)( - secrets.items.map((x) => `${ruleName("redact", x.rule, x.custom)} ×${x.count}`), + // 保存的密钥写是哪一把(上游「DeepSeek」的密钥),比规则名说得清楚 + secrets.items.map( + (x) => `${x.source ? savedSourceText(x.source) : ruleName("redact", x.rule, x.custom)} ×${x.count}`, + ), )} > {(secrets.replaced ? t.redacted : t.withSecrets)(secrets.count)} diff --git a/src/traffic/drawer/Timeline.tsx b/src/traffic/drawer/Timeline.tsx index 58fe6b47..db377a46 100644 --- a/src/traffic/drawer/Timeline.tsx +++ b/src/traffic/drawer/Timeline.tsx @@ -11,6 +11,7 @@ import { appLabel, failureKind, formatLabel, notSentText } from "@/labels"; import { useNav } from "@/nav"; import { notSent } from "@/requestRouting"; import { HitDetail } from "@/security/HitDetail"; +import { useRuleCatalog } from "@/security/catalog"; import { ActionBadge, byCodepoints, EventDetail, ruleName, whereOf } from "@/security/labels"; import { pluginName } from "@/plugins/defaults"; import { pluginLabelsText } from "@/plugins/labels.i18n"; @@ -217,6 +218,8 @@ export function Timeline({ d, state }: { d: RequestDetail; state: DrawerState }) * 详情里不再给「请求」的链接;会话的链接跳到流量页的那次会话。 */ function SecurityHits({ events }: { events: SecurityEventView[] }) { + // 规则名按 id 到规则目录里查(名字由 core 给),取到了重画 + useRuleCatalog(); const nav = useNav(); const [open, setOpen] = useState>(() => new Set()); const toggle = (id: number) =>