omniwire 3.1.4 → 3.2.0

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.
@@ -1,5 +1,5 @@
1
1
  {
2
- "lastCheck": 1774756238937,
2
+ "lastCheck": 1774767791264,
3
3
  "lastVersion": "3.0.1",
4
4
  "autoUpdateEnabled": true,
5
5
  "source": "auto",
package/README.md CHANGED
@@ -19,7 +19,7 @@
19
19
 
20
20
  **The infrastructure layer for AI agent swarms.**
21
21
 
22
- 81 MCP tools · A2A protocol · OmniMesh VPN · nftables firewall · CDP browser · cookie sync · CyberBase persistence
22
+ 82 MCP tools · A2A protocol · OmniMesh VPN · nftables firewall · CDP browser · cookie sync · 2FA TOTP · CyberBase persistence
23
23
 
24
24
  </div>
25
25
 
@@ -683,6 +683,25 @@ omniwire/
683
683
 
684
684
  ---
685
685
 
686
+ ## Changelog
687
+
688
+ | Version | Date | Changes |
689
+ |---------|------|---------|
690
+ | **v3.2.0** | 2026-03-29 | New: `omniwire_2fa` TOTP manager — add/generate/verify/import/export 2FA codes, CyberBase + 1Password persistence, otpauth:// URI import, bulk code generation |
691
+ | **v3.1.5** | 2026-03-29 | Fix: skip auto-audit batch entries from Obsidian vault + Canvas sync to prevent junk files |
692
+ | **v3.1.4** | 2026-03-29 | Auto-sync CyberBase writes to Obsidian vault + Canvas mindmap, collision-avoidance grid placement, `sync-obsidian` / `sync-canvas` actions in knowledge tool |
693
+ | **v3.1.3** | 2026-03-29 | OmniMesh WireGuard mesh manager, event bus (Webhook/WS/SSE), knowledge tool (12 actions), auto-update system, CDP rewrite (persistent Docker container, 18 actions), mesh expose/gateway, CyberBase circuit breaker + SQL hardening |
694
+ | **v3.1.2** | 2026-03-28 | Collapsible tool sections in README, npm README sync |
695
+ | **v3.1.1** | 2026-03-28 | Bug fixes, improved error handling in CDP tool |
696
+ | **v3.1.0** | 2026-03-27 | OmniMesh VPN, 81 MCP tools, A2A protocol, event system, background dispatch |
697
+ | **v3.0.0** | 2026-03-25 | Major rewrite: CyberSync, pipeline DAGs, blackboard, task queues, LZ4 transfers, AES-128-GCM encryption |
698
+ | **v2.6.1** | 2026-03-20 | VPN routing (Mullvad/OpenVPN/WG/Tailscale), multi-hop, DAITA, quantum tunnels |
699
+ | **v2.5.0** | 2026-03-15 | Firewall management (nftables), cert management, deploy tool |
700
+ | **v2.0.0** | 2026-03-10 | CDP browser automation, cookie sync, 1Password integration |
701
+ | **v1.0.0** | 2026-03-01 | Initial release — SSH exec, file transfer, node management |
702
+
703
+ ---
704
+
686
705
  <br/>
687
706
 
688
707
  <p align="center">
@@ -204,7 +204,9 @@ function sqlEscape(val) {
204
204
  /** Fire-and-forget write to CyberBase + Obsidian vault + Canvas. Never blocks, never throws. */
205
205
  function cb(category, key, value) {
206
206
  // Sync to Obsidian vault + Canvas mindmap (local, synchronous, best-effort)
207
- syncVault(category, key, value);
207
+ // Skip auto-audit batch entries — they pollute vault/canvas with junk
208
+ if (!key.startsWith('batch-'))
209
+ syncVault(category, key, value);
208
210
  // Sync to CyberBase PostgreSQL (remote, async, queued)
209
211
  if (!cbManager || cbCircuitOpen())
210
212
  return;
@@ -3658,7 +3660,237 @@ echo "port-knock configured: ${ports.join(' -> ')} -> port ${target}"`;
3658
3660
  }
3659
3661
  return fail('invalid action');
3660
3662
  });
3661
- // --- Tool 53: omniwire_knowledge ---
3663
+ // --- Tool 53: omniwire_2fa ---
3664
+ // TOTP 2FA manager — stores secrets in CyberBase + 1Password, generates codes on demand
3665
+ const twoFaStore = new Map();
3666
+ function base32Decode(encoded) {
3667
+ const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
3668
+ const stripped = encoded.replace(/[\s=-]/g, '').toUpperCase();
3669
+ let bits = '';
3670
+ for (const c of stripped) {
3671
+ const idx = alphabet.indexOf(c);
3672
+ if (idx === -1)
3673
+ continue;
3674
+ bits += idx.toString(2).padStart(5, '0');
3675
+ }
3676
+ const bytes = [];
3677
+ for (let i = 0; i + 8 <= bits.length; i += 8)
3678
+ bytes.push(parseInt(bits.slice(i, i + 8), 2));
3679
+ return Buffer.from(bytes);
3680
+ }
3681
+ function generateTOTP(secret, options) {
3682
+ const period = options?.period ?? 30;
3683
+ const digits = options?.digits ?? 6;
3684
+ const algo = options?.algorithm ?? 'sha1';
3685
+ const now = options?.time ?? Math.floor(Date.now() / 1000);
3686
+ const counter = Math.floor(now / period);
3687
+ const counterBuf = Buffer.alloc(8);
3688
+ counterBuf.writeUInt32BE(Math.floor(counter / 0x100000000), 0);
3689
+ counterBuf.writeUInt32BE(counter & 0xFFFFFFFF, 4);
3690
+ const keyBuf = base32Decode(secret);
3691
+ const hmac = require('crypto').createHmac(algo, keyBuf).update(counterBuf).digest();
3692
+ const offset = hmac[hmac.length - 1] & 0x0f;
3693
+ const code = ((hmac[offset] & 0x7f) << 24 | hmac[offset + 1] << 16 | hmac[offset + 2] << 8 | hmac[offset + 3]) % (10 ** digits);
3694
+ return code.toString().padStart(digits, '0');
3695
+ }
3696
+ server.tool('omniwire_2fa', 'TOTP 2FA manager — add/generate/list/delete/verify/export 2FA secrets. Stores encrypted in CyberBase + 1Password. Generate codes for any stored service instantly.', {
3697
+ action: z.enum(['add', 'generate', 'list', 'delete', 'verify', 'export', 'import', 'bulk-generate']).describe('Action: add=store secret, generate=get current code, list=show all, delete=remove, verify=check code, export=dump all, import=parse otpauth URI, bulk-generate=codes for all'),
3698
+ name: z.string().optional().describe('Service name (e.g., "github", "discord", "aws")'),
3699
+ secret: z.string().optional().describe('Base32-encoded TOTP secret (for add) or otpauth:// URI (for import)'),
3700
+ issuer: z.string().optional().describe('Issuer name (e.g., "GitHub", "Discord")'),
3701
+ code: z.string().optional().describe('6-digit code to verify (for verify action)'),
3702
+ algorithm: z.enum(['sha1', 'sha256', 'sha512']).optional().describe('HMAC algorithm (default: sha1)'),
3703
+ digits: z.number().optional().describe('Code length (default: 6)'),
3704
+ period: z.number().optional().describe('Time step in seconds (default: 30)'),
3705
+ format: z.enum(['json', 'uri', 'table']).optional().describe('Export format (default: table)'),
3706
+ node: z.string().optional().describe('Node for 1Password sync'),
3707
+ background: z.boolean().optional().describe('Run in background'),
3708
+ }, async (args) => {
3709
+ const { action, name, secret, issuer, code: verifyCode, algorithm, digits, period, format, node: targetNode } = args;
3710
+ const nodeId = targetNode ?? 'contabo';
3711
+ // Load from CyberBase on first access if memory is empty
3712
+ if (twoFaStore.size === 0 && cbManager) {
3713
+ try {
3714
+ const r = await cbManager.exec('contabo', pgExec(`SELECT key, value FROM knowledge WHERE source_tool = 'omniwire' AND key LIKE '2fa:%' LIMIT 100`));
3715
+ if (r.code === 0 && r.stdout) {
3716
+ for (const line of r.stdout.split('\n')) {
3717
+ const match = line.match(/^\s*2fa:(\S+)\s*\|\s*(.+)/);
3718
+ if (match) {
3719
+ try {
3720
+ const parsed = JSON.parse(match[2].trim());
3721
+ const data = parsed.data ? JSON.parse(parsed.data) : parsed;
3722
+ twoFaStore.set(match[1], data);
3723
+ }
3724
+ catch { /* skip malformed */ }
3725
+ }
3726
+ }
3727
+ }
3728
+ }
3729
+ catch { /* CyberBase offline, continue with memory */ }
3730
+ }
3731
+ switch (action) {
3732
+ case 'add': {
3733
+ if (!name)
3734
+ return fail('name required');
3735
+ if (!secret)
3736
+ return fail('secret required');
3737
+ const entry = {
3738
+ secret: secret.replace(/\s/g, '').toUpperCase(),
3739
+ issuer: issuer ?? name,
3740
+ algorithm: algorithm ?? 'sha1',
3741
+ digits: digits ?? 6,
3742
+ period: period ?? 30,
3743
+ addedAt: new Date().toISOString(),
3744
+ };
3745
+ twoFaStore.set(name, entry);
3746
+ // Persist to CyberBase (secret stored as JSON, no raw secret in key)
3747
+ cb('2fa', name, JSON.stringify(entry));
3748
+ // Sync to 1Password
3749
+ if (cbManager) {
3750
+ const opCmd = `op item get "OmniWire 2FA - ${name}" --vault "CyberBase" >/dev/null 2>&1 && op item edit "OmniWire 2FA - ${name}" --vault "CyberBase" "notesPlain=${entry.secret}" 2>/dev/null || op item create --category=SecureNote --vault="CyberBase" --title="OmniWire 2FA - ${name}" "notesPlain=${entry.secret}" 2>/dev/null`;
3751
+ cbManager.exec(nodeId, opCmd).catch(() => { });
3752
+ }
3753
+ const currentCode = generateTOTP(entry.secret, { period: entry.period, digits: entry.digits, algorithm: entry.algorithm });
3754
+ return okBrief(`2FA added: ${name} (${entry.issuer})\nCurrent code: ${currentCode}\nStored in: memory + CyberBase + 1Password`);
3755
+ }
3756
+ case 'generate': {
3757
+ if (!name) {
3758
+ // Generate for all
3759
+ if (twoFaStore.size === 0)
3760
+ return fail('no 2FA secrets stored — use add first');
3761
+ const lines = ['Service | Code | Expires in'];
3762
+ const now = Math.floor(Date.now() / 1000);
3763
+ for (const [n, e] of twoFaStore) {
3764
+ const c = generateTOTP(e.secret, { period: e.period, digits: e.digits, algorithm: e.algorithm });
3765
+ const remaining = e.period - (now % e.period);
3766
+ lines.push(`${n} | ${c} | ${remaining}s`);
3767
+ }
3768
+ return okBrief(lines.join('\n'));
3769
+ }
3770
+ const entry = twoFaStore.get(name);
3771
+ if (!entry)
3772
+ return fail(`${name} not found — use list to see available`);
3773
+ const now = Math.floor(Date.now() / 1000);
3774
+ const currentCode = generateTOTP(entry.secret, { period: entry.period, digits: entry.digits, algorithm: entry.algorithm });
3775
+ const remaining = entry.period - (now % entry.period);
3776
+ return okBrief(`${name}: ${currentCode} (expires in ${remaining}s)`);
3777
+ }
3778
+ case 'bulk-generate': {
3779
+ if (twoFaStore.size === 0)
3780
+ return fail('no 2FA secrets stored');
3781
+ const lines = ['Service | Code | Expires'];
3782
+ const now = Math.floor(Date.now() / 1000);
3783
+ for (const [n, e] of twoFaStore) {
3784
+ const c = generateTOTP(e.secret, { period: e.period, digits: e.digits, algorithm: e.algorithm });
3785
+ const remaining = e.period - (now % e.period);
3786
+ lines.push(`${n} | ${c} | ${remaining}s`);
3787
+ }
3788
+ return okBrief(lines.join('\n'));
3789
+ }
3790
+ case 'list': {
3791
+ if (twoFaStore.size === 0)
3792
+ return okBrief('No 2FA secrets stored');
3793
+ const lines = ['Service | Issuer | Algorithm | Digits | Period | Added'];
3794
+ for (const [n, e] of twoFaStore) {
3795
+ lines.push(`${n} | ${e.issuer} | ${e.algorithm} | ${e.digits} | ${e.period}s | ${e.addedAt.split('T')[0]}`);
3796
+ }
3797
+ return okBrief(lines.join('\n'));
3798
+ }
3799
+ case 'delete': {
3800
+ if (!name)
3801
+ return fail('name required');
3802
+ if (!twoFaStore.has(name))
3803
+ return fail(`${name} not found`);
3804
+ twoFaStore.delete(name);
3805
+ // Remove from CyberBase
3806
+ if (cbManager) {
3807
+ const sql = `DELETE FROM knowledge WHERE source_tool = 'omniwire' AND key = '2fa:${sqlEscape(name)}'`;
3808
+ CB_QUEUE.push(sql);
3809
+ if (!cbDraining)
3810
+ drainCb();
3811
+ // Remove from 1Password
3812
+ cbManager.exec(nodeId, `op item delete "OmniWire 2FA - ${name}" --vault "CyberBase" 2>/dev/null`).catch(() => { });
3813
+ }
3814
+ return okBrief(`2FA deleted: ${name}`);
3815
+ }
3816
+ case 'verify': {
3817
+ if (!name)
3818
+ return fail('name required');
3819
+ if (!verifyCode)
3820
+ return fail('code required');
3821
+ const entry = twoFaStore.get(name);
3822
+ if (!entry)
3823
+ return fail(`${name} not found`);
3824
+ const now = Math.floor(Date.now() / 1000);
3825
+ // Check current window ± 1 step
3826
+ for (const offset of [0, -1, 1]) {
3827
+ const expected = generateTOTP(entry.secret, {
3828
+ period: entry.period, digits: entry.digits, algorithm: entry.algorithm,
3829
+ time: now + (offset * entry.period)
3830
+ });
3831
+ if (verifyCode === expected)
3832
+ return okBrief(`VALID — ${name} code matches (window: ${offset === 0 ? 'current' : offset < 0 ? 'previous' : 'next'})`);
3833
+ }
3834
+ return fail(`INVALID — ${name} code does not match`);
3835
+ }
3836
+ case 'import': {
3837
+ if (!secret)
3838
+ return fail('otpauth:// URI required');
3839
+ // Parse otpauth://totp/Issuer:Account?secret=XXX&issuer=YYY&algorithm=SHA1&digits=6&period=30
3840
+ const url = new URL(secret);
3841
+ if (url.protocol !== 'otpauth:')
3842
+ return fail('invalid URI — must start with otpauth://');
3843
+ const label = decodeURIComponent(url.pathname.replace(/^\/\/totp\//, ''));
3844
+ const parsedSecret = url.searchParams.get('secret');
3845
+ if (!parsedSecret)
3846
+ return fail('no secret in URI');
3847
+ const parsedIssuer = url.searchParams.get('issuer') ?? label.split(':')[0] ?? 'Unknown';
3848
+ const parsedAlgo = (url.searchParams.get('algorithm') ?? 'SHA1').toLowerCase();
3849
+ const parsedDigits = parseInt(url.searchParams.get('digits') ?? '6');
3850
+ const parsedPeriod = parseInt(url.searchParams.get('period') ?? '30');
3851
+ const entryName = name ?? label.replace(/[^a-zA-Z0-9_-]/g, '_').toLowerCase();
3852
+ const entry = {
3853
+ secret: parsedSecret.toUpperCase(),
3854
+ issuer: parsedIssuer,
3855
+ algorithm: parsedAlgo,
3856
+ digits: parsedDigits,
3857
+ period: parsedPeriod,
3858
+ addedAt: new Date().toISOString(),
3859
+ };
3860
+ twoFaStore.set(entryName, entry);
3861
+ cb('2fa', entryName, JSON.stringify(entry));
3862
+ const currentCode = generateTOTP(entry.secret, { period: entry.period, digits: entry.digits, algorithm: entry.algorithm });
3863
+ return okBrief(`Imported: ${entryName} (${parsedIssuer})\nCurrent code: ${currentCode}`);
3864
+ }
3865
+ case 'export': {
3866
+ if (twoFaStore.size === 0)
3867
+ return fail('no 2FA secrets stored');
3868
+ const fmt = format ?? 'table';
3869
+ if (fmt === 'json') {
3870
+ const data = {};
3871
+ for (const [n, e] of twoFaStore)
3872
+ data[n] = { ...e };
3873
+ return okBrief(JSON.stringify(data, null, 2));
3874
+ }
3875
+ if (fmt === 'uri') {
3876
+ const lines = [];
3877
+ for (const [n, e] of twoFaStore) {
3878
+ lines.push(`otpauth://totp/${encodeURIComponent(e.issuer)}:${encodeURIComponent(n)}?secret=${e.secret}&issuer=${encodeURIComponent(e.issuer)}&algorithm=${e.algorithm.toUpperCase()}&digits=${e.digits}&period=${e.period}`);
3879
+ }
3880
+ return okBrief(lines.join('\n'));
3881
+ }
3882
+ // table
3883
+ const lines = ['Service | Issuer | Secret (last 4) | Algorithm'];
3884
+ for (const [n, e] of twoFaStore) {
3885
+ lines.push(`${n} | ${e.issuer} | ...${e.secret.slice(-4)} | ${e.algorithm}`);
3886
+ }
3887
+ return okBrief(lines.join('\n'));
3888
+ }
3889
+ default:
3890
+ return fail('invalid action');
3891
+ }
3892
+ });
3893
+ // --- Tool 54: omniwire_knowledge ---
3662
3894
  server.tool('omniwire_knowledge', 'CyberBase knowledge base — CRUD, search, and health management for the unified PostgreSQL knowledge store. Auto-syncs all writes to Obsidian vault + Canvas mindmap. Supports text search, semantic/vector search, categories, bulk operations, and explicit sync-obsidian/sync-canvas actions.', {
3663
3895
  action: z.enum(['get', 'set', 'delete', 'search', 'semantic-search', 'list', 'stats', 'health', 'categories', 'bulk-set', 'export', 'vacuum', 'sync-obsidian', 'sync-canvas']).describe('Action'),
3664
3896
  category: z.string().optional().describe('Knowledge category (e.g., tools, vulns, infra, notes)'),