yaml-flow 8.1.1 → 8.2.1

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.
Files changed (108) hide show
  1. package/browser/asset-integrity.json +3 -3
  2. package/browser/board-livecards-client.js +1 -1
  3. package/browser/board-livecards-localstorage.js +4 -6
  4. package/cli/{board-live-cards-lib-tjYsPt5U.d.ts → board-live-cards-lib-Iq_XAC09.d.ts} +1 -1
  5. package/cli/browser-api/board-live-cards-browser-adapter.d.ts +4 -3
  6. package/cli/browser-api/board-live-cards-browser-adapter.js +2 -2
  7. package/cli/browser-api/card-store-browser-api.d.ts +1 -1
  8. package/cli/node/artifacts-store-cli.js +8 -8
  9. package/cli/node/board-live-cards-cli.js +8 -8
  10. package/cli/node/card-store-cli.js +4 -4
  11. package/cli/node/fs-board-adapter.d.ts +6 -33
  12. package/cli/node/fs-board-adapter.js +10 -8
  13. package/cli/node/step-machine-cli.js +3 -3
  14. package/cli/{types-D2XnLbBj.d.ts → types--rXGWbSR.d.ts} +77 -5
  15. package/examples/board/.board-ws/cards/store/_index.json +17 -0
  16. package/examples/board/.board-ws/cards/store/card-market-prices.json +80 -0
  17. package/examples/board/.board-ws/cards/store/card-portfolio-value.json +90 -0
  18. package/examples/board/.board-ws/cards/store/card-portfolio.json +78 -0
  19. package/examples/board/cards/cardT-market-prices.json +6 -4
  20. package/examples/board/cards/cardT-portfolio-value.json +10 -38
  21. package/examples/board/cards/cardT-portfolio.json +9 -4
  22. package/examples/board/demo-shell-with-server.html +3 -3
  23. package/examples/board/server/board-server.js +593 -0
  24. package/examples/board/server/board-worker/source-def-flows/mock-handler/mock-db.js +13 -0
  25. package/examples/board/server/board-worker/source-def-flows/sqlite-handler/.retain/compliance.db +0 -0
  26. package/examples/board/server/board-worker/source-def-flows/sqlite-handler/.retain/optimus.db +0 -0
  27. package/examples/board/server/board-worker/source-def-flows/sqlite-handler/query.cjs +51 -0
  28. package/examples/board/server/board-worker/source-def-flows/sqlite-handler/seed-cpm.cjs +197 -0
  29. package/examples/board/server/board-worker/source-def-flows/sqlite-handler/seed-cpmV2.cjs +128 -0
  30. package/examples/board/server/board-worker/source-def-flows/sqlite-handler/seed-optimus.cjs +352 -0
  31. package/examples/board/server/board-worker/source-def-flows/sqlite-handler/sqlite-config.json +3 -0
  32. package/examples/board/server/board-worker/source-def-flows/sqlite-handler/sqlite-handler.js +84 -0
  33. package/examples/board/{source-def-flows/url.flow.json → server/board-worker/source-def-flows/sqlite.flow.json} +7 -7
  34. package/examples/board/{source-def-handlers → server/board-worker/source-def-flows/url-handler}/http-source-handler.js +29 -21
  35. package/examples/board/server/board-worker/source-def-flows/url.flow.json +73 -0
  36. package/examples/board/{source_def_flows.json → server/board-worker/source_def_flows.json} +61 -115
  37. package/examples/board/server/board-worker/task-executor.js +475 -0
  38. package/examples/board/server/chat-flow/chat-clear-processing.js +41 -0
  39. package/examples/board/server/chat-flow/chat-open-turn.js +144 -0
  40. package/examples/board/server/chat-flow/chat-write-assistant.js +44 -0
  41. package/examples/board/server/chat-flow/copilot-chat/assistant.js +253 -0
  42. package/examples/board/server/chat-flow/echo-probe/assistant.js +28 -0
  43. package/examples/board/server/chat-flow/flow-steps.json +167 -0
  44. package/examples/board/server-config.json +22 -0
  45. package/examples/board/test/server-http-test.js +707 -0
  46. package/examples/board/test/{portfolio-tracker-sse-worker.js → sse-worker.js} +9 -8
  47. package/examples/board-local/demo-shell-localstorage.html +3 -3
  48. package/lib/{artifacts-store-lib-public-DBICnGL6.d.cts → artifacts-store-lib-public-C5UL5tyG.d.cts} +3 -31
  49. package/lib/{artifacts-store-lib-public-BWC3YuLa.d.ts → artifacts-store-lib-public-GD4H-fFp.d.ts} +3 -31
  50. package/lib/artifacts-store-public.d.cts +3 -3
  51. package/lib/artifacts-store-public.d.ts +3 -3
  52. package/lib/board-live-cards-node.cjs +10 -8
  53. package/lib/board-live-cards-node.d.cts +9 -8
  54. package/lib/board-live-cards-node.d.ts +9 -8
  55. package/lib/board-live-cards-node.js +10 -8
  56. package/lib/{board-live-cards-public-BF9FP0mL.d.cts → board-live-cards-public-BLXbcBNk.d.cts} +2 -2
  57. package/lib/{board-live-cards-public-dJAl5IL-.d.ts → board-live-cards-public-BZaNb2mi.d.ts} +2 -2
  58. package/lib/board-live-cards-public.cjs +2 -2
  59. package/lib/board-live-cards-public.d.cts +2 -2
  60. package/lib/board-live-cards-public.d.ts +2 -2
  61. package/lib/board-live-cards-public.js +2 -2
  62. package/lib/board-live-cards-server-runtime.cjs +4 -6
  63. package/lib/board-live-cards-server-runtime.d.cts +3 -3
  64. package/lib/board-live-cards-server-runtime.d.ts +3 -3
  65. package/lib/board-live-cards-server-runtime.js +4 -6
  66. package/lib/board-livegraph-runtime/index.cjs +2 -2
  67. package/lib/board-livegraph-runtime/index.js +2 -2
  68. package/lib/card-store-public.d.cts +2 -2
  69. package/lib/card-store-public.d.ts +2 -2
  70. package/lib/execution-refs.cjs +1 -1
  71. package/lib/execution-refs.js +1 -1
  72. package/lib/index.cjs +1 -1
  73. package/lib/index.d.cts +1 -1
  74. package/lib/index.d.ts +1 -1
  75. package/lib/index.js +1 -1
  76. package/lib/server-runtime/index.cjs +4 -6
  77. package/lib/server-runtime/index.d.cts +4 -4
  78. package/lib/server-runtime/index.d.ts +4 -4
  79. package/lib/server-runtime/index.js +4 -6
  80. package/lib/step-machine-public/index.cjs +3 -3
  81. package/lib/step-machine-public/index.d.cts +27 -10
  82. package/lib/step-machine-public/index.d.ts +27 -10
  83. package/lib/step-machine-public/index.js +3 -3
  84. package/lib/{storage-interface-BhAON-gW.d.ts → storage-interface-B6ecOulj.d.cts} +25 -3
  85. package/lib/{storage-interface-BhAON-gW.d.cts → storage-interface-B6ecOulj.d.ts} +25 -3
  86. package/lib/stores/index.d.cts +1 -1
  87. package/lib/stores/index.d.ts +1 -1
  88. package/lib/stores/kv.d.cts +1 -1
  89. package/lib/stores/kv.d.ts +1 -1
  90. package/lib/{types-CXBzvC0s.d.cts → types-Bztd1KoK.d.cts} +75 -3
  91. package/lib/{types-D48hpnTR.d.ts → types-D-xVWPdY.d.ts} +75 -3
  92. package/package.json +1 -1
  93. package/examples/board/demo-chat-handler.js +0 -169
  94. package/examples/board/demo-server-config.json +0 -10
  95. package/examples/board/demo-server.js +0 -580
  96. package/examples/board/demo-task-executor.js +0 -721
  97. package/examples/board/gandalf-cards/card-source-kinds.json +0 -36
  98. package/examples/board/gandalf-cards/cards/_index.json +0 -7
  99. package/examples/board/gandalf-cards/cards/card-source-kinds.json +0 -64
  100. package/examples/board/scripts/copilot_wrapper.bat +0 -157
  101. package/examples/board/scripts/copilot_wrapper_helper.ps1 +0 -190
  102. package/examples/board/scripts/workiq_wrapper.mjs +0 -66
  103. package/examples/board/source-def-flows/copilot.flow.json +0 -33
  104. package/examples/board/source-def-flows/url-list.flow.json +0 -33
  105. package/examples/board/source-def-flows/workiq.flow.json +0 -34
  106. package/examples/board/source-def-handlers/copilot-source-handler.js +0 -141
  107. package/examples/board/test/demo-http-test.js +0 -317
  108. /package/examples/board/{source-def-flows → server/board-worker/source-def-flows}/mock.flow.json +0 -0
