erosolar-cli 2.1.241 → 2.1.243
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/dist/capabilities/iMessageVerificationCapability.d.ts +31 -0
- package/dist/capabilities/iMessageVerificationCapability.d.ts.map +1 -0
- package/dist/capabilities/iMessageVerificationCapability.js +56 -0
- package/dist/capabilities/iMessageVerificationCapability.js.map +1 -0
- package/dist/capabilities/index.d.ts +1 -0
- package/dist/capabilities/index.d.ts.map +1 -1
- package/dist/capabilities/index.js +1 -0
- package/dist/capabilities/index.js.map +1 -1
- package/dist/core/agentOrchestrator.d.ts +79 -1
- package/dist/core/agentOrchestrator.d.ts.map +1 -1
- package/dist/core/agentOrchestrator.js +494 -19
- package/dist/core/agentOrchestrator.js.map +1 -1
- package/dist/core/iMessageVerification.d.ts +408 -0
- package/dist/core/iMessageVerification.d.ts.map +1 -0
- package/dist/core/iMessageVerification.js +883 -0
- package/dist/core/iMessageVerification.js.map +1 -0
- package/dist/core/techFraudInvestigator.d.ts +131 -0
- package/dist/core/techFraudInvestigator.d.ts.map +1 -0
- package/dist/core/techFraudInvestigator.js +992 -0
- package/dist/core/techFraudInvestigator.js.map +1 -0
- package/dist/plugins/tools/imessageVerification/iMessageVerificationPlugin.d.ts +3 -0
- package/dist/plugins/tools/imessageVerification/iMessageVerificationPlugin.d.ts.map +1 -0
- package/dist/plugins/tools/imessageVerification/iMessageVerificationPlugin.js +14 -0
- package/dist/plugins/tools/imessageVerification/iMessageVerificationPlugin.js.map +1 -0
- package/dist/plugins/tools/nodeDefaults.d.ts.map +1 -1
- package/dist/plugins/tools/nodeDefaults.js +2 -0
- package/dist/plugins/tools/nodeDefaults.js.map +1 -1
- package/dist/tools/iMessageVerificationTools.d.ts +17 -0
- package/dist/tools/iMessageVerificationTools.d.ts.map +1 -0
- package/dist/tools/iMessageVerificationTools.js +842 -0
- package/dist/tools/iMessageVerificationTools.js.map +1 -0
- package/dist/tools/taoTools.d.ts.map +1 -1
- package/dist/tools/taoTools.js +1277 -1
- package/dist/tools/taoTools.js.map +1 -1
- package/dist/ui/UnifiedUIRenderer.js +5 -5
- package/dist/ui/UnifiedUIRenderer.js.map +1 -1
- package/dist/ui/display.d.ts +14 -0
- package/dist/ui/display.d.ts.map +1 -1
- package/dist/ui/display.js +42 -24
- package/dist/ui/display.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { TASK_FULLY_COMPLETE } from './constants.js';
|
|
2
2
|
import { TaskCompletionDetector } from './taskCompletionDetector.js';
|
|
3
|
+
import { planAttackChain, selectNextTechnique, executeTechniqueInChain, techniqueRegistry, parseNaturalLanguageIntent, validateAuthorizationContext, } from '../tools/tao/index.js';
|
|
3
4
|
/**
|
|
4
5
|
* Single-pass orchestration: drive the agent to finish as much as possible
|
|
5
6
|
* in one go, with strong bias toward real actions over planning.
|
|
@@ -10,6 +11,42 @@ export class AgentOrchestrator {
|
|
|
10
11
|
this.agent = agent;
|
|
11
12
|
}
|
|
12
13
|
async runToCompletion(request, options) {
|
|
14
|
+
// Early safety check for potentially harmful requests
|
|
15
|
+
const harmfulCheck = this.checkForHarmfulRequest(request);
|
|
16
|
+
if (harmfulCheck.isHarmful) {
|
|
17
|
+
return {
|
|
18
|
+
finalResponse: harmfulCheck.response,
|
|
19
|
+
toolsUsed: [],
|
|
20
|
+
planOnly: false,
|
|
21
|
+
tookAction: false,
|
|
22
|
+
completion: {
|
|
23
|
+
isComplete: true,
|
|
24
|
+
confidence: 1.0,
|
|
25
|
+
signals: {
|
|
26
|
+
hasExplicitCompletionStatement: true,
|
|
27
|
+
hasIncompleteWorkIndicators: false,
|
|
28
|
+
hasPendingActionIndicators: false,
|
|
29
|
+
hasErrorIndicators: false,
|
|
30
|
+
hasFollowUpQuestions: false,
|
|
31
|
+
toolsUsedInLastResponse: 0,
|
|
32
|
+
lastToolWasReadOnly: false,
|
|
33
|
+
consecutiveResponsesWithoutTools: 0,
|
|
34
|
+
hasRecentFileWrites: false,
|
|
35
|
+
hasRecentCommits: false,
|
|
36
|
+
todoItemsPending: 0,
|
|
37
|
+
todoItemsCompleted: 0,
|
|
38
|
+
mentionsFutureWork: false,
|
|
39
|
+
completionConfidence: 1.0,
|
|
40
|
+
},
|
|
41
|
+
reason: 'Request declined for safety reasons',
|
|
42
|
+
shouldVerify: false,
|
|
43
|
+
},
|
|
44
|
+
exitReason: 'refusal',
|
|
45
|
+
statusSummary: null,
|
|
46
|
+
limitations: [],
|
|
47
|
+
recommendations: [],
|
|
48
|
+
};
|
|
49
|
+
}
|
|
13
50
|
const streaming = options?.streaming ?? true;
|
|
14
51
|
const enforceActions = options?.enforceActions ?? true;
|
|
15
52
|
const verificationMode = options?.verificationMode ?? 'auto';
|
|
@@ -52,15 +89,28 @@ export class AgentOrchestrator {
|
|
|
52
89
|
if (attempt.exitReason === 'refusal') {
|
|
53
90
|
break;
|
|
54
91
|
}
|
|
55
|
-
// Track consecutive no-progress attempts
|
|
56
|
-
//
|
|
92
|
+
// Track consecutive no-progress attempts
|
|
93
|
+
// Key insight: tool usage alone doesn't mean progress if the model is stuck
|
|
94
|
+
// or refusing. We need to detect actual forward momentum.
|
|
57
95
|
const terminalNoProgress = attempt.exitReason === 'empty-response' ||
|
|
58
96
|
attempt.exitReason === 'no-action' ||
|
|
59
97
|
attempt.exitReason === 'blocked';
|
|
60
|
-
|
|
61
|
-
|
|
98
|
+
// Detect response repetition - if we keep getting similar responses, we're stuck
|
|
99
|
+
// This catches cases where model calls tools but produces same reasoning/refusal
|
|
100
|
+
const responseFingerprint = this.computeResponseFingerprint(attempt.response);
|
|
101
|
+
const previousAttempt = attempts.length >= 2 ? attempts[attempts.length - 2] : undefined;
|
|
102
|
+
const isRepeatedResponse = previousAttempt !== undefined &&
|
|
103
|
+
this.computeResponseFingerprint(previousAttempt.response) === responseFingerprint &&
|
|
104
|
+
responseFingerprint !== '';
|
|
105
|
+
// Progress is considered made when:
|
|
106
|
+
// 1. Task is complete or needs verification
|
|
107
|
+
// 2. OR tools were used with a DIFFERENT response (model is working through the problem)
|
|
108
|
+
const isCompleting = attempt.exitReason === 'complete' ||
|
|
62
109
|
attempt.exitReason === 'verification-needed';
|
|
63
|
-
|
|
110
|
+
const isProgressingWithTools = attempt.toolsUsed.length > 0 && !isRepeatedResponse;
|
|
111
|
+
const hasRealProgress = isCompleting || isProgressingWithTools;
|
|
112
|
+
// No progress if: terminal state, no completion AND no tool progress, OR repeated response
|
|
113
|
+
if (terminalNoProgress || (!hasRealProgress && !isCompleting) || (isRepeatedResponse && !isCompleting)) {
|
|
64
114
|
consecutiveNoProgress++;
|
|
65
115
|
}
|
|
66
116
|
else {
|
|
@@ -366,18 +416,22 @@ ${actionLine}
|
|
|
366
416
|
if (attempts.length >= maxAttempts) {
|
|
367
417
|
return false;
|
|
368
418
|
}
|
|
369
|
-
//
|
|
370
|
-
if (exitReason === 'refusal') {
|
|
419
|
+
// Terminal states - never retry
|
|
420
|
+
if (exitReason === 'refusal' || exitReason === 'complete') {
|
|
371
421
|
return false;
|
|
372
422
|
}
|
|
373
|
-
// Detect stuck loops early - if we have 2+ consecutive attempts
|
|
374
|
-
// This catches cases where the model refuses silently
|
|
423
|
+
// Detect stuck loops early - if we have 2+ consecutive attempts without completion, stop
|
|
424
|
+
// This catches cases where the model refuses silently, calls tools without progress,
|
|
425
|
+
// or returns empty/no-action repeatedly
|
|
375
426
|
if (attempts.length >= 2) {
|
|
376
427
|
const recentAttempts = attempts.slice(-2);
|
|
377
|
-
const
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
428
|
+
const allNoCompletion = recentAttempts.every(a => a.exitReason !== 'complete' && a.exitReason !== 'verification-needed');
|
|
429
|
+
if (allNoCompletion) {
|
|
430
|
+
// Check if responses are similar (stuck in a loop)
|
|
431
|
+
const fingerprints = recentAttempts.map(a => this.computeResponseFingerprint(a.response));
|
|
432
|
+
if (fingerprints[0] === fingerprints[1] && fingerprints[0] !== '') {
|
|
433
|
+
return false; // Stuck producing same response
|
|
434
|
+
}
|
|
381
435
|
}
|
|
382
436
|
}
|
|
383
437
|
// Allow recovery attempts for common failure modes before giving up
|
|
@@ -397,12 +451,16 @@ ${actionLine}
|
|
|
397
451
|
const blockedResponses = attempts.filter((a) => a.exitReason === 'blocked').length;
|
|
398
452
|
return blockedResponses < 2;
|
|
399
453
|
}
|
|
400
|
-
// Detect
|
|
401
|
-
if (exitReason === 'incomplete' && attempts.length >=
|
|
402
|
-
const recentAttempts = attempts.slice(-
|
|
403
|
-
const
|
|
404
|
-
if (
|
|
405
|
-
|
|
454
|
+
// Detect tool loops - if last 2 attempts used tools, are incomplete, AND produced similar responses
|
|
455
|
+
if (exitReason === 'incomplete' && attempts.length >= 2) {
|
|
456
|
+
const recentAttempts = attempts.slice(-2);
|
|
457
|
+
const allIncomplete = recentAttempts.every(a => a.exitReason === 'incomplete' && a.toolsUsed.length > 0);
|
|
458
|
+
if (allIncomplete) {
|
|
459
|
+
// Only stop if responses are also similar (model is stuck in a pattern)
|
|
460
|
+
const fingerprints = recentAttempts.map(a => this.computeResponseFingerprint(a.response));
|
|
461
|
+
if (fingerprints[0] === fingerprints[1] && fingerprints[0] !== '') {
|
|
462
|
+
return false; // Same response pattern with tools - stuck in a loop
|
|
463
|
+
}
|
|
406
464
|
}
|
|
407
465
|
}
|
|
408
466
|
return exitReason === 'incomplete';
|
|
@@ -711,6 +769,22 @@ ${actionLine}
|
|
|
711
769
|
return 0;
|
|
712
770
|
return text.trim().split(/\s+/).length;
|
|
713
771
|
}
|
|
772
|
+
/**
|
|
773
|
+
* Compute a simple fingerprint of a response for repetition detection.
|
|
774
|
+
* Normalizes whitespace and takes first N significant words to detect
|
|
775
|
+
* when the model is producing the same response repeatedly.
|
|
776
|
+
*/
|
|
777
|
+
computeResponseFingerprint(response) {
|
|
778
|
+
if (!response?.trim())
|
|
779
|
+
return '';
|
|
780
|
+
// Normalize: lowercase, collapse whitespace, take first 200 chars
|
|
781
|
+
const normalized = response.toLowerCase().replace(/\s+/g, ' ').trim();
|
|
782
|
+
// Extract key content - skip common filler words
|
|
783
|
+
const words = normalized.split(' ')
|
|
784
|
+
.filter(w => w.length > 3)
|
|
785
|
+
.slice(0, 30);
|
|
786
|
+
return words.join(' ');
|
|
787
|
+
}
|
|
714
788
|
/**
|
|
715
789
|
* Detects when a response is sufficiently detailed to stand on its own,
|
|
716
790
|
* avoiding false "incomplete" flags for thorough answers without explicit
|
|
@@ -725,5 +799,406 @@ ${actionLine}
|
|
|
725
799
|
const threshold = options.tookAction ? 80 : 120;
|
|
726
800
|
return options.wordCount >= threshold;
|
|
727
801
|
}
|
|
802
|
+
/**
|
|
803
|
+
* Early safety check for potentially harmful requests.
|
|
804
|
+
* Returns immediately with a helpful response instead of looping.
|
|
805
|
+
*
|
|
806
|
+
* This catches obvious cases before the model even processes them,
|
|
807
|
+
* preventing wasted compute and infinite "thinking" loops.
|
|
808
|
+
*/
|
|
809
|
+
checkForHarmfulRequest(request) {
|
|
810
|
+
const lower = request.toLowerCase();
|
|
811
|
+
// Known major companies/services that should NEVER be attacked without explicit authorization
|
|
812
|
+
const protectedTargets = [
|
|
813
|
+
'google', 'microsoft', 'apple', 'amazon', 'meta', 'facebook',
|
|
814
|
+
'twitter', 'x.com', 'linkedin', 'github', 'gitlab', 'netflix',
|
|
815
|
+
'spotify', 'openai', 'anthropic', 'cloudflare', 'aws', 'azure',
|
|
816
|
+
'gcp', 'oracle', 'ibm', 'cisco', 'adobe', 'salesforce', 'slack',
|
|
817
|
+
'zoom', 'dropbox', 'paypal', 'stripe', 'visa', 'mastercard',
|
|
818
|
+
];
|
|
819
|
+
// Action words that indicate malicious intent
|
|
820
|
+
const maliciousActions = [
|
|
821
|
+
'attack', 'hack', 'pwn', 'exploit', 'compromise', 'breach',
|
|
822
|
+
'ddos', 'dos', 'flood', 'crash', 'destroy', 'deface',
|
|
823
|
+
'steal', 'exfiltrate', 'dump', 'leak',
|
|
824
|
+
];
|
|
825
|
+
// Check for combined patterns: malicious action + protected target
|
|
826
|
+
const hasMaliciousAction = maliciousActions.some(action => lower.includes(action));
|
|
827
|
+
const hasProtectedTarget = protectedTargets.some(target => lower.includes(target));
|
|
828
|
+
// Only trigger for clear cases of unauthorized attacks
|
|
829
|
+
if (hasMaliciousAction && hasProtectedTarget) {
|
|
830
|
+
// Check for legitimate contexts that override this
|
|
831
|
+
const legitimateContexts = [
|
|
832
|
+
'ctf', 'capture the flag', 'hackthebox', 'tryhackme',
|
|
833
|
+
'lab', 'sandbox', 'authorized', 'pentest', 'penetration test',
|
|
834
|
+
'bug bounty', 'scope', 'engagement', 'test environment',
|
|
835
|
+
];
|
|
836
|
+
const hasLegitimateContext = legitimateContexts.some(ctx => lower.includes(ctx));
|
|
837
|
+
if (!hasLegitimateContext) {
|
|
838
|
+
return {
|
|
839
|
+
isHarmful: true,
|
|
840
|
+
response: `I can't help with unauthorized attacks against production systems or services.
|
|
841
|
+
|
|
842
|
+
For legitimate security work, I can help with:
|
|
843
|
+
• **CTF challenges** - Specify the CTF platform or challenge name
|
|
844
|
+
• **Authorized pentests** - Share the scope document or confirm you have written authorization
|
|
845
|
+
• **Bug bounty** - Confirm the target is in scope for a bug bounty program
|
|
846
|
+
• **Lab environments** - Specify it's a personal lab, HackTheBox, TryHackMe, etc.
|
|
847
|
+
• **Security research** - Describe the defensive or educational purpose
|
|
848
|
+
|
|
849
|
+
What authorized security work can I help you with?`,
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
// Also catch generic "attack everything" requests without any target
|
|
854
|
+
if (hasMaliciousAction && !lower.includes('ctf') && !lower.includes('lab')) {
|
|
855
|
+
const veryGenericPatterns = [
|
|
856
|
+
/attack\s+(any|random|all)/i,
|
|
857
|
+
/hack\s+(into|the|any)/i,
|
|
858
|
+
/ddos\s+(the|any|all)/i,
|
|
859
|
+
];
|
|
860
|
+
if (veryGenericPatterns.some(p => p.test(request))) {
|
|
861
|
+
return {
|
|
862
|
+
isHarmful: true,
|
|
863
|
+
response: `I need more context about what you're trying to accomplish.
|
|
864
|
+
|
|
865
|
+
If this is for:
|
|
866
|
+
• **CTF/Competition** - What platform or challenge?
|
|
867
|
+
• **Learning** - What concept are you trying to understand?
|
|
868
|
+
• **Authorized testing** - What's the scope and authorization?
|
|
869
|
+
|
|
870
|
+
Please provide context so I can help appropriately.`,
|
|
871
|
+
};
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
return { isHarmful: false, response: '' };
|
|
875
|
+
}
|
|
876
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
877
|
+
// ATTACK CHAIN EXECUTION
|
|
878
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
879
|
+
/**
|
|
880
|
+
* Detect if a request should trigger attack chain execution.
|
|
881
|
+
* Uses natural language parsing to identify security operation intents.
|
|
882
|
+
*/
|
|
883
|
+
shouldExecuteAttackChain(request, mode = 'auto') {
|
|
884
|
+
if (mode === 'disabled') {
|
|
885
|
+
return { shouldExecute: false, intent: null };
|
|
886
|
+
}
|
|
887
|
+
if (mode === 'forced') {
|
|
888
|
+
const intent = parseNaturalLanguageIntent(request);
|
|
889
|
+
return { shouldExecute: true, intent };
|
|
890
|
+
}
|
|
891
|
+
// Auto mode: parse intent and check for security operations
|
|
892
|
+
const intent = parseNaturalLanguageIntent(request);
|
|
893
|
+
const securityActions = ['recon', 'scan', 'enumerate', 'exploit', 'extract', 'test', 'monitor'];
|
|
894
|
+
const isSecurityOp = securityActions.includes(intent.action);
|
|
895
|
+
const hasTargets = intent.targets.length > 0;
|
|
896
|
+
return {
|
|
897
|
+
shouldExecute: isSecurityOp && hasTargets,
|
|
898
|
+
intent: isSecurityOp ? intent : null,
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
/**
|
|
902
|
+
* Validate authorization context for attack chain execution.
|
|
903
|
+
* Ensures proper authorization before executing security techniques.
|
|
904
|
+
*/
|
|
905
|
+
validateAttackAuthorization(intent, authContext) {
|
|
906
|
+
// Use the TAO authorization validator
|
|
907
|
+
const validation = validateAuthorizationContext(intent);
|
|
908
|
+
// Additional checks for protected targets
|
|
909
|
+
const protectedTargets = [
|
|
910
|
+
'google.com', 'microsoft.com', 'apple.com', 'amazon.com',
|
|
911
|
+
'facebook.com', 'twitter.com', 'github.com',
|
|
912
|
+
];
|
|
913
|
+
const targetingProtected = intent.targets.some(t => protectedTargets.some(pt => t.toLowerCase().includes(pt)));
|
|
914
|
+
if (targetingProtected && !validation.valid) {
|
|
915
|
+
return {
|
|
916
|
+
authorized: false,
|
|
917
|
+
reason: 'Targeting protected production systems without explicit authorization',
|
|
918
|
+
warnings: [
|
|
919
|
+
'Protected targets detected. Provide CTF/lab context or authorization scope.',
|
|
920
|
+
...validation.warnings,
|
|
921
|
+
],
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
// Check for explicit authorization context
|
|
925
|
+
if (authContext) {
|
|
926
|
+
const authorizedContexts = ['ctf', 'lab', 'pentest', 'bug-bounty', 'authorized'];
|
|
927
|
+
const hasExplicitAuth = authorizedContexts.some(ctx => authContext.toLowerCase().includes(ctx));
|
|
928
|
+
if (hasExplicitAuth) {
|
|
929
|
+
return {
|
|
930
|
+
authorized: true,
|
|
931
|
+
reason: `Explicit authorization: ${authContext}`,
|
|
932
|
+
warnings: validation.warnings,
|
|
933
|
+
};
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
return {
|
|
937
|
+
authorized: validation.valid,
|
|
938
|
+
reason: validation.type,
|
|
939
|
+
warnings: validation.warnings,
|
|
940
|
+
};
|
|
941
|
+
}
|
|
942
|
+
/**
|
|
943
|
+
* Execute an attack chain with TAO techniques.
|
|
944
|
+
* Returns results including all executed techniques and artifacts.
|
|
945
|
+
*/
|
|
946
|
+
async executeAttackChain(request, options = {}) {
|
|
947
|
+
const intent = parseNaturalLanguageIntent(request);
|
|
948
|
+
const targets = options.targets ?? intent.targets;
|
|
949
|
+
// Normalize depth - map 'comprehensive' to 'deep'
|
|
950
|
+
const rawDepth = options.depth ?? intent.depth;
|
|
951
|
+
const depth = rawDepth === 'comprehensive' ? 'deep' : rawDepth;
|
|
952
|
+
const stealth = options.stealth ?? intent.constraints.includes('stealth');
|
|
953
|
+
// Validate authorization
|
|
954
|
+
const auth = this.validateAttackAuthorization(intent, options.authContext);
|
|
955
|
+
if (!auth.authorized) {
|
|
956
|
+
throw new Error(`Attack chain execution not authorized: ${auth.reason}`);
|
|
957
|
+
}
|
|
958
|
+
const executedTechniques = [];
|
|
959
|
+
const phasesCompleted = new Set();
|
|
960
|
+
const startTime = Date.now();
|
|
961
|
+
// Execute chain for each target
|
|
962
|
+
for (const target of targets) {
|
|
963
|
+
const chain = planAttackChain(intent, `Attack chain: ${target}`);
|
|
964
|
+
while (chain.state === 'planning' || chain.state === 'executing') {
|
|
965
|
+
const action = selectNextTechnique(chain);
|
|
966
|
+
if (!action)
|
|
967
|
+
break;
|
|
968
|
+
const technique = techniqueRegistry.get(action.id);
|
|
969
|
+
if (!technique)
|
|
970
|
+
continue;
|
|
971
|
+
const params = {
|
|
972
|
+
target,
|
|
973
|
+
depth,
|
|
974
|
+
stealth,
|
|
975
|
+
timeout: depth === 'deep' ? 60000 : depth === 'standard' ? 30000 : 10000,
|
|
976
|
+
context: {
|
|
977
|
+
chainId: chain.id,
|
|
978
|
+
phase: technique.phase,
|
|
979
|
+
previousArtifacts: executedTechniques
|
|
980
|
+
.filter(t => t.success)
|
|
981
|
+
.flatMap(t => t.artifacts),
|
|
982
|
+
},
|
|
983
|
+
};
|
|
984
|
+
try {
|
|
985
|
+
const { result } = await executeTechniqueInChain(chain, action, params);
|
|
986
|
+
executedTechniques.push({
|
|
987
|
+
id: technique.id,
|
|
988
|
+
name: technique.name,
|
|
989
|
+
phase: technique.phase,
|
|
990
|
+
success: result.success,
|
|
991
|
+
duration: result.duration,
|
|
992
|
+
artifacts: result.artifacts,
|
|
993
|
+
});
|
|
994
|
+
if (result.success) {
|
|
995
|
+
phasesCompleted.add(technique.phase);
|
|
996
|
+
}
|
|
997
|
+
options.onProgress?.(chain, technique.id, result);
|
|
998
|
+
}
|
|
999
|
+
catch (err) {
|
|
1000
|
+
// Log but continue chain execution
|
|
1001
|
+
executedTechniques.push({
|
|
1002
|
+
id: technique.id,
|
|
1003
|
+
name: technique.name,
|
|
1004
|
+
phase: technique.phase,
|
|
1005
|
+
success: false,
|
|
1006
|
+
duration: 0,
|
|
1007
|
+
artifacts: [{ type: 'error', data: String(err) }],
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
const successCount = executedTechniques.filter(t => t.success).length;
|
|
1013
|
+
return {
|
|
1014
|
+
chain: planAttackChain(intent, request), // Return final chain state
|
|
1015
|
+
techniques: executedTechniques,
|
|
1016
|
+
totalDuration: Date.now() - startTime,
|
|
1017
|
+
successRate: executedTechniques.length > 0
|
|
1018
|
+
? successCount / executedTechniques.length
|
|
1019
|
+
: 0,
|
|
1020
|
+
phasesCompleted: Array.from(phasesCompleted),
|
|
1021
|
+
};
|
|
1022
|
+
}
|
|
1023
|
+
/**
|
|
1024
|
+
* Run orchestration with optional attack chain integration.
|
|
1025
|
+
* When attack chain mode is enabled, security operations are executed
|
|
1026
|
+
* directly through TAO techniques rather than relying on LLM tool calls.
|
|
1027
|
+
*/
|
|
1028
|
+
async runWithAttackChain(request, options = {}) {
|
|
1029
|
+
const attackChainMode = options.attackChainMode ?? 'auto';
|
|
1030
|
+
// Check if we should execute attack chain
|
|
1031
|
+
const { shouldExecute, intent } = this.shouldExecuteAttackChain(request, attackChainMode);
|
|
1032
|
+
if (!shouldExecute || !intent) {
|
|
1033
|
+
// Fall back to normal orchestration
|
|
1034
|
+
return this.runToCompletion(request, options);
|
|
1035
|
+
}
|
|
1036
|
+
// Validate authorization
|
|
1037
|
+
const auth = this.validateAttackAuthorization(intent, options.authorizationContext);
|
|
1038
|
+
if (!auth.authorized) {
|
|
1039
|
+
return {
|
|
1040
|
+
finalResponse: `Cannot execute security operation: ${auth.reason}\n\n${auth.warnings.join('\n')}`,
|
|
1041
|
+
toolsUsed: [],
|
|
1042
|
+
planOnly: false,
|
|
1043
|
+
tookAction: false,
|
|
1044
|
+
completion: {
|
|
1045
|
+
isComplete: true,
|
|
1046
|
+
confidence: 1.0,
|
|
1047
|
+
signals: {
|
|
1048
|
+
hasExplicitCompletionStatement: true,
|
|
1049
|
+
hasIncompleteWorkIndicators: false,
|
|
1050
|
+
hasPendingActionIndicators: false,
|
|
1051
|
+
hasErrorIndicators: false,
|
|
1052
|
+
hasFollowUpQuestions: false,
|
|
1053
|
+
toolsUsedInLastResponse: 0,
|
|
1054
|
+
lastToolWasReadOnly: false,
|
|
1055
|
+
consecutiveResponsesWithoutTools: 0,
|
|
1056
|
+
hasRecentFileWrites: false,
|
|
1057
|
+
hasRecentCommits: false,
|
|
1058
|
+
todoItemsPending: 0,
|
|
1059
|
+
todoItemsCompleted: 0,
|
|
1060
|
+
mentionsFutureWork: false,
|
|
1061
|
+
completionConfidence: 1.0,
|
|
1062
|
+
},
|
|
1063
|
+
reason: 'Authorization required',
|
|
1064
|
+
shouldVerify: false,
|
|
1065
|
+
},
|
|
1066
|
+
exitReason: 'attack-chain-aborted',
|
|
1067
|
+
statusSummary: `Authorization required: ${auth.reason}`,
|
|
1068
|
+
limitations: auth.warnings,
|
|
1069
|
+
recommendations: [
|
|
1070
|
+
'Provide explicit authorization context (CTF, lab, pentest scope)',
|
|
1071
|
+
'Use --auth-context flag to specify authorization',
|
|
1072
|
+
],
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
// Execute attack chain
|
|
1076
|
+
try {
|
|
1077
|
+
// Normalize depth for attack chain
|
|
1078
|
+
const attackRawDepth = options.attackDepth ?? intent.depth;
|
|
1079
|
+
const attackDepth = attackRawDepth === 'comprehensive' ? 'deep' : attackRawDepth;
|
|
1080
|
+
const chainResult = await this.executeAttackChain(request, {
|
|
1081
|
+
targets: options.attackTargets ?? intent.targets,
|
|
1082
|
+
depth: attackDepth,
|
|
1083
|
+
stealth: options.stealthMode ?? intent.constraints.includes('stealth'),
|
|
1084
|
+
authContext: options.authorizationContext,
|
|
1085
|
+
onProgress: options.onAttackChainProgress,
|
|
1086
|
+
});
|
|
1087
|
+
// Build response summary
|
|
1088
|
+
const summary = this.buildAttackChainSummary(chainResult);
|
|
1089
|
+
return {
|
|
1090
|
+
finalResponse: summary,
|
|
1091
|
+
toolsUsed: chainResult.techniques.map(t => t.id),
|
|
1092
|
+
planOnly: false,
|
|
1093
|
+
tookAction: true,
|
|
1094
|
+
completion: {
|
|
1095
|
+
isComplete: true,
|
|
1096
|
+
confidence: chainResult.successRate,
|
|
1097
|
+
signals: {
|
|
1098
|
+
hasExplicitCompletionStatement: true,
|
|
1099
|
+
hasIncompleteWorkIndicators: false,
|
|
1100
|
+
hasPendingActionIndicators: false,
|
|
1101
|
+
hasErrorIndicators: chainResult.successRate < 0.5,
|
|
1102
|
+
hasFollowUpQuestions: false,
|
|
1103
|
+
toolsUsedInLastResponse: chainResult.techniques.length,
|
|
1104
|
+
lastToolWasReadOnly: false,
|
|
1105
|
+
consecutiveResponsesWithoutTools: 0,
|
|
1106
|
+
hasRecentFileWrites: false,
|
|
1107
|
+
hasRecentCommits: false,
|
|
1108
|
+
todoItemsPending: 0,
|
|
1109
|
+
todoItemsCompleted: chainResult.techniques.length,
|
|
1110
|
+
mentionsFutureWork: false,
|
|
1111
|
+
completionConfidence: chainResult.successRate,
|
|
1112
|
+
},
|
|
1113
|
+
reason: 'Attack chain completed',
|
|
1114
|
+
shouldVerify: chainResult.successRate < 1.0,
|
|
1115
|
+
},
|
|
1116
|
+
exitReason: 'attack-chain-complete',
|
|
1117
|
+
statusSummary: `Attack chain: ${chainResult.techniques.length} techniques, ${Math.round(chainResult.successRate * 100)}% success`,
|
|
1118
|
+
limitations: [],
|
|
1119
|
+
recommendations: chainResult.successRate < 1.0
|
|
1120
|
+
? ['Review failed techniques and adjust approach']
|
|
1121
|
+
: [],
|
|
1122
|
+
attackChainResult: chainResult,
|
|
1123
|
+
};
|
|
1124
|
+
}
|
|
1125
|
+
catch (err) {
|
|
1126
|
+
return {
|
|
1127
|
+
finalResponse: `Attack chain execution failed: ${String(err)}`,
|
|
1128
|
+
toolsUsed: [],
|
|
1129
|
+
planOnly: false,
|
|
1130
|
+
tookAction: false,
|
|
1131
|
+
completion: {
|
|
1132
|
+
isComplete: true,
|
|
1133
|
+
confidence: 0,
|
|
1134
|
+
signals: {
|
|
1135
|
+
hasExplicitCompletionStatement: true,
|
|
1136
|
+
hasIncompleteWorkIndicators: false,
|
|
1137
|
+
hasPendingActionIndicators: false,
|
|
1138
|
+
hasErrorIndicators: true,
|
|
1139
|
+
hasFollowUpQuestions: false,
|
|
1140
|
+
toolsUsedInLastResponse: 0,
|
|
1141
|
+
lastToolWasReadOnly: false,
|
|
1142
|
+
consecutiveResponsesWithoutTools: 0,
|
|
1143
|
+
hasRecentFileWrites: false,
|
|
1144
|
+
hasRecentCommits: false,
|
|
1145
|
+
todoItemsPending: 0,
|
|
1146
|
+
todoItemsCompleted: 0,
|
|
1147
|
+
mentionsFutureWork: false,
|
|
1148
|
+
completionConfidence: 0,
|
|
1149
|
+
},
|
|
1150
|
+
reason: `Error: ${String(err)}`,
|
|
1151
|
+
shouldVerify: false,
|
|
1152
|
+
},
|
|
1153
|
+
exitReason: 'attack-chain-aborted',
|
|
1154
|
+
statusSummary: `Attack chain failed: ${String(err)}`,
|
|
1155
|
+
limitations: [String(err)],
|
|
1156
|
+
recommendations: ['Check target connectivity', 'Verify authorization context'],
|
|
1157
|
+
};
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
/**
|
|
1161
|
+
* Build a human-readable summary of attack chain execution.
|
|
1162
|
+
*/
|
|
1163
|
+
buildAttackChainSummary(result) {
|
|
1164
|
+
const lines = [];
|
|
1165
|
+
lines.push('## Attack Chain Execution Summary\n');
|
|
1166
|
+
// Overall stats
|
|
1167
|
+
lines.push(`**Duration:** ${Math.round(result.totalDuration / 1000)}s`);
|
|
1168
|
+
lines.push(`**Success Rate:** ${Math.round(result.successRate * 100)}%`);
|
|
1169
|
+
lines.push(`**Phases Completed:** ${result.phasesCompleted.join(', ')}\n`);
|
|
1170
|
+
// Technique breakdown
|
|
1171
|
+
lines.push('### Techniques Executed\n');
|
|
1172
|
+
const byPhase = new Map();
|
|
1173
|
+
for (const tech of result.techniques) {
|
|
1174
|
+
const list = byPhase.get(tech.phase) || [];
|
|
1175
|
+
list.push(tech);
|
|
1176
|
+
byPhase.set(tech.phase, list);
|
|
1177
|
+
}
|
|
1178
|
+
for (const [phase, techniques] of byPhase) {
|
|
1179
|
+
lines.push(`#### ${phase}`);
|
|
1180
|
+
for (const tech of techniques) {
|
|
1181
|
+
const status = tech.success ? '✓' : '✗';
|
|
1182
|
+
lines.push(`- ${status} **${tech.name}** (${Math.round(tech.duration / 1000)}s)`);
|
|
1183
|
+
if (tech.artifacts.length > 0) {
|
|
1184
|
+
lines.push(` - Artifacts: ${tech.artifacts.length} collected`);
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
lines.push('');
|
|
1188
|
+
}
|
|
1189
|
+
// Artifacts summary
|
|
1190
|
+
const allArtifacts = result.techniques.flatMap(t => t.artifacts);
|
|
1191
|
+
if (allArtifacts.length > 0) {
|
|
1192
|
+
lines.push('### Collected Artifacts\n');
|
|
1193
|
+
const artifactsByType = new Map();
|
|
1194
|
+
for (const artifact of allArtifacts) {
|
|
1195
|
+
artifactsByType.set(artifact.type, (artifactsByType.get(artifact.type) || 0) + 1);
|
|
1196
|
+
}
|
|
1197
|
+
for (const [type, count] of artifactsByType) {
|
|
1198
|
+
lines.push(`- **${type}:** ${count}`);
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
return lines.join('\n');
|
|
1202
|
+
}
|
|
728
1203
|
}
|
|
729
1204
|
//# sourceMappingURL=agentOrchestrator.js.map
|