pi-information-protecter 0.6.0 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,43 @@
1
1
  # Changelog / 更新日志
2
2
 
3
+ ## 0.6.2 — Numeric replacement retry and restoration / 数值替换重试与还原
4
+
5
+ ### Fixed / 修复
6
+
7
+ - Retry numeric replacements instead of blocking the request. Validity was checked only after a candidate had been committed, so an unlucky draw aborted the request; roughly one request in ten failed with `SCAN_NUMBER_UNSAFE` when a rule matched a decimal, and about two in three when it matched an exponent.
8
+ 数值替换改为重试而不拦截请求。之前校验发生在候选值落定之后,随机抽到不合法的值就会终止请求:规则匹配小数时约十分之一的请求以 `SCAN_NUMBER_UNSAFE` 失败,匹配指数时约三分之二。
9
+ - Judge each numeric candidate inside the whole number rather than on its own, and keep exponent digits, a multi-digit integer part's leading digit and a fractional part's trailing digit structurally valid.
10
+ 在整个数值上下文中判断候选值,并保持指数位、多位整数部分的首位和小数部分的末位在结构上有效。
11
+ - Restore redacted numbers as numbers. Since 0.6.0 a numeric field keeps its JSON type, but restoration only walked strings, so a numeric tool argument was executed with the replacement value instead of the original.
12
+ 脱敏后的数字以数字还原。0.6.0 起数值字段保持 JSON 类型,但还原只遍历字符串,导致数值类工具参数会以替换值而非原值执行。
13
+ - Give a digit run an integer-safe shape even in text, so a value that appears both as a string and as a JSON number can share one mapping. A text shape could start with a leading zero, which the number form then rejected. Long digit strings beyond the safe-integer range keep working, because the range cap applies only to numeric fields.
14
+ 纯数字串即使在文本中也使用整数安全的形状,使同时以字符串和 JSON 数字出现的值共用一份映射。文本形状可能以前导零开头,而数字形式会拒绝。超出安全整数范围的长数字串仍可使用,因为范围限制仅适用于数值字段。
15
+ - Add a regression that repeatedly redacts decimals and exponents and asserts every field stays a finite JSON number.
16
+ 新增回归:反复脱敏小数与指数,断言每个字段仍为有限的 JSON 数字。
17
+ - Add a regression asserting that one value maps to one replacement across strings, object keys, numbers, nested structures and JSON-encoded tool arguments in the same request.
18
+ 新增回归:同一请求中的字符串、对象键、数字、嵌套结构和 JSON 编码的工具参数,同一值始终替换为同一个值。
19
+
20
+ ### Notes / 说明
21
+
22
+ - A rule without digit boundaries can match inside an unrelated long number. Replacing digits in a value close to `Number.MAX_SAFE_INTEGER`, such as a tool schema upper bound, then pushed the result past the safe-integer range and blocked almost every request; add `(?<!\d)` and `(?!\d)` guards to numeric rules.
23
+ 没有数字边界的规则会在无关的长数字内部匹配。当数值接近 `Number.MAX_SAFE_INTEGER`(例如工具结构的上限)时,替换其中的数字会使结果超出安全整数范围,几乎拦截每个请求;请为数字规则加上 `(?<!\d)` 和 `(?!\d)` 边界。
24
+
25
+ ## 0.6.1 — Valid address replacements / 合法的地址替换
26
+
27
+ ### Fixed / 修复
28
+
29
+ - Keep replaced IPv4 addresses valid by bounding every octet to 0-255; per-character replacement produced octets such as `574.676.2.86`.
30
+ 替换后的 IPv4 保持每段在 0-255 内;逐字符替换会产生如 `574.676.2.86` 的非法地址。
31
+ - Keep replaced IPv6 addresses valid by replacing hex digits within the hexadecimal alphabet and preserving `::` compression, group widths and any embedded IPv4 tail; per-character replacement produced non-hex characters.
32
+ 替换后的 IPv6 保持合法:十六进制字符仍在十六进制范围内,并保留 `::` 压缩写法、分组宽度和末尾嵌入的 IPv4;逐字符替换会产生非十六进制字符。
33
+
34
+ ### Added / 新增
35
+
36
+ - Add internal-domain, email, IPv4 and IPv6 rule examples to `protecter.example.json`, including a fixed-alias domain.
37
+ 在示例配置中新增内网域名、邮箱、IPv4 和 IPv6 规则示例,包含一个固定别名域名。
38
+ - Add regressions asserting `net.isIPv4`/`net.isIPv6` acceptance, `::` preservation, no false match on version-like numbers, and that the shipped example file loads and protects its targets.
39
+ 新增回归:验证替换结果通过 `net.isIPv4`/`net.isIPv6`、保留 `::`、不误匹配类版本号数字,以及发布的示例配置可加载并生效。
40
+
3
41
  ## 0.6.0 — Shape-preserving replacements and single-language messages / 同形替换与单语言消息
4
42
 
5
43
  ### Changed / 变更
package/README.md CHANGED
@@ -53,7 +53,7 @@ Validated configuration is cached per extension instance. Outgoing scans and sta
53
53
  }
54
54
  ```
55
55
 
56
- More examples are in [`protecter.example.json`](protecter.example.json); they are not automatically enabled. Number patterns do not validate authenticity or check digits. Prefer exact values for names and passwords.
56
+ More examples are in [`protecter.example.json`](protecter.example.json), covering internal domains, email addresses, IPv4 and IPv6; they are not automatically enabled. Review the address and domain patterns before use: they match public values too, so a rule that is too broad can redact documentation examples, loopback addresses or dependency hosts. Number patterns do not validate authenticity or check digits. Prefer exact values for names and passwords.
57
57
 
58
58
  - **Literals:** strings and `literal` rules match all case-sensitive occurrences, without regex interpretation.
59
59
  - **Regex:** JavaScript regex replaces the entire match. Use lookbehind to match only a credential value. Flags `g i m s u` are supported; `g` is added automatically. `/pattern/flags` shorthand is not supported.
@@ -94,8 +94,12 @@ Since 0.6.0 a matched value is replaced by a random value of the **same shape**
94
94
  | `张三` | `丅欕` | CJK script, character count |
95
95
  | `Nguyễn Văn Tèo` | `Ufefṉg Zżt Sàb` | Latin with diacritics, word boundaries |
96
96
  | `ひらがな` | `ゅせはっ` | Hiragana |
97
+ | `192.168.1.10` | `101.203.3.77` | valid IPv4, octets ≤ 255 |
98
+ | `fe80::1ff:fe23:4567` | `af44::6ea:bf19:2700` | valid IPv6, `::` and group widths |
97
99
 
98
- This fixes provider errors such as `'9007199254740991' is not of type 'number'`, which happened when a numeric field was replaced by a token string. Numeric JSON values stay numbers, keep their digit count and remain safe integers. If a partial match cannot round-trip as a number, the request is blocked with `SCAN_NUMBER_UNSAFE` instead of sending an invalid type.
100
+ This fixes provider errors such as `'9007199254740991' is not of type 'number'`, which happened when a numeric field was replaced by a token string. Numeric JSON values stay numbers, keep their digit count and remain safe integers. Each candidate is judged inside the whole number and redrawn when it would not round-trip, so matching a decimal or an exponent no longer blocks the request; `SCAN_NUMBER_UNSAFE` remains only as a last resort, mainly when a value already mapped in text context reappears as a number.
101
+
102
+ Addresses are format-aware: an IPv4 match keeps every octet within 0-255, and an IPv6 match keeps hex digits hexadecimal along with `::` compression, group widths and any embedded IPv4 tail, so `net.isIPv4`/`net.isIPv6` and provider validators still accept them. A plain per-character replacement would otherwise yield octets above 255 or non-hex characters. Addresses embedded inside a longer match, such as a URL matched as a whole, still use per-character replacement.
99
103
 
100
104
  Letters and digits are replaced inside their own writing system: ASCII, Latin-1, Latin Extended-A, Latin Extended Additional (including Vietnamese), Greek, Cyrillic, Hebrew, Arabic, Thai, Hiragana, Katakana, Hangul and CJK. Punctuation, whitespace, emoji, symbols and code points outside these ranges are kept unchanged, which is what keeps URLs, emails and embedded JSON parseable.
101
105
 
@@ -249,7 +253,7 @@ pi -e ./src/index.ts
249
253
  }
250
254
  ```
251
255
 
252
- 更多示例见 [`protecter.example.json`](protecter.example.json),不会自动启用。号码规则不验证真实性或校验位,姓名和密码建议使用精确值。
256
+ 更多示例见 [`protecter.example.json`](protecter.example.json),涵盖内网域名、邮箱地址、IPv4 和 IPv6,不会自动启用。地址与域名规则请先核对:它们同样会匹配公开值,过于宽泛的规则可能脱敏文档示例、回环地址或依赖仓库域名。号码规则不验证真实性或校验位,姓名和密码建议使用精确值。
253
257
 
254
258
  - **字面规则:**字符串和 `literal` 按大小写敏感方式匹配全部出现位置,不作正则解释。
255
259
  - **正则:**JavaScript 正则替换整个匹配,可用后行断言仅匹配凭证值。支持 `g i m s u`,自动补 `g`,不支持 `/pattern/flags` 简写。
@@ -290,8 +294,12 @@ pi -e ./src/index.ts
290
294
  | `张三` | `丅欕` | 汉字体系、字数 |
291
295
  | `Nguyễn Văn Tèo` | `Ufefṉg Zżt Sàb` | 带重音拉丁字母、词边界 |
292
296
  | `ひらがな` | `ゅせはっ` | 平假名 |
297
+ | `192.168.1.10` | `101.203.3.77` | 合法 IPv4,每段不超过 255 |
298
+ | `fe80::1ff:fe23:4567` | `af44::6ea:bf19:2700` | 合法 IPv6,保留 `::` 与分组宽度 |
299
+
300
+ 这修复了类似 `'9007199254740991' is not of type 'number'` 的提供商错误:过去数值字段会被替换成字符串占位符。现在数值仍为数字,位数不变并保持安全整数;候选值在整个数值上下文中校验,不合法则重新抽取,因此匹配小数或指数不再拦截请求;`SCAN_NUMBER_UNSAFE` 仅作为最后手段保留,主要出现在文本上下文已建立映射的值再次以数值形式出现时。
293
301
 
294
- 这修复了类似 `'9007199254740991' is not of type 'number'` 的提供商错误:过去数值字段会被替换成字符串占位符。现在数值仍为数字,位数不变并保持安全整数;若部分匹配无法作为数字精确往返,则以 `SCAN_NUMBER_UNSAFE` 拦截,而不发送错误类型。
302
+ 地址采用格式感知替换:IPv4 匹配保证每段在 0-255 内,IPv6 匹配保证十六进制字符仍为十六进制,并保留 `::` 压缩写法、分组宽度及末尾嵌入的 IPv4,因此 `net.isIPv4`/`net.isIPv6` 和提供商校验仍然通过。若仅逐字符替换,会产生超过 255 的段或非十六进制字符。嵌在更长匹配(如整条 URL)中的地址仍按字符替换。
295
303
 
296
304
  字母和数字在各自文字体系内替换:ASCII、Latin-1、Latin Extended-A、Latin Extended Additional(含越南语)、希腊语、西里尔语、希伯来语、阿拉伯语、泰语、平假名、片假名、谚文及汉字。标点、空白、表情、符号以及上述范围之外的码位保持不变,这正是 URL、邮箱和内嵌 JSON 仍可解析的原因。
297
305
 
package/dist/index.mjs CHANGED
@@ -1,13 +1,13 @@
1
1
  /*! SPI Protecter | MIT | see LICENSE / SPI 隐私保护插件 | MIT 许可 */
