@zhuixin/engine 0.2.4 → 0.2.5
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 +10 -0
- package/dist/engine-main.js +324 -23
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,6 +5,16 @@ Private source repository for the AppTrace analysis engine.
|
|
|
5
5
|
The published npm package contains only the compiled bundle (`dist/engine-main.js`).
|
|
6
6
|
Source, tests, and build configuration stay in this repository.
|
|
7
7
|
|
|
8
|
+
## Status
|
|
9
|
+
|
|
10
|
+
- macOS arm64: bundle inspection and Electron/JavaScript analysis are verified.
|
|
11
|
+
Reports include the code-signature authority chain, team identifier and entitlement
|
|
12
|
+
keys. Ghidra headless import and automatic analysis work with a thin Mach-O, but
|
|
13
|
+
macOS native analysis is not implemented. `.dmg` acquisition is unsupported.
|
|
14
|
+
- Windows x64: local application inspection, PE metadata and Authenticode signature
|
|
15
|
+
inspection are implemented. Native Windows analysis and MSIX/MSI packages are not
|
|
16
|
+
supported.
|
|
17
|
+
|
|
8
18
|
## Release
|
|
9
19
|
|
|
10
20
|
```bash
|
package/dist/engine-main.js
CHANGED
|
@@ -19950,6 +19950,12 @@ function archName2(cpu, subtype) {
|
|
|
19950
19950
|
if (cpu === 16777228) return (subtype & 16777215) === 2 ? "arm64e" : "arm64";
|
|
19951
19951
|
return `cpu-${cpu.toString(16)}`;
|
|
19952
19952
|
}
|
|
19953
|
+
function readEntitlements(stdout) {
|
|
19954
|
+
const trimmed = stdout.trim();
|
|
19955
|
+
if (!trimmed) return void 0;
|
|
19956
|
+
const xmlStart = trimmed.search(/<\?xml|<plist/i);
|
|
19957
|
+
return (xmlStart === -1 ? trimmed : trimmed.slice(xmlStart)).trim() || void 0;
|
|
19958
|
+
}
|
|
19953
19959
|
function machOArchitectures(filePath) {
|
|
19954
19960
|
const fd = import_node_fs4.default.openSync(filePath, "r");
|
|
19955
19961
|
try {
|
|
@@ -19989,7 +19995,9 @@ async function readSignature(bundlePath, warnings) {
|
|
|
19989
19995
|
try {
|
|
19990
19996
|
const { stdout, stderr } = await execFileAsync(
|
|
19991
19997
|
"codesign",
|
|
19992
|
-
|
|
19998
|
+
// Single `-v` omits the Authority chain on macOS 14.x; the certificate
|
|
19999
|
+
// chain is only printed at triple verbosity.
|
|
20000
|
+
["-dvvv", "--entitlements", ":-", bundlePath],
|
|
19993
20001
|
{ timeout: 3e4, maxBuffer: 4 * 1024 * 1024 }
|
|
19994
20002
|
);
|
|
19995
20003
|
const details = `${stderr}`;
|
|
@@ -19998,11 +20006,14 @@ async function readSignature(bundlePath, warnings) {
|
|
|
19998
20006
|
);
|
|
19999
20007
|
const team = /^TeamIdentifier=(.+)$/m.exec(details)?.[1]?.trim();
|
|
20000
20008
|
const signed = /^CodeDirectory|^Signature=/m.test(details) || authority.length > 0;
|
|
20009
|
+
if (/invalid entitlements blob/i.test(details)) {
|
|
20010
|
+
warnings.push("Code signature reports an invalid entitlements blob; the OS ignores these entitlements.");
|
|
20011
|
+
}
|
|
20001
20012
|
return {
|
|
20002
20013
|
signed,
|
|
20003
20014
|
authority: authority.length ? authority : void 0,
|
|
20004
20015
|
teamIdentifier: team,
|
|
20005
|
-
entitlements: stdout
|
|
20016
|
+
entitlements: readEntitlements(stdout)
|
|
20006
20017
|
};
|
|
20007
20018
|
} catch (error) {
|
|
20008
20019
|
const stderr = error.stderr ?? "";
|
|
@@ -20950,6 +20961,11 @@ function inspectionValue(inspection, key) {
|
|
|
20950
20961
|
function record(value) {
|
|
20951
20962
|
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
20952
20963
|
}
|
|
20964
|
+
function entitlementKeys(value) {
|
|
20965
|
+
if (typeof value !== "string" || !value.includes("<key>")) return void 0;
|
|
20966
|
+
const keys = [...value.matchAll(/<key>([^<]+)<\/key>/g)].map((match) => match[1]);
|
|
20967
|
+
return keys.length ? keys : void 0;
|
|
20968
|
+
}
|
|
20953
20969
|
function collectEvidencePaths(jobs) {
|
|
20954
20970
|
const paths = /* @__PURE__ */ new Set();
|
|
20955
20971
|
for (const job of jobs) {
|
|
@@ -20986,6 +21002,13 @@ function renderMacAppInspection(lines, inspection, warnings) {
|
|
|
20986
21002
|
`| Authority | ${cell(
|
|
20987
21003
|
Array.isArray(signature?.authority) ? signature?.authority.join(", ") : void 0
|
|
20988
21004
|
)} |`,
|
|
21005
|
+
`| Team identifier | ${cell(signature?.teamIdentifier)} |`,
|
|
21006
|
+
`| Entitlements | ${cell(
|
|
21007
|
+
(() => {
|
|
21008
|
+
const keys = entitlementKeys(signature?.entitlements);
|
|
21009
|
+
return keys ? keys.join(", ") : void 0;
|
|
21010
|
+
})()
|
|
21011
|
+
)} |`,
|
|
20989
21012
|
""
|
|
20990
21013
|
);
|
|
20991
21014
|
if (warnings.length > 0) {
|
|
@@ -21063,6 +21086,210 @@ function renderIpaInspection(lines, inspection, warnings) {
|
|
|
21063
21086
|
lines.push("");
|
|
21064
21087
|
}
|
|
21065
21088
|
}
|
|
21089
|
+
function numberValue(value) {
|
|
21090
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
21091
|
+
}
|
|
21092
|
+
var IRREGULAR_PLURALS = {
|
|
21093
|
+
"storage entry": "storage entries",
|
|
21094
|
+
slice: "slices"
|
|
21095
|
+
};
|
|
21096
|
+
function countLabel(count, noun) {
|
|
21097
|
+
const many = IRREGULAR_PLURALS[noun] ?? `${noun}s`;
|
|
21098
|
+
return `${count.toLocaleString("en-US")} ${count === 1 ? noun : many}`;
|
|
21099
|
+
}
|
|
21100
|
+
var PLATFORM_LABEL = {
|
|
21101
|
+
ios: "iOS",
|
|
21102
|
+
macos: "macOS",
|
|
21103
|
+
windows: "Windows",
|
|
21104
|
+
linux: "Linux"
|
|
21105
|
+
};
|
|
21106
|
+
var FORMAT_LABEL = {
|
|
21107
|
+
ipa: "IPA",
|
|
21108
|
+
app: "macOS app bundle",
|
|
21109
|
+
exe: "Windows executable"
|
|
21110
|
+
};
|
|
21111
|
+
function formatBytes(bytes) {
|
|
21112
|
+
if (typeof bytes !== "number" || bytes <= 0) return "unknown size";
|
|
21113
|
+
const units = ["B", "KB", "MB", "GB"];
|
|
21114
|
+
let size = bytes;
|
|
21115
|
+
let unit = 0;
|
|
21116
|
+
while (size >= 1024 && unit < units.length - 1) {
|
|
21117
|
+
size /= 1024;
|
|
21118
|
+
unit += 1;
|
|
21119
|
+
}
|
|
21120
|
+
return `${size.toFixed(unit === 0 ? 0 : 1)} ${units[unit]}`;
|
|
21121
|
+
}
|
|
21122
|
+
function routeSegments(value) {
|
|
21123
|
+
return value.replace(/^(?:\.{1,2}\/)+/, "").replace(/^\/+/, "").split(/[/?#]/).filter((segment) => segment.length > 0);
|
|
21124
|
+
}
|
|
21125
|
+
function routeGroup(value, depth = 1) {
|
|
21126
|
+
const segments = routeSegments(value).slice(0, depth);
|
|
21127
|
+
return segments.length > 0 ? segments.join("/") : "(root)";
|
|
21128
|
+
}
|
|
21129
|
+
function groupRoutes(values2) {
|
|
21130
|
+
if (values2.length === 0) return [];
|
|
21131
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
21132
|
+
for (const value of values2) {
|
|
21133
|
+
const key = routeGroup(value, 1);
|
|
21134
|
+
const bucket = buckets.get(key);
|
|
21135
|
+
if (bucket) bucket.values.push(value);
|
|
21136
|
+
else buckets.set(key, { depth: 1, values: [value] });
|
|
21137
|
+
}
|
|
21138
|
+
for (let guard = 0; guard < 8; guard += 1) {
|
|
21139
|
+
const [key, bucket] = [...buckets.entries()].sort(
|
|
21140
|
+
(a, b) => b[1].values.length - a[1].values.length
|
|
21141
|
+
)[0];
|
|
21142
|
+
if (bucket.depth >= 3 || bucket.values.length <= values2.length / 2) break;
|
|
21143
|
+
const split = /* @__PURE__ */ new Map();
|
|
21144
|
+
for (const value of bucket.values) {
|
|
21145
|
+
const next = routeGroup(value, bucket.depth + 1);
|
|
21146
|
+
const list = split.get(next);
|
|
21147
|
+
if (list) list.push(value);
|
|
21148
|
+
else split.set(next, [value]);
|
|
21149
|
+
}
|
|
21150
|
+
if (split.size * 2 > bucket.values.length) break;
|
|
21151
|
+
if (split.size === 1 && split.has(key)) break;
|
|
21152
|
+
buckets.delete(key);
|
|
21153
|
+
for (const [next, list] of split) {
|
|
21154
|
+
buckets.set(next, { depth: bucket.depth + 1, values: list });
|
|
21155
|
+
}
|
|
21156
|
+
}
|
|
21157
|
+
return [...buckets.entries()].map(([group, bucket]) => ({
|
|
21158
|
+
group,
|
|
21159
|
+
count: bucket.values.length,
|
|
21160
|
+
example: bucket.values[0]
|
|
21161
|
+
})).sort((a, b) => b.count - a.count || a.group.localeCompare(b.group));
|
|
21162
|
+
}
|
|
21163
|
+
function selectReportJobs(jobs, limit = 20) {
|
|
21164
|
+
const window = jobs.slice(0, limit);
|
|
21165
|
+
const present = new Set(window.map((job) => job.id));
|
|
21166
|
+
const carried = [];
|
|
21167
|
+
for (const capability of ["native:inspect"]) {
|
|
21168
|
+
const latest = jobs.find(
|
|
21169
|
+
(job) => job.capabilityId === capability && String(job.status) === "analyzed"
|
|
21170
|
+
);
|
|
21171
|
+
if (latest && !present.has(latest.id)) carried.push(latest);
|
|
21172
|
+
}
|
|
21173
|
+
return [...window, ...carried];
|
|
21174
|
+
}
|
|
21175
|
+
function nativeFacts(jobs) {
|
|
21176
|
+
const job = [...jobs].reverse().find(
|
|
21177
|
+
(item) => item.capabilityId === "native:inspect" && String(item.status) === "analyzed"
|
|
21178
|
+
);
|
|
21179
|
+
if (!job?.result) return { inspected: false };
|
|
21180
|
+
const counts = record(job.result.counts) ?? {};
|
|
21181
|
+
const encryption = record(job.result.encryption) ?? {};
|
|
21182
|
+
return {
|
|
21183
|
+
inspected: true,
|
|
21184
|
+
procedureCount: numberValue(
|
|
21185
|
+
counts.procedure_count ?? counts.procedureCount
|
|
21186
|
+
),
|
|
21187
|
+
stringCount: numberValue(counts.string_count ?? counts.stringCount),
|
|
21188
|
+
declaredSlices: numberValue(encryption.declaredSlices),
|
|
21189
|
+
encryptedSlices: numberValue(encryption.encryptedSlices),
|
|
21190
|
+
allDeclaredEncrypted: typeof encryption.allDeclaredEncrypted === "boolean" ? encryption.allDeclaredEncrypted : void 0
|
|
21191
|
+
};
|
|
21192
|
+
}
|
|
21193
|
+
function encryptionStatement(native, packageFormat) {
|
|
21194
|
+
if (packageFormat !== "ipa") {
|
|
21195
|
+
return "Not applicable: declared encryption only applies to iOS Mach-O slices.";
|
|
21196
|
+
}
|
|
21197
|
+
if (!native.inspected) {
|
|
21198
|
+
return "Not determined \u2014 native inspection has not run for this artifact, so the encryption state is unknown.";
|
|
21199
|
+
}
|
|
21200
|
+
const declared = native.declaredSlices ?? 0;
|
|
21201
|
+
const encrypted = native.encryptedSlices ?? 0;
|
|
21202
|
+
if (declared === 0) {
|
|
21203
|
+
return "The main executable declares no slices, so encryption state is unknown.";
|
|
21204
|
+
}
|
|
21205
|
+
if (encrypted === 0) {
|
|
21206
|
+
return `No slice declares encryption (0 of ${declared}). Native code is readable, subject to normal static-analysis limits.`;
|
|
21207
|
+
}
|
|
21208
|
+
const subject = native.allDeclaredEncrypted ? "Every main-executable slice declares" : `${encrypted} of ${countLabel(declared, "slice")} declare`;
|
|
21209
|
+
return `${subject} encryption (\`cryptid != 0\`, FairPlay). Symbol and string tables remain readable; decompiling those regions does not yield trustworthy original code.`;
|
|
21210
|
+
}
|
|
21211
|
+
function renderSummary(input2) {
|
|
21212
|
+
const { target, artifact, counts, routeGroups, native, technology } = input2;
|
|
21213
|
+
const countOf = (type) => counts.get(type) ?? 0;
|
|
21214
|
+
const lines = ["## Summary", ""];
|
|
21215
|
+
const format = artifact ? FORMAT_LABEL[artifact.packageFormat] ?? artifact.packageFormat.toUpperCase() : void 0;
|
|
21216
|
+
const platform = PLATFORM_LABEL[target.targetRef.platform] ?? target.targetRef.platform;
|
|
21217
|
+
const identity = [
|
|
21218
|
+
`**${target.name}**`,
|
|
21219
|
+
`${platform} application`,
|
|
21220
|
+
target.version ? `version ${target.version}` : void 0,
|
|
21221
|
+
target.publisher ? `published by ${target.publisher}` : void 0
|
|
21222
|
+
].filter(Boolean).join(", ");
|
|
21223
|
+
const article = format && /^[AEIOU]/i.test(format) ? "an" : "a";
|
|
21224
|
+
lines.push(
|
|
21225
|
+
`${identity}${/[.!?]$/.test(identity) ? "" : "."}${artifact ? ` The artifact is ${article} ${format} of ${formatBytes(artifact.sizeBytes)}, SHA-256 \`${artifact.sha256.slice(0, 12)}\u2026\`.` : " No artifact has been registered yet."}`,
|
|
21226
|
+
""
|
|
21227
|
+
);
|
|
21228
|
+
const hasScriptSurface = countOf("route") > 0 || countOf("module") > 0 || countOf("asset") > 0;
|
|
21229
|
+
if (hasScriptSurface) {
|
|
21230
|
+
const surface = [
|
|
21231
|
+
countOf("route") > 0 ? `${countLabel(countOf("route"), "route")} in ${countLabel(routeGroups.length, "path group")}` : void 0,
|
|
21232
|
+
countOf("module") > 0 ? countLabel(countOf("module"), "JavaScript module") : void 0,
|
|
21233
|
+
countOf("asset") > 0 ? countLabel(countOf("asset"), "asset") : void 0,
|
|
21234
|
+
countLabel(countOf("network_endpoint"), "network endpoint candidate"),
|
|
21235
|
+
countOf("storage") > 0 ? countLabel(countOf("storage"), "storage entry") : void 0
|
|
21236
|
+
].filter(Boolean);
|
|
21237
|
+
lines.push(`**Static surface.** ${surface.join(", ")}.`, "");
|
|
21238
|
+
if (countOf("network_endpoint") === 0) {
|
|
21239
|
+
lines.push(
|
|
21240
|
+
"No endpoint was classified. The analyzer reports absolute URLs found in inert JavaScript; a base URL assembled at runtime is not visible this way, so this is **not** evidence that the app makes no network calls.",
|
|
21241
|
+
""
|
|
21242
|
+
);
|
|
21243
|
+
}
|
|
21244
|
+
} else {
|
|
21245
|
+
lines.push(
|
|
21246
|
+
"**Static surface.** No bundle script, route or module findings were recorded; the bundle inspection above is the complete recorded surface.",
|
|
21247
|
+
""
|
|
21248
|
+
);
|
|
21249
|
+
}
|
|
21250
|
+
lines.push(
|
|
21251
|
+
`**Technology signals.** ${technology.length > 0 ? technology.join(" / ") : "none detected"} (static hints, not a build-tool claim).`,
|
|
21252
|
+
"",
|
|
21253
|
+
`**Encryption.** ${encryptionStatement(native, artifact?.packageFormat)}`,
|
|
21254
|
+
""
|
|
21255
|
+
);
|
|
21256
|
+
if (native.inspected) {
|
|
21257
|
+
const indexed = [
|
|
21258
|
+
native.procedureCount !== void 0 ? countLabel(native.procedureCount, "procedure") : void 0,
|
|
21259
|
+
native.stringCount !== void 0 ? countLabel(native.stringCount, "string") : void 0
|
|
21260
|
+
].filter(Boolean);
|
|
21261
|
+
if (indexed.length > 0) {
|
|
21262
|
+
lines.push(
|
|
21263
|
+
`**Native surface.** ${indexed.join(" and ")} indexed; \`search_native\` and \`decompile_function\` answer from this index.`,
|
|
21264
|
+
""
|
|
21265
|
+
);
|
|
21266
|
+
}
|
|
21267
|
+
}
|
|
21268
|
+
const questions = [];
|
|
21269
|
+
if (countOf("route") > 0 || countOf("module") > 0) {
|
|
21270
|
+
questions.push(
|
|
21271
|
+
"Which routes and modules make up a feature, and where each one is defined in the bundle."
|
|
21272
|
+
);
|
|
21273
|
+
}
|
|
21274
|
+
if (native.inspected) {
|
|
21275
|
+
questions.push(
|
|
21276
|
+
"Which native symbols and strings match a name, and what one exact symbol decompiles to."
|
|
21277
|
+
);
|
|
21278
|
+
}
|
|
21279
|
+
if (technology.length > 0) {
|
|
21280
|
+
questions.push("Which framework produced the package and how it is laid out.");
|
|
21281
|
+
}
|
|
21282
|
+
if (questions.length > 0) {
|
|
21283
|
+
lines.push("**Questions this evidence can answer.**", "");
|
|
21284
|
+
for (const question of questions) lines.push(`- ${question}`);
|
|
21285
|
+
lines.push("");
|
|
21286
|
+
}
|
|
21287
|
+
lines.push(
|
|
21288
|
+
"**What it cannot establish.** Runtime behaviour, server-side logic, and code paths outside the recorded evidence; the app was not executed and no backend was contacted.",
|
|
21289
|
+
""
|
|
21290
|
+
);
|
|
21291
|
+
return lines;
|
|
21292
|
+
}
|
|
21066
21293
|
function renderReportMarkdown(input2) {
|
|
21067
21294
|
const {
|
|
21068
21295
|
target,
|
|
@@ -21076,6 +21303,20 @@ function renderReportMarkdown(input2) {
|
|
|
21076
21303
|
const inspection = artifact?.inspection;
|
|
21077
21304
|
const warnings = Array.isArray(inspection?.warnings) ? inspection.warnings.map(String) : [];
|
|
21078
21305
|
const evidencePaths = collectEvidencePaths(jobs);
|
|
21306
|
+
const counts = /* @__PURE__ */ new Map();
|
|
21307
|
+
for (const [type, total] of Object.entries(input2.typeTotals ?? {})) {
|
|
21308
|
+
counts.set(type, total);
|
|
21309
|
+
}
|
|
21310
|
+
if (counts.size === 0) {
|
|
21311
|
+
for (const finding of findings) {
|
|
21312
|
+
counts.set(finding.type, (counts.get(finding.type) ?? 0) + 1);
|
|
21313
|
+
}
|
|
21314
|
+
}
|
|
21315
|
+
const technology = findings.filter((finding) => finding.type === "technology").map((finding) => finding.value);
|
|
21316
|
+
const routeGroups = groupRoutes(
|
|
21317
|
+
findings.filter((finding) => finding.type === "route").map((finding) => finding.value)
|
|
21318
|
+
);
|
|
21319
|
+
const native = nativeFacts(jobs);
|
|
21079
21320
|
const lines = [
|
|
21080
21321
|
`# ${target.name} \xB7 Analysis report`,
|
|
21081
21322
|
"",
|
|
@@ -21085,6 +21326,14 @@ function renderReportMarkdown(input2) {
|
|
|
21085
21326
|
`- Source: ${target.targetRef.sourceId} / ${target.targetRef.sourceRef}`,
|
|
21086
21327
|
"- Scope: this report summarizes the local artifact, static inspection and job records. It does not describe runtime behavior.",
|
|
21087
21328
|
"",
|
|
21329
|
+
...renderSummary({
|
|
21330
|
+
target,
|
|
21331
|
+
artifact,
|
|
21332
|
+
counts,
|
|
21333
|
+
routeGroups,
|
|
21334
|
+
native,
|
|
21335
|
+
technology
|
|
21336
|
+
}),
|
|
21088
21337
|
"## Target",
|
|
21089
21338
|
"",
|
|
21090
21339
|
"| Field | Value |",
|
|
@@ -21135,34 +21384,71 @@ function renderReportMarkdown(input2) {
|
|
|
21135
21384
|
"| Type | Count |",
|
|
21136
21385
|
"| --- | --- |"
|
|
21137
21386
|
);
|
|
21138
|
-
|
|
21139
|
-
for (const finding of findings) {
|
|
21140
|
-
byType.set(finding.type, (byType.get(finding.type) ?? 0) + 1);
|
|
21141
|
-
}
|
|
21142
|
-
if (byType.size === 0) {
|
|
21387
|
+
if (counts.size === 0) {
|
|
21143
21388
|
lines.push("| \u2014 | 0 |");
|
|
21144
21389
|
} else {
|
|
21145
|
-
for (const [type, count] of [...
|
|
21390
|
+
for (const [type, count] of [...counts.entries()].sort(
|
|
21146
21391
|
(a, b) => a[0].localeCompare(b[0])
|
|
21147
21392
|
)) {
|
|
21148
21393
|
lines.push(`| ${cell(type)} | ${count} |`);
|
|
21149
21394
|
}
|
|
21150
21395
|
}
|
|
21151
21396
|
lines.push("");
|
|
21397
|
+
const itemLine = (item) => {
|
|
21398
|
+
const location = item.sourcePath ? ` \u2014 \`${item.sourcePath}\`${item.start ? `:${item.start.line}:${item.start.column}` : ""}` : "";
|
|
21399
|
+
return `- ${item.value}${location} (Evidence: \`${item.evidenceId}\`)`;
|
|
21400
|
+
};
|
|
21401
|
+
const routeItems = findings.filter((finding) => finding.type === "route");
|
|
21402
|
+
const routeTotal = counts.get("route") ?? routeItems.length;
|
|
21403
|
+
if (routeItems.length > 0) {
|
|
21404
|
+
lines.push(
|
|
21405
|
+
`### Routes (${routeTotal}, ${routeGroups.length} path groups)`,
|
|
21406
|
+
"",
|
|
21407
|
+
"Grouped by path prefix. A group holding more than half of the routes is split further, so one row cannot hide the rest.",
|
|
21408
|
+
"",
|
|
21409
|
+
"| Group | Routes | Example |",
|
|
21410
|
+
"| --- | --- | --- |"
|
|
21411
|
+
);
|
|
21412
|
+
const shown = routeGroups.slice(0, 25);
|
|
21413
|
+
for (const group of shown) {
|
|
21414
|
+
lines.push(
|
|
21415
|
+
`| ${cell(group.group)} | ${group.count} | \`${cell(group.example)}\` |`
|
|
21416
|
+
);
|
|
21417
|
+
}
|
|
21418
|
+
if (routeGroups.length > shown.length) {
|
|
21419
|
+
const rest = routeGroups.slice(shown.length).reduce((total, group) => total + group.count, 0);
|
|
21420
|
+
lines.push(
|
|
21421
|
+
`| (other ${routeGroups.length - shown.length} groups) | ${rest} | \u2014 |`
|
|
21422
|
+
);
|
|
21423
|
+
}
|
|
21424
|
+
lines.push(
|
|
21425
|
+
"",
|
|
21426
|
+
routeItems.length < routeTotal ? `The inventory below covers the ${routeItems.length} routed findings included in this report; the full set stays queryable with \`search_findings --types route\`.` : "The inventory below is complete for this artifact.",
|
|
21427
|
+
"",
|
|
21428
|
+
`<details><summary>All ${routeItems.length} routed findings</summary>`,
|
|
21429
|
+
""
|
|
21430
|
+
);
|
|
21431
|
+
for (const item of routeItems) lines.push(itemLine(item));
|
|
21432
|
+
lines.push("", "</details>", "");
|
|
21433
|
+
}
|
|
21434
|
+
const endpointItems = findings.filter(
|
|
21435
|
+
(finding) => finding.type === "network_endpoint"
|
|
21436
|
+
);
|
|
21437
|
+
if (endpointItems.length > 0) {
|
|
21438
|
+
lines.push("### Network endpoint candidates", "");
|
|
21439
|
+
for (const item of endpointItems) lines.push(itemLine(item));
|
|
21440
|
+
lines.push("");
|
|
21441
|
+
}
|
|
21152
21442
|
for (const [type, title] of [
|
|
21153
|
-
["route", "Routes"],
|
|
21154
|
-
["network_endpoint", "Network endpoint candidates"],
|
|
21155
21443
|
["module", "JavaScript modules"],
|
|
21444
|
+
["storage", "Storage entries"],
|
|
21156
21445
|
["native_procedure", "Native symbols"],
|
|
21157
21446
|
["risk", "Risks and limitations"]
|
|
21158
21447
|
]) {
|
|
21159
|
-
const items = findings.filter((finding) => finding.type === type).slice(0,
|
|
21448
|
+
const items = findings.filter((finding) => finding.type === type).slice(0, 40);
|
|
21160
21449
|
if (items.length === 0) continue;
|
|
21161
21450
|
lines.push(`### ${title}`, "");
|
|
21162
|
-
for (const item of items)
|
|
21163
|
-
const location = item.sourcePath ? ` \u2014 \`${item.sourcePath}\`${item.start ? `:${item.start.line}:${item.start.column}` : ""}` : "";
|
|
21164
|
-
lines.push(`- ${item.value}${location} (Evidence: \`${item.evidenceId}\`)`);
|
|
21165
|
-
}
|
|
21451
|
+
for (const item of items) lines.push(itemLine(item));
|
|
21166
21452
|
lines.push("");
|
|
21167
21453
|
}
|
|
21168
21454
|
const analysisGaps = findings.filter((finding) => finding.type === "limitation").slice(0, 20);
|
|
@@ -22199,13 +22485,22 @@ var CapabilityJobRunner = class {
|
|
|
22199
22485
|
"risk",
|
|
22200
22486
|
"limitation"
|
|
22201
22487
|
];
|
|
22488
|
+
const limits = { route: 200, module: 200 };
|
|
22202
22489
|
const findings = findingTypes.flatMap(
|
|
22203
22490
|
(type) => this.store.searchFindings({
|
|
22204
22491
|
targetId: target.id,
|
|
22205
22492
|
types: [type],
|
|
22206
|
-
limit:
|
|
22493
|
+
limit: limits[type] ?? 40
|
|
22207
22494
|
}).items
|
|
22208
22495
|
);
|
|
22496
|
+
const typeTotals = {};
|
|
22497
|
+
for (const type of findingTypes) {
|
|
22498
|
+
typeTotals[type] = this.store.searchFindings({
|
|
22499
|
+
targetId: target.id,
|
|
22500
|
+
types: [type],
|
|
22501
|
+
limit: 1
|
|
22502
|
+
}).total;
|
|
22503
|
+
}
|
|
22209
22504
|
const findingTotal = this.store.searchFindings({
|
|
22210
22505
|
targetId: target.id,
|
|
22211
22506
|
limit: 1
|
|
@@ -22213,9 +22508,13 @@ var CapabilityJobRunner = class {
|
|
|
22213
22508
|
const markdown = renderReportMarkdown({
|
|
22214
22509
|
target,
|
|
22215
22510
|
artifact,
|
|
22216
|
-
jobs:
|
|
22511
|
+
jobs: selectReportJobs(
|
|
22512
|
+
this.store.listJobs(target.id).filter((item) => item.id !== job.id),
|
|
22513
|
+
20
|
|
22514
|
+
),
|
|
22217
22515
|
findings,
|
|
22218
22516
|
findingTotal,
|
|
22517
|
+
typeTotals,
|
|
22219
22518
|
hostPlatform: this.config.hostPlatform,
|
|
22220
22519
|
generatedAt
|
|
22221
22520
|
});
|
|
@@ -22437,7 +22736,9 @@ var FREE_FEATURES = /* @__PURE__ */ new Set([
|
|
|
22437
22736
|
"inspect:pe",
|
|
22438
22737
|
"extract:app",
|
|
22439
22738
|
"javascript:analyze",
|
|
22440
|
-
"generate:report"
|
|
22739
|
+
"generate:report",
|
|
22740
|
+
"native",
|
|
22741
|
+
"compare"
|
|
22441
22742
|
]);
|
|
22442
22743
|
function decode(value) {
|
|
22443
22744
|
return Buffer.from(value, "base64url");
|
|
@@ -22723,11 +23024,11 @@ function evidenceFromRow(row) {
|
|
|
22723
23024
|
};
|
|
22724
23025
|
}
|
|
22725
23026
|
function findingFromRow(row) {
|
|
22726
|
-
const
|
|
22727
|
-
const startLine =
|
|
22728
|
-
const startColumn =
|
|
22729
|
-
const endLine =
|
|
22730
|
-
const endColumn =
|
|
23027
|
+
const numberValue2 = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
23028
|
+
const startLine = numberValue2(row.start_line);
|
|
23029
|
+
const startColumn = numberValue2(row.start_column);
|
|
23030
|
+
const endLine = numberValue2(row.end_line);
|
|
23031
|
+
const endColumn = numberValue2(row.end_column);
|
|
22731
23032
|
return {
|
|
22732
23033
|
id: String(row.id),
|
|
22733
23034
|
targetId: String(row.target_id),
|