@eyeauras/insight-cli 0.3.1 → 0.4.0
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/.cli-package-build.json +1 -1
- package/SKILL.md +4 -1
- package/dist/src/analysis-default.d.ts +26 -0
- package/dist/src/analysis-default.js +149 -0
- package/dist/src/analysis-storage.d.ts +39 -20
- package/dist/src/analysis-storage.js +112 -16
- package/dist/src/arguments.js +9 -4
- package/dist/src/cli.js +45 -37
- package/dist/src/code-references.d.ts +48 -0
- package/dist/src/code-references.js +154 -0
- package/dist/src/context-diagnostics.d.ts +25 -0
- package/dist/src/context-diagnostics.js +82 -0
- package/dist/src/contexts.d.ts +13 -1
- package/dist/src/contexts.js +38 -9
- package/dist/src/function-boundaries.d.ts +9 -0
- package/dist/src/function-boundaries.js +68 -0
- package/dist/src/function-entries.d.ts +33 -0
- package/dist/src/function-entries.js +84 -0
- package/dist/src/function-inspection.d.ts +4 -2
- package/dist/src/function-inspection.js +94 -59
- package/dist/src/function-results.d.ts +3 -1
- package/dist/src/function-results.js +9 -3
- package/dist/src/inline-script.d.ts +15 -0
- package/dist/src/inline-script.js +62 -0
- package/dist/src/internal-timing.d.ts +3 -0
- package/dist/src/internal-timing.js +52 -0
- package/dist/src/lifecycle.js +14 -4
- package/dist/src/native-inputs.d.ts +1 -1
- package/dist/src/native-inputs.js +1 -1
- package/dist/src/native-queries.js +1 -1
- package/dist/src/persistent-client.d.ts +30 -0
- package/dist/src/persistent-client.js +125 -0
- package/dist/src/queries.js +4 -1
- package/dist/src/query-policy.js +9 -1
- package/dist/src/research-view.d.ts +6 -0
- package/dist/src/research-view.js +9 -0
- package/dist/src/research.d.ts +38 -0
- package/dist/src/research.js +78 -8
- package/dist/src/string-anchors.d.ts +43 -0
- package/dist/src/string-anchors.js +176 -0
- package/dist/src/sync-policy.js +3 -0
- package/dist/src/timed-backend.d.ts +3 -0
- package/dist/src/timed-backend.js +16 -0
- package/dist/src/transport.d.ts +1 -0
- package/dist/src/transport.js +4 -0
- package/dist/src/workspace.js +5 -2
- package/package.json +12 -1
- package/references/native/InsightAnchorScan.java +135 -0
- package/references/native/InsightCodeReferences.java +129 -0
- package/references/native/InsightFunctionBoundaries.java +56 -0
- package/references/native/InsightInlineExecutor.java +143 -0
- package/references/native/InsightInlineScript.java +14 -0
- package/references/native/InsightPeRanges.java +92 -0
- package/references/native/InsightResearchQueries.java +22 -1
- package/references/native/InsightResearchView.java +69 -0
- package/references/native/InsightStringAnchors.java +49 -0
- package/references/persistent-client.mjs +125 -0
- package/references/persistent.md +50 -0
- package/references/query-semantics.md +12 -0
- package/references/recovery.md +36 -0
- package/references/research.md +87 -0
- package/references/setup.md +1 -1
- package/references/sparse-navigation.md +173 -0
- package/references/storage.md +35 -7
- package/runtime/GhidraMCP-7.0.0.jar +0 -0
- package/runtime/GhidraMCP-NOTICE +5 -0
- package/runtime/provenance.json +17 -3
- package/runtime/research-view.provenance.json +28 -0
- package/runtime/toolbox.json +10 -3
- package/skill-bundle/insight-artifacts/SKILL.md +1 -1
- package/skill-bundle/insight-artifacts/references/recovery.md +36 -0
- package/skill-bundle/insight-artifacts/references/storage.md +35 -7
- package/skill-bundle/insight-binary/SKILL.md +1 -1
- package/skill-bundle/insight-capture/SKILL.md +1 -1
- package/skill-bundle/insight-cli/SKILL.md +4 -1
- package/skill-bundle/insight-cli/references/persistent-client.mjs +125 -0
- package/skill-bundle/insight-cli/references/persistent.md +50 -0
- package/skill-bundle/insight-cli/references/recovery.md +36 -0
- package/skill-bundle/insight-cli/references/setup.md +1 -1
- package/skill-bundle/insight-cli/references/storage.md +35 -7
- package/skill-bundle/insight-die/SKILL.md +1 -1
- package/skill-bundle/insight-dnspy/SKILL.md +1 -1
- package/skill-bundle/insight-dotnet-dump/SKILL.md +1 -1
- package/skill-bundle/insight-dotnet-stack/SKILL.md +1 -1
- package/skill-bundle/insight-dotnet-trace/SKILL.md +1 -1
- package/skill-bundle/insight-frida/SKILL.md +1 -1
- package/skill-bundle/insight-ghidra/SKILL.md +14 -1
- package/skill-bundle/insight-ghidra/references/persistent-client.mjs +125 -0
- package/skill-bundle/insight-ghidra/references/persistent.md +50 -0
- package/skill-bundle/insight-ghidra/references/query-semantics.md +12 -0
- package/skill-bundle/insight-ghidra/references/recovery.md +36 -0
- package/skill-bundle/insight-ghidra/references/research.md +87 -0
- package/skill-bundle/insight-ghidra/references/setup.md +1 -1
- package/skill-bundle/insight-ghidra/references/sparse-navigation.md +173 -0
- package/skill-bundle/insight-ghidra/references/storage.md +35 -7
- package/skill-bundle/insight-ilspy/SKILL.md +1 -1
- package/skill-bundle/insight-interop/SKILL.md +1 -1
- package/skill-bundle/insight-lldb/SKILL.md +1 -1
- package/skill-bundle/insight-llvm/SKILL.md +1 -1
- package/skill-bundle/insight-managed-heap/SKILL.md +1 -1
- package/skill-bundle/insight-managed-metadata/SKILL.md +1 -1
- package/skill-bundle/insight-mitmproxy/SKILL.md +1 -1
- package/skill-bundle/insight-overview/SKILL.md +1 -1
- package/skill-bundle/insight-procmon/SKILL.md +1 -1
- package/skill-bundle/insight-re/SKILL.md +4 -1
- package/skill-bundle/insight-re/references/persistent-client.mjs +125 -0
- package/skill-bundle/insight-re/references/persistent.md +50 -0
- package/skill-bundle/insight-re/references/recovery.md +36 -0
- package/skill-bundle/insight-re/references/storage.md +35 -7
- package/skill-bundle/insight-sevenzip/SKILL.md +1 -1
- package/skill-bundle/insight-strace/SKILL.md +1 -1
- package/skill-bundle/insight-tshark/SKILL.md +1 -1
- package/skill-bundle/insight-windbg/SKILL.md +1 -1
- package/skill-bundle/manifest.json +1 -1
- package/skills/insight-artifacts/SKILL.md +1 -1
- package/skills/insight-artifacts/references/recovery.md +36 -0
- package/skills/insight-artifacts/references/storage.md +35 -7
- package/skills/insight-binary/SKILL.md +1 -1
- package/skills/insight-capture/SKILL.md +1 -1
- package/skills/insight-die/SKILL.md +1 -1
- package/skills/insight-dnspy/SKILL.md +1 -1
- package/skills/insight-dotnet-dump/SKILL.md +1 -1
- package/skills/insight-dotnet-stack/SKILL.md +1 -1
- package/skills/insight-dotnet-trace/SKILL.md +1 -1
- package/skills/insight-frida/SKILL.md +1 -1
- package/skills/insight-ghidra/SKILL.md +14 -1
- package/skills/insight-ghidra/references/persistent-client.mjs +125 -0
- package/skills/insight-ghidra/references/persistent.md +50 -0
- package/skills/insight-ghidra/references/query-semantics.md +12 -0
- package/skills/insight-ghidra/references/recovery.md +36 -0
- package/skills/insight-ghidra/references/research.md +87 -0
- package/skills/insight-ghidra/references/setup.md +1 -1
- package/skills/insight-ghidra/references/sparse-navigation.md +173 -0
- package/skills/insight-ghidra/references/storage.md +35 -7
- package/skills/insight-ilspy/SKILL.md +1 -1
- package/skills/insight-interop/SKILL.md +1 -1
- package/skills/insight-lldb/SKILL.md +1 -1
- package/skills/insight-llvm/SKILL.md +1 -1
- package/skills/insight-managed-heap/SKILL.md +1 -1
- package/skills/insight-managed-metadata/SKILL.md +1 -1
- package/skills/insight-mitmproxy/SKILL.md +1 -1
- package/skills/insight-overview/SKILL.md +1 -1
- package/skills/insight-procmon/SKILL.md +1 -1
- package/skills/insight-re/SKILL.md +4 -1
- package/skills/insight-re/references/persistent-client.mjs +125 -0
- package/skills/insight-re/references/persistent.md +50 -0
- package/skills/insight-re/references/recovery.md +36 -0
- package/skills/insight-re/references/storage.md +35 -7
- package/skills/insight-sevenzip/SKILL.md +1 -1
- package/skills/insight-strace/SKILL.md +1 -1
- package/skills/insight-tshark/SKILL.md +1 -1
- package/skills/insight-windbg/SKILL.md +1 -1
|
@@ -9,7 +9,7 @@ import { promisify } from 'node:util';
|
|
|
9
9
|
import { fail, stable, text } from './model.js';
|
|
10
10
|
import { managedExtensions, extensionJvmArguments } from './extensions.js';
|
|
11
11
|
import { runtimeValues, runtimeSelection } from './runtime-selection.js';
|
|
12
|
-
export const pinnedMcpJar = '
|
|
12
|
+
export const pinnedMcpJar = 'f4a9272e911466ed774c8cf73bc1adf1daef6009d895fda57aa0b28522fcdf32';
|
|
13
13
|
export const packagedMcpJar = fileURLToPath(new URL('../../runtime/GhidraMCP-7.0.0.jar', import.meta.url));
|
|
14
14
|
/** Ghidra has its own cache/settings overrides in addition to Java's home/tmp properties. */
|
|
15
15
|
export const nativeJvmDirectories = (root) => ['-Duser.home=' + root, '-Djava.io.tmpdir=' + root,
|
|
@@ -2,7 +2,7 @@ import { readFile } from 'node:fs/promises';
|
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
3
|
import { object, fail } from './model.js';
|
|
4
4
|
export async function nativeQuery(http, program, operation, parameters, write = false) {
|
|
5
|
-
const base = operation.startsWith('/sdk/') ? 'InsightSdkScript' : operation.startsWith('/emulate_') ? 'InsightEmulation' : 'InsightResearchQueries';
|
|
5
|
+
const base = operation === '/code_references' ? 'InsightCodeReferences' : operation.startsWith('/sdk/') ? 'InsightSdkScript' : operation.startsWith('/emulate_') ? 'InsightEmulation' : 'InsightResearchQueries';
|
|
6
6
|
const name = base + '_' + randomUUID().replaceAll('-', '');
|
|
7
7
|
const source = await readFile(new URL('../../references/native/' + base + '.java', import.meta.url), 'utf8');
|
|
8
8
|
const response = object(await http.call('/run_script_inline', { program, code: source.replace('public class ' + base + ' ', 'public class ' + name + ' '),
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export declare class InsightCommandError extends Error {
|
|
2
|
+
readonly rpc: {
|
|
3
|
+
code: number;
|
|
4
|
+
message: string;
|
|
5
|
+
data?: unknown;
|
|
6
|
+
};
|
|
7
|
+
constructor(rpc: {
|
|
8
|
+
code: number;
|
|
9
|
+
message: string;
|
|
10
|
+
data?: unknown;
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
export declare class InsightTransportError extends Error {
|
|
14
|
+
readonly dispatched: boolean;
|
|
15
|
+
constructor(message: string, dispatched: boolean);
|
|
16
|
+
}
|
|
17
|
+
export interface InsightConnectionOptions {
|
|
18
|
+
executable: string;
|
|
19
|
+
/** For example [installedBin] with executable: process.execPath. No shell is used. */
|
|
20
|
+
arguments?: readonly string[];
|
|
21
|
+
cwd?: string;
|
|
22
|
+
env?: NodeJS.ProcessEnv;
|
|
23
|
+
}
|
|
24
|
+
export interface InsightConnection {
|
|
25
|
+
execute<T = unknown>(argv: readonly string[]): Promise<T>;
|
|
26
|
+
/** Drain admitted requests, then end this transport. Does not stop the owner or Ghidra. */
|
|
27
|
+
close(): Promise<void>;
|
|
28
|
+
}
|
|
29
|
+
/** A thin client of Factory's existing stream, with no reconnect or command replay. */
|
|
30
|
+
export declare function connectInsight(options: InsightConnectionOptions): Promise<InsightConnection>;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { StringDecoder } from 'node:string_decoder';
|
|
3
|
+
export class InsightCommandError extends Error {
|
|
4
|
+
rpc;
|
|
5
|
+
constructor(rpc) {
|
|
6
|
+
super(rpc.message);
|
|
7
|
+
this.rpc = rpc;
|
|
8
|
+
this.name = 'InsightCommandError';
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export class InsightTransportError extends Error {
|
|
12
|
+
dispatched;
|
|
13
|
+
constructor(message, dispatched) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.dispatched = dispatched;
|
|
16
|
+
this.name = 'InsightTransportError';
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/** A thin client of Factory's existing stream, with no reconnect or command replay. */
|
|
20
|
+
export async function connectInsight(options) {
|
|
21
|
+
const child = spawn(options.executable, [...(options.arguments ?? []), '--json-rpc'], { cwd: options.cwd, env: options.env, stdio: ['pipe', 'pipe', 'pipe'], shell: false });
|
|
22
|
+
const maximum = 4 * 1024 ** 2, decoder = new StringDecoder('utf8');
|
|
23
|
+
let buffer = '', bytes = 0, sequence = 0, accepting = true, failed = false, ended = false;
|
|
24
|
+
let closePromise, tail = Promise.resolve();
|
|
25
|
+
const pending = new Map();
|
|
26
|
+
// Drain diagnostics without retaining arbitrary stderr, paths, tokens or unbounded output.
|
|
27
|
+
child.stderr.resume();
|
|
28
|
+
const fail = (message) => {
|
|
29
|
+
if (failed)
|
|
30
|
+
return;
|
|
31
|
+
failed = true;
|
|
32
|
+
accepting = false;
|
|
33
|
+
for (const p of pending.values())
|
|
34
|
+
p.reject(new InsightTransportError(message, true));
|
|
35
|
+
pending.clear();
|
|
36
|
+
child.stdin.destroy();
|
|
37
|
+
child.stdout.destroy();
|
|
38
|
+
};
|
|
39
|
+
child.on('error', () => fail('Persistent CLI process failed.'));
|
|
40
|
+
child.stdout.on('error', () => fail('Persistent CLI output was lost.'));
|
|
41
|
+
child.stderr.on('error', () => fail('Persistent CLI diagnostic stream was lost.'));
|
|
42
|
+
child.stdin.on('error', () => fail('Persistent CLI input was lost; inspect request/status before retrying a changing command.'));
|
|
43
|
+
const exited = new Promise((resolve, reject) => {
|
|
44
|
+
child.once('close', (code, signal) => {
|
|
45
|
+
ended = true;
|
|
46
|
+
if (code !== 0 || signal || pending.size || buffer.trim()) {
|
|
47
|
+
fail('Persistent CLI closed without a complete acknowledged response.');
|
|
48
|
+
reject(new InsightTransportError('Persistent CLI transport ended unexpectedly.', false));
|
|
49
|
+
}
|
|
50
|
+
else if (failed)
|
|
51
|
+
reject(new InsightTransportError('Persistent CLI transport failed.', false));
|
|
52
|
+
else
|
|
53
|
+
resolve();
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
void exited.catch(() => { });
|
|
57
|
+
child.stdout.on('data', (chunk) => {
|
|
58
|
+
if (failed)
|
|
59
|
+
return;
|
|
60
|
+
buffer += decoder.write(chunk);
|
|
61
|
+
bytes += chunk.length;
|
|
62
|
+
if (bytes > maximum) {
|
|
63
|
+
fail('Persistent CLI response exceeded the bounded transport limit.');
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
for (;;) {
|
|
67
|
+
const newline = buffer.indexOf('\n');
|
|
68
|
+
if (newline < 0)
|
|
69
|
+
break;
|
|
70
|
+
const line = buffer.slice(0, newline);
|
|
71
|
+
buffer = buffer.slice(newline + 1);
|
|
72
|
+
bytes = Buffer.byteLength(buffer);
|
|
73
|
+
if (!line.trim())
|
|
74
|
+
continue;
|
|
75
|
+
try {
|
|
76
|
+
const r = JSON.parse(line);
|
|
77
|
+
if (!r || r.jsonrpc !== '2.0' || typeof r.id !== 'number' || !pending.has(r.id) || Object.hasOwn(r, 'result') === Object.hasOwn(r, 'error'))
|
|
78
|
+
throw Error();
|
|
79
|
+
const p = pending.get(r.id);
|
|
80
|
+
if (Object.hasOwn(r, 'error') && (!r.error || typeof r.error !== 'object' || typeof r.error.code !== 'number' || !Number.isInteger(r.error.code) || typeof r.error.message !== 'string'))
|
|
81
|
+
throw Error();
|
|
82
|
+
pending.delete(r.id);
|
|
83
|
+
if (r.error)
|
|
84
|
+
p.reject(new InsightCommandError(r.error));
|
|
85
|
+
else
|
|
86
|
+
p.resolve(r.result);
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
fail('Persistent CLI returned a malformed or uncorrelated response.');
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
await new Promise((resolve, reject) => { child.once('spawn', resolve); child.once('error', () => reject(new InsightTransportError('Could not start persistent CLI.', false))); });
|
|
95
|
+
return {
|
|
96
|
+
execute(argv) {
|
|
97
|
+
if (!Array.isArray(argv) || argv.length > 512 || argv.some(x => typeof x !== 'string' || x.includes('\0')) || Buffer.byteLength(JSON.stringify(argv)) > 1024 ** 2)
|
|
98
|
+
return Promise.reject(new TypeError('argv must be a bounded array of strings without NUL.'));
|
|
99
|
+
if (!accepting || failed || ended)
|
|
100
|
+
return Promise.reject(new InsightTransportError('Persistent CLI connection is closed.', false));
|
|
101
|
+
const args = [...argv];
|
|
102
|
+
const request = tail.then(() => {
|
|
103
|
+
if (failed || ended)
|
|
104
|
+
throw new InsightTransportError('Persistent CLI connection was lost before dispatch.', false);
|
|
105
|
+
const id = ++sequence;
|
|
106
|
+
return new Promise((resolve, reject) => {
|
|
107
|
+
pending.set(id, { resolve, reject });
|
|
108
|
+
try {
|
|
109
|
+
child.stdin.write(JSON.stringify({ jsonrpc: '2.0', id, method: 'cli.execute', params: { argv: args } }) + '\n');
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
fail('Persistent CLI input was lost; inspect request/status before retrying a changing command.');
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
tail = request.catch(() => { });
|
|
117
|
+
return request;
|
|
118
|
+
},
|
|
119
|
+
close() {
|
|
120
|
+
accepting = false;
|
|
121
|
+
return closePromise ??= tail.then(async () => { if (!failed && !ended)
|
|
122
|
+
child.stdin.end(); await exited; });
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
}
|
package/dist/src/queries.js
CHANGED
|
@@ -51,7 +51,7 @@ export function queryCommands(research) {
|
|
|
51
51
|
parameters[p.name] = parsed(p, input);
|
|
52
52
|
}
|
|
53
53
|
if (q.path === '/get_functions')
|
|
54
|
-
return research(c).inspectFunctions(scope(o), parameters, { limit: number(o.limitChars ?? 16000, 32768, 1), offset: number(o.offsetChars ?? 0, 4 * 1024 ** 2), total: number(o.totalChars ?? 64000, 128000, 1), ...(o.expectView === undefined ? {} : { expectView: String(o.expectView) }), ...(o.saveOwnerDirectory === undefined ? {} : { saveDirectory: String(o.saveOwnerDirectory) }) });
|
|
54
|
+
return research(c).inspectFunctions(scope(o), parameters, { limit: number(o.limitChars ?? 16000, 32768, 1), offset: number(o.offsetChars ?? 0, 4 * 1024 ** 2), total: number(o.totalChars ?? 64000, 128000, 1), ...(o.expectView === undefined ? {} : { expectView: String(o.expectView) }), ...(o.saveOwnerDirectory === undefined ? {} : { saveDirectory: String(o.saveOwnerDirectory) }), ...(o.resolveAddress === undefined ? {} : { resolveAddress: String(o.resolveAddress) }), ...(o.maxEntryDistance === undefined ? {} : { maxEntryDistance: number(o.maxEntryDistance, 65536, 1) }), ...(o.maxUnwindBytes === undefined ? {} : { maxUnwindBytes: number(o.maxUnwindBytes, 16777216, 1) }) });
|
|
55
55
|
if (nativeQueries.has(q.path)) {
|
|
56
56
|
if (Number(parameters.timeout_seconds ?? 5) > 30)
|
|
57
57
|
fail('invalid', 'Native query timeout must be at most 30 seconds.');
|
|
@@ -65,6 +65,9 @@ export function queryCommands(research) {
|
|
|
65
65
|
await research(c).read(who, q.path, parameters);
|
|
66
66
|
return { ...who, value: value(qualifyQuery(q.path, queryResult(q.path, parameters, value(result)))) };
|
|
67
67
|
}, { permission: Permission.ReadOnly, options: [...selectedOptions, ...q.params.map(option), ...(q.path === '/get_functions' ? [
|
|
68
|
+
{ flags: '--resolve-address <mode>', description: 'containing-or-preceding or containing-or-pe-runtime; address selectors only' },
|
|
69
|
+
{ flags: '--max-entry-distance <bytes>', description: 'Required with containing-or-preceding: fallback distance 1–65536 within the same initialized executable block' },
|
|
70
|
+
{ flags: '--max-unwind-bytes <bytes>', description: 'PE runtime mode: 1–16777216 actual header/table bytes, default 4194304; fixed 5s cooperative lookup deadline' },
|
|
68
71
|
{ flags: '--limit-chars <count>', description: 'Per-function Unicode window, 1–32768', defaultValue: 16000 },
|
|
69
72
|
{ flags: '--offset-chars <count>', description: 'Per-function Unicode offset', defaultValue: 0 },
|
|
70
73
|
{ flags: '--total-chars <count>', description: 'Total returned decompiler characters, 1–128000', defaultValue: 64000 },
|
package/dist/src/query-policy.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { fail, object } from './model.js';
|
|
2
2
|
/** These operations have facade-owned, bounded native implementations. */
|
|
3
|
-
export const nativeQueries = new Set(['/research_view', '/decode_instructions', '/find_dead_code', '/detect_crypto_constants', '/get_valid_data_types',
|
|
3
|
+
export const nativeQueries = new Set(['/code_references', '/navigation_diagnostic', '/research_view', '/decode_instructions', '/find_dead_code', '/detect_crypto_constants', '/get_valid_data_types',
|
|
4
4
|
'/validate_data_type', '/validate_function_prototype', '/get_entry_points', '/list_globals',
|
|
5
5
|
'/list_strings', '/search_strings', '/find_data_types', '/search_byte_patterns', '/search_instructions',
|
|
6
6
|
'/emulate_function', '/emulate_hash_batch']);
|
|
@@ -25,6 +25,12 @@ const heuristics = Object.fromEntries([
|
|
|
25
25
|
'Scores and confidence labels are not calibrated probabilities; the agent decides what to accept.',
|
|
26
26
|
] }]));
|
|
27
27
|
export function queryDescription(path, fallback) {
|
|
28
|
+
if (path === '/force_decompile')
|
|
29
|
+
return 'Refresh decompiler output for an existing function entry; does not repair Listing or create functions';
|
|
30
|
+
if (path === '/disassemble_function')
|
|
31
|
+
return 'Read stored Listing instructions; for sparse/undefined code use functions decode';
|
|
32
|
+
if (path === '/get_functions')
|
|
33
|
+
return 'Inspect 1–20 functions; compact code/call edges: --fields decompiled_code,callees';
|
|
28
34
|
if (path === '/find_dead_code')
|
|
29
35
|
return 'Find blocks unreachable in the bounded static CFG; unresolved flow is reported';
|
|
30
36
|
if (path === '/detect_crypto_constants')
|
|
@@ -39,6 +45,8 @@ export function queryDescription(path, fallback) {
|
|
|
39
45
|
return 'Read external-entry metadata; report name/image-base candidates separately';
|
|
40
46
|
if (path === '/list_globals')
|
|
41
47
|
return 'Read structured global records with bounded symbol/reference scanning';
|
|
48
|
+
if (path === '/list_strings' || path === '/search_strings')
|
|
49
|
+
return 'Search defined string data only, with bounded visited-data scan; undefined literals: strings anchors';
|
|
42
50
|
if (path.startsWith('/emulate_'))
|
|
43
51
|
return 'Bounded isolated x86/x64 emulation with explicit ABI and termination reason';
|
|
44
52
|
return heuristics[path] ? fallback + ' (qualified ' + heuristics[path].kind + '; not ground truth)' : fallback;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { object, fail } from './model.js';
|
|
2
|
+
/** Strip transport versioning from the existing logical observation and its identity digest. */
|
|
3
|
+
export function researchView(raw) {
|
|
4
|
+
const r = object(raw);
|
|
5
|
+
if (r.protocol !== 'insight-research-view-v1' || typeof r.modification !== 'string' || !/^\d{1,20}$/.test(r.modification) ||
|
|
6
|
+
!['language', 'compiler'].every(k => typeof r[k] === 'string' && r[k].length > 0 && r[k].length <= 256 && !r[k].includes('\0')))
|
|
7
|
+
fail('partial', 'Native research-view observation is malformed or unsupported.');
|
|
8
|
+
return { modification: r.modification, language: r.language, compiler: r.compiler };
|
|
9
|
+
}
|
package/dist/src/research.d.ts
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import { type FunctionInspectionOptions } from './function-inspection.js';
|
|
2
|
+
import { type AnchorRequest } from './string-anchors.js';
|
|
2
3
|
import type { Artifact } from './artifacts.js';
|
|
3
4
|
import type { GhidraContext, Scope, ScalarChange, Publication, DebuggerState, GhidraLoadSettings } from './workspace.js';
|
|
4
5
|
import { Workspace } from './workspace.js';
|
|
5
6
|
import type { ToolMode } from './contexts.js';
|
|
6
7
|
import type { ScriptPage } from './scripts.js';
|
|
8
|
+
import { type ReferenceRequest } from './code-references.js';
|
|
7
9
|
export interface ResearchBackend {
|
|
10
|
+
inlineScript?(scope: Scope, parameters: Record<string, unknown>, requestId: string): Promise<{
|
|
11
|
+
response: Record<string, unknown>;
|
|
12
|
+
safe: boolean;
|
|
13
|
+
}>;
|
|
8
14
|
observation?(scope: Scope): Promise<unknown>;
|
|
15
|
+
inspectObservation?(scope: Scope): Promise<unknown>;
|
|
9
16
|
sdkFile?(operation: string, p: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
10
17
|
archivePayloads?(scope: Scope, p: Record<string, unknown>): Promise<unknown>;
|
|
11
18
|
debuggerStart?(scope: Scope): Promise<void>;
|
|
@@ -166,6 +173,36 @@ export declare class Research {
|
|
|
166
173
|
maximumCharacters: number;
|
|
167
174
|
returnedCharacters: number;
|
|
168
175
|
};
|
|
176
|
+
boundaryLookup?: Record<string, unknown> | undefined;
|
|
177
|
+
};
|
|
178
|
+
session: string;
|
|
179
|
+
target: string;
|
|
180
|
+
}>;
|
|
181
|
+
stringAnchors(selected: Scope, request: AnchorRequest): Promise<{
|
|
182
|
+
value: {
|
|
183
|
+
view: {
|
|
184
|
+
identity: string;
|
|
185
|
+
};
|
|
186
|
+
cursor: string | null;
|
|
187
|
+
};
|
|
188
|
+
session: string;
|
|
189
|
+
target: string;
|
|
190
|
+
}>;
|
|
191
|
+
codeReferences(selected: Scope, request: ReferenceRequest): Promise<{
|
|
192
|
+
value: {
|
|
193
|
+
view: {
|
|
194
|
+
identity: string;
|
|
195
|
+
};
|
|
196
|
+
cursor: string | null;
|
|
197
|
+
};
|
|
198
|
+
session: string;
|
|
199
|
+
target: string;
|
|
200
|
+
}>;
|
|
201
|
+
navigation(selected: Scope, maximum: number): Promise<{
|
|
202
|
+
value: {
|
|
203
|
+
initialAnalysis: string;
|
|
204
|
+
priorAnalysis: string;
|
|
205
|
+
provenance: string;
|
|
169
206
|
};
|
|
170
207
|
session: string;
|
|
171
208
|
target: string;
|
|
@@ -210,6 +247,7 @@ export declare class Research {
|
|
|
210
247
|
expectedShared: string | null;
|
|
211
248
|
}): Promise<unknown>;
|
|
212
249
|
private sharedOrder;
|
|
250
|
+
reconcileRequests(selected: Scope, backend: ResearchBackend): Promise<void>;
|
|
213
251
|
private reconcile;
|
|
214
252
|
merge(selected: Scope, callerId?: string): Promise<unknown>;
|
|
215
253
|
refresh(selected: Scope, callerId?: string): Promise<unknown>;
|
package/dist/src/research.js
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { textWindow } from './text-window.js';
|
|
2
2
|
import { functionResult, functionFailure } from './function-results.js';
|
|
3
3
|
import { inspectFunctions } from './function-inspection.js';
|
|
4
|
+
import { entryRequest } from './function-entries.js';
|
|
5
|
+
import { inspectAnchors } from './string-anchors.js';
|
|
4
6
|
import { fail, id, object, text, digest } from './model.js';
|
|
5
7
|
import { fieldKey, scalarMerge, scalarRefresh } from './merge.js';
|
|
6
8
|
import { verifyMutation } from './mutation-effects.js';
|
|
7
9
|
import { transferResult } from './cross-results.js';
|
|
8
10
|
import { CliError } from '@eyeauras/cli-factory';
|
|
11
|
+
import { timed } from './internal-timing.js';
|
|
12
|
+
import { navigationResult } from './context-diagnostics.js';
|
|
13
|
+
import { inspectCodeReferences } from './code-references.js';
|
|
9
14
|
export function address(value) {
|
|
10
15
|
const input = text(value, 128);
|
|
11
16
|
const match = /^(?:([A-Za-z0-9_]+):)?(?:0x)?([a-fA-F0-9]+)$/.exec(input);
|
|
@@ -66,7 +71,7 @@ export class Research {
|
|
|
66
71
|
}
|
|
67
72
|
async ordered(selected, action) {
|
|
68
73
|
const key = this.workspace.context(selected).id, previous = this.tasks.get(key);
|
|
69
|
-
const task = (
|
|
74
|
+
const task = timed('facade.queue', 'research', async () => { await previous?.catch(() => { }); }).then(action);
|
|
70
75
|
this.tasks.set(key, task);
|
|
71
76
|
try {
|
|
72
77
|
return await task;
|
|
@@ -77,7 +82,7 @@ export class Research {
|
|
|
77
82
|
}
|
|
78
83
|
}
|
|
79
84
|
async boundary(selected, action) {
|
|
80
|
-
const key = 'command:' + this.workspace.context(selected).id, previous = this.tasks.get(key), task = (
|
|
85
|
+
const key = 'command:' + this.workspace.context(selected).id, previous = this.tasks.get(key), task = timed('facade.queue', 'command', async () => { await previous?.catch(() => { }); }).then(action);
|
|
81
86
|
this.tasks.set(key, task);
|
|
82
87
|
try {
|
|
83
88
|
return await task;
|
|
@@ -147,10 +152,14 @@ export class Research {
|
|
|
147
152
|
return opened(request.result);
|
|
148
153
|
if (reserved.replay && request.state !== 'accepted')
|
|
149
154
|
throw new CliError('This open retains an unproved outcome; inspect the retained context before continuing.', { code: 'coordinator.unknown', result: retained(request.state), next });
|
|
150
|
-
|
|
155
|
+
const unresolved = this.workspace.unresolved(reserved.context).filter(other => other.id !== request.id);
|
|
156
|
+
if (unresolved.length) {
|
|
151
157
|
const result = { session: reserved.context.session, target, state: 'blocked' };
|
|
152
158
|
this.workspace.recordRequest(request.id, 'failed', result);
|
|
153
|
-
|
|
159
|
+
const close = unresolved.find(other => other.operation === 'ghidra.close');
|
|
160
|
+
throw new CliError('This context retains an unproved effect; continuation did not start a second operation.', {
|
|
161
|
+
code: 'coordinator.blocked', result, next: close ? [['ghidra', 'close', '--session', reserved.context.session, '--target', target, '--request-id', close.callerId ?? close.id]] : next
|
|
162
|
+
});
|
|
154
163
|
}
|
|
155
164
|
this.workspace.startRequest(request.id);
|
|
156
165
|
try {
|
|
@@ -292,13 +301,48 @@ export class Research {
|
|
|
292
301
|
});
|
|
293
302
|
}
|
|
294
303
|
async inspectFunctions(selected, parameters, options) {
|
|
304
|
+
const lookup = entryRequest(parameters, options);
|
|
295
305
|
return this.ordered(selected, async () => {
|
|
296
306
|
const backend = await this.backend();
|
|
297
307
|
await backend.open(selected);
|
|
298
308
|
const context = this.workspace.context(selected);
|
|
299
|
-
const
|
|
309
|
+
const observe = backend.inspectObservation ? () => backend.inspectObservation(selected) : backend.observation ? () => backend.observation(selected) : undefined;
|
|
310
|
+
if (lookup && !observe)
|
|
311
|
+
fail('unsupported', 'Address resolution requires guarded native observations.');
|
|
312
|
+
const observation = await observe?.();
|
|
300
313
|
const view = { revision: revision(context.baseRevision), identity: digest({ target: selected.target, context: context.id, generation: context.generation, snapshot: context.workingSnapshot, epoch: context.viewEpoch ?? 0, observation }) };
|
|
301
|
-
return inspectFunctions(backend, selected, parameters, options, view, observation);
|
|
314
|
+
return inspectFunctions(backend, selected, parameters, options, view, observation, observe);
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
async stringAnchors(selected, request) {
|
|
318
|
+
return this.ordered(selected, async () => {
|
|
319
|
+
const backend = await this.backend();
|
|
320
|
+
await backend.open(selected);
|
|
321
|
+
const observe = backend.inspectObservation ? () => backend.inspectObservation(selected) : backend.observation ? () => backend.observation(selected) : undefined;
|
|
322
|
+
if (!observe)
|
|
323
|
+
fail('unsupported', 'String anchors require guarded native observations.');
|
|
324
|
+
const context = this.workspace.context(selected), observation = await observe();
|
|
325
|
+
const view = digest({ target: selected.target, context: context.id, generation: context.generation, snapshot: context.workingSnapshot, epoch: context.viewEpoch ?? 0, observation });
|
|
326
|
+
return inspectAnchors(backend, selected, request, view, observation, observe);
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
async codeReferences(selected, request) {
|
|
330
|
+
return this.ordered(selected, async () => {
|
|
331
|
+
const backend = await this.backend();
|
|
332
|
+
await backend.open(selected);
|
|
333
|
+
if (!backend.observation)
|
|
334
|
+
fail('unsupported', 'Code-reference continuation requires native view observation.');
|
|
335
|
+
const context = this.workspace.context(selected), observation = await backend.observation(selected);
|
|
336
|
+
const view = digest({ target: selected.target, context: context.id, generation: context.generation, snapshot: context.workingSnapshot, epoch: context.viewEpoch ?? 0, observation });
|
|
337
|
+
return inspectCodeReferences(backend, selected, request, view, observation);
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
async navigation(selected, maximum) {
|
|
341
|
+
return this.ordered(selected, async () => {
|
|
342
|
+
const backend = await this.backend();
|
|
343
|
+
await backend.open(selected);
|
|
344
|
+
const value = navigationResult(await backend.call(selected, '/navigation_diagnostic', { max_scan: maximum }), maximum);
|
|
345
|
+
return { ...selected, value: { ...value, initialAnalysis: this.workspace.model(selected.target).settings.autoAnalyze ? 'requested' : 'none', priorAnalysis: 'unknown', provenance: 'Initial facade policy only; imported/saved metadata may have an unknown analysis history.' } };
|
|
302
346
|
});
|
|
303
347
|
}
|
|
304
348
|
async xrefs(selected, at, limit, offset) {
|
|
@@ -428,6 +472,9 @@ export class Research {
|
|
|
428
472
|
this.tasks.delete(key);
|
|
429
473
|
}
|
|
430
474
|
}
|
|
475
|
+
async reconcileRequests(selected, backend) {
|
|
476
|
+
return this.sharedOrder(selected.target, () => this.reconcile(selected, backend));
|
|
477
|
+
}
|
|
431
478
|
async reconcile(selected, backend) {
|
|
432
479
|
const active = this.workspace.activePublication(selected.target);
|
|
433
480
|
if (active) {
|
|
@@ -541,7 +588,7 @@ export class Research {
|
|
|
541
588
|
if (!await backend.closed(selected))
|
|
542
589
|
continue;
|
|
543
590
|
const current = this.workspace.context(selected);
|
|
544
|
-
this.workspace.completeSavedRequest(selected, request.id, current.generation, {}, { ...selected, state: 'closed', pendingChanges: count(current) });
|
|
591
|
+
this.workspace.completeSavedRequest(selected, request.id, current.generation, { state: 'stopped' }, { ...selected, state: 'closed', pendingChanges: count(current) });
|
|
545
592
|
}
|
|
546
593
|
catch { /* A stopped metadata flag alone never proves process release. */ }
|
|
547
594
|
continue;
|
|
@@ -794,6 +841,10 @@ export class Research {
|
|
|
794
841
|
if (reserved.replay && request.state !== 'accepted') {
|
|
795
842
|
if (request.state === 'applied')
|
|
796
843
|
return request.result;
|
|
844
|
+
if (operation === '/run_script_inline' && request.state === 'failed' && object(request.result ?? {}).reason === 'script_not_executed') {
|
|
845
|
+
const result = object(request.result), execution = object(result.execution);
|
|
846
|
+
fail('script.' + String(execution.stage), String(object(result.diagnostic).message), result);
|
|
847
|
+
}
|
|
797
848
|
if (operation.startsWith('/sdk/') && request.state === 'failed' && object(request.result ?? {}).state === 'rejected')
|
|
798
849
|
fail('rejected', 'This SDK transaction was rolled back and is not repeated.', request.result);
|
|
799
850
|
fail('unknown', 'This saved-state operation retains an unresolved outcome; it is not re-executed.', request.result);
|
|
@@ -807,7 +858,24 @@ export class Research {
|
|
|
807
858
|
savedVerification: !!verifySaved });
|
|
808
859
|
let savedSnapshot, phase = 'operation';
|
|
809
860
|
try {
|
|
810
|
-
|
|
861
|
+
let raw;
|
|
862
|
+
if (operation === '/run_script_inline' && backend.inlineScript) {
|
|
863
|
+
const attempt = await backend.inlineScript(selected, parameters, request.id);
|
|
864
|
+
raw = attempt.response;
|
|
865
|
+
if (attempt.response.success === false) {
|
|
866
|
+
const result = { ...selected, requestId: request.callerId ?? request.id, state: attempt.safe ? 'rejected' : 'unknown',
|
|
867
|
+
contextState: attempt.safe ? 'ready' : 'needs_recovery', reason: attempt.safe ? 'script_not_executed' : 'script_execution_not_proven_complete',
|
|
868
|
+
execution: attempt.response.execution, diagnostic: attempt.response.diagnostic, diagnostics: attempt.response.diagnostics,
|
|
869
|
+
diagnosticsTruncated: attempt.response.diagnosticsTruncated, compilerOutput: attempt.response.compilerOutput,
|
|
870
|
+
output: attempt.response.console_output, outputTruncated: attempt.response.outputTruncated };
|
|
871
|
+
if (attempt.safe)
|
|
872
|
+
this.workspace.recordRequest(request.id, 'failed', result);
|
|
873
|
+
fail('script.' + String(object(attempt.response.execution).stage), String(object(attempt.response.diagnostic).message), result);
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
else
|
|
877
|
+
raw = await backend.call(selected, operation, parameters);
|
|
878
|
+
const response = raw && typeof raw === 'object' && !Array.isArray(raw) ? object(raw) : { output: raw };
|
|
811
879
|
if (['rejected', 'partial', 'failed', 'error'].includes(String(response.status)) || response.success === false)
|
|
812
880
|
fail('partial', 'Program operation reported a rejected or incomplete effect.');
|
|
813
881
|
if (!verifySaved && response.success !== true && response.status !== 'success')
|
|
@@ -825,6 +893,8 @@ export class Research {
|
|
|
825
893
|
return result;
|
|
826
894
|
}
|
|
827
895
|
catch (error) {
|
|
896
|
+
if (operation === '/run_script_inline' && this.workspace.request(request.id).state === 'failed' && object(this.workspace.request(request.id).result ?? {}).reason === 'script_not_executed')
|
|
897
|
+
throw error;
|
|
828
898
|
if (operation.startsWith('/sdk/') && String(error.code).endsWith('.rejected')) {
|
|
829
899
|
this.workspace.recordRequest(request.id, 'failed', { ...selected, state: 'rejected', reason: 'native_transaction_rolled_back' });
|
|
830
900
|
throw error;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type CommandContext } from '@eyeauras/cli-factory';
|
|
2
|
+
import { type Research, type ResearchBackend } from './research.js';
|
|
3
|
+
import type { Scope } from './workspace.js';
|
|
4
|
+
export declare const anchorAlgorithm = "raw-string-anchors-v1";
|
|
5
|
+
export declare const anchorEncodings: readonly ["ascii", "utf16le"];
|
|
6
|
+
type Encoding = typeof anchorEncodings[number];
|
|
7
|
+
interface Frontier {
|
|
8
|
+
address: string;
|
|
9
|
+
encoding: Encoding;
|
|
10
|
+
}
|
|
11
|
+
interface Query {
|
|
12
|
+
text: string;
|
|
13
|
+
encodings: Encoding[];
|
|
14
|
+
start: string;
|
|
15
|
+
end: string;
|
|
16
|
+
cap: number;
|
|
17
|
+
}
|
|
18
|
+
interface Budgets {
|
|
19
|
+
max_bytes: number;
|
|
20
|
+
max_candidates: number;
|
|
21
|
+
timeout_seconds: number;
|
|
22
|
+
limit: number;
|
|
23
|
+
}
|
|
24
|
+
export interface AnchorRequest {
|
|
25
|
+
query: Query;
|
|
26
|
+
from: Frontier;
|
|
27
|
+
budgets: Budgets;
|
|
28
|
+
expectedView?: string;
|
|
29
|
+
}
|
|
30
|
+
export declare function anchorRequest(o: Record<string, unknown>, who: Scope): AnchorRequest;
|
|
31
|
+
export declare function anchorResult(raw: unknown, r: AnchorRequest, modification: string): Record<string, unknown>;
|
|
32
|
+
export declare function inspectAnchors(backend: ResearchBackend, who: Scope, r: AnchorRequest, view: string, observation: unknown, observe: () => Promise<unknown>): Promise<{
|
|
33
|
+
value: {
|
|
34
|
+
view: {
|
|
35
|
+
identity: string;
|
|
36
|
+
};
|
|
37
|
+
cursor: string | null;
|
|
38
|
+
};
|
|
39
|
+
session: string;
|
|
40
|
+
target: string;
|
|
41
|
+
}>;
|
|
42
|
+
export declare function stringAnchorCommands(research: (c: CommandContext) => Research): import("@eyeauras/cli-factory").CommandDefinition[];
|
|
43
|
+
export {};
|