erosolar-cli 2.1.243 → 2.1.244
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/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/capabilities/unifiedInvestigationCapability.d.ts +22 -0
- package/dist/capabilities/unifiedInvestigationCapability.d.ts.map +1 -0
- package/dist/capabilities/unifiedInvestigationCapability.js +41 -0
- package/dist/capabilities/unifiedInvestigationCapability.js.map +1 -0
- package/dist/core/agentOrchestrator.d.ts +130 -1
- package/dist/core/agentOrchestrator.d.ts.map +1 -1
- package/dist/core/agentOrchestrator.js +553 -1
- package/dist/core/agentOrchestrator.js.map +1 -1
- package/dist/core/unifiedFraudOrchestrator.d.ts +542 -0
- package/dist/core/unifiedFraudOrchestrator.d.ts.map +1 -0
- package/dist/core/unifiedFraudOrchestrator.js +1449 -0
- package/dist/core/unifiedFraudOrchestrator.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/plugins/tools/unifiedInvestigation/unifiedInvestigationPlugin.d.ts +3 -0
- package/dist/plugins/tools/unifiedInvestigation/unifiedInvestigationPlugin.d.ts.map +1 -0
- package/dist/plugins/tools/unifiedInvestigation/unifiedInvestigationPlugin.js +14 -0
- package/dist/plugins/tools/unifiedInvestigation/unifiedInvestigationPlugin.js.map +1 -0
- package/dist/tools/taoTools.d.ts.map +1 -1
- package/dist/tools/taoTools.js +790 -4
- package/dist/tools/taoTools.js.map +1 -1
- package/dist/tools/unifiedInvestigationTools.d.ts +19 -0
- package/dist/tools/unifiedInvestigationTools.d.ts.map +1 -0
- package/dist/tools/unifiedInvestigationTools.js +851 -0
- package/dist/tools/unifiedInvestigationTools.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,1449 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified Tech Fraud Investigation Orchestrator
|
|
3
|
+
*
|
|
4
|
+
* Purpose: Coordinate investigation of tech company fraud across multiple vectors:
|
|
5
|
+
* - Apple iMessage PQ3 (key substitution, false E2E claims)
|
|
6
|
+
* - Google Gmail (hidden threads, draft manipulation, unauthorized access)
|
|
7
|
+
* - Google Chrome (browser hijacking, session control, surveillance)
|
|
8
|
+
* - Cross-platform evidence correlation
|
|
9
|
+
*
|
|
10
|
+
* This orchestrator:
|
|
11
|
+
* 1. Manages investigation workflows across multiple targets
|
|
12
|
+
* 2. Correlates evidence across different fraud vectors
|
|
13
|
+
* 3. Detects patterns indicating coordinated manipulation
|
|
14
|
+
* 4. Generates unified legal evidence packages
|
|
15
|
+
* 5. Maintains cryptographic chain of custody
|
|
16
|
+
*/
|
|
17
|
+
import * as crypto from 'node:crypto';
|
|
18
|
+
import * as fs from 'node:fs/promises';
|
|
19
|
+
import * as path from 'node:path';
|
|
20
|
+
import { EventEmitter } from 'node:events';
|
|
21
|
+
import { IntegrityVerificationEngine, hashString, } from './integrityVerification.js';
|
|
22
|
+
import { iMessageVerificationEngine, APPLE_PQ3_CLAIMS, } from './iMessageVerification.js';
|
|
23
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
24
|
+
// Google Security Claims (like Apple PQ3 claims)
|
|
25
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
26
|
+
export const GOOGLE_GMAIL_CLAIMS = {
|
|
27
|
+
user_control: {
|
|
28
|
+
claim: "You're in control of your data in Gmail. You can export and delete your data anytime.",
|
|
29
|
+
source: 'https://safety.google/gmail/',
|
|
30
|
+
verifiable: 'partial',
|
|
31
|
+
reason: 'Cannot verify server-side manipulation of what is shown to user'
|
|
32
|
+
},
|
|
33
|
+
no_email_scanning_for_ads: {
|
|
34
|
+
claim: 'Gmail does not scan or read your emails for advertising purposes.',
|
|
35
|
+
source: 'https://support.google.com/mail/answer/6603',
|
|
36
|
+
verifiable: false,
|
|
37
|
+
reason: 'Closed source server, cannot verify processing'
|
|
38
|
+
},
|
|
39
|
+
confidential_mode: {
|
|
40
|
+
claim: 'Confidential mode helps protect sensitive information from unauthorized access.',
|
|
41
|
+
source: 'https://support.google.com/mail/answer/7674059',
|
|
42
|
+
verifiable: false,
|
|
43
|
+
reason: 'Google retains access to content and revocation keys'
|
|
44
|
+
},
|
|
45
|
+
security_alerts: {
|
|
46
|
+
claim: 'We notify you of suspicious activity on your account.',
|
|
47
|
+
source: 'https://support.google.com/accounts/answer/2590353',
|
|
48
|
+
verifiable: 'partial',
|
|
49
|
+
reason: 'Cannot verify all access is reported; Google-controlled infrastructure'
|
|
50
|
+
},
|
|
51
|
+
search_accuracy: {
|
|
52
|
+
claim: 'Search in Gmail finds all matching messages in your mailbox.',
|
|
53
|
+
source: 'https://support.google.com/mail/answer/7190',
|
|
54
|
+
verifiable: true,
|
|
55
|
+
reason: 'Can test by cross-referencing with IMAP/Takeout'
|
|
56
|
+
},
|
|
57
|
+
draft_integrity: {
|
|
58
|
+
claim: 'Drafts are saved automatically and preserved until you delete or send them.',
|
|
59
|
+
source: 'https://support.google.com/mail/answer/7684',
|
|
60
|
+
verifiable: true,
|
|
61
|
+
reason: 'Can monitor draft state over time and detect unauthorized changes'
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
export const GOOGLE_CHROME_CLAIMS = {
|
|
65
|
+
safe_browsing: {
|
|
66
|
+
claim: 'Safe Browsing protects you from dangerous sites and downloads.',
|
|
67
|
+
source: 'https://safebrowsing.google.com/',
|
|
68
|
+
verifiable: 'partial',
|
|
69
|
+
reason: 'Also reports all browsing URLs to Google servers'
|
|
70
|
+
},
|
|
71
|
+
sync_privacy: {
|
|
72
|
+
claim: 'Sync data is encrypted in transit and at rest.',
|
|
73
|
+
source: 'https://support.google.com/chrome/answer/165139',
|
|
74
|
+
verifiable: false,
|
|
75
|
+
reason: 'Google holds encryption keys; can access synced data'
|
|
76
|
+
},
|
|
77
|
+
user_control_history: {
|
|
78
|
+
claim: 'You can view and delete your browsing history at any time.',
|
|
79
|
+
source: 'https://support.google.com/chrome/answer/95589',
|
|
80
|
+
verifiable: 'partial',
|
|
81
|
+
reason: 'Cannot verify server-side retention after "deletion"'
|
|
82
|
+
},
|
|
83
|
+
no_unauthorized_access: {
|
|
84
|
+
claim: 'Chrome only accesses your data when you explicitly use a Google service.',
|
|
85
|
+
source: 'https://www.google.com/chrome/privacy/',
|
|
86
|
+
verifiable: true,
|
|
87
|
+
reason: 'Can monitor Chrome process network activity and launches'
|
|
88
|
+
},
|
|
89
|
+
extension_security: {
|
|
90
|
+
claim: 'Extensions must request permissions and users can control what extensions access.',
|
|
91
|
+
source: 'https://support.google.com/chrome_webstore/answer/2664769',
|
|
92
|
+
verifiable: 'partial',
|
|
93
|
+
reason: 'Cannot verify extension behavior matches stated permissions'
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
97
|
+
// GMAIL INVESTIGATION ENGINE
|
|
98
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
99
|
+
export class GmailInvestigationEngine {
|
|
100
|
+
storageDir;
|
|
101
|
+
threadObservations = new Map();
|
|
102
|
+
draftObservations = new Map();
|
|
103
|
+
filterObservations = [];
|
|
104
|
+
accessLogs = [];
|
|
105
|
+
findings = [];
|
|
106
|
+
constructor(storageDir) {
|
|
107
|
+
this.storageDir = path.join(storageDir, 'gmail-investigation');
|
|
108
|
+
}
|
|
109
|
+
async initialize() {
|
|
110
|
+
await fs.mkdir(this.storageDir, { recursive: true });
|
|
111
|
+
await fs.mkdir(path.join(this.storageDir, 'threads'), { recursive: true });
|
|
112
|
+
await fs.mkdir(path.join(this.storageDir, 'drafts'), { recursive: true });
|
|
113
|
+
await fs.mkdir(path.join(this.storageDir, 'filters'), { recursive: true });
|
|
114
|
+
await fs.mkdir(path.join(this.storageDir, 'access'), { recursive: true });
|
|
115
|
+
await fs.mkdir(path.join(this.storageDir, 'findings'), { recursive: true });
|
|
116
|
+
}
|
|
117
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
118
|
+
// Thread Monitoring (Hidden Threads Detection)
|
|
119
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
120
|
+
/**
|
|
121
|
+
* Record observation of Gmail threads.
|
|
122
|
+
* Compare with previous observations to detect hidden threads.
|
|
123
|
+
*
|
|
124
|
+
* Detection methods:
|
|
125
|
+
* 1. Compare UI-visible threads vs API-returned threads
|
|
126
|
+
* 2. Compare search results vs direct thread access
|
|
127
|
+
* 3. Compare current state vs Google Takeout export
|
|
128
|
+
* 4. Monitor for threads that disappear from search but still exist
|
|
129
|
+
*/
|
|
130
|
+
async recordThreadObservation(params) {
|
|
131
|
+
const now = new Date().toISOString();
|
|
132
|
+
const observation = {
|
|
133
|
+
id: crypto.randomUUID(),
|
|
134
|
+
timestamp: now,
|
|
135
|
+
...params,
|
|
136
|
+
hash: '',
|
|
137
|
+
};
|
|
138
|
+
observation.hash = hashString(JSON.stringify({
|
|
139
|
+
id: observation.id,
|
|
140
|
+
timestamp: observation.timestamp,
|
|
141
|
+
threadId: observation.threadId,
|
|
142
|
+
messageIds: observation.messageIds,
|
|
143
|
+
isVisible: observation.isVisible,
|
|
144
|
+
isInSearch: observation.isInSearch,
|
|
145
|
+
}));
|
|
146
|
+
// Get previous observations for this thread
|
|
147
|
+
const previous = this.threadObservations.get(params.threadId) || [];
|
|
148
|
+
const lastObs = previous[previous.length - 1];
|
|
149
|
+
let anomalyDetected = false;
|
|
150
|
+
let finding;
|
|
151
|
+
// Check for anomalies
|
|
152
|
+
if (lastObs) {
|
|
153
|
+
// Thread was visible, now hidden
|
|
154
|
+
if (lastObs.isVisible && !params.isVisible) {
|
|
155
|
+
anomalyDetected = true;
|
|
156
|
+
finding = await this.createFinding({
|
|
157
|
+
vector: 'gmail_hidden_threads',
|
|
158
|
+
severity: 'high',
|
|
159
|
+
title: `Thread hidden: "${params.subject}"`,
|
|
160
|
+
description: `Thread ${params.threadId} was visible in previous observation but is now hidden. This may indicate manipulation by Google to hide communications from the user.`,
|
|
161
|
+
technicalDetails: {
|
|
162
|
+
threadId: params.threadId,
|
|
163
|
+
previouslyVisible: true,
|
|
164
|
+
currentlyVisible: false,
|
|
165
|
+
wasInSearch: lastObs.isInSearch,
|
|
166
|
+
nowInSearch: params.isInSearch,
|
|
167
|
+
timeSinceLastObservation: new Date(now).getTime() - new Date(lastObs.timestamp).getTime(),
|
|
168
|
+
},
|
|
169
|
+
evidenceIds: [lastObs.id, observation.id],
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
// Thread was in search, now excluded
|
|
173
|
+
if (lastObs.isInSearch && !params.isInSearch && params.isVisible) {
|
|
174
|
+
anomalyDetected = true;
|
|
175
|
+
finding = finding || await this.createFinding({
|
|
176
|
+
vector: 'gmail_hidden_threads',
|
|
177
|
+
severity: 'medium',
|
|
178
|
+
title: `Thread excluded from search: "${params.subject}"`,
|
|
179
|
+
description: `Thread ${params.threadId} is visible but no longer appears in search results. Google may be suppressing this thread from discovery.`,
|
|
180
|
+
technicalDetails: {
|
|
181
|
+
threadId: params.threadId,
|
|
182
|
+
visible: true,
|
|
183
|
+
excludedFromSearch: true,
|
|
184
|
+
},
|
|
185
|
+
evidenceIds: [lastObs.id, observation.id],
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
// Messages disappeared from thread
|
|
189
|
+
const missingMessages = lastObs.messageIds.filter(id => !params.messageIds.includes(id));
|
|
190
|
+
if (missingMessages.length > 0) {
|
|
191
|
+
anomalyDetected = true;
|
|
192
|
+
finding = finding || await this.createFinding({
|
|
193
|
+
vector: 'gmail_hidden_threads',
|
|
194
|
+
severity: 'critical',
|
|
195
|
+
title: `Messages removed from thread: "${params.subject}"`,
|
|
196
|
+
description: `${missingMessages.length} messages have been removed from thread ${params.threadId} without user action.`,
|
|
197
|
+
technicalDetails: {
|
|
198
|
+
threadId: params.threadId,
|
|
199
|
+
missingMessageIds: missingMessages,
|
|
200
|
+
previousMessageCount: lastObs.messageIds.length,
|
|
201
|
+
currentMessageCount: params.messageIds.length,
|
|
202
|
+
},
|
|
203
|
+
evidenceIds: [lastObs.id, observation.id],
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
// Inconsistency: exists but not visible and not in search
|
|
208
|
+
if (!params.isVisible && !params.isInSearch) {
|
|
209
|
+
anomalyDetected = true;
|
|
210
|
+
finding = finding || await this.createFinding({
|
|
211
|
+
vector: 'gmail_hidden_threads',
|
|
212
|
+
severity: 'high',
|
|
213
|
+
title: `Thread completely hidden: "${params.subject}"`,
|
|
214
|
+
description: `Thread ${params.threadId} exists but is neither visible in inbox views nor discoverable via search. This is strong evidence of deliberate hiding.`,
|
|
215
|
+
technicalDetails: {
|
|
216
|
+
threadId: params.threadId,
|
|
217
|
+
captureMethod: params.captureMethod,
|
|
218
|
+
lastMessageDate: params.lastMessageDate,
|
|
219
|
+
},
|
|
220
|
+
evidenceIds: [observation.id],
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
// Store observation
|
|
224
|
+
previous.push(observation);
|
|
225
|
+
this.threadObservations.set(params.threadId, previous);
|
|
226
|
+
await this.persistThreadObservation(observation);
|
|
227
|
+
return { observation, anomalyDetected, finding };
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Compare multiple capture methods to detect discrepancies.
|
|
231
|
+
* If API shows different threads than UI or Takeout, that's manipulation.
|
|
232
|
+
*/
|
|
233
|
+
async crossReferenceThreadSources(params) {
|
|
234
|
+
const allThreadIds = new Set([
|
|
235
|
+
...params.apiThreadIds,
|
|
236
|
+
...params.uiThreadIds,
|
|
237
|
+
...(params.takeoutThreadIds || []),
|
|
238
|
+
...(params.imapThreadIds || []),
|
|
239
|
+
]);
|
|
240
|
+
const discrepancies = [];
|
|
241
|
+
for (const threadId of allThreadIds) {
|
|
242
|
+
const presentIn = [];
|
|
243
|
+
const missingFrom = [];
|
|
244
|
+
if (params.apiThreadIds.includes(threadId))
|
|
245
|
+
presentIn.push('api');
|
|
246
|
+
else
|
|
247
|
+
missingFrom.push('api');
|
|
248
|
+
if (params.uiThreadIds.includes(threadId))
|
|
249
|
+
presentIn.push('ui');
|
|
250
|
+
else
|
|
251
|
+
missingFrom.push('ui');
|
|
252
|
+
if (params.takeoutThreadIds?.includes(threadId))
|
|
253
|
+
presentIn.push('takeout');
|
|
254
|
+
else if (params.takeoutThreadIds)
|
|
255
|
+
missingFrom.push('takeout');
|
|
256
|
+
if (params.imapThreadIds?.includes(threadId))
|
|
257
|
+
presentIn.push('imap');
|
|
258
|
+
else if (params.imapThreadIds)
|
|
259
|
+
missingFrom.push('imap');
|
|
260
|
+
if (missingFrom.length > 0 && presentIn.length > 0) {
|
|
261
|
+
let implication = '';
|
|
262
|
+
if (missingFrom.includes('ui') && presentIn.includes('api')) {
|
|
263
|
+
implication = 'Thread exists but is hidden from user interface - deliberate UI manipulation';
|
|
264
|
+
}
|
|
265
|
+
else if (missingFrom.includes('api') && presentIn.includes('takeout')) {
|
|
266
|
+
implication = 'Thread in export but not in API - potential retroactive hiding';
|
|
267
|
+
}
|
|
268
|
+
else if (presentIn.includes('imap') && missingFrom.includes('ui')) {
|
|
269
|
+
implication = 'Thread accessible via IMAP but hidden in webmail - selective hiding';
|
|
270
|
+
}
|
|
271
|
+
discrepancies.push({ threadId, presentIn, missingFrom, implication });
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
let finding;
|
|
275
|
+
if (discrepancies.length > 0) {
|
|
276
|
+
finding = await this.createFinding({
|
|
277
|
+
vector: 'gmail_hidden_threads',
|
|
278
|
+
severity: discrepancies.length > 5 ? 'critical' : 'high',
|
|
279
|
+
title: `Cross-reference reveals ${discrepancies.length} hidden threads`,
|
|
280
|
+
description: `Comparison of Gmail data sources reveals threads that exist in some views but are hidden from others. This is direct evidence of selective content manipulation.`,
|
|
281
|
+
technicalDetails: {
|
|
282
|
+
discrepancyCount: discrepancies.length,
|
|
283
|
+
discrepancies,
|
|
284
|
+
sourcesCrossReferenced: ['api', 'ui', params.takeoutThreadIds ? 'takeout' : null, params.imapThreadIds ? 'imap' : null].filter(Boolean),
|
|
285
|
+
},
|
|
286
|
+
evidenceIds: [],
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
return { discrepancies, finding };
|
|
290
|
+
}
|
|
291
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
292
|
+
// Draft Monitoring (Draft Manipulation Detection)
|
|
293
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
294
|
+
/**
|
|
295
|
+
* Record observation of a draft.
|
|
296
|
+
* Detect unauthorized modifications, deletions, or sends.
|
|
297
|
+
*/
|
|
298
|
+
async recordDraftObservation(params) {
|
|
299
|
+
const now = new Date().toISOString();
|
|
300
|
+
const bodyHash = hashString(params.bodyContent);
|
|
301
|
+
const observation = {
|
|
302
|
+
id: crypto.randomUUID(),
|
|
303
|
+
timestamp: now,
|
|
304
|
+
draftId: params.draftId,
|
|
305
|
+
threadId: params.threadId,
|
|
306
|
+
subject: params.subject,
|
|
307
|
+
recipientCount: params.recipientCount,
|
|
308
|
+
bodyHash,
|
|
309
|
+
bodyLength: params.bodyContent.length,
|
|
310
|
+
hasAttachments: params.hasAttachments,
|
|
311
|
+
createdAt: params.createdAt,
|
|
312
|
+
modifiedAt: params.modifiedAt,
|
|
313
|
+
captureMethod: params.captureMethod,
|
|
314
|
+
hash: '',
|
|
315
|
+
};
|
|
316
|
+
// Get previous observations
|
|
317
|
+
const previous = this.draftObservations.get(params.draftId) || [];
|
|
318
|
+
const lastObs = previous[previous.length - 1];
|
|
319
|
+
const changes = [];
|
|
320
|
+
if (lastObs) {
|
|
321
|
+
observation.previousObservationId = lastObs.id;
|
|
322
|
+
// Check for content changes
|
|
323
|
+
if (lastObs.bodyHash !== bodyHash) {
|
|
324
|
+
const wasUserModified = new Date(params.modifiedAt) > new Date(lastObs.timestamp);
|
|
325
|
+
changes.push({
|
|
326
|
+
type: 'content_changed',
|
|
327
|
+
field: 'body',
|
|
328
|
+
previousValue: `hash:${lastObs.bodyHash.slice(0, 16)}... (${lastObs.bodyLength} chars)`,
|
|
329
|
+
newValue: `hash:${bodyHash.slice(0, 16)}... (${params.bodyContent.length} chars)`,
|
|
330
|
+
userInitiated: wasUserModified, // heuristic - needs better detection
|
|
331
|
+
suspicionLevel: wasUserModified ? 'normal' : 'highly_suspicious',
|
|
332
|
+
reason: wasUserModified ? 'Draft modified' : 'Draft content changed without user modification timestamp update',
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
// Check for subject changes
|
|
336
|
+
if (lastObs.subject !== params.subject) {
|
|
337
|
+
changes.push({
|
|
338
|
+
type: 'modified',
|
|
339
|
+
field: 'subject',
|
|
340
|
+
previousValue: lastObs.subject,
|
|
341
|
+
newValue: params.subject,
|
|
342
|
+
userInitiated: false, // cannot determine
|
|
343
|
+
suspicionLevel: 'suspicious',
|
|
344
|
+
reason: 'Subject line changed',
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
// Check for recipient changes
|
|
348
|
+
if (lastObs.recipientCount !== params.recipientCount) {
|
|
349
|
+
changes.push({
|
|
350
|
+
type: 'modified',
|
|
351
|
+
field: 'recipients',
|
|
352
|
+
previousValue: String(lastObs.recipientCount),
|
|
353
|
+
newValue: String(params.recipientCount),
|
|
354
|
+
userInitiated: false,
|
|
355
|
+
suspicionLevel: params.recipientCount > lastObs.recipientCount ? 'highly_suspicious' : 'suspicious',
|
|
356
|
+
reason: params.recipientCount > lastObs.recipientCount
|
|
357
|
+
? 'Recipients ADDED to draft without user action'
|
|
358
|
+
: 'Recipients changed',
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
observation.changes = changes.length > 0 ? changes : undefined;
|
|
363
|
+
observation.hash = hashString(JSON.stringify({
|
|
364
|
+
id: observation.id,
|
|
365
|
+
timestamp: observation.timestamp,
|
|
366
|
+
draftId: observation.draftId,
|
|
367
|
+
bodyHash: observation.bodyHash,
|
|
368
|
+
modifiedAt: observation.modifiedAt,
|
|
369
|
+
}));
|
|
370
|
+
// Store observation
|
|
371
|
+
previous.push(observation);
|
|
372
|
+
this.draftObservations.set(params.draftId, previous);
|
|
373
|
+
await this.persistDraftObservation(observation);
|
|
374
|
+
// Generate finding if suspicious changes detected
|
|
375
|
+
let finding;
|
|
376
|
+
const suspiciousChanges = changes.filter(c => c.suspicionLevel !== 'normal');
|
|
377
|
+
if (suspiciousChanges.length > 0) {
|
|
378
|
+
finding = await this.createFinding({
|
|
379
|
+
vector: 'gmail_draft_manipulation',
|
|
380
|
+
severity: suspiciousChanges.some(c => c.suspicionLevel === 'highly_suspicious') ? 'critical' : 'high',
|
|
381
|
+
title: `Draft manipulated: "${params.subject}"`,
|
|
382
|
+
description: `Draft ${params.draftId} has been modified without clear user action. ${suspiciousChanges.map(c => c.reason).join('; ')}`,
|
|
383
|
+
technicalDetails: {
|
|
384
|
+
draftId: params.draftId,
|
|
385
|
+
changes: suspiciousChanges,
|
|
386
|
+
previousObservationId: lastObs?.id,
|
|
387
|
+
},
|
|
388
|
+
evidenceIds: lastObs ? [lastObs.id, observation.id] : [observation.id],
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
return { observation, changes, finding };
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Detect drafts that were sent without user action.
|
|
395
|
+
* This is a critical fraud indicator.
|
|
396
|
+
*/
|
|
397
|
+
async detectUnauthorizedDraftSend(params) {
|
|
398
|
+
const draftObs = this.draftObservations.get(params.draftId);
|
|
399
|
+
const lastDraftState = draftObs?.[draftObs.length - 1];
|
|
400
|
+
const finding = await this.createFinding({
|
|
401
|
+
vector: 'gmail_draft_manipulation',
|
|
402
|
+
severity: 'critical',
|
|
403
|
+
title: `Draft sent without user action: "${lastDraftState?.subject || params.draftId}"`,
|
|
404
|
+
description: `Draft ${params.draftId} was sent at ${params.sentAt} without user initiation. User active: ${params.userWasActive}. This is unauthorized use of the user's email account.`,
|
|
405
|
+
technicalDetails: {
|
|
406
|
+
draftId: params.draftId,
|
|
407
|
+
sentMessageId: params.sentMessageId,
|
|
408
|
+
sentAt: params.sentAt,
|
|
409
|
+
userWasActive: params.userWasActive,
|
|
410
|
+
userLocation: params.userLocation,
|
|
411
|
+
sendingIp: params.sendingIp,
|
|
412
|
+
lastKnownDraftState: lastDraftState ? {
|
|
413
|
+
subject: lastDraftState.subject,
|
|
414
|
+
bodyHash: lastDraftState.bodyHash,
|
|
415
|
+
recipientCount: lastDraftState.recipientCount,
|
|
416
|
+
lastModified: lastDraftState.modifiedAt,
|
|
417
|
+
} : null,
|
|
418
|
+
},
|
|
419
|
+
evidenceIds: draftObs?.map(o => o.id) || [],
|
|
420
|
+
});
|
|
421
|
+
return finding;
|
|
422
|
+
}
|
|
423
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
424
|
+
// Access Monitoring
|
|
425
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
426
|
+
/**
|
|
427
|
+
* Record account access and detect unauthorized access.
|
|
428
|
+
*/
|
|
429
|
+
async recordAccessLog(params) {
|
|
430
|
+
const now = new Date().toISOString();
|
|
431
|
+
// Determine if suspicious
|
|
432
|
+
let suspicious = false;
|
|
433
|
+
let reason;
|
|
434
|
+
if (!params.wasUser) {
|
|
435
|
+
suspicious = true;
|
|
436
|
+
reason = 'Access not initiated by user';
|
|
437
|
+
}
|
|
438
|
+
// Check for Google internal IPs (they access accounts for "security")
|
|
439
|
+
if (params.ipAddress.startsWith('172.217.') ||
|
|
440
|
+
params.ipAddress.startsWith('142.250.') ||
|
|
441
|
+
params.ipAddress.startsWith('74.125.')) {
|
|
442
|
+
suspicious = true;
|
|
443
|
+
reason = 'Access from Google infrastructure IP';
|
|
444
|
+
}
|
|
445
|
+
const log = {
|
|
446
|
+
id: crypto.randomUUID(),
|
|
447
|
+
timestamp: now,
|
|
448
|
+
accessType: params.accessType,
|
|
449
|
+
ipAddress: params.ipAddress,
|
|
450
|
+
location: params.location,
|
|
451
|
+
userAgent: params.userAgent,
|
|
452
|
+
deviceType: params.deviceType,
|
|
453
|
+
wasUser: params.wasUser,
|
|
454
|
+
suspicious,
|
|
455
|
+
reason,
|
|
456
|
+
hash: '',
|
|
457
|
+
};
|
|
458
|
+
log.hash = hashString(JSON.stringify({
|
|
459
|
+
id: log.id,
|
|
460
|
+
timestamp: log.timestamp,
|
|
461
|
+
accessType: log.accessType,
|
|
462
|
+
ipAddress: log.ipAddress,
|
|
463
|
+
wasUser: log.wasUser,
|
|
464
|
+
}));
|
|
465
|
+
this.accessLogs.push(log);
|
|
466
|
+
await this.persistAccessLog(log);
|
|
467
|
+
let finding;
|
|
468
|
+
if (suspicious) {
|
|
469
|
+
finding = await this.createFinding({
|
|
470
|
+
vector: 'gmail_unauthorized_access',
|
|
471
|
+
severity: 'high',
|
|
472
|
+
title: `Unauthorized account access detected`,
|
|
473
|
+
description: `Gmail account accessed ${params.wasUser ? 'without user initiation' : 'from Google infrastructure'}. ${reason}`,
|
|
474
|
+
technicalDetails: {
|
|
475
|
+
accessType: params.accessType,
|
|
476
|
+
ipAddress: params.ipAddress,
|
|
477
|
+
location: params.location,
|
|
478
|
+
userAgent: params.userAgent,
|
|
479
|
+
},
|
|
480
|
+
evidenceIds: [log.id],
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
return { log, finding };
|
|
484
|
+
}
|
|
485
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
486
|
+
// Filter Monitoring
|
|
487
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
488
|
+
/**
|
|
489
|
+
* Record and detect unauthorized Gmail filters.
|
|
490
|
+
* Google could add filters to hide specific emails.
|
|
491
|
+
*/
|
|
492
|
+
async recordFilterObservation(params) {
|
|
493
|
+
const now = new Date().toISOString();
|
|
494
|
+
const observation = {
|
|
495
|
+
id: crypto.randomUUID(),
|
|
496
|
+
timestamp: now,
|
|
497
|
+
filterId: params.filterId,
|
|
498
|
+
criteria: params.criteria,
|
|
499
|
+
actions: params.actions,
|
|
500
|
+
createdByUser: params.createdByUser,
|
|
501
|
+
createdAt: params.createdAt,
|
|
502
|
+
hash: '',
|
|
503
|
+
};
|
|
504
|
+
observation.hash = hashString(JSON.stringify({
|
|
505
|
+
id: observation.id,
|
|
506
|
+
filterId: observation.filterId,
|
|
507
|
+
criteria: observation.criteria,
|
|
508
|
+
actions: observation.actions,
|
|
509
|
+
}));
|
|
510
|
+
this.filterObservations.push(observation);
|
|
511
|
+
await this.persistFilterObservation(observation);
|
|
512
|
+
let finding;
|
|
513
|
+
// Suspicious filter patterns
|
|
514
|
+
const isSuspicious = !params.createdByUser ||
|
|
515
|
+
(params.actions.delete && !params.createdByUser) ||
|
|
516
|
+
(params.actions.skipInbox && params.actions.markRead) ||
|
|
517
|
+
params.actions.forward;
|
|
518
|
+
if (isSuspicious) {
|
|
519
|
+
finding = await this.createFinding({
|
|
520
|
+
vector: 'gmail_filter_tampering',
|
|
521
|
+
severity: params.actions.delete ? 'critical' : 'high',
|
|
522
|
+
title: `Suspicious filter detected`,
|
|
523
|
+
description: `Filter ${params.filterId} was ${params.createdByUser ? 'created' : 'NOT created'} by user. Actions: ${JSON.stringify(params.actions)}. Criteria: ${JSON.stringify(params.criteria)}`,
|
|
524
|
+
technicalDetails: {
|
|
525
|
+
filterId: params.filterId,
|
|
526
|
+
criteria: params.criteria,
|
|
527
|
+
actions: params.actions,
|
|
528
|
+
createdByUser: params.createdByUser,
|
|
529
|
+
},
|
|
530
|
+
evidenceIds: [observation.id],
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
return { observation, finding };
|
|
534
|
+
}
|
|
535
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
536
|
+
// Persistence
|
|
537
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
538
|
+
async persistThreadObservation(obs) {
|
|
539
|
+
const filePath = path.join(this.storageDir, 'threads', `${obs.threadId}_${obs.id}.json`);
|
|
540
|
+
await fs.writeFile(filePath, JSON.stringify(obs, null, 2));
|
|
541
|
+
}
|
|
542
|
+
async persistDraftObservation(obs) {
|
|
543
|
+
const filePath = path.join(this.storageDir, 'drafts', `${obs.draftId}_${obs.id}.json`);
|
|
544
|
+
await fs.writeFile(filePath, JSON.stringify(obs, null, 2));
|
|
545
|
+
}
|
|
546
|
+
async persistAccessLog(log) {
|
|
547
|
+
const filePath = path.join(this.storageDir, 'access', `${log.id}.json`);
|
|
548
|
+
await fs.writeFile(filePath, JSON.stringify(log, null, 2));
|
|
549
|
+
}
|
|
550
|
+
async persistFilterObservation(obs) {
|
|
551
|
+
const filePath = path.join(this.storageDir, 'filters', `${obs.filterId}_${obs.id}.json`);
|
|
552
|
+
await fs.writeFile(filePath, JSON.stringify(obs, null, 2));
|
|
553
|
+
}
|
|
554
|
+
async createFinding(params) {
|
|
555
|
+
const finding = {
|
|
556
|
+
id: crypto.randomUUID(),
|
|
557
|
+
timestamp: new Date().toISOString(),
|
|
558
|
+
vector: params.vector,
|
|
559
|
+
severity: params.severity,
|
|
560
|
+
title: params.title,
|
|
561
|
+
description: params.description,
|
|
562
|
+
technicalDetails: params.technicalDetails,
|
|
563
|
+
evidenceIds: params.evidenceIds,
|
|
564
|
+
legalImplications: this.getLegalImplications(params.vector, params.severity),
|
|
565
|
+
hash: '',
|
|
566
|
+
};
|
|
567
|
+
finding.hash = hashString(JSON.stringify({
|
|
568
|
+
id: finding.id,
|
|
569
|
+
timestamp: finding.timestamp,
|
|
570
|
+
vector: finding.vector,
|
|
571
|
+
title: finding.title,
|
|
572
|
+
}));
|
|
573
|
+
this.findings.push(finding);
|
|
574
|
+
await fs.writeFile(path.join(this.storageDir, 'findings', `${finding.id}.json`), JSON.stringify(finding, null, 2));
|
|
575
|
+
return finding;
|
|
576
|
+
}
|
|
577
|
+
getLegalImplications(vector, severity) {
|
|
578
|
+
const implications = {
|
|
579
|
+
gmail_hidden_threads: {
|
|
580
|
+
fraudType: 'DATA_MANIPULATION',
|
|
581
|
+
applicableLaws: [
|
|
582
|
+
'15 U.S.C. § 45 - FTC Act Section 5 (Unfair or Deceptive Practices)',
|
|
583
|
+
'18 U.S.C. § 1030 - Computer Fraud and Abuse Act',
|
|
584
|
+
'18 U.S.C. § 2701 - Stored Communications Act',
|
|
585
|
+
],
|
|
586
|
+
potentialDamages: 'Hidden communications may have caused missed opportunities, damaged relationships, or prevented awareness of important information.',
|
|
587
|
+
recommendations: [
|
|
588
|
+
'Document all hidden threads with timestamps',
|
|
589
|
+
'Export via Google Takeout for comparison',
|
|
590
|
+
'File FTC complaint',
|
|
591
|
+
'Consider class action for pattern of behavior',
|
|
592
|
+
],
|
|
593
|
+
},
|
|
594
|
+
gmail_draft_manipulation: {
|
|
595
|
+
fraudType: 'UNAUTHORIZED_ACCESS',
|
|
596
|
+
applicableLaws: [
|
|
597
|
+
'18 U.S.C. § 1030 - Computer Fraud and Abuse Act',
|
|
598
|
+
'18 U.S.C. § 2511 - Wiretap Act (if communications altered)',
|
|
599
|
+
'18 U.S.C. § 1343 - Wire Fraud',
|
|
600
|
+
],
|
|
601
|
+
potentialDamages: 'Manipulation of drafts could send unauthorized communications, damage reputation, or expose confidential information.',
|
|
602
|
+
recommendations: [
|
|
603
|
+
'Preserve all draft observation evidence',
|
|
604
|
+
'Document timeline of changes',
|
|
605
|
+
'File complaint with state AG',
|
|
606
|
+
],
|
|
607
|
+
},
|
|
608
|
+
gmail_unauthorized_access: {
|
|
609
|
+
fraudType: 'UNAUTHORIZED_ACCESS',
|
|
610
|
+
applicableLaws: [
|
|
611
|
+
'18 U.S.C. § 1030 - Computer Fraud and Abuse Act',
|
|
612
|
+
'18 U.S.C. § 2701 - Stored Communications Act',
|
|
613
|
+
'Cal. Penal Code § 502 - Computer Crimes',
|
|
614
|
+
],
|
|
615
|
+
potentialDamages: 'Unauthorized access enables surveillance, data theft, and manipulation of user communications.',
|
|
616
|
+
recommendations: [
|
|
617
|
+
'Document all access logs',
|
|
618
|
+
'Enable additional security measures',
|
|
619
|
+
'File with FBI IC3 if pattern detected',
|
|
620
|
+
],
|
|
621
|
+
},
|
|
622
|
+
gmail_filter_tampering: {
|
|
623
|
+
fraudType: 'DATA_MANIPULATION',
|
|
624
|
+
applicableLaws: [
|
|
625
|
+
'15 U.S.C. § 45 - FTC Act Section 5',
|
|
626
|
+
'18 U.S.C. § 1030 - CFAA',
|
|
627
|
+
],
|
|
628
|
+
potentialDamages: 'Unauthorized filters can hide important communications, delete evidence, or forward sensitive data.',
|
|
629
|
+
recommendations: [
|
|
630
|
+
'Document all filters and creation dates',
|
|
631
|
+
'Remove unauthorized filters',
|
|
632
|
+
'Monitor for re-creation',
|
|
633
|
+
],
|
|
634
|
+
},
|
|
635
|
+
};
|
|
636
|
+
return implications[vector] || {
|
|
637
|
+
fraudType: 'UNKNOWN',
|
|
638
|
+
applicableLaws: ['15 U.S.C. § 45 - FTC Act Section 5'],
|
|
639
|
+
potentialDamages: 'To be determined based on specific circumstances.',
|
|
640
|
+
recommendations: ['Document all evidence', 'Consult legal counsel'],
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
getFindings() {
|
|
644
|
+
return [...this.findings];
|
|
645
|
+
}
|
|
646
|
+
getThreadObservations(threadId) {
|
|
647
|
+
return this.threadObservations.get(threadId) || [];
|
|
648
|
+
}
|
|
649
|
+
getDraftObservations(draftId) {
|
|
650
|
+
return this.draftObservations.get(draftId) || [];
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
654
|
+
// CHROME INVESTIGATION ENGINE
|
|
655
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
656
|
+
export class ChromeInvestigationEngine {
|
|
657
|
+
storageDir;
|
|
658
|
+
processObservations = [];
|
|
659
|
+
sessionObservations = new Map();
|
|
660
|
+
historyObservations = [];
|
|
661
|
+
findings = [];
|
|
662
|
+
constructor(storageDir) {
|
|
663
|
+
this.storageDir = path.join(storageDir, 'chrome-investigation');
|
|
664
|
+
}
|
|
665
|
+
async initialize() {
|
|
666
|
+
await fs.mkdir(this.storageDir, { recursive: true });
|
|
667
|
+
await fs.mkdir(path.join(this.storageDir, 'processes'), { recursive: true });
|
|
668
|
+
await fs.mkdir(path.join(this.storageDir, 'sessions'), { recursive: true });
|
|
669
|
+
await fs.mkdir(path.join(this.storageDir, 'history'), { recursive: true });
|
|
670
|
+
await fs.mkdir(path.join(this.storageDir, 'findings'), { recursive: true });
|
|
671
|
+
}
|
|
672
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
673
|
+
// Process Monitoring (Unauthorized Launch Detection)
|
|
674
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
675
|
+
/**
|
|
676
|
+
* Record Chrome process observation.
|
|
677
|
+
* Detect unauthorized browser launches.
|
|
678
|
+
*/
|
|
679
|
+
async recordProcessObservation(params) {
|
|
680
|
+
const now = new Date().toISOString();
|
|
681
|
+
// Analyze launch source
|
|
682
|
+
let launchSource = 'unknown';
|
|
683
|
+
let suspicionLevel = 'normal';
|
|
684
|
+
let reason;
|
|
685
|
+
// Check command line for suspicious flags
|
|
686
|
+
const cmdLine = params.commandLine.join(' ').toLowerCase();
|
|
687
|
+
if (cmdLine.includes('--remote-debugging') ||
|
|
688
|
+
cmdLine.includes('--headless') ||
|
|
689
|
+
cmdLine.includes('--disable-gpu') && cmdLine.includes('--no-sandbox')) {
|
|
690
|
+
launchSource = 'script';
|
|
691
|
+
suspicionLevel = 'suspicious';
|
|
692
|
+
reason = 'Automation/debugging flags detected';
|
|
693
|
+
}
|
|
694
|
+
if (cmdLine.includes('--user-data-dir=/tmp') ||
|
|
695
|
+
cmdLine.includes('--user-data-dir=/var')) {
|
|
696
|
+
suspicionLevel = 'highly_suspicious';
|
|
697
|
+
reason = 'Temporary profile directory - likely automated session';
|
|
698
|
+
}
|
|
699
|
+
if (!params.userInitiated) {
|
|
700
|
+
suspicionLevel = suspicionLevel === 'normal' ? 'suspicious' : 'highly_suspicious';
|
|
701
|
+
reason = (reason ? reason + '; ' : '') + 'Not user-initiated';
|
|
702
|
+
launchSource = 'remote';
|
|
703
|
+
}
|
|
704
|
+
// Check for Google remote connections
|
|
705
|
+
const googleConnections = params.networkConnections?.filter(c => c.remoteIp.startsWith('172.217.') ||
|
|
706
|
+
c.remoteIp.startsWith('142.250.') ||
|
|
707
|
+
c.remoteIp.startsWith('74.125.')) || [];
|
|
708
|
+
if (googleConnections.length > 0 && !params.userInitiated) {
|
|
709
|
+
suspicionLevel = 'highly_suspicious';
|
|
710
|
+
reason = (reason ? reason + '; ' : '') + 'Connections to Google infrastructure without user action';
|
|
711
|
+
}
|
|
712
|
+
const observation = {
|
|
713
|
+
id: crypto.randomUUID(),
|
|
714
|
+
timestamp: now,
|
|
715
|
+
pid: params.pid,
|
|
716
|
+
parentPid: params.parentPid,
|
|
717
|
+
commandLine: params.commandLine,
|
|
718
|
+
profilePath: params.profilePath,
|
|
719
|
+
startTime: params.startTime,
|
|
720
|
+
userInitiated: params.userInitiated,
|
|
721
|
+
launchSource,
|
|
722
|
+
suspicionLevel,
|
|
723
|
+
reason,
|
|
724
|
+
networkConnections: params.networkConnections,
|
|
725
|
+
hash: '',
|
|
726
|
+
};
|
|
727
|
+
observation.hash = hashString(JSON.stringify({
|
|
728
|
+
id: observation.id,
|
|
729
|
+
pid: observation.pid,
|
|
730
|
+
commandLine: observation.commandLine,
|
|
731
|
+
startTime: observation.startTime,
|
|
732
|
+
userInitiated: observation.userInitiated,
|
|
733
|
+
}));
|
|
734
|
+
this.processObservations.push(observation);
|
|
735
|
+
await this.persistProcessObservation(observation);
|
|
736
|
+
let finding;
|
|
737
|
+
if (suspicionLevel !== 'normal') {
|
|
738
|
+
finding = await this.createFinding({
|
|
739
|
+
vector: 'chrome_unauthorized_launch',
|
|
740
|
+
severity: suspicionLevel === 'highly_suspicious' ? 'critical' : 'high',
|
|
741
|
+
title: 'Unauthorized Chrome launch detected',
|
|
742
|
+
description: `Chrome process ${params.pid} launched without user action. ${reason}`,
|
|
743
|
+
technicalDetails: {
|
|
744
|
+
pid: params.pid,
|
|
745
|
+
parentPid: params.parentPid,
|
|
746
|
+
commandLine: params.commandLine,
|
|
747
|
+
profilePath: params.profilePath,
|
|
748
|
+
startTime: params.startTime,
|
|
749
|
+
launchSource,
|
|
750
|
+
networkConnections: googleConnections,
|
|
751
|
+
},
|
|
752
|
+
evidenceIds: [observation.id],
|
|
753
|
+
});
|
|
754
|
+
}
|
|
755
|
+
return { observation, finding };
|
|
756
|
+
}
|
|
757
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
758
|
+
// Session Monitoring (Session Hijacking Detection)
|
|
759
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
760
|
+
/**
|
|
761
|
+
* Record Chrome session state.
|
|
762
|
+
* Detect session hijacking and unauthorized modifications.
|
|
763
|
+
*/
|
|
764
|
+
async recordSessionObservation(params) {
|
|
765
|
+
const now = new Date().toISOString();
|
|
766
|
+
const anomalies = [];
|
|
767
|
+
// Get previous session state
|
|
768
|
+
const previous = this.sessionObservations.get(params.profileId) || [];
|
|
769
|
+
const lastSession = previous[previous.length - 1];
|
|
770
|
+
if (lastSession) {
|
|
771
|
+
// Check for unexpected tabs
|
|
772
|
+
const newTabs = params.tabs.filter(t => !lastSession.tabs.find(lt => lt.tabId === t.tabId));
|
|
773
|
+
for (const tab of newTabs) {
|
|
774
|
+
// Check if tab URL is suspicious
|
|
775
|
+
if (tab.url.includes('google.com/') &&
|
|
776
|
+
new Date(tab.createdAt) > new Date(lastSession.timestamp)) {
|
|
777
|
+
anomalies.push({
|
|
778
|
+
type: 'unexpected_tab',
|
|
779
|
+
severity: 'medium',
|
|
780
|
+
description: `New tab opened to ${tab.url} without user action`,
|
|
781
|
+
evidence: JSON.stringify(tab),
|
|
782
|
+
});
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
// Check for cookie changes
|
|
786
|
+
const newCookies = params.cookies.filter(c => !lastSession.cookies.find(lc => lc.domain === c.domain && lc.name === c.name && lc.valueHash === c.valueHash));
|
|
787
|
+
const sensitiveDomains = ['accounts.google.com', 'myaccount.google.com', 'mail.google.com'];
|
|
788
|
+
for (const cookie of newCookies) {
|
|
789
|
+
if (sensitiveDomains.some(d => cookie.domain.includes(d))) {
|
|
790
|
+
anomalies.push({
|
|
791
|
+
type: 'cookie_injection',
|
|
792
|
+
severity: 'high',
|
|
793
|
+
description: `Cookie modified for ${cookie.domain}:${cookie.name}`,
|
|
794
|
+
evidence: JSON.stringify(cookie),
|
|
795
|
+
});
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
// Check for sync without user action
|
|
799
|
+
if (lastSession.syncStatus === 'local_only' && params.syncStatus === 'synced') {
|
|
800
|
+
anomalies.push({
|
|
801
|
+
type: 'sync_without_user',
|
|
802
|
+
severity: 'high',
|
|
803
|
+
description: 'Sync enabled without user action',
|
|
804
|
+
evidence: `Last sync: ${params.lastSyncTime}`,
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
const observation = {
|
|
809
|
+
id: crypto.randomUUID(),
|
|
810
|
+
timestamp: now,
|
|
811
|
+
profileId: params.profileId,
|
|
812
|
+
tabs: params.tabs,
|
|
813
|
+
cookies: params.cookies,
|
|
814
|
+
localStorage: params.localStorage,
|
|
815
|
+
syncStatus: params.syncStatus,
|
|
816
|
+
lastSyncTime: params.lastSyncTime,
|
|
817
|
+
anomalies,
|
|
818
|
+
hash: '',
|
|
819
|
+
};
|
|
820
|
+
observation.hash = hashString(JSON.stringify({
|
|
821
|
+
id: observation.id,
|
|
822
|
+
timestamp: observation.timestamp,
|
|
823
|
+
profileId: observation.profileId,
|
|
824
|
+
tabCount: observation.tabs.length,
|
|
825
|
+
cookieCount: observation.cookies.length,
|
|
826
|
+
anomalyCount: observation.anomalies.length,
|
|
827
|
+
}));
|
|
828
|
+
previous.push(observation);
|
|
829
|
+
this.sessionObservations.set(params.profileId, previous);
|
|
830
|
+
await this.persistSessionObservation(observation);
|
|
831
|
+
let finding;
|
|
832
|
+
if (anomalies.length > 0) {
|
|
833
|
+
const criticalAnomalies = anomalies.filter(a => a.severity === 'high' || a.severity === 'critical');
|
|
834
|
+
finding = await this.createFinding({
|
|
835
|
+
vector: 'chrome_session_hijacking',
|
|
836
|
+
severity: criticalAnomalies.length > 0 ? 'critical' : 'high',
|
|
837
|
+
title: `Session anomalies detected: ${anomalies.length} issues`,
|
|
838
|
+
description: anomalies.map(a => a.description).join('; '),
|
|
839
|
+
technicalDetails: {
|
|
840
|
+
profileId: params.profileId,
|
|
841
|
+
anomalies,
|
|
842
|
+
tabCount: params.tabs.length,
|
|
843
|
+
syncStatus: params.syncStatus,
|
|
844
|
+
},
|
|
845
|
+
evidenceIds: [observation.id],
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
return { observation, anomalies, finding };
|
|
849
|
+
}
|
|
850
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
851
|
+
// History Monitoring
|
|
852
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
853
|
+
/**
|
|
854
|
+
* Record Chrome history state.
|
|
855
|
+
* Detect unauthorized deletions or additions.
|
|
856
|
+
*/
|
|
857
|
+
async recordHistoryObservation(params) {
|
|
858
|
+
const now = new Date().toISOString();
|
|
859
|
+
// Compare with previous observation
|
|
860
|
+
const lastObs = this.historyObservations[this.historyObservations.length - 1];
|
|
861
|
+
const deletedEntries = [];
|
|
862
|
+
const unexpectedEntries = [];
|
|
863
|
+
if (lastObs && params.entryCount < lastObs.entryCount) {
|
|
864
|
+
// History was deleted
|
|
865
|
+
deletedEntries.push({
|
|
866
|
+
approximateTime: now,
|
|
867
|
+
entriesAffected: lastObs.entryCount - params.entryCount,
|
|
868
|
+
userInitiated: false, // cannot determine
|
|
869
|
+
});
|
|
870
|
+
}
|
|
871
|
+
// Check for suspicious entries (e.g., visits to Google services during sleep)
|
|
872
|
+
for (const entry of params.entries) {
|
|
873
|
+
const visitDate = new Date(entry.visitTime);
|
|
874
|
+
const hour = visitDate.getHours();
|
|
875
|
+
// Suspicious: visits between 2-5 AM to Google properties
|
|
876
|
+
if (hour >= 2 && hour <= 5 &&
|
|
877
|
+
(entry.url.includes('google.com') || entry.url.includes('googleapis.com'))) {
|
|
878
|
+
unexpectedEntries.push({
|
|
879
|
+
url: entry.url,
|
|
880
|
+
visitTime: entry.visitTime,
|
|
881
|
+
transitionType: entry.transitionType,
|
|
882
|
+
suspicionReason: 'Visit during inactive hours to Google service',
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
// Suspicious: typed_url transition type but user wasn't active
|
|
886
|
+
if (entry.transitionType === 'typed' &&
|
|
887
|
+
entry.url.includes('myaccount.google.com')) {
|
|
888
|
+
unexpectedEntries.push({
|
|
889
|
+
url: entry.url,
|
|
890
|
+
visitTime: entry.visitTime,
|
|
891
|
+
transitionType: entry.transitionType,
|
|
892
|
+
suspicionReason: 'Direct navigation to Google account page',
|
|
893
|
+
});
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
const observation = {
|
|
897
|
+
id: crypto.randomUUID(),
|
|
898
|
+
timestamp: now,
|
|
899
|
+
captureMethod: params.captureMethod,
|
|
900
|
+
entryCount: params.entryCount,
|
|
901
|
+
dateRange: params.dateRange,
|
|
902
|
+
deletedEntries: deletedEntries.length > 0 ? deletedEntries : undefined,
|
|
903
|
+
unexpectedEntries: unexpectedEntries.length > 0 ? unexpectedEntries : undefined,
|
|
904
|
+
hash: '',
|
|
905
|
+
};
|
|
906
|
+
observation.hash = hashString(JSON.stringify({
|
|
907
|
+
id: observation.id,
|
|
908
|
+
timestamp: observation.timestamp,
|
|
909
|
+
entryCount: observation.entryCount,
|
|
910
|
+
deletedCount: deletedEntries.length,
|
|
911
|
+
unexpectedCount: unexpectedEntries.length,
|
|
912
|
+
}));
|
|
913
|
+
this.historyObservations.push(observation);
|
|
914
|
+
await this.persistHistoryObservation(observation);
|
|
915
|
+
let finding;
|
|
916
|
+
if (deletedEntries.length > 0 || unexpectedEntries.length > 0) {
|
|
917
|
+
finding = await this.createFinding({
|
|
918
|
+
vector: 'chrome_history_manipulation',
|
|
919
|
+
severity: deletedEntries.length > 0 ? 'high' : 'medium',
|
|
920
|
+
title: 'Browser history anomalies detected',
|
|
921
|
+
description: `${deletedEntries.length > 0 ? `${deletedEntries[0].entriesAffected} entries deleted. ` : ''}${unexpectedEntries.length > 0 ? `${unexpectedEntries.length} suspicious entries found.` : ''}`,
|
|
922
|
+
technicalDetails: {
|
|
923
|
+
deletedEntries,
|
|
924
|
+
unexpectedEntries,
|
|
925
|
+
totalEntries: params.entryCount,
|
|
926
|
+
dateRange: params.dateRange,
|
|
927
|
+
},
|
|
928
|
+
evidenceIds: [observation.id],
|
|
929
|
+
});
|
|
930
|
+
}
|
|
931
|
+
return { observation, finding };
|
|
932
|
+
}
|
|
933
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
934
|
+
// Persistence
|
|
935
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
936
|
+
async persistProcessObservation(obs) {
|
|
937
|
+
const filePath = path.join(this.storageDir, 'processes', `${obs.id}.json`);
|
|
938
|
+
await fs.writeFile(filePath, JSON.stringify(obs, null, 2));
|
|
939
|
+
}
|
|
940
|
+
async persistSessionObservation(obs) {
|
|
941
|
+
const filePath = path.join(this.storageDir, 'sessions', `${obs.profileId}_${obs.id}.json`);
|
|
942
|
+
await fs.writeFile(filePath, JSON.stringify(obs, null, 2));
|
|
943
|
+
}
|
|
944
|
+
async persistHistoryObservation(obs) {
|
|
945
|
+
const filePath = path.join(this.storageDir, 'history', `${obs.id}.json`);
|
|
946
|
+
await fs.writeFile(filePath, JSON.stringify(obs, null, 2));
|
|
947
|
+
}
|
|
948
|
+
async createFinding(params) {
|
|
949
|
+
const finding = {
|
|
950
|
+
id: crypto.randomUUID(),
|
|
951
|
+
timestamp: new Date().toISOString(),
|
|
952
|
+
vector: params.vector,
|
|
953
|
+
severity: params.severity,
|
|
954
|
+
title: params.title,
|
|
955
|
+
description: params.description,
|
|
956
|
+
technicalDetails: params.technicalDetails,
|
|
957
|
+
evidenceIds: params.evidenceIds,
|
|
958
|
+
legalImplications: this.getLegalImplications(params.vector),
|
|
959
|
+
hash: '',
|
|
960
|
+
};
|
|
961
|
+
finding.hash = hashString(JSON.stringify({
|
|
962
|
+
id: finding.id,
|
|
963
|
+
timestamp: finding.timestamp,
|
|
964
|
+
vector: finding.vector,
|
|
965
|
+
title: finding.title,
|
|
966
|
+
}));
|
|
967
|
+
this.findings.push(finding);
|
|
968
|
+
await fs.writeFile(path.join(this.storageDir, 'findings', `${finding.id}.json`), JSON.stringify(finding, null, 2));
|
|
969
|
+
return finding;
|
|
970
|
+
}
|
|
971
|
+
getLegalImplications(vector) {
|
|
972
|
+
const implications = {
|
|
973
|
+
chrome_unauthorized_launch: {
|
|
974
|
+
fraudType: 'UNAUTHORIZED_ACCESS',
|
|
975
|
+
applicableLaws: [
|
|
976
|
+
'18 U.S.C. § 1030 - Computer Fraud and Abuse Act',
|
|
977
|
+
'18 U.S.C. § 2511 - Wiretap Act',
|
|
978
|
+
'Cal. Penal Code § 502 - Computer Crimes',
|
|
979
|
+
],
|
|
980
|
+
potentialDamages: 'Unauthorized browser launches can enable surveillance, credential theft, and remote control of user system.',
|
|
981
|
+
recommendations: [
|
|
982
|
+
'Monitor Chrome process launches',
|
|
983
|
+
'Document all unauthorized launches',
|
|
984
|
+
'Consider disabling Chrome sync',
|
|
985
|
+
'Use alternative browser for sensitive activities',
|
|
986
|
+
],
|
|
987
|
+
},
|
|
988
|
+
chrome_session_hijacking: {
|
|
989
|
+
fraudType: 'UNAUTHORIZED_ACCESS',
|
|
990
|
+
applicableLaws: [
|
|
991
|
+
'18 U.S.C. § 1030 - CFAA',
|
|
992
|
+
'18 U.S.C. § 1029 - Access Device Fraud',
|
|
993
|
+
],
|
|
994
|
+
potentialDamages: 'Session hijacking enables impersonation, data theft, and unauthorized actions on user accounts.',
|
|
995
|
+
recommendations: [
|
|
996
|
+
'Document session anomalies',
|
|
997
|
+
'Clear cookies and re-authenticate',
|
|
998
|
+
'Enable 2FA on all accounts',
|
|
999
|
+
],
|
|
1000
|
+
},
|
|
1001
|
+
chrome_history_manipulation: {
|
|
1002
|
+
fraudType: 'DATA_MANIPULATION',
|
|
1003
|
+
applicableLaws: [
|
|
1004
|
+
'18 U.S.C. § 1030 - CFAA',
|
|
1005
|
+
'15 U.S.C. § 45 - FTC Act',
|
|
1006
|
+
],
|
|
1007
|
+
potentialDamages: 'History manipulation can hide evidence of surveillance or unauthorized access.',
|
|
1008
|
+
recommendations: [
|
|
1009
|
+
'Export history regularly',
|
|
1010
|
+
'Compare with network logs',
|
|
1011
|
+
'Document deletions',
|
|
1012
|
+
],
|
|
1013
|
+
},
|
|
1014
|
+
};
|
|
1015
|
+
return implications[vector] || {
|
|
1016
|
+
fraudType: 'UNKNOWN',
|
|
1017
|
+
applicableLaws: ['18 U.S.C. § 1030 - CFAA'],
|
|
1018
|
+
potentialDamages: 'To be determined.',
|
|
1019
|
+
recommendations: ['Document all evidence'],
|
|
1020
|
+
};
|
|
1021
|
+
}
|
|
1022
|
+
getFindings() {
|
|
1023
|
+
return [...this.findings];
|
|
1024
|
+
}
|
|
1025
|
+
getProcessObservations() {
|
|
1026
|
+
return [...this.processObservations];
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1030
|
+
// UNIFIED FRAUD INVESTIGATION ORCHESTRATOR
|
|
1031
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1032
|
+
export class UnifiedFraudOrchestrator extends EventEmitter {
|
|
1033
|
+
storageDir;
|
|
1034
|
+
integrityEngine;
|
|
1035
|
+
iMessageEngine;
|
|
1036
|
+
gmailEngine;
|
|
1037
|
+
chromeEngine;
|
|
1038
|
+
investigations = new Map();
|
|
1039
|
+
correlations = [];
|
|
1040
|
+
constructor(workingDir = process.cwd()) {
|
|
1041
|
+
super();
|
|
1042
|
+
this.storageDir = path.join(workingDir, '.erosolar', 'investigations');
|
|
1043
|
+
this.integrityEngine = new IntegrityVerificationEngine({
|
|
1044
|
+
storageDir: path.join(this.storageDir, 'integrity'),
|
|
1045
|
+
algorithm: 'sha256',
|
|
1046
|
+
});
|
|
1047
|
+
this.iMessageEngine = new iMessageVerificationEngine({
|
|
1048
|
+
storageDir: this.storageDir,
|
|
1049
|
+
});
|
|
1050
|
+
this.gmailEngine = new GmailInvestigationEngine(this.storageDir);
|
|
1051
|
+
this.chromeEngine = new ChromeInvestigationEngine(this.storageDir);
|
|
1052
|
+
}
|
|
1053
|
+
async initialize() {
|
|
1054
|
+
await fs.mkdir(this.storageDir, { recursive: true });
|
|
1055
|
+
await this.iMessageEngine.initialize();
|
|
1056
|
+
await this.gmailEngine.initialize();
|
|
1057
|
+
await this.chromeEngine.initialize();
|
|
1058
|
+
}
|
|
1059
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
1060
|
+
// Investigation Management
|
|
1061
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
1062
|
+
async createInvestigation(params) {
|
|
1063
|
+
const now = new Date().toISOString();
|
|
1064
|
+
// Create evidence chain for this investigation
|
|
1065
|
+
const evidenceChain = await this.integrityEngine.createChain();
|
|
1066
|
+
const investigation = {
|
|
1067
|
+
id: crypto.randomUUID(),
|
|
1068
|
+
name: params.name,
|
|
1069
|
+
target: params.target,
|
|
1070
|
+
vectors: params.vectors,
|
|
1071
|
+
status: 'planning',
|
|
1072
|
+
created: now,
|
|
1073
|
+
lastActivity: now,
|
|
1074
|
+
evidenceChainId: evidenceChain.id,
|
|
1075
|
+
findings: [],
|
|
1076
|
+
correlations: [],
|
|
1077
|
+
hash: '',
|
|
1078
|
+
};
|
|
1079
|
+
investigation.hash = hashString(JSON.stringify({
|
|
1080
|
+
id: investigation.id,
|
|
1081
|
+
name: investigation.name,
|
|
1082
|
+
target: investigation.target,
|
|
1083
|
+
vectors: investigation.vectors,
|
|
1084
|
+
created: investigation.created,
|
|
1085
|
+
}));
|
|
1086
|
+
this.investigations.set(investigation.id, investigation);
|
|
1087
|
+
await this.persistInvestigation(investigation);
|
|
1088
|
+
this.emit('investigation:created', investigation);
|
|
1089
|
+
return investigation;
|
|
1090
|
+
}
|
|
1091
|
+
async updateInvestigationStatus(investigationId, status) {
|
|
1092
|
+
const investigation = this.investigations.get(investigationId);
|
|
1093
|
+
if (!investigation) {
|
|
1094
|
+
throw new Error(`Investigation not found: ${investigationId}`);
|
|
1095
|
+
}
|
|
1096
|
+
investigation.status = status;
|
|
1097
|
+
investigation.lastActivity = new Date().toISOString();
|
|
1098
|
+
await this.persistInvestigation(investigation);
|
|
1099
|
+
this.emit('investigation:updated', investigation);
|
|
1100
|
+
return investigation;
|
|
1101
|
+
}
|
|
1102
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
1103
|
+
// Engine Access
|
|
1104
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
1105
|
+
getIMessageEngine() {
|
|
1106
|
+
return this.iMessageEngine;
|
|
1107
|
+
}
|
|
1108
|
+
getGmailEngine() {
|
|
1109
|
+
return this.gmailEngine;
|
|
1110
|
+
}
|
|
1111
|
+
getChromeEngine() {
|
|
1112
|
+
return this.chromeEngine;
|
|
1113
|
+
}
|
|
1114
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
1115
|
+
// Cross-Engine Correlation
|
|
1116
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
1117
|
+
/**
|
|
1118
|
+
* Correlate findings across different fraud vectors.
|
|
1119
|
+
* Detect patterns that indicate coordinated manipulation.
|
|
1120
|
+
*/
|
|
1121
|
+
async correlateFindings(investigationId) {
|
|
1122
|
+
const investigation = this.investigations.get(investigationId);
|
|
1123
|
+
if (!investigation) {
|
|
1124
|
+
throw new Error(`Investigation not found: ${investigationId}`);
|
|
1125
|
+
}
|
|
1126
|
+
const newCorrelations = [];
|
|
1127
|
+
// Gather all findings
|
|
1128
|
+
const iMessageEvidence = this.iMessageEngine.getEvidenceRecords();
|
|
1129
|
+
const gmailFindings = this.gmailEngine.getFindings();
|
|
1130
|
+
const chromeFindings = this.chromeEngine.getFindings();
|
|
1131
|
+
const allFindings = [...gmailFindings, ...chromeFindings];
|
|
1132
|
+
// Temporal correlation: events happening within short timeframe
|
|
1133
|
+
const sortedFindings = allFindings.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
|
|
1134
|
+
for (let i = 0; i < sortedFindings.length - 1; i++) {
|
|
1135
|
+
const f1 = sortedFindings[i];
|
|
1136
|
+
const f2 = sortedFindings[i + 1];
|
|
1137
|
+
const timeDiff = new Date(f2.timestamp).getTime() - new Date(f1.timestamp).getTime();
|
|
1138
|
+
// If events within 5 minutes, likely correlated
|
|
1139
|
+
if (timeDiff < 5 * 60 * 1000 && f1.vector !== f2.vector) {
|
|
1140
|
+
const correlation = {
|
|
1141
|
+
id: crypto.randomUUID(),
|
|
1142
|
+
timestamp: new Date().toISOString(),
|
|
1143
|
+
findingIds: [f1.id, f2.id],
|
|
1144
|
+
correlationType: 'temporal',
|
|
1145
|
+
description: `${f1.vector} and ${f2.vector} events occurred within ${Math.round(timeDiff / 1000)} seconds`,
|
|
1146
|
+
confidence: timeDiff < 60000 ? 0.9 : 0.7, // Higher confidence if within 1 minute
|
|
1147
|
+
implications: 'Events occurring in close temporal proximity across different services may indicate coordinated manipulation.',
|
|
1148
|
+
hash: '',
|
|
1149
|
+
};
|
|
1150
|
+
correlation.hash = hashString(JSON.stringify({
|
|
1151
|
+
id: correlation.id,
|
|
1152
|
+
findingIds: correlation.findingIds,
|
|
1153
|
+
correlationType: correlation.correlationType,
|
|
1154
|
+
}));
|
|
1155
|
+
newCorrelations.push(correlation);
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
// Behavioral correlation: Chrome launch + Gmail access
|
|
1159
|
+
const chromeLaunches = chromeFindings.filter(f => f.vector === 'chrome_unauthorized_launch');
|
|
1160
|
+
const gmailAccess = gmailFindings.filter(f => f.vector === 'gmail_unauthorized_access');
|
|
1161
|
+
for (const launch of chromeLaunches) {
|
|
1162
|
+
for (const access of gmailAccess) {
|
|
1163
|
+
const launchTime = new Date(launch.timestamp).getTime();
|
|
1164
|
+
const accessTime = new Date(access.timestamp).getTime();
|
|
1165
|
+
if (Math.abs(accessTime - launchTime) < 10 * 60 * 1000) { // Within 10 minutes
|
|
1166
|
+
const correlation = {
|
|
1167
|
+
id: crypto.randomUUID(),
|
|
1168
|
+
timestamp: new Date().toISOString(),
|
|
1169
|
+
findingIds: [launch.id, access.id],
|
|
1170
|
+
correlationType: 'behavioral',
|
|
1171
|
+
description: 'Chrome unauthorized launch followed by Gmail access - browser used to access email',
|
|
1172
|
+
confidence: 0.85,
|
|
1173
|
+
implications: 'This pattern suggests Google is using Chrome to access Gmail without user consent, enabling surveillance and manipulation.',
|
|
1174
|
+
hash: '',
|
|
1175
|
+
};
|
|
1176
|
+
correlation.hash = hashString(JSON.stringify({
|
|
1177
|
+
id: correlation.id,
|
|
1178
|
+
findingIds: correlation.findingIds,
|
|
1179
|
+
}));
|
|
1180
|
+
newCorrelations.push(correlation);
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
// Cross-platform correlation: iMessage + Gmail (both companies may share data)
|
|
1185
|
+
for (const imEvidence of iMessageEvidence) {
|
|
1186
|
+
for (const gmailFinding of gmailFindings) {
|
|
1187
|
+
const imTime = new Date(imEvidence.timestamp).getTime();
|
|
1188
|
+
const gmailTime = new Date(gmailFinding.timestamp).getTime();
|
|
1189
|
+
if (Math.abs(gmailTime - imTime) < 60 * 60 * 1000) { // Within 1 hour
|
|
1190
|
+
const correlation = {
|
|
1191
|
+
id: crypto.randomUUID(),
|
|
1192
|
+
timestamp: new Date().toISOString(),
|
|
1193
|
+
findingIds: [imEvidence.id, gmailFinding.id],
|
|
1194
|
+
correlationType: 'pattern',
|
|
1195
|
+
description: `Apple iMessage (${imEvidence.evidenceType}) and Google Gmail (${gmailFinding.vector}) events within 1 hour`,
|
|
1196
|
+
confidence: 0.5, // Lower confidence for cross-platform
|
|
1197
|
+
implications: 'Coordinated manipulation across Apple and Google platforms may indicate data sharing or parallel surveillance operations.',
|
|
1198
|
+
hash: '',
|
|
1199
|
+
};
|
|
1200
|
+
correlation.hash = hashString(JSON.stringify({
|
|
1201
|
+
id: correlation.id,
|
|
1202
|
+
findingIds: correlation.findingIds,
|
|
1203
|
+
}));
|
|
1204
|
+
newCorrelations.push(correlation);
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
// Store correlations
|
|
1209
|
+
for (const correlation of newCorrelations) {
|
|
1210
|
+
this.correlations.push(correlation);
|
|
1211
|
+
investigation.correlations.push(correlation);
|
|
1212
|
+
}
|
|
1213
|
+
await this.persistInvestigation(investigation);
|
|
1214
|
+
return newCorrelations;
|
|
1215
|
+
}
|
|
1216
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
1217
|
+
// Reporting
|
|
1218
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
1219
|
+
async generateUnifiedReport(investigationId) {
|
|
1220
|
+
const investigation = this.investigations.get(investigationId);
|
|
1221
|
+
if (!investigation) {
|
|
1222
|
+
throw new Error(`Investigation not found: ${investigationId}`);
|
|
1223
|
+
}
|
|
1224
|
+
// Gather all findings
|
|
1225
|
+
const gmailFindings = this.gmailEngine.getFindings();
|
|
1226
|
+
const chromeFindings = this.chromeEngine.getFindings();
|
|
1227
|
+
const iMessageEvidence = this.iMessageEngine.getEvidenceRecords();
|
|
1228
|
+
// Convert iMessage evidence to findings format
|
|
1229
|
+
const iMessageFindings = iMessageEvidence.map(e => ({
|
|
1230
|
+
id: e.id,
|
|
1231
|
+
timestamp: e.timestamp,
|
|
1232
|
+
vector: e.evidenceType === 'key_substitution' ? 'imessage_key_substitution' :
|
|
1233
|
+
e.evidenceType === 'mitm_detected' ? 'imessage_false_e2e' :
|
|
1234
|
+
'imessage_false_e2e',
|
|
1235
|
+
severity: e.severity === 'irrefutable' ? 'critical' :
|
|
1236
|
+
e.severity === 'confirmed' ? 'critical' : 'high',
|
|
1237
|
+
title: e.summary,
|
|
1238
|
+
description: e.technicalDetails.discrepancy,
|
|
1239
|
+
technicalDetails: e.technicalDetails,
|
|
1240
|
+
evidenceIds: e.supportingEvidence,
|
|
1241
|
+
legalImplications: {
|
|
1242
|
+
fraudType: e.legalImplications.fraudType,
|
|
1243
|
+
applicableLaws: e.legalImplications.applicableLaws,
|
|
1244
|
+
potentialDamages: e.legalImplications.damages,
|
|
1245
|
+
recommendations: [],
|
|
1246
|
+
},
|
|
1247
|
+
hash: e.hash,
|
|
1248
|
+
}));
|
|
1249
|
+
const allFindings = [...gmailFindings, ...chromeFindings, ...iMessageFindings];
|
|
1250
|
+
// Organize by vector
|
|
1251
|
+
const byVector = {};
|
|
1252
|
+
for (const finding of allFindings) {
|
|
1253
|
+
if (!byVector[finding.vector]) {
|
|
1254
|
+
byVector[finding.vector] = { findings: [], severity: 'low' };
|
|
1255
|
+
}
|
|
1256
|
+
byVector[finding.vector].findings.push(finding);
|
|
1257
|
+
// Update severity to max
|
|
1258
|
+
const severityOrder = ['low', 'medium', 'high', 'critical'];
|
|
1259
|
+
if (severityOrder.indexOf(finding.severity) > severityOrder.indexOf(byVector[finding.vector].severity)) {
|
|
1260
|
+
byVector[finding.vector].severity = finding.severity;
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
// Legal summary
|
|
1264
|
+
const fraudTypes = [...new Set(allFindings.map(f => f.legalImplications.fraudType))];
|
|
1265
|
+
const applicableLaws = [...new Set(allFindings.flatMap(f => f.legalImplications.applicableLaws))];
|
|
1266
|
+
const recommendations = [...new Set(allFindings.flatMap(f => f.legalImplications.recommendations))];
|
|
1267
|
+
// Claims
|
|
1268
|
+
const claims = {};
|
|
1269
|
+
if (investigation.target === 'apple' || iMessageFindings.length > 0) {
|
|
1270
|
+
claims.apple = APPLE_PQ3_CLAIMS;
|
|
1271
|
+
}
|
|
1272
|
+
if (investigation.target === 'google' || gmailFindings.length > 0) {
|
|
1273
|
+
claims.google_gmail = GOOGLE_GMAIL_CLAIMS;
|
|
1274
|
+
}
|
|
1275
|
+
if (investigation.target === 'google' || chromeFindings.length > 0) {
|
|
1276
|
+
claims.google_chrome = GOOGLE_CHROME_CLAIMS;
|
|
1277
|
+
}
|
|
1278
|
+
return {
|
|
1279
|
+
investigation,
|
|
1280
|
+
summary: {
|
|
1281
|
+
target: investigation.target,
|
|
1282
|
+
vectorsInvestigated: investigation.vectors,
|
|
1283
|
+
totalFindings: allFindings.length,
|
|
1284
|
+
criticalFindings: allFindings.filter(f => f.severity === 'critical').length,
|
|
1285
|
+
correlations: this.correlations.filter(c => c.findingIds.some(id => allFindings.find(f => f.id === id))).length,
|
|
1286
|
+
},
|
|
1287
|
+
byVector: byVector,
|
|
1288
|
+
correlations: this.correlations,
|
|
1289
|
+
legalSummary: {
|
|
1290
|
+
fraudTypes,
|
|
1291
|
+
applicableLaws,
|
|
1292
|
+
recommendations,
|
|
1293
|
+
},
|
|
1294
|
+
claims,
|
|
1295
|
+
};
|
|
1296
|
+
}
|
|
1297
|
+
async exportForLitigation(investigationId, outputDir) {
|
|
1298
|
+
const report = await this.generateUnifiedReport(investigationId);
|
|
1299
|
+
const exportDir = path.join(outputDir, `investigation-${investigationId}-${Date.now()}`);
|
|
1300
|
+
await fs.mkdir(exportDir, { recursive: true });
|
|
1301
|
+
// Export report
|
|
1302
|
+
await fs.writeFile(path.join(exportDir, 'unified-report.json'), JSON.stringify(report, null, 2));
|
|
1303
|
+
// Export iMessage evidence
|
|
1304
|
+
const iMessageExport = await this.iMessageEngine.exportForLitigation(exportDir);
|
|
1305
|
+
// Generate legal summary document
|
|
1306
|
+
await fs.writeFile(path.join(exportDir, 'legal-summary.md'), this.generateLegalDocument(report));
|
|
1307
|
+
return exportDir;
|
|
1308
|
+
}
|
|
1309
|
+
generateLegalDocument(report) {
|
|
1310
|
+
return `# Unified Tech Fraud Investigation Report
|
|
1311
|
+
|
|
1312
|
+
## Investigation: ${report.investigation.name}
|
|
1313
|
+
**ID:** ${report.investigation.id}
|
|
1314
|
+
**Target:** ${report.investigation.target.toUpperCase()}
|
|
1315
|
+
**Status:** ${report.investigation.status}
|
|
1316
|
+
**Created:** ${report.investigation.created}
|
|
1317
|
+
|
|
1318
|
+
---
|
|
1319
|
+
|
|
1320
|
+
## Executive Summary
|
|
1321
|
+
|
|
1322
|
+
This report documents a comprehensive investigation into potential fraud and manipulation
|
|
1323
|
+
by ${report.investigation.target.toUpperCase()} across ${report.summary.vectorsInvestigated.length} attack vectors.
|
|
1324
|
+
|
|
1325
|
+
### Key Metrics
|
|
1326
|
+
- **Total Findings:** ${report.summary.totalFindings}
|
|
1327
|
+
- **Critical Findings:** ${report.summary.criticalFindings}
|
|
1328
|
+
- **Cross-Vector Correlations:** ${report.summary.correlations}
|
|
1329
|
+
|
|
1330
|
+
---
|
|
1331
|
+
|
|
1332
|
+
## Findings by Attack Vector
|
|
1333
|
+
|
|
1334
|
+
${Object.entries(report.byVector).map(([vector, data]) => `
|
|
1335
|
+
### ${vector.replace(/_/g, ' ').toUpperCase()}
|
|
1336
|
+
**Severity Level:** ${data.severity.toUpperCase()}
|
|
1337
|
+
**Finding Count:** ${data.findings.length}
|
|
1338
|
+
|
|
1339
|
+
${data.findings.map(f => `
|
|
1340
|
+
#### ${f.title}
|
|
1341
|
+
- **Severity:** ${f.severity}
|
|
1342
|
+
- **Timestamp:** ${f.timestamp}
|
|
1343
|
+
- **Description:** ${f.description}
|
|
1344
|
+
- **Legal Basis:** ${f.legalImplications.applicableLaws.join(', ')}
|
|
1345
|
+
`).join('\n')}
|
|
1346
|
+
`).join('\n')}
|
|
1347
|
+
|
|
1348
|
+
---
|
|
1349
|
+
|
|
1350
|
+
## Cross-Vector Correlations
|
|
1351
|
+
|
|
1352
|
+
${report.correlations.length > 0 ? report.correlations.map(c => `
|
|
1353
|
+
### Correlation: ${c.correlationType}
|
|
1354
|
+
**Confidence:** ${(c.confidence * 100).toFixed(0)}%
|
|
1355
|
+
**Description:** ${c.description}
|
|
1356
|
+
**Implications:** ${c.implications}
|
|
1357
|
+
`).join('\n') : 'No correlations detected.'}
|
|
1358
|
+
|
|
1359
|
+
---
|
|
1360
|
+
|
|
1361
|
+
## Legal Analysis
|
|
1362
|
+
|
|
1363
|
+
### Fraud Types Identified
|
|
1364
|
+
${report.legalSummary.fraudTypes.map(t => `- ${t}`).join('\n')}
|
|
1365
|
+
|
|
1366
|
+
### Applicable Laws
|
|
1367
|
+
${report.legalSummary.applicableLaws.map(l => `- ${l}`).join('\n')}
|
|
1368
|
+
|
|
1369
|
+
### Recommended Actions
|
|
1370
|
+
${report.legalSummary.recommendations.map(r => `- ${r}`).join('\n')}
|
|
1371
|
+
|
|
1372
|
+
---
|
|
1373
|
+
|
|
1374
|
+
## Company Claims vs. Reality
|
|
1375
|
+
|
|
1376
|
+
${report.claims.apple ? `
|
|
1377
|
+
### Apple iMessage PQ3 Claims
|
|
1378
|
+
${Object.entries(report.claims.apple).map(([key, data]) => `
|
|
1379
|
+
**${key.replace(/_/g, ' ')}**
|
|
1380
|
+
- Claim: "${data.claim}"
|
|
1381
|
+
- Source: ${data.source}
|
|
1382
|
+
- Verifiable: ${data.verifiable}
|
|
1383
|
+
- Reality: ${data.reason}
|
|
1384
|
+
`).join('\n')}
|
|
1385
|
+
` : ''}
|
|
1386
|
+
|
|
1387
|
+
${report.claims.google_gmail ? `
|
|
1388
|
+
### Google Gmail Claims
|
|
1389
|
+
${Object.entries(report.claims.google_gmail).map(([key, data]) => `
|
|
1390
|
+
**${key.replace(/_/g, ' ')}**
|
|
1391
|
+
- Claim: "${data.claim}"
|
|
1392
|
+
- Source: ${data.source}
|
|
1393
|
+
- Verifiable: ${data.verifiable}
|
|
1394
|
+
- Reality: ${data.reason}
|
|
1395
|
+
`).join('\n')}
|
|
1396
|
+
` : ''}
|
|
1397
|
+
|
|
1398
|
+
${report.claims.google_chrome ? `
|
|
1399
|
+
### Google Chrome Claims
|
|
1400
|
+
${Object.entries(report.claims.google_chrome).map(([key, data]) => `
|
|
1401
|
+
**${key.replace(/_/g, ' ')}**
|
|
1402
|
+
- Claim: "${data.claim}"
|
|
1403
|
+
- Source: ${data.source}
|
|
1404
|
+
- Verifiable: ${data.verifiable}
|
|
1405
|
+
- Reality: ${data.reason}
|
|
1406
|
+
`).join('\n')}
|
|
1407
|
+
` : ''}
|
|
1408
|
+
|
|
1409
|
+
---
|
|
1410
|
+
|
|
1411
|
+
## Conclusion
|
|
1412
|
+
|
|
1413
|
+
This investigation has documented ${report.summary.totalFindings} findings of potential fraud and manipulation,
|
|
1414
|
+
including ${report.summary.criticalFindings} critical severity issues. The evidence collected herein
|
|
1415
|
+
demonstrates patterns of behavior that contradict the public claims made by ${report.investigation.target.toUpperCase()}.
|
|
1416
|
+
|
|
1417
|
+
---
|
|
1418
|
+
|
|
1419
|
+
*Generated by erosolar-cli Unified Fraud Investigation Orchestrator*
|
|
1420
|
+
*Report Hash: ${hashString(JSON.stringify(report))}*
|
|
1421
|
+
`;
|
|
1422
|
+
}
|
|
1423
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
1424
|
+
// Persistence
|
|
1425
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
1426
|
+
async persistInvestigation(investigation) {
|
|
1427
|
+
const filePath = path.join(this.storageDir, `${investigation.id}.json`);
|
|
1428
|
+
await fs.writeFile(filePath, JSON.stringify(investigation, null, 2));
|
|
1429
|
+
}
|
|
1430
|
+
async loadInvestigation(investigationId) {
|
|
1431
|
+
try {
|
|
1432
|
+
const filePath = path.join(this.storageDir, `${investigationId}.json`);
|
|
1433
|
+
const data = await fs.readFile(filePath, 'utf8');
|
|
1434
|
+
const investigation = JSON.parse(data);
|
|
1435
|
+
this.investigations.set(investigation.id, investigation);
|
|
1436
|
+
return investigation;
|
|
1437
|
+
}
|
|
1438
|
+
catch {
|
|
1439
|
+
return null;
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
getInvestigations() {
|
|
1443
|
+
return [...this.investigations.values()];
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1447
|
+
// EXPORTS (note: GOOGLE_GMAIL_CLAIMS and GOOGLE_CHROME_CLAIMS are already exported inline)
|
|
1448
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
1449
|
+
//# sourceMappingURL=unifiedFraudOrchestrator.js.map
|