2
- import{getAgentDir as et,VERSION as Ee}from"@earendil-works/pi-coding-agent";import{Worker as Ye}from"node:worker_threads";import{fileURLToPath as We}from"node:url";import*as d from"node:fs";import{join as C,resolve as Ge}from"node:path";import{createHash as Me,randomUUID as ke}from"node:crypto";import{Worker as ve}from"node:worker_threads";import{fileURLToPath as Ue}from"node:url";var z={INTERNAL:["Unexpected local failure","Reload; check the installed package integrity"],NOT_READY:["Protection is not initialized","Run /protecter status for the initialization error, fix it, then /reload"],UNSUPPORTED_PI:["Unsupported Pi version","Use Pi 0.85.1-0.85.x"],MACHINE_ID:["Machine identifier unavailable","Check local OS machine-ID access; no random fallback is used"],CONFIG_JSON:["Invalid configuration JSON","Fix JSON syntax locally, then /reload"],CONFIG_SCHEMA:["Invalid configuration structure or rule","Use version 1, a sensitiveWords array and documented fields; rule shows the 1-based index"],CONFIG_REGEX:["Invalid regex or flags","Fix the numbered rule; flags support gimsu only"],CONFIG_TARGET:["Invalid fixed replacement target","Use a non-blank string under 8192 characters without reserved __PIP_ names"],CONFIG_CONFLICT:["Conflicting rule definitions","Give the same matcher one replacement policy across all configs"],CONFIG_SIZE:["Configuration exceeds size or rule limit","Reduce configuration size or rule count; rules are never truncated"],CONFIG_IO:["Cannot access configuration","Check local permissions, ownership and free space"],CONFIG_UNSAFE:["Unsafe configuration file","Use an owned regular file without symbolic or hard links"],CONFIG_EMPTY_MATCH:["Regex matches an empty string","Require a non-empty match in the numbered rule"],CONFIG_SENSITIVE_TARGET:["Fixed target overlaps a sensitive rule","Choose a target unrelated to every sensitive matcher"],CONFIG_ALIAS_CONFLICT:["Fixed aliases overlap each other","Use distinct aliases that are not substrings of one another"],CONFIG_TIMEOUT:["Rule validation timed out","Simplify regex rules, then /reload"],MIGRATION_LOCKED:["Configuration migration lock is busy","Wait for other Pi processes; inspect a stale lock only after stopping them"],MIGRATION_CHANGED:["Configuration changed during migration","Stop concurrent edits and /reload; source files are preserved"],MIGRATION_LIMIT:["Too many configuration candidates","Consolidate old configuration files locally"],MIGRATION_IO:["Migration file operation failed","Check permissions, free space and leftover files before retrying"],PAYLOAD_JSON:["Request is not serializable JSON","Inspect the provider or another payload-modifying extension; no payload is logged"],PAYLOAD_SIZE:["Request exceeds the size limit","Reduce history or tool output size, or start a new session"],SCAN_TIMEOUT:["Redaction worker timed out","Simplify regex rules or reduce input size"],WORKER_FAILED:["Worker failed to start or exited early","Verify all dist files and the Node version; reinstall if needed"],SCAN_INTERNAL:["Unexpected scan failure","Review recent rule changes and retry with smaller input"],SCAN_NUMBER_UNSAFE:["Replacement would turn a numeric JSON value into an invalid number","Narrow the rule so it does not match inside numeric fields such as timestamps or schema limits"],SCAN_UNIQUE_FAILED:["Could not generate a unique shape-preserving replacement","The matched value is too short; match a longer value or set an explicit replacement"],SCAN_ZERO_WIDTH:["Regex produced a zero-width match","Require a non-empty match in the numbered rule"],SCAN_MATCH_LIMIT:["Too many matches in one request","Narrow overly broad rules or reduce input"],SCAN_MAPPING_LIMIT:["Mapping limit reached","Start a new session or /reload; in-memory mappings reset"],FIXED_MAPPING_CONFLICT:["One original has conflicting replacements","Use one replacement for overlapping matchers, then /reload"],FIXED_ALIAS_REUSED:["Fixed alias already maps to another original","A fixed regex represents one original per instance; use random replacement or split literal rules"],FIXED_SENSITIVE_TARGET:["Replacement contains the matched original","Choose a target without sensitive content"],FIXED_OVERLAP:["Fixed replacement partially overlaps another match","Remove the partial overlap or use random rules; rule and otherRule show both rules"],FIXED_ALIAS_CROSSING:["Regex crosses a learned fixed alias","Narrow the regex or choose a different alias"],SCAN_COMPLEXITY:["Request exceeds depth or node limits","Reduce nested data and tool schema size"],SCAN_JSON_REWRITE:["Replacement breaks embedded JSON syntax","Avoid rules spanning JSON syntax; match field values instead"],SCAN_ATTACHMENT:["Unsupported image, audio, video or file content","Remove attachments or send reviewed text only"],SCAN_KEY_COLLISION:["Replacement creates duplicate object keys","Use distinct targets or avoid matching schema keys"],RESTORE_COMPLEXITY:["Response nesting is too deep to restore","Reduce nested structures or start a new session with /new"],RESTORE_KEY_COLLISION:["Restoring produced duplicate object keys","Use more specific rules or fixed targets that cannot collide with schema keys"],AUDIT_BATCH_LIMIT:["Audit batch exceeds the size limit","Reduce matched content per request"],AUDIT_FULL:["Audit log would exceed the size limit","Review records, then run /protecter logs clear or archive locally"],AUDIT_PARTIAL:["Audit log has an incomplete last line","Inspect locally or run /protecter logs clear; never upload the log"],AUDIT_LOCKED:["Audit lock stayed busy","Wait for writers; stop all Pi processes before inspecting a stale lock"],AUDIT_UNSAFE:["Unsafe audit file","Use an owned regular file without links; checks are not bypassed"],AUDIT_IO:["Audit file operation failed","Check errno, permissions and free space; no path or content is exposed"]},_e={INTERNAL:["未预期的本地错误","重载并检查安装包完整性"],NOT_READY:["防护尚未初始化","查看初始化错误,修复后重载"],UNSUPPORTED_PI:["Pi 版本不受支持","请使用受支持的 Pi 版本"],MACHINE_ID:["无法获取机器标识","检查系统机器标识访问权限,不使用随机回退"],CONFIG_JSON:["配置 JSON 无效","本地修复 JSON 语法后重载"],CONFIG_SCHEMA:["配置结构或规则无效","使用版本1、敏感词数组及文档字段,rule 为规则序号"],CONFIG_REGEX:["正则或标志无效","修复指定规则,标志仅支持 gimsu"],CONFIG_TARGET:["固定替换目标无效","使用非空且少于8192字符的目标,不含保留名称"],CONFIG_CONFLICT:["规则定义冲突","为同一匹配器设置一致的替换策略"],CONFIG_SIZE:["配置超过体积或规则数上限","减少配置体积或规则数,规则不会被截断"],CONFIG_IO:["无法访问配置","检查本地权限、所有者和磁盘空间"],CONFIG_UNSAFE:["配置文件不安全","使用本人所有的普通文件,不含符号或硬链接"],CONFIG_EMPTY_MATCH:["正则匹配空字符串","修改指定规则,要求非空匹配"],CONFIG_SENSITIVE_TARGET:["固定目标与敏感规则重叠","选择与所有敏感匹配器无关的目标"],CONFIG_ALIAS_CONFLICT:["固定别名相互重叠","使用互不包含的不同别名"],CONFIG_TIMEOUT:["规则验证超时","简化正则规则后重载"],MIGRATION_LOCKED:["配置迁移锁被占用","等待其他进程,全部停止后再检查残留锁"],MIGRATION_CHANGED:["迁移期间配置发生变化","停止并发编辑后重载,源文件会保留"],MIGRATION_LIMIT:["候选配置过多","在本地整理旧配置文件"],MIGRATION_IO:["迁移文件操作失败","检查权限、空间和残留文件后重试"],PAYLOAD_JSON:["请求无法序列化为 JSON","检查提供商或其他请求改写扩展,不记录请求内容"],PAYLOAD_SIZE:["请求超过体积上限","减少历史或工具输出,或新建会话"],SCAN_TIMEOUT:["脱敏 worker 超时","简化正则规则或缩小输入"],WORKER_FAILED:["worker 启动失败或提前退出","检查 dist 文件完整性和 Node 版本,必要时重装"],SCAN_INTERNAL:["未预期的扫描错误","检查最近的规则改动并缩小输入"],SCAN_NUMBER_UNSAFE:["替换会把 JSON 数值变成无效数字","缩小规则范围,避免匹配时间戳或结构上限等数值字段"],SCAN_UNIQUE_FAILED:["无法生成唯一的同形替换值","命中内容过短或可选空间过小,请匹配更长内容或设置固定 replacement"],SCAN_ZERO_WIDTH:["正则产生零宽匹配","修改指定规则,要求非空匹配"],SCAN_MATCH_LIMIT:["单次请求匹配次数过多","缩小过宽规则或减少输入"],SCAN_MAPPING_LIMIT:["映射数量达到上限","新建会话或重载,内存映射将重置"],FIXED_MAPPING_CONFLICT:["同一原文存在冲突替换","为重叠匹配器使用同一目标后重载"],FIXED_ALIAS_REUSED:["固定别名已映射其他原文","固定正则在同一实例仅代表一种原文,改用随机替换或拆分字面规则"],FIXED_SENSITIVE_TARGET:["替换目标包含命中原文","选择不含敏感内容的目标"],FIXED_OVERLAP:["固定替换与其他匹配部分重叠","消除部分重叠或改用随机规则,rule 与 otherRule 指出两条规则"],FIXED_ALIAS_CROSSING:["正则跨越已学习的固定别名","缩小正则范围或更换别名"],SCAN_COMPLEXITY:["请求超过层级或节点上限","减少嵌套数据和工具结构体积"],SCAN_JSON_REWRITE:["替换破坏内嵌 JSON 语法","避免跨 JSON 语法的规则,改为匹配字段值"],SCAN_ATTACHMENT:["不支持图片、音频、视频或文件内容","移除附件或仅发送已审查的文本"],SCAN_KEY_COLLISION:["替换后对象键重复","使用不同目标或避免匹配结构键"],RESTORE_COMPLEXITY:["还原响应时层级过深","减少嵌套结构,或使用 /new 开始新会话"],RESTORE_KEY_COLLISION:["还原后对象键重复","使用更具体的规则或固定替换目标,避免与结构键冲突"],AUDIT_BATCH_LIMIT:["审计批次超过上限","减少单个请求的命中内容"],AUDIT_FULL:["审计日志将超过上限","检查记录后清空日志或本地归档"],AUDIT_PARTIAL:["审计日志末行不完整","本地检查或清空日志,不要上传日志"],AUDIT_LOCKED:["审计锁持续被占用","等待写入完成,全部停止后再检查残留锁"],AUDIT_UNSAFE:["审计文件不安全","使用本人普通文件且不含链接,不会绕过检查"],AUDIT_IO:["审计文件操作失败","检查错误码、权限和磁盘空间,不暴露路径或内容"]},q={blockedHeader:"SPI Protecter blocked this request",reasonLabel:"Reason",actionLabel:"Action",detailsLabel:"Details",statusReady:"ready",statusNotReady:"not ready",statusLine:"SPI Protecter: {state}; {rules}; {mappings}. Memory snapshot; /reload to refresh.",statusLastBlock:"Last block @ {at}:",statusNoBlock:"No recorded block in this session.",statusBarRules:"SPI Protecter · {rules}",statusBarNotReady:"SPI Protecter · not ready",emptyRules:"SPI Protecter: no rules configured; edit the local machine config, then /reload.",commandDescription:"View local SPI protection records",usage:"Usage: /protecter [logs [1-100|clear]|status|reload]. Never type secrets as arguments.",logsTuiOnly:"Open the local TUI to view protection records.",logsNone:"No readable records.",logsSelectTitle:"Protection records",logsTruncated:" (limited tail)",logsRevealTitle:"Sensitive plaintext",logsRevealBody:"Reveal locally? Never share this view.",logsPreviewTitle:"Local preview; edits are discarded",logsPreviewTruncated:"[Preview truncated]",clearTitle:"Clear the current audit log?",clearBody:"Irreversible. Only this machine's log is cleared; configuration and in-memory mappings stay. Later requests may add new records.",cleared:"Audit log cleared: {bytes}.",toolBlocked:"SPI Protecter blocked tool access to the protected configuration or audit log; edit it locally.",compactUnsupported:"SPI Protecter does not support remote context compaction yet; use /new to start a new session."},Ae={blockedHeader:"SPI Protecter 已阻止该请求",reasonLabel:"原因",actionLabel:"处理",detailsLabel:"详情",statusReady:"就绪",statusNotReady:"未就绪",statusLine:"SPI Protecter:{state};{rules};{mappings}。内存快照,重载后更新。",statusLastBlock:"最近一次拦截 @ {at}:",statusNoBlock:"本会话暂无拦截记录。",statusBarRules:"SPI Protecter · {rules}",statusBarNotReady:"SPI Protecter · 未就绪",emptyRules:"SPI Protecter:尚未配置规则,请编辑本地机器配置后重载。",commandDescription:"查看本地保护记录",usage:"用法:/protecter [logs [1-100|clear]|status|reload]。不要在参数中输入敏感内容。",logsTuiOnly:"请在本地 TUI 查看保护记录。",logsNone:"暂无可读记录。",logsSelectTitle:"保护记录",logsTruncated:"(仅显示有限尾部)",logsRevealTitle:"敏感明文",logsRevealBody:"确认在本地显示?请勿分享此界面。",logsPreviewTitle:"本地预览,编辑不会保存",logsPreviewTruncated:"[预览已截断]",clearTitle:"确认清空当前审计日志?",clearBody:"不可撤销。仅清空本机日志,配置和内存映射保留;后续请求可能写入新记录。",cleared:"已清空审计日志:{bytes}。",toolBlocked:"SPI Protecter 已阻止工具访问受保护的配置或审计日志,请在本地编辑。",compactUnsupported:"SPI Protecter 暂不支持远程上下文压缩,请使用 /new 开始新会话。"};function H(n=process.env){let e=n.PI_PROTECTER_LANG||n.LC_ALL||n.LC_MESSAGES||n.LANG||n.LANGUAGE||Se();return/^zh\b|^zh[-_]/i.test(e.trim())?"zh":"en"}function Se(){try{return new Intl.DateTimeFormat().resolvedOptions().locale}catch{return""}}var _=H();function V(){return _}function Q(n){_=n}function m(n,e={}){return((_==="zh"?Ae:q)[n]??q[n]).replace(/\{(\w+)\}/g,(t,o)=>Object.hasOwn(e,o)?String(e[o]):t)}var Te={en:{rules:{one:"{n} rule",other:"{n} rules"},mappings:{one:"{n} mapping",other:"{n} mappings"},bytes:{one:"{n} byte",other:"{n} bytes"}},zh:{rules:{other:"规则 {n} 条"},mappings:{other:"映射 {n} 个"},bytes:{other:"{n} 字节"}}},Z=new Map;function R(n,e){let r=_==="zh"?"zh-CN":"en",t=Z.get(_);if(!t){try{t=new Intl.PluralRules(r)}catch{t=void 0}t&&Z.set(_,t)}let o=t?.select(e)??"other",s=Te[_][n],l=s[o]??s.other,i=new Intl.NumberFormat(r).format(e);return l.replace("{n}",i)}function ee(n){let r=(_==="zh"?_e[n]:z[n])??z[n]??z.INTERNAL;return{reason:r[0],action:r[1]}}var te={INTERNAL:"internal",NOT_READY:"initialization",UNSUPPORTED_PI:"initialization",MACHINE_ID:"initialization",CONFIG_JSON:"configuration",CONFIG_SCHEMA:"configuration",CONFIG_REGEX:"configuration",CONFIG_TARGET:"configuration",CONFIG_CONFLICT:"configuration",CONFIG_SIZE:"configuration",CONFIG_IO:"configuration",CONFIG_UNSAFE:"configuration",CONFIG_EMPTY_MATCH:"configuration",CONFIG_SENSITIVE_TARGET:"configuration",CONFIG_ALIAS_CONFLICT:"configuration",CONFIG_TIMEOUT:"configuration",MIGRATION_LOCKED:"migration",MIGRATION_CHANGED:"migration",MIGRATION_LIMIT:"migration",MIGRATION_IO:"migration",PAYLOAD_JSON:"request",PAYLOAD_SIZE:"request",SCAN_TIMEOUT:"scan",WORKER_FAILED:"worker",SCAN_INTERNAL:"scan",SCAN_NUMBER_UNSAFE:"scan",SCAN_UNIQUE_FAILED:"scan",SCAN_ZERO_WIDTH:"scan",SCAN_MATCH_LIMIT:"scan",SCAN_MAPPING_LIMIT:"scan",FIXED_MAPPING_CONFLICT:"scan",FIXED_ALIAS_REUSED:"scan",FIXED_SENSITIVE_TARGET:"scan",FIXED_OVERLAP:"scan",FIXED_ALIAS_CROSSING:"scan",SCAN_COMPLEXITY:"scan",SCAN_JSON_REWRITE:"scan",SCAN_ATTACHMENT:"scan",SCAN_KEY_COLLISION:"scan",RESTORE_COMPLEXITY:"restore",RESTORE_KEY_COLLISION:"restore",AUDIT_BATCH_LIMIT:"audit",AUDIT_FULL:"audit",AUDIT_PARTIAL:"audit",AUDIT_LOCKED:"audit",AUDIT_UNSAFE:"audit",AUDIT_IO:"audit"},re=["rule","otherRule","node","bytes","limit","timeoutMs"],Ce=/^(EACCES|EPERM|ENOENT|ENOSPC|EDQUOT|EROFS|ELOOP|EISDIR|ENOTDIR|EMFILE|ENFILE|EIO|EEXIST|EBADF|EBUSY|ENOTEMPTY)$/;function we(n,e={}){let{reason:r,action:t}=ee(n),o={};for(let s of re){let l=e[s];typeof l=="number"&&Number.isSafeInteger(l)&&l>=0&&(o[s]=l)}return typeof e.errno=="string"&&Ce.test(e.errno)&&(o.errno=e.errno),{code:n,stage:te[n],reason:r,action:t,...o}}function S(n){let e=V()==="zh"?":":": ",r=[...re,"errno"].filter(t=>n[t]!==void 0).map(t=>`${t}=${n[t]}`).join(" ");return[m("blockedHeader"),`code=${n.code} stage=${n.stage}${r?` ${r}`:""}`,`${m("reasonLabel")}${e}${n.reason}`,`${m("actionLabel")}${e}${n.action}`,`${m("detailsLabel")}${e}/protecter status`].join(`
2
+ import{getAgentDir as et,VERSION as Ee}from"@earendil-works/pi-coding-agent";import{Worker as Ye}from"node:worker_threads";import{fileURLToPath as We}from"node:url";import*as d from"node:fs";import{join as C,resolve as Ge}from"node:path";import{createHash as Me,randomUUID as ke}from"node:crypto";import{Worker as Ue}from"node:worker_threads";import{fileURLToPath as ve}from"node:url";var z={INTERNAL:["Unexpected local failure","Reload; check the installed package integrity"],NOT_READY:["Protection is not initialized","Run /protecter status for the initialization error, fix it, then /reload"],UNSUPPORTED_PI:["Unsupported Pi version","Use Pi 0.85.1-0.85.x"],MACHINE_ID:["Machine identifier unavailable","Check local OS machine-ID access; no random fallback is used"],CONFIG_JSON:["Invalid configuration JSON","Fix JSON syntax locally, then /reload"],CONFIG_SCHEMA:["Invalid configuration structure or rule","Use version 1, a sensitiveWords array and documented fields; rule shows the 1-based index"],CONFIG_REGEX:["Invalid regex or flags","Fix the numbered rule; flags support gimsu only"],CONFIG_TARGET:["Invalid fixed replacement target","Use a non-blank string under 8192 characters without reserved __PIP_ names"],CONFIG_CONFLICT:["Conflicting rule definitions","Give the same matcher one replacement policy across all configs"],CONFIG_SIZE:["Configuration exceeds size or rule limit","Reduce configuration size or rule count; rules are never truncated"],CONFIG_IO:["Cannot access configuration","Check local permissions, ownership and free space"],CONFIG_UNSAFE:["Unsafe configuration file","Use an owned regular file without symbolic or hard links"],CONFIG_EMPTY_MATCH:["Regex matches an empty string","Require a non-empty match in the numbered rule"],CONFIG_SENSITIVE_TARGET:["Fixed target overlaps a sensitive rule","Choose a target unrelated to every sensitive matcher"],CONFIG_ALIAS_CONFLICT:["Fixed aliases overlap each other","Use distinct aliases that are not substrings of one another"],CONFIG_TIMEOUT:["Rule validation timed out","Simplify regex rules, then /reload"],MIGRATION_LOCKED:["Configuration migration lock is busy","Wait for other Pi processes; inspect a stale lock only after stopping them"],MIGRATION_CHANGED:["Configuration changed during migration","Stop concurrent edits and /reload; source files are preserved"],MIGRATION_LIMIT:["Too many configuration candidates","Consolidate old configuration files locally"],MIGRATION_IO:["Migration file operation failed","Check permissions, free space and leftover files before retrying"],PAYLOAD_JSON:["Request is not serializable JSON","Inspect the provider or another payload-modifying extension; no payload is logged"],PAYLOAD_SIZE:["Request exceeds the size limit","Reduce history or tool output size, or start a new session"],SCAN_TIMEOUT:["Redaction worker timed out","Simplify regex rules or reduce input size"],WORKER_FAILED:["Worker failed to start or exited early","Verify all dist files and the Node version; reinstall if needed"],SCAN_INTERNAL:["Unexpected scan failure","Review recent rule changes and retry with smaller input"],SCAN_NUMBER_UNSAFE:["Replacement would turn a numeric JSON value into an invalid number","Narrow the rule so it does not match inside numeric fields such as timestamps or schema limits"],SCAN_UNIQUE_FAILED:["Could not generate a unique shape-preserving replacement","The matched value is too short; match a longer value or set an explicit replacement"],SCAN_ZERO_WIDTH:["Regex produced a zero-width match","Require a non-empty match in the numbered rule"],SCAN_MATCH_LIMIT:["Too many matches in one request","Narrow overly broad rules or reduce input"],SCAN_MAPPING_LIMIT:["Mapping limit reached","Start a new session or /reload; in-memory mappings reset"],FIXED_MAPPING_CONFLICT:["One original has conflicting replacements","Use one replacement for overlapping matchers, then /reload"],FIXED_ALIAS_REUSED:["Fixed alias already maps to another original","A fixed regex represents one original per instance; use random replacement or split literal rules"],FIXED_SENSITIVE_TARGET:["Replacement contains the matched original","Choose a target without sensitive content"],FIXED_OVERLAP:["Fixed replacement partially overlaps another match","Remove the partial overlap or use random rules; rule and otherRule show both rules"],FIXED_ALIAS_CROSSING:["Regex crosses a learned fixed alias","Narrow the regex or choose a different alias"],SCAN_COMPLEXITY:["Request exceeds depth or node limits","Reduce nested data and tool schema size"],SCAN_JSON_REWRITE:["Replacement breaks embedded JSON syntax","Avoid rules spanning JSON syntax; match field values instead"],SCAN_ATTACHMENT:["Unsupported image, audio, video or file content","Remove attachments or send reviewed text only"],SCAN_KEY_COLLISION:["Replacement creates duplicate object keys","Use distinct targets or avoid matching schema keys"],RESTORE_COMPLEXITY:["Response nesting is too deep to restore","Reduce nested structures or start a new session with /new"],RESTORE_KEY_COLLISION:["Restoring produced duplicate object keys","Use more specific rules or fixed targets that cannot collide with schema keys"],AUDIT_BATCH_LIMIT:["Audit batch exceeds the size limit","Reduce matched content per request"],AUDIT_FULL:["Audit log would exceed the size limit","Review records, then run /protecter logs clear or archive locally"],AUDIT_PARTIAL:["Audit log has an incomplete last line","Inspect locally or run /protecter logs clear; never upload the log"],AUDIT_LOCKED:["Audit lock stayed busy","Wait for writers; stop all Pi processes before inspecting a stale lock"],AUDIT_UNSAFE:["Unsafe audit file","Use an owned regular file without links; checks are not bypassed"],AUDIT_IO:["Audit file operation failed","Check errno, permissions and free space; no path or content is exposed"]},_e={INTERNAL:["未预期的本地错误","重载并检查安装包完整性"],NOT_READY:["防护尚未初始化","查看初始化错误,修复后重载"],UNSUPPORTED_PI:["Pi 版本不受支持","请使用受支持的 Pi 版本"],MACHINE_ID:["无法获取机器标识","检查系统机器标识访问权限,不使用随机回退"],CONFIG_JSON:["配置 JSON 无效","本地修复 JSON 语法后重载"],CONFIG_SCHEMA:["配置结构或规则无效","使用版本1、敏感词数组及文档字段,rule 为规则序号"],CONFIG_REGEX:["正则或标志无效","修复指定规则,标志仅支持 gimsu"],CONFIG_TARGET:["固定替换目标无效","使用非空且少于8192字符的目标,不含保留名称"],CONFIG_CONFLICT:["规则定义冲突","为同一匹配器设置一致的替换策略"],CONFIG_SIZE:["配置超过体积或规则数上限","减少配置体积或规则数,规则不会被截断"],CONFIG_IO:["无法访问配置","检查本地权限、所有者和磁盘空间"],CONFIG_UNSAFE:["配置文件不安全","使用本人所有的普通文件,不含符号或硬链接"],CONFIG_EMPTY_MATCH:["正则匹配空字符串","修改指定规则,要求非空匹配"],CONFIG_SENSITIVE_TARGET:["固定目标与敏感规则重叠","选择与所有敏感匹配器无关的目标"],CONFIG_ALIAS_CONFLICT:["固定别名相互重叠","使用互不包含的不同别名"],CONFIG_TIMEOUT:["规则验证超时","简化正则规则后重载"],MIGRATION_LOCKED:["配置迁移锁被占用","等待其他进程,全部停止后再检查残留锁"],MIGRATION_CHANGED:["迁移期间配置发生变化","停止并发编辑后重载,源文件会保留"],MIGRATION_LIMIT:["候选配置过多","在本地整理旧配置文件"],MIGRATION_IO:["迁移文件操作失败","检查权限、空间和残留文件后重试"],PAYLOAD_JSON:["请求无法序列化为 JSON","检查提供商或其他请求改写扩展,不记录请求内容"],PAYLOAD_SIZE:["请求超过体积上限","减少历史或工具输出,或新建会话"],SCAN_TIMEOUT:["脱敏 worker 超时","简化正则规则或缩小输入"],WORKER_FAILED:["worker 启动失败或提前退出","检查 dist 文件完整性和 Node 版本,必要时重装"],SCAN_INTERNAL:["未预期的扫描错误","检查最近的规则改动并缩小输入"],SCAN_NUMBER_UNSAFE:["替换会把 JSON 数值变成无效数字","缩小规则范围,避免匹配时间戳或结构上限等数值字段"],SCAN_UNIQUE_FAILED:["无法生成唯一的同形替换值","命中内容过短或可选空间过小,请匹配更长内容或设置固定 replacement"],SCAN_ZERO_WIDTH:["正则产生零宽匹配","修改指定规则,要求非空匹配"],SCAN_MATCH_LIMIT:["单次请求匹配次数过多","缩小过宽规则或减少输入"],SCAN_MAPPING_LIMIT:["映射数量达到上限","新建会话或重载,内存映射将重置"],FIXED_MAPPING_CONFLICT:["同一原文存在冲突替换","为重叠匹配器使用同一目标后重载"],FIXED_ALIAS_REUSED:["固定别名已映射其他原文","固定正则在同一实例仅代表一种原文,改用随机替换或拆分字面规则"],FIXED_SENSITIVE_TARGET:["替换目标包含命中原文","选择不含敏感内容的目标"],FIXED_OVERLAP:["固定替换与其他匹配部分重叠","消除部分重叠或改用随机规则,rule 与 otherRule 指出两条规则"],FIXED_ALIAS_CROSSING:["正则跨越已学习的固定别名","缩小正则范围或更换别名"],SCAN_COMPLEXITY:["请求超过层级或节点上限","减少嵌套数据和工具结构体积"],SCAN_JSON_REWRITE:["替换破坏内嵌 JSON 语法","避免跨 JSON 语法的规则,改为匹配字段值"],SCAN_ATTACHMENT:["不支持图片、音频、视频或文件内容","移除附件或仅发送已审查的文本"],SCAN_KEY_COLLISION:["替换后对象键重复","使用不同目标或避免匹配结构键"],RESTORE_COMPLEXITY:["还原响应时层级过深","减少嵌套结构,或使用 /new 开始新会话"],RESTORE_KEY_COLLISION:["还原后对象键重复","使用更具体的规则或固定替换目标,避免与结构键冲突"],AUDIT_BATCH_LIMIT:["审计批次超过上限","减少单个请求的命中内容"],AUDIT_FULL:["审计日志将超过上限","检查记录后清空日志或本地归档"],AUDIT_PARTIAL:["审计日志末行不完整","本地检查或清空日志,不要上传日志"],AUDIT_LOCKED:["审计锁持续被占用","等待写入完成,全部停止后再检查残留锁"],AUDIT_UNSAFE:["审计文件不安全","使用本人普通文件且不含链接,不会绕过检查"],AUDIT_IO:["审计文件操作失败","检查错误码、权限和磁盘空间,不暴露路径或内容"]},q={blockedHeader:"SPI Protecter blocked this request",reasonLabel:"Reason",actionLabel:"Action",detailsLabel:"Details",statusReady:"ready",statusNotReady:"not ready",statusLine:"SPI Protecter: {state}; {rules}; {mappings}. Memory snapshot; /reload to refresh.",statusLastBlock:"Last block @ {at}:",statusNoBlock:"No recorded block in this session.",statusBarRules:"SPI Protecter · {rules}",statusBarNotReady:"SPI Protecter · not ready",emptyRules:"SPI Protecter: no rules configured; edit the local machine config, then /reload.",commandDescription:"View local SPI protection records",usage:"Usage: /protecter [logs [1-100|clear]|status|reload]. Never type secrets as arguments.",logsTuiOnly:"Open the local TUI to view protection records.",logsNone:"No readable records.",logsSelectTitle:"Protection records",logsTruncated:" (limited tail)",logsRevealTitle:"Sensitive plaintext",logsRevealBody:"Reveal locally? Never share this view.",logsPreviewTitle:"Local preview; edits are discarded",logsPreviewTruncated:"[Preview truncated]",clearTitle:"Clear the current audit log?",clearBody:"Irreversible. Only this machine's log is cleared; configuration and in-memory mappings stay. Later requests may add new records.",cleared:"Audit log cleared: {bytes}.",toolBlocked:"SPI Protecter blocked tool access to the protected configuration or audit log; edit it locally.",compactUnsupported:"SPI Protecter does not support remote context compaction yet; use /new to start a new session."},Ae={blockedHeader:"SPI Protecter 已阻止该请求",reasonLabel:"原因",actionLabel:"处理",detailsLabel:"详情",statusReady:"就绪",statusNotReady:"未就绪",statusLine:"SPI Protecter:{state};{rules};{mappings}。内存快照,重载后更新。",statusLastBlock:"最近一次拦截 @ {at}:",statusNoBlock:"本会话暂无拦截记录。",statusBarRules:"SPI Protecter · {rules}",statusBarNotReady:"SPI Protecter · 未就绪",emptyRules:"SPI Protecter:尚未配置规则,请编辑本地机器配置后重载。",commandDescription:"查看本地保护记录",usage:"用法:/protecter [logs [1-100|clear]|status|reload]。不要在参数中输入敏感内容。",logsTuiOnly:"请在本地 TUI 查看保护记录。",logsNone:"暂无可读记录。",logsSelectTitle:"保护记录",logsTruncated:"(仅显示有限尾部)",logsRevealTitle:"敏感明文",logsRevealBody:"确认在本地显示?请勿分享此界面。",logsPreviewTitle:"本地预览,编辑不会保存",logsPreviewTruncated:"[预览已截断]",clearTitle:"确认清空当前审计日志?",clearBody:"不可撤销。仅清空本机日志,配置和内存映射保留;后续请求可能写入新记录。",cleared:"已清空审计日志:{bytes}。",toolBlocked:"SPI Protecter 已阻止工具访问受保护的配置或审计日志,请在本地编辑。",compactUnsupported:"SPI Protecter 暂不支持远程上下文压缩,请使用 /new 开始新会话。"};function H(n=process.env){let e=n.PI_PROTECTER_LANG||n.LC_ALL||n.LC_MESSAGES||n.LANG||n.LANGUAGE||Se();return/^zh\b|^zh[-_]/i.test(e.trim())?"zh":"en"}function Se(){try{return new Intl.DateTimeFormat().resolvedOptions().locale}catch{return""}}var _=H();function V(){return _}function Q(n){_=n}function m(n,e={}){return((_==="zh"?Ae:q)[n]??q[n]).replace(/\{(\w+)\}/g,(t,o)=>Object.hasOwn(e,o)?String(e[o]):t)}var Te={en:{rules:{one:"{n} rule",other:"{n} rules"},mappings:{one:"{n} mapping",other:"{n} mappings"},bytes:{one:"{n} byte",other:"{n} bytes"}},zh:{rules:{other:"规则 {n} 条"},mappings:{other:"映射 {n} 个"},bytes:{other:"{n} 字节"}}},Z=new Map;function R(n,e){let r=_==="zh"?"zh-CN":"en",t=Z.get(_);if(!t){try{t=new Intl.PluralRules(r)}catch{t=void 0}t&&Z.set(_,t)}let o=t?.select(e)??"other",s=Te[_][n],l=s[o]??s.other,i=new Intl.NumberFormat(r).format(e);return l.replace("{n}",i)}function ee(n){let r=(_==="zh"?_e[n]:z[n])??z[n]??z.INTERNAL;return{reason:r[0],action:r[1]}}var te={INTERNAL:"internal",NOT_READY:"initialization",UNSUPPORTED_PI:"initialization",MACHINE_ID:"initialization",CONFIG_JSON:"configuration",CONFIG_SCHEMA:"configuration",CONFIG_REGEX:"configuration",CONFIG_TARGET:"configuration",CONFIG_CONFLICT:"configuration",CONFIG_SIZE:"configuration",CONFIG_IO:"configuration",CONFIG_UNSAFE:"configuration",CONFIG_EMPTY_MATCH:"configuration",CONFIG_SENSITIVE_TARGET:"configuration",CONFIG_ALIAS_CONFLICT:"configuration",CONFIG_TIMEOUT:"configuration",MIGRATION_LOCKED:"migration",MIGRATION_CHANGED:"migration",MIGRATION_LIMIT:"migration",MIGRATION_IO:"migration",PAYLOAD_JSON:"request",PAYLOAD_SIZE:"request",SCAN_TIMEOUT:"scan",WORKER_FAILED:"worker",SCAN_INTERNAL:"scan",SCAN_NUMBER_UNSAFE:"scan",SCAN_UNIQUE_FAILED:"scan",SCAN_ZERO_WIDTH:"scan",SCAN_MATCH_LIMIT:"scan",SCAN_MAPPING_LIMIT:"scan",FIXED_MAPPING_CONFLICT:"scan",FIXED_ALIAS_REUSED:"scan",FIXED_SENSITIVE_TARGET:"scan",FIXED_OVERLAP:"scan",FIXED_ALIAS_CROSSING:"scan",SCAN_COMPLEXITY:"scan",SCAN_JSON_REWRITE:"scan",SCAN_ATTACHMENT:"scan",SCAN_KEY_COLLISION:"scan",RESTORE_COMPLEXITY:"restore",RESTORE_KEY_COLLISION:"restore",AUDIT_BATCH_LIMIT:"audit",AUDIT_FULL:"audit",AUDIT_PARTIAL:"audit",AUDIT_LOCKED:"audit",AUDIT_UNSAFE:"audit",AUDIT_IO:"audit"},re=["rule","otherRule","node","bytes","limit","timeoutMs"],Ce=/^(EACCES|EPERM|ENOENT|ENOSPC|EDQUOT|EROFS|ELOOP|EISDIR|ENOTDIR|EMFILE|ENFILE|EIO|EEXIST|EBADF|EBUSY|ENOTEMPTY)$/;function we(n,e={}){let{reason:r,action:t}=ee(n),o={};for(let s of re){let l=e[s];typeof l=="number"&&Number.isSafeInteger(l)&&l>=0&&(o[s]=l)}return typeof e.errno=="string"&&Ce.test(e.errno)&&(o.errno=e.errno),{code:n,stage:te[n],reason:r,action:t,...o}}function S(n){let e=V()==="zh"?":":": ",r=[...re,"errno"].filter(t=>n[t]!==void 0).map(t=>`${t}=${n[t]}`).join(" ");return[m("blockedHeader"),`code=${n.code} stage=${n.stage}${r?` ${r}`:""}`,`${m("reasonLabel")}${e}${n.reason}`,`${m("actionLabel")}${e}${n.action}`,`${m("detailsLabel")}${e}/protecter status`].join(`
3
3
  `)}var A=class extends Error{diagnostic;constructor(e,r={}){let t=we(e,r);super(S(t)),this.name="ProtectionError",this.diagnostic=t}};function a(n,e={}){return new A(n,e)}function y(n,e,r={}){if(n instanceof A)return a(n.diagnostic.code,{...n.diagnostic,...r});let t=n&&typeof n=="object"&&"code"in n?n.code:void 0;return a(e,{...r,errno:typeof t=="string"?t:void 0})}function b(n,e){if(!n||typeof n!="object")return a(e);let r=n,t=typeof r.code=="string"&&Object.hasOwn(te,r.code)?r.code:e;return a(t,r)}import{constants as T,closeSync as ne,fchmodSync as Re,fstatSync as De,mkdirSync as Le,openSync as oe,readFileSync as Fe,writeFileSync as Pe}from"node:fs";import{dirname as be}from"node:path";var J={version:1,sensitiveWords:[]};function B(n){let e;try{e=JSON.parse(n)}catch{throw a("CONFIG_JSON")}let r=0;try{if(!e||e.version!==1||!Array.isArray(e.sensitiveWords)||Object.keys(e).some(o=>!["version","sensitiveWords"].includes(o)))throw a("CONFIG_SCHEMA");if(e.sensitiveWords.length>1e3)throw a("CONFIG_SIZE",{limit:1e3});let t=new Map;for(let o of e.sensitiveWords){if(r++,typeof o=="string"){if(!o||o.length>8192)throw a("CONFIG_SCHEMA",{rule:r,limit:8192});let l=JSON.stringify(["literal",o]);if(t.has(l)&&t.get(l)!==void 0)throw a("CONFIG_CONFLICT",{rule:r});t.set(l,void 0);continue}if(!o||typeof o!="object")throw a("CONFIG_SCHEMA",{rule:r});if(Object.hasOwn(o,"replacement")&&(typeof o.replacement!="string"||!o.replacement.trim()||o.replacement.length>8192||o.replacement.includes("__PIP_")))throw a("CONFIG_TARGET",{rule:r,limit:8192});if(o.type==="literal"){if(Object.keys(o).some(l=>!["type","value","replacement"].includes(l))||typeof o.value!="string"||!o.value||o.value.length>8192)throw a("CONFIG_SCHEMA",{rule:r,limit:8192})}else if(o.type==="regex"){if(Object.keys(o).some(l=>!["type","pattern","flags","replacement"].includes(l))||typeof o.pattern!="string"||!o.pattern||o.pattern.length>8192||o.flags!==void 0&&(typeof o.flags!="string"||!/^[gimsu]*$/.test(o.flags)))throw a("CONFIG_REGEX",{rule:r,limit:8192});try{new RegExp(o.pattern,o.flags??"")}catch{throw a("CONFIG_REGEX",{rule:r})}}else throw a("CONFIG_SCHEMA",{rule:r});let s=o.type==="literal"?JSON.stringify(["literal",o.value]):JSON.stringify(["regex",o.pattern,[...new Set((o.flags??"")+"g")].sort().join("")]);if(t.has(s)&&t.get(s)!==o.replacement)throw a("CONFIG_CONFLICT",{rule:r});t.set(s,o.replacement)}return e}catch(t){throw t instanceof A?t:a("CONFIG_SCHEMA",{rule:r})}}function ie(n,e=!1){let r;try{if(e){Le(be(n),{recursive:!0,mode:448});try{let s=oe(n,T.O_WRONLY|T.O_CREAT|T.O_EXCL,384);try{Pe(s,JSON.stringify(J,null,2)+`
4
- `)}finally{ne(s)}}catch(s){if(s.code!=="EEXIST")throw y(s,"CONFIG_IO")}}r=oe(n,T.O_RDONLY|T.O_NOFOLLOW|T.O_NONBLOCK);let t=De(r);if(t.size>1024*1024)throw a("CONFIG_SIZE",{bytes:t.size,limit:1024*1024});if(!t.isFile()||t.nlink!==1||process.getuid&&t.uid!==process.getuid())throw a("CONFIG_UNSAFE");process.platform!=="win32"&&Re(r,384);let o=Fe(r,"utf8");if(Buffer.byteLength(o)>1024*1024)throw a("CONFIG_SIZE",{bytes:Buffer.byteLength(o),limit:1024*1024});return{config:B(o),raw:o}}catch(t){let o=t&&typeof t=="object"&&"code"in t?t.code:void 0;throw y(t,o==="ELOOP"||o==="EISDIR"||o==="ENOTDIR"?"CONFIG_UNSAFE":"CONFIG_IO")}finally{r!==void 0&&ne(r)}}var Y=/^protecter(?:\.[a-f0-9]{32})?\.json$/;function xe(n){return new Promise((e,r)=>{let t=new ve(Ue(new URL("./validate-worker.mjs",import.meta.url)),{workerData:n,execArgv:[],resourceLimits:{maxOldGenerationSizeMb:64}}),o=!1,s=(i,c=a("WORKER_FAILED"))=>{o||(o=!0,clearTimeout(l),t.terminate(),i?e():r(c))},l=setTimeout(()=>s(!1,a("CONFIG_TIMEOUT",{timeoutMs:2e3})),2e3);t.once("message",i=>i===!0?s(!0):s(!1,b(i,"CONFIG_SCHEMA"))),t.once("error",()=>s(!1)),t.once("exit",()=>s(!1))})}function ze(n){let e=new Set,r=[];for(let o of n)for(let s of o.sensitiveWords){let l=typeof s=="string"?JSON.stringify(["literal",s,null]):s.type==="literal"?JSON.stringify(["literal",s.value,s.replacement??null]):JSON.stringify(["regex",s.pattern,[...new Set((s.flags??"")+"g")].sort().join(""),s.replacement??null]);e.has(l)||(e.add(l),r.push(s))}let t=JSON.stringify({version:1,sensitiveWords:r});if(Buffer.byteLength(t)>1024*1024)throw a("CONFIG_SIZE",{bytes:Buffer.byteLength(t),limit:1024*1024});return B(t)}function se(n){let e;try{e=d.lstatSync(n,{bigint:!0})}catch(r){throw y(r,"CONFIG_IO")}if(!e.isFile()||e.nlink!==1n||process.getuid&&e.uid!==BigInt(process.getuid()))throw a("CONFIG_UNSAFE");return`${e.dev}:${e.ino}:${e.size}:${e.mtimeNs}`}function $(n,e){let r=C(n,e),t=se(r),o=ie(r);if(se(r)!==t)throw a("MIGRATION_CHANGED");return{name:e,...o,identity:t,digest:Me("sha256").update(o.raw).digest("hex")}}function D(n,e){let r=$(n,e.name);if(r.identity!==e.identity||r.digest!==e.digest)throw a("MIGRATION_CHANGED")}function G(n){return d.readdirSync(n).filter(e=>Y.test(e)).sort()}function ae(n){if(process.platform==="win32")return;let e=d.openSync(n,d.constants.O_RDONLY);try{d.fsyncSync(e)}finally{d.closeSync(e)}}async function ce(n,e,r={}){if(!/^[a-f0-9]{32}$/.test(e))throw a("MACHINE_ID");let t=Ge(n),o=`protecter.${e}.json`,s=C(t,o);d.mkdirSync(t,{recursive:!0,mode:448});let l=C(t,".protecter-migration.lock"),i=Date.now()+(r.lockTimeoutMs??5e3);for(;;)try{d.mkdirSync(l,{mode:448});break}catch(u){if(u.code!=="EEXIST")throw y(u,"MIGRATION_IO");if(Date.now()>=i)throw a("MIGRATION_LOCKED",{timeoutMs:r.lockTimeoutMs??5e3});await new Promise(O=>setTimeout(O,50))}let c;try{let u=G(t);if(u.length>64)throw a("MIGRATION_LIMIT",{limit:64});let g=[...u].sort((I,w)=>I===o?-1:w===o?1:I.localeCompare(w)).map(I=>$(t,I)),h=ze(g.length?g.map(I=>I.config):[J]);if(await xe([h]),g.length===1&&g[0].name===o){if(D(t,g[0]),JSON.stringify(G(t))!==JSON.stringify(u))throw a("MIGRATION_CHANGED");return{path:s,config:g[0].config,raw:g[0].raw,sourceRaws:[g[0].raw],migrated:0}}let p=JSON.stringify(h,null,2)+`
4
+ `)}finally{ne(s)}}catch(s){if(s.code!=="EEXIST")throw y(s,"CONFIG_IO")}}r=oe(n,T.O_RDONLY|T.O_NOFOLLOW|T.O_NONBLOCK);let t=De(r);if(t.size>1024*1024)throw a("CONFIG_SIZE",{bytes:t.size,limit:1024*1024});if(!t.isFile()||t.nlink!==1||process.getuid&&t.uid!==process.getuid())throw a("CONFIG_UNSAFE");process.platform!=="win32"&&Re(r,384);let o=Fe(r,"utf8");if(Buffer.byteLength(o)>1024*1024)throw a("CONFIG_SIZE",{bytes:Buffer.byteLength(o),limit:1024*1024});return{config:B(o),raw:o}}catch(t){let o=t&&typeof t=="object"&&"code"in t?t.code:void 0;throw y(t,o==="ELOOP"||o==="EISDIR"||o==="ENOTDIR"?"CONFIG_UNSAFE":"CONFIG_IO")}finally{r!==void 0&&ne(r)}}var Y=/^protecter(?:\.[a-f0-9]{32})?\.json$/;function xe(n){return new Promise((e,r)=>{let t=new Ue(ve(new URL("./validate-worker.mjs",import.meta.url)),{workerData:n,execArgv:[],resourceLimits:{maxOldGenerationSizeMb:64}}),o=!1,s=(i,c=a("WORKER_FAILED"))=>{o||(o=!0,clearTimeout(l),t.terminate(),i?e():r(c))},l=setTimeout(()=>s(!1,a("CONFIG_TIMEOUT",{timeoutMs:2e3})),2e3);t.once("message",i=>i===!0?s(!0):s(!1,b(i,"CONFIG_SCHEMA"))),t.once("error",()=>s(!1)),t.once("exit",()=>s(!1))})}function ze(n){let e=new Set,r=[];for(let o of n)for(let s of o.sensitiveWords){let l=typeof s=="string"?JSON.stringify(["literal",s,null]):s.type==="literal"?JSON.stringify(["literal",s.value,s.replacement??null]):JSON.stringify(["regex",s.pattern,[...new Set((s.flags??"")+"g")].sort().join(""),s.replacement??null]);e.has(l)||(e.add(l),r.push(s))}let t=JSON.stringify({version:1,sensitiveWords:r});if(Buffer.byteLength(t)>1024*1024)throw a("CONFIG_SIZE",{bytes:Buffer.byteLength(t),limit:1024*1024});return B(t)}function se(n){let e;try{e=d.lstatSync(n,{bigint:!0})}catch(r){throw y(r,"CONFIG_IO")}if(!e.isFile()||e.nlink!==1n||process.getuid&&e.uid!==BigInt(process.getuid()))throw a("CONFIG_UNSAFE");return`${e.dev}:${e.ino}:${e.size}:${e.mtimeNs}`}function $(n,e){let r=C(n,e),t=se(r),o=ie(r);if(se(r)!==t)throw a("MIGRATION_CHANGED");return{name:e,...o,identity:t,digest:Me("sha256").update(o.raw).digest("hex")}}function D(n,e){let r=$(n,e.name);if(r.identity!==e.identity||r.digest!==e.digest)throw a("MIGRATION_CHANGED")}function G(n){return d.readdirSync(n).filter(e=>Y.test(e)).sort()}function ae(n){if(process.platform==="win32")return;let e=d.openSync(n,d.constants.O_RDONLY);try{d.fsyncSync(e)}finally{d.closeSync(e)}}async function ce(n,e,r={}){if(!/^[a-f0-9]{32}$/.test(e))throw a("MACHINE_ID");let t=Ge(n),o=`protecter.${e}.json`,s=C(t,o);d.mkdirSync(t,{recursive:!0,mode:448});let l=C(t,".protecter-migration.lock"),i=Date.now()+(r.lockTimeoutMs??5e3);for(;;)try{d.mkdirSync(l,{mode:448});break}catch(u){if(u.code!=="EEXIST")throw y(u,"MIGRATION_IO");if(Date.now()>=i)throw a("MIGRATION_LOCKED",{timeoutMs:r.lockTimeoutMs??5e3});await new Promise(O=>setTimeout(O,50))}let c;try{let u=G(t);if(u.length>64)throw a("MIGRATION_LIMIT",{limit:64});let g=[...u].sort((I,w)=>I===o?-1:w===o?1:I.localeCompare(w)).map(I=>$(t,I)),h=ze(g.length?g.map(I=>I.config):[J]);if(await xe([h]),g.length===1&&g[0].name===o){if(D(t,g[0]),JSON.stringify(G(t))!==JSON.stringify(u))throw a("MIGRATION_CHANGED");return{path:s,config:g[0].config,raw:g[0].raw,sourceRaws:[g[0].raw],migrated:0}}let p=JSON.stringify(h,null,2)+`
5
5
  `;if(Buffer.byteLength(p)>1024*1024)throw a("CONFIG_SIZE",{bytes:Buffer.byteLength(p),limit:1024*1024});c=C(t,`.protecter-migration.${ke()}.tmp`);let N=d.openSync(c,d.constants.O_WRONLY|d.constants.O_CREAT|d.constants.O_EXCL,384);try{d.writeFileSync(N,p),d.fsyncSync(N)}finally{d.closeSync(N)}if(r.checkpoint?.("beforeCommit",s),JSON.stringify(G(t))!==JSON.stringify(u))throw a("MIGRATION_CHANGED");for(let I of g)D(t,I);d.renameSync(c,s),c=void 0,ae(t);let E=$(t,o);if(E.raw!==p)throw a("MIGRATION_CHANGED");r.checkpoint?.("afterCommit",s);for(let I of g)I.name!==o&&(r.checkpoint?.("beforeDelete",C(t,I.name)),D(t,E),D(t,I),d.unlinkSync(C(t,I.name)));ae(t);let F=[o];if(JSON.stringify(G(t))!==JSON.stringify(F))throw a("MIGRATION_CHANGED");return D(t,E),{path:s,config:h,raw:p,sourceRaws:[...new Set([...g.map(I=>I.raw),p])],migrated:g.filter(I=>I.name!==o).length}}catch(u){throw u instanceof A?u:y(u,"MIGRATION_IO")}finally{if(c)try{d.unlinkSync(c)}catch{}d.rmdirSync(l)}}import{createHash as He}from"node:crypto";import{readFileSync as Je}from"node:fs";import{execFileSync as le}from"node:child_process";function W(n){let e=n.trim().toLowerCase().replace(/-/g,"");if(!/^[a-f0-9]{32}$/.test(e)||/^0+$/.test(e))throw a("MACHINE_ID");return He("sha256").update(`pi-information-protecter:v1:${e}`).digest("hex").slice(0,32)}function ue(){try{if(process.platform==="linux")return W(Je("/etc/machine-id","utf8"));if(process.platform==="darwin"){let n=le("/usr/sbin/ioreg",["-rd1","-c","IOPlatformExpertDevice"],{encoding:"utf8",timeout:5e3,maxBuffer:1048576,stdio:["ignore","pipe","pipe"]});return W(n.match(/"IOPlatformUUID"\s*=\s*"([a-fA-F0-9-]+)"/)?.[1]??"")}if(process.platform==="win32"){let n=le("reg.exe",["query","HKLM\\SOFTWARE\\Microsoft\\Cryptography","/v","MachineGuid","/reg:64"],{encoding:"utf8",timeout:5e3,maxBuffer:65536,stdio:["ignore","pipe","pipe"]});return W(n.match(/MachineGuid\s+REG_SZ\s+([a-fA-F0-9-]+)/i)?.[1]??"")}}catch{}throw a("MACHINE_ID")}import*as f from"node:fs";import{randomUUID as Be}from"node:crypto";var fe=32*1024*1024,de=8*1024*1024;function $e(n){let e=r=>String(r).padStart(2,"0");return`${n.getFullYear()}-${e(n.getMonth()+1)}-${e(n.getDate())} ${e(n.getHours())}:${e(n.getMinutes())}:${e(n.getSeconds())}`}function X(n,e,r=e){let t=f.openSync(n,(e?f.constants.O_RDWR|(r?f.constants.O_CREAT:0):f.constants.O_RDONLY)|f.constants.O_NOFOLLOW|f.constants.O_NONBLOCK,384);try{let o=f.fstatSync(t);if(!o.isFile()||o.nlink!==1||process.getuid&&o.uid!==process.getuid())throw a("AUDIT_UNSAFE");return process.platform!=="win32"&&f.fchmodSync(t,384),t}catch(o){throw f.closeSync(t),y(o,"AUDIT_UNSAFE")}}function M(n){let e=n&&typeof n=="object"&&"code"in n?n.code:void 0;return y(n,e==="ELOOP"||e==="EISDIR"||e==="ENOTDIR"?"AUDIT_UNSAFE":"AUDIT_IO")}async function ge(n){let e=`${n}.lock`,r=Date.now()+2e3;for(;;)try{return f.mkdirSync(e,{mode:448}),e}catch(t){if(t.code!=="EEXIST")throw y(t,"AUDIT_IO");if(Date.now()>=r)throw a("AUDIT_LOCKED",{timeoutMs:2e3});await new Promise(o=>setTimeout(o,25))}}async function me(n){let e=await ge(n),r;try{try{r=X(n,!0,!1)}catch(o){if(o.code==="ENOENT")return 0;throw o}let t=f.fstatSync(r).size;return f.ftruncateSync(r,0),f.fsyncSync(r),t}catch(t){throw M(t)}finally{try{r!==void 0&&f.closeSync(r)}finally{f.rmdirSync(e)}}}async function pe(n,e,r){if(!r.length)return;let t=new Date,o=Be(),s=Buffer.from(r.map(([c,u])=>JSON.stringify({version:1,time:$e(t),timezoneOffset:-t.getTimezoneOffset(),requestId:o,provider:e||"unknown",original:u,replacement:c})).join(`
6
6
  `)+`
7
7
  `);if(s.length>de)throw a("AUDIT_BATCH_LIMIT",{bytes:s.length,limit:de});let l=await ge(n),i;try{i=X(n,!0);let c=f.fstatSync(i).size;if(c+s.length>fe)throw a("AUDIT_FULL",{bytes:c+s.length,limit:fe});if(c){let u=Buffer.alloc(1);if(f.readSync(i,u,0,1,c-1),u[0]!==10)throw a("AUDIT_PARTIAL")}try{let u=0;for(;u<s.length;){let O=f.writeSync(i,s,u,s.length-u,c+u);if(!O)throw a("AUDIT_IO");u+=O}f.fsyncSync(i)}catch(u){throw f.ftruncateSync(i,c),f.fsyncSync(i),M(u)}}catch(c){throw M(c)}finally{i!==void 0&&f.closeSync(i),f.rmdirSync(l)}}function he(n,e=20){if(!Number.isInteger(e)||e<1||e>100)throw a("INTERNAL",{limit:100});let r;try{try{r=X(n,!1)}catch(h){if(h.code==="ENOENT")return{records:[],truncated:!1};throw h}let t=f.fstatSync(r).size,o=Math.max(0,t-1024*1024),s=Buffer.alloc(t-o),l=f.readSync(r,s,0,s.length,o),i=s.subarray(0,l).toString("utf8");if(o){let h=i.indexOf(`
8
8
  `);i=h<0?"":i.slice(h+1)}let c=i.split(`
9
- `),u=c.pop()!=="",O=[],g=!1;for(let h of c.slice(-e))try{let p=JSON.parse(h);if(p.version!==1||typeof p.time!="string"||typeof p.provider!="string"||typeof p.original!="string"||typeof p.replacement!="string"||typeof p.requestId!="string"||typeof p.timezoneOffset!="number")throw new Error;O.push(p)}catch{g=!0}return{records:O,truncated:o>0||c.length>e||u||g}}catch(t){throw M(t)}finally{r!==void 0&&f.closeSync(r)}}var k=class{constructor(e,r=2e3,t=ue){this.configDir=e;this.timeoutMs=r;this.machineId=t}configDir;timeoutMs;machineId;tokens=new Map;queue=Promise.resolve();workers=new Set;closed=!1;snapshot;lastError;restorePattern;restoreSize=-1;get lastDiagnostic(){return this.lastError?{...this.lastError}:void 0}get lastDiagnosticText(){return this.lastError?S(this.lastError):void 0}async initialize(){try{if(this.closed)throw a("NOT_READY");this.snapshot=void 0;let e;try{e=this.machineId()}catch{throw a("MACHINE_ID")}let r=await ce(this.configDir,e);if(this.closed)throw a("NOT_READY");this.snapshot=r,this.lastError=void 0}catch(e){let r=y(e,"CONFIG_IO");throw this.lastError=r.diagnostic,r}}get configPath(){return this.snapshot?.path??""}get auditPath(){return this.configPath?this.configPath.replace(/\.json$/,".jsonl"):""}auditRecords(e=20){if(!this.ready)throw this.notReady();return he(this.auditPath,e)}clearAuditRecords(){let e=this.queue.then(()=>{if(!this.ready)throw this.notReady();return me(this.auditPath)});return this.queue=e.catch(()=>{}),e}get ruleCount(){return this.snapshot?.config.sensitiveWords.length??0}get ready(){return!!this.snapshot&&!this.closed}get mappingCount(){return this.tokens.size}redact(e,r="unknown"){let t=this.queue.then(()=>this.scan(e,r)).catch(o=>{let s=y(o,"INTERNAL");throw this.lastError=s.diagnostic,s});return this.queue=t.catch(()=>{}),t}notReady(){return this.lastError?a(this.lastError.code,this.lastError):a("NOT_READY")}async scan(e,r){if(!this.ready)throw this.notReady();let{config:t,sourceRaws:o}=this.snapshot,s;try{s=JSON.stringify(e)}catch{throw a("PAYLOAD_JSON")}if(!s)throw a("PAYLOAD_JSON");if(Buffer.byteLength(s)>8*1024*1024)throw a("PAYLOAD_SIZE",{bytes:Buffer.byteLength(s),limit:8*1024*1024});let l;try{l=JSON.parse(s)}catch{throw a("PAYLOAD_JSON")}let i=await new Promise((c,u)=>{let O=!1,g;try{g=new Ye(We(new URL("./scan-worker.mjs",import.meta.url)),{workerData:{payload:l,rules:t.sensitiveWords,entries:[...this.tokens],configRaws:o},execArgv:[],resourceLimits:{maxOldGenerationSizeMb:128}})}catch(N){u(y(N,"WORKER_FAILED"));return}this.workers.add(g);let h=(N,E=a("WORKER_FAILED"))=>{if(!O){if(O=!0,clearTimeout(p),this.workers.delete(g),g.terminate(),!N||this.closed){u(this.closed?a("NOT_READY"):E);return}c(N)}},p=setTimeout(()=>h(void 0,a("SCAN_TIMEOUT",{timeoutMs:this.timeoutMs})),this.timeoutMs);g.once("message",N=>{N?.failed?h(void 0,b(N.diagnostic,"SCAN_INTERNAL")):N&&Array.isArray(N.additions)&&Array.isArray(N.hits)?h(N):h()}),g.once("error",()=>h()),g.once("exit",()=>h())});if(this.closed)throw a("NOT_READY");if(await pe(this.auditPath,r,i.hits),this.closed)throw a("NOT_READY");for(let[c,u]of i.additions)this.tokens.set(c,u);return i.payload}restoreText(e){if(!this.tokens.size)return e;if(this.restoreSize!==this.tokens.size){let r=t=>t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");this.restorePattern=new RegExp([...this.tokens.keys()].sort((t,o)=>o.length-t.length).map(r).join("|"),"g"),this.restoreSize=this.tokens.size}return e.replace(this.restorePattern,r=>this.tokens.get(r)??r)}restore(e,r=0){if(r>80)throw a("RESTORE_COMPLEXITY",{node:r});if(typeof e=="string")return this.restoreText(e);if(Array.isArray(e))return e.map(t=>this.restore(t,r+1));if(e&&typeof e=="object"){let t=Object.create(null);for(let[o,s]of Object.entries(e)){let l=this.restoreText(o);if(Object.hasOwn(t,l))throw a("RESTORE_KEY_COLLISION");t[l]=this.restore(s,r+1)}return t}return e}close(){this.closed=!0,this.snapshot=void 0,this.tokens.clear();for(let e of this.workers)e.terminate();this.workers.clear()}};import{realpathSync as Xe,statSync as Ie,readdirSync as je}from"node:fs";import{homedir as Ke}from"node:os";import{basename as j,dirname as L,isAbsolute as qe,relative as Ne,resolve as v}from"node:path";import{fileURLToPath as Ze}from"node:url";function Ve(n,e){let r=n.replace(/^@/,"");return r.startsWith("file://")&&(r=Ze(r)),(r==="~"||r.startsWith("~/"))&&(r=Ke()+r.slice(1)),v(e,r)}function K(n){try{return Xe(n)}catch{let e=L(n);return e===n?n:v(K(e),Ne(e,n))}}function Qe(n,e){let r=Ne(n,e);return r===""||!r.startsWith("..")&&!qe(r)}function ye(n,e){try{let r=Ie(n),t=Ie(e);return r.dev===t.dev&&r.ino===t.ino}catch{return!1}}function Oe(n,e,r,t){if(!t)return!0;let o=K(v(t));if(["bash","powershell"].includes(n)){let i=typeof e.command=="string"?e.command:"";if(/protecter(?:\.[a-f0-9]{32})?\.jsonl?|\.protecter-migration/i.test(i)||i.includes(t))return!0}let s=e.path??e.file_path??e.filePath,l=["grep","find","ls"].includes(n);if(typeof s!="string"&&!l)return!1;try{let i=Ve(typeof s=="string"?s:".",r),c=K(i),u=/^protecter\.[a-f0-9]{32}\.jsonl(?:\.lock)?$/,O=je(L(o)).filter(g=>u.test(g)).some(g=>ye(i,v(L(o),g)));return L(c)===L(o)&&(Y.test(j(c))||u.test(j(c))||j(c).startsWith(".protecter-migration"))||c===o||ye(i,t)||O||l&&Qe(c,o)}catch{return!0}}function tt(n){Q(H());let e=new k(et()),r=/^0\.85\./.test(Ee)&&Number(Ee.split(".")[2])>=1,t=!1,o,s=l=>{let i=S(l);return o={at:new Date().toISOString(),text:i},i};n.on("session_start",async(l,i)=>{try{if(!r)throw a("UNSUPPORTED_PI");await e.initialize(),t=!0;let c=e.ruleCount;i.ui.setStatus("protecter",m("statusBarRules",{rules:R("rules",c)})),c===0&&i.ui.notify(m("emptyRules"),"warning")}catch(c){t=!1,i.ui.setStatus("protecter",m("statusBarNotReady")),i.ui.notify(s(y(c,"CONFIG_IO").diagnostic),"error")}}),n.on("before_provider_request",async(l,i)=>{try{if(!r)throw a("UNSUPPORTED_PI");let c=await e.redact(l.payload,i.model?.provider??"unknown");return t=!0,c}catch(c){t=!1;let u=s(y(c,"INTERNAL").diagnostic);try{await i.abort()}catch{}try{i.ui.notify(u,"error")}catch{}return{}}}),n.on("message_end",l=>{if(l.message.role!=="assistant")return;let i=l.message;return{message:{...i,content:i.content.map(c=>c.type==="text"?{...c,text:e.restoreText(c.text)}:c.type==="thinking"?{...c,thinking:e.restoreText(c.thinking)}:c.type==="toolCall"?{...c,arguments:e.restore(c.arguments)}:c),...i.errorMessage?{errorMessage:e.restoreText(i.errorMessage)}:{}}}}),n.registerMarkdownTransformer((l,i)=>i.messageType==="user"?l:e.restoreText(l)),n.on("tool_call",(l,i)=>{if(!t)return{block:!0,reason:e.lastDiagnosticText??o?.text??S(a("NOT_READY").diagnostic)};let c=e.restore(l.input);for(let u of Object.keys(l.input))delete l.input[u];if(Object.assign(l.input,c),Oe(l.toolName,l.input,i.cwd,e.configPath))return{block:!0,reason:m("toolBlocked")}}),n.on("session_before_compact",(l,i)=>(i.ui.notify(m("compactUnsupported"),"warning"),{cancel:!0})),n.on("session_before_tree",l=>l.preparation.userWantsSummary?{cancel:!0}:void 0),n.registerCommand("protecter",{description:m("commandDescription"),handler:async(l,i)=>{let c=l.trim().split(/\s+/).filter(Boolean),u=c[0]??"logs";if(!["status","reload","logs"].includes(u)||u!=="logs"&&c.length>1||c.length>2||c[1]!==void 0&&c[1]!=="clear"&&!/^(?:[1-9]\d?|100)$/.test(c[1])){i.ui.notify(m("usage"),"info");return}if(u==="logs"){if(i.mode!=="tui"){i.ui.notify(m("logsTuiOnly"),"warning");return}if(c[1]==="clear"){try{if(!e.ready)throw a("NOT_READY");if(!await i.ui.confirm(m("clearTitle"),m("clearBody")))return;await i.waitForIdle();let p=await e.clearAuditRecords();t=e.ready,i.ui.notify(m("cleared",{bytes:R("bytes",p)}),"info")}catch(p){i.ui.notify(s(y(p,"AUDIT_IO").diagnostic),"error")}return}try{let{records:p,truncated:N}=e.auditRecords(Number(c[1]??20));if(!p.length){i.ui.notify(m("logsNone"),"info");return}let E=P=>JSON.stringify(P).replace(/[\u007f-\u009f\u202a-\u202e\u2066-\u2069]/g,x=>`\\u${x.charCodeAt(0).toString(16).padStart(4,"0")}`),F=p.map((P,x)=>`${x+1}. ${E(P.time)} | ${E(P.provider).slice(0,120)}`),I=await i.ui.select(m("logsSelectTitle")+(N?m("logsTruncated"):""),F),w=I===void 0?-1:F.indexOf(I);if(w<0||!await i.ui.confirm(m("logsRevealTitle"),m("logsRevealBody")))return;let U=E(p[w]);await i.ui.editor(m("logsPreviewTitle"),U.length>2e4?`${U.slice(0,2e4)}
10
- ${m("logsPreviewTruncated")}`:U)}catch(p){i.ui.notify(s(y(p,"AUDIT_IO").diagnostic),"error")}return}if(u==="reload"){await i.waitForIdle(),await i.reload();return}let O=e.lastDiagnosticText??o?.text,g=m("statusLine",{state:m(e.ready?"statusReady":"statusNotReady"),rules:R("rules",e.ruleCount),mappings:R("mappings",e.mappingCount)}),h=O?`
9
+ `),u=c.pop()!=="",O=[],g=!1;for(let h of c.slice(-e))try{let p=JSON.parse(h);if(p.version!==1||typeof p.time!="string"||typeof p.provider!="string"||typeof p.original!="string"||typeof p.replacement!="string"||typeof p.requestId!="string"||typeof p.timezoneOffset!="number")throw new Error;O.push(p)}catch{g=!0}return{records:O,truncated:o>0||c.length>e||u||g}}catch(t){throw M(t)}finally{r!==void 0&&f.closeSync(r)}}var k=class{constructor(e,r=2e3,t=ue){this.configDir=e;this.timeoutMs=r;this.machineId=t}configDir;timeoutMs;machineId;tokens=new Map;queue=Promise.resolve();workers=new Set;closed=!1;snapshot;lastError;restorePattern;restoreSize=-1;get lastDiagnostic(){return this.lastError?{...this.lastError}:void 0}get lastDiagnosticText(){return this.lastError?S(this.lastError):void 0}async initialize(){try{if(this.closed)throw a("NOT_READY");this.snapshot=void 0;let e;try{e=this.machineId()}catch{throw a("MACHINE_ID")}let r=await ce(this.configDir,e);if(this.closed)throw a("NOT_READY");this.snapshot=r,this.lastError=void 0}catch(e){let r=y(e,"CONFIG_IO");throw this.lastError=r.diagnostic,r}}get configPath(){return this.snapshot?.path??""}get auditPath(){return this.configPath?this.configPath.replace(/\.json$/,".jsonl"):""}auditRecords(e=20){if(!this.ready)throw this.notReady();return he(this.auditPath,e)}clearAuditRecords(){let e=this.queue.then(()=>{if(!this.ready)throw this.notReady();return me(this.auditPath)});return this.queue=e.catch(()=>{}),e}get ruleCount(){return this.snapshot?.config.sensitiveWords.length??0}get ready(){return!!this.snapshot&&!this.closed}get mappingCount(){return this.tokens.size}redact(e,r="unknown"){let t=this.queue.then(()=>this.scan(e,r)).catch(o=>{let s=y(o,"INTERNAL");throw this.lastError=s.diagnostic,s});return this.queue=t.catch(()=>{}),t}notReady(){return this.lastError?a(this.lastError.code,this.lastError):a("NOT_READY")}async scan(e,r){if(!this.ready)throw this.notReady();let{config:t,sourceRaws:o}=this.snapshot,s;try{s=JSON.stringify(e)}catch{throw a("PAYLOAD_JSON")}if(!s)throw a("PAYLOAD_JSON");if(Buffer.byteLength(s)>8*1024*1024)throw a("PAYLOAD_SIZE",{bytes:Buffer.byteLength(s),limit:8*1024*1024});let l;try{l=JSON.parse(s)}catch{throw a("PAYLOAD_JSON")}let i=await new Promise((c,u)=>{let O=!1,g;try{g=new Ye(We(new URL("./scan-worker.mjs",import.meta.url)),{workerData:{payload:l,rules:t.sensitiveWords,entries:[...this.tokens],configRaws:o},execArgv:[],resourceLimits:{maxOldGenerationSizeMb:128}})}catch(N){u(y(N,"WORKER_FAILED"));return}this.workers.add(g);let h=(N,E=a("WORKER_FAILED"))=>{if(!O){if(O=!0,clearTimeout(p),this.workers.delete(g),g.terminate(),!N||this.closed){u(this.closed?a("NOT_READY"):E);return}c(N)}},p=setTimeout(()=>h(void 0,a("SCAN_TIMEOUT",{timeoutMs:this.timeoutMs})),this.timeoutMs);g.once("message",N=>{N?.failed?h(void 0,b(N.diagnostic,"SCAN_INTERNAL")):N&&Array.isArray(N.additions)&&Array.isArray(N.hits)?h(N):h()}),g.once("error",()=>h()),g.once("exit",()=>h())});if(this.closed)throw a("NOT_READY");if(await pe(this.auditPath,r,i.hits),this.closed)throw a("NOT_READY");for(let[c,u]of i.additions)this.tokens.set(c,u);return i.payload}restoreText(e){if(!this.tokens.size)return e;if(this.restoreSize!==this.tokens.size){let r=t=>t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");this.restorePattern=new RegExp([...this.tokens.keys()].sort((t,o)=>o.length-t.length).map(r).join("|"),"g"),this.restoreSize=this.tokens.size}return e.replace(this.restorePattern,r=>this.tokens.get(r)??r)}restore(e,r=0){if(r>80)throw a("RESTORE_COMPLEXITY",{node:r});if(typeof e=="string")return this.restoreText(e);if(typeof e=="number"){let t=this.tokens.get(String(e));if(t===void 0)return e;let o=Number(t);return!Number.isFinite(o)||String(o)!==t?e:o}if(Array.isArray(e))return e.map(t=>this.restore(t,r+1));if(e&&typeof e=="object"){let t=Object.create(null);for(let[o,s]of Object.entries(e)){let l=this.restoreText(o);if(Object.hasOwn(t,l))throw a("RESTORE_KEY_COLLISION");t[l]=this.restore(s,r+1)}return t}return e}close(){this.closed=!0,this.snapshot=void 0,this.tokens.clear();for(let e of this.workers)e.terminate();this.workers.clear()}};import{realpathSync as Xe,statSync as Ie,readdirSync as je}from"node:fs";import{homedir as Ke}from"node:os";import{basename as j,dirname as L,isAbsolute as qe,relative as Ne,resolve as U}from"node:path";import{fileURLToPath as Ze}from"node:url";function Ve(n,e){let r=n.replace(/^@/,"");return r.startsWith("file://")&&(r=Ze(r)),(r==="~"||r.startsWith("~/"))&&(r=Ke()+r.slice(1)),U(e,r)}function K(n){try{return Xe(n)}catch{let e=L(n);return e===n?n:U(K(e),Ne(e,n))}}function Qe(n,e){let r=Ne(n,e);return r===""||!r.startsWith("..")&&!qe(r)}function ye(n,e){try{let r=Ie(n),t=Ie(e);return r.dev===t.dev&&r.ino===t.ino}catch{return!1}}function Oe(n,e,r,t){if(!t)return!0;let o=K(U(t));if(["bash","powershell"].includes(n)){let i=typeof e.command=="string"?e.command:"";if(/protecter(?:\.[a-f0-9]{32})?\.jsonl?|\.protecter-migration/i.test(i)||i.includes(t))return!0}let s=e.path??e.file_path??e.filePath,l=["grep","find","ls"].includes(n);if(typeof s!="string"&&!l)return!1;try{let i=Ve(typeof s=="string"?s:".",r),c=K(i),u=/^protecter\.[a-f0-9]{32}\.jsonl(?:\.lock)?$/,O=je(L(o)).filter(g=>u.test(g)).some(g=>ye(i,U(L(o),g)));return L(c)===L(o)&&(Y.test(j(c))||u.test(j(c))||j(c).startsWith(".protecter-migration"))||c===o||ye(i,t)||O||l&&Qe(c,o)}catch{return!0}}function tt(n){Q(H());let e=new k(et()),r=/^0\.85\./.test(Ee)&&Number(Ee.split(".")[2])>=1,t=!1,o,s=l=>{let i=S(l);return o={at:new Date().toISOString(),text:i},i};n.on("session_start",async(l,i)=>{try{if(!r)throw a("UNSUPPORTED_PI");await e.initialize(),t=!0;let c=e.ruleCount;i.ui.setStatus("protecter",m("statusBarRules",{rules:R("rules",c)})),c===0&&i.ui.notify(m("emptyRules"),"warning")}catch(c){t=!1,i.ui.setStatus("protecter",m("statusBarNotReady")),i.ui.notify(s(y(c,"CONFIG_IO").diagnostic),"error")}}),n.on("before_provider_request",async(l,i)=>{try{if(!r)throw a("UNSUPPORTED_PI");let c=await e.redact(l.payload,i.model?.provider??"unknown");return t=!0,c}catch(c){t=!1;let u=s(y(c,"INTERNAL").diagnostic);try{await i.abort()}catch{}try{i.ui.notify(u,"error")}catch{}return{}}}),n.on("message_end",l=>{if(l.message.role!=="assistant")return;let i=l.message;return{message:{...i,content:i.content.map(c=>c.type==="text"?{...c,text:e.restoreText(c.text)}:c.type==="thinking"?{...c,thinking:e.restoreText(c.thinking)}:c.type==="toolCall"?{...c,arguments:e.restore(c.arguments)}:c),...i.errorMessage?{errorMessage:e.restoreText(i.errorMessage)}:{}}}}),n.registerMarkdownTransformer((l,i)=>i.messageType==="user"?l:e.restoreText(l)),n.on("tool_call",(l,i)=>{if(!t)return{block:!0,reason:e.lastDiagnosticText??o?.text??S(a("NOT_READY").diagnostic)};let c=e.restore(l.input);for(let u of Object.keys(l.input))delete l.input[u];if(Object.assign(l.input,c),Oe(l.toolName,l.input,i.cwd,e.configPath))return{block:!0,reason:m("toolBlocked")}}),n.on("session_before_compact",(l,i)=>(i.ui.notify(m("compactUnsupported"),"warning"),{cancel:!0})),n.on("session_before_tree",l=>l.preparation.userWantsSummary?{cancel:!0}:void 0),n.registerCommand("protecter",{description:m("commandDescription"),handler:async(l,i)=>{let c=l.trim().split(/\s+/).filter(Boolean),u=c[0]??"logs";if(!["status","reload","logs"].includes(u)||u!=="logs"&&c.length>1||c.length>2||c[1]!==void 0&&c[1]!=="clear"&&!/^(?:[1-9]\d?|100)$/.test(c[1])){i.ui.notify(m("usage"),"info");return}if(u==="logs"){if(i.mode!=="tui"){i.ui.notify(m("logsTuiOnly"),"warning");return}if(c[1]==="clear"){try{if(!e.ready)throw a("NOT_READY");if(!await i.ui.confirm(m("clearTitle"),m("clearBody")))return;await i.waitForIdle();let p=await e.clearAuditRecords();t=e.ready,i.ui.notify(m("cleared",{bytes:R("bytes",p)}),"info")}catch(p){i.ui.notify(s(y(p,"AUDIT_IO").diagnostic),"error")}return}try{let{records:p,truncated:N}=e.auditRecords(Number(c[1]??20));if(!p.length){i.ui.notify(m("logsNone"),"info");return}let E=P=>JSON.stringify(P).replace(/[\u007f-\u009f\u202a-\u202e\u2066-\u2069]/g,x=>`\\u${x.charCodeAt(0).toString(16).padStart(4,"0")}`),F=p.map((P,x)=>`${x+1}. ${E(P.time)} | ${E(P.provider).slice(0,120)}`),I=await i.ui.select(m("logsSelectTitle")+(N?m("logsTruncated"):""),F),w=I===void 0?-1:F.indexOf(I);if(w<0||!await i.ui.confirm(m("logsRevealTitle"),m("logsRevealBody")))return;let v=E(p[w]);await i.ui.editor(m("logsPreviewTitle"),v.length>2e4?`${v.slice(0,2e4)}
10
+ ${m("logsPreviewTruncated")}`:v)}catch(p){i.ui.notify(s(y(p,"AUDIT_IO").diagnostic),"error")}return}if(u==="reload"){await i.waitForIdle(),await i.reload();return}let O=e.lastDiagnosticText??o?.text,g=m("statusLine",{state:m(e.ready?"statusReady":"statusNotReady"),rules:R("rules",e.ruleCount),mappings:R("mappings",e.mappingCount)}),h=O?`
11
11
  ${m("statusLastBlock",{at:o?.at??""})}
12
12
  ${O}`:`
13
13
  ${m("statusNoBlock")}`;i.ui.notify(g+h,e.ready?"info":"error")}}),n.on("session_shutdown",()=>e.close())}export{tt as default};
@@ -1,2 +1,2 @@
1
1
  /*! SPI Protecter | MIT | see LICENSE / SPI 隐私保护插件 | MIT 许可 */
2
- import{parentPort as J,workerData as k}from"node:worker_threads";import{randomBytes as H}from"node:crypto";var $=[[48,57],[65,90],[97,122],[192,214],[216,246],[248,255],[256,383],[7680,7935],[913,937],[945,969],[940,974],[1040,1103],[1488,1514],[1569,1594],[1601,1610],[3585,3630],[3632,3635],[3636,3642],[3648,3652],[3655,3662],[12353,12438],[12449,12538],[44032,55203],[19968,40869]],P=BigInt(Number.MAX_SAFE_INTEGER);function C(s){if(s<=1n)return 0n;let u=s.toString(2).length,l=Math.ceil(u/8),h=(1n<<BigInt(u))-1n;for(let d=0;d<64;d++){let f=0n;for(let g of H(l))f=f<<8n|BigInt(g);if(f&=h,f<s)return f}return s-1n}function U(s){return $.find(([u,l])=>s>=u&&s<=l)}function j(s){let u="";for(let l of s){let h=l.codePointAt(0),d=U(h);if(!d){u+=l;continue}let[f,g]=d,y=BigInt(g-f+1);u+=String.fromCodePoint(f+Number(C(y)))}return u}function F(s){if(!/^\d+$/.test(s))return;if(s.length===1)return s==="0"?void 0:String(1n+C(9n));if(s.startsWith("0")){let d="";for(let f of s)d+=f==="0"?"0":String(1n+C(9n));return d}let u=10n**BigInt(s.length-1),l=10n**BigInt(s.length)-1n,h=l>P?P:l;if(!(h<u))return String(u+C(h-u+1n))}function B(s){let u=Number(s);return Number.isFinite(u)&&String(u)===s}var N,M=0;function c(s,u){throw{code:s,rule:N,otherRule:u,node:M}}try{let L=function(e,r){for(let n=0;n<64;n++){let t=r&&/^\d+$/.test(e)?F(e):j(e);if(!(!t||t===e)&&!f.has(t)&&!d.has(t)&&!X.includes(t))return t}},D=function(e,r,n=!1){let t=d.get(e);return t?(r!==void 0&&t!==r&&c("FIXED_MAPPING_CONFLICT"),t):r!==void 0?(f.size>=5e4&&c("SCAN_MAPPING_LIMIT"),f.has(r)&&f.get(r)!==e&&c("FIXED_ALIAS_REUSED"),r.includes(e)&&c("FIXED_SENSITIVE_TARGET"),d.set(e,r),f.set(r,e),g.push([r,e]),r):(f.size>=5e4&&c("SCAN_MAPPING_LIMIT"),t=L(e,n),t||c("SCAN_UNIQUE_FAILED"),d.set(e,t),f.set(t,e),g.push([t,e]),t)},E=function(e,r=!1){let n=[];for(let o of T)if(N=o.rule,o.literal===void 0){o.regex.lastIndex=0;for(let i of e.matchAll(o.regex))i[0].length||c("SCAN_ZERO_WIDTH"),n.push([i.index,i.index+i[0].length,o.replacement,o.rule]),++O>1e5&&c("SCAN_MATCH_LIMIT")}else{if(!o.literal)continue;let i=0;for(;(i=e.indexOf(o.literal,i))!==-1;)n.push([i,i+o.literal.length,o.replacement,o.rule]),++O>1e5&&c("SCAN_MATCH_LIMIT"),i++}n.sort((o,i)=>o[0]-i[0]||i[1]-o[1]);let t=[];for(let o of n){let i=t.at(-1);i&&o[0]<i[1]?((i[2]!==void 0||o[2]!==void 0)&&((i[0]!==o[0]||i[1]!==o[1]||i[2]!==void 0&&o[2]!==void 0&&i[2]!==o[2])&&(N=i[3],c("FIXED_OVERLAP",o[3])),i[2]===void 0&&o[2]!==void 0&&(i[3]=o[3]),i[2]??=o[2]),i[1]=Math.max(i[1],o[1])):t.push([...o])}let x="",p=0;for(let[o,i,a,A]of t){N=A;let S=D(e.slice(o,i),a,r);y.add(S),x+=e.slice(p,o)+S,p=i}return x+e.slice(p)},_=function(e,r=!1){let n="",t=0,x=a=>a.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),p=[...f.keys()];if(!p.length)return E(e,r);let o=new RegExp(p.sort((a,A)=>A.length-a.length).map(x).join("|"),"g"),i=[...e.matchAll(o)].filter(a=>f.has(a[0]));for(let a of T)if(!(!a.regex||!p.length)){N=a.rule,a.regex.lastIndex=0;for(let A of e.matchAll(a.regex))++O>1e5&&c("SCAN_MATCH_LIMIT"),A[0].length||c("SCAN_ZERO_WIDTH"),i.some(S=>p.includes(S[0])&&A.index<S.index+S[0].length&&A.index+A[0].length>S.index)&&c("FIXED_ALIAS_CROSSING")}for(let a of i)f.has(a[0])&&(n+=E(e.slice(t,a.index),r)+a[0],t=a.index+a[0].length);return n+E(e.slice(t),r)},I=function(e,r=0){if(M=++b,N=void 0,(b>2e5||r>80)&&c("SCAN_COMPLEXITY"),typeof e=="string"){if(R.some(n=>e.includes(n)))return _(e);if(/^\s*[[{]/.test(e)){let n;try{n=JSON.parse(e)}catch{}if(n&&typeof n=="object"){let t=JSON.stringify(I(n,r+1)),x=_(t);if(x===t)return t;try{JSON.parse(x)}catch{c("SCAN_JSON_REWRITE")}return x}}return _(e)}if(typeof e=="number"){let n=String(e),t=_(n,!0);return t===n?e:(B(t)||c("SCAN_NUMBER_UNSAFE"),Number(t))}if(Array.isArray(e))return e.map(n=>I(n,r+1));if(e&&typeof e=="object"){typeof e.type=="string"&&/image|audio|video|document|(^|_)file($|_)/i.test(e.type)&&c("SCAN_ATTACHMENT"),Object.keys(e).some(t=>/^(inlineData|inline_data|fileData|file_data|image_url|input_audio)$/.test(t))&&c("SCAN_ATTACHMENT");let n=Object.create(null);for(let[t,x]of Object.entries(e)){let p=_(t);Object.hasOwn(n,p)&&c("SCAN_KEY_COLLISION"),n[p]=I(x,r+1)}return n}if(e===null||typeof e=="boolean")return e;c("PAYLOAD_JSON")},m=function(e,r,n=0){if(n>80&&c("SCAN_COMPLEXITY"),typeof e=="string"){if(e.includes(r))return!0;let t;try{t=JSON.parse(e)}catch{return!1}return t&&typeof t=="object"?m(t,r,n+1):!1}return Array.isArray(e)?e.some(t=>m(t,r,n+1)):e&&typeof e=="object"?Object.entries(e).some(([t,x])=>t.includes(r)||m(x,r,n+1)):!1};W=L,Y=D,z=E,K=_,V=I,Z=m;let{payload:s,rules:u,entries:l,configRaws:h}=k,d=new Map(l.map(([e,r])=>[r,e])),f=new Map(l),g=[],y=new Set,T=u.map((e,r)=>{let n=r+1;return typeof e=="string"?{literal:e,rule:n}:e.type==="literal"?{literal:e.value,replacement:e.replacement,rule:n}:{rule:n,replacement:e.replacement,regex:new RegExp(e.pattern,[...new Set((e.flags??"")+"g")].join(""))}});for(let e of d.keys())T.push({literal:e});let R=[...new Set(h.flatMap(e=>[e,JSON.stringify(JSON.parse(e))]))];for(let e of R)T.push({literal:e});let X=JSON.stringify(s),O=0,b=0,w=I(s),G=[...y].filter(e=>m(w,e)).map(e=>[e,f.get(e)]);J.postMessage({payload:w,additions:g,hits:G})}catch(s){J.postMessage({failed:!0,diagnostic:{code:typeof s?.code=="string"?s.code:"SCAN_INTERNAL",rule:s?.rule??N,otherRule:s?.otherRule,node:s?.node??M}})}var W,Y,z,K,V,Z;
2
+ import{parentPort as U,workerData as ee}from"node:worker_threads";import{randomBytes as k}from"node:crypto";var Y=[[48,57],[65,90],[97,122],[192,214],[216,246],[248,255],[256,383],[7680,7935],[913,937],[945,969],[940,974],[1040,1103],[1488,1514],[1569,1594],[1601,1610],[3585,3630],[3632,3635],[3636,3642],[3648,3652],[3655,3662],[12353,12438],[12449,12538],[44032,55203],[19968,40869]],B=BigInt(Number.MAX_SAFE_INTEGER);function S(n){if(n<=1n)return 0n;let t=n.toString(2).length,o=Math.ceil(t/8),h=(1n<<BigInt(t))-1n;for(let d=0;d<64;d++){let a=0n;for(let x of k(o))a=a<<8n|BigInt(x);if(a&=h,a<n)return a}return n-1n}function Z(n){return Y.find(([t,o])=>n>=t&&n<=o)}function z(n){let t="";for(let o of n){let h=o.codePointAt(0),d=Z(h);if(!d){t+=o;continue}let[a,x]=d,A=BigInt(x-a+1);t+=String.fromCodePoint(a+Number(S(A)))}return t}var K=/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/,Q={1:[0n,9n],2:[10n,99n],3:[100n,255n]};function q(n){if(n.length>1&&n.startsWith("0")){let h="0";for(let d=1;d<n.length;d++)h+=String(S(10n));return h}let[t,o]=Q[n.length];return String(t+S(o-t+1n))}function J(n){let t=K.exec(n);if(!(!t||t.slice(1).some(o=>Number(o)>255)))return t.slice(1).map(q).join(".")}function $(n){let t="";for(let o of n)o>="0"&&o<="9"?t+=String(S(10n)):o>="a"&&o<="f"?t+=String.fromCharCode(97+Number(S(6n))):o>="A"&&o<="F"?t+=String.fromCharCode(65+Number(S(6n))):t+=o;return t}function v(n){if(!n.includes(":"))return;let t=n.lastIndexOf(":"),o=n.slice(t+1);if(o.includes(".")){let h=J(o);return!h||!/^[0-9A-Fa-f:]*$/.test(n.slice(0,t+1))?void 0:$(n.slice(0,t+1))+h}if(/^[0-9A-Fa-f:]+$/.test(n))return $(n)}function X(n){return J(n)??v(n)??z(n)}function H(n,t=!0){if(!/^\d+$/.test(n))return;if(n.length===1)return n==="0"?void 0:String(1n+S(9n));if(n.startsWith("0")){let a="";for(let x of n)a+=x==="0"?"0":String(1n+S(9n));return a}let o=10n**BigInt(n.length-1),h=10n**BigInt(n.length)-1n,d=t&&h>B?B:h;if(!(d<o))return String(o+S(d-o+1n))}function G(n){let t=/^([0-9.+-]*?)([eE][+-]?\d+)?$/.exec(n);if(!t||!/\d/.test(t[1]))return;let o=t[1],h=t[2]??"",d=o.indexOf("."),a=o.search(/\d(?!.*\d)/),x=d===-1?o.length:d,A="";for(let[m,I]of[...o].entries()){if(I<"0"||I>"9"){A+=I;continue}let b=d===-1||m<d,y=b&&!/\d/.test(A),C=!b&&m===a;if(I==="0"&&(y||C)){A+="0";continue}let R=y&&x>1||C;A+=String(R?1n+S(9n):S(10n))}return A+h}function P(n){let t=Number(n);return Number.isFinite(t)&&String(t)===n}var _,D=0;function l(n,t){throw{code:n,rule:_,otherRule:t,node:D}}try{let R=function(e,i,r){for(let f=0;f<64;f++){let c;if(/^\d+$/.test(e)?c=H(e,i):i?c=G(e):c=X(e),!(!c||c===e)&&!a.has(c)&&!d.has(c)&&!b.includes(c)&&!(r&&!r(c)))return c}},w=function(e,i,r=!1,f){let c=d.get(e);return c?(i!==void 0&&c!==i&&l("FIXED_MAPPING_CONFLICT"),c):i!==void 0?(a.size>=5e4&&l("SCAN_MAPPING_LIMIT"),a.has(i)&&a.get(i)!==e&&l("FIXED_ALIAS_REUSED"),i.includes(e)&&l("FIXED_SENSITIVE_TARGET"),d.set(e,i),a.set(i,e),x.push([i,e]),i):(a.size>=5e4&&l("SCAN_MAPPING_LIMIT"),c=R(e,r,f),c||l("SCAN_UNIQUE_FAILED"),d.set(e,c),a.set(c,e),x.push([c,e]),c)},L=function(e,i=!1){let r=[];for(let u of m)if(_=u.rule,u.literal===void 0){u.regex.lastIndex=0;for(let s of e.matchAll(u.regex))s[0].length||l("SCAN_ZERO_WIDTH"),r.push([s.index,s.index+s[0].length,u.replacement,u.rule]),++y>1e5&&l("SCAN_MATCH_LIMIT")}else{if(!u.literal)continue;let s=0;for(;(s=e.indexOf(u.literal,s))!==-1;)r.push([s,s+u.literal.length,u.replacement,u.rule]),++y>1e5&&l("SCAN_MATCH_LIMIT"),s++}r.sort((u,s)=>u[0]-s[0]||s[1]-u[1]);let f=[];for(let u of r){let s=f.at(-1);s&&u[0]<s[1]?((s[2]!==void 0||u[2]!==void 0)&&((s[0]!==u[0]||s[1]!==u[1]||s[2]!==void 0&&u[2]!==void 0&&s[2]!==u[2])&&(_=s[3],l("FIXED_OVERLAP",u[3])),s[2]===void 0&&u[2]!==void 0&&(s[3]=u[3]),s[2]??=u[2]),s[1]=Math.max(s[1],u[1])):f.push([...u])}let c="",g=0;for(let[u,s,p,N]of f){_=N;let E=i?W=>P(e.slice(0,u)+W+e.slice(s)):void 0,j=w(e.slice(u,s),p,i,E);A.add(j),c+=e.slice(g,u)+j,g=s}return c+e.slice(g)},T=function(e,i=!1){let r="",f=0,c=p=>p.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),g=[...a.keys()];if(!g.length)return L(e,i);let u=new RegExp(g.sort((p,N)=>N.length-p.length).map(c).join("|"),"g"),s=[...e.matchAll(u)].filter(p=>a.has(p[0]));for(let p of m)if(!(!p.regex||!g.length)){_=p.rule,p.regex.lastIndex=0;for(let N of e.matchAll(p.regex))++y>1e5&&l("SCAN_MATCH_LIMIT"),N[0].length||l("SCAN_ZERO_WIDTH"),s.some(E=>g.includes(E[0])&&N.index<E.index+E[0].length&&N.index+N[0].length>E.index)&&l("FIXED_ALIAS_CROSSING")}for(let p of s)a.has(p[0])&&(r+=L(e.slice(f,p.index),i)+p[0],f=p.index+p[0].length);return r+L(e.slice(f),i)},O=function(e,i=0){if(D=++C,_=void 0,(C>2e5||i>80)&&l("SCAN_COMPLEXITY"),typeof e=="string"){if(I.some(r=>e.includes(r)))return T(e);if(/^\s*[[{]/.test(e)){let r;try{r=JSON.parse(e)}catch{}if(r&&typeof r=="object"){let f=JSON.stringify(O(r,i+1)),c=T(f);if(c===f)return f;try{JSON.parse(c)}catch{l("SCAN_JSON_REWRITE")}return c}}return T(e)}if(typeof e=="number"){let r=String(e),f=T(r,!0);return f===r?e:(P(f)||l("SCAN_NUMBER_UNSAFE"),Number(f))}if(Array.isArray(e))return e.map(r=>O(r,i+1));if(e&&typeof e=="object"){typeof e.type=="string"&&/image|audio|video|document|(^|_)file($|_)/i.test(e.type)&&l("SCAN_ATTACHMENT"),Object.keys(e).some(f=>/^(inlineData|inline_data|fileData|file_data|image_url|input_audio)$/.test(f))&&l("SCAN_ATTACHMENT");let r=Object.create(null);for(let[f,c]of Object.entries(e)){let g=T(f);Object.hasOwn(r,g)&&l("SCAN_KEY_COLLISION"),r[g]=O(c,i+1)}return r}if(e===null||typeof e=="boolean")return e;l("PAYLOAD_JSON")},M=function(e,i,r=0){if(r>80&&l("SCAN_COMPLEXITY"),typeof e=="string"){if(e.includes(i))return!0;let f;try{f=JSON.parse(e)}catch{return!1}return f&&typeof f=="object"?M(f,i,r+1):!1}return Array.isArray(e)?e.some(f=>M(f,i,r+1)):e&&typeof e=="object"?Object.entries(e).some(([f,c])=>f.includes(i)||M(c,i,r+1)):!1};ne=R,te=w,re=L,ie=T,se=O,oe=M;let{payload:n,rules:t,entries:o,configRaws:h}=ee,d=new Map(o.map(([e,i])=>[i,e])),a=new Map(o),x=[],A=new Set,m=t.map((e,i)=>{let r=i+1;return typeof e=="string"?{literal:e,rule:r}:e.type==="literal"?{literal:e.value,replacement:e.replacement,rule:r}:{rule:r,replacement:e.replacement,regex:new RegExp(e.pattern,[...new Set((e.flags??"")+"g")].join(""))}});for(let e of d.keys())m.push({literal:e});let I=[...new Set(h.flatMap(e=>[e,JSON.stringify(JSON.parse(e))]))];for(let e of I)m.push({literal:e});let b=JSON.stringify(n),y=0,C=0,F=O(n),V=[...A].filter(e=>M(F,e)).map(e=>[e,a.get(e)]);U.postMessage({payload:F,additions:x,hits:V})}catch(n){U.postMessage({failed:!0,diagnostic:{code:typeof n?.code=="string"?n.code:"SCAN_INTERNAL",rule:n?.rule??_,otherRule:n?.otherRule,node:n?.node??D}})}var ne,te,re,ie,se,oe;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-information-protecter",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
4
4
  "description": "Sensitive Personal Information (SPI) Protecter — local reversible privacy redaction for Pi",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -5,7 +5,13 @@
5
5
  { "type": "literal", "value": "Apple Inc.", "replacement": "Alphabet Inc." },
6
6
  { "type": "literal", "value": "example-private-key-123", "replacement": "MyPrivateKey" },
7
7
  { "type": "literal", "value": "replace-with-your-real-password" },
8
- { "type": "regex", "pattern": "https?://[^\\s\\\"<>]+", "flags": "i" },
8
+ { "type": "regex", "pattern": "https?://[^\\s\\\"<>/:]+", "flags": "i" },
9
+ { "type": "literal", "value": "vpn.internal.example", "replacement": "gateway.internal.example" },
10
+ { "type": "regex", "pattern": "(?<![\\w.-])(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+(?:corp|internal|intra|lan|local)(?![\\w.-])", "flags": "i" },
11
+ { "type": "regex", "pattern": "(?<![\\w.-])[A-Za-z0-9._%+-]+@[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?(?:\\.[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?)*\\.[A-Za-z]{2,}(?![\\w.-])" },
12
+ { "type": "regex", "pattern": "(?<![\\w.])(?:(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\.){3}(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(?![\\w.])" },
13
+ { "type": "regex", "pattern": "(?<![\\w:.])(?:[0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4}(?![\\w:.])" },
14
+ { "type": "regex", "pattern": "(?<![\\w:.])(?:[0-9A-Fa-f]{1,4}:){1,6}:(?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4}){0,5})?(?![\\w:.])" },
9
15
  { "type": "regex", "pattern": "(?<!\\d)1[3-9]\\d{9}(?!\\d)" },
10
16
  { "type": "regex", "pattern": "(?<![0-9A-Za-z])\\d{17}[0-9Xx](?![0-9A-Za-z])" },
11
17
  { "type": "regex", "pattern": "(?<=Bearer )[A-Za-z0-9._~+/-]+=*", "flags": "i" }