@naeemo/capnp 0.9.0 → 0.9.1

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.
@@ -0,0 +1,380 @@
1
+ #!/usr/bin/env node
2
+ import { a as decodePointer, i as PointerTag, n as WORD_SIZE, r as ElementSize, t as Segment } from "./segment-yid_PYS5.js";
3
+ import { readFileSync } from "node:fs";
4
+
5
+ //#region src/cli-audit.ts
6
+ /** 默认安全选项 */
7
+ const DEFAULT_AUDIT_OPTIONS = {
8
+ maxSegments: 64,
9
+ maxTotalSize: 64 * 1024 * 1024,
10
+ strictMode: false
11
+ };
12
+ /**
13
+ * Cap'n Proto 消息审计读取器
14
+ * 用于安全审计消息文件
15
+ */
16
+ var AuditReader = class {
17
+ segments;
18
+ options;
19
+ issues = [];
20
+ visitedPointers = /* @__PURE__ */ new Set();
21
+ pointersScanned = 0;
22
+ maxNestingDepth = 0;
23
+ constructor(buffer, options = {}) {
24
+ this.options = {
25
+ ...DEFAULT_AUDIT_OPTIONS,
26
+ ...options
27
+ };
28
+ const uint8Array = buffer instanceof ArrayBuffer ? new Uint8Array(buffer) : buffer;
29
+ this.segments = [];
30
+ if (uint8Array.byteLength < 8) {
31
+ this.addIssue("error", "invalid_header", "消息太小,无法包含有效的 Cap'n Proto 头部", "header", "消息至少需要8字节的头部");
32
+ return;
33
+ }
34
+ if (uint8Array.byteLength > this.options.maxTotalSize) {
35
+ this.addIssue("error", "size_exceeded", `消息大小(${uint8Array.byteLength}字节)超过最大限制(${this.options.maxTotalSize}字节)`, "header", "减小消息大小或增加 --max-size 限制");
36
+ if (this.options.strictMode) return;
37
+ }
38
+ const view = new DataView(uint8Array.buffer, uint8Array.byteOffset, uint8Array.byteLength);
39
+ const firstWordLow = view.getUint32(0, true);
40
+ const firstWordHigh = view.getUint32(4, true);
41
+ const segmentCount = (firstWordLow & 4294967295) + 1;
42
+ const firstSegmentSize = firstWordHigh;
43
+ if (segmentCount > this.options.maxSegments) {
44
+ this.addIssue("error", "segment_count_exceeded", `段数(${segmentCount})超过最大限制(${this.options.maxSegments})`, "header", "减少段数或增加 --max-segments 限制");
45
+ if (this.options.strictMode) return;
46
+ }
47
+ let offset = 8;
48
+ const segmentSizes = [firstSegmentSize];
49
+ for (let i = 1; i < segmentCount; i++) {
50
+ if (offset + 4 > uint8Array.byteLength) {
51
+ this.addIssue("error", "truncated_header", "段表在段大小信息之前结束", `header.segment[${i}]`, "消息文件可能已损坏");
52
+ return;
53
+ }
54
+ segmentSizes.push(view.getUint32(offset, true));
55
+ offset += 4;
56
+ }
57
+ offset = offset + 7 & -8;
58
+ if (offset > uint8Array.byteLength) {
59
+ this.addIssue("error", "truncated_header", "段表头部不完整", "header", "消息文件可能已损坏");
60
+ return;
61
+ }
62
+ let totalWords = 0;
63
+ for (let i = 0; i < segmentSizes.length; i++) {
64
+ const size = segmentSizes[i];
65
+ totalWords += size;
66
+ if (offset + size * WORD_SIZE > uint8Array.byteLength) {
67
+ this.addIssue("warning", "truncated_segment", `段${i}数据不足,声明大小:${size}字,实际可用:${Math.floor((uint8Array.byteLength - offset) / WORD_SIZE)}字`, `segment[${i}]`, "消息文件可能已损坏或被截断");
68
+ break;
69
+ }
70
+ const segmentBuffer = uint8Array.slice(offset, offset + size * WORD_SIZE);
71
+ this.segments.push(Segment.fromBuffer(segmentBuffer.buffer));
72
+ offset += size * WORD_SIZE;
73
+ }
74
+ if (totalWords > this.options.maxTotalSize / WORD_SIZE) this.addIssue("warning", "large_message", `消息总字数(${totalWords})较大,可能影响性能`, "message", "考虑分割大型消息");
75
+ this.scanAllPointers();
76
+ }
77
+ /**
78
+ * 添加审计问题
79
+ */
80
+ addIssue(type, category, message, location, suggestion) {
81
+ this.issues.push({
82
+ type,
83
+ category,
84
+ message,
85
+ location,
86
+ suggestion
87
+ });
88
+ }
89
+ /**
90
+ * 获取段
91
+ */
92
+ getSegment(index) {
93
+ return this.segments[index];
94
+ }
95
+ /**
96
+ * 扫描所有指针
97
+ */
98
+ scanAllPointers() {
99
+ if (this.segments.length === 0) return;
100
+ for (let segIdx = 0; segIdx < this.segments.length; segIdx++) {
101
+ const segment = this.segments[segIdx];
102
+ const wordCount = segment.wordCount;
103
+ for (let wordIdx = 0; wordIdx < wordCount; wordIdx++) {
104
+ const ptrValue = segment.getWord(wordIdx);
105
+ if (ptrValue === 0n) continue;
106
+ this.scanPointer(segIdx, wordIdx, ptrValue, 0);
107
+ }
108
+ }
109
+ }
110
+ /**
111
+ * 扫描单个指针
112
+ */
113
+ scanPointer(segmentIndex, wordOffset, ptrValue, depth) {
114
+ this.pointersScanned++;
115
+ this.maxNestingDepth = Math.max(this.maxNestingDepth, depth);
116
+ const ptrKey = `${segmentIndex}:${wordOffset}`;
117
+ if (this.visitedPointers.has(ptrKey)) {
118
+ this.addIssue("warning", "circular_reference", `检测到可能的循环引用 at segment[${segmentIndex}].word[${wordOffset}]`, `segment[${segmentIndex}].word[${wordOffset}]`, "检查消息结构是否存在循环");
119
+ return;
120
+ }
121
+ this.visitedPointers.add(ptrKey);
122
+ if (depth > 100) {
123
+ this.addIssue("error", "nesting_too_deep", "指针嵌套深度超过100,可能存在恶意构造的消息", `segment[${segmentIndex}].word[${wordOffset}]`, "检查消息是否被恶意构造");
124
+ return;
125
+ }
126
+ const ptr = decodePointer(ptrValue);
127
+ const segment = this.getSegment(segmentIndex);
128
+ if (!segment) return;
129
+ switch (ptr.tag) {
130
+ case PointerTag.STRUCT: {
131
+ const structPtr = ptr;
132
+ const targetOffset = wordOffset + 1 + structPtr.offset;
133
+ if (targetOffset < 0 || targetOffset + structPtr.dataWords + structPtr.pointerCount > segment.wordCount) this.addIssue("error", "out_of_bounds", `Struct指针目标超出段范围: offset=${structPtr.offset}, dataWords=${structPtr.dataWords}, pointerCount=${structPtr.pointerCount}`, `segment[${segmentIndex}].word[${wordOffset}]`, "消息可能已损坏或被篡改");
134
+ const pointerStart = targetOffset + structPtr.dataWords;
135
+ for (let i = 0; i < structPtr.pointerCount; i++) {
136
+ const ptrIdx = pointerStart + i;
137
+ if (ptrIdx < segment.wordCount) {
138
+ const nestedPtr = segment.getWord(ptrIdx);
139
+ if (nestedPtr !== 0n) this.scanPointer(segmentIndex, ptrIdx, nestedPtr, depth + 1);
140
+ }
141
+ }
142
+ break;
143
+ }
144
+ case PointerTag.LIST: {
145
+ const listPtr = ptr;
146
+ if (listPtr.elementSize === ElementSize.COMPOSITE) {
147
+ const tagOffset = wordOffset + 1 + listPtr.offset;
148
+ if (tagOffset >= 0 && tagOffset < segment.wordCount) {
149
+ const tagWord = segment.getWord(tagOffset);
150
+ const elementCount = Number(tagWord & BigInt(4294967295));
151
+ const dataWords = Number(tagWord >> BigInt(32) & BigInt(65535));
152
+ const pointerCount = Number(tagWord >> BigInt(48) & BigInt(65535));
153
+ if (elementCount > 1e6) this.addIssue("warning", "large_list", `复合列表元素数量异常: ${elementCount}`, `segment[${segmentIndex}].word[${wordOffset}]`, "检查列表大小是否合理");
154
+ const elementSize = dataWords + pointerCount;
155
+ const dataStart = tagOffset + 1;
156
+ for (let i = 0; i < elementCount; i++) for (let j = 0; j < pointerCount; j++) {
157
+ const ptrIdx = dataStart + i * elementSize + dataWords + j;
158
+ if (ptrIdx < segment.wordCount) {
159
+ const nestedPtr = segment.getWord(ptrIdx);
160
+ if (nestedPtr !== 0n) this.scanPointer(segmentIndex, ptrIdx, nestedPtr, depth + 1);
161
+ }
162
+ }
163
+ }
164
+ } else if (listPtr.elementSize === ElementSize.POINTER) {
165
+ const targetOffset = wordOffset + 1 + listPtr.offset;
166
+ for (let i = 0; i < listPtr.elementCount; i++) {
167
+ const ptrIdx = targetOffset + i;
168
+ if (ptrIdx < segment.wordCount) {
169
+ const nestedPtr = segment.getWord(ptrIdx);
170
+ if (nestedPtr !== 0n) this.scanPointer(segmentIndex, ptrIdx, nestedPtr, depth + 1);
171
+ }
172
+ }
173
+ }
174
+ break;
175
+ }
176
+ case PointerTag.FAR: {
177
+ const farPtr = ptr;
178
+ if (farPtr.targetSegment >= this.segments.length) this.addIssue("error", "invalid_far_pointer", `Far指针引用不存在的段: ${farPtr.targetSegment}`, `segment[${segmentIndex}].word[${wordOffset}]`, "消息可能已损坏");
179
+ else {
180
+ const targetSegment = this.getSegment(farPtr.targetSegment);
181
+ if (targetSegment && farPtr.targetOffset >= targetSegment.wordCount) this.addIssue("error", "invalid_far_pointer", `Far指针目标偏移超出范围: segment=${farPtr.targetSegment}, offset=${farPtr.targetOffset}`, `segment[${segmentIndex}].word[${wordOffset}]`, "消息可能已损坏");
182
+ if (farPtr.doubleFar) {
183
+ if (targetSegment && farPtr.targetOffset < targetSegment.wordCount) {
184
+ const landingPadPtr = targetSegment.getWord(farPtr.targetOffset);
185
+ if (landingPadPtr !== 0n) this.scanPointer(farPtr.targetSegment, farPtr.targetOffset, landingPadPtr, depth + 1);
186
+ }
187
+ }
188
+ }
189
+ break;
190
+ }
191
+ case PointerTag.OTHER:
192
+ this.addIssue("warning", "capability_in_message", "序列化消息中发现 Capability 或其他特殊指针", `segment[${segmentIndex}].word[${wordOffset}]`, "Capability 通常不应在序列化消息中");
193
+ break;
194
+ }
195
+ }
196
+ /**
197
+ * 生成审计报告
198
+ */
199
+ generateReport(filePath, fileSize) {
200
+ const errors = this.issues.filter((i) => i.type === "error").length;
201
+ const warnings = this.issues.filter((i) => i.type === "warning").length;
202
+ const infos = this.issues.filter((i) => i.type === "info").length;
203
+ return {
204
+ filePath,
205
+ fileSize,
206
+ passed: errors === 0,
207
+ segmentCount: this.segments.length,
208
+ totalWords: this.segments.reduce((sum, s) => sum + s.wordCount, 0),
209
+ issues: this.issues,
210
+ summary: {
211
+ error: errors,
212
+ warning: warnings,
213
+ info: infos
214
+ },
215
+ pointersScanned: this.pointersScanned,
216
+ maxNestingDepth: this.maxNestingDepth
217
+ };
218
+ }
219
+ };
220
+ /**
221
+ * 格式化审计报告为可读文本
222
+ */
223
+ function formatAuditReport(report) {
224
+ const lines = [];
225
+ lines.push("=".repeat(60));
226
+ lines.push("CAP'N PROTO SECURITY AUDIT REPORT");
227
+ lines.push("=".repeat(60));
228
+ lines.push("");
229
+ lines.push("File Information:");
230
+ lines.push(` Path: ${report.filePath}`);
231
+ lines.push(` Size: ${report.fileSize.toLocaleString()} bytes`);
232
+ lines.push(` Segments: ${report.segmentCount}`);
233
+ lines.push(` Total Words: ${report.totalWords.toLocaleString()}`);
234
+ lines.push(` Pointers Scanned: ${report.pointersScanned.toLocaleString()}`);
235
+ lines.push(` Max Nesting Depth: ${report.maxNestingDepth}`);
236
+ lines.push("");
237
+ if (report.passed) lines.push("✅ AUDIT PASSED");
238
+ else lines.push("❌ AUDIT FAILED");
239
+ lines.push("");
240
+ lines.push("Summary:");
241
+ lines.push(` Errors: ${report.summary.error}`);
242
+ lines.push(` Warnings: ${report.summary.warning}`);
243
+ lines.push(` Info: ${report.summary.info}`);
244
+ lines.push("");
245
+ if (report.issues.length > 0) {
246
+ lines.push("Issues:");
247
+ lines.push("-".repeat(60));
248
+ const errors = report.issues.filter((i) => i.type === "error");
249
+ const warnings = report.issues.filter((i) => i.type === "warning");
250
+ const infos = report.issues.filter((i) => i.type === "info");
251
+ if (errors.length > 0) {
252
+ lines.push("");
253
+ lines.push("❌ ERRORS:");
254
+ for (const issue of errors) {
255
+ lines.push(` [${issue.category}] ${issue.message}`);
256
+ lines.push(` Location: ${issue.location}`);
257
+ if (issue.suggestion) lines.push(` 💡 ${issue.suggestion}`);
258
+ lines.push("");
259
+ }
260
+ }
261
+ if (warnings.length > 0) {
262
+ lines.push("");
263
+ lines.push("⚠️ WARNINGS:");
264
+ for (const issue of warnings) {
265
+ lines.push(` [${issue.category}] ${issue.message}`);
266
+ lines.push(` Location: ${issue.location}`);
267
+ if (issue.suggestion) lines.push(` 💡 ${issue.suggestion}`);
268
+ lines.push("");
269
+ }
270
+ }
271
+ if (infos.length > 0) {
272
+ lines.push("");
273
+ lines.push("ℹ️ INFO:");
274
+ for (const issue of infos) {
275
+ lines.push(` [${issue.category}] ${issue.message}`);
276
+ lines.push(` Location: ${issue.location}`);
277
+ lines.push("");
278
+ }
279
+ }
280
+ } else lines.push("No issues found.");
281
+ lines.push("");
282
+ lines.push("=".repeat(60));
283
+ return lines.join("\n");
284
+ }
285
+ /**
286
+ * 审计消息文件
287
+ */
288
+ function auditMessageFile(filePath, options = {}) {
289
+ const buffer = readFileSync(filePath);
290
+ return new AuditReader(buffer, options).generateReport(filePath, buffer.byteLength);
291
+ }
292
+ const CLI_VERSION = "0.9.0";
293
+ function printUsage() {
294
+ console.log(`
295
+ Cap'n Proto Security Audit CLI v${CLI_VERSION}
296
+
297
+ Usage: capnp audit <file> [options]
298
+
299
+ Arguments:
300
+ file Path to Cap'n Proto message binary file
301
+
302
+ Options:
303
+ --strict Strict mode, fail on warnings
304
+ --json Output as JSON
305
+ -o, --output <file> Write report to file
306
+ --max-segments <n> Maximum allowed segments (default: 64)
307
+ --max-size <bytes> Maximum message size in bytes (default: 64MB)
308
+ -h, --help Show this help
309
+
310
+ Examples:
311
+ capnp audit message.bin
312
+ capnp audit message.bin --strict
313
+ capnp audit message.bin --json -o report.json
314
+ capnp audit message.bin --max-segments 128 --max-size 134217728
315
+ `);
316
+ }
317
+ function parseArgs(args) {
318
+ const options = {};
319
+ for (let i = 0; i < args.length; i++) {
320
+ const arg = args[i];
321
+ if (arg === "-h" || arg === "--help") {
322
+ printUsage();
323
+ process.exit(0);
324
+ }
325
+ if (arg === "--strict") options.strict = true;
326
+ else if (arg === "--json") options.json = true;
327
+ else if (arg === "-o" || arg === "--output") options.output = args[++i];
328
+ else if (arg === "--max-segments") {
329
+ const val = Number.parseInt(args[++i], 10);
330
+ if (Number.isNaN(val) || val <= 0) {
331
+ console.error("Error: --max-segments must be a positive integer");
332
+ process.exit(1);
333
+ }
334
+ options.maxSegments = val;
335
+ } else if (arg === "--max-size") {
336
+ const val = Number.parseInt(args[++i], 10);
337
+ if (Number.isNaN(val) || val <= 0) {
338
+ console.error("Error: --max-size must be a positive integer");
339
+ process.exit(1);
340
+ }
341
+ options.maxSize = val;
342
+ } else if (!arg.startsWith("-")) {
343
+ if (!options.file) options.file = arg;
344
+ }
345
+ }
346
+ return options;
347
+ }
348
+ async function run(args) {
349
+ const options = parseArgs(args);
350
+ if (!options.file) {
351
+ console.error("Error: File path is required");
352
+ printUsage();
353
+ process.exit(1);
354
+ }
355
+ try {
356
+ const auditOptions = {
357
+ strictMode: options.strict,
358
+ maxSegments: options.maxSegments,
359
+ maxTotalSize: options.maxSize
360
+ };
361
+ const report = auditMessageFile(options.file, auditOptions);
362
+ let output;
363
+ if (options.json) output = JSON.stringify(report, null, 2);
364
+ else output = formatAuditReport(report);
365
+ if (options.output) {
366
+ const { writeFileSync } = await import("node:fs");
367
+ writeFileSync(options.output, output, "utf-8");
368
+ console.log(`Report written to: ${options.output}`);
369
+ } else console.log(output);
370
+ const shouldFail = !report.passed || options.strict && report.summary.warning > 0;
371
+ process.exit(shouldFail ? 1 : 0);
372
+ } catch (err) {
373
+ console.error("Error:", err instanceof Error ? err.message : err);
374
+ process.exit(2);
375
+ }
376
+ }
377
+
378
+ //#endregion
379
+ export { run };
380
+ //# sourceMappingURL=cli-audit-j58Eyd5d.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-audit-j58Eyd5d.js","names":[],"sources":["../src/cli-audit.ts"],"sourcesContent":["import { readFileSync } from 'node:fs';\nimport {\n ElementSize,\n type FarPointer,\n type ListPointer,\n PointerTag,\n type StructPointer,\n decodePointer,\n} from './core/pointer.js';\nimport { Segment, WORD_SIZE } from './core/segment.js';\n\n/**\n * 审计安全选项配置\n */\nexport interface AuditSecurityOptions {\n /** 最大段数限制(默认64) */\n maxSegments?: number;\n /** 消息总大小限制,单位字节(默认64MB) */\n maxTotalSize?: number;\n /** 严格模式,遇到异常立即抛出而非静默处理(默认false) */\n strictMode?: boolean;\n}\n\n/** 默认安全选项 */\nconst DEFAULT_AUDIT_OPTIONS: Required<AuditSecurityOptions> = {\n maxSegments: 64,\n maxTotalSize: 64 * 1024 * 1024, // 64MB\n strictMode: false,\n};\n\n/**\n * 审计问题类型\n */\nexport type AuditIssueType = 'error' | 'warning' | 'info';\n\n/**\n * 审计问题\n */\nexport interface AuditIssue {\n /** 问题类型 */\n type: AuditIssueType;\n /** 问题分类 */\n category: string;\n /** 问题描述 */\n message: string;\n /** 问题位置 */\n location: string;\n /** 修复建议 */\n suggestion?: string;\n}\n\n/**\n * 审计报告\n */\nexport interface AuditReport {\n /** 文件路径 */\n filePath: string;\n /** 文件大小 */\n fileSize: number;\n /** 是否通过审计 */\n passed: boolean;\n /** 段数 */\n segmentCount: number;\n /** 总字数 */\n totalWords: number;\n /** 问题列表 */\n issues: AuditIssue[];\n /** 摘要 */\n summary: {\n error: number;\n warning: number;\n info: number;\n };\n /** 扫描的指针数量 */\n pointersScanned: number;\n /** 最大嵌套深度 */\n maxNestingDepth: number;\n}\n\n/**\n * Cap'n Proto 消息审计读取器\n * 用于安全审计消息文件\n */\nexport class AuditReader {\n private segments: Segment[];\n private options: Required<AuditSecurityOptions>;\n private issues: AuditIssue[] = [];\n private visitedPointers = new Set<string>();\n private pointersScanned = 0;\n private maxNestingDepth = 0;\n\n constructor(buffer: ArrayBuffer | Uint8Array, options: AuditSecurityOptions = {}) {\n this.options = { ...DEFAULT_AUDIT_OPTIONS, ...options };\n const uint8Array = buffer instanceof ArrayBuffer ? new Uint8Array(buffer) : buffer;\n\n // 初始化空段数组\n this.segments = [];\n\n // 检查最小大小\n if (uint8Array.byteLength < 8) {\n this.addIssue(\n 'error',\n 'invalid_header',\n \"消息太小,无法包含有效的 Cap'n Proto 头部\",\n 'header',\n '消息至少需要8字节的头部'\n );\n return;\n }\n\n // 检查总大小限制\n if (uint8Array.byteLength > this.options.maxTotalSize) {\n this.addIssue(\n 'error',\n 'size_exceeded',\n `消息大小(${uint8Array.byteLength}字节)超过最大限制(${this.options.maxTotalSize}字节)`,\n 'header',\n '减小消息大小或增加 --max-size 限制'\n );\n if (this.options.strictMode) return;\n }\n\n // 解析消息头\n const view = new DataView(uint8Array.buffer, uint8Array.byteOffset, uint8Array.byteLength);\n const firstWordLow = view.getUint32(0, true);\n const firstWordHigh = view.getUint32(4, true);\n\n const segmentCount = (firstWordLow & 0xffffffff) + 1;\n const firstSegmentSize = firstWordHigh;\n\n // 检查段数限制\n if (segmentCount > this.options.maxSegments) {\n this.addIssue(\n 'error',\n 'segment_count_exceeded',\n `段数(${segmentCount})超过最大限制(${this.options.maxSegments})`,\n 'header',\n '减少段数或增加 --max-segments 限制'\n );\n if (this.options.strictMode) return;\n }\n\n let offset = 8;\n const segmentSizes: number[] = [firstSegmentSize];\n\n // 读取剩余段大小\n for (let i = 1; i < segmentCount; i++) {\n if (offset + 4 > uint8Array.byteLength) {\n this.addIssue(\n 'error',\n 'truncated_header',\n '段表在段大小信息之前结束',\n `header.segment[${i}]`,\n '消息文件可能已损坏'\n );\n return;\n }\n segmentSizes.push(view.getUint32(offset, true));\n offset += 4;\n }\n\n // 对齐到 8 字节\n offset = (offset + 7) & ~7;\n\n if (offset > uint8Array.byteLength) {\n this.addIssue('error', 'truncated_header', '段表头部不完整', 'header', '消息文件可能已损坏');\n return;\n }\n\n // 创建段\n let totalWords = 0;\n for (let i = 0; i < segmentSizes.length; i++) {\n const size = segmentSizes[i];\n totalWords += size;\n\n if (offset + size * WORD_SIZE > uint8Array.byteLength) {\n this.addIssue(\n 'warning',\n 'truncated_segment',\n `段${i}数据不足,声明大小:${size}字,实际可用:${Math.floor((uint8Array.byteLength - offset) / WORD_SIZE)}字`,\n `segment[${i}]`,\n '消息文件可能已损坏或被截断'\n );\n break;\n }\n const segmentBuffer = uint8Array.slice(offset, offset + size * WORD_SIZE);\n this.segments.push(Segment.fromBuffer(segmentBuffer.buffer));\n offset += size * WORD_SIZE;\n }\n\n if (totalWords > this.options.maxTotalSize / WORD_SIZE) {\n this.addIssue(\n 'warning',\n 'large_message',\n `消息总字数(${totalWords})较大,可能影响性能`,\n 'message',\n '考虑分割大型消息'\n );\n }\n\n // 扫描所有指针\n this.scanAllPointers();\n }\n\n /**\n * 添加审计问题\n */\n private addIssue(\n type: AuditIssueType,\n category: string,\n message: string,\n location: string,\n suggestion?: string\n ): void {\n this.issues.push({ type, category, message, location, suggestion });\n }\n\n /**\n * 获取段\n */\n private getSegment(index: number): Segment | undefined {\n return this.segments[index];\n }\n\n /**\n * 扫描所有指针\n */\n private scanAllPointers(): void {\n if (this.segments.length === 0) return;\n\n // 从根段开始扫描\n for (let segIdx = 0; segIdx < this.segments.length; segIdx++) {\n const segment = this.segments[segIdx];\n const wordCount = segment.wordCount;\n\n for (let wordIdx = 0; wordIdx < wordCount; wordIdx++) {\n const ptrValue = segment.getWord(wordIdx);\n if (ptrValue === 0n) continue; // 空指针\n\n this.scanPointer(segIdx, wordIdx, ptrValue, 0);\n }\n }\n }\n\n /**\n * 扫描单个指针\n */\n private scanPointer(\n segmentIndex: number,\n wordOffset: number,\n ptrValue: bigint,\n depth: number\n ): void {\n this.pointersScanned++;\n this.maxNestingDepth = Math.max(this.maxNestingDepth, depth);\n\n // 检查循环引用\n const ptrKey = `${segmentIndex}:${wordOffset}`;\n if (this.visitedPointers.has(ptrKey)) {\n this.addIssue(\n 'warning',\n 'circular_reference',\n `检测到可能的循环引用 at segment[${segmentIndex}].word[${wordOffset}]`,\n `segment[${segmentIndex}].word[${wordOffset}]`,\n '检查消息结构是否存在循环'\n );\n return;\n }\n this.visitedPointers.add(ptrKey);\n\n // 检查嵌套深度\n if (depth > 100) {\n this.addIssue(\n 'error',\n 'nesting_too_deep',\n '指针嵌套深度超过100,可能存在恶意构造的消息',\n `segment[${segmentIndex}].word[${wordOffset}]`,\n '检查消息是否被恶意构造'\n );\n return;\n }\n\n const ptr = decodePointer(ptrValue);\n const segment = this.getSegment(segmentIndex);\n if (!segment) return;\n\n switch (ptr.tag) {\n case PointerTag.STRUCT: {\n const structPtr = ptr as StructPointer;\n const targetOffset = wordOffset + 1 + structPtr.offset;\n\n // 检查目标范围\n if (\n targetOffset < 0 ||\n targetOffset + structPtr.dataWords + structPtr.pointerCount > segment.wordCount\n ) {\n this.addIssue(\n 'error',\n 'out_of_bounds',\n `Struct指针目标超出段范围: offset=${structPtr.offset}, dataWords=${structPtr.dataWords}, pointerCount=${structPtr.pointerCount}`,\n `segment[${segmentIndex}].word[${wordOffset}]`,\n '消息可能已损坏或被篡改'\n );\n }\n\n // 检查指针区域中的指针\n const pointerStart = targetOffset + structPtr.dataWords;\n for (let i = 0; i < structPtr.pointerCount; i++) {\n const ptrIdx = pointerStart + i;\n if (ptrIdx < segment.wordCount) {\n const nestedPtr = segment.getWord(ptrIdx);\n if (nestedPtr !== 0n) {\n this.scanPointer(segmentIndex, ptrIdx, nestedPtr, depth + 1);\n }\n }\n }\n break;\n }\n\n case PointerTag.LIST: {\n const listPtr = ptr as ListPointer;\n\n if (listPtr.elementSize === ElementSize.COMPOSITE) {\n // 需要读取tag word\n const tagOffset = wordOffset + 1 + listPtr.offset;\n if (tagOffset >= 0 && tagOffset < segment.wordCount) {\n const tagWord = segment.getWord(tagOffset);\n const elementCount = Number(tagWord & BigInt(0xffffffff));\n const dataWords = Number((tagWord >> BigInt(32)) & BigInt(0xffff));\n const pointerCount = Number((tagWord >> BigInt(48)) & BigInt(0xffff));\n\n if (elementCount > 1000000) {\n this.addIssue(\n 'warning',\n 'large_list',\n `复合列表元素数量异常: ${elementCount}`,\n `segment[${segmentIndex}].word[${wordOffset}]`,\n '检查列表大小是否合理'\n );\n }\n\n // 检查每个元素中的指针\n const elementSize = dataWords + pointerCount;\n const dataStart = tagOffset + 1;\n for (let i = 0; i < elementCount; i++) {\n for (let j = 0; j < pointerCount; j++) {\n const ptrIdx = dataStart + i * elementSize + dataWords + j;\n if (ptrIdx < segment.wordCount) {\n const nestedPtr = segment.getWord(ptrIdx);\n if (nestedPtr !== 0n) {\n this.scanPointer(segmentIndex, ptrIdx, nestedPtr, depth + 1);\n }\n }\n }\n }\n }\n } else if (listPtr.elementSize === ElementSize.POINTER) {\n // 指针列表\n const targetOffset = wordOffset + 1 + listPtr.offset;\n for (let i = 0; i < listPtr.elementCount; i++) {\n const ptrIdx = targetOffset + i;\n if (ptrIdx < segment.wordCount) {\n const nestedPtr = segment.getWord(ptrIdx);\n if (nestedPtr !== 0n) {\n this.scanPointer(segmentIndex, ptrIdx, nestedPtr, depth + 1);\n }\n }\n }\n }\n break;\n }\n\n case PointerTag.FAR: {\n const farPtr = ptr as FarPointer;\n\n if (farPtr.targetSegment >= this.segments.length) {\n this.addIssue(\n 'error',\n 'invalid_far_pointer',\n `Far指针引用不存在的段: ${farPtr.targetSegment}`,\n `segment[${segmentIndex}].word[${wordOffset}]`,\n '消息可能已损坏'\n );\n } else {\n const targetSegment = this.getSegment(farPtr.targetSegment);\n if (targetSegment && farPtr.targetOffset >= targetSegment.wordCount) {\n this.addIssue(\n 'error',\n 'invalid_far_pointer',\n `Far指针目标偏移超出范围: segment=${farPtr.targetSegment}, offset=${farPtr.targetOffset}`,\n `segment[${segmentIndex}].word[${wordOffset}]`,\n '消息可能已损坏'\n );\n }\n\n if (farPtr.doubleFar) {\n // 检查 double-far landing pad\n if (targetSegment && farPtr.targetOffset < targetSegment.wordCount) {\n const landingPadPtr = targetSegment.getWord(farPtr.targetOffset);\n if (landingPadPtr !== 0n) {\n this.scanPointer(\n farPtr.targetSegment,\n farPtr.targetOffset,\n landingPadPtr,\n depth + 1\n );\n }\n }\n }\n }\n break;\n }\n\n case PointerTag.OTHER:\n // Capability 指针或其他特殊类型在序列化消息中不应出现\n this.addIssue(\n 'warning',\n 'capability_in_message',\n '序列化消息中发现 Capability 或其他特殊指针',\n `segment[${segmentIndex}].word[${wordOffset}]`,\n 'Capability 通常不应在序列化消息中'\n );\n break;\n }\n }\n\n /**\n * 生成审计报告\n */\n generateReport(filePath: string, fileSize: number): AuditReport {\n const errors = this.issues.filter((i) => i.type === 'error').length;\n const warnings = this.issues.filter((i) => i.type === 'warning').length;\n const infos = this.issues.filter((i) => i.type === 'info').length;\n\n return {\n filePath,\n fileSize,\n passed: errors === 0,\n segmentCount: this.segments.length,\n totalWords: this.segments.reduce((sum, s) => sum + s.wordCount, 0),\n issues: this.issues,\n summary: {\n error: errors,\n warning: warnings,\n info: infos,\n },\n pointersScanned: this.pointersScanned,\n maxNestingDepth: this.maxNestingDepth,\n };\n }\n}\n\n/**\n * 格式化审计报告为可读文本\n */\nexport function formatAuditReport(report: AuditReport): string {\n const lines: string[] = [];\n\n lines.push('='.repeat(60));\n lines.push(\"CAP'N PROTO SECURITY AUDIT REPORT\");\n lines.push('='.repeat(60));\n lines.push('');\n\n // 文件信息\n lines.push('File Information:');\n lines.push(` Path: ${report.filePath}`);\n lines.push(` Size: ${report.fileSize.toLocaleString()} bytes`);\n lines.push(` Segments: ${report.segmentCount}`);\n lines.push(` Total Words: ${report.totalWords.toLocaleString()}`);\n lines.push(` Pointers Scanned: ${report.pointersScanned.toLocaleString()}`);\n lines.push(` Max Nesting Depth: ${report.maxNestingDepth}`);\n lines.push('');\n\n // 状态\n if (report.passed) {\n lines.push('✅ AUDIT PASSED');\n } else {\n lines.push('❌ AUDIT FAILED');\n }\n lines.push('');\n\n // 摘要\n lines.push('Summary:');\n lines.push(` Errors: ${report.summary.error}`);\n lines.push(` Warnings: ${report.summary.warning}`);\n lines.push(` Info: ${report.summary.info}`);\n lines.push('');\n\n // 问题详情\n if (report.issues.length > 0) {\n lines.push('Issues:');\n lines.push('-'.repeat(60));\n\n // 按类型分组\n const errors = report.issues.filter((i) => i.type === 'error');\n const warnings = report.issues.filter((i) => i.type === 'warning');\n const infos = report.issues.filter((i) => i.type === 'info');\n\n if (errors.length > 0) {\n lines.push('');\n lines.push('❌ ERRORS:');\n for (const issue of errors) {\n lines.push(` [${issue.category}] ${issue.message}`);\n lines.push(` Location: ${issue.location}`);\n if (issue.suggestion) {\n lines.push(` 💡 ${issue.suggestion}`);\n }\n lines.push('');\n }\n }\n\n if (warnings.length > 0) {\n lines.push('');\n lines.push('⚠️ WARNINGS:');\n for (const issue of warnings) {\n lines.push(` [${issue.category}] ${issue.message}`);\n lines.push(` Location: ${issue.location}`);\n if (issue.suggestion) {\n lines.push(` 💡 ${issue.suggestion}`);\n }\n lines.push('');\n }\n }\n\n if (infos.length > 0) {\n lines.push('');\n lines.push('ℹ️ INFO:');\n for (const issue of infos) {\n lines.push(` [${issue.category}] ${issue.message}`);\n lines.push(` Location: ${issue.location}`);\n lines.push('');\n }\n }\n } else {\n lines.push('No issues found.');\n }\n\n lines.push('');\n lines.push('='.repeat(60));\n\n return lines.join('\\n');\n}\n\n/**\n * 审计消息文件\n */\nexport function auditMessageFile(\n filePath: string,\n options: AuditSecurityOptions = {}\n): AuditReport {\n const buffer = readFileSync(filePath);\n const reader = new AuditReader(buffer, options);\n return reader.generateReport(filePath, buffer.byteLength);\n}\n\nconst CLI_VERSION = '0.9.0';\n\nfunction printUsage() {\n console.log(`\nCap'n Proto Security Audit CLI v${CLI_VERSION}\n\nUsage: capnp audit <file> [options]\n\nArguments:\n file Path to Cap'n Proto message binary file\n\nOptions:\n --strict Strict mode, fail on warnings\n --json Output as JSON\n -o, --output <file> Write report to file\n --max-segments <n> Maximum allowed segments (default: 64)\n --max-size <bytes> Maximum message size in bytes (default: 64MB)\n -h, --help Show this help\n\nExamples:\n capnp audit message.bin\n capnp audit message.bin --strict\n capnp audit message.bin --json -o report.json\n capnp audit message.bin --max-segments 128 --max-size 134217728\n`);\n}\n\nfunction parseArgs(args: string[]) {\n const options: {\n file?: string;\n strict?: boolean;\n json?: boolean;\n output?: string;\n maxSegments?: number;\n maxSize?: number;\n } = {};\n\n for (let i = 0; i < args.length; i++) {\n const arg = args[i];\n\n if (arg === '-h' || arg === '--help') {\n printUsage();\n process.exit(0);\n }\n\n if (arg === '--strict') {\n options.strict = true;\n } else if (arg === '--json') {\n options.json = true;\n } else if (arg === '-o' || arg === '--output') {\n options.output = args[++i];\n } else if (arg === '--max-segments') {\n const val = Number.parseInt(args[++i], 10);\n if (Number.isNaN(val) || val <= 0) {\n console.error('Error: --max-segments must be a positive integer');\n process.exit(1);\n }\n options.maxSegments = val;\n } else if (arg === '--max-size') {\n const val = Number.parseInt(args[++i], 10);\n if (Number.isNaN(val) || val <= 0) {\n console.error('Error: --max-size must be a positive integer');\n process.exit(1);\n }\n options.maxSize = val;\n } else if (!arg.startsWith('-')) {\n if (!options.file) {\n options.file = arg;\n }\n }\n }\n\n return options;\n}\n\nexport async function run(args: string[]): Promise<void> {\n const options = parseArgs(args);\n\n if (!options.file) {\n console.error('Error: File path is required');\n printUsage();\n process.exit(1);\n }\n\n try {\n const auditOptions: AuditSecurityOptions = {\n strictMode: options.strict,\n maxSegments: options.maxSegments,\n maxTotalSize: options.maxSize,\n };\n\n const report = auditMessageFile(options.file, auditOptions);\n\n let output: string;\n if (options.json) {\n output = JSON.stringify(report, null, 2);\n } else {\n output = formatAuditReport(report);\n }\n\n if (options.output) {\n const { writeFileSync } = await import('node:fs');\n writeFileSync(options.output, output, 'utf-8');\n console.log(`Report written to: ${options.output}`);\n } else {\n console.log(output);\n }\n\n // Exit with error code if audit failed or strict mode has warnings\n const shouldFail = !report.passed || (options.strict && report.summary.warning > 0);\n process.exit(shouldFail ? 1 : 0);\n } catch (err) {\n console.error('Error:', err instanceof Error ? err.message : err);\n process.exit(2);\n }\n}\n"],"mappings":"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"}