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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sicario-red-team",
3
- "version": "0.5.8",
3
+ "version": "0.5.10",
4
4
  "type": "module",
5
5
  "description": "Autonomous Agentic Red-Teaming Swarm Protocol",
6
6
  "repository": {
@@ -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
- elements = await runScout(finalTarget, { auth: options.auth });
81
- log.success('[Scout] : Perimeter mapped.');
82
- if (client && missionId) await client.mutation('handler:logMessage', { missionId, type: 'Scout', message: 'Perimeter mapped.' });
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 clack from '@clack/prompts';
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
- Pay special attention to inputs where type='hidden' or marked as isHighValueTarget.
46
- These often control critical business logic like pricing, user IDs, or privilege levels.
47
- Hunt for logic flaws by attempting to mutate these hidden values (e.g., set price/quantity to 0 or negative, change account roles to 'admin', or swap user session IDs).
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', // Adjust model as needed
79
+ model: 'llama3.1-8b',
80
+ temperature: 0,
78
81
  response_format: { type: 'json_object' }
79
82
  });
80
83
 
@@ -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. Is it a real техническая vulnerability or just a best-practice bug?
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.2
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
- // Apply the mutation to the target element
30
- await page.$eval(selector, (el, v) => {
29
+ // 1. Shadow-Piercing Mutation
30
+ const targetLocator = page.locator(selector);
31
+ await targetLocator.evaluate((el, v) => {
31
32
  el.value = v;
32
- // Trigger change events if any
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
- // Pull the trigger: Find the nearest submit button
38
- console.log(pc.red(` [Executor] : Pulling the trigger (Live POST/PUT)...`));
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
- page.waitForNavigation({ timeout: 10000 }).catch(() => {}),
42
- page.click('button[type="submit"], input[type="submit"], [form] button').catch(async () => {
43
- // Fallback to form submit
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 the response
49
- const responseText = await page.innerText('body');
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: responseText.substring(0, 2000), // Limit data
83
+ evidence: serverEvidence.substring(0, 3000), // Larger limit for network data
56
84
  screenshot
57
85
  };
58
86
  } else {
@@ -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
- // We tell Playwright to wait until it sees at least one button or input.
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: Juice Shop has a massive "Dismiss" popup on first load.
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
- elements.forEach((el) => {
85
- // Scraping visibility check: visible elements OR high-value hidden inputs
86
- const isHiddenInput = el.tagName === 'INPUT' && el.type === 'hidden';
87
- const isVisible = el.offsetWidth > 0 && el.offsetHeight > 0;
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
- if (isVisible || isHiddenInput) {
90
- extracted.push({
91
- tag: el.tagName.toLowerCase(),
92
- id: el.id || null,
93
- name: el.name || null,
94
- type: el.type || null,
95
- value: el.value || null,
96
- isHighValueTarget: isHiddenInput && el.value !== '',
97
- text: el.innerText ? el.innerText.trim().substring(0, 50) : null,
98
- href: el.href || null
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 interactiveElements;
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();
@@ -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.1,
39
+ temperature: 0,
35
40
  max_tokens: 300
36
41
  });
37
42
 
@@ -26,7 +26,7 @@ REPLY ONLY WITH A COMMA-SEPARATED LIST OF 5 BRIEF TECHNICAL VECTORS.
26
26
  { role: 'user', content: 'Generate tactical vectors.' }
27
27
  ],
28
28
  model: 'llama3.1-8b',
29
- temperature: 0.1,
29
+ temperature: 0,
30
30
  max_tokens: 100
31
31
  });
32
32