deepspider 0.3.1 → 0.3.2
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/.env.example +3 -0
- package/README.md +13 -13
- package/package.json +6 -6
- package/src/agent/core/PanelBridge.js +28 -76
- package/src/agent/core/StreamHandler.js +139 -14
- package/src/agent/index.js +51 -12
- package/src/agent/logger.js +183 -8
- package/src/agent/middleware/report.js +41 -15
- package/src/agent/middleware/subagent.js +233 -0
- package/src/agent/middleware/toolGuard.js +77 -0
- package/src/agent/middleware/validationWorkflow.js +171 -0
- package/src/agent/prompts/system.js +181 -59
- package/src/agent/run.js +41 -6
- package/src/agent/skills/crawler/SKILL.md +64 -3
- package/src/agent/skills/crawler/evolved.md +9 -1
- package/src/agent/skills/dynamic-analysis/SKILL.md +74 -7
- package/src/agent/skills/env/SKILL.md +75 -0
- package/src/agent/skills/sandbox/SKILL.md +35 -0
- package/src/agent/skills/static-analysis/SKILL.md +98 -2
- package/src/agent/subagents/anti-detect.js +10 -20
- package/src/agent/subagents/captcha.js +7 -19
- package/src/agent/subagents/crawler.js +25 -37
- package/src/agent/subagents/factory.js +109 -9
- package/src/agent/subagents/index.js +4 -13
- package/src/agent/subagents/js2python.js +7 -19
- package/src/agent/subagents/reverse.js +180 -0
- package/src/agent/tools/analysis.js +84 -1
- package/src/agent/tools/anti-detect.js +5 -2
- package/src/agent/tools/browser.js +160 -0
- package/src/agent/tools/capture.js +24 -3
- package/src/agent/tools/correlate.js +129 -15
- package/src/agent/tools/crawler.js +2 -1
- package/src/agent/tools/crawlerGenerator.js +90 -0
- package/src/agent/tools/debug.js +43 -6
- package/src/agent/tools/evolve.js +5 -2
- package/src/agent/tools/extractor.js +5 -1
- package/src/agent/tools/file.js +14 -5
- package/src/agent/tools/generateHook.js +66 -0
- package/src/agent/tools/hookManager.js +19 -9
- package/src/agent/tools/index.js +33 -20
- package/src/agent/tools/nodejs.js +41 -6
- package/src/agent/tools/sandbox.js +21 -1
- package/src/agent/tools/scratchpad.js +70 -0
- package/src/agent/tools/tracing.js +26 -0
- package/src/agent/tools/verifyAlgorithm.js +117 -0
- package/src/browser/EnvBridge.js +27 -13
- package/src/browser/client.js +124 -18
- package/src/browser/collector.js +101 -22
- package/src/browser/defaultHooks.js +3 -1
- package/src/browser/hooks/index.js +5 -0
- package/src/browser/interceptors/AntiDebugInterceptor.js +132 -0
- package/src/browser/interceptors/NetworkInterceptor.js +76 -12
- package/src/browser/interceptors/ScriptInterceptor.js +32 -7
- package/src/browser/interceptors/index.js +1 -0
- package/src/browser/ui/analysisPanel.js +469 -464
- package/src/cli/commands/config.js +11 -3
- package/src/config/paths.js +9 -1
- package/src/config/settings.js +7 -1
- package/src/core/PatchGenerator.js +24 -4
- package/src/core/Sandbox.js +140 -3
- package/src/env/EnvCodeGenerator.js +60 -88
- package/src/env/modules/bom/history.js +6 -0
- package/src/env/modules/bom/location.js +6 -0
- package/src/env/modules/bom/navigator.js +13 -0
- package/src/env/modules/bom/screen.js +6 -0
- package/src/env/modules/bom/storage.js +7 -0
- package/src/env/modules/dom/document.js +14 -0
- package/src/env/modules/dom/event.js +4 -0
- package/src/env/modules/index.js +27 -10
- package/src/env/modules/webapi/fetch.js +4 -0
- package/src/env/modules/webapi/url.js +4 -0
- package/src/env/modules/webapi/xhr.js +8 -0
- package/src/store/DataStore.js +125 -42
- package/src/store/Store.js +2 -1
- package/src/agent/subagents/dynamic.js +0 -64
- package/src/agent/subagents/env-agent.js +0 -82
- package/src/agent/subagents/sandbox.js +0 -55
- package/src/agent/subagents/static.js +0 -66
package/src/agent/tools/index.js
CHANGED
|
@@ -11,9 +11,9 @@ export { patchTools, generatePatch, matchModule } from './patch.js';
|
|
|
11
11
|
export { envTools, listEnvModules, loadEnvModule, loadAllEnvModules } from './env.js';
|
|
12
12
|
export { profileTools, listProfiles, loadProfile, generateProfileCode } from './profile.js';
|
|
13
13
|
export { runtimeTools, launchBrowser, navigateTo, browserClose, addInitScript, clearCookies } from './runtime.js';
|
|
14
|
-
export { debugTools, setBreakpoint, setXHRBreakpoint, getCallStack, getFrameVariables, evaluateAtBreakpoint, resumeExecution, stepOver } from './debug.js';
|
|
14
|
+
export { debugTools, setBreakpoint, setXHRBreakpoint, getCallStack, getFrameVariables, evaluateAtBreakpoint, resumeExecution, stepOver, getAgentLogs } from './debug.js';
|
|
15
15
|
export { captureTools, collectEnv, collectProperty, autoFixEnv, getHookLogs } from './capture.js';
|
|
16
|
-
export { browserTools, clickElement, fillInput, waitForSelector } from './browser.js';
|
|
16
|
+
export { browserTools, clickElement, fillInput, waitForSelector, takeScreenshot, reloadPage, goBack, goForward, scrollPage, pressKey, hoverElement, getPageInfo, getPageSource, getElementHtml, getCookies, getInteractiveElements } from './browser.js';
|
|
17
17
|
export { reportTools, saveAnalysisReport } from './report.js';
|
|
18
18
|
export { webcrackTools, unpackBundle, analyzeBundle } from './webcrack.js';
|
|
19
19
|
export { preprocessTools, preprocessCode } from './preprocess.js';
|
|
@@ -24,17 +24,22 @@ export { asyncTools, generatePromiseHook, generateTimerHook } from './async.js';
|
|
|
24
24
|
export { antiDebugTools, generateAntiDebugger, generateAntiConsoleDetect, generateAntiCDP, generateFullAntiDebug } from './antidebug.js';
|
|
25
25
|
export { verifyTools, verifyMD5, verifySHA256, verifyHMAC, verifyAES, identifyEncryption } from './verify.js';
|
|
26
26
|
export { cryptoHookTools, generateCryptoJSHook, generateRSAHook } from './cryptohook.js';
|
|
27
|
-
|
|
27
|
+
// 合并工具(reverse-agent 使用)
|
|
28
|
+
export { generateHookTools, generateHook } from './generateHook.js';
|
|
29
|
+
export { verifyAlgorithmTools, verifyAlgorithm } from './verifyAlgorithm.js';
|
|
30
|
+
export { correlateTools, analyzeCorrelation, locateCryptoSource, analyzeHeaderEncryption, analyzeCookieEncryption, analyzeResponseDecryption, analyzeRequestParams } from './correlate.js';
|
|
28
31
|
export { extractorTools, listFunctions, getFunctionCode } from './extractor.js';
|
|
29
|
-
export { tracingTools, getSiteList, searchInResponses, getRequestDetail, getRequestList, getScriptList, getScriptSource, searchInScripts, clearSiteData, clearAllData } from './tracing.js';
|
|
32
|
+
export { tracingTools, getSiteList, searchInResponses, getRequestDetail, getRequestList, getRequestInitiator, getScriptList, getScriptSource, searchInScripts, clearSiteData, clearAllData } from './tracing.js';
|
|
30
33
|
export { analysisTools, getPendingAnalysis, getPendingChat, sendPanelMessage, startSelector } from './analysis.js';
|
|
31
34
|
export { fileTools, artifactSave, artifactLoad, artifactEdit, artifactGlob, artifactGrep } from './file.js';
|
|
32
35
|
export { evolveTools, evolveSkill } from './evolve.js';
|
|
33
36
|
export { captchaTools } from './captcha.js';
|
|
34
37
|
export { antiDetectTools } from './anti-detect.js';
|
|
35
38
|
export { crawlerTools } from './crawler.js';
|
|
39
|
+
export { crawlerGeneratorTools, generateCrawlerWithConfirm, delegateCrawlerGeneration } from './crawlerGenerator.js';
|
|
36
40
|
export { nodejsTools, runNodeCode } from './nodejs.js';
|
|
37
41
|
export { hookManagerTools, listHooks, enableHook, disableHook, injectHook, setHookConfig } from './hookManager.js';
|
|
42
|
+
export { scratchpadTools, saveMemo, loadMemo, listMemo } from './scratchpad.js';
|
|
38
43
|
// pythonTools 只在 js2python 子代理中使用,不导出到主工具集
|
|
39
44
|
|
|
40
45
|
// 所有工具
|
|
@@ -62,15 +67,17 @@ import { verifyTools } from './verify.js';
|
|
|
62
67
|
import { cryptoHookTools } from './cryptohook.js';
|
|
63
68
|
import { correlateTools } from './correlate.js';
|
|
64
69
|
import { extractorTools } from './extractor.js';
|
|
65
|
-
import { tracingTools } from './tracing.js';
|
|
70
|
+
import { tracingTools, getSiteList, getRequestList, searchInResponses, getRequestDetail, getRequestInitiator } from './tracing.js';
|
|
66
71
|
import { analysisTools } from './analysis.js';
|
|
67
72
|
import { fileTools } from './file.js';
|
|
68
73
|
import { evolveTools } from './evolve.js';
|
|
69
74
|
import { captchaTools } from './captcha.js';
|
|
70
75
|
import { antiDetectTools } from './anti-detect.js';
|
|
71
76
|
import { crawlerTools } from './crawler.js';
|
|
77
|
+
import { crawlerGeneratorTools } from './crawlerGenerator.js';
|
|
72
78
|
import { nodejsTools } from './nodejs.js';
|
|
73
79
|
import { hookManagerTools } from './hookManager.js';
|
|
80
|
+
import { scratchpadTools } from './scratchpad.js';
|
|
74
81
|
|
|
75
82
|
export const allTools = [
|
|
76
83
|
...sandboxTools,
|
|
@@ -104,34 +111,40 @@ export const allTools = [
|
|
|
104
111
|
...captchaTools,
|
|
105
112
|
...antiDetectTools,
|
|
106
113
|
...crawlerTools,
|
|
114
|
+
...crawlerGeneratorTools,
|
|
107
115
|
...nodejsTools,
|
|
108
116
|
...hookManagerTools,
|
|
117
|
+
...scratchpadTools,
|
|
109
118
|
];
|
|
110
119
|
|
|
111
120
|
/**
|
|
112
121
|
* 主 Agent 核心工具
|
|
113
|
-
*
|
|
114
|
-
*
|
|
122
|
+
* 职责:浏览器生命周期、简单页面交互、数据查询、委托调度
|
|
123
|
+
*
|
|
124
|
+
* 以下工具有意不包含在主 agent 中,由专属子代理持有:
|
|
125
|
+
* - hookManagerTools (inject_hook 等) → reverse-agent
|
|
126
|
+
* - captureTools (collect_env, get_hook_logs 等) → reverse-agent
|
|
127
|
+
* - sandboxTools → reverse-agent
|
|
128
|
+
* - debugTools → reverse-agent
|
|
129
|
+
* - 静态分析工具 → reverse-agent
|
|
115
130
|
*/
|
|
116
131
|
export const coreTools = [
|
|
117
|
-
//
|
|
132
|
+
// 浏览器运行时(生命周期管理)
|
|
118
133
|
...runtimeTools,
|
|
119
|
-
//
|
|
120
|
-
...browserTools,
|
|
121
|
-
// 浏览器分析交互
|
|
134
|
+
// 浏览器分析面板交互
|
|
122
135
|
...analysisTools,
|
|
123
|
-
//
|
|
124
|
-
|
|
125
|
-
//
|
|
126
|
-
...
|
|
127
|
-
// Hook 日志
|
|
128
|
-
...captureTools,
|
|
136
|
+
// 数据查询(仅调度所需的最小集:列表、搜索、详情、initiator)
|
|
137
|
+
getSiteList, getRequestList, searchInResponses, getRequestDetail, getRequestInitiator,
|
|
138
|
+
// 报告生成
|
|
139
|
+
...reportTools,
|
|
129
140
|
// 文件操作
|
|
130
141
|
...fileTools,
|
|
131
142
|
// 经验进化
|
|
132
143
|
...evolveTools,
|
|
133
|
-
// Node.js
|
|
144
|
+
// Node.js 执行(委托前快速验证假设)- 已添加网络请求防护
|
|
134
145
|
...nodejsTools,
|
|
135
|
-
//
|
|
136
|
-
...
|
|
146
|
+
// 工作记忆
|
|
147
|
+
...scratchpadTools,
|
|
148
|
+
// 爬虫代码生成(带 HITL 确认)
|
|
149
|
+
...crawlerGeneratorTools,
|
|
137
150
|
];
|
|
@@ -17,10 +17,19 @@ const PROJECT_ROOT = join(__dirname, '../../..');
|
|
|
17
17
|
// 输出大小限制
|
|
18
18
|
const MAX_OUTPUT_SIZE = 100000;
|
|
19
19
|
|
|
20
|
+
// 超时上限(防止 LLM 传入过大值)
|
|
21
|
+
const MAX_TIMEOUT = 30000;
|
|
22
|
+
|
|
23
|
+
// 连续超时计数器
|
|
24
|
+
let consecutiveTimeouts = 0;
|
|
25
|
+
const MAX_CONSECUTIVE_TIMEOUTS = 3;
|
|
26
|
+
|
|
20
27
|
/**
|
|
21
28
|
* 执行 Node.js 代码
|
|
22
29
|
*/
|
|
23
30
|
async function executeNode(code, timeout = 10000) {
|
|
31
|
+
const effectiveTimeout = Math.min(timeout, MAX_TIMEOUT);
|
|
32
|
+
|
|
24
33
|
return new Promise((resolve) => {
|
|
25
34
|
const proc = spawn('node', ['-e', code], {
|
|
26
35
|
env: { ...process.env },
|
|
@@ -30,12 +39,17 @@ async function executeNode(code, timeout = 10000) {
|
|
|
30
39
|
let stdout = '';
|
|
31
40
|
let stderr = '';
|
|
32
41
|
let killed = false;
|
|
42
|
+
let killTimer = null;
|
|
33
43
|
|
|
34
|
-
//
|
|
44
|
+
// SIGTERM 超时
|
|
35
45
|
const timer = setTimeout(() => {
|
|
36
46
|
killed = true;
|
|
37
47
|
proc.kill('SIGTERM');
|
|
38
|
-
|
|
48
|
+
// SIGKILL 兜底:环境检测死循环可能忽略 SIGTERM
|
|
49
|
+
killTimer = setTimeout(() => {
|
|
50
|
+
try { proc.kill('SIGKILL'); } catch { /* already dead */ }
|
|
51
|
+
}, 2000);
|
|
52
|
+
}, effectiveTimeout);
|
|
39
53
|
|
|
40
54
|
proc.stdout.on('data', (data) => {
|
|
41
55
|
if (stdout.length < MAX_OUTPUT_SIZE) {
|
|
@@ -51,21 +65,25 @@ async function executeNode(code, timeout = 10000) {
|
|
|
51
65
|
|
|
52
66
|
proc.on('close', (exitCode) => {
|
|
53
67
|
clearTimeout(timer);
|
|
68
|
+
if (killTimer) clearTimeout(killTimer);
|
|
54
69
|
resolve({
|
|
55
70
|
success: !killed && exitCode === 0,
|
|
56
71
|
stdout: stdout.trim(),
|
|
57
|
-
stderr: killed ?
|
|
72
|
+
stderr: killed ? `Timeout after ${effectiveTimeout}ms: process killed` : stderr.trim(),
|
|
58
73
|
exitCode: killed ? -1 : exitCode,
|
|
74
|
+
timedOut: killed,
|
|
59
75
|
});
|
|
60
76
|
});
|
|
61
77
|
|
|
62
78
|
proc.on('error', (err) => {
|
|
63
79
|
clearTimeout(timer);
|
|
80
|
+
if (killTimer) clearTimeout(killTimer);
|
|
64
81
|
resolve({
|
|
65
82
|
success: false,
|
|
66
83
|
stdout: '',
|
|
67
84
|
stderr: err.message,
|
|
68
85
|
exitCode: -1,
|
|
86
|
+
timedOut: false,
|
|
69
87
|
});
|
|
70
88
|
});
|
|
71
89
|
});
|
|
@@ -76,8 +94,25 @@ async function executeNode(code, timeout = 10000) {
|
|
|
76
94
|
*/
|
|
77
95
|
export const runNodeCode = tool(
|
|
78
96
|
async ({ code, timeout }) => {
|
|
79
|
-
|
|
80
|
-
|
|
97
|
+
// 连续超时保护:降级为短超时探测,而非完全拒绝
|
|
98
|
+
const isBlocked = consecutiveTimeouts >= MAX_CONSECUTIVE_TIMEOUTS;
|
|
99
|
+
const effectiveTimeout = isBlocked ? 5000 : (timeout || 10000);
|
|
100
|
+
const result = await executeNode(code, effectiveTimeout);
|
|
101
|
+
|
|
102
|
+
// 更新连续超时计数
|
|
103
|
+
if (result.timedOut) {
|
|
104
|
+
consecutiveTimeouts++;
|
|
105
|
+
if (consecutiveTimeouts >= MAX_CONSECUTIVE_TIMEOUTS) {
|
|
106
|
+
result.stderr += `\n\n⚠️ 已连续超时 ${consecutiveTimeouts} 次,后续调用将降级为 5s 短超时。代码很可能存在死循环或触发了环境检测。请停止重试相同逻辑,改用 sandbox_execute(沙箱执行)、断点调试或静态分析。`;
|
|
107
|
+
} else {
|
|
108
|
+
result.stderr += `\n\n⚠️ 连续超时 ${consecutiveTimeouts}/${MAX_CONSECUTIVE_TIMEOUTS} 次。如果代码包含从网站提取的混淆代码,可能存在环境检测导致死循环。建议:1) 检查代码是否有 while(true)/setInterval 等循环 2) 改用 sandbox_execute 在受控环境中执行`;
|
|
109
|
+
}
|
|
110
|
+
} else {
|
|
111
|
+
consecutiveTimeouts = 0; // 成功执行或非超时失败,重置计数
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const { timedOut: _, ...output } = result;
|
|
115
|
+
return JSON.stringify(output);
|
|
81
116
|
},
|
|
82
117
|
{
|
|
83
118
|
name: 'run_node_code',
|
|
@@ -93,7 +128,7 @@ export const runNodeCode = tool(
|
|
|
93
128
|
示例:const CryptoJS = require('crypto-js'); console.log(CryptoJS.MD5('test').toString());`,
|
|
94
129
|
schema: z.object({
|
|
95
130
|
code: z.string().describe('要执行的 JS 代码,可使用 require 引入加密库'),
|
|
96
|
-
timeout: z.number().optional().default(10000).describe('
|
|
131
|
+
timeout: z.number().optional().default(10000).describe('超时时间(毫秒),上限 30000'),
|
|
97
132
|
}),
|
|
98
133
|
}
|
|
99
134
|
);
|
|
@@ -76,4 +76,24 @@ export const sandboxReset = tool(
|
|
|
76
76
|
}
|
|
77
77
|
);
|
|
78
78
|
|
|
79
|
-
|
|
79
|
+
/**
|
|
80
|
+
* 自动补环境执行工具
|
|
81
|
+
*/
|
|
82
|
+
export const sandboxAutoFix = tool(
|
|
83
|
+
async ({ code, timeout, maxIterations }) => {
|
|
84
|
+
const sb = await getSandbox();
|
|
85
|
+
const result = await sb.executeWithAutoFix(code, { timeout, maxIterations });
|
|
86
|
+
return JSON.stringify(result, null, 2);
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: 'sandbox_auto_fix',
|
|
90
|
+
description: '自动补环境闭环执行:加载预置模块 → 执行代码 → 发现缺失环境 → 自动生成补丁 → 重试,直到成功或无法继续。适合快速验证混淆代码能否在沙箱中运行。',
|
|
91
|
+
schema: z.object({
|
|
92
|
+
code: z.string().describe('要执行的目标JS代码'),
|
|
93
|
+
timeout: z.number().optional().default(5000).describe('单次执行超时时间(ms)'),
|
|
94
|
+
maxIterations: z.number().optional().default(10).describe('最大迭代次数'),
|
|
95
|
+
}),
|
|
96
|
+
}
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
export const sandboxTools = [sandboxExecute, sandboxInject, sandboxReset, sandboxAutoFix];
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DeepSpider - 工作记忆工具(Scratchpad)
|
|
3
|
+
* 保存/读取关键发现,防止多步推理中丢失上下文
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import { tool } from '@langchain/core/tools';
|
|
8
|
+
import { writeFileSync, readFileSync, existsSync, readdirSync } from 'fs';
|
|
9
|
+
import { join } from 'path';
|
|
10
|
+
import { DEEPSPIDER_HOME, ensureDir } from '../../config/paths.js';
|
|
11
|
+
|
|
12
|
+
const MEMO_DIR = join(DEEPSPIDER_HOME, 'memo');
|
|
13
|
+
|
|
14
|
+
/** 清理 key:只保留字母、数字、连字符、下划线,防止路径穿越 */
|
|
15
|
+
function sanitizeKey(key) {
|
|
16
|
+
return key.replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 100);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const saveMemo = tool(
|
|
20
|
+
async ({ key, content }) => {
|
|
21
|
+
ensureDir(MEMO_DIR);
|
|
22
|
+
const safeKey = sanitizeKey(key);
|
|
23
|
+
const filePath = join(MEMO_DIR, `${safeKey}.txt`);
|
|
24
|
+
writeFileSync(filePath, content, 'utf-8');
|
|
25
|
+
return JSON.stringify({ success: true, key: safeKey, size: content.length });
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: 'save_memo',
|
|
29
|
+
description: '保存工作记忆。用于记录关键发现、中间结果、待验证假设等,防止多步推理中丢失上下文',
|
|
30
|
+
schema: z.object({
|
|
31
|
+
key: z.string().describe('记忆键名,如 "encryption-analysis"、"key-source"'),
|
|
32
|
+
content: z.string().describe('要保存的内容'),
|
|
33
|
+
}),
|
|
34
|
+
}
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
export const loadMemo = tool(
|
|
38
|
+
async ({ key }) => {
|
|
39
|
+
const safeKey = sanitizeKey(key);
|
|
40
|
+
const filePath = join(MEMO_DIR, `${safeKey}.txt`);
|
|
41
|
+
if (!existsSync(filePath)) {
|
|
42
|
+
return JSON.stringify({ success: false, error: `memo "${safeKey}" not found` });
|
|
43
|
+
}
|
|
44
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
45
|
+
return JSON.stringify({ success: true, key: safeKey, content });
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: 'load_memo',
|
|
49
|
+
description: '读取之前保存的工作记忆',
|
|
50
|
+
schema: z.object({
|
|
51
|
+
key: z.string().describe('记忆键名'),
|
|
52
|
+
}),
|
|
53
|
+
}
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
export const listMemo = tool(
|
|
57
|
+
async () => {
|
|
58
|
+
ensureDir(MEMO_DIR);
|
|
59
|
+
const files = readdirSync(MEMO_DIR).filter(f => f.endsWith('.txt'));
|
|
60
|
+
const memos = files.map(f => f.replace('.txt', ''));
|
|
61
|
+
return JSON.stringify({ success: true, memos, count: memos.length });
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: 'list_memo',
|
|
65
|
+
description: '列出所有已保存的工作记忆',
|
|
66
|
+
schema: z.object({}),
|
|
67
|
+
}
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
export const scratchpadTools = [saveMemo, loadMemo, listMemo];
|
|
@@ -188,11 +188,37 @@ export const clearAllData = tool(
|
|
|
188
188
|
}
|
|
189
189
|
);
|
|
190
190
|
|
|
191
|
+
/**
|
|
192
|
+
* 获取请求的 initiator(调用栈)
|
|
193
|
+
*/
|
|
194
|
+
export const getRequestInitiator = tool(
|
|
195
|
+
async ({ site, id }) => {
|
|
196
|
+
const store = getDataStore();
|
|
197
|
+
const result = await store.getResponse(site, id);
|
|
198
|
+
if (!result) {
|
|
199
|
+
return JSON.stringify({ error: '未找到该请求' });
|
|
200
|
+
}
|
|
201
|
+
if (!result.initiator) {
|
|
202
|
+
return JSON.stringify({ error: '该请求无 initiator 信息(可能是旧数据或浏览器内部请求)' });
|
|
203
|
+
}
|
|
204
|
+
return JSON.stringify(result.initiator, null, 2);
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
name: 'get_request_initiator',
|
|
208
|
+
description: '获取发起该请求的 JS 代码位置(脚本URL + 行号 + 函数名)。返回调用栈的前5帧。用于从目标请求反向定位加密函数入口,是逆向分析的第一步。',
|
|
209
|
+
schema: z.object({
|
|
210
|
+
site: z.string().describe('站点 hostname'),
|
|
211
|
+
id: z.string().describe('请求 ID'),
|
|
212
|
+
}),
|
|
213
|
+
}
|
|
214
|
+
);
|
|
215
|
+
|
|
191
216
|
export const tracingTools = [
|
|
192
217
|
getSiteList,
|
|
193
218
|
searchInResponses,
|
|
194
219
|
getRequestDetail,
|
|
195
220
|
getRequestList,
|
|
221
|
+
getRequestInitiator,
|
|
196
222
|
getScriptList,
|
|
197
223
|
getScriptSource,
|
|
198
224
|
searchInScripts,
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DeepSpider - 统一算法验证工具
|
|
3
|
+
* 合并 verify_md5/sha256/hmac/aes + identify_encryption
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import { tool } from '@langchain/core/tools';
|
|
8
|
+
import crypto from 'crypto';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 识别密文特征
|
|
12
|
+
*/
|
|
13
|
+
function identifyPattern(ciphertext) {
|
|
14
|
+
const features = [];
|
|
15
|
+
const len = ciphertext.length;
|
|
16
|
+
|
|
17
|
+
if (len === 32) features.push('可能是 MD5');
|
|
18
|
+
if (len === 40) features.push('可能是 SHA1');
|
|
19
|
+
if (len === 64) features.push('可能是 SHA256');
|
|
20
|
+
if (len === 128) features.push('可能是 SHA512');
|
|
21
|
+
if (/^[A-Za-z0-9+/]+=*$/.test(ciphertext)) features.push('Base64 编码');
|
|
22
|
+
if (/^[0-9a-fA-F]+$/.test(ciphertext)) features.push('Hex 编码');
|
|
23
|
+
if (/^eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(ciphertext)) features.push('JWT Token');
|
|
24
|
+
|
|
25
|
+
return features;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const verifyAlgorithm = tool(
|
|
29
|
+
async ({ algorithm, input, expected, key, iv, hmacHash, aesMode }) => {
|
|
30
|
+
// 识别模式:不传 algorithm,只传 expected
|
|
31
|
+
if (!algorithm) {
|
|
32
|
+
return JSON.stringify({
|
|
33
|
+
ciphertext: expected.slice(0, 50) + (expected.length > 50 ? '...' : ''),
|
|
34
|
+
length: expected.length,
|
|
35
|
+
features: identifyPattern(expected),
|
|
36
|
+
}, null, 2);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 验证模式:需要 input + expected
|
|
40
|
+
if (!input) {
|
|
41
|
+
return JSON.stringify({ error: `验证 ${algorithm} 需要 input 参数` });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let computed;
|
|
45
|
+
let algoLabel;
|
|
46
|
+
|
|
47
|
+
switch (algorithm) {
|
|
48
|
+
case 'md5':
|
|
49
|
+
case 'sha1':
|
|
50
|
+
case 'sha256':
|
|
51
|
+
case 'sha512': {
|
|
52
|
+
computed = crypto.createHash(algorithm).update(input).digest('hex');
|
|
53
|
+
algoLabel = algorithm.toUpperCase();
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
case 'hmac': {
|
|
57
|
+
if (!key) return JSON.stringify({ error: 'HMAC 需要 key 参数' });
|
|
58
|
+
const hash = hmacHash || 'sha256';
|
|
59
|
+
computed = crypto.createHmac(hash, key).update(input).digest('hex');
|
|
60
|
+
algoLabel = `HMAC-${hash.toUpperCase()}`;
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
case 'aes': {
|
|
64
|
+
if (!key) return JSON.stringify({ error: 'AES 需要 key 参数' });
|
|
65
|
+
try {
|
|
66
|
+
const keyBuf = Buffer.from(key, 'utf8');
|
|
67
|
+
const ivBuf = iv ? Buffer.from(iv, 'utf8') : Buffer.alloc(16, 0);
|
|
68
|
+
const mode = aesMode || 'cbc';
|
|
69
|
+
const cipher = crypto.createCipheriv(
|
|
70
|
+
`aes-${keyBuf.length * 8}-${mode}`,
|
|
71
|
+
keyBuf,
|
|
72
|
+
mode === 'ecb' ? null : ivBuf
|
|
73
|
+
);
|
|
74
|
+
computed = cipher.update(input, 'utf8', 'base64') + cipher.final('base64');
|
|
75
|
+
algoLabel = `AES-${keyBuf.length * 8}-${mode.toUpperCase()}`;
|
|
76
|
+
} catch (e) {
|
|
77
|
+
return JSON.stringify({ error: e.message });
|
|
78
|
+
}
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
default:
|
|
82
|
+
return JSON.stringify({ error: `未知算法: ${algorithm}` });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const match = algorithm === 'aes'
|
|
86
|
+
? computed === expected
|
|
87
|
+
: computed.toLowerCase() === expected.toLowerCase();
|
|
88
|
+
|
|
89
|
+
return JSON.stringify({
|
|
90
|
+
algorithm: algoLabel,
|
|
91
|
+
input,
|
|
92
|
+
computed,
|
|
93
|
+
expected,
|
|
94
|
+
match,
|
|
95
|
+
conclusion: match ? `标准 ${algoLabel}` : '可能魔改或参数不同',
|
|
96
|
+
}, null, 2);
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
name: 'verify_algorithm',
|
|
100
|
+
description: `验证是否为标准加密算法,或根据密文特征识别算法类型。
|
|
101
|
+
|
|
102
|
+
验证模式:传入 algorithm + input + expected,对比计算结果
|
|
103
|
+
识别模式:只传 expected(密文),自动识别可能的算法类型`,
|
|
104
|
+
schema: z.object({
|
|
105
|
+
algorithm: z.enum(['md5', 'sha1', 'sha256', 'sha512', 'hmac', 'aes']).optional()
|
|
106
|
+
.describe('算法类型。不传则进入识别模式'),
|
|
107
|
+
input: z.string().optional().describe('原始输入'),
|
|
108
|
+
expected: z.string().describe('目标加密结果(验证模式)或待识别的密文(识别模式)'),
|
|
109
|
+
key: z.string().optional().describe('密钥(HMAC/AES 需要)'),
|
|
110
|
+
iv: z.string().optional().describe('IV 向量(AES 可选)'),
|
|
111
|
+
hmacHash: z.enum(['md5', 'sha1', 'sha256', 'sha512']).optional().describe('HMAC 哈希算法,默认 sha256'),
|
|
112
|
+
aesMode: z.enum(['cbc', 'ecb']).optional().describe('AES 模式,默认 cbc'),
|
|
113
|
+
}),
|
|
114
|
+
}
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
export const verifyAlgorithmTools = [verifyAlgorithm];
|
package/src/browser/EnvBridge.js
CHANGED
|
@@ -26,24 +26,38 @@ export class EnvBridge {
|
|
|
26
26
|
|
|
27
27
|
for (const path of missingPaths) {
|
|
28
28
|
try {
|
|
29
|
-
// 1.
|
|
29
|
+
// 1. 尝试从真实浏览器采集
|
|
30
30
|
const collected = await this.collector.collect(path, { depth: 2 });
|
|
31
31
|
|
|
32
|
-
if (
|
|
33
|
-
results.
|
|
34
|
-
|
|
35
|
-
}
|
|
32
|
+
if (collected.success) {
|
|
33
|
+
results.collected.push(path);
|
|
34
|
+
this.collectedData.set(path, collected);
|
|
36
35
|
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
// 2. 生成补丁代码
|
|
37
|
+
const patch = this._generatePatch(path, collected);
|
|
38
|
+
if (patch) {
|
|
39
|
+
results.patched.push({ path, code: patch });
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
39
43
|
|
|
40
|
-
//
|
|
41
|
-
const
|
|
42
|
-
if (
|
|
43
|
-
results.patched.push({ path, code:
|
|
44
|
+
// 3. 采集失败时 fallback 到 PatchGenerator
|
|
45
|
+
const fallback = await this.patchGenerator.generate(path);
|
|
46
|
+
if (fallback.code) {
|
|
47
|
+
results.patched.push({ path, code: fallback.code, source: fallback.source });
|
|
48
|
+
} else {
|
|
49
|
+
results.failed.push({ path, reason: 'no_patch' });
|
|
44
50
|
}
|
|
45
51
|
|
|
46
52
|
} catch (e) {
|
|
53
|
+
// 浏览器不可用时也 fallback
|
|
54
|
+
try {
|
|
55
|
+
const fallback = await this.patchGenerator.generate(path);
|
|
56
|
+
if (fallback.code) {
|
|
57
|
+
results.patched.push({ path, code: fallback.code, source: fallback.source });
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
} catch { /* ignore */ }
|
|
47
61
|
results.failed.push({ path, reason: 'error', error: e.message });
|
|
48
62
|
}
|
|
49
63
|
}
|
|
@@ -66,7 +80,7 @@ export class EnvBridge {
|
|
|
66
80
|
// 根据数据类型生成不同的补丁
|
|
67
81
|
switch (data.type) {
|
|
68
82
|
case 'string':
|
|
69
|
-
return `${parentPath}.${propName} =
|
|
83
|
+
return `${parentPath}.${propName} = ${JSON.stringify(data.value)};`;
|
|
70
84
|
|
|
71
85
|
case 'number':
|
|
72
86
|
return `${parentPath}.${propName} = ${data.value};`;
|
|
@@ -123,7 +137,7 @@ export class EnvBridge {
|
|
|
123
137
|
|
|
124
138
|
_serializeValue(data) {
|
|
125
139
|
switch (data.type) {
|
|
126
|
-
case 'string': return
|
|
140
|
+
case 'string': return JSON.stringify(data.value);
|
|
127
141
|
case 'number': return data.value;
|
|
128
142
|
case 'boolean': return data.value;
|
|
129
143
|
case 'null': return 'null';
|