auramaxx 1.0.0-alpha.4

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 (363) hide show
  1. package/LICENSE +26 -0
  2. package/README.md +112 -0
  3. package/bin/aurawallet.js +121 -0
  4. package/docs/ADAPTERS.md +467 -0
  5. package/docs/API.md +2679 -0
  6. package/docs/APPS.md +198 -0
  7. package/docs/ARCHITECTURE.md +350 -0
  8. package/docs/AUTH.md +698 -0
  9. package/docs/BEST-PRACTICES.md +121 -0
  10. package/docs/CLI.md +61 -0
  11. package/docs/DEVELOPING-APPS.md +452 -0
  12. package/docs/EXTENSION.md +97 -0
  13. package/docs/JOBS.md +33 -0
  14. package/docs/MCP.md +76 -0
  15. package/docs/PROTOCOL.md +142 -0
  16. package/docs/SETUP.md +219 -0
  17. package/docs/WORKSPACE.md +672 -0
  18. package/docs/agent-auth.md +63 -0
  19. package/docs/aura-file.md +48 -0
  20. package/docs/credentials.md +53 -0
  21. package/docs/external/getting-started.md +65 -0
  22. package/docs/external/overview.md +45 -0
  23. package/docs/external/use-cases.md +48 -0
  24. package/docs/external/why-aura.md +35 -0
  25. package/docs/jobs/connect-agent.md +77 -0
  26. package/docs/jobs/migrate-from-dotenv.md +79 -0
  27. package/docs/jobs/recover-from-lockout.md +72 -0
  28. package/docs/jobs/secure-ci.md +63 -0
  29. package/docs/oauth2.md +42 -0
  30. package/docs/passkeys.md +60 -0
  31. package/docs/security.md +540 -0
  32. package/docs/specs/aura-open-protocol.md +61 -0
  33. package/docs/specs/aura-provider-plugin.md +24 -0
  34. package/docs/specs/aura-registry-model.md +31 -0
  35. package/docs/specs/fixtures/invalid-bad-key.aura +1 -0
  36. package/docs/specs/fixtures/invalid-bad-unicode-escape.aura +1 -0
  37. package/docs/specs/fixtures/invalid-duplicate-key.aura +2 -0
  38. package/docs/specs/fixtures/valid-basic.aura +4 -0
  39. package/docs/specs/fixtures/valid-provider-ref.aura +1 -0
  40. package/docs/specs/fixtures/valid-quoted-escapes.aura +2 -0
  41. package/docs/templates/RELEASE_NOTES_TEMPLATE.md +22 -0
  42. package/docs/totp.md +40 -0
  43. package/docs/wallet/AI.md +508 -0
  44. package/docs/wallet/DEVELOPING-STRATEGIES.md +713 -0
  45. package/docs/wallet/README.md +47 -0
  46. package/docs/wallet/STRATEGY.md +89 -0
  47. package/next.config.ts +21 -0
  48. package/package.json +151 -0
  49. package/postcss.config.mjs +8 -0
  50. package/prisma/migrations/20260214170000_baseline/migration.sql +511 -0
  51. package/prisma/migrations/20260216214537_add_passkey_model/migration.sql +18 -0
  52. package/prisma/migrations/20260217150500_add_credential_access_audit/migration.sql +31 -0
  53. package/prisma/migrations/migration_lock.toml +3 -0
  54. package/prisma/schema.prisma +447 -0
  55. package/public/logo-chevron.svg +31 -0
  56. package/public/logo-concentric.svg +31 -0
  57. package/public/logo-crosshatch.svg +39 -0
  58. package/public/logo-dashed.svg +39 -0
  59. package/public/logo-horizontal.svg +31 -0
  60. package/public/logo-m56.svg +64 -0
  61. package/public/logo.webp +0 -0
  62. package/scripts/add-app.js +245 -0
  63. package/scripts/init.sh +57 -0
  64. package/scripts/migrate-apikeys-to-credentials.ts +35 -0
  65. package/scripts/sandbox-agent-flow.sh +235 -0
  66. package/scripts/sandbox.sh +175 -0
  67. package/scripts/validate-job-docs.mjs +125 -0
  68. package/server/abi/SwapHelper.json +438 -0
  69. package/server/cli/approval.ts +447 -0
  70. package/server/cli/commands/app.ts +204 -0
  71. package/server/cli/commands/cron.ts +24 -0
  72. package/server/cli/commands/doctor.ts +1007 -0
  73. package/server/cli/commands/env.ts +456 -0
  74. package/server/cli/commands/init.ts +752 -0
  75. package/server/cli/commands/mcp.ts +125 -0
  76. package/server/cli/commands/restore.ts +314 -0
  77. package/server/cli/commands/shell-hook.ts +468 -0
  78. package/server/cli/commands/start.ts +62 -0
  79. package/server/cli/commands/status.ts +59 -0
  80. package/server/cli/commands/stop.ts +14 -0
  81. package/server/cli/commands/token.ts +180 -0
  82. package/server/cli/commands/unlock.ts +49 -0
  83. package/server/cli/commands/vault.ts +417 -0
  84. package/server/cli/index.ts +328 -0
  85. package/server/cli/lib/aura-parser.ts +64 -0
  86. package/server/cli/lib/credential-create.ts +74 -0
  87. package/server/cli/lib/credential-resolve.ts +254 -0
  88. package/server/cli/lib/dotenv-migrate.ts +116 -0
  89. package/server/cli/lib/dotenv-parser.ts +146 -0
  90. package/server/cli/lib/http.ts +91 -0
  91. package/server/cli/lib/init-steps.ts +76 -0
  92. package/server/cli/lib/local-agent-trust.ts +45 -0
  93. package/server/cli/lib/process.ts +136 -0
  94. package/server/cli/lib/prompt.ts +85 -0
  95. package/server/cli/lib/theme.ts +240 -0
  96. package/server/cli/socket.ts +570 -0
  97. package/server/cli/transport-client.ts +50 -0
  98. package/server/cron/index.ts +137 -0
  99. package/server/cron/job.ts +31 -0
  100. package/server/cron/jobs/balance-sync.ts +436 -0
  101. package/server/cron/jobs/incoming-scan.ts +506 -0
  102. package/server/cron/jobs/native-price.ts +70 -0
  103. package/server/cron/jobs/orphan-cleanup.ts +40 -0
  104. package/server/cron/jobs/strategy-runner.ts +175 -0
  105. package/server/cron/scheduler.ts +125 -0
  106. package/server/index.ts +406 -0
  107. package/server/lib/adapters/factory.ts +110 -0
  108. package/server/lib/adapters/index.ts +19 -0
  109. package/server/lib/adapters/router.ts +297 -0
  110. package/server/lib/adapters/telegram.ts +645 -0
  111. package/server/lib/adapters/types.ts +89 -0
  112. package/server/lib/adapters/webhook.ts +95 -0
  113. package/server/lib/address.ts +49 -0
  114. package/server/lib/agent-auth/contracts.ts +1194 -0
  115. package/server/lib/agent-profiles.ts +328 -0
  116. package/server/lib/ai.ts +285 -0
  117. package/server/lib/api-registry/contracts.ts +86 -0
  118. package/server/lib/api-registry/validation.ts +172 -0
  119. package/server/lib/apikey-migration.ts +189 -0
  120. package/server/lib/app-installer.ts +505 -0
  121. package/server/lib/app-tokens.ts +247 -0
  122. package/server/lib/auth.ts +314 -0
  123. package/server/lib/batch.ts +242 -0
  124. package/server/lib/cold.ts +874 -0
  125. package/server/lib/config.ts +381 -0
  126. package/server/lib/credential-access-audit.ts +85 -0
  127. package/server/lib/credential-access-policy.ts +110 -0
  128. package/server/lib/credential-health.ts +343 -0
  129. package/server/lib/credential-import.ts +487 -0
  130. package/server/lib/credential-scope.ts +87 -0
  131. package/server/lib/credential-shares.ts +190 -0
  132. package/server/lib/credential-transport.ts +342 -0
  133. package/server/lib/credential-vault.ts +77 -0
  134. package/server/lib/credentials.ts +333 -0
  135. package/server/lib/crypto.ts +8 -0
  136. package/server/lib/db.ts +15 -0
  137. package/server/lib/defaults.ts +366 -0
  138. package/server/lib/dex/index.ts +80 -0
  139. package/server/lib/dex/relay.ts +235 -0
  140. package/server/lib/dex/types.ts +59 -0
  141. package/server/lib/dex/uniswap.ts +370 -0
  142. package/server/lib/e2e-agent/artifacts.ts +36 -0
  143. package/server/lib/e2e-agent/contracts.ts +112 -0
  144. package/server/lib/e2e-agent/validation.ts +135 -0
  145. package/server/lib/encrypt.ts +128 -0
  146. package/server/lib/error.ts +20 -0
  147. package/server/lib/events.ts +205 -0
  148. package/server/lib/hot.ts +357 -0
  149. package/server/lib/key-fingerprint.ts +28 -0
  150. package/server/lib/logger.ts +331 -0
  151. package/server/lib/network.ts +137 -0
  152. package/server/lib/notifications.ts +219 -0
  153. package/server/lib/oauth2-refresh.ts +241 -0
  154. package/server/lib/oursecret.ts +54 -0
  155. package/server/lib/passkey-credential.ts +360 -0
  156. package/server/lib/passkey.ts +68 -0
  157. package/server/lib/permissions.ts +248 -0
  158. package/server/lib/pino.ts +24 -0
  159. package/server/lib/policy-preview.ts +138 -0
  160. package/server/lib/price.ts +338 -0
  161. package/server/lib/prices.ts +34 -0
  162. package/server/lib/project-scope.ts +239 -0
  163. package/server/lib/resolve-action.ts +427 -0
  164. package/server/lib/resolve.ts +36 -0
  165. package/server/lib/sessions.ts +632 -0
  166. package/server/lib/solana/connection.ts +26 -0
  167. package/server/lib/solana/jupiter.ts +128 -0
  168. package/server/lib/solana/transfer.ts +108 -0
  169. package/server/lib/solana/wallet.ts +136 -0
  170. package/server/lib/strategy/emits.ts +21 -0
  171. package/server/lib/strategy/engine.ts +1305 -0
  172. package/server/lib/strategy/executor.ts +115 -0
  173. package/server/lib/strategy/hook-context.ts +158 -0
  174. package/server/lib/strategy/hooks.ts +990 -0
  175. package/server/lib/strategy/index.ts +28 -0
  176. package/server/lib/strategy/installer.ts +305 -0
  177. package/server/lib/strategy/loader.ts +256 -0
  178. package/server/lib/strategy/message.ts +235 -0
  179. package/server/lib/strategy/repository.ts +218 -0
  180. package/server/lib/strategy/session-logger.ts +693 -0
  181. package/server/lib/strategy/sources.ts +288 -0
  182. package/server/lib/strategy/state.ts +189 -0
  183. package/server/lib/strategy/templates.ts +403 -0
  184. package/server/lib/strategy/tick.ts +404 -0
  185. package/server/lib/strategy/types.ts +230 -0
  186. package/server/lib/swap.ts +3 -0
  187. package/server/lib/temp.ts +86 -0
  188. package/server/lib/token-metadata.ts +86 -0
  189. package/server/lib/token-safety.ts +200 -0
  190. package/server/lib/token-search.ts +444 -0
  191. package/server/lib/totp.ts +194 -0
  192. package/server/lib/transactions.ts +123 -0
  193. package/server/lib/transport.ts +75 -0
  194. package/server/lib/txhistory/decoder.ts +262 -0
  195. package/server/lib/txhistory/enricher.ts +652 -0
  196. package/server/lib/txhistory/index.ts +391 -0
  197. package/server/lib/txhistory/signatures.ts +59 -0
  198. package/server/lib/verified-summary.ts +421 -0
  199. package/server/mcp/profile-policy.ts +30 -0
  200. package/server/mcp/server.ts +619 -0
  201. package/server/mcp/tools.ts +523 -0
  202. package/server/middleware/auth.ts +119 -0
  203. package/server/middleware/requestLogger.ts +84 -0
  204. package/server/routes/actions.ts +459 -0
  205. package/server/routes/adapters.ts +703 -0
  206. package/server/routes/addressbook.ts +113 -0
  207. package/server/routes/ai.ts +34 -0
  208. package/server/routes/apikeys.ts +295 -0
  209. package/server/routes/apps.ts +601 -0
  210. package/server/routes/auth.ts +457 -0
  211. package/server/routes/backup.ts +340 -0
  212. package/server/routes/batch.ts +270 -0
  213. package/server/routes/bookmarks.ts +162 -0
  214. package/server/routes/credential-shares.ts +198 -0
  215. package/server/routes/credential-vaults.ts +154 -0
  216. package/server/routes/credentials.ts +1290 -0
  217. package/server/routes/dashboard.ts +71 -0
  218. package/server/routes/defaults.ts +124 -0
  219. package/server/routes/fund.ts +229 -0
  220. package/server/routes/import.ts +352 -0
  221. package/server/routes/launch.ts +665 -0
  222. package/server/routes/lock.ts +54 -0
  223. package/server/routes/logs.ts +68 -0
  224. package/server/routes/nuke.ts +111 -0
  225. package/server/routes/passkey-credentials.ts +99 -0
  226. package/server/routes/passkey.ts +346 -0
  227. package/server/routes/portfolio.ts +217 -0
  228. package/server/routes/price.ts +63 -0
  229. package/server/routes/resolve.ts +31 -0
  230. package/server/routes/security.ts +45 -0
  231. package/server/routes/send-evm.ts +241 -0
  232. package/server/routes/send-solana.ts +281 -0
  233. package/server/routes/send.ts +178 -0
  234. package/server/routes/setup.ts +210 -0
  235. package/server/routes/strategy.ts +894 -0
  236. package/server/routes/swap-evm.ts +353 -0
  237. package/server/routes/swap-solana.ts +177 -0
  238. package/server/routes/swap.ts +356 -0
  239. package/server/routes/token.ts +247 -0
  240. package/server/routes/unlock.ts +403 -0
  241. package/server/routes/wallet-assets.ts +361 -0
  242. package/server/routes/wallet-transactions.ts +515 -0
  243. package/server/routes/wallet.ts +710 -0
  244. package/server/types.ts +146 -0
  245. package/skills/aurawallet/SKILL.md +739 -0
  246. package/skills/aurawallet-setup/SKILL.md +74 -0
  247. package/skills/security-review/SKILL.md +148 -0
  248. package/src/app/api/agent-requests/route.ts +30 -0
  249. package/src/app/api/apps/install/route.ts +126 -0
  250. package/src/app/api/apps/manifests/route.ts +16 -0
  251. package/src/app/api/apps/static/[...path]/route.ts +57 -0
  252. package/src/app/api/events/route.ts +92 -0
  253. package/src/app/api/page.tsx +212 -0
  254. package/src/app/api/workspace/[id]/apps/[wid]/route.ts +119 -0
  255. package/src/app/api/workspace/[id]/apps/route.ts +81 -0
  256. package/src/app/api/workspace/[id]/export/route.ts +67 -0
  257. package/src/app/api/workspace/[id]/route.ts +168 -0
  258. package/src/app/api/workspace/auth.ts +34 -0
  259. package/src/app/api/workspace/config/route.ts +106 -0
  260. package/src/app/api/workspace/import/route.ts +127 -0
  261. package/src/app/api/workspace/route.ts +116 -0
  262. package/src/app/app/page.tsx +2122 -0
  263. package/src/app/apple-icon.png +0 -0
  264. package/src/app/docs/page.tsx +178 -0
  265. package/src/app/favicon.ico +0 -0
  266. package/src/app/globals.css +572 -0
  267. package/src/app/health/page.tsx +5 -0
  268. package/src/app/hello/page.tsx +15 -0
  269. package/src/app/icon.png +0 -0
  270. package/src/app/layout.tsx +34 -0
  271. package/src/app/page.tsx +986 -0
  272. package/src/app/providers.tsx +90 -0
  273. package/src/app/share/[token]/page.tsx +295 -0
  274. package/src/components/ChainSelector.tsx +144 -0
  275. package/src/components/HumanActionBar.tsx +695 -0
  276. package/src/components/NotificationDrawer.tsx +129 -0
  277. package/src/components/apps/AgentKeysApp.tsx +490 -0
  278. package/src/components/apps/App.tsx +153 -0
  279. package/src/components/apps/AppGrid.tsx +15 -0
  280. package/src/components/apps/DetailedAddressDrawer.tsx +325 -0
  281. package/src/components/apps/DraggableApp.tsx +562 -0
  282. package/src/components/apps/IFrameApp.tsx +73 -0
  283. package/src/components/apps/LogsApp.tsx +360 -0
  284. package/src/components/apps/SendApp.tsx +394 -0
  285. package/src/components/apps/SetupWizardApp.tsx +1004 -0
  286. package/src/components/apps/SystemDefaultsApp.tsx +845 -0
  287. package/src/components/apps/ThirdPartyApp.tsx +428 -0
  288. package/src/components/apps/TokenApp.tsx +319 -0
  289. package/src/components/apps/TransactionsApp.tsx +438 -0
  290. package/src/components/apps/WalletDetailApp.tsx +1505 -0
  291. package/src/components/apps/index.ts +13 -0
  292. package/src/components/design-system/Button.tsx +53 -0
  293. package/src/components/design-system/ChainIndicator.tsx +65 -0
  294. package/src/components/design-system/ChainSelector.tsx +137 -0
  295. package/src/components/design-system/ConfirmationModal.tsx +106 -0
  296. package/src/components/design-system/ConfirmationPopover.tsx +81 -0
  297. package/src/components/design-system/Drawer.tsx +123 -0
  298. package/src/components/design-system/FilterDropdown.tsx +72 -0
  299. package/src/components/design-system/Modal.tsx +206 -0
  300. package/src/components/design-system/Popover.tsx +142 -0
  301. package/src/components/design-system/TextInput.tsx +85 -0
  302. package/src/components/design-system/Toggle.tsx +58 -0
  303. package/src/components/design-system/index.ts +11 -0
  304. package/src/components/docs/DocsThemeToggle.tsx +49 -0
  305. package/src/components/health/CredentialHealthDashboard.tsx +214 -0
  306. package/src/components/icons/ChainIcons.tsx +72 -0
  307. package/src/components/layout/AppStoreDrawer.tsx +369 -0
  308. package/src/components/layout/ContentArea.tsx +21 -0
  309. package/src/components/layout/TabBar.tsx +278 -0
  310. package/src/components/layout/WalletSidebar.tsx +1033 -0
  311. package/src/components/layout/index.ts +4 -0
  312. package/src/components/marketing/AuraWalletSpecOverlay.tsx +635 -0
  313. package/src/components/marketing/DeviceMorphExperience.tsx +216 -0
  314. package/src/components/vault/ApiKeysConsole.tsx +1080 -0
  315. package/src/components/vault/AuditConsole.tsx +584 -0
  316. package/src/components/vault/CredentialDetail.tsx +455 -0
  317. package/src/components/vault/CredentialEmpty.tsx +55 -0
  318. package/src/components/vault/CredentialField.tsx +361 -0
  319. package/src/components/vault/CredentialForm.tsx +1212 -0
  320. package/src/components/vault/CredentialList.tsx +165 -0
  321. package/src/components/vault/CredentialRow.tsx +97 -0
  322. package/src/components/vault/CredentialShareModal.tsx +178 -0
  323. package/src/components/vault/CredentialVault.tsx +754 -0
  324. package/src/components/vault/CredentialWalletWidget.tsx +103 -0
  325. package/src/components/vault/ImportCredentialsModal.tsx +515 -0
  326. package/src/components/vault/LargeTypeModal.tsx +64 -0
  327. package/src/components/vault/PasswordGenerator.tsx +224 -0
  328. package/src/components/vault/TOTPDisplay.tsx +123 -0
  329. package/src/components/vault/VaultSidebar.tsx +413 -0
  330. package/src/components/vault/types.ts +54 -0
  331. package/src/context/AuthContext.tsx +337 -0
  332. package/src/context/PriceContext.tsx +113 -0
  333. package/src/context/ThemeContext.tsx +164 -0
  334. package/src/context/WebSocketContext.tsx +269 -0
  335. package/src/context/WorkspaceContext.tsx +668 -0
  336. package/src/hooks/index.ts +3 -0
  337. package/src/hooks/useAgentActions.ts +368 -0
  338. package/src/hooks/useBalance.ts +103 -0
  339. package/src/hooks/useBalances.ts +129 -0
  340. package/src/instrumentation.ts +12 -0
  341. package/src/lib/api.ts +449 -0
  342. package/src/lib/app-loader.ts +148 -0
  343. package/src/lib/app-registry.ts +178 -0
  344. package/src/lib/app-sdk.ts +157 -0
  345. package/src/lib/audit-console-adapter.ts +151 -0
  346. package/src/lib/auth-client.ts +75 -0
  347. package/src/lib/config.ts +74 -0
  348. package/src/lib/crypto.ts +112 -0
  349. package/src/lib/db.ts +21 -0
  350. package/src/lib/docs.ts +390 -0
  351. package/src/lib/events.ts +361 -0
  352. package/src/lib/pino.ts +24 -0
  353. package/src/lib/theme-handlers.ts +168 -0
  354. package/src/lib/theme.ts +351 -0
  355. package/src/lib/tokenData.ts +378 -0
  356. package/src/lib/vault-crypto.ts +129 -0
  357. package/src/lib/websocket-server.ts +302 -0
  358. package/src/lib/websocket-setup.ts +79 -0
  359. package/src/lib/wordlist.ts +2050 -0
  360. package/src/lib/workspace-handlers.ts +285 -0
  361. package/start.sh +80 -0
  362. package/tailwind.config.ts +99 -0
  363. package/tsconfig.json +42 -0