@@ -0,0 +1,352 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * seed-optimus.cjs — Create and seed the OPTIMUS threat hunting database.
5
+ *
6
+ * Usage:
7
+ * node seed-optimus.cjs [--db <path>]
8
+ *
9
+ * Default db path: sqlite-handler/.retain/optimus.db
10
+ */
11
+
12
+ const Database = require('better-sqlite3');
13
+ const path = require('path');
14
+ const fs = require('fs');
15
+
16
+ const args = process.argv.slice(2);
17
+ const dbArgIdx = args.indexOf('--db');
18
+ const dbPath = dbArgIdx !== -1 && args[dbArgIdx + 1]
19
+ ? path.resolve(args[dbArgIdx + 1])
20
+ : path.resolve(__dirname, '.retain', 'optimus.db');
21
+
22
+ const dbDir = path.dirname(dbPath);
23
+ if (!fs.existsSync(dbDir)) fs.mkdirSync(dbDir, { recursive: true });
24
+ if (fs.existsSync(dbPath)) fs.unlinkSync(dbPath);
25
+
26
+ const db = new Database(dbPath);
27
+
28
+ // ---------------------------------------------------------------------------
29
+ // Schema
30
+ // ---------------------------------------------------------------------------
31
+ db.exec(`
32
+ CREATE TABLE attack_planes (
33
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
34
+ plane TEXT NOT NULL UNIQUE,
35
+ data_source TEXT NOT NULL,
36
+ signal_type TEXT,
37
+ t_weight REAL DEFAULT 0.0,
38
+ description TEXT
39
+ );
40
+
41
+ CREATE TABLE tapc_config (
42
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
43
+ component TEXT NOT NULL CHECK(component IN ('T','A','P','C','negative')),
44
+ signal TEXT NOT NULL,
45
+ weight REAL NOT NULL,
46
+ description TEXT
47
+ );
48
+
49
+ CREATE TABLE agents (
50
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
51
+ name TEXT NOT NULL UNIQUE,
52
+ role TEXT NOT NULL,
53
+ model TEXT,
54
+ tool_count INTEGER DEFAULT 0,
55
+ status TEXT DEFAULT 'idle' CHECK(status IN ('idle','scanning','graphing','validating','critiquing','complete','error')),
56
+ last_run TEXT,
57
+ description TEXT
58
+ );
59
+
60
+ CREATE TABLE scan_candidates (
61
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
62
+ entity TEXT NOT NULL,
63
+ org_id TEXT NOT NULL,
64
+ plane TEXT NOT NULL,
65
+ pre_graph_ta REAL DEFAULT 0.0,
66
+ t_score REAL DEFAULT 0.0,
67
+ a_score REAL DEFAULT 0.0,
68
+ p_score REAL,
69
+ c_score REAL,
70
+ tapc_final REAL,
71
+ promoted INTEGER DEFAULT 0,
72
+ run_date TEXT,
73
+ details TEXT,
74
+ FOREIGN KEY (plane) REFERENCES attack_planes(plane)
75
+ );
76
+
77
+ CREATE TABLE findings (
78
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
79
+ entity TEXT NOT NULL,
80
+ org_id TEXT NOT NULL,
81
+ tapc_score REAL NOT NULL,
82
+ admiralty_code TEXT,
83
+ mitre_technique TEXT,
84
+ kill_chain_stage TEXT,
85
+ status TEXT DEFAULT 'validated' CHECK(status IN ('validated','downgraded','fp_killed','escalated')),
86
+ summary TEXT,
87
+ run_date TEXT
88
+ );
89
+
90
+ CREATE TABLE fp_patterns (
91
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
92
+ pattern_name TEXT NOT NULL,
93
+ plane TEXT,
94
+ suppression_rule TEXT,
95
+ hits INTEGER DEFAULT 0,
96
+ added_date TEXT,
97
+ description TEXT
98
+ );
99
+
100
+ CREATE TABLE scan_history (
101
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
102
+ run_date TEXT NOT NULL,
103
+ plane TEXT NOT NULL,
104
+ candidates_found INTEGER DEFAULT 0,
105
+ promoted INTEGER DEFAULT 0,
106
+ findings_validated INTEGER DEFAULT 0,
107
+ fp_killed INTEGER DEFAULT 0,
108
+ FOREIGN KEY (plane) REFERENCES attack_planes(plane)
109
+ );
110
+
111
+ CREATE TABLE mcp_tools (
112
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
113
+ server TEXT NOT NULL,
114
+ tool_name TEXT NOT NULL,
115
+ agent TEXT,
116
+ category TEXT,
117
+ description TEXT
118
+ );
119
+ `);
120
+
121
+ // ---------------------------------------------------------------------------
122
+ // Seed: Attack Planes
123
+ // ---------------------------------------------------------------------------
124
+ const insertPlane = db.prepare(`
125
+ INSERT INTO attack_planes (plane, data_source, signal_type, t_weight, description)
126
+ VALUES (?, ?, ?, ?, ?)
127
+ `);
128
+
129
+ const planes = [
130
+ ['Identity Auth', 'IdentityLogonEvents', 'Legacy auth protocols', 0.90, 'Authentication events — legacy auth, MFA bypass, protocol abuse'],
131
+ ['Identity Control', 'CloudAppEvents', 'Role/permission changes', 0.75, 'Administrative actions — role assignments, conditional access changes, app registrations'],
132
+ ['Identity Recon', 'IdentityQueryEvents', 'LDAP/SAM-R enumeration', 0.65, 'Reconnaissance — directory queries, group enumeration, user discovery'],
133
+ ['Cloud / SaaS', 'CloudAppEvents', 'Inbox rules, OAuth grants', 0.85, 'Cloud post-compromise — inbox rule creation, OAuth consent, data exfiltration'],
134
+ ['Endpoint', 'MtpAlertEvidence', 'Process/file anomalies', 0.70, 'Endpoint indicators — suspicious processes, file drops, persistence mechanisms'],
135
+ ['Network', 'IdentityLogonEvents', 'Geo-impossible travel, VPN', 0.60, 'Network-layer signals — impossible travel, known bad IPs, TOR exit nodes'],
136
+ ['Detection Gaps', 'MtpAlerts (inverse)', 'Absence-of-alerts signal', 0.80, 'Inverse detection — accounts with telemetry anomalies but ZERO existing alerts'],
137
+ ];
138
+ for (const p of planes) insertPlane.run(...p);
139
+
140
+ // ---------------------------------------------------------------------------
141
+ // Seed: TAPC Configuration
142
+ // ---------------------------------------------------------------------------
143
+ const insertTapc = db.prepare(`
144
+ INSERT INTO tapc_config (component, signal, weight, description)
145
+ VALUES (?, ?, ?, ?)
146
+ `);
147
+
148
+ const tapcConfig = [
149
+ // T — Threat Likeness (weight 0.4)
150
+ ['T', 'autologon', 0.95, 'Autologon protocol — highest exploitation risk'],
151
+ ['T', 'NTLMv1', 0.90, 'NTLMv1 authentication — trivially crackable'],
152
+ ['T', 'WSTrust', 0.85, 'WS-Trust mixed endpoint — federation abuse vector'],
153
+ ['T', 'deviceCode', 0.80, 'Device code flow — phishing-friendly OAuth grant'],
154
+ ['T', 'WeakKerb', 0.75, 'Weak Kerberos encryption (RC4/DES)'],
155
+ ['T', 'LegacyMail', 0.70, 'Legacy mail protocols (POP3/IMAP/SMTP AUTH)'],
156
+ ['T', 'LDAPclear', 0.65, 'LDAP cleartext bind — credential exposure'],
157
+ ['T', 'ADFS', 0.55, 'ADFS authentication — moderate federation risk'],
158
+ // A — Anomaly (weight 0.2)
159
+ ['A', 'volume_spike', 0.80, 'Volume exceeds 3-sigma above 30-day baseline'],
160
+ ['A', 'new_ip', 0.70, 'IP address never seen for this entity in 30 days'],
161
+ ['A', 'geo_impossible', 0.90, 'Geographically impossible travel between auth events'],
162
+ ['A', 'off_hours', 0.50, 'Activity outside normal working hours pattern'],
163
+ ['A', 'spray_structure', 0.85, 'Password spray: high fail + many users + legacy proto'],
164
+ ['A', 'cross_plane', 0.15, 'Bonus: entity appears in >=2 attack planes'],
165
+ // P — Progression (weight 0.3)
166
+ ['P', 'credential', 0.10, 'P1: Initial credential access attempt'],
167
+ ['P', 'token_exchange', 0.15, 'P2: Token exchange / elevation'],
168
+ ['P', 'resource_access', 0.25, 'P3: Resource access (mail, files, APIs)'],
169
+ ['P', 'persistence', 0.30, 'P4: Persistence mechanisms (inbox rules, app consent)'],
170
+ ['P', 'lateral_movement',0.20, 'P5: Lateral movement to other accounts/systems'],
171
+ // C — Context (weight 0.1)
172
+ ['C', 'admin_privilege', 1.50, 'Admin account multiplier (1.5x)'],
173
+ ['C', 'sso_misconfigured',0.70, 'SSO misconfiguration detected in tenant'],
174
+ ['C', 'prior_alerts', 0.60, 'Entity has existing (potentially unrelated) alerts'],
175
+ // Negative evidence
176
+ ['negative', 'internal_inbox', -0.40, 'Forwarding to internal inbox only — benign pattern'],
177
+ ['negative', 'managed_device', -0.20, 'Auth from corporate managed device'],
178
+ ['negative', 'peer_prevalence', -0.30, 'IP seen across many peer accounts (shared infra)'],
179
+ ['negative', 'svc_account', -0.20, 'Service account with expected legacy auth pattern'],
180
+ ['negative', 'ip_100orgs', -0.30, 'IP appears in 100+ orgs — shared infrastructure'],
181
+ ];
182
+ for (const t of tapcConfig) insertTapc.run(...t);
183
+
184
+ // ---------------------------------------------------------------------------
185
+ // Seed: Agents
186
+ // ---------------------------------------------------------------------------
187
+ const insertAgent = db.prepare(`
188
+ INSERT INTO agents (name, role, model, tool_count, status, last_run, description)
189
+ VALUES (?, ?, ?, ?, ?, ?, ?)
190
+ `);
191
+
192
+ const agentData = [
193
+ ['BEACON', 'TAPC Observable Scanner', 'Claude Opus 4.6', 10, 'complete', '2026-04-27', 'Sweeps all 7 attack planes with TAPC T-signal scoring. Generates anomaly candidates and promotes findings >= 0.35 PreGraphTA.'],
194
+ ['WEAVER', 'Graph Pattern Operator', 'Claude Opus 4.6', 8, 'complete', '2026-04-27', 'Builds observable graphs from promoted candidates. Detects temporal attack motifs and cross-tenant campaign infrastructure.'],
195
+ ['CRUCIBLE', 'Stress-Test Validator', 'Claude Opus 4.6', 5, 'complete', '2026-04-27', 'Assumes every finding is FP. Applies 7-check validation framework. Generates Admiralty/NATO confidence codes.'],
196
+ ['RUBBER DUCK', 'Independent Critique Agent', 'Claude Sonnet 4.6', 0, 'complete', '2026-04-27', 'Provides adversarial feedback. Catches overclaims, logic errors, FP blind spots. Separate model for independent reasoning.'],
197
+ ];
198
+ for (const a of agentData) insertAgent.run(...a);
199
+
200
+ // ---------------------------------------------------------------------------
201
+ // Seed: Scan Candidates (Cycle 3: 2026-04-27 — latest)
202
+ // ---------------------------------------------------------------------------
203
+ const insertCandidate = db.prepare(`
204
+ INSERT INTO scan_candidates (entity, org_id, plane, pre_graph_ta, t_score, a_score, p_score, c_score, tapc_final, promoted, run_date, details)
205
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
206
+ `);
207
+
208
+ const c3 = '2026-04-27';
209
+ const candidates = [
210
+ // Promoted candidates (PreGraphTA >= 0.35)
211
+ ['UserPII_a7f3e2d1', 'OrgPII_contoso', 'Identity Auth', 0.62, 0.90, 0.50, 0.25, 0.70, 0.68, 1, c3, 'WS-Trust mixed auth from new IP, volume 4.2-sigma above baseline, off-hours'],
212
+ ['UserPII_b8c4f901', 'OrgPII_fabrikam', 'Identity Auth', 0.54, 0.85, 0.40, 0.15, 0.60, 0.57, 1, c3, 'Device code flow from residential IP, no MFA challenge, first-time protocol use'],
213
+ ['UserPII_c9d5e012', 'OrgPII_contoso', 'Cloud / SaaS', 0.48, 0.85, 0.30, 0.30, 0.80, 0.62, 1, c3, 'New-InboxRule forwarding to external domain 2h after legacy auth'],
214
+ ['UserPII_a7f3e2d1', 'OrgPII_contoso', 'Cloud / SaaS', 0.44, 0.70, 0.35, 0.25, 0.70, 0.55, 1, c3, 'OAuth consent grant for unknown app post-WSTrust auth (cross-plane)'],
215
+ ['UserPII_d1e6f123', 'OrgPII_woodgrove', 'Identity Auth', 0.42, 0.75, 0.30, 0.10, 0.50, 0.47, 1, c3, 'Legacy SMTP AUTH from IP seen in only this org, 3.1-sigma spike'],
216
+ ['UserPII_e2f7a234', 'OrgPII_contoso', 'Detection Gaps', 0.40, 0.80, 0.20, null, null, null, 1, c3, 'Admin account with 47 identity events, ZERO existing alerts'],
217
+ ['UserPII_f3a8b345', 'OrgPII_fabrikam', 'Identity Recon', 0.38, 0.65, 0.30, null, null, null, 1, c3, 'LDAP enumeration of all global admin group members'],
218
+ ['UserPII_a4b9c456', 'OrgPII_woodgrove', 'Endpoint', 0.36, 0.70, 0.20, null, null, null, 1, c3, 'Suspicious PowerShell execution post-auth from flagged IP'],
219
+ // Not promoted (below threshold)
220
+ ['UserPII_x1y2z301', 'OrgPII_contoso', 'Network', 0.28, 0.60, 0.10, null, null, null, 0, c3, 'VPN from new geo — but managed device, peer prevalence high'],
221
+ ['UserPII_x2y3z402', 'OrgPII_fabrikam', 'Identity Auth', 0.24, 0.55, 0.05, null, null, null, 0, c3, 'ADFS auth from known corporate range — benign service account'],
222
+ ['UserPII_x3y4z503', 'OrgPII_contoso', 'Identity Control', 0.22, 0.40, 0.15, null, null, null, 0, c3, 'Role assignment — but part of scheduled rotation'],
223
+ ['UserPII_x4y5z604', 'OrgPII_woodgrove', 'Cloud / SaaS', 0.20, 0.35, 0.12, null, null, null, 0, c3, 'Inbox rule but internal-only forwarding (same domain)'],
224
+ ['UserPII_x5y6z705', 'OrgPII_fabrikam', 'Identity Auth', 0.18, 0.45, 0.00, null, null, null, 0, c3, 'Legacy auth but high peer prevalence — shared VPN gateway'],
225
+ ['UserPII_x6y7z806', 'OrgPII_contoso', 'Network', 0.14, 0.30, 0.05, null, null, null, 0, c3, 'Off-hours access but from home IP consistently used'],
226
+ ['UserPII_x7y8z907', 'OrgPII_woodgrove', 'Identity Recon', 0.12, 0.25, 0.05, null, null, null, 0, c3, 'Single LDAP query — normal admin behavior'],
227
+ ];
228
+ for (const c of candidates) insertCandidate.run(...c);
229
+
230
+ // ---------------------------------------------------------------------------
231
+ // Seed: Validated Findings (Cycle 3)
232
+ // ---------------------------------------------------------------------------
233
+ const insertFinding = db.prepare(`
234
+ INSERT INTO findings (entity, org_id, tapc_score, admiralty_code, mitre_technique, kill_chain_stage, status, summary, run_date)
235
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
236
+ `);
237
+
238
+ const findingsData = [
239
+ ['UserPII_a7f3e2d1', 'OrgPII_contoso', 0.68, 'B1', 'T1078.004 + T1098.003', 'Credential → Persistence', 'validated', 'WS-Trust auth from new IP → OAuth consent grant for unknown app → 2 cross-plane signals. Admin account, no existing alerts. Kill chain: credential access via federation abuse followed by persistence through malicious OAuth app.', c3],
240
+ ['UserPII_b8c4f901', 'OrgPII_fabrikam', 0.57, 'B2', 'T1528', 'Credential → Token Theft', 'validated', 'Device code phishing pattern: residential IP, no prior auth history, token obtained without MFA. Corroborated across IdentityLogonEvents + CloudAppEvents. Same IP seen in 2 other DEX orgs.', c3],
241
+ ['UserPII_c9d5e012', 'OrgPII_contoso', 0.62, 'B1', 'T1114.003 + T1078', 'Access → Exfil Staging', 'validated', 'Inbox rule forwarding to external domain created 2h after legacy SMTP auth. External domain registered 3 days prior. Classic BEC post-compromise pattern.', c3],
242
+ ['UserPII_d1e6f123', 'OrgPII_woodgrove', 0.47, 'B2', 'T1110.003', 'Credential Spray', 'downgraded', 'Legacy SMTP spike corroborated but limited to single protocol. No post-compromise activity detected in 24h window. Downgraded from B1 to B2 by RUBBER DUCK — insufficient progression evidence.', c3],
243
+ ['UserPII_e2f7a234', 'OrgPII_contoso', 0.40, 'B2', 'T1078 (suspected)', 'Detection Gap — No Actions', 'validated', 'Admin with 47 identity events and zero alerts. High T-signal (legacy auth protocols) but no confirmed post-compromise activity. Flagged as detection gap for monitoring — existing detection coverage insufficient.', c3],
244
+ ];
245
+ for (const f of findingsData) insertFinding.run(...f);
246
+
247
+ // ---------------------------------------------------------------------------
248
+ // Seed: FP Pattern Library
249
+ // ---------------------------------------------------------------------------
250
+ const insertFP = db.prepare(`
251
+ INSERT INTO fp_patterns (pattern_name, plane, suppression_rule, hits, added_date, description)
252
+ VALUES (?, ?, ?, ?, ?, ?)
253
+ `);
254
+
255
+ const fpPatterns = [
256
+ ['same_domain_forward', 'Cloud / SaaS', 'inbox_rule.forward_to LIKE same_org_domain', 234, '2026-04-20', 'Inbox rule forwarding within same organization domain — benign delegation pattern'],
257
+ ['managed_device_legacy', 'Identity Auth', 'device.compliant = true AND auth.legacy = true', 189, '2026-04-15', 'Legacy auth from managed device — often VPN client or legacy thick client'],
258
+ ['shared_vpn_gateway', 'Identity Auth', 'ip.org_count >= 50', 156, '2026-04-15', 'IP used by 50+ orgs — shared VPN/proxy infrastructure'],
259
+ ['svc_account_pattern', 'Identity Auth', 'account.type = service AND auth.pattern = recurring', 98, '2026-04-18', 'Service account with predictable legacy auth pattern'],
260
+ ['admin_role_rotation', 'Identity Control', 'role_change.scheduled = true', 67, '2026-04-20', 'Planned admin role rotation per IT change management calendar'],
261
+ ['geo_vpn_expected', 'Network', 'ip.geo_distance > 1000km AND ip.is_corp_vpn = true', 45, '2026-04-22', 'Impossible travel from known corporate VPN exit nodes'],
262
+ ['monitoring_ldap', 'Identity Recon', 'query.source = monitoring_tool AND query.pattern = periodic', 34, '2026-04-25', 'LDAP queries from known monitoring tools (periodic health checks)'],
263
+ ['stale_token_refresh', 'Cloud / SaaS', 'token.type = refresh AND token.age > 30d', 12, '2026-04-27', 'Stale refresh token usage — usually automated app re-auth, not attacker'],
264
+ ];
265
+ for (const f of fpPatterns) insertFP.run(...f);
266
+
267
+ // ---------------------------------------------------------------------------
268
+ // Seed: Scan History (3 cycles)
269
+ // ---------------------------------------------------------------------------
270
+ const insertHistory = db.prepare(`
271
+ INSERT INTO scan_history (run_date, plane, candidates_found, promoted, findings_validated, fp_killed)
272
+ VALUES (?, ?, ?, ?, ?, ?)
273
+ `);
274
+
275
+ const historyData = [
276
+ // Cycle 1: 2026-04-13 — baseline, quiet period
277
+ ['2026-04-13', 'Identity Auth', 8, 2, 0, 1],
278
+ ['2026-04-13', 'Identity Control', 3, 0, 0, 0],
279
+ ['2026-04-13', 'Identity Recon', 2, 0, 0, 0],
280
+ ['2026-04-13', 'Cloud / SaaS', 5, 1, 0, 1],
281
+ ['2026-04-13', 'Endpoint', 1, 0, 0, 0],
282
+ ['2026-04-13', 'Network', 4, 1, 0, 0],
283
+ ['2026-04-13', 'Detection Gaps', 2, 1, 0, 0],
284
+ // Cycle 2: 2026-04-20 — first anomalies, FP discovery
285
+ ['2026-04-20', 'Identity Auth', 14, 5, 1, 2],
286
+ ['2026-04-20', 'Identity Control', 4, 1, 0, 1],
287
+ ['2026-04-20', 'Identity Recon', 6, 2, 0, 1],
288
+ ['2026-04-20', 'Cloud / SaaS', 9, 3, 1, 1],
289
+ ['2026-04-20', 'Endpoint', 3, 1, 0, 0],
290
+ ['2026-04-20', 'Network', 5, 1, 0, 1],
291
+ ['2026-04-20', 'Detection Gaps', 4, 2, 1, 0],
292
+ // Cycle 3: 2026-04-27 — campaign detected, multi-tenant
293
+ ['2026-04-27', 'Identity Auth', 18, 8, 2, 3],
294
+ ['2026-04-27', 'Identity Control', 5, 1, 0, 1],
295
+ ['2026-04-27', 'Identity Recon', 7, 3, 0, 1],
296
+ ['2026-04-27', 'Cloud / SaaS', 12, 4, 2, 2],
297
+ ['2026-04-27', 'Endpoint', 4, 2, 0, 0],
298
+ ['2026-04-27', 'Network', 6, 2, 0, 1],
299
+ ['2026-04-27', 'Detection Gaps', 5, 3, 1, 0],
300
+ ];
301
+ for (const h of historyData) insertHistory.run(...h);
302
+
303
+ // ---------------------------------------------------------------------------
304
+ // Seed: MCP Tools (representative subset)
305
+ // ---------------------------------------------------------------------------
306
+ const insertTool = db.prepare(`
307
+ INSERT INTO mcp_tools (server, tool_name, agent, category, description)
308
+ VALUES (?, ?, ?, ?, ?)
309
+ `);
310
+
311
+ const toolsData = [
312
+ ['optimus', 'beacon_scan_all_planes', 'BEACON', 'scan', 'Parallel KQL sweep across all 7 attack planes'],
313
+ ['optimus', 'beacon_scan_plane', 'BEACON', 'scan', 'Targeted single-plane anomaly scan'],
314
+ ['optimus', 'beacon_fuse_results', 'BEACON', 'fusion', 'Cross-plane entity fusion and promotion'],
315
+ ['optimus', 'beacon_hunt_password_spray', 'BEACON', 'hunt', 'Structural spray detection (post-promotion only)'],
316
+ ['optimus', 'beacon_status', 'BEACON', 'status', 'Current BEACON agent state'],
317
+ ['optimus', 'weaver_build_graph', 'WEAVER', 'graph', 'Kusto make-graph: user→IP→action edges'],
318
+ ['optimus', 'weaver_find_motifs', 'WEAVER', 'graph', 'Temporal attack motif detection'],
319
+ ['optimus', 'weaver_find_campaigns', 'WEAVER', 'graph', 'Cross-tenant campaign infrastructure (>=3 orgs)'],
320
+ ['optimus', 'weaver_cross_plane', 'WEAVER', 'graph', 'Identity→Cloud plane traversal detection'],
321
+ ['optimus', 'weaver_blast_radius', 'WEAVER', 'graph', 'Compromised account blast radius assessment'],
322
+ ['optimus', 'weaver_enrich_pc', 'WEAVER', 'enrich', 'P/C score enrichment from graph context'],
323
+ ['optimus', 'crucible_validate', 'CRUCIBLE', 'validate', '7-check validation framework'],
324
+ ['optimus', 'crucible_investigate_deeper', 'CRUCIBLE', 'validate', 'Deep investigation KQL generation'],
325
+ ['optimus', 'crucible_kill_fp', 'CRUCIBLE', 'validate', 'Add FP to negative evidence library'],
326
+ ['optimus', 'optimus_run_cycle', 'MASTER', 'pipeline', 'Full TAPC-first pipeline: BEACON→WEAVER→CRUCIBLE'],
327
+ ['optimus', 'optimus_get_tapc_config', 'MASTER', 'config', 'Full TAPC scoring configuration'],
328
+ ['optimus', 'optimus_get_fp_patterns', 'MASTER', 'config', 'All known false positive patterns'],
329
+ ['optimus', 'optimus_remember', 'MASTER', 'memory', 'Persist finding/pattern to knowledge store'],
330
+ ['optimus', 'optimus_recall', 'MASTER', 'memory', 'Search persistent knowledge'],
331
+ ['attack_intersectionality', 'lookup_technique', null, 'mitre', 'MITRE technique lookup by ID or name'],
332
+ ['attack_intersectionality', 'analyze_intersection', null, 'mitre', 'Pairwise technique intersection analysis'],
333
+ ['attack_intersectionality', 'match_attack_chain', null, 'mitre', 'Match observed techniques to known attack chains'],
334
+ ['attack_intersectionality', 'tapc_score', null, 'tapc', 'TAPC model explanation and formula'],
335
+ ['attack_intersectionality', 'validate_finding', null, 'validate', '7-check self-critic validation engine'],
336
+ ['attack_intersectionality', 'investigate', null, 'pivot', 'Investigation pivot (IP/user/org)'],
337
+ ['attack_intersectionality', 'scrub', null, 'ghost', 'SHA-1 PII hashing'],
338
+ ['attack_intersectionality', 'unscrub', null, 'ghost', 'Rainbow table PII reversal'],
339
+ ['kusto', 'kusto_query', null, 'kql', 'Execute KQL query against ADX cluster'],
340
+ ];
341
+ for (const t of toolsData) insertTool.run(...t);
342
+
343
+ db.close();
344
+ console.log(`[seed-optimus] Database seeded: ${dbPath}`);
345
+ console.log(` attack_planes: ${planes.length}`);
346
+ console.log(` tapc_config: ${tapcConfig.length}`);
347
+ console.log(` agents: ${agentData.length}`);
348
+ console.log(` scan_candidates: ${candidates.length}`);
349
+ console.log(` findings: ${findingsData.length}`);
350
+ console.log(` fp_patterns: ${fpPatterns.length}`);
351
+ console.log(` scan_history: ${historyData.length}`);
352
+ console.log(` mcp_tools: ${toolsData.length}`);
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * sqlite-handler.js — Query a SQLite database via the local query.cjs helper.
5
+ *
6
+ * DB filename is resolved relative to the configured defaultDbDir.
7
+ * Supports SELECT (returns row array) and exec mode for INSERT/UPDATE/DELETE.
8
+ */
9
+
10
+ import path from 'node:path';
11
+ import { execFileSync } from 'node:child_process';
12
+ import fs from 'node:fs';
13
+ import { fileURLToPath } from 'node:url';
14
+
15
+ const HANDLER_DIR = path.dirname(fileURLToPath(import.meta.url));
16
+ const SQLITE_CONFIG_FILE = path.join(HANDLER_DIR, 'sqlite-config.json');
17
+ const SQLITE_QUERY_SCRIPT = path.join(HANDLER_DIR, 'query.cjs');
18
+
19
+ function interpolate(template, args) {
20
+ return String(template).replace(/\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g, (_m, key) => {
21
+ const v = args?.[key];
22
+ if (v === undefined) return '';
23
+ return typeof v === 'string' ? v : JSON.stringify(v);
24
+ });
25
+ }
26
+
27
+ function readJson(filePath) {
28
+ return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
29
+ }
30
+
31
+ function loadSqliteConfig() {
32
+ try {
33
+ return readJson(SQLITE_CONFIG_FILE);
34
+ } catch {
35
+ return {};
36
+ }
37
+ }
38
+
39
+ function resolveDbPath(dbRef, config) {
40
+ if (path.isAbsolute(dbRef) || dbRef.includes(path.sep) || dbRef.includes('/')) {
41
+ return path.resolve(dbRef);
42
+ }
43
+ const defaultDbDir = typeof config.defaultDbDir === 'string'
44
+ ? path.resolve(HANDLER_DIR, config.defaultDbDir)
45
+ : path.resolve(HANDLER_DIR, '.retain');
46
+ return path.join(defaultDbDir, dbRef);
47
+ }
48
+
49
+ export async function execute(context) {
50
+ const sourceDef = context?.sourceDef || {};
51
+ const handlerConfig = loadSqliteConfig();
52
+
53
+ const cfg = typeof sourceDef.sqlite === 'object' ? sourceDef.sqlite : {};
54
+ if (!cfg.db || !cfg.query) {
55
+ return { result: 'failure', data: { error: 'sqlite: db and query are required' }, error: 'missing db/query' };
56
+ }
57
+
58
+ const dbPath = resolveDbPath(cfg.db, handlerConfig);
59
+ const cliArgs = ['--db', dbPath, '--sql', cfg.query];
60
+ if (cfg.params) {
61
+ const resolvedParams = Array.isArray(cfg.params)
62
+ ? cfg.params.map(p => typeof p === 'string' ? interpolate(p, sourceDef._projections || {}) : p)
63
+ : [];
64
+ cliArgs.push('--params', JSON.stringify(resolvedParams));
65
+ }
66
+ if (cfg.mode === 'exec') {
67
+ cliArgs.push('--mode', 'exec');
68
+ }
69
+
70
+ try {
71
+ const raw = execFileSync(process.execPath, [SQLITE_QUERY_SCRIPT, ...cliArgs], {
72
+ encoding: 'utf-8',
73
+ maxBuffer: 10 * 1024 * 1024,
74
+ timeout: 30_000,
75
+ cwd: process.cwd(),
76
+ windowsHide: true,
77
+ });
78
+ const resultValue = raw.trim() ? JSON.parse(raw) : [];
79
+ return { result: 'success', data: { resultValue } };
80
+ } catch (err) {
81
+ const msg = err.stderr ? err.stderr.trim() : (err.message || String(err));
82
+ return { result: 'failure', data: { error: `sqlite query failed: ${msg}` }, error: msg };
83
+ }
84
+ }
@@ -1,17 +1,17 @@
1
1
  {
2
- "id": "demo-source-url",
2
+ "id": "demo-source-sqlite",
3
3
  "settings": {
4
4
  "start_step": "execute",
5
- "max_total_steps": 10,
6
- "timeout_ms": 60000
5
+ "max_total_steps": 5,
6
+ "timeout_ms": 30000
7
7
  },
8
8
  "steps": {
9
9
  "execute": {
10
- "description": "Execute single URL source def",
10
+ "description": "Execute SQLite query source def",
11
11
  "handler": {
12
12
  "type": "ref",
13
13
  "howToRun": "demo-local-module",
14
- "whatToRun": { "kind": "fs-path", "value": "./source-def-handlers/http-source-handler.js" }
14
+ "whatToRun": { "kind": "fs-path", "value": "./source-def-flows/sqlite-handler/sqlite-handler.js" }
15
15
  },
16
16
  "transitions": {
17
17
  "success": "completed",
@@ -21,12 +21,12 @@
21
21
  },
22
22
  "terminal_states": {
23
23
  "completed": {
24
- "description": "URL fetch succeeded",
24
+ "description": "SQLite query succeeded",
25
25
  "return_intent": "success",
26
26
  "return_artifacts": ["resultValue", "wroteOutputDirectly"]
27
27
  },
28
28
  "failed": {
29
- "description": "URL fetch failed",
29
+ "description": "SQLite query failed",
30
30
  "return_intent": "failure"
31
31
  }
32
32
  }
@@ -78,9 +78,9 @@ function resolveTickersArg(sourceDef, fetchArgs) {
78
78
  }
79
79
 
80
80
  async function executeUrl(sourceDef) {
81
- const cfg = sourceDef?.url;
81
+ const cfg = sourceDef?.urls;
82
82
  if (!cfg || typeof cfg !== 'object') {
83
- throw new Error('url source requires object config');
83
+ throw new Error('urls source requires object config');
84
84
  }
85
85
  const method = String(cfg.method || 'GET').toUpperCase();
86
86
  const headers = cfg.headers && typeof cfg.headers === 'object' ? cfg.headers : {};
@@ -89,7 +89,7 @@ async function executeUrl(sourceDef) {
89
89
 
90
90
  resolveTickersArg(sourceDef, fetchArgs);
91
91
  if (sourceDef?.tickersFrom && !fetchArgs.tickers) {
92
- throw new Error('url: tickersFrom resolved to empty list - skipping fetch');
92
+ throw new Error('urls: tickersFrom resolved to empty list - skipping fetch');
93
93
  }
94
94
 
95
95
  const ctx = {
@@ -98,42 +98,50 @@ async function executeUrl(sourceDef) {
98
98
  };
99
99
 
100
100
  if (typeof cfg.url !== 'string' || !cfg.url) {
101
- throw new Error('url source missing url template');
101
+ throw new Error('urls source missing url template');
102
102
  }
103
103
  const url = interpolate(cfg.url, ctx);
104
104
  return doFetchApi(url, method, headers, ttlMs);
105
105
  }
106
106
 
107
- async function executeUrlList(sourceDef) {
108
- const cfg = sourceDef?.['url-list'];
107
+ async function executeProjectedUrl(sourceDef, selectedUrl) {
108
+ const cfg = sourceDef?.urls;
109
109
  if (!cfg || typeof cfg !== 'object') {
110
- throw new Error('url-list source requires object config');
110
+ throw new Error('urls source requires object config');
111
111
  }
112
112
  const method = String(cfg.method || 'GET').toUpperCase();
113
113
  const headers = cfg.headers && typeof cfg.headers === 'object' ? cfg.headers : {};
114
114
  const ttlMs = typeof cfg.cacheTimeout === 'number' ? cfg.cacheTimeout * 1000 : DEFAULT_CACHE_TTL_MS;
115
+ const fetchArgs = cfg.args && typeof cfg.args === 'object' ? { ...cfg.args } : {};
115
116
 
116
- const urlList = sourceDef?._projections?.url_list;
117
- if (!Array.isArray(urlList) || urlList.length === 0) {
118
- throw new Error('url-list source requires _projections.url_list as non-empty array');
119
- }
117
+ resolveTickersArg(sourceDef, fetchArgs);
118
+
119
+ const ctx = {
120
+ ...(sourceDef?._projections || {}),
121
+ ...fetchArgs,
122
+ };
120
123
 
121
- const results = [];
122
- for (const u of urlList) {
123
- results.push(await doFetchApi(String(u), method, headers, ttlMs));
124
+ if (typeof selectedUrl !== 'string' || !selectedUrl) {
125
+ throw new Error('urls projected execution requires a concrete URL string');
124
126
  }
125
- return results;
127
+
128
+ const url = interpolate(selectedUrl, ctx);
129
+ return doFetchApi(url, method, headers, ttlMs);
126
130
  }
127
131
 
128
132
  export async function execute(context) {
129
- const kind = context?.kind;
130
- const sourceDef = context?.sourceDef || {};
133
+ const kind = context?.kind || context?.expects_data?.kind;
134
+ const sourceDef = context?.sourceDef || context?.expects_data?.sourceDef || {};
131
135
  try {
132
136
  let resultValue;
133
- if (kind === 'url') {
134
- resultValue = await executeUrl(sourceDef);
135
- } else if (kind === 'url-list') {
136
- resultValue = await executeUrlList(sourceDef);
137
+ if (kind === 'urls' || sourceDef?.urls) {
138
+ const projectedUrl =
139
+ typeof context?.url === 'string' ? context.url :
140
+ typeof context?.item === 'string' ? context.item :
141
+ typeof context?.expects_data?.url === 'string' ? context.expects_data.url :
142
+ typeof context?.expects_data?.item === 'string' ? context.expects_data.item :
143
+ undefined;
144
+ resultValue = projectedUrl ? await executeProjectedUrl(sourceDef, projectedUrl) : await executeUrl(sourceDef);
137
145
  } else {
138
146
  throw new Error(`http-source-handler does not support kind: ${kind}`);
139
147
  }
@@ -0,0 +1,73 @@
1
+ {
2
+ "id": "demo-source-urls",
3
+ "settings": {
4
+ "start_step": "prepare",
5
+ "max_total_steps": 10,
6
+ "timeout_ms": 60000
7
+ },
8
+ "steps": {
9
+ "prepare": {
10
+ "description": "Resolve URL targets",
11
+ "produces_data": ["target_urls", "fanout_mode", "sourceDef"],
12
+ "handler": {
13
+ "type": "compute-jsonata",
14
+ "expr": [
15
+ "data.fanout_mode = $exists(expects_data.sourceDef.urls.projectionList) and expects_data.sourceDef.urls.projectionList != ''",
16
+ "data.target_urls = data.fanout_mode ? $lookup(expects_data.sourceDef._projections, expects_data.sourceDef.urls.projectionList) : [expects_data.sourceDef.urls.url]",
17
+ "data.sourceDef = expects_data.sourceDef",
18
+ "result = $type(data.target_urls) = 'array' and $count(data.target_urls) > 0 ? 'success' : 'failure'"
19
+ ]
20
+ },
21
+ "transitions": {
22
+ "success": "execute",
23
+ "failure": "failed"
24
+ }
25
+ },
26
+ "execute": {
27
+ "description": "Fetch each resolved URL",
28
+ "expects_data": ["target_urls", "fanout_mode", "sourceDef"],
29
+ "produces_data": ["fetched_values"],
30
+ "forEach": {
31
+ "items": "target_urls",
32
+ "as": "url",
33
+ "concurrency": 4,
34
+ "collectAs": "fetched_values"
35
+ },
36
+ "handler": {
37
+ "type": "ref",
38
+ "howToRun": "demo-local-module",
39
+ "whatToRun": { "kind": "fs-path", "value": "./source-def-flows/url-handler/http-source-handler.js" }
40
+ },
41
+ "transitions": {
42
+ "success": "finalize",
43
+ "failure": "failed"
44
+ }
45
+ },
46
+ "finalize": {
47
+ "description": "Normalize flow output",
48
+ "expects_data": ["fetched_values", "fanout_mode"],
49
+ "handler": {
50
+ "type": "compute-jsonata",
51
+ "expr": [
52
+ "data.resultValue = expects_data.fanout_mode ? expects_data.fetched_values : expects_data.fetched_values[0]",
53
+ "result = 'success'"
54
+ ]
55
+ },
56
+ "transitions": {
57
+ "success": "completed",
58
+ "failure": "failed"
59
+ }
60
+ }
61
+ },
62
+ "terminal_states": {
63
+ "completed": {
64
+ "description": "URL fetch succeeded",
65
+ "return_intent": "success",
66
+ "return_artifacts": ["resultValue", "wroteOutputDirectly"]
67
+ },
68
+ "failed": {
69
+ "description": "URL fetch failed",
70
+ "return_intent": "failure"
71
+ }
72
+ }
73
+ }