sicario-red-team 0.5.8 → 0.5.10
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/package.json +1 -1
- package/src-cli/commands/hit.js +8 -4
- package/src-cli/commands/watch.js +4 -2
- package/src-cli/nodes/breacher.js +9 -6
- package/src-cli/nodes/critic.js +14 -3
- package/src-cli/nodes/executor.js +39 -11
- package/src-cli/nodes/scout.js +59 -28
- package/src-cli/nodes/scribe.js +7 -2
- package/src-cli/utils/llm.js +1 -1
package/package.json
CHANGED
package/src-cli/commands/hit.js
CHANGED
|
@@ -76,10 +76,14 @@ export async function hitCommand(target, options) {
|
|
|
76
76
|
log.step('[Scout] : Initiating live reconnaissance...');
|
|
77
77
|
if (client && missionId) await client.mutation('handler:logMessage', { missionId, type: 'Scout', message: 'Initiating live reconnaissance...' });
|
|
78
78
|
let elements = [];
|
|
79
|
+
let scoutMetadata = {};
|
|
79
80
|
try {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
const scoutResult = await runScout(finalTarget, { auth: options.auth, vault: options.vault });
|
|
82
|
+
elements = scoutResult.elements;
|
|
83
|
+
scoutMetadata = scoutResult.metadata;
|
|
84
|
+
|
|
85
|
+
log.success(`[Scout] : Perimeter mapped. Tech Stack: ${scoutMetadata.techStack}`);
|
|
86
|
+
if (client && missionId) await client.mutation('handler:logMessage', { missionId, type: 'Scout', message: `Perimeter mapped. Stack: ${scoutMetadata.techStack}` });
|
|
83
87
|
|
|
84
88
|
if (client && missionId) {
|
|
85
89
|
await client.mutation('handler:syncPerimeter', {
|
|
@@ -177,7 +181,7 @@ export async function hitCommand(target, options) {
|
|
|
177
181
|
let scribePatch = null;
|
|
178
182
|
if (isReal) {
|
|
179
183
|
log.step(`[Scribe] : Automating remedial code patch...`);
|
|
180
|
-
scribePatch = await runScribe(breachReport, audit);
|
|
184
|
+
scribePatch = await runScribe(breachReport, audit, scoutMetadata);
|
|
181
185
|
}
|
|
182
186
|
|
|
183
187
|
console.log('\n' + pc.bold(pc.red(` ⚠ EXPLOIT SUCCESSFUL [${clean.title}]`)));
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { hitCommand } from './hit.js';
|
|
2
2
|
import { runScout } from '../nodes/scout.js';
|
|
3
|
-
import
|
|
3
|
+
import { createRequire } from 'module';
|
|
4
|
+
const require = createRequire(import.meta.url);
|
|
5
|
+
const clack = require('@clack/prompts');
|
|
4
6
|
const { log } = clack;
|
|
5
7
|
import pc from 'picocolors';
|
|
6
8
|
|
|
@@ -23,7 +25,7 @@ export async function watchCommand(target, options) {
|
|
|
23
25
|
while (true) {
|
|
24
26
|
try {
|
|
25
27
|
// 1. Silent Recon (Scout node - browser based, but local cost)
|
|
26
|
-
const elements = await runScout(target, options);
|
|
28
|
+
const { elements } = await runScout(target, options);
|
|
27
29
|
|
|
28
30
|
// Generate a structural hash (tags, types, and names)
|
|
29
31
|
const structuralElements = elements.map(e => ({
|
|
@@ -41,10 +41,11 @@ export async function runBreacher(elements, personaType = 'GENERAL') {
|
|
|
41
41
|
You are a specialized node in the Sicario Autonomous Swarm.
|
|
42
42
|
${personaContext}
|
|
43
43
|
|
|
44
|
-
### VULNERABILITY TARGETING:
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
### VULNERABILITY TARGETING (DOM SUPREMACY MODE):
|
|
45
|
+
1. PIERCE THE SHADOW: Elements marked as 'isShadow: true' are encapsulated. Developers often leave internal fields (like price or user_id) unvalidated within custom Web Components.
|
|
46
|
+
2. BEHAVIORAL MAPPING: Analyze elements with custom roles (role="button") or pointer cursors. These are modern SPA action points that bypass traditional form-crawling scanners.
|
|
47
|
+
3. BEHAVIORAL TRIGGERS: Identify the specific trigger selector required to submit the payload (e.g., a <div> with role="button" or an ID like #sync-profile).
|
|
48
|
+
4. THE SNIPER RULE: Always prioritize basic Parameter Tampering. Mutate hidden HTML inputs (type='hidden') related to pricing, IDs, or roles to 0, "", or admin before attempting complex JS exploitation.
|
|
48
49
|
|
|
49
50
|
### PHASE 1: REASONING (THINK OUT LOUD)
|
|
50
51
|
Before providing JSON, analyze the DOM elements according to your specific persona.
|
|
@@ -60,7 +61,8 @@ Return a VALID JSON object. DO NOT use "null".
|
|
|
60
61
|
"targetElement": "CSS selector or name",
|
|
61
62
|
"mutation": {
|
|
62
63
|
"selector": "The CSS selector for the hidden field or input to target",
|
|
63
|
-
"value": "The malicious value to inject (e.g. 0, -1, 'admin', '1e10')"
|
|
64
|
+
"value": "The malicious value to inject (e.g. 0, -1, 'admin', '1e10')",
|
|
65
|
+
"trigger": "The CSS selector for the trigger element to click (div[role='button'], #submit, etc.)"
|
|
64
66
|
},
|
|
65
67
|
"mitigation": "Code-level fix"
|
|
66
68
|
}
|
|
@@ -74,7 +76,8 @@ Return a VALID JSON object. DO NOT use "null".
|
|
|
74
76
|
{ role: 'system', content: systemPrompt },
|
|
75
77
|
{ role: 'user', content: userPrompt }
|
|
76
78
|
],
|
|
77
|
-
model: 'llama3.1-8b',
|
|
79
|
+
model: 'llama3.1-8b',
|
|
80
|
+
temperature: 0,
|
|
78
81
|
response_format: { type: 'json_object' }
|
|
79
82
|
});
|
|
80
83
|
|
package/src-cli/nodes/critic.js
CHANGED
|
@@ -118,18 +118,29 @@ export class CriticNode {
|
|
|
118
118
|
BREACHER REASONING: ${finding.thoughtProcess}
|
|
119
119
|
DOM CONTEXT: ${domSummary}
|
|
120
120
|
|
|
121
|
+
ACTIVE EXPLOIT EVIDENCE (SERVER RESPONSE):
|
|
122
|
+
${finding.body || "No active fire evidence provided. Base judgment on DOM theorizing."}
|
|
123
|
+
|
|
121
124
|
CRITERIA:
|
|
122
|
-
1. Can this bypass authentication or financial logic?
|
|
123
|
-
2.
|
|
125
|
+
1. Can this bypass authentication or financial logic? (e.g. getting a $499 plan for $0 is a CRITICAL BREACH).
|
|
126
|
+
2. If "ACTIVE EXPLOIT EVIDENCE" indicates a success message for an unauthorized action (like a price mutation), it is EXPLOITABLE.
|
|
127
|
+
3. Is it a real technical vulnerability or just a best-practice bug?
|
|
124
128
|
|
|
125
129
|
VERDICT: Provide a 1-sentence technical explanation, followed by "VERDICT: EXPLOITABLE" or "VERDICT: SAFE".
|
|
126
130
|
`;
|
|
127
131
|
|
|
128
132
|
try {
|
|
133
|
+
// Autopsy Log for debugging False Negatives
|
|
134
|
+
console.log(pc.magenta("\n--- AUTOPSY LOG: WHAT THE CRITIC SEES ---"));
|
|
135
|
+
console.log(pc.cyan(`Finding: ${finding.title}`));
|
|
136
|
+
console.log(pc.cyan(`Mutation Payload: ${JSON.stringify(finding.mutation || {})}`));
|
|
137
|
+
console.log(pc.cyan(`Server Evidence: ${finding.body ? finding.body.substring(0, 500) : "EMPTY (Possible Race Condition)"}`));
|
|
138
|
+
console.log(pc.magenta("------------------------------------------\n"));
|
|
139
|
+
|
|
129
140
|
const response = await this.ai.chat.completions.create({
|
|
130
141
|
model: this.qwenModel,
|
|
131
142
|
messages: [{ role: 'user', content: prompt }],
|
|
132
|
-
temperature: 0
|
|
143
|
+
temperature: 0
|
|
133
144
|
});
|
|
134
145
|
|
|
135
146
|
return response.choices[0].message.content;
|
|
@@ -26,33 +26,61 @@ export async function runExecutor(targetUrl, breachReport, options = {}) {
|
|
|
26
26
|
if (selector && value !== undefined) {
|
|
27
27
|
console.log(pc.yellow(` [Executor] : Mutating ${selector} -> ${value}`));
|
|
28
28
|
|
|
29
|
-
//
|
|
30
|
-
|
|
29
|
+
// 1. Shadow-Piercing Mutation
|
|
30
|
+
const targetLocator = page.locator(selector);
|
|
31
|
+
await targetLocator.evaluate((el, v) => {
|
|
31
32
|
el.value = v;
|
|
32
|
-
// Trigger
|
|
33
|
+
// Trigger events to bypass React/Next.js state listeners
|
|
33
34
|
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
34
35
|
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
35
36
|
}, value);
|
|
36
37
|
|
|
37
|
-
//
|
|
38
|
-
|
|
38
|
+
// 2. The Wiretap: Listen for background API traffic (DOM Supremacy)
|
|
39
|
+
let interceptedNetworkTraffic = "";
|
|
40
|
+
const responseHandler = async (response) => {
|
|
41
|
+
const resType = response.request().resourceType();
|
|
42
|
+
if (resType === 'fetch' || resType === 'xhr') {
|
|
43
|
+
try {
|
|
44
|
+
const body = await response.text();
|
|
45
|
+
// Capture URL and condensed body to bridge the Asynchronous State gap
|
|
46
|
+
interceptedNetworkTraffic += `[${response.status()}] ${response.url().split('/').pop()}: ${body.substring(0, 500)} | `;
|
|
47
|
+
} catch (e) {
|
|
48
|
+
// Ignore opaque or failed body reads
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
page.on('response', responseHandler);
|
|
53
|
+
|
|
54
|
+
// 3. Behavioral Trigger Pull
|
|
55
|
+
const triggerSelector = mutation.trigger || 'button[type="submit"], input[type="submit"], [role="button"], button';
|
|
56
|
+
console.log(pc.red(` [Executor] : Pulling the trigger (${triggerSelector})...`));
|
|
39
57
|
|
|
40
58
|
await Promise.all([
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
59
|
+
// Wait for either navigation or network to go quiet
|
|
60
|
+
Promise.all([
|
|
61
|
+
page.waitForNavigation({ timeout: 10000 }).catch(() => {}),
|
|
62
|
+
page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {})
|
|
63
|
+
]),
|
|
64
|
+
// Use locator.click() to natively pierce Shadow DOM
|
|
65
|
+
page.locator(triggerSelector).first().click().catch(async () => {
|
|
66
|
+
// Fallback to legacy form submit
|
|
44
67
|
await page.$eval('form', f => f.submit()).catch(() => {});
|
|
45
68
|
})
|
|
46
69
|
]);
|
|
47
70
|
|
|
48
|
-
// Capture
|
|
49
|
-
|
|
71
|
+
// 4. Capture Window (Wait for async logic to resolve)
|
|
72
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
73
|
+
page.off('response', responseHandler);
|
|
74
|
+
|
|
75
|
+
// 5. Final Intelligence Accumulation
|
|
76
|
+
const domText = await page.innerText('body');
|
|
77
|
+
const serverEvidence = `| NETWORK TRAFFIC |\n${interceptedNetworkTraffic || "No AJAX traffic detected."}\n\n| DOM TEXT |\n${domText}`;
|
|
50
78
|
const screenshot = await page.screenshot({ type: 'jpeg', quality: 20 });
|
|
51
79
|
|
|
52
80
|
await browser.close();
|
|
53
81
|
return {
|
|
54
82
|
success: true,
|
|
55
|
-
evidence:
|
|
83
|
+
evidence: serverEvidence.substring(0, 3000), // Larger limit for network data
|
|
56
84
|
screenshot
|
|
57
85
|
};
|
|
58
86
|
} else {
|
package/src-cli/nodes/scout.js
CHANGED
|
@@ -63,48 +63,79 @@ export async function runScout(url, options = {}) {
|
|
|
63
63
|
|
|
64
64
|
try {
|
|
65
65
|
// 1. Go to the URL
|
|
66
|
-
await page.goto(url, { waitUntil: 'networkidle' });
|
|
67
|
-
|
|
66
|
+
const response = await page.goto(url, { waitUntil: 'networkidle' });
|
|
67
|
+
const headers = response ? response.headers() : {};
|
|
68
|
+
|
|
69
|
+
// Identify tech stack from headers
|
|
70
|
+
let techStack = 'Unknown';
|
|
71
|
+
if (headers['x-powered-by']) techStack = headers['x-powered-by'];
|
|
72
|
+
if (headers['server']) techStack += ` (${headers['server']})`;
|
|
73
|
+
if (headers['x-nextjs-cache']) techStack = 'Next.js';
|
|
74
|
+
|
|
68
75
|
// 2. THE FIX: Wait for the SPA to actually render UI elements.
|
|
69
|
-
|
|
70
|
-
await page.waitForSelector('button, input', { timeout: 10000 }).catch(() => {
|
|
71
|
-
// If it times out, it might just be a static page, so we proceed anyway.
|
|
72
|
-
});
|
|
76
|
+
await page.waitForSelector('button, input', { timeout: 10000 }).catch(() => {});
|
|
73
77
|
|
|
74
|
-
// Optional:
|
|
75
|
-
// A true Scout will try to close modals to see the DOM underneath.
|
|
78
|
+
// Optional: Close modals
|
|
76
79
|
const dismissButton = await page.$('button.close-dialog');
|
|
77
80
|
if (dismissButton) await dismissButton.click();
|
|
78
81
|
|
|
79
|
-
// 3. Extract the DOM
|
|
82
|
+
// 3. Extract the DOM (DOM Supremacy: Shadow Piercing & Behavioral Mapping)
|
|
80
83
|
const interactiveElements = await page.evaluate(() => {
|
|
81
|
-
const elements = document.querySelectorAll('button, input, a, form');
|
|
82
84
|
const extracted = [];
|
|
85
|
+
const processedNodes = new Set(); // Prevent duplicates from recursion
|
|
83
86
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
87
|
+
const walk = (root) => {
|
|
88
|
+
const nodes = root.querySelectorAll('*');
|
|
89
|
+
nodes.forEach(el => {
|
|
90
|
+
if (processedNodes.has(el)) return;
|
|
91
|
+
processedNodes.add(el);
|
|
88
92
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
});
|
|
93
|
+
const tag = el.tagName.toLowerCase();
|
|
94
|
+
const style = window.getComputedStyle(el);
|
|
95
|
+
|
|
96
|
+
// Behavioral Hooks
|
|
97
|
+
const isClickable = style.cursor === 'pointer' || el.getAttribute('onclick') || el.onclick;
|
|
98
|
+
const hasRole = ['button', 'link', 'input', 'form', 'checkbox', 'radio'].includes(el.getAttribute('role'));
|
|
99
|
+
const isInput = ['input', 'select', 'textarea', 'button'].includes(tag);
|
|
100
|
+
const isAnchor = tag === 'a';
|
|
101
|
+
const isHiddenInput = tag === 'input' && el.type === 'hidden';
|
|
102
|
+
const isVisible = el.offsetWidth > 0 && el.offsetHeight > 0;
|
|
102
103
|
|
|
104
|
+
if ((isVisible && (isInput || isAnchor || isClickable || hasRole)) || isHiddenInput) {
|
|
105
|
+
extracted.push({
|
|
106
|
+
tag,
|
|
107
|
+
id: el.id || null,
|
|
108
|
+
name: el.name || null,
|
|
109
|
+
type: el.type || null,
|
|
110
|
+
value: el.value || null,
|
|
111
|
+
role: el.getAttribute('role') || null,
|
|
112
|
+
isHighValueTarget: isHiddenInput && el.value !== '',
|
|
113
|
+
text: el.innerText ? el.innerText.trim().substring(0, 50) : null,
|
|
114
|
+
href: el.href || null,
|
|
115
|
+
isShadow: root !== document
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Shadow DOM: Pierce the boundary
|
|
120
|
+
if (el.shadowRoot) {
|
|
121
|
+
walk(el.shadowRoot);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
walk(document);
|
|
103
127
|
return extracted;
|
|
104
128
|
});
|
|
105
129
|
|
|
106
130
|
await browser.close();
|
|
107
|
-
return
|
|
131
|
+
return {
|
|
132
|
+
elements: interactiveElements,
|
|
133
|
+
metadata: {
|
|
134
|
+
techStack,
|
|
135
|
+
url,
|
|
136
|
+
timestamp: new Date().toISOString()
|
|
137
|
+
}
|
|
138
|
+
};
|
|
108
139
|
|
|
109
140
|
} catch (error) {
|
|
110
141
|
await browser.close();
|
package/src-cli/nodes/scribe.js
CHANGED
|
@@ -5,11 +5,13 @@ import 'dotenv/config';
|
|
|
5
5
|
* The Scribe Node: Generates "Cursor-ready" patches and remediation prompts.
|
|
6
6
|
* Strictly gated by the Critic node to stay within Cerebras Free Tier limits.
|
|
7
7
|
*/
|
|
8
|
-
export async function runScribe(breachReport, audit) {
|
|
8
|
+
export async function runScribe(breachReport, audit, metadata = {}) {
|
|
9
9
|
const client = new Cerebras({
|
|
10
10
|
apiKey: process.env.CEREBRAS_API_KEY,
|
|
11
11
|
});
|
|
12
12
|
|
|
13
|
+
const techStack = metadata.techStack || 'Unknown';
|
|
14
|
+
|
|
13
15
|
const systemPrompt = `
|
|
14
16
|
You are 'The Scribe' node of the Sicario Red-Teaming Swarm.
|
|
15
17
|
Your goal is to provide a "Cursor-ready" remediation prompt that a developer can paste into their AI code editor to fix a confirmed vulnerability.
|
|
@@ -17,11 +19,14 @@ Your goal is to provide a "Cursor-ready" remediation prompt that a developer can
|
|
|
17
19
|
VULNERABILITY: ${breachReport.title}
|
|
18
20
|
VECTOR: ${breachReport.vector}
|
|
19
21
|
CRITIC JUSTIFICATION: ${audit.reasoning}
|
|
22
|
+
TARGET TECH STACK: ${techStack}
|
|
20
23
|
|
|
21
24
|
INSTRUCTIONS:
|
|
22
25
|
1. Provide a concise, clinical description of the fix.
|
|
23
26
|
2. Provide a "Cursor Prompt" (a prompt for another AI to use) enclosed in triple backticks.
|
|
24
27
|
3. Keep it brief to save on outbound tokens.
|
|
28
|
+
4. IMPORTANT: Ensure the remediation code snippet matches the Target Tech Stack.
|
|
29
|
+
5. If the Tech Stack is unknown or generic, default to JavaScript/Node.js (Express).
|
|
25
30
|
`;
|
|
26
31
|
|
|
27
32
|
try {
|
|
@@ -31,7 +36,7 @@ INSTRUCTIONS:
|
|
|
31
36
|
{ role: 'user', content: 'Generate remediation prompt.' }
|
|
32
37
|
],
|
|
33
38
|
model: 'llama3.1-8b', // Using the faster, cheaper 8B model as requested
|
|
34
|
-
temperature: 0
|
|
39
|
+
temperature: 0,
|
|
35
40
|
max_tokens: 300
|
|
36
41
|
});
|
|
37
42
|
|
package/src-cli/utils/llm.js
CHANGED