total-recall-brain 3.40.1 → 3.40.2

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 (186) hide show
  1. package/README.md +152 -41
  2. package/bin/total-recall.mjs +16 -76
  3. package/docs/reference/cli-reference.md +10 -10
  4. package/extension/background.js +6 -2
  5. package/extension/config.js +8 -0
  6. package/extension/lib/brain-client.js +29 -4
  7. package/extension/options/options.html +1 -0
  8. package/extension/sidepanel/sidepanel.html +1 -0
  9. package/frontend/dist/assets/{index-CvQRdT2j.css → index-AGUes-VG.css} +1 -1
  10. package/frontend/dist/assets/index-Bt5iVPZj.js +5618 -0
  11. package/frontend/dist/index.html +2 -2
  12. package/frontend/src/api/keys.ts +9 -2
  13. package/frontend/src/api/plugins.ts +4 -0
  14. package/frontend/src/components/plugins/card/CardEngine.tsx +2 -1
  15. package/frontend/src/components/plugins/card/card.css +1 -1
  16. package/frontend/src/pages/IntegrationsPage.tsx +315 -8
  17. package/frontend/src/pages/PluginsPage.tsx +532 -87
  18. package/package.json +6 -5
  19. package/scaffold/.agent/skills/meta-harness/references/optimized/original.md +1 -1
  20. package/scaffold/.agent/skills/meta-harness/references/optimized/section-09.md +1 -1
  21. package/scaffold/.agent/skills/total-recall/plugins/auth/package-lock.json +10 -10
  22. package/scaffold/.agent/skills/total-recall/plugins/auth/package.json +1 -6
  23. package/scaffold/.agent/skills/total-recall/plugins/auth/plugin.json +0 -1
  24. package/scaffold/.agent/skills/total-recall/plugins/auth/src/browser-session.mjs +309 -4
  25. package/scaffold/.agent/skills/total-recall/plugins/auth/src/cli-secret.mjs +44 -34
  26. package/scaffold/.agent/skills/total-recall/plugins/auth/src/google-oauth.mjs +6 -4
  27. package/scaffold/.agent/skills/total-recall/plugins/auth/src/provider-catalog.mjs +1 -1
  28. package/scaffold/.agent/skills/total-recall/plugins/auth/src/provider-rotation-recipes.mjs +118 -4
  29. package/scaffold/.agent/skills/total-recall/plugins/auth/src/routes/auth.mjs +2 -2
  30. package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-env-export.mjs +3 -3
  31. package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-remote-deploy.mjs +1 -1
  32. package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-rotate.mjs +24 -6
  33. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/card.js +164 -123
  34. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/README.md +65 -0
  35. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/cli.mjs +76 -0
  36. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/package.json +19 -0
  37. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/plugin.json +290 -0
  38. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/adapters/dsh.mjs +446 -0
  39. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/documentation.mjs +152 -0
  40. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/executor.mjs +71 -0
  41. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/runtime.mjs +36 -0
  42. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/setup.mjs +35 -0
  43. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/worker.mjs +175 -0
  44. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/ui/card.js +18 -0
  45. package/scaffold/.agent/skills/total-recall/plugins/code-quality/LICENSE +21 -0
  46. package/scaffold/.agent/skills/total-recall/plugins/code-quality/README.md +84 -0
  47. package/scaffold/.agent/skills/total-recall/plugins/code-quality/cli.mjs +79 -0
  48. package/scaffold/.agent/skills/total-recall/plugins/code-quality/commands/check.mjs +6 -0
  49. package/scaffold/.agent/skills/total-recall/plugins/code-quality/commands/report.mjs +6 -0
  50. package/scaffold/.agent/skills/total-recall/plugins/code-quality/commands/run-core.mjs +27 -0
  51. package/scaffold/.agent/skills/total-recall/plugins/code-quality/commands/snapshot.mjs +163 -0
  52. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/check.mjs +757 -0
  53. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/config.schema.json +86 -0
  54. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/detect.mjs +89 -0
  55. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/report-data.mjs +145 -0
  56. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/report.mjs +210 -0
  57. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/state.mjs +20 -0
  58. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +2869 -0
  59. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package.json +28 -0
  60. package/scaffold/.agent/skills/total-recall/plugins/code-quality/plugin.json +186 -0
  61. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/DESIGN.md +18 -0
  62. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/card.js +272 -0
  63. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/code-quality-report.mjs +129 -0
  64. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/LICENSE +21 -0
  65. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/README.md +73 -0
  66. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/cli.mjs +375 -0
  67. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/examples/commands/echo-tool.mjs +23 -0
  68. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/examples/commands/group.mjs +262 -0
  69. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/examples/commands/project-id.mjs +74 -0
  70. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +2869 -0
  71. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package.json +31 -0
  72. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/plugin.json +221 -0
  73. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/schemas/cli_command.schema.json +107 -0
  74. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/skills/composable-cli/SKILL.md +63 -0
  75. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/command.mjs +313 -0
  76. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/compile.mjs +19 -0
  77. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/compose.mjs +144 -0
  78. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/contract.mjs +110 -0
  79. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/library.mjs +23 -0
  80. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/registry.mjs +192 -0
  81. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/resolve.mjs +221 -0
  82. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/risk.mjs +126 -0
  83. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/ssss-documents.mjs +29 -0
  84. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/DESIGN.md +18 -0
  85. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/card.js +273 -0
  86. package/scaffold/.agent/skills/total-recall/plugins/embeddings/LICENSE +21 -0
  87. package/scaffold/.agent/skills/total-recall/plugins/embeddings/README.md +20 -0
  88. package/scaffold/.agent/skills/total-recall/plugins/embeddings/cli.mjs +310 -0
  89. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +2826 -0
  90. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package.json +11 -0
  91. package/scaffold/.agent/skills/total-recall/plugins/embeddings/plugin.json +62 -0
  92. package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/card.js +297 -0
  93. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/LICENSE +21 -0
  94. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/README.md +43 -0
  95. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/cli.mjs +258 -0
  96. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package-lock.json +4005 -0
  97. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package.json +20 -0
  98. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/plugin.json +125 -0
  99. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/ui/DESIGN.md +18 -0
  100. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/ui/card.js +278 -0
  101. package/scaffold/.agent/skills/total-recall/plugins/mesh/README.md +9 -0
  102. package/scaffold/.agent/skills/total-recall/plugins/mesh/cli.mjs +6 -0
  103. package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +4721 -0
  104. package/scaffold/.agent/skills/total-recall/plugins/mesh/package.json +3 -21
  105. package/scaffold/.agent/skills/total-recall/plugins/mesh/plugin.json +14 -1
  106. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/cli/mesh.mjs +10 -0
  107. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/headscale-client.mjs +5 -1
  108. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/leader-election.mjs +4 -13
  109. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/mesh-access-verify.mjs +22 -0
  110. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/mesh-files.mjs +18 -0
  111. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/mesh.mjs +2 -0
  112. package/scaffold/.agent/skills/total-recall/plugins/mesh/test-support/memory-root.mjs +14 -0
  113. package/scaffold/.agent/skills/total-recall/plugins/openwiki/LICENSE +21 -0
  114. package/scaffold/.agent/skills/total-recall/plugins/openwiki/README.md +35 -0
  115. package/scaffold/.agent/skills/total-recall/plugins/openwiki/cli.mjs +124 -0
  116. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +4097 -0
  117. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +12 -0
  118. package/scaffold/.agent/skills/total-recall/plugins/openwiki/plugin.json +165 -0
  119. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/ingest.mjs +127 -0
  120. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/DESIGN.md +18 -0
  121. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/card.js +279 -0
  122. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/panel.js +539 -0
  123. package/scaffold/.agent/skills/total-recall/plugins/security/LICENSE +21 -0
  124. package/scaffold/.agent/skills/total-recall/plugins/security/README.md +67 -0
  125. package/scaffold/.agent/skills/total-recall/plugins/security/cli.mjs +932 -0
  126. package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +3256 -0
  127. package/scaffold/.agent/skills/total-recall/plugins/security/package.json +25 -0
  128. package/scaffold/.agent/skills/total-recall/plugins/security/plugin.json +297 -0
  129. package/scaffold/.agent/skills/total-recall/plugins/security/scripts/build-ui.mjs +4 -0
  130. package/scaffold/.agent/skills/total-recall/plugins/security/skills/security/SKILL.md +37 -0
  131. package/scaffold/.agent/skills/total-recall/plugins/security/src/android.mjs +21 -0
  132. package/scaffold/.agent/skills/total-recall/plugins/security/src/devices.mjs +94 -0
  133. package/scaffold/.agent/skills/total-recall/plugins/security/src/machines.mjs +164 -0
  134. package/scaffold/.agent/skills/total-recall/plugins/security/src/scanner.mjs +41 -0
  135. package/scaffold/.agent/skills/total-recall/plugins/security/src/windows.mjs +42 -0
  136. package/scaffold/.agent/skills/total-recall/plugins/security/ui/DESIGN.md +18 -0
  137. package/scaffold/.agent/skills/total-recall/plugins/security/ui/card.js +95 -0
  138. package/scaffold/.agent/skills/total-recall/plugins/security/ui/panel.js +98 -0
  139. package/scaffold/.agent/skills/total-recall/references/auth.md +1 -1
  140. package/src/cli/brief.mjs +33 -9
  141. package/src/cli/command.mjs +2 -7
  142. package/src/cli/config.mjs +1 -0
  143. package/src/cli/connect.mjs +5 -0
  144. package/src/cli/context.mjs +2 -1
  145. package/src/cli/daemon.mjs +36 -1
  146. package/src/cli/doctor.mjs +6 -0
  147. package/src/cli/harness.mjs +3 -0
  148. package/src/cli/help.mjs +26 -0
  149. package/src/cli/init.mjs +4 -4
  150. package/src/cli/memory-entry.mjs +1 -0
  151. package/src/cli/memory-init.mjs +2 -0
  152. package/src/cli/plugin/index.mjs +11 -0
  153. package/src/cli/plugin/install.mjs +2 -1
  154. package/src/cli/rebuild.mjs +3 -1
  155. package/src/cli/startup.mjs +2 -1
  156. package/src/cli/update.mjs +19 -0
  157. package/src/core/automatic-brain-updates.mjs +67 -0
  158. package/src/core/brain-updates.mjs +21 -2
  159. package/src/core/cli-commands.mjs +71 -0
  160. package/src/core/cli-maintenance.mjs +45 -0
  161. package/src/core/cli-quick-reference.mjs +20 -0
  162. package/src/core/cli-semantic-index.mjs +722 -0
  163. package/src/core/context-compiler.mjs +27 -1
  164. package/src/core/crons.mjs +2 -13
  165. package/src/core/daemon-control.mjs +6 -4
  166. package/src/core/daemon-loop.mjs +12 -8
  167. package/src/core/default-plugins.mjs +5 -0
  168. package/src/core/global-brain-updates.mjs +39 -0
  169. package/src/core/mesh-brain-updates.mjs +30 -0
  170. package/src/core/meta-harness.mjs +108 -0
  171. package/src/core/plugin-dependencies.mjs +2 -1
  172. package/src/core/plugin-loader.mjs +2 -1
  173. package/src/core/plugin-runner.mjs +12 -4
  174. package/src/core/plugin-setup.mjs +20 -0
  175. package/src/core/plugin-store.mjs +22 -4
  176. package/src/core/plugin-tasks.mjs +1 -1
  177. package/src/core/project-brain.mjs +2 -0
  178. package/src/core/project-status.mjs +64 -0
  179. package/src/core/required-plugins.mjs +46 -0
  180. package/src/core/scaffold-plugins.mjs +17 -3
  181. package/src/core/source-brain-updates.mjs +47 -0
  182. package/src/core/startup-health.mjs +5 -3
  183. package/src/core/surface.mjs +44 -10
  184. package/src/server/routes/extension.mjs +53 -8
  185. package/templates/onboarding-interview.md +1 -1
  186. package/frontend/dist/assets/index-SOVUlZqm.js +0 -5596
