@sofagent/rules 1.3.8 → 1.3.9

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.
Files changed (45) hide show
  1. package/dist/approval-mode.js +1 -1
  2. package/dist/ast/engine.d.ts +52 -0
  3. package/dist/ast/engine.js +291 -0
  4. package/dist/ast/fixtures/vuln-db.json +25 -0
  5. package/dist/ast/index.d.ts +10 -0
  6. package/dist/ast/index.js +32 -0
  7. package/dist/ast/plugin-adapter.d.ts +35 -0
  8. package/dist/ast/plugin-adapter.js +71 -0
  9. package/dist/ast/rules/asi01-prompt-injection.d.ts +3 -0
  10. package/dist/ast/rules/asi01-prompt-injection.js +65 -0
  11. package/dist/ast/rules/asi04-sbom.d.ts +30 -0
  12. package/dist/ast/rules/asi04-sbom.js +158 -0
  13. package/dist/ast/rules/index.d.ts +6 -0
  14. package/dist/ast/rules/index.js +34 -0
  15. package/dist/ast/rules/no-child-process-shell.d.ts +3 -0
  16. package/dist/ast/rules/no-child-process-shell.js +57 -0
  17. package/dist/ast/rules/no-debugger.d.ts +3 -0
  18. package/dist/ast/rules/no-debugger.js +22 -0
  19. package/dist/ast/rules/no-dynamic-require.d.ts +3 -0
  20. package/dist/ast/rules/no-dynamic-require.js +34 -0
  21. package/dist/ast/rules/no-empty-catch.d.ts +3 -0
  22. package/dist/ast/rules/no-empty-catch.js +29 -0
  23. package/dist/ast/rules/no-eval.d.ts +3 -0
  24. package/dist/ast/rules/no-eval.js +33 -0
  25. package/dist/ast/rules/no-hardcoded-secret.d.ts +3 -0
  26. package/dist/ast/rules/no-hardcoded-secret.js +59 -0
  27. package/dist/ast/rules/no-insecure-url.d.ts +3 -0
  28. package/dist/ast/rules/no-insecure-url.js +32 -0
  29. package/dist/ast/rules/no-sql-string-concat.d.ts +3 -0
  30. package/dist/ast/rules/no-sql-string-concat.js +52 -0
  31. package/dist/ast/rules/semver.d.ts +11 -0
  32. package/dist/ast/rules/semver.js +58 -0
  33. package/dist/ast/types.d.ts +81 -0
  34. package/dist/ast/types.js +12 -0
  35. package/dist/ast/walk.d.ts +14 -0
  36. package/dist/ast/walk.js +69 -0
  37. package/dist/engine.js +1 -1
  38. package/dist/index.d.ts +6 -0
  39. package/dist/index.js +20 -6
  40. package/dist/rules/index.js +1 -1
  41. package/dist/rules/tool-injection.js +1 -1
  42. package/dist/rules/tool-secret-leak.js +2 -2
  43. package/dist/rules/tool-sensitive-file.js +1 -1
  44. package/dist/types.js +1 -1
  45. package/package.json +24 -5
@@ -6,7 +6,7 @@
6
6
  //(allow-all / deny-all / read-only / always-ask)。
7
7
  //
8
8
  // 四种模式行为:
9
- // allow-with-audit 全部放行 + 写审计日志(默认模式 = v1.3.8 行为,不破坏既有)
9
+ // allow-with-audit 全部放行 + 写审计日志(默认模式 = v1.3.9 行为,不破坏既有)
10
10
  // deny-all 全部拦截(调试/安全演练)
11
11
  // read-only 只读工具(permission='r')自动放行,读写需人工确认(Benchmark 评测)
12
12
  // always-ask 每次工具调用都问人(危险操作密集场景)
