dsh-rule-engine 0.5.11 → 0.5.13
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/README.md +30 -99
- package/lib/core/authorization.js +517 -513
- package/lib/core/config.js +73 -66
- package/lib/core/contract.js +41 -0
- package/lib/core/guard-core.js +754 -724
- package/lib/core/intent.js +11 -3
- package/lib/core/label-fingerprint.js +145 -0
- package/lib/core/lexicon.js +72 -72
- package/lib/core/parser.js +111 -103
- package/lib/core/patterns.js +42 -3
- package/lib/core/state.js +324 -301
- package/lib/core/understander.js +221 -150
- package/lib/index.js +1788 -1663
- package/lib/service.js +17 -2
- package/package.json +1 -1
- package/scripts/publish-aptitude-check.mjs +144 -0
- package/scripts/verify-all.mjs +4 -0
package/lib/core/patterns.js
CHANGED
|
@@ -247,6 +247,25 @@ const READONLY_CMD_RE =
|
|
|
247
247
|
/(?:\b(?:Get-Content|Get-ChildItem|Get-Item|Get-Command|Get-Date|Select-String|Find-String|Test-Path|Get-Process|Get-Service|Get-ItemProperty|Get-Variable|Get-FileHash|cat|type|dir|ls|grep|findstr|more|netstat|where|echo|Write-Output|Write-Host|Get-Location|pwd|cwd|Get-Help|help|Select-Object|Out-String|Format-Table|Format-List|Format-Wide|Measure-Object|Sort-Object|Where-Object|Group-Object|ForEach-Object|Out-Null|ConvertTo-Json|ConvertFrom-Json|Join-Path|Split-Path|Resolve-Path|New-Object|Add-Type|Get-Member|Get-ChildItemProperty|Get-CimInstance|Get-WmiObject)\b)|(?:\bgit\s+(?:-[^\s]+\s+[^\s]+\s+|--[^\s]+\s+)*\b(?:status|log|diff|show|branch|tag|remote|rev-parse|diff-tree|ls-files)(?:\s|$))|(?:\bdsh\s+(?:--version|--help|--dump-config|plugin\s+(?:list|show|status))(?:\s|$))|(?:\bdsh\s+--profile\s+[^\s]+\s+(?:--dump-config|plugin\s+(?:list|show|status))(?:\s|$))|(?:\bnode\s+(?:--check|--test|--version)(?:\s|$))|(?:\bnpm\s+(?:test|run\s+test)(?:\s|$))|(?:\bpnpm\s+(?:test|run\s+test)(?:\s|$))|(?:\bgh\s+(?:auth\s+status|repo\s+view)(?:\s|$))|(?:\bnpm\s+(?:ls|view)(?:\s|$))/i;
|
|
248
248
|
const MUTATING_CMD_RE =
|
|
249
249
|
/(?:Set-Content|Add-Content|Out-File|Tee-Object|Remove-Item|Move-Item|Copy-Item|Rename-Item|New-Item|Clear-Content|Start-Process|Invoke-Expression|Invoke-Item|Export-Csv|Export-Clixml|Export-ModuleMember|Compress-Archive|Expand-Archive|Add-Type\s+-Output(?:Assembly|Type)|git\s+(?:push|commit)|rm\s+-r|rmdir\s+\/s|del\s+\/s|(?:^|[^0-9])>>|(?:^|[^0-9])>|(?:curl|wget|iwr|Invoke-WebRequest|Invoke-RestMethod)\s+(?=[^\n;|]*\s(?:-o|--output|-OutFile|OutFile)\s))/i;
|
|
250
|
+
// 0.5.12(F4):PowerShell 别名展开表——写/删除/移动/执行别名 → 标准 cmdlet 名
|
|
251
|
+
// 供 normalizeAliases 做命令规范化(MUTATING_CMD_RE 覆盖完整 cmdlet;别名先展开再判)
|
|
252
|
+
const PS_ALIAS_MAP = [
|
|
253
|
+
[/^rm\b(?![a-z])/i, "Remove-Item"], [/^rd\b(?![a-z])/i, "Remove-Item"], [/^ri\b(?![a-z])/i, "Remove-Item"],
|
|
254
|
+
[/^del\b(?![a-z])/i, "Remove-Item"], [/^erase\b(?![a-z])/i, "Remove-Item"],
|
|
255
|
+
[/^cp\b(?![a-z])/i, "Copy-Item"], [/^copy\b(?![a-z])/i, "Copy-Item"],
|
|
256
|
+
[/^mv\b(?![a-z])/i, "Move-Item"], [/^move\b(?![a-z])/i, "Move-Item"],
|
|
257
|
+
[/^ren\b(?![a-z])/i, "Rename-Item"], [/^iex\b(?![a-z])/i, "Invoke-Expression"],
|
|
258
|
+
[/^gc\b(?![a-z])/i, "Get-Content"], [/^gci\b(?![a-z])/i, "Get-ChildItem"], [/^gi\b(?![a-z])/i, "Get-Item"],
|
|
259
|
+
[/^gp\b(?![a-z])/i, "Get-ItemProperty"], [/^gv\b(?![a-z])/i, "Get-Variable"], [/^gl\b(?![a-z])/i, "Get-Location"]
|
|
260
|
+
];
|
|
261
|
+
/** 规范化命令文本中的 PS 别名 → 标准 cmdlet 大写(只变换命令头,不动参数/字符串) */
|
|
262
|
+
export function normalizeAliases(command) {
|
|
263
|
+
const s = String(command || "");
|
|
264
|
+
return s.replace(/(?:^|[\s;|&{(])\s*(rm|rd|ri|del|erase|cp|copy|mv|move|ren|iex|gc|gci|gi|gp|gv|gl)(?=\s+)/gi, (m, name) => {
|
|
265
|
+
const hit = PS_ALIAS_MAP.find(([re]) => re.test(name));
|
|
266
|
+
return hit ? `${m.slice(0, m.length - name.length)}${hit[1]}` : m;
|
|
267
|
+
});
|
|
268
|
+
}
|
|
250
269
|
// .NET 静态/实例写方法(MUTATING 补充:Set-Content 等 cmdlet 之外的写路径)
|
|
251
270
|
const MUTATING_METHOD_RE =
|
|
252
271
|
/(?:\[[\w.]+\]::\s*(?:Write|Set|Remove|Delete|Move|Copy|Create|Save|Append)\w*\s*\(|\.(?:Write(?:AllText|Line|Bytes|Text)?|SetContent|MoveTo?|CopyTo|Delete|Remove|SaveAs?2?\s*\(|CreateDirectory|CreateFile|WriteLine)\s*\()/i;
|
|
@@ -255,7 +274,10 @@ const MUTATING_METHOD_RE =
|
|
|
255
274
|
// 保守边界:内容属性赋值($doc.Text=... / $doc.Content.Text=...)是写文档,不豁免。
|
|
256
275
|
const COM_CONTENT_ASSIGN_RE = /\.(?:Text|Content|Value|Range|Paragraphs|Sections|Documents|Document)\s*=/i;
|
|
257
276
|
const ALLOW_SEG_RE =
|
|
258
|
-
/(?:\$[A-Za-z_][\w$]*(?:(?:\.[\w$]+)|(?:\[[^\]]+\]))*\s*=)|(?:\$[\w$.]+\.(?:Content|Range|Text|Value|Paragraphs|Sections|Length|Count|Name|Selected)\b)|(?:\$[\w$.]+$)|(?:\b(?:Documents|Document)\.(?:Open|Item)\s*\([^)]*,\s*\d+\s*,\s*\$?true\s*\))/;
|
|
277
|
+
/(?:\$[A-Za-z_][\w$]*(?:(?:\.[\w$]+)|(?:\[[^\]]+\]))*\s*=)|(?:\$[\w$.]+\.(?:Content|Range|Text|Value|Paragraphs|Sections|Length|Count|Name|Selected|LinkType|Target|FullName|Extension|Directory|PSPath|Property|Attributes|Mode|LastWriteTime|Exists)\b)|(?:\$[\w$.]+$)|(?:\b(?:Documents|Document)\.(?:Open|Item)\s*\([^)]*,\s*\d+\s*,\s*\$?true\s*\))|(?:\(!?\$[\w$.]+(?:\.\w+)?\)|\([^()\r\n]*\$[\w$.]+\.[\w.]+\s*\))/;
|
|
278
|
+
// 0.5.12(F4):纯字符串/纯输出段(整段是引号包裹字符串或 $() 子表达式)——不是命令,放行。
|
|
279
|
+
// 保守边界:只匹配"整段即字符串"(^...$ 锚定),不让 node -e "..." / curl -o "..." 借道。
|
|
280
|
+
const PURE_STRING_SEG_RE = /^(?:"[^"\r\n]*"|'[^'\r\n]*'|`[^`\r\n]*`|\$\(\s*[^()\r\n]*\s*\))+$/;
|
|
259
281
|
// 剥离 try/catch/finally 控制块:块内语句与主语句同样接受只读判定(Try-* 不是 cmdlet)。
|
|
260
282
|
// 注意:不做全量 [{}] 替换——if/else 子表达式($(if(...){...}else{...}))会被切碎误判。
|
|
261
283
|
function stripControlBlocks(command) {
|
|
@@ -289,7 +311,10 @@ function splitCommandSegments(command) {
|
|
|
289
311
|
* ③ 其它不认识的段 → 保守判非只读(不放大白名单,宁紧勿松)。 */
|
|
290
312
|
export function isReadOnlyCommand(command) {
|
|
291
313
|
if (typeof command !== "string") return false;
|
|
292
|
-
|
|
314
|
+
// 0.5.12(F4):先做别名规范化(rm→Remove-Item、gc→Get-Content…),
|
|
315
|
+
// 保证 MUTATING/READONLY 词表只看标准 cmdlet(别名双向缺口:写漏 rm/del/iex、读漏 gc/gci)
|
|
316
|
+
const normalized = normalizeAliases(command);
|
|
317
|
+
const segments = splitCommandSegments(stripControlBlocks(normalized));
|
|
293
318
|
if (segments.length === 0) return false;
|
|
294
319
|
return segments.every((seg) => {
|
|
295
320
|
const s = String(seg).trim();
|
|
@@ -300,6 +325,7 @@ export function isReadOnlyCommand(command) {
|
|
|
300
325
|
if (COM_CONTENT_ASSIGN_RE.test(s)) return false; // $doc.Text=... 是写文档
|
|
301
326
|
if (READONLY_CMD_RE.test(s)) return true;
|
|
302
327
|
if (ALLOW_SEG_RE.test(s)) return true;
|
|
328
|
+
if (PURE_STRING_SEG_RE.test(s)) return true; // 0.5.12(F4):纯字符串/输出段(非命令)
|
|
303
329
|
return false; // 看不清的段:保守按非只读处理
|
|
304
330
|
});
|
|
305
331
|
}
|
|
@@ -362,6 +388,9 @@ export function isAnalysisOp(toolName, args) {
|
|
|
362
388
|
}
|
|
363
389
|
}
|
|
364
390
|
if (name === "node" || name === "pwsh" || name === "bash") {
|
|
391
|
+
// ㊄ 验证通道(2026-08-31,用户定稿):验证类命令(测试/体检/探针/审计/dry-run/只读查询)
|
|
392
|
+
// 独立放行——调研/验证灵活性与"执行类"严格授权分层;清单见 isVerificationCommand(执行类不匹配)。
|
|
393
|
+
if (isVerificationCommand(cmd)) return true;
|
|
365
394
|
// ③ 临时区写:写命令(不含删除/移动——删除/移动属 13A 红线,不走分析豁免)+ 提取路径全部命中临时区 + 无受保护名
|
|
366
395
|
if (isMutationCommand(cmd) && !/(?:Remove-Item|Move-Item|Rename-Item|del\s|rm\s|git\s+push|git\s+commit)/i.test(cmd)) {
|
|
367
396
|
const paths = extractAnalysisScratchPaths(cmd);
|
|
@@ -550,11 +579,21 @@ export function isAuditCommand(command) {
|
|
|
550
579
|
* v0.5.7 P0-1(用户拍板 2026-08-26):验证类命令判定——运行测试/冷加载探针/挂载审计/语法检查。
|
|
551
580
|
* 与"修/改/写"授权伴生放行使用:用户授权修改后,验证是变更的正常闭环,不再被
|
|
552
581
|
* write vs command 类型不匹配误拦(今天实弹连卡 4 次)。
|
|
582
|
+
* 2026-08-31(验证通道放行面,用户定稿):清单扩展 + 由 isAnalysisOp 独立调用——
|
|
583
|
+
* ① 单测通配(任意 *.test.mjs)、② 验证/门禁脚本(check-tool-coverage / publish-aptitude-check)、
|
|
584
|
+
* ③ --dry-run(发布脚本 dry-run=只读推导,发布链无 dry-run 不匹配)、④ gh api 只读 GET、npm whoami。
|
|
585
|
+
* 安全边界:执行类脚本(release-plugin 无 --dry-run / inject / uninject / install / publish / commit /
|
|
586
|
+
* push / Remove-Item 等)一律不匹配——执行类仍走授权语义严格版(0.5.11 防借道回归不放松)。
|
|
553
587
|
*/
|
|
554
588
|
export function isVerificationCommand(command) {
|
|
555
589
|
if (typeof command !== "string") return false;
|
|
556
|
-
if (/\b(?:node|npm|npx)\s+[^\n]*(?:run-all\.mjs|check-plugin-load\.mjs|audit-mount-consistency\.mjs|loader-smoke\.e2e\.mjs|verify-all\.mjs|health-audit\.mjs|--check|--test)\b/i.test(command)) return true;
|
|
590
|
+
if (/\b(?:node|npm|npx)\s+[^\n]*(?:run-all\.mjs|check-plugin-load\.mjs|audit-mount-consistency\.mjs|loader-smoke\.e2e\.mjs|verify-all\.mjs|health-audit\.mjs|check-tool-coverage\.mjs|publish-aptitude-check\.mjs|\.test\.mjs\b|--check|--test|--dry-run)\b/i.test(command)) return true;
|
|
557
591
|
if (/\b(?:npm|pnpm)\s+(?:test|run\s+test)\b/i.test(command)) return true;
|
|
592
|
+
if (/\bgh\s+api\b/i.test(command)) {
|
|
593
|
+
// 只读 GET 形态;写形态(--method/-X 显式方法、-F/--field 表单)不豁免(gg api 写=执行类)
|
|
594
|
+
return !/(?:--method|-X)\s+(?:POST|PUT|PATCH|DELETE)|\s-(?:F|f)\s|--field\s/i.test(command);
|
|
595
|
+
}
|
|
596
|
+
if (/\bnpm\s+whoami\b/i.test(command)) return true;
|
|
558
597
|
return false;
|
|
559
598
|
}
|
|
560
599
|
|