@@ -0,0 +1,932 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Total Recall Standalone Security Plugin CLI
4
+ * Complete Website & System Defensive Suite:
5
+ * - Proof-of-Work Challenges (PoW) & Bot Mitigation
6
+ * - Content Security Policy (CSP) & HTTP Security Headers
7
+ * - Website Security Audit & Scorecard (A+ to F)
8
+ * - Anti-CSRF & Sliding Window Rate Limiting
9
+ * - Plugin Fleet Hardening & File Permissions (0600)
10
+ * - Static Analysis (SAST) & AES-256-GCM Envelope Encryption
11
+ */
12
+ import crypto from "node:crypto";
13
+ import fs from "node:fs";
14
+ import path from "node:path";
15
+ import os from "node:os";
16
+ import { auditMachine, auditFleet, hardenSshPermissions, loadOwner, runCheck } from "./src/machines.mjs";
17
+ import { assessDevice, platformInventory } from './src/devices.mjs';
18
+ import { auditAndroid } from './src/android.mjs';
19
+ import { scanSource } from "./src/scanner.mjs";
20
+
21
+ const TOKEN_KEY = "SECURITY_MASTER_KEY";
22
+ const CHALLENGE_KEY = "CHALLENGE_SECRET_KEY";
23
+
24
+ async function resolveSecret(key) {
25
+ const owner = await loadOwner('auth', 'secrets-store');
26
+ const result = await owner.getSecret(process.env.TR_SECRETS_BRAIN || owner.defaultBrainDir(), key, { actor: 'security-plugin', action: 'use' });
27
+ return result.found ? result.value : null;
28
+ }
29
+ async function requiredSecret(key) {
30
+ const value = await resolveSecret(key);
31
+ if (typeof value !== 'string' || Buffer.byteLength(value) < 32) throw new Error(`${key} requires at least 32 bytes in the auth plugin`);
32
+ return value;
33
+ }
34
+ function equalSignature(left, right) {
35
+ if (typeof left !== 'string' || !/^[a-f0-9]{64}$/.test(left)) return false;
36
+ return crypto.timingSafeEqual(Buffer.from(left, 'hex'), Buffer.from(right, 'hex'));
37
+ }
38
+
39
+ async function readStdin(limit = 1048576) {
40
+ const chunks = [];
41
+ let bytes = 0;
42
+ for await (const chunk of process.stdin) { bytes += chunk.length; if (bytes > limit) throw new Error('Input exceeds limit'); chunks.push(chunk); }
43
+ return Buffer.concat(chunks).toString("utf8").trim();
44
+ }
45
+
46
+ // ─────────────────────────────────────────────────────────────────────────────
47
+ // 1. CRYPTO & ENVELOPE ENCRYPTION (AES-256-GCM)
48
+ // ─────────────────────────────────────────────────────────────────────────────
49
+
50
+ function encryptText(text, passphrase) {
51
+ const salt = crypto.randomBytes(16);
52
+ const iv = crypto.randomBytes(12);
53
+ const key = crypto.scryptSync(passphrase, salt, 32);
54
+ const cipher = crypto.createCipheriv("aes-256-gcm", key, iv);
55
+ const encrypted = Buffer.concat([cipher.update(text, "utf8"), cipher.final()]);
56
+ const tag = cipher.getAuthTag();
57
+ return {
58
+ cipher: "aes-256-gcm",
59
+ kdf: "scrypt",
60
+ salt: salt.toString("hex"),
61
+ iv: iv.toString("hex"),
62
+ tag: tag.toString("hex"),
63
+ ciphertext: encrypted.toString("hex"),
64
+ encoded: `trsec:v1:${salt.toString("hex")}:${iv.toString("hex")}:${tag.toString("hex")}:${encrypted.toString("hex")}`
65
+ };
66
+ }
67
+
68
+ function decryptText(payload, passphrase) {
69
+ let saltHex, ivHex, tagHex, dataHex;
70
+ if (typeof payload === "string" && payload.startsWith("trsec:v1:")) {
71
+ const parts = payload.split(":");
72
+ saltHex = parts[2];
73
+ ivHex = parts[3];
74
+ tagHex = parts[4];
75
+ dataHex = parts[5];
76
+ } else if (typeof payload === "object") {
77
+ saltHex = payload.salt;
78
+ ivHex = payload.iv;
79
+ tagHex = payload.tag;
80
+ dataHex = payload.ciphertext;
81
+ } else {
82
+ throw new Error("Invalid encrypted payload format");
83
+ }
84
+
85
+ const salt = Buffer.from(saltHex, "hex");
86
+ const iv = Buffer.from(ivHex, "hex");
87
+ const tag = Buffer.from(tagHex, "hex");
88
+ const encrypted = Buffer.from(dataHex, "hex");
89
+
90
+ const key = crypto.scryptSync(passphrase, salt, 32);
91
+ const decipher = crypto.createDecipheriv("aes-256-gcm", key, iv);
92
+ decipher.setAuthTag(tag);
93
+ const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);
94
+ return decrypted.toString("utf8");
95
+ }
96
+
97
+ // ─────────────────────────────────────────────────────────────────────────────
98
+ // 2. PROOF-OF-WORK (PoW) CHALLENGE & CAPTCHA ENGINE
99
+ // ─────────────────────────────────────────────────────────────────────────────
100
+
101
+
102
+
103
+ async function createPowChallenge(difficulty = 3) {
104
+ if (!Number.isInteger(difficulty) || difficulty < 1 || difficulty > 6) throw new Error("Difficulty must be an integer between 1 and 6");
105
+ const salt = crypto.randomBytes(12).toString("hex");
106
+ const expires = Date.now() + 5 * 60 * 1000; // 5 min TTL
107
+ const payload = `${salt}:${difficulty}:${expires}`;
108
+ const signature = crypto.createHmac("sha256", await requiredSecret(TOKEN_KEY)).update(payload).digest("hex");
109
+ return {
110
+ algorithm: "SHA-256",
111
+ difficulty,
112
+ salt,
113
+ expires,
114
+ signature,
115
+ prefix: "0".repeat(difficulty)
116
+ };
117
+ }
118
+
119
+ async function verifyPowChallenge(salt, difficulty, expires, signature, nonce) {
120
+ if (!Number.isInteger(Number(difficulty)) || Number(difficulty) < 1 || Number(difficulty) > 6 || !Number.isFinite(Number(expires)) || Number(expires) > Date.now() + 300000) return { valid: false, error: "Invalid challenge parameters" };
121
+ if (Date.now() > Number(expires)) {
122
+ return { valid: false, error: "Challenge expired" };
123
+ }
124
+ const payload = `${salt}:${difficulty}:${expires}`;
125
+ const expectedSig = crypto.createHmac("sha256", await requiredSecret(TOKEN_KEY)).update(payload).digest("hex");
126
+ if (!equalSignature(signature, expectedSig)) {
127
+ return { valid: false, error: "Invalid challenge signature or tampered parameters" };
128
+ }
129
+ const hash = crypto.createHash("sha256").update(`${salt}:${nonce}`).digest("hex");
130
+ const requiredPrefix = "0".repeat(Number(difficulty));
131
+ if (!hash.startsWith(requiredPrefix)) {
132
+ return { valid: false, error: `Proof-of-work hash does not meet difficulty prefix '${requiredPrefix}'` };
133
+ }
134
+ return { valid: true, hash };
135
+ }
136
+
137
+ function solvePowChallenge(salt, difficulty) {
138
+ const prefix = "0".repeat(Number(difficulty));
139
+ let nonce = 0;
140
+ while (nonce < 10000000) {
141
+ const hash = crypto.createHash("sha256").update(`${salt}:${nonce}`).digest("hex");
142
+ if (hash.startsWith(prefix)) return { nonce, hash };
143
+ nonce++;
144
+ }
145
+ return { nonce: null, hash: null };
146
+ }
147
+
148
+ // ─────────────────────────────────────────────────────────────────────────────
149
+ // 3. BOT MITIGATION & HONEYPOT ANALYZER
150
+ // ─────────────────────────────────────────────────────────────────────────────
151
+
152
+ async function evaluateBotRisk({ honeypot = "", elapsedMs = 1500, token = null } = {}) {
153
+ const signals = [];
154
+ let score = 0; // 0 = Human, 100 = Definitely Bot
155
+
156
+ // 1. Honeypot check
157
+ if (honeypot && honeypot.trim().length > 0) {
158
+ score += 80;
159
+ signals.push("Honeypot tripwire field populated");
160
+ }
161
+
162
+ // 2. Velocity check (< 400ms is inhumanly fast for form completion)
163
+ if (elapsedMs < 400) {
164
+ score += 50;
165
+ signals.push(`Rapid submission velocity (${elapsedMs}ms < 400ms threshold)`);
166
+ } else if (elapsedMs < 800) {
167
+ score += 25;
168
+ signals.push(`Fast submission velocity (${elapsedMs}ms)`);
169
+ }
170
+
171
+ // 3. Remote token verification (e.g. Turnstile or Captcha)
172
+ if (token) {
173
+ const secret = await requiredSecret(CHALLENGE_KEY);
174
+ if (secret) {
175
+ try {
176
+ const verifyRes = await fetch("https://challenges.cloudflare.com/turnstile/v0/siteverify", {
177
+ method: "POST",
178
+ headers: { "Content-Type": "application/json" },
179
+ body: JSON.stringify({ secret, response: token }),
180
+ signal: AbortSignal.timeout(3000)
181
+ });
182
+ const vData = await verifyRes.json();
183
+ if (vData.success) {
184
+ score = Math.max(0, score - 60);
185
+ signals.push("Remote challenge verification passed");
186
+ } else {
187
+ score += 60;
188
+ signals.push(`Remote challenge rejected: ${(vData["error-codes"] || []).join(", ")}`);
189
+ }
190
+ } catch (err) {
191
+ score += 70;
192
+ signals.push("Remote challenge verification unavailable; fail closed");
193
+ }
194
+ } else {
195
+ signals.push("CHALLENGE_SECRET_KEY not set; using local behavioral evaluation");
196
+ }
197
+ }
198
+
199
+ const recommendation = score >= 70 ? "block" : score >= 30 ? "challenge" : "allow";
200
+ return {
201
+ risk_score: Math.min(100, score),
202
+ recommendation,
203
+ signals
204
+ };
205
+ }
206
+
207
+ // ─────────────────────────────────────────────────────────────────────────────
208
+ // 4. ANTI-CSRF & RATE LIMITING
209
+ // ─────────────────────────────────────────────────────────────────────────────
210
+
211
+ async function generateCsrfToken(sessionId) {
212
+ if (typeof sessionId !== "string" || !sessionId) throw new Error("Authenticated session ID required");
213
+ const timestamp = Date.now();
214
+ const raw = `${sessionId}:${timestamp}`;
215
+ const sig = crypto.createHmac("sha256", await requiredSecret(TOKEN_KEY)).update(raw).digest("hex");
216
+ return `${timestamp}:${sig}`;
217
+ }
218
+
219
+ async function verifyCsrfToken(token, sessionId, ttlMs = 3600000) {
220
+ if (typeof sessionId !== "string" || !sessionId) return false;
221
+ if (!token || !token.includes(":")) return false;
222
+ const [timestampStr, sig] = token.split(":");
223
+ const timestamp = Number(timestampStr);
224
+ if (!Number.isFinite(timestamp) || timestamp > Date.now() || Date.now() - timestamp > ttlMs) return false;
225
+ const raw = `${sessionId}:${timestamp}`;
226
+ const expectedSig = crypto.createHmac("sha256", await requiredSecret(TOKEN_KEY)).update(raw).digest("hex");
227
+ return equalSignature(sig, expectedSig);
228
+ }
229
+
230
+ const rateLimitBuckets = new Map();
231
+
232
+ function checkRateLimit(ipOrKey, { maxRequests = 60, windowMs = 60000 } = {}) {
233
+ const now = Date.now();
234
+ const bucket = rateLimitBuckets.get(ipOrKey) || [];
235
+ const valid = bucket.filter((t) => now - t < windowMs);
236
+ if (valid.length >= maxRequests) {
237
+ return { allowed: false, remaining: 0, resetInMs: windowMs - (now - valid[0]) };
238
+ }
239
+ valid.push(now);
240
+ rateLimitBuckets.set(ipOrKey, valid);
241
+ return { allowed: true, remaining: maxRequests - valid.length, resetInMs: windowMs };
242
+ }
243
+
244
+ // ─────────────────────────────────────────────────────────────────────────────
245
+ // 5. WEBSITE SECURITY AUDITOR & SCORECARD (A+ to F)
246
+ // ─────────────────────────────────────────────────────────────────────────────
247
+
248
+ async function auditWebsiteUrl(targetUrl) {
249
+ const checks = [];
250
+ let score = 100;
251
+ let headers = {};
252
+
253
+ try {
254
+ const url = new URL(targetUrl);
255
+ if (url.protocol !== "https:" || url.username || url.password || url.search || url.hash) throw new Error("HTTPS without embedded credentials is required");
256
+ const res = await fetch(url, { method: "HEAD", redirect: "error", signal: AbortSignal.timeout(5000) });
257
+ if (!res.ok) throw new Error("Website returned unsuccessful HTTP status");
258
+ headers = Object.fromEntries(res.headers.entries());
259
+
260
+ // 1. Content-Security-Policy
261
+ const csp = headers["content-security-policy"];
262
+ if (csp) {
263
+ checks.push({ name: "Content-Security-Policy", status: "PASS", detail: "Configured and enforced" });
264
+ } else {
265
+ score -= 25;
266
+ checks.push({ name: "Content-Security-Policy", status: "FAIL", detail: "Missing CSP header; vulnerable to XSS and injection" });
267
+ }
268
+
269
+ // 2. Strict-Transport-Security (HSTS)
270
+ const hsts = headers["strict-transport-security"];
271
+ if (hsts) {
272
+ checks.push({ name: "Strict-Transport-Security", status: "PASS", detail: "HSTS header active" });
273
+ } else {
274
+ score -= 20;
275
+ checks.push({ name: "Strict-Transport-Security", status: "FAIL", detail: "Missing HSTS; downgrade attacks possible" });
276
+ }
277
+
278
+ // 3. X-Content-Type-Options
279
+ const xcto = headers["x-content-type-options"];
280
+ if (xcto === "nosniff") {
281
+ checks.push({ name: "X-Content-Type-Options", status: "PASS", detail: "nosniff enforced" });
282
+ } else {
283
+ score -= 15;
284
+ checks.push({ name: "X-Content-Type-Options", status: "FAIL", detail: "Missing or invalid nosniff header" });
285
+ }
286
+
287
+ // 4. X-Frame-Options
288
+ const xfo = headers["x-frame-options"];
289
+ if (xfo && (xfo.toUpperCase() === "DENY" || xfo.toUpperCase() === "SAMEORIGIN")) {
290
+ checks.push({ name: "X-Frame-Options", status: "PASS", detail: xfo.toUpperCase() });
291
+ } else {
292
+ score -= 15;
293
+ checks.push({ name: "X-Frame-Options", status: "FAIL", detail: "Missing framing restriction; clickjacking risk" });
294
+ }
295
+
296
+ // 5. Referrer-Policy
297
+ const rp = headers["referrer-policy"];
298
+ if (rp) {
299
+ checks.push({ name: "Referrer-Policy", status: "PASS", detail: rp });
300
+ } else {
301
+ score -= 10;
302
+ checks.push({ name: "Referrer-Policy", status: "WARN", detail: "Default referrer policy may leak sensitive URLs" });
303
+ }
304
+
305
+ // 6. Permissions-Policy
306
+ const pp = headers["permissions-policy"];
307
+ if (pp) {
308
+ checks.push({ name: "Permissions-Policy", status: "PASS", detail: "Hardware access constrained" });
309
+ } else {
310
+ score -= 10;
311
+ checks.push({ name: "Permissions-Policy", status: "WARN", detail: "Missing Permissions-Policy header" });
312
+ }
313
+ } catch (err) {
314
+ return {
315
+ target: targetUrl,
316
+ error: `Connection failed: ${err.message}`,
317
+ grade: "F",
318
+ score: 0,
319
+ checks: []
320
+ };
321
+ }
322
+
323
+ score = Math.max(0, score);
324
+ let grade = "F";
325
+ if (score >= 95) grade = "A+";
326
+ else if (score >= 85) grade = "A";
327
+ else if (score >= 75) grade = "B";
328
+ else if (score >= 60) grade = "C";
329
+ else if (score >= 45) grade = "D";
330
+
331
+ return {
332
+ target: targetUrl,
333
+ grade,
334
+ score,
335
+ checks_count: checks.length,
336
+ checks,
337
+ timestamp: new Date().toISOString()
338
+ };
339
+ }
340
+
341
+ // ─────────────────────────────────────────────────────────────────────────────
342
+ // 6. DROP-IN SECURITY SHIELD CODE GENERATOR
343
+ // ─────────────────────────────────────────────────────────────────────────────
344
+
345
+ function generateSiteShieldCode() {
346
+ return `// Drop-in Node.js / Express Security Shield
347
+ import helmet from 'helmet';
348
+
349
+ export function configureSiteSecurity(app) {
350
+ // 1. Strict Content Security Policy & Protective Headers
351
+ app.use(helmet({
352
+ contentSecurityPolicy: {
353
+ directives: {
354
+ defaultSrc: ["'self'"],
355
+ scriptSrc: ["'self'"],
356
+ styleSrc: ["'self'", "'unsafe-inline'"],
357
+ imgSrc: ["'self'", "data:", "https:"],
358
+ objectSrc: ["'none'"],
359
+ upgradeInsecureRequests: [],
360
+ },
361
+ },
362
+ crossOriginEmbedderPolicy: true,
363
+ crossOriginOpenerPolicy: { policy: "same-origin" },
364
+ crossOriginResourcePolicy: { policy: "same-origin" },
365
+ hsts: { maxAge: 31536000, includeSubDomains: true, preload: true },
366
+ noSniff: true,
367
+ frameguard: { action: 'deny' },
368
+ referrerPolicy: { policy: 'strict-origin-when-cross-origin' },
369
+ }));
370
+
371
+ // 2. Anti-Clickjacking & Framing Guard
372
+ app.use((req, res, next) => {
373
+ res.setHeader('X-Frame-Options', 'DENY');
374
+ res.setHeader('Permissions-Policy', 'camera=(), microphone=(), geolocation=()');
375
+ next();
376
+ });
377
+ }
378
+ `;
379
+ }
380
+
381
+ // ─────────────────────────────────────────────────────────────────────────────
382
+ // 7. STATIC CODE SCANNER & PLUGIN AUDITOR
383
+ // ─────────────────────────────────────────────────────────────────────────────
384
+
385
+ function scanPath(targetPath) { return scanSource(targetPath); }
386
+
387
+ function discoverPluginDirs() {
388
+ const dirs = new Set();
389
+ const cwd = process.cwd();
390
+
391
+ const projectPlugins = path.join(cwd, ".agent", "skills", "total-recall", "plugins");
392
+ if (fs.existsSync(projectPlugins)) {
393
+ for (const d of fs.readdirSync(projectPlugins)) {
394
+ const full = path.join(projectPlugins, d);
395
+ if (fs.existsSync(path.join(full, "plugin.json"))) dirs.add(full);
396
+ }
397
+ }
398
+
399
+ const globalPlugins = path.join(os.homedir(), '.agent', 'skills', 'total-recall', 'plugins');
400
+ if (fs.existsSync(globalPlugins)) {
401
+ for (const entry of fs.readdirSync(globalPlugins)) {
402
+ const full = path.join(globalPlugins, entry);
403
+ if (fs.existsSync(path.join(full, 'plugin.json'))) dirs.add(full);
404
+ }
405
+ }
406
+
407
+ return [...new Set([...dirs].map(dir => fs.realpathSync(dir)))];
408
+ }
409
+
410
+ function auditPlugins({ harden = false } = {}) {
411
+ if (harden) throw new Error("Manifest edits cannot enforce runtime security; fix the reported owning source");
412
+ const pluginDirs = discoverPluginDirs();
413
+ const results = [];
414
+
415
+ for (const dir of pluginDirs) {
416
+ const manifestPath = path.join(dir, "plugin.json");
417
+ let manifest;
418
+ try {
419
+ manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
420
+ } catch (err) {
421
+ results.push({ plugin: path.basename(dir), path: dir, status: "error", error: err.message, findings: [] });
422
+ continue;
423
+ }
424
+
425
+ const pluginId = manifest.id || path.basename(dir);
426
+ const findings = [];
427
+
428
+
429
+ if (!manifest.auth || manifest.auth.plugin !== "auth") {
430
+ findings.push({ rule: "Missing Auth Plugin Integration", severity: "HIGH", detail: "Manifest does not declare auth plugin binding" });
431
+
432
+ }
433
+
434
+ const grants = manifest.deploy?.access_grants || [];
435
+ if (!grants.includes("auth:session:verify")) {
436
+ findings.push({ rule: "Missing Session Verification Grant", severity: "MEDIUM", detail: "deploy.access_grants lacks auth:session:verify" });
437
+
438
+ }
439
+
440
+ const codeFindings = scanPath(dir);
441
+ for (const cf of codeFindings) {
442
+ findings.push({ rule: cf.rule, severity: cf.severity, detail: `${cf.file}:${cf.line}` });
443
+ }
444
+
445
+
446
+
447
+ results.push({
448
+ plugin: pluginId,
449
+ path: dir,
450
+ status: findings.some(f => ["CRITICAL", "HIGH"].includes(f.severity)) ? "vulnerable" : findings.length ? "review-required" : "passed",
451
+ hardened: false,
452
+ findings_count: findings.length,
453
+ findings
454
+ });
455
+ }
456
+
457
+ return results;
458
+ }
459
+
460
+ // ─────────────────────────────────────────────────────────────────────────────
461
+ // 8. CLI SUBCOMMAND DISPATCH
462
+ // ─────────────────────────────────────────────────────────────────────────────
463
+
464
+ async function challengeCommand(args) {
465
+ const json = args.includes("--json");
466
+ const sub = args[0] || "create";
467
+
468
+ if (sub === "create") {
469
+ const diffIdx = args.indexOf("--difficulty");
470
+ const diff = diffIdx !== -1 ? parseInt(args[diffIdx + 1], 10) : 3;
471
+ const puzzle = await createPowChallenge(diff);
472
+ if (json) console.log(JSON.stringify(puzzle, null, 2));
473
+ else {
474
+ console.log("Proof-of-Work Challenge Created:");
475
+ console.log(` Salt : ${puzzle.salt}`);
476
+ console.log(` Difficulty : ${puzzle.difficulty} leading zeroes`);
477
+ console.log(` Signature : ${puzzle.signature}`);
478
+ console.log(` Expires In : 5 minutes`);
479
+ }
480
+ return;
481
+ }
482
+
483
+ if (sub === "test") {
484
+ console.log("Running self-contained Proof-of-Work solve and verify cycle…");
485
+ const puzzle = await createPowChallenge(3);
486
+ const start = performance.now();
487
+ const solution = solvePowChallenge(puzzle.salt, puzzle.difficulty);
488
+ const timeMs = Math.round(performance.now() - start);
489
+ const result = await verifyPowChallenge(puzzle.salt, puzzle.difficulty, puzzle.expires, puzzle.signature, solution.nonce);
490
+ const out = {
491
+ test: "Proof-of-Work Verification Cycle",
492
+ solved_in_ms: timeMs,
493
+ nonce: solution.nonce,
494
+ hash: solution.hash,
495
+ verified: result.valid
496
+ };
497
+ if (json) console.log(JSON.stringify(out, null, 2));
498
+ else {
499
+ console.log(` PoW Solved in ${timeMs}ms with nonce ${solution.nonce}`);
500
+ console.log(` Hash: ${solution.hash}`);
501
+ console.log(` Verification: ${result.valid ? "VALID" : "INVALID"}`);
502
+ }
503
+ return;
504
+ }
505
+
506
+ if (sub === "verify") {
507
+ const salt = args[1];
508
+ const nonce = args[2];
509
+ const sigIdx = args.indexOf("--signature");
510
+ const sig = sigIdx !== -1 ? args[sigIdx + 1] : "";
511
+ const diffIdx = args.indexOf("--difficulty");
512
+ const diff = diffIdx !== -1 ? parseInt(args[diffIdx + 1], 10) : 3;
513
+ const expIdx = args.indexOf("--expires");
514
+ const expires = expIdx !== -1 ? parseInt(args[expIdx + 1], 10) : Date.now() + 60000;
515
+
516
+ const res = await verifyPowChallenge(salt, diff, expires, sig, nonce);
517
+ if (json) console.log(JSON.stringify(res));
518
+ else console.log(`Verification: ${res.valid ? "PASSED" : `FAILED (${res.error})`}`);
519
+ return;
520
+ }
521
+
522
+ console.error("Usage: total-recall security challenge <create|verify|test>");
523
+ process.exitCode = 1;
524
+ }
525
+
526
+ async function botMitigateCommand(args) {
527
+ const json = args.includes("--json");
528
+ const hpIdx = args.indexOf("--honeypot");
529
+ const honeypot = hpIdx !== -1 ? args[hpIdx + 1] : "";
530
+ const elIdx = args.indexOf("--elapsed");
531
+ const elapsed = elIdx !== -1 ? parseInt(args[elIdx + 1], 10) : 1500;
532
+ const tokIdx = args.indexOf("--token");
533
+ const token = tokIdx !== -1 ? args[tokIdx + 1] : null;
534
+
535
+ const result = await evaluateBotRisk({ honeypot, elapsedMs: elapsed, token });
536
+ if (json) console.log(JSON.stringify(result, null, 2));
537
+ else {
538
+ console.log("Bot Mitigation Evaluation");
539
+ console.log("───────────────────────────────────────────────────────");
540
+ console.log(`Risk Score : ${result.risk_score} / 100`);
541
+ console.log(`Recommendation : ${result.recommendation.toUpperCase()}`);
542
+ if (result.signals.length) {
543
+ console.log("Signals:");
544
+ for (const s of result.signals) console.log(` • ${s}`);
545
+ }
546
+ }
547
+ }
548
+
549
+ async function siteAuditCommand(args) {
550
+ const json = args.includes("--json");
551
+ const target = args.find((a) => !a.startsWith("--"));
552
+ if (!target) throw new Error("site-audit requires an authorized URL");
553
+ const report = await auditWebsiteUrl(target);
554
+ if (json) console.log(JSON.stringify(report, null, 2));
555
+ else {
556
+ console.log(`Website Security Audit for: ${target}`);
557
+ console.log("───────────────────────────────────────────────────────");
558
+ console.log(`Security Grade : ${report.grade} (Score: ${report.score}/100)`);
559
+ console.log(`Headers Checked: ${report.checks_count}`);
560
+ for (const c of report.checks) {
561
+ const badge = c.status === "PASS" ? "" : c.status === "WARN" ? "" : "";
562
+ console.log(` ${badge} ${c.name.padEnd(28)} : ${c.detail}`);
563
+ }
564
+ }
565
+ }
566
+
567
+ async function siteShieldCommand(args) {
568
+ const code = generateSiteShieldCode();
569
+ const json = args.includes("--json");
570
+ if (json) console.log(JSON.stringify({ shield_middleware: code }));
571
+ else {
572
+ console.log("Website Security Shield Middleware Snippet:");
573
+ console.log(code);
574
+ }
575
+ }
576
+
577
+ async function auditCommand(args) {
578
+ const json = args.includes("--json");
579
+ const cwd = process.cwd();
580
+ const findings = [];
581
+
582
+ const candidates = [
583
+ path.join(cwd, ".agent", "secrets.enc"),
584
+ path.join(cwd, ".agent", "config", "webauthn.json"),
585
+ path.join(process.env.HOME || "", ".agent", "secrets.enc")
586
+ ];
587
+
588
+ for (const c of candidates) {
589
+ if (fs.existsSync(c)) {
590
+ try {
591
+ const stat = fs.statSync(c);
592
+ const mode = (stat.mode & 0o777).toString(8);
593
+ if (mode !== "600" && mode !== "400") {
594
+ findings.push({
595
+ category: "file-permissions",
596
+ target: path.basename(c),
597
+ severity: "HIGH",
598
+ message: `Permissions mode is ${mode}, expected 0600 (owner only)`
599
+ });
600
+ }
601
+ } catch {
602
+ // ignore
603
+ }
604
+ }
605
+ }
606
+
607
+ const sastFindings = scanPath(cwd).filter(f => ["CRITICAL", "HIGH"].includes(f.severity));
608
+ for (const sf of sastFindings) {
609
+ findings.push({
610
+ category: "sast",
611
+ target: `${sf.file}:${sf.line}`,
612
+ severity: sf.severity,
613
+ message: sf.rule
614
+ });
615
+ }
616
+
617
+ const pluginAudit = auditPlugins({ harden: false });
618
+ const vulnerablePlugins = pluginAudit.filter((p) => p.status === "vulnerable");
619
+ if (vulnerablePlugins.length > 0) {
620
+ findings.push({
621
+ category: "plugins",
622
+ target: `${vulnerablePlugins.length} plugin(s)`,
623
+ severity: "HIGH",
624
+ message: `High-priority source findings require review in plugins: ${vulnerablePlugins.map((p) => p.plugin).join(", ")}`
625
+ });
626
+ }
627
+
628
+ const passed = !findings.some(f => ["CRITICAL", "HIGH"].includes(f.severity)) && pluginAudit.length > 0 && !pluginAudit.some(p => p.status === "error");
629
+ const result = {
630
+ audit: "Total Recall Security Baseline Audit",
631
+ passed,
632
+ findings_count: findings.length,
633
+ findings,
634
+ plugins_audited: pluginAudit.length,
635
+ timestamp: new Date().toISOString()
636
+ };
637
+
638
+ if (json) console.log(JSON.stringify(result, null, 2));
639
+ else {
640
+ console.log("Total Recall Security Baseline Audit");
641
+ console.log("───────────────────────────────────────────────────────");
642
+ console.log(`Status : ${passed ? " PASSED (inspected scope)" : " FAILED (risks or incomplete coverage)"}`);
643
+ console.log(`Plugins : ${pluginAudit.length} plugin(s) inspected`);
644
+ console.log(`Findings : ${findings.length} total finding(s)`);
645
+ for (const f of findings) {
646
+ console.log(` [${f.severity}] ${f.category} -> ${f.target}: ${f.message}`);
647
+ }
648
+ }
649
+ if (!passed) process.exitCode = 1;
650
+ }
651
+
652
+ async function pluginsCommand(args) {
653
+ const json = args.includes("--json");
654
+ const harden = args.includes("--harden") || args.includes("-h");
655
+ const results = auditPlugins({ harden });
656
+ const totalFindings = results.reduce((acc, r) => acc + r.findings_count, 0);
657
+ const totalHardened = results.filter((r) => r.hardened).length;
658
+
659
+ const summary = {
660
+ timestamp: new Date().toISOString(),
661
+ status: results.length === 0 || results.some(item => item.status === "error") ? "incomplete" : results.some(item => item.status === "vulnerable") ? "at-risk" : totalFindings ? "review-required" : "passed",
662
+ total_plugins: results.length,
663
+ total_findings: totalFindings,
664
+ hardened_count: totalHardened,
665
+ plugins: results
666
+ };
667
+
668
+ if (summary.status !== "passed") process.exitCode = 1;
669
+ if (json) console.log(JSON.stringify(summary, null, 2));
670
+ else {
671
+ console.log("User Plugins Security Manager");
672
+ console.log("───────────────────────────────────────────────────────");
673
+ console.log(`Audited : ${summary.total_plugins} plugin(s)`);
674
+ console.log(`Findings : ${summary.total_findings} security finding(s)`);
675
+ if (harden) console.log(`Hardened : ${summary.hardened_count} plugin(s) secured`);
676
+ console.log();
677
+ for (const p of results) {
678
+ const badge = p.status === 'error' ? ' AUDIT FAILED' : p.findings_count === 0 ? " NO SOURCE FINDINGS" : ` ${p.findings_count} FINDING(S)`;
679
+ console.log(` • ${p.plugin.padEnd(20)} [${badge}]${p.hardened ? " (HARDENED)" : ""}`);
680
+ }
681
+ }
682
+ }
683
+
684
+ async function machineCommand(args) {
685
+ if (args.includes('--apply-permissions') && !['darwin', 'linux'].includes(process.platform)) throw new Error('SSH permission repair is unavailable on this platform');
686
+ const remediation = args.includes('--apply-permissions') ? hardenSshPermissions() : null;
687
+ const result = await auditMachine();
688
+ if (remediation) result.remediation = remediation;
689
+ console.log(JSON.stringify(result, null, 2));
690
+ if (result.status !== 'passed' || remediation?.status === 'failed') process.exitCode = 1;
691
+ }
692
+ function option(args, name) {
693
+ const at = args.indexOf(name);
694
+ if (at < 0) return undefined;
695
+ if (!args[at + 1] || args[at + 1].startsWith('--')) throw new Error('Missing option value');
696
+ return args[at + 1];
697
+ }
698
+ async function deviceCommand(args) {
699
+ if (args.includes('--stdin') && args.includes('--answers-json')) throw new Error('Choose one answers input');
700
+ const text = args.includes('--stdin') ? await readStdin(8192) : option(args, '--answers-json');
701
+ if (text && Buffer.byteLength(text) > 8192) throw new Error('Input exceeds limit');
702
+ const report = assessDevice({ platform: option(args, '--platform'), deviceId: option(args, '--device'), observations: text ? JSON.parse(text) : {} });
703
+ console.log(JSON.stringify(report, null, 2));
704
+ if (report.status !== 'reviewed') process.exitCode = 1;
705
+ }
706
+ async function androidCommand(args) {
707
+ const serial = option(args, '--serial');
708
+ if (!serial) throw new Error('An existing authorized ADB device serial is required');
709
+ const report = auditAndroid({ serial, execute: runCheck });
710
+ console.log(JSON.stringify(report, null, 2));
711
+ process.exitCode = 1; // A property-only assessment never establishes full device coverage.
712
+ }
713
+ async function fleetCommand(args) {
714
+ const at = args.indexOf('--target');
715
+ if (at >= 0 && (!args[at + 1] || args[at + 1].startsWith('--'))) throw new Error('--target requires a discovered machine name');
716
+ const result = await auditFleet({ target: at >= 0 ? args[at + 1] : undefined });
717
+ console.log(JSON.stringify(result, null, 2));
718
+ if (result.status !== 'passed') process.exitCode = 1;
719
+ }
720
+ async function secureAllCommand(args) {
721
+ // Fleet inventory and actionable recommendations; never claim blanket protection.
722
+ if (args.includes('--apply')) throw new Error('Review each machine report. Firewall/SSH/disk changes require a verified access and rollback plan. Use machine --apply-permissions for bounded local permission remediation.');
723
+ return fleetCommand(args);
724
+ }
725
+
726
+ async function scanCommand(args) {
727
+ const json = args.includes("--json");
728
+ const target = args.find((a) => !a.startsWith("--")) || ".";
729
+ const findings = scanPath(target);
730
+
731
+ const report = {
732
+ scanner: "Total Recall SAST & Secret Leak Analyzer",
733
+ timestamp: new Date().toISOString(),
734
+ target,
735
+ total_findings: findings.length,
736
+ critical: findings.filter((f) => f.severity === "CRITICAL").length,
737
+ high: findings.filter((f) => f.severity === "HIGH").length,
738
+ findings
739
+ };
740
+
741
+ if (report.critical || report.high) process.exitCode = 1;
742
+ if (json) console.log(JSON.stringify(report, null, 2));
743
+ else {
744
+ console.log(`Security Scan for: ${target}`);
745
+ console.log(`Findings: ${report.total_findings} (${report.critical} critical, ${report.high} high)`);
746
+ for (const f of findings) {
747
+ console.log(` • [${f.severity}] ${f.file}:${f.line} - ${f.rule}`);
748
+ }
749
+ }
750
+ }
751
+
752
+ async function hardenCommand(args) {
753
+ if (!args.includes('--apply-permissions')) {
754
+ console.log(JSON.stringify({ status: 'plan', applied_count: 0, machine: await auditMachine(), next: 'machine --apply-permissions applies and verifies recognized local SSH permissions; review firewall/SSH/disk steps per finding' }, null, 2));
755
+ return;
756
+ }
757
+ return machineCommand(args);
758
+ }
759
+
760
+ async function encryptCommand(args) {
761
+ const json = args.includes("--json");
762
+ const passIdx = args.indexOf("--passphrase");
763
+ if (passIdx !== -1) throw new Error("Passphrases in argv are prohibited; configure SECURITY_MASTER_KEY through auth");
764
+ const pass = await requiredSecret(TOKEN_KEY);
765
+
766
+ const nonFlags = args.filter((a, i) => !a.startsWith("--") && (passIdx === -1 || (i !== passIdx + 1)));
767
+ if (!args.includes("--stdin") || nonFlags.length) throw new Error("Encryption requires --stdin; plaintext in argv is prohibited");
768
+ const text = await readStdin();
769
+ if (!text) throw new Error("Plaintext input is required");
770
+
771
+ const encrypted = encryptText(text, pass);
772
+ if (json) console.log(JSON.stringify(encrypted, null, 2));
773
+ else {
774
+ console.log("Encrypted Payload (AES-256-GCM):");
775
+ console.log(encrypted.encoded);
776
+ }
777
+ }
778
+
779
+ async function decryptCommand(args) {
780
+ const json = args.includes("--json");
781
+ const passIdx = args.indexOf("--passphrase");
782
+ if (passIdx !== -1) throw new Error("Passphrases in argv are prohibited; configure SECURITY_MASTER_KEY through auth");
783
+ const pass = await requiredSecret(TOKEN_KEY);
784
+
785
+ const payload = args.find((a, i) => !a.startsWith("--") && (passIdx === -1 || (i !== passIdx + 1)));
786
+ if (!payload) {
787
+ console.error("Usage: total-recall security decrypt <payload> [--passphrase pass]");
788
+ process.exitCode = 1;
789
+ return;
790
+ }
791
+
792
+ try {
793
+ const plain = decryptText(payload, pass);
794
+ if (json) console.log(JSON.stringify({ plaintext: plain }));
795
+ else console.log(`Decrypted: ${plain}`);
796
+ } catch (err) {
797
+ console.error(`Decryption failed: ${err.message}`);
798
+ process.exitCode = 1;
799
+ }
800
+ }
801
+
802
+ async function probeCommand(args) {
803
+ const at = args.indexOf('--url');
804
+ if (at < 0 || !args[at + 1]) throw new Error('probe requires --url https://your-authorized-site');
805
+ const url = new URL(args[at + 1]);
806
+ if (url.protocol !== 'https:' || url.username || url.password || url.search || url.hash) throw new Error('Use HTTPS without embedded credentials, query or fragment');
807
+ const started = Date.now();
808
+ const response = await fetch(url, { method: 'HEAD', redirect: 'error', signal: AbortSignal.timeout(8000) });
809
+ const headers = Object.fromEntries(['content-security-policy', 'strict-transport-security', 'x-content-type-options', 'x-frame-options', 'referrer-policy'].map(key => [key, response.headers.has(key)]));
810
+ const result = { status: response.status, ok: response.ok, latency_ms: Date.now() - started, endpoint: url.origin, security_headers: headers, timestamp: new Date().toISOString() };
811
+ console.log(JSON.stringify(result));
812
+ if (!response.ok || Object.values(headers).some(present => !present)) process.exitCode = 1;
813
+ }
814
+
815
+ async function tokenCommand(args) {
816
+ const [action = "status"] = args;
817
+ const json = args.includes("--json");
818
+ if (action === "status") {
819
+ const key = await resolveSecret(TOKEN_KEY);
820
+ const status = { present: Boolean(key), secretKey: TOKEN_KEY };
821
+ console.log(json ? JSON.stringify(status) : `Security Master Key: ${status.present ? "present" : "missing"} (${TOKEN_KEY})`);
822
+ if (!status.present) process.exitCode = 1;
823
+ return;
824
+ }
825
+ if (action === "set" && args.includes("--stdin")) {
826
+ const val = await readStdin();
827
+ if (!val) { console.error("No key provided on stdin."); process.exitCode = 2; return; }
828
+ if (Buffer.byteLength(val) < 32) throw new Error('Master key requires at least 32 bytes');
829
+ const owner = await loadOwner('auth', 'secrets-store');
830
+ await owner.setSecret(process.env.TR_SECRETS_BRAIN || owner.defaultBrainDir(), TOKEN_KEY, val, { actor: 'security-plugin' });
831
+ console.log(JSON.stringify({ saved: true, provider_verified: false, validation: 'local-key-length', secretKey: TOKEN_KEY }));
832
+ return;
833
+ }
834
+ console.error("Usage: security token <status|set --stdin>");
835
+ process.exitCode = 2;
836
+ }
837
+
838
+ const IMPLEMENTED = {
839
+ status: async (args) => {
840
+ const json = args.includes("--json");
841
+ const status = {
842
+ plugin: "security",
843
+ active_suite: [
844
+ "platforms", "device", "android",
845
+ "machine",
846
+ "fleet",
847
+ "audit",
848
+ "scan",
849
+ "harden",
850
+ "plugins",
851
+ "secure-all",
852
+ "challenge",
853
+ "bot-mitigate",
854
+ "site-audit",
855
+ "site-shield",
856
+ "encrypt",
857
+ "decrypt",
858
+ "probe",
859
+ "token"
860
+ ],
861
+ encryption_cipher: "AES-256-GCM (scrypt KDF)",
862
+ proof_of_work: "SHA-256 PoW challenge engine",
863
+ bot_mitigation: "Honeypot + velocity + token risk analyzer",
864
+ master_key_state: "not-checked; use token status",
865
+ auth_integration: "auth",
866
+ device_coverage: platformInventory()
867
+ };
868
+ if (json) console.log(JSON.stringify(status, null, 2));
869
+ else {
870
+ console.log("Total Recall Complete Security & Website Defensive Suite");
871
+ console.log("───────────────────────────────────────────────────────");
872
+ console.log(`Encryption : ${status.encryption_cipher}`);
873
+ console.log(`PoW Defense : ${status.proof_of_work}`);
874
+ console.log(`Bot Defense : ${status.bot_mitigation}`);
875
+ console.log(`Master Key : ${"Not checked"}`);
876
+ console.log(`Tools Active : ${status.active_suite.join(", ")}`);
877
+ }
878
+ },
879
+ machine: machineCommand,
880
+ platforms: async () => console.log(JSON.stringify(platformInventory(), null, 2)),
881
+ device: deviceCommand,
882
+ android: androidCommand,
883
+ network: machineCommand,
884
+ fleet: fleetCommand,
885
+ audit: auditCommand,
886
+ scan: scanCommand,
887
+ harden: hardenCommand,
888
+ plugins: pluginsCommand,
889
+ "secure-all": secureAllCommand,
890
+ challenge: challengeCommand,
891
+ "bot-mitigate": botMitigateCommand,
892
+ "site-audit": siteAuditCommand,
893
+ "site-shield": siteShieldCommand,
894
+ encrypt: encryptCommand,
895
+ decrypt: decryptCommand,
896
+ probe: probeCommand,
897
+ token: tokenCommand
898
+ };
899
+
900
+ export async function run(argv = []) {
901
+ const args = Array.isArray(argv) ? argv.slice(3) : [];
902
+ const sub = args[0] || "status";
903
+ if (['--help', '-h', 'help'].includes(sub)) {
904
+ console.log('Usage: total-recall security <command> [--json]\nCommands: ' + Object.keys(IMPLEMENTED).join(', '));
905
+ console.log('Device review: device --platform <platform> --device <label> --json\nSource audit: scan <path> --json; plugins --json\nCredentials: auth plugin only. Missing evidence remains unverified.');
906
+ return;
907
+ }
908
+ if (IMPLEMENTED[sub]) {
909
+ try { return await IMPLEMENTED[sub](args.slice(1)); }
910
+ catch { console.log(JSON.stringify({ status: 'failed', operation: sub, error: 'Security operation failed; verify arguments, owning plugin availability and authorization. No success was established.' })); process.exitCode = 1; return; }
911
+ }
912
+ console.error(`Security: "${sub}" is not implemented.`);
913
+ process.exitCode = 2;
914
+ }
915
+
916
+ export {
917
+ encryptText,
918
+ decryptText,
919
+ scanPath,
920
+ auditPlugins,
921
+ discoverPluginDirs,
922
+ createPowChallenge,
923
+ verifyPowChallenge,
924
+ solvePowChallenge,
925
+ evaluateBotRisk,
926
+ generateCsrfToken,
927
+ verifyCsrfToken,
928
+ checkRateLimit,
929
+ auditWebsiteUrl,
930
+ generateSiteShieldCode
931
+ };
932
+ export default run;