@@ -0,0 +1,360 @@
1
+ /**
2
+ * Passkey Credential Operations — Software WebAuthn Authenticator
3
+ * ===============================================================
4
+ *
5
+ * Generates ECDSA P-256 keypairs, builds WebAuthn attestation/assertion objects,
6
+ * and stores passkey credentials in the encrypted vault.
7
+ */
8
+
9
+ import crypto from 'crypto';
10
+ import { encode as cborEncode } from 'cbor-x';
11
+ import {
12
+ createCredential,
13
+ listCredentials,
14
+ getCredential,
15
+ readCredentialSecrets,
16
+ updateCredential,
17
+ } from './credentials';
18
+ import { CredentialField } from '../types';
19
+
20
+ // Our software authenticator AAGUID (random, unique to Aura)
21
+ const AURA_AAGUID = Buffer.from('a0a1a2a3a4a5a6a7a8a9aaabacadaeaf', 'hex');
22
+
23
+ const PASSKEY_CHALLENGE_TTL_MS = 120_000;
24
+ const usedChallenges = new Map<string, number>();
25
+
26
+ // ---------------------------------------------------------------------------
27
+ // Helpers
28
+ // ---------------------------------------------------------------------------
29
+
30
+ export class PasskeyCredentialValidationError extends Error {
31
+ constructor(message: string) {
32
+ super(message);
33
+ this.name = 'PasskeyCredentialValidationError';
34
+ }
35
+ }
36
+
37
+ function base64urlEncode(buf: Buffer | Uint8Array): string {
38
+ return Buffer.from(buf).toString('base64url');
39
+ }
40
+
41
+ function base64urlDecode(str: string): Buffer {
42
+ return Buffer.from(str, 'base64url');
43
+ }
44
+
45
+ function sha256(data: Buffer | string): Buffer {
46
+ return crypto.createHash('sha256').update(data).digest();
47
+ }
48
+
49
+ function cleanupUsedChallenges(now = Date.now()): void {
50
+ for (const [challenge, expiresAt] of usedChallenges.entries()) {
51
+ if (expiresAt <= now) {
52
+ usedChallenges.delete(challenge);
53
+ }
54
+ }
55
+ }
56
+
57
+ function consumeFreshChallenge(challenge: string): void {
58
+ const now = Date.now();
59
+ cleanupUsedChallenges(now);
60
+
61
+ const key = challenge.trim();
62
+ if (!key) {
63
+ throw new PasskeyCredentialValidationError('challenge is required');
64
+ }
65
+
66
+ if (usedChallenges.has(key)) {
67
+ throw new PasskeyCredentialValidationError('challenge replay detected');
68
+ }
69
+
70
+ usedChallenges.set(key, now + PASSKEY_CHALLENGE_TTL_MS);
71
+ }
72
+
73
+ export function _resetPasskeyCredentialChallengeStoreForTests(): void {
74
+ usedChallenges.clear();
75
+ }
76
+
77
+ function validateOriginPolicy(origin: string, rpId: string): void {
78
+ let parsed: URL;
79
+ try {
80
+ parsed = new URL(origin);
81
+ } catch {
82
+ throw new PasskeyCredentialValidationError('clientDataJSON.origin must be a valid URL');
83
+ }
84
+
85
+ const host = parsed.hostname.toLowerCase();
86
+ const normalizedRpId = rpId.toLowerCase();
87
+ const isRpMatch = host === normalizedRpId || host.endsWith(`.${normalizedRpId}`);
88
+ if (!isRpMatch) {
89
+ throw new PasskeyCredentialValidationError('clientDataJSON.origin does not match rpId');
90
+ }
91
+
92
+ const isLocalhost = host === 'localhost' || host.endsWith('.localhost');
93
+ const allowedHttp = parsed.protocol === 'http:' && isLocalhost;
94
+ if (parsed.protocol !== 'https:' && !allowedHttp) {
95
+ throw new PasskeyCredentialValidationError('clientDataJSON.origin must be https (except localhost)');
96
+ }
97
+ }
98
+
99
+ function parseAndValidateClientDataJSON(params: {
100
+ clientDataJSON: string;
101
+ expectedType: 'webauthn.create' | 'webauthn.get';
102
+ expectedChallenge: string;
103
+ rpId: string;
104
+ expectedOrigin?: string;
105
+ }): { challenge: string; origin: string } {
106
+ let parsed: Record<string, unknown>;
107
+
108
+ try {
109
+ const raw = base64urlDecode(params.clientDataJSON).toString('utf8');
110
+ parsed = JSON.parse(raw) as Record<string, unknown>;
111
+ } catch {
112
+ throw new PasskeyCredentialValidationError('clientDataJSON must be valid base64url-encoded JSON');
113
+ }
114
+
115
+ const type = parsed.type;
116
+ const challenge = parsed.challenge;
117
+ const origin = parsed.origin;
118
+
119
+ if (type !== params.expectedType) {
120
+ throw new PasskeyCredentialValidationError(`clientDataJSON.type must be ${params.expectedType}`);
121
+ }
122
+
123
+ if (typeof challenge !== 'string' || challenge !== params.expectedChallenge) {
124
+ throw new PasskeyCredentialValidationError('clientDataJSON.challenge mismatch');
125
+ }
126
+
127
+ if (typeof origin !== 'string' || !origin.trim()) {
128
+ throw new PasskeyCredentialValidationError('clientDataJSON.origin is required');
129
+ }
130
+
131
+ validateOriginPolicy(origin, params.rpId);
132
+
133
+ if (params.expectedOrigin && origin !== params.expectedOrigin) {
134
+ throw new PasskeyCredentialValidationError('clientDataJSON.origin mismatch');
135
+ }
136
+
137
+ return { challenge, origin };
138
+ }
139
+
140
+ /**
141
+ * Encode an EC public key in COSE_Key format (ECDSA P-256).
142
+ * See RFC 8152 Section 13.1.1
143
+ */
144
+ function encodeCosePublicKey(publicKeyDer: Buffer): Buffer {
145
+ const raw = crypto.createPublicKey({ key: publicKeyDer, format: 'der', type: 'spki' })
146
+ .export({ format: 'jwk' });
147
+
148
+ const x = base64urlDecode(raw.x!);
149
+ const y = base64urlDecode(raw.y!);
150
+
151
+ const coseKey = new Map<number, number | Buffer>();
152
+ coseKey.set(1, 2); // kty: EC2
153
+ coseKey.set(3, -7); // alg: ES256
154
+ coseKey.set(-1, 1); // crv: P-256
155
+ coseKey.set(-2, x); // x coordinate
156
+ coseKey.set(-3, y); // y coordinate
157
+
158
+ return Buffer.from(cborEncode(coseKey));
159
+ }
160
+
161
+ // ---------------------------------------------------------------------------
162
+ // Registration (navigator.credentials.create)
163
+ // ---------------------------------------------------------------------------
164
+
165
+ export interface PasskeyRegisterOptions {
166
+ vaultId: string;
167
+ rpId: string;
168
+ rpName?: string;
169
+ userName?: string;
170
+ displayName?: string;
171
+ userHandle: string; // base64url
172
+ challenge: string; // base64url — from clientDataJSON
173
+ origin: string;
174
+ clientDataJSON: string; // base64url — raw from browser
175
+ }
176
+
177
+ export interface PasskeyRegisterResult {
178
+ credentialId: string; // base64url
179
+ attestationObject: string; // base64url
180
+ clientDataJSON: string; // base64url (pass-through)
181
+ publicKey: string; // base64url (SPKI DER)
182
+ publicKeyCose: string; // base64url (COSE)
183
+ transports: string[];
184
+ auraCredentialId: string; // internal cred-xxx id
185
+ }
186
+
187
+ export function registerPasskey(opts: PasskeyRegisterOptions): PasskeyRegisterResult {
188
+ parseAndValidateClientDataJSON({
189
+ clientDataJSON: opts.clientDataJSON,
190
+ expectedType: 'webauthn.create',
191
+ expectedChallenge: opts.challenge,
192
+ rpId: opts.rpId,
193
+ expectedOrigin: opts.origin,
194
+ });
195
+ consumeFreshChallenge(opts.challenge);
196
+
197
+ const { publicKey, privateKey } = crypto.generateKeyPairSync('ec', {
198
+ namedCurve: 'prime256v1',
199
+ publicKeyEncoding: { type: 'spki', format: 'der' },
200
+ privateKeyEncoding: { type: 'pkcs8', format: 'der' },
201
+ });
202
+
203
+ const credentialIdBuf = crypto.randomBytes(32);
204
+ const credentialId = base64urlEncode(credentialIdBuf);
205
+
206
+ const cosePublicKey = encodeCosePublicKey(publicKey as Buffer);
207
+
208
+ const rpIdHash = sha256(opts.rpId);
209
+ const flags = Buffer.from([0x45]); // UP + UV + AT
210
+ const signCount = Buffer.alloc(4);
211
+
212
+ const credIdLenBuf = Buffer.alloc(2);
213
+ credIdLenBuf.writeUInt16BE(credentialIdBuf.length);
214
+
215
+ const authData = Buffer.concat([
216
+ rpIdHash,
217
+ flags,
218
+ signCount,
219
+ AURA_AAGUID,
220
+ credIdLenBuf,
221
+ credentialIdBuf,
222
+ cosePublicKey,
223
+ ]);
224
+
225
+ const attestationObject = cborEncode({
226
+ fmt: 'none',
227
+ attStmt: {},
228
+ authData,
229
+ });
230
+
231
+ const sensitiveFields: CredentialField[] = [
232
+ { key: 'privateKey', value: base64urlEncode(privateKey as Buffer), type: 'secret', sensitive: true },
233
+ ];
234
+
235
+ const meta: Record<string, unknown> = {
236
+ rpId: opts.rpId,
237
+ rpName: opts.rpName || opts.rpId,
238
+ credentialId,
239
+ publicKey: base64urlEncode(publicKey as Buffer),
240
+ publicKeyCose: base64urlEncode(cosePublicKey),
241
+ userHandle: opts.userHandle,
242
+ userName: opts.userName || '',
243
+ displayName: opts.displayName || '',
244
+ signCount: 0,
245
+ transports: ['internal'],
246
+ discoverable: true,
247
+ };
248
+
249
+ const name = `${opts.rpId} — ${opts.displayName || opts.userName || 'passkey'}`;
250
+ const cred = createCredential(opts.vaultId, 'passkey', name, meta, sensitiveFields);
251
+
252
+ return {
253
+ credentialId,
254
+ attestationObject: base64urlEncode(Buffer.from(attestationObject)),
255
+ clientDataJSON: opts.clientDataJSON,
256
+ publicKey: base64urlEncode(publicKey as Buffer),
257
+ publicKeyCose: base64urlEncode(cosePublicKey),
258
+ transports: ['internal'],
259
+ auraCredentialId: cred.id,
260
+ };
261
+ }
262
+
263
+ // ---------------------------------------------------------------------------
264
+ // Authentication (navigator.credentials.get)
265
+ // ---------------------------------------------------------------------------
266
+
267
+ export interface PasskeyAuthOptions {
268
+ auraCredentialId: string; // internal cred-xxx id
269
+ rpId: string;
270
+ challenge: string;
271
+ origin?: string;
272
+ clientDataJSON: string; // base64url — raw from browser
273
+ }
274
+
275
+ export interface PasskeyAuthResult {
276
+ credentialId: string; // base64url
277
+ authenticatorData: string; // base64url
278
+ signature: string; // base64url
279
+ userHandle: string; // base64url
280
+ }
281
+
282
+ export function authenticatePasskey(opts: PasskeyAuthOptions): PasskeyAuthResult {
283
+ parseAndValidateClientDataJSON({
284
+ clientDataJSON: opts.clientDataJSON,
285
+ expectedType: 'webauthn.get',
286
+ expectedChallenge: opts.challenge,
287
+ rpId: opts.rpId,
288
+ expectedOrigin: opts.origin,
289
+ });
290
+ consumeFreshChallenge(opts.challenge);
291
+
292
+ const cred = getCredential(opts.auraCredentialId);
293
+ if (!cred || cred.type !== 'passkey') {
294
+ throw new Error('Passkey credential not found');
295
+ }
296
+
297
+ if (cred.meta.rpId !== opts.rpId) {
298
+ throw new Error('rpId mismatch');
299
+ }
300
+
301
+ const secrets = readCredentialSecrets(opts.auraCredentialId);
302
+ const privateKeyField = secrets.find(f => f.key === 'privateKey');
303
+ if (!privateKeyField) {
304
+ throw new Error('Private key not found in credential');
305
+ }
306
+
307
+ const privateKeyDer = base64urlDecode(privateKeyField.value);
308
+ const privateKey = crypto.createPrivateKey({ key: privateKeyDer, format: 'der', type: 'pkcs8' });
309
+
310
+ const currentCount = (cred.meta.signCount as number) || 0;
311
+ const newCount = currentCount + 1;
312
+
313
+ const rpIdHash = sha256(opts.rpId);
314
+ const flags = Buffer.from([0x05]); // UP + UV
315
+ const signCountBuf = Buffer.alloc(4);
316
+ signCountBuf.writeUInt32BE(newCount);
317
+
318
+ const authenticatorData = Buffer.concat([rpIdHash, flags, signCountBuf]);
319
+
320
+ const clientDataHash = sha256(base64urlDecode(opts.clientDataJSON));
321
+ const signedData = Buffer.concat([authenticatorData, clientDataHash]);
322
+ const signature = crypto.sign('sha256', signedData, privateKey);
323
+
324
+ updateCredential(opts.auraCredentialId, {
325
+ meta: { ...cred.meta, signCount: newCount },
326
+ });
327
+
328
+ return {
329
+ credentialId: cred.meta.credentialId as string,
330
+ authenticatorData: base64urlEncode(authenticatorData),
331
+ signature: base64urlEncode(signature),
332
+ userHandle: (cred.meta.userHandle as string) || '',
333
+ };
334
+ }
335
+
336
+ // ---------------------------------------------------------------------------
337
+ // Match — find passkeys for an rpId
338
+ // ---------------------------------------------------------------------------
339
+
340
+ export interface PasskeyMatch {
341
+ auraCredentialId: string;
342
+ credentialId: string;
343
+ rpId: string;
344
+ userName: string;
345
+ displayName: string;
346
+ }
347
+
348
+ export function matchPasskeys(rpId: string, vaultId?: string): PasskeyMatch[] {
349
+ const creds = listCredentials({ type: 'passkey', vaultId });
350
+ return creds
351
+ .filter(c => c.meta.rpId === rpId)
352
+ .map(c => ({
353
+ auraCredentialId: c.id,
354
+ credentialId: c.meta.credentialId as string,
355
+ rpId: c.meta.rpId as string,
356
+ userName: (c.meta.userName as string) || '',
357
+ displayName: (c.meta.displayName as string) || '',
358
+ }))
359
+ .sort((a, b) => a.auraCredentialId.localeCompare(b.auraCredentialId));
360
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Passkey challenge store and helpers for WebAuthn biometric unlock.
3
+ * Challenges are in-memory, single-use, with 60s TTL.
4
+ */
5
+
6
+ const CHALLENGE_TTL_MS = 60_000;
7
+
8
+ interface PendingChallenge {
9
+ type: 'register' | 'authenticate';
10
+ expiresAt: number;
11
+ }
12
+
13
+ const challenges = new Map<string, PendingChallenge>();
14
+
15
+ // Cleanup expired challenges periodically
16
+ let cleanupTimer: ReturnType<typeof setInterval> | null = null;
17
+
18
+ function ensureCleanup() {
19
+ if (cleanupTimer) return;
20
+ cleanupTimer = setInterval(() => {
21
+ const now = Date.now();
22
+ for (const [key, val] of challenges) {
23
+ if (val.expiresAt <= now) challenges.delete(key);
24
+ }
25
+ if (challenges.size === 0 && cleanupTimer) {
26
+ clearInterval(cleanupTimer);
27
+ cleanupTimer = null;
28
+ }
29
+ }, 30_000);
30
+ // Don't keep process alive for cleanup
31
+ if (cleanupTimer && typeof cleanupTimer === 'object' && 'unref' in cleanupTimer) {
32
+ cleanupTimer.unref();
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Store a challenge for later verification.
38
+ */
39
+ export function storeChallenge(challenge: string, type: 'register' | 'authenticate'): void {
40
+ challenges.set(challenge, { type, expiresAt: Date.now() + CHALLENGE_TTL_MS });
41
+ ensureCleanup();
42
+ }
43
+
44
+ /**
45
+ * Consume a challenge (single-use). Returns true if valid and not expired.
46
+ */
47
+ export function consumeChallenge(challenge: string, type: 'register' | 'authenticate'): boolean {
48
+ const entry = challenges.get(challenge);
49
+ if (!entry) return false;
50
+ challenges.delete(challenge);
51
+ if (entry.type !== type) return false;
52
+ if (entry.expiresAt <= Date.now()) return false;
53
+ return true;
54
+ }
55
+
56
+ /**
57
+ * Convert base64url string to Uint8Array.
58
+ */
59
+ export function base64urlToUint8Array(base64url: string): Uint8Array {
60
+ return Buffer.from(base64url, 'base64url');
61
+ }
62
+
63
+ /**
64
+ * Convert Uint8Array (or Buffer) to base64url string.
65
+ */
66
+ export function uint8ArrayToBase64url(bytes: Uint8Array | Buffer): string {
67
+ return Buffer.from(bytes).toString('base64url');
68
+ }
@@ -0,0 +1,248 @@
1
+ import { Request, Response, NextFunction } from 'express';
2
+ import { AgentTokenPayload } from '../types';
3
+
4
+ /**
5
+ * PERMISSION SYSTEM
6
+ * =================
7
+ *
8
+ * Permission strings control access to specific routes and operations.
9
+ * Admin tokens bypass all permission checks.
10
+ */
11
+
12
+ // All valid permission strings
13
+ export type Permission =
14
+ // Wallet operations
15
+ | 'wallet:list' // List/view wallets
16
+ | 'wallet:create:hot' // Create hot wallets
17
+ | 'wallet:create:temp' // Create temp wallets
18
+ | 'wallet:rename' // Rename wallets
19
+ | 'wallet:export' // Export private keys
20
+ | 'wallet:tx:add' // Manually add transactions to wallet history
21
+ | 'wallet:asset:add' // Add assets to track for a wallet
22
+ | 'wallet:asset:remove' // Remove tracked assets from a wallet
23
+
24
+ // Transaction operations
25
+ | 'send:hot' // Send from hot wallets
26
+ | 'send:temp' // Send from temp wallets
27
+ | 'swap' // Execute swaps
28
+ | 'fund' // Cold→hot transfers
29
+ | 'launch' // Execute token launches
30
+
31
+ // API Key operations
32
+ | 'apikey:get' // Read API keys
33
+ | 'apikey:set' // Create/update/delete API keys
34
+
35
+ // Workspace/UI operations
36
+ | 'workspace:modify' // Add/update/remove apps, modify workspaces
37
+
38
+ // Strategy operations
39
+ | 'strategy:read' // View strategies and their state
40
+ | 'strategy:manage' // Enable/disable strategies, update config
41
+
42
+ // App operations
43
+ | 'app:storage' // Read/write own app's storage via Express API
44
+ | 'app:storage:all' // Read/write ANY app's storage via Express API
45
+ | 'app:accesskey' // Read API keys from app storage
46
+
47
+ // Action operations
48
+ | 'action:create' // Create human action requests (propose actions for approval)
49
+ | 'action:read' // Read/list pending actions
50
+ | 'action:resolve' // Approve or reject pending actions
51
+
52
+ // Adapter operations
53
+ | 'adapter:manage' // Configure and restart approval adapters (Telegram, webhooks)
54
+
55
+ // Address book & bookmark operations
56
+ | 'addressbook:write' // Create/update/delete address labels
57
+ | 'bookmark:write' // Create/delete token bookmarks
58
+
59
+ // Credential vault operations
60
+ | 'secret:read' // Read credentials from the vault
61
+ | 'secret:write' // Create/update/delete credentials in the vault
62
+ | 'totp:read' // Generate TOTP codes from credential secrets
63
+
64
+ // Compound permissions (expand to multiple permissions)
65
+ | 'trade:all' // All trading permissions + apikey:get + strategy:read
66
+ | 'wallet:write' // All wallet write operations
67
+ | 'extension:*' // Browser extension permissions
68
+
69
+ // Admin (UI only)
70
+ | 'admin:*'; // All permissions, bypass limits
71
+
72
+ /**
73
+ * Compound permission mappings
74
+ * These permissions expand to multiple underlying permissions
75
+ */
76
+ const COMPOUND_PERMISSIONS: Record<string, Permission[]> = {
77
+ 'trade:all': [
78
+ 'wallet:list',
79
+ 'wallet:create:hot',
80
+ 'wallet:create:temp',
81
+ 'send:hot',
82
+ 'send:temp',
83
+ 'swap',
84
+ 'fund',
85
+ 'launch',
86
+ 'apikey:get',
87
+ 'strategy:read'
88
+ ],
89
+ 'extension:*': [
90
+ 'wallet:list',
91
+ 'secret:read',
92
+ 'action:read',
93
+ 'action:resolve'
94
+ ],
95
+ 'wallet:write': [
96
+ 'wallet:create:hot',
97
+ 'wallet:create:temp',
98
+ 'wallet:rename',
99
+ 'wallet:tx:add',
100
+ 'wallet:asset:add',
101
+ 'wallet:asset:remove'
102
+ ]
103
+ };
104
+
105
+ /**
106
+ * Expand and normalize permissions array
107
+ * - Expands compound permissions (e.g., 'trade:all' → multiple permissions)
108
+ * - Deduplicates the result
109
+ */
110
+ export function expandPermissions(permissions: string[]): Permission[] {
111
+ const expanded = new Set<Permission>();
112
+
113
+ for (const perm of permissions) {
114
+ // Check if this is a compound permission
115
+ if (COMPOUND_PERMISSIONS[perm]) {
116
+ for (const subPerm of COMPOUND_PERMISSIONS[perm]) {
117
+ expanded.add(subPerm);
118
+ }
119
+ }
120
+ // Always add the original permission too
121
+ expanded.add(perm as Permission);
122
+ }
123
+
124
+ return Array.from(expanded);
125
+ }
126
+
127
+ /**
128
+ * Get the list of compound permissions and what they expand to
129
+ */
130
+ export function getCompoundPermissions(): Record<string, Permission[]> {
131
+ return { ...COMPOUND_PERMISSIONS };
132
+ }
133
+
134
+ /**
135
+ * Check if token has ANY of the required permissions
136
+ * Automatically expands compound permissions before checking
137
+ */
138
+ export function hasAnyPermission(
139
+ tokenPermissions: string[],
140
+ requiredPermissions: string[]
141
+ ): boolean {
142
+ // Admin bypass
143
+ if (tokenPermissions.includes('admin:*')) {
144
+ return true;
145
+ }
146
+
147
+ // Expand compound permissions
148
+ const expanded = expandPermissions(tokenPermissions);
149
+
150
+ for (const required of requiredPermissions) {
151
+ if (expanded.includes(required as Permission)) {
152
+ return true;
153
+ }
154
+ }
155
+
156
+ return false;
157
+ }
158
+
159
+ /**
160
+ * Check if token has ALL of the required permissions
161
+ * Automatically expands compound permissions before checking
162
+ */
163
+ export function hasAllPermissions(
164
+ tokenPermissions: string[],
165
+ requiredPermissions: string[]
166
+ ): boolean {
167
+ // Admin bypass
168
+ if (tokenPermissions.includes('admin:*')) {
169
+ return true;
170
+ }
171
+
172
+ // Expand compound permissions
173
+ const expanded = expandPermissions(tokenPermissions);
174
+
175
+ for (const required of requiredPermissions) {
176
+ if (!expanded.includes(required as Permission)) {
177
+ return false;
178
+ }
179
+ }
180
+
181
+ return true;
182
+ }
183
+
184
+ /**
185
+ * Check if a token is an admin token (has admin:* permission)
186
+ */
187
+ export function isAdmin(auth: { token: { permissions: string[] } }): boolean {
188
+ return auth.token.permissions.includes('admin:*');
189
+ }
190
+
191
+ /**
192
+ * Middleware factory that requires specific permissions
193
+ *
194
+ * Usage:
195
+ * router.post('/create', requireWalletAuth, requirePermission('wallet:create:hot'), handler)
196
+ * router.post('/send', requireWalletAuth, requirePermission('send:hot', 'send:temp'), handler)
197
+ */
198
+ export function requirePermission(...permissions: string[]) {
199
+ return (req: Request, res: Response, next: NextFunction): void => {
200
+ if (!req.auth) {
201
+ res.status(401).json({ error: 'Authentication required' });
202
+ return;
203
+ }
204
+
205
+ // Check if token has any of the required permissions (admin:* bypasses via hasAnyPermission)
206
+ if (!hasAnyPermission(req.auth.token.permissions, permissions)) {
207
+ res.status(403).json({
208
+ error: 'Insufficient permissions',
209
+ required: permissions,
210
+ have: req.auth.token.permissions
211
+ });
212
+ return;
213
+ }
214
+
215
+ next();
216
+ };
217
+ }
218
+
219
+ /**
220
+ * Middleware that requires admin permission specifically
221
+ */
222
+ export function requireAdmin(req: Request, res: Response, next: NextFunction): void {
223
+ if (!req.auth) {
224
+ res.status(401).json({ error: 'Authentication required' });
225
+ return;
226
+ }
227
+
228
+ if (!isAdmin(req.auth)) {
229
+ res.status(403).json({ error: 'Admin access required' });
230
+ return;
231
+ }
232
+
233
+ next();
234
+ }
235
+
236
+ /**
237
+ * Get the permission required for a wallet tier operation
238
+ */
239
+ export function getWalletCreatePermission(tier: 'hot' | 'temp'): Permission {
240
+ return tier === 'hot' ? 'wallet:create:hot' : 'wallet:create:temp';
241
+ }
242
+
243
+ /**
244
+ * Get the permission required for sending from a wallet tier
245
+ */
246
+ export function getSendPermission(tier: 'hot' | 'temp'): Permission {
247
+ return tier === 'hot' ? 'send:hot' : 'send:temp';
248
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Shared Pino logger instance for the Express server (port 4242)
3
+ */
4
+
5
+ import pino from 'pino';
6
+
7
+ const isDev = process.env.NODE_ENV !== 'production';
8
+ const isTest = process.env.NODE_ENV === 'test' || process.env.VITEST === 'true';
9
+
10
+ export const log = pino({
11
+ level: isTest ? 'silent' : (process.env.LOG_LEVEL || 'debug'),
12
+ ...(isDev && !isTest
13
+ ? {
14
+ transport: {
15
+ target: 'pino-pretty',
16
+ options: {
17
+ colorize: true,
18
+ translateTime: 'HH:MM:ss.l',
19
+ ignore: 'pid,hostname',
20
+ },
21
+ },
22
+ }
23
+ : {}),
24
+ });