@@ -0,0 +1,52 @@
1
+ import type { AstFinding, AstScanInput } from './types';
2
+ export interface AstEngineOptions {
3
+ /** 只跑这些规则 ID(缺省跑全部内置规则) */
4
+ ruleIds?: string[];
5
+ /** TS 不可用时的降级行为:返回 WARN finding(默认) */
6
+ onTsUnavailable?: 'warn' | 'throw';
7
+ }
8
+ /**
9
+ * AST 规则引擎——把扫描内容落临时文件、驱动 TS7 server 解析、
10
+ * 逐规则遍历语法树产出 finding。
11
+ */
12
+ export declare class AstRuleEngine {
13
+ private readonly rules;
14
+ private readonly onTsUnavailable;
15
+ private tsApi;
16
+ private tsApiFailed;
17
+ private syntaxKind;
18
+ private tmpRoot;
19
+ /** extractExports 临时文件序号(每次调用唯一路径——TS7 snapshot 同路径缓存规避) */
20
+ private extractSeq;
21
+ constructor(options?: AstEngineOptions);
22
+ /** 支持的代码文件后缀(TS7 可解析) */
23
+ private static readonly CODE_EXTS;
24
+ /**
25
+ * 扫描一批文件——AST 规则走 TS 解析,文本规则直接消费内容。
26
+ * 返回 findings(可能为空;TS 不可用时按策略降级)。
27
+ */
28
+ scan(inputs: readonly AstScanInput[]): AstFinding[];
29
+ /** 释放资源(TS server 进程 + 临时目录) */
30
+ close(): void;
31
+ private getTsApi;
32
+ /** SyntaxKind 名字→数字(供规则判型;未加载成功时返回 -1 恒不匹配) */
33
+ kindOf(name: string): number;
34
+ /**
35
+ * 提取 TS 源文件的 export 符号清单(v1.3.9 四:API 语义解析复用)。
36
+ * 覆盖形态:export {} 块 / export 声明(const·function·class·type·interface)/
37
+ * export default / export *。
38
+ *
39
+ * @returns 符号名 + 语句所在行(1-based)——供 @public/@internal 分级标记对齐
40
+ */
41
+ extractExports(path: string, content: string): Array<{
42
+ name: string;
43
+ line: number;
44
+ }>;
45
+ private ensureTmpRoot;
46
+ /**
47
+ * 临时文件路径:序号前缀防同名冲突 + basename 消毒防路径穿越。
48
+ * 扩展名保留(TS7 靠后缀推断 scriptKind),未知后缀归一为 .ts。
49
+ */
50
+ private makeTmpPath;
51
+ }
52
+ //# sourceMappingURL=engine.d.ts.map
@@ -0,0 +1,291 @@
1
+ "use strict";
2
+ // ============================================================
3
+ // engine.ts · AST 规则引擎核心
4
+ // v1.3.9(一):官方 AST 规则引擎参考实现(sofagent-ruleset-ast)
5
+ //
6
+ // 依赖说明(TypeScript 7 原生端 API 变迁):
7
+ // TypeScript 7(Go 原生移植)移除了 5.x 的 createSourceFile 同步解析 API,
8
+ // 官方替代入口是 `typescript/unstable/sync` 的 API 类——
9
+ // new API() → updateSnapshot({openFiles}) → getDefaultProjectForFile → program.getSourceFile
10
+ // 服务端要求文件真实存在于磁盘,因此引擎把扫描内容写入临时目录再解析。
11
+ // 虚拟 FS(typescript/unstable/fs)对 openFiles 的项目解析不生效(实测),
12
+ // 临时文件是 TS7 下唯一稳定的内存内容解析通道。
13
+ //
14
+ // 安全约束:
15
+ // - 临时文件名 = 序号 + 消毒后的 basename——diff 路径可能含 ../ 穿越,必须消毒
16
+ // - typescript 以 peerDependency 声明,require 失败时引擎降级为 WARN(不硬崩)
17
+ // - 引擎单例持有 API 实例(server 进程复用),close() 显式回收
18
+ // ============================================================
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.AstRuleEngine = void 0;
21
+ const fs_1 = require("fs");
22
+ const os_1 = require("os");
23
+ const path_1 = require("path");
24
+ const rules_1 = require("./rules");
25
+ /** 从 typescript/unstable/sync 动态加载 API 构造器(失败返回 null 走降级) */
26
+ function loadTsApi() {
27
+ try {
28
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
29
+ const mod = require('typescript/unstable/sync');
30
+ return typeof mod.API === 'function' ? mod.API : null;
31
+ }
32
+ catch {
33
+ return null;
34
+ }
35
+ }
36
+ /** SyntaxKind 数字→名字 映射(tree-walk 判型用) */
37
+ function loadSyntaxKind() {
38
+ try {
39
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
40
+ const ast = require('typescript/unstable/ast');
41
+ return ast.SyntaxKind ?? {};
42
+ }
43
+ catch {
44
+ return {};
45
+ }
46
+ }
47
+ // ── 引擎本体 ──
48
+ /**
49
+ * AST 规则引擎——把扫描内容落临时文件、驱动 TS7 server 解析、
50
+ * 逐规则遍历语法树产出 finding。
51
+ */
52
+ class AstRuleEngine {
53
+ rules;
54
+ onTsUnavailable;
55
+ tsApi = null;
56
+ tsApiFailed = false;
57
+ syntaxKind = {};
58
+ tmpRoot = null;
59
+ /** extractExports 临时文件序号(每次调用唯一路径——TS7 snapshot 同路径缓存规避) */
60
+ extractSeq = 0;
61
+ constructor(options = {}) {
62
+ const all = rules_1.builtinAstRules;
63
+ const filter = options.ruleIds;
64
+ this.rules = filter ? all.filter((r) => filter.includes(r.id)) : all;
65
+ this.onTsUnavailable = options.onTsUnavailable ?? 'warn';
66
+ }
67
+ /** 支持的代码文件后缀(TS7 可解析) */
68
+ static CODE_EXTS = new Set([
69
+ '.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs',
70
+ ]);
71
+ /**
72
+ * 扫描一批文件——AST 规则走 TS 解析,文本规则直接消费内容。
73
+ * 返回 findings(可能为空;TS 不可用时按策略降级)。
74
+ */
75
+ scan(inputs) {
76
+ const findings = [];
77
+ const codeInputs = [];
78
+ // 第一步:分派——代码文件准备临时落盘,其余走文本规则
79
+ this.ensureTmpRoot();
80
+ inputs.forEach((input, idx) => {
81
+ const ext = (0, path_1.extname)(input.path).toLowerCase();
82
+ if (AstRuleEngine.CODE_EXTS.has(ext)) {
83
+ codeInputs.push({ input, tmpPath: this.makeTmpPath(idx, input.path) });
84
+ }
85
+ });
86
+ // 第二步:文本规则(不依赖 TS server,先跑)
87
+ for (const input of inputs) {
88
+ for (const rule of this.rules) {
89
+ if (!rule.checkText)
90
+ continue;
91
+ if (rule.filePattern && !rule.filePattern.test(input.path))
92
+ continue;
93
+ const hits = [];
94
+ rule.checkText({
95
+ path: input.path,
96
+ text: input.content,
97
+ report: (line, message) => hits.push({ ruleId: rule.id, file: input.path, line, message, severity: rule.severity }),
98
+ });
99
+ findings.push(...hits);
100
+ }
101
+ }
102
+ // 第三步:AST 规则(需要 TS server)
103
+ if (codeInputs.length > 0) {
104
+ const api = this.getTsApi();
105
+ if (!api) {
106
+ // TS 不可用——按策略降级(默认 WARN,让盲区可见而非静默跳过)
107
+ if (this.onTsUnavailable === 'throw') {
108
+ throw new Error('[ast-engine] typescript/unstable/sync 不可用——AST 规则无法执行。' +
109
+ '请安装 typescript >= 7.0.0(peerDependency)。');
110
+ }
111
+ for (const { input } of codeInputs) {
112
+ for (const rule of this.rules) {
113
+ if (!rule.checkCode)
114
+ continue;
115
+ findings.push({
116
+ ruleId: rule.id,
117
+ file: input.path,
118
+ line: 1,
119
+ message: 'typescript 不可用,AST 规则未执行(降级 WARN,消除盲区)',
120
+ severity: 'WARN',
121
+ });
122
+ }
123
+ }
124
+ return findings;
125
+ }
126
+ // 落盘 + 单次 snapshot 批量打开(server 进程复用)
127
+ for (const { input, tmpPath } of codeInputs) {
128
+ (0, fs_1.writeFileSync)(tmpPath, input.content, 'utf-8');
129
+ }
130
+ const snapshot = api.updateSnapshot({ openFiles: codeInputs.map((c) => c.tmpPath) });
131
+ for (const { input, tmpPath } of codeInputs) {
132
+ const project = snapshot.getDefaultProjectForFile(tmpPath);
133
+ const sf = project?.program.getSourceFile(tmpPath);
134
+ if (!sf)
135
+ continue;
136
+ for (const rule of this.rules) {
137
+ if (!rule.checkCode)
138
+ continue;
139
+ if (rule.filePattern && !rule.filePattern.test(input.path))
140
+ continue;
141
+ const hits = [];
142
+ rule.checkCode({
143
+ path: input.path,
144
+ sourceFile: sf,
145
+ text: sf.text,
146
+ kind: (name) => this.kindOf(name),
147
+ report: (node, message) => {
148
+ const { line } = sf.getLineAndCharacterOfPosition(node.getStart());
149
+ hits.push({
150
+ ruleId: rule.id,
151
+ file: input.path,
152
+ line: line + 1,
153
+ message,
154
+ severity: rule.severity,
155
+ });
156
+ },
157
+ reportLine: (line, message) => hits.push({ ruleId: rule.id, file: input.path, line, message, severity: rule.severity }),
158
+ });
159
+ findings.push(...hits);
160
+ }
161
+ }
162
+ }
163
+ return findings;
164
+ }
165
+ /** 释放资源(TS server 进程 + 临时目录) */
166
+ close() {
167
+ if (this.tsApi) {
168
+ try {
169
+ this.tsApi.close();
170
+ }
171
+ catch { /* server 已退出则忽略 */ }
172
+ this.tsApi = null;
173
+ }
174
+ if (this.tmpRoot) {
175
+ try {
176
+ (0, fs_1.rmSync)(this.tmpRoot, { recursive: true, force: true });
177
+ }
178
+ catch { /* 并发清理竞态可忽略 */ }
179
+ this.tmpRoot = null;
180
+ }
181
+ }
182
+ // ── 内部工具 ──
183
+ getTsApi() {
184
+ if (this.tsApi)
185
+ return this.tsApi;
186
+ if (this.tsApiFailed)
187
+ return null;
188
+ const Ctor = loadTsApi();
189
+ if (!Ctor) {
190
+ this.tsApiFailed = true;
191
+ return null;
192
+ }
193
+ this.syntaxKind = loadSyntaxKind();
194
+ this.tsApi = new Ctor();
195
+ return this.tsApi;
196
+ }
197
+ /** SyntaxKind 名字→数字(供规则判型;未加载成功时返回 -1 恒不匹配) */
198
+ kindOf(name) {
199
+ return this.syntaxKind[name] ?? -1;
200
+ }
201
+ /**
202
+ * 提取 TS 源文件的 export 符号清单(v1.3.9 四:API 语义解析复用)。
203
+ * 覆盖形态:export {} 块 / export 声明(const·function·class·type·interface)/
204
+ * export default / export *。
205
+ *
206
+ * @returns 符号名 + 语句所在行(1-based)——供 @public/@internal 分级标记对齐
207
+ */
208
+ extractExports(path, content) {
209
+ const out = [];
210
+ const api = this.getTsApi();
211
+ if (!api)
212
+ return out; // TS 不可用——调用方自行降级(公共 API 检查用正则兜底)
213
+ this.ensureTmpRoot();
214
+ // 🔴 每次调用用唯一临时路径:TS7 snapshot 对同路径已打开文件缓存首次内容,
215
+ // 复用路径会读到上一个调用者的内容(fileChanges 才能改内容——直接换路径最稳)
216
+ const seq = ++this.extractSeq;
217
+ const tmpPath = this.makeTmpPath(seq, `${seq}-${path}`);
218
+ (0, fs_1.writeFileSync)(tmpPath, content, 'utf-8');
219
+ const snapshot = api.updateSnapshot({ openFiles: [tmpPath] });
220
+ const project = snapshot.getDefaultProjectForFile(tmpPath);
221
+ const sf = project?.program.getSourceFile(tmpPath);
222
+ if (!sf)
223
+ return out;
224
+ const kindIs = (n, k) => !!n && n.kind === this.kindOf(k);
225
+ const lineOf = (n) => sf.getLineAndCharacterOfPosition(n.getStart()).line + 1;
226
+ for (const stmt of sf.statements ?? []) {
227
+ // 形态一:export { a, b } from './x' / export { a, b }
228
+ if (kindIs(stmt, 'ExportDeclaration')) {
229
+ const named = stmt.exportClause;
230
+ if (kindIs(named, 'NamedExports')) {
231
+ const elements = named.elements ?? [];
232
+ for (const el of elements) {
233
+ // ExportSpecifier:name 是对外导出名(importer 可见),propertyName 是本地名
234
+ // (export { local as exported } → 取 exported;无 as 时 name 即导出名)
235
+ const exported = el;
236
+ const name = exported.name?.text ?? exported.propertyName?.text;
237
+ if (name)
238
+ out.push({ name, line: lineOf(stmt) });
239
+ }
240
+ }
241
+ else {
242
+ out.push({ name: '*', line: lineOf(stmt) }); // export * from
243
+ }
244
+ continue;
245
+ }
246
+ // 形态二:export default
247
+ if (kindIs(stmt, 'ExportAssignment')) {
248
+ out.push({ name: 'default', line: lineOf(stmt) });
249
+ continue;
250
+ }
251
+ // 形态三:export const/let(VariableStatement 带 export 修饰符)
252
+ if (kindIs(stmt, 'VariableStatement')) {
253
+ const declList = stmt.declarationList;
254
+ const decls = declList?.declarations ?? [];
255
+ for (const d of decls) {
256
+ const name = d.name?.text;
257
+ if (name)
258
+ out.push({ name, line: lineOf(stmt) });
259
+ }
260
+ continue;
261
+ }
262
+ // 形态四:export function/class/type/interface/enum —— 有 name 的声明
263
+ const name = stmt.name?.text;
264
+ if (name && [
265
+ 'FunctionDeclaration', 'ClassDeclaration', 'TypeAliasDeclaration',
266
+ 'InterfaceDeclaration', 'EnumDeclaration', 'ModuleDeclaration',
267
+ ].some((k) => kindIs(stmt, k))) {
268
+ out.push({ name, line: lineOf(stmt) });
269
+ }
270
+ }
271
+ return out;
272
+ }
273
+ ensureTmpRoot() {
274
+ if (!this.tmpRoot) {
275
+ this.tmpRoot = (0, fs_1.mkdtempSync)((0, path_1.join)((0, os_1.tmpdir)(), 'sofagent-ast-'));
276
+ }
277
+ }
278
+ /**
279
+ * 临时文件路径:序号前缀防同名冲突 + basename 消毒防路径穿越。
280
+ * 扩展名保留(TS7 靠后缀推断 scriptKind),未知后缀归一为 .ts。
281
+ */
282
+ makeTmpPath(idx, originalPath) {
283
+ const safeBase = (0, path_1.basename)(originalPath).replace(/[^\w.-]/g, '_') || 'file.ts';
284
+ const ext = (0, path_1.extname)(safeBase).toLowerCase();
285
+ const normalized = AstRuleEngine.CODE_EXTS.has(ext) ? safeBase : `${safeBase}.ts`;
286
+ (0, fs_1.mkdirSync)(this.tmpRoot, { recursive: true });
287
+ return (0, path_1.join)(this.tmpRoot, `${idx}-${normalized}`);
288
+ }
289
+ }
290
+ exports.AstRuleEngine = AstRuleEngine;
291
+ //# sourceMappingURL=engine.js.map
@@ -0,0 +1,25 @@
1
+ {
2
+ "_comment": "离线样例漏洞清单(fixture)——OWASP ASI04 供应链 SBOM 检测的数据源。仅含少量公开 CVE 样例用于规则验证,不是漏洞库全量数据;CI 不联网拉取外部漏洞数据库(发版 prompt 明确约束)。ranges 为空格分隔的 AND 条件,每条形如 <x / <=x / >x / >=x / =x。",
3
+ "npm": {
4
+ "lodash": [
5
+ { "ranges": ["<4.17.21"], "id": "CVE-2021-23337", "summary": "命令注入(模板编译)" }
6
+ ],
7
+ "minimist": [
8
+ { "ranges": ["<1.2.6"], "id": "CVE-2021-44906", "summary": "原型污染" }
9
+ ],
10
+ "node-fetch": [
11
+ { "ranges": ["<2.6.7", ">=3.0.0 <3.2.10"], "id": "CVE-2022-0235", "summary": "凭据泄漏(重定向跟随)" }
12
+ ],
13
+ "axios": [
14
+ { "ranges": [">=1.3.0 <1.6.0"], "id": "CVE-2023-45857", "summary": "SSRF / 凭据泄漏" }
15
+ ],
16
+ "ws": [
17
+ { "ranges": [">=7.0.0 <7.4.6"], "id": "CVE-2021-32640", "summary": "DoS(畸形 HTTP 头)" }
18
+ ]
19
+ },
20
+ "go": {
21
+ "github.com/gorilla/websocket": [
22
+ { "ranges": ["<1.4.1"], "id": "GO-2020-0035", "summary": "DoS(超大帧)" }
23
+ ]
24
+ }
25
+ }
@@ -0,0 +1,10 @@
1
+ export { AstRuleEngine } from './engine';
2
+ export type { AstEngineOptions } from './engine';
3
+ export { walk, is, nodeText, collectImports } from './walk';
4
+ export { builtinAstRules, astRuleById } from './rules';
5
+ export { buildSbom, parsePackageJson, parseGoMod } from './rules/asi04-sbom';
6
+ export type { SbomEntry } from './rules/asi04-sbom';
7
+ export { inRange, compareVersions, parseVersion } from './rules/semver';
8
+ export { run as runAstPlugin, default } from './plugin-adapter';
9
+ export type { AstRule, AstFinding, AstScanInput, AstSeverity, AstRuleContext, AstTextRuleContext, AstNodeHost, } from './types';
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ // ============================================================
3
+ // ast/index.ts · AST 规则引擎 barrel export
4
+ // v1.3.9(一):官方 AST 规则引擎参考实现(sofagent-ruleset-ast)
5
+ // ============================================================
6
+ var __importDefault = (this && this.__importDefault) || function (mod) {
7
+ return (mod && mod.__esModule) ? mod : { "default": mod };
8
+ };
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.default = exports.runAstPlugin = exports.parseVersion = exports.compareVersions = exports.inRange = exports.parseGoMod = exports.parsePackageJson = exports.buildSbom = exports.astRuleById = exports.builtinAstRules = exports.collectImports = exports.nodeText = exports.is = exports.walk = exports.AstRuleEngine = void 0;
11
+ var engine_1 = require("./engine");
12
+ Object.defineProperty(exports, "AstRuleEngine", { enumerable: true, get: function () { return engine_1.AstRuleEngine; } });
13
+ var walk_1 = require("./walk");
14
+ Object.defineProperty(exports, "walk", { enumerable: true, get: function () { return walk_1.walk; } });
15
+ Object.defineProperty(exports, "is", { enumerable: true, get: function () { return walk_1.is; } });
16
+ Object.defineProperty(exports, "nodeText", { enumerable: true, get: function () { return walk_1.nodeText; } });
17
+ Object.defineProperty(exports, "collectImports", { enumerable: true, get: function () { return walk_1.collectImports; } });
18
+ var rules_1 = require("./rules");
19
+ Object.defineProperty(exports, "builtinAstRules", { enumerable: true, get: function () { return rules_1.builtinAstRules; } });
20
+ Object.defineProperty(exports, "astRuleById", { enumerable: true, get: function () { return rules_1.astRuleById; } });
21
+ var asi04_sbom_1 = require("./rules/asi04-sbom");
22
+ Object.defineProperty(exports, "buildSbom", { enumerable: true, get: function () { return asi04_sbom_1.buildSbom; } });
23
+ Object.defineProperty(exports, "parsePackageJson", { enumerable: true, get: function () { return asi04_sbom_1.parsePackageJson; } });
24
+ Object.defineProperty(exports, "parseGoMod", { enumerable: true, get: function () { return asi04_sbom_1.parseGoMod; } });
25
+ var semver_1 = require("./rules/semver");
26
+ Object.defineProperty(exports, "inRange", { enumerable: true, get: function () { return semver_1.inRange; } });
27
+ Object.defineProperty(exports, "compareVersions", { enumerable: true, get: function () { return semver_1.compareVersions; } });
28
+ Object.defineProperty(exports, "parseVersion", { enumerable: true, get: function () { return semver_1.parseVersion; } });
29
+ var plugin_adapter_1 = require("./plugin-adapter");
30
+ Object.defineProperty(exports, "runAstPlugin", { enumerable: true, get: function () { return plugin_adapter_1.run; } });
31
+ Object.defineProperty(exports, "default", { enumerable: true, get: function () { return __importDefault(plugin_adapter_1).default; } });
32
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,35 @@
1
+ /** 插件上下文(与 @sofagent/audit 的 PluginContext 结构对齐,鸭子类型避免包间硬依赖) */
2
+ interface PluginContextShape {
3
+ diffFiles: Array<{
4
+ path: string;
5
+ status?: string;
6
+ lines?: string[];
7
+ /** v1.3.9 十二:超大 diff spill 落盘后的取回定位符 */
8
+ spillFile?: string;
9
+ }>;
10
+ options?: {
11
+ /** 仓库根目录——提供时优先读磁盘上的完整文件(行号精确),否则用 diff 重建 */
12
+ cwd?: string;
13
+ /** 只跑这些规则 ID */
14
+ ruleIds?: string[];
15
+ };
16
+ }
17
+ /** 插件返回的单条检测结果(与 PluginResult 对齐) */
18
+ interface PluginResultShape {
19
+ file: string;
20
+ line?: number;
21
+ message: string;
22
+ }
23
+ /**
24
+ * 插件主入口(plugin-runner 经 require('@sofagent/rules/ast') 加载)。
25
+ * 引擎实例每次调用重建——审计是低频动作,server 启动开销可接受,
26
+ * 换取无状态与进程卫生。
27
+ */
28
+ declare function run(ctx: PluginContextShape): PluginResultShape[];
29
+ export { run };
30
+ /** CommonJS 兼容导出:plugin-runner 认 function 或 { run } 两种形态 */
31
+ declare const _default: {
32
+ run: typeof run;
33
+ };
34
+ export default _default;
35
+ //# sourceMappingURL=plugin-adapter.d.ts.map
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ // ============================================================
3
+ // plugin-adapter.ts · v1.2.9 插件协议适配器
4
+ // v1.3.9(一):官方 AST 引擎以 `sofagent-ruleset-ast` 形态接入——
5
+ // 规则集 JSON 声明 type:plugin + plugin:"@sofagent/rules/ast",
6
+ // 与 git-diff pattern 规则同管线(ruleset-loader → plugin-runner)
7
+ //
8
+ // 插件协议(与 plugin-runner.ts 对齐):
9
+ // ctx = { diffFiles, options } → PluginResult[]
10
+ // PluginResult = { file, line?, message }
11
+ // ============================================================
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.run = run;
14
+ const fs_1 = require("fs");
15
+ const path_1 = require("path");
16
+ const engine_1 = require("./engine");
17
+ /**
18
+ * 从 diff 行重建「新增内容」(+ 开头行,去掉 diff 头 +++ 行)。
19
+ * 行号是新增行序——磁盘文件缺失时的近似定位。
20
+ */
21
+ function reconstructFromDiff(lines) {
22
+ return lines
23
+ .filter((l) => l.startsWith('+') && !l.startsWith('+++'))
24
+ .map((l) => l.slice(1))
25
+ .join('\n');
26
+ }
27
+ /**
28
+ * 插件主入口(plugin-runner 经 require('@sofagent/rules/ast') 加载)。
29
+ * 引擎实例每次调用重建——审计是低频动作,server 启动开销可接受,
30
+ * 换取无状态与进程卫生。
31
+ */
32
+ function run(ctx) {
33
+ const options = ctx.options ?? {};
34
+ const engine = new engine_1.AstRuleEngine({ ruleIds: options.ruleIds });
35
+ try {
36
+ const inputs = [];
37
+ for (const f of ctx.diffFiles) {
38
+ if (f.status === 'deleted')
39
+ continue; // 删除的文件没有「新增内容」可审
40
+ // 优先读磁盘完整文件(行号精确);读不到再走 diff 重建(近似行号)
41
+ let content = null;
42
+ if (options.cwd) {
43
+ try {
44
+ content = (0, fs_1.readFileSync)((0, path_1.join)(options.cwd, f.path), 'utf-8');
45
+ }
46
+ catch {
47
+ content = null; // 文件不在工作树(如审计历史 commit)——走重建
48
+ }
49
+ }
50
+ if (content === null) {
51
+ content = reconstructFromDiff(f.lines ?? []);
52
+ }
53
+ if (content.trim().length === 0)
54
+ continue;
55
+ inputs.push({ path: f.path, content });
56
+ }
57
+ if (inputs.length === 0)
58
+ return [];
59
+ return engine.scan(inputs).map((f) => ({
60
+ file: f.file,
61
+ line: f.line,
62
+ message: `[${f.ruleId}] ${f.message}`,
63
+ }));
64
+ }
65
+ finally {
66
+ engine.close();
67
+ }
68
+ }
69
+ /** CommonJS 兼容导出:plugin-runner 认 function 或 { run } 两种形态 */
70
+ exports.default = { run };
71
+ //# sourceMappingURL=plugin-adapter.js.map
@@ -0,0 +1,3 @@
1
+ import type { AstRule } from '../types';
2
+ export declare const asi01PromptInjectionRule: AstRule;
3
+ //# sourceMappingURL=asi01-prompt-injection.d.ts.map
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ // ============================================================
3
+ // asi01-prompt-injection.ts · OWASP ASI01 目标劫持检测
4
+ // v1.3.9(一):扫描 SKILL.md / fde.md 等 system prompt 类文件中的
5
+ // 对抗性注入模式——「忽略上述指令」类指令覆盖(Microsoft AGT 启发)
6
+ //
7
+ // 边界说明:markdown 没有 TS AST,语义级检测落在
8
+ // 「指令覆盖模式 + 角色劫持模式 + 结构伪装模式」三类文本特征上
9
+ // ============================================================
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.asi01PromptInjectionRule = void 0;
12
+ /** 注入模式三类(命中任一即报)——模式统一容忍空白(\s*),配合 normalizeLine 归一化 */
13
+ const INJECTION_PATTERNS = [
14
+ // 一、指令覆盖类:「忽略上述指令」的多种语言形态
15
+ // v1.3.9 阶段四修复(fresh-eyes 视角7):模式加 \s* 容忍注入空白——「忽略 上述 指令」「忽略
16
+ // 上述 指令」等空白折叠变体不再绕过(归一化见 normalizeLine)。
17
+ { re: /忽略\s*(以上|上述|之前|前面|先前|上面)\s*(的)?\s*(所有|全部)?\s*(指令|规则|要求|约束|设定)/, label: '指令覆盖' },
18
+ { re: /无视\s*(以上|上述|之前|前面)\s*(的)?\s*(所有|全部)?\s*(指令|规则|要求|约束)/, label: '指令覆盖' },
19
+ { re: /(?<![\w])(ignore|disregard|forget|override)\s+(?:(?:all|any|the|previous|prior|above|earlier|preceding|system)\s+)*(?:instructions?|rules?|constraints?|prompts?|directions?|guardrails?)/i, label: '指令覆盖' },
20
+ { re: /(?<![\w])disregard\s+(all|any|the)\s+(safety|security|content)/i, label: '安全约束覆盖' },
21
+ // 二、角色劫持类:强制改写 agent 身份
22
+ { re: /(你现在\s*是|从现在开始\s*你是|你不再\s*是你|你的新\s*(身份|角色|任务)\s*是)/, label: '角色劫持' },
23
+ { re: /(?<![\w])you\s+are\s+now\s+(a|an|the)\s+(?!silicon|machine)/i, label: '角色劫持' },
24
+ { re: /(?<![\w])(pretend|act)\s+(to\s+be|as\s+if\s+you\s+(are|were))\s+(a|an|the)/i, label: '角色劫持' },
25
+ // 三、结构伪装类:伪造系统消息边界(分隔符逃逸)
26
+ { re: /<\/?(system|assistant|instruction|工具|系统)\s*(>|消息|提示)/i, label: '结构伪装' },
27
+ { re: /###\s*(system\s*prompt|系统提示词|真实指令)/i, label: '结构伪装' },
28
+ ];
29
+ /**
30
+ * 归一化行——对抗编码变体绕过(v1.3.9 阶段四 fresh-eyes 视角7 修复):
31
+ * ① 剥离零宽字符(ZWSP U+200B / ZWNJ U+200C / ZWJ U+200D / WJ U+2060 / BOM U+FEFF)
32
+ * ——攻击者用零宽字符插入「忽略[ZWSP]上述指令」即绕过原正则;
33
+ * ② 全角空格(U+3000)→ 半角;
34
+ * ③ 折叠连续空白(多个空格/Tab → 单个空格)。
35
+ * 注:同形字替换(西里尔 а vs 拉丁 a)超出纯正则能力,留 L3 语义检测(与 LIMITATIONS A9 口径一致)。
36
+ */
37
+ function normalizeLine(raw) {
38
+ return raw
39
+ .replace(/[\u200B\u200C\u200D\u2060\uFEFF]/g, '')
40
+ .replace(/\u3000/g, ' ')
41
+ .replace(/[ \t]+/g, ' ')
42
+ .trim();
43
+ }
44
+ /** ASI01 适用文件:system prompt 载体(SKILL.md / fde.md / role-*.md) */
45
+ const PROMPT_FILE = /(SKILL\.md|fde\.md|role-[^/]+\.md|system[-_]?prompt)/i;
46
+ exports.asi01PromptInjectionRule = {
47
+ id: 'asi01-prompt-injection',
48
+ name: 'OWASP ASI01 目标劫持检测',
49
+ severity: 'FAIL',
50
+ description: '扫描 system prompt 类文件中的对抗性注入模式(指令覆盖/角色劫持/结构伪装,含编码变体归一化)',
51
+ filePattern: PROMPT_FILE,
52
+ checkText(ctx) {
53
+ const lines = ctx.text.split('\n');
54
+ lines.forEach((line, idx) => {
55
+ const normalized = normalizeLine(line);
56
+ for (const { re, label } of INJECTION_PATTERNS) {
57
+ if (re.test(normalized)) {
58
+ ctx.report(idx + 1, `[ASI01·${label}] 检测到疑似 prompt 注入:${line.trim().slice(0, 80)}`);
59
+ break; // 每行只报一次,避免同一行多模式重复计数
60
+ }
61
+ }
62
+ });
63
+ },
64
+ };
65
+ //# sourceMappingURL=asi01-prompt-injection.js.map
@@ -0,0 +1,30 @@
1
+ import type { AstRule } from '../types';
2
+ /** SBOM 单条依赖 */
3
+ export interface SbomEntry {
4
+ /** 依赖名(npm 包名 / go module path) */
5
+ name: string;
6
+ /** 版本(清单里写的原样版本,含 ^/~ 前缀会被剥离) */
7
+ version: string;
8
+ /** 生态:npm / go */
9
+ ecosystem: 'npm' | 'go';
10
+ /** 来源行号(1-based) */
11
+ line: number;
12
+ }
13
+ /**
14
+ * 解析 package-lock.json → SBOM 条目(精确锁定版本——v2/v3 的 packages 对象;v1 兜底 dependencies 嵌套)。
15
+ * v1.3.9 阶段四修复(fresh-eyes 视角7):ASI04 此前只扫 manifest 的 range(^4.17.20),
16
+ * range 宽则误报、窄则漏报(注释自承「精确锁定版本在 lock 文件里」)。本函数用 lockfile
17
+ * 的精确版本做漏洞匹配——消除假阳/假阴;manifest 仅作无 lockfile 时的 fallback。
18
+ */
19
+ export declare function parsePackageLock(text: string): SbomEntry[];
20
+ /**
21
+ * 解析 package.json → SBOM 条目。
22
+ * 版本声明形如 "^4.17.20"——无 lockfile 时剥离前缀近似(保守:命中漏洞标「range 不确定」)。
23
+ */
24
+ export declare function parsePackageJson(text: string): SbomEntry[];
25
+ /** 解析 go.mod → SBOM 条目(require 行 + require 块两种形态) */
26
+ export declare function parseGoMod(text: string): SbomEntry[];
27
+ /** 生成 SBOM(按清单类型分派)——lockfile 优先(精确),manifest 兜底(range) */
28
+ export declare function buildSbom(path: string, text: string): SbomEntry[];
29
+ export declare const asi04SbomRule: AstRule;
30
+ //# sourceMappingURL=asi04-sbom.d.ts.map