246 lines
51 KiB
Markdown
246 lines
51 KiB
Markdown
# Overnight Plan — Archy Refactoring & App Integration Hardening
|
|
|
|
> Make the Archy codebase rock-solid: fix all broken containers/iframes, perfect app installation/management/icons, get IndeedHub + Nostr signer flawless, and begin critical refactoring. No new features, no design changes. Bitcoin only.
|
|
> See `docs/refactoring-plan.md` for the full 3-year plan. See `CLAUDE.md` for all project rules and conventions.
|
|
> Deploy after every change: `./scripts/deploy-to-target.sh --live` — test at http://192.168.1.228
|
|
|
|
---
|
|
|
|
## Phase 1: Fix App Icon Consistency
|
|
|
|
- [x] **Fix PhotoPrism icon typo in backend metadata**: In `core/archipelago/src/container/docker_packages.rs`, the `get_app_metadata()` function references `photoprims.svg` (missing 'h') for the PhotoPrism icon. Search for `photoprims` and replace with `photoprism`. Verify the icon file exists at `neode-ui/public/assets/img/app-icons/photoprism.svg`. Run `cargo clippy --all-targets --all-features` in `core/` on the dev server after the fix.
|
|
|
|
- [ ] **Fix IndeedHub duplicate icon — consolidate to indeedhub.png**: Two icon files exist: `neode-ui/public/assets/img/app-icons/indeedhub.ico` and `indeehub.ico` (typo). Delete `indeehub.ico`. Convert `indeedhub.ico` to `indeedhub.png` (better format consistency). Update all references: (1) `neode-ui/src/utils/dummyApps.ts` line ~518 — change `indeehub.ico` to `indeedhub.png`, (2) `neode-ui/src/views/Marketplace.vue` line ~913 — change `indeehub.ico` to `indeedhub.png`, (3) `core/archipelago/src/container/docker_packages.rs` lines ~451-454 — change `indeehub.ico` to `indeedhub.png`. Search the entire codebase for `indeehub` (missing 'd') and fix all occurrences to `indeedhub`. Run `cd neode-ui && npm run type-check` to verify.
|
|
|
|
- [ ] **Audit all app icons match their references**: Cross-check every icon path referenced in `docker_packages.rs` `get_app_metadata()` against actual files in `neode-ui/public/assets/img/app-icons/`. Verify each app in the `Marketplace.vue` `getCuratedAppList()` function has an icon that exists. If any icon is missing, check if a similar-named file exists (e.g., wrong extension). Fix all mismatches. Remove orphaned icons that no app references (e.g., `atob.png`, `community-store.png`, `k484.png`, `lorabell.png`, `morphos.png` — verify they're truly unused first). Standardize: prefer `.png` or `.svg` over `.ico` and `.webp` where possible without changing existing working icons.
|
|
|
|
---
|
|
|
|
## Phase 2: Fix Container Crash Loops & Health
|
|
|
|
- [ ] **Diagnose and fix container networking DNS failures**: SSH to 192.168.1.228 (`sshpass -p 'EwPDR8q45l0Upx@' ssh -o StrictHostKeyChecking=no archipelago@192.168.1.228`). Run `sudo podman ps -a --format '{{.Names}} {{.Status}}' | grep -i restart` to identify containers in crash loops. The known issue is DNS resolution failures — containers can't resolve each other by name (e.g., mempool-web can't find mempool-api). Check if the `archy-net` Podman network exists: `sudo podman network ls`. If missing, create it: `sudo podman network create archy-net`. Reconnect all containers that need inter-container DNS to this network. Verify with `sudo podman exec archy-mempool-web ping mempool-api`. Restart affected containers and monitor for 2 minutes to confirm no more crash loops.
|
|
|
|
- [ ] **Fix .198 server swap and memory**: SSH to 192.168.1.198. Check current swap: `free -h`. If no swap configured, create a 4GB swap file: `sudo fallocate -l 4G /swapfile && sudo chmod 600 /swapfile && sudo mkswap /swapfile && sudo swapon /swapfile`. Add to `/etc/fstab`: `/swapfile none swap sw 0 0`. Verify with `free -h`. This prevents OOM kills that crash containers.
|
|
|
|
- [ ] **Stop and remove ollama container if not needed**: SSH to 192.168.1.228. Check ollama status: `sudo podman ps -a | grep ollama`. If it's in "Created" state and never started, remove it: `sudo podman rm ollama`. This frees a container slot and removes clutter from the app list. If the user has ollama in their installed apps, leave it but start it: `sudo podman start ollama`.
|
|
|
|
- [ ] **Verify all core Bitcoin containers are healthy**: SSH to 192.168.1.228. Check these containers are running and healthy: `bitcoin-knots`, `lnd`, `mempool-api`, `archy-mempool-web`, `mempool-electrs`, `btcpay-server`, `archy-nbxplorer`. Run `sudo podman ps --format '{{.Names}}\t{{.Status}}' | grep -E "(bitcoin|lnd|mempool|btcpay|nbxplorer|electrs)"`. For any that are not "Up", check logs: `sudo podman logs --tail 50 {container-name}`. Fix the root cause (usually missing network, wrong env var, or dependency not ready). After fixes, run `curl -s http://localhost:5678/health` to verify the Archy backend sees them all.
|
|
|
|
---
|
|
|
|
## Phase 3: Fix Iframe Embedding for All Apps
|
|
|
|
- [ ] **Audit X-Frame-Options headers for all proxied apps**: SSH to 192.168.1.228. For each app with a known port, check the actual response headers: `for port in 81 3000 3001 4080 7777 8080 8081 8082 8083 8085 8096 8123 8175 8176 8190 8240 8334 8888 9000 9001 9980 11434 2283 2342 23000 50002; do echo "Port $port:"; curl -sI http://localhost:$port/ 2>/dev/null | grep -i "x-frame\|content-security-policy" || echo " (no frame restrictions)"; done`. Record the results. Compare against the blocking list in `neode-ui/src/stores/appLauncher.ts` (lines 23-31, the `XFRAME_BLOCKED_PORTS` array). Update the blocking list to match reality — if an app no longer sends X-Frame-Options DENY, remove it from the blocked list. If an app sends it but isn't in the list, add it.
|
|
|
|
- [ ] **Ensure nginx strips X-Frame-Options for iframe-compatible apps**: In `image-recipe/configs/nginx-archipelago.conf`, verify every `/app/{id}/` location block includes `proxy_hide_header X-Frame-Options;` for apps that should work in iframes. Apps that genuinely can't work in iframes (BTCPay with DENY, Home Assistant with SAMEORIGIN that rejects proxy origin) should open in new tabs. For apps like Grafana (port 3000) — check if setting the env var `GF_SECURITY_ALLOW_EMBEDDING=true` on the Grafana container fixes it, then remove it from the blocked list. For Nextcloud (port 8085) — check if the nginx `sub_filter` approach or Nextcloud's `overwriteprotocol` setting allows embedding. For Uptime Kuma (port 3001) — it may work with the header stripped. Test each by loading `http://192.168.1.228/app/{id}/` in a browser iframe or `curl -sI http://192.168.1.228/app/{id}/ | grep -i frame`.
|
|
|
|
- [ ] **Fix nginx sub_filter for apps with root-relative asset paths**: Apps served under `/app/{id}/` may have root-relative paths like `/static/main.js` that break because they resolve to the Archy root, not the app root. In `image-recipe/configs/nginx-archipelago.conf`, check IndeedHub's location block (lines 334-367) — it already uses `sub_filter` to rewrite paths. Verify the same pattern exists for other Next.js/React apps that need it (Penpot on 9001, Immich on 2283, Fedimint UI on 8175). For each, test: load the app at `http://192.168.1.228/app/{id}/`, open browser dev tools Network tab, check for 404s on static assets. If assets 404, add appropriate `sub_filter` rules to their nginx location block. After changes, sync the config: `scp image-recipe/configs/nginx-archipelago.conf archipelago@192.168.1.228:/tmp/ && ssh archipelago@192.168.1.228 'sudo cp /tmp/nginx-archipelago.conf /etc/nginx/sites-available/archipelago && sudo nginx -t && sudo systemctl reload nginx'`.
|
|
|
|
- [ ] **Deploy and verify iframe loading for all apps**: Deploy with `./scripts/deploy-to-target.sh --live`. After deploy, test each app iframe by hitting the Archy UI at `http://192.168.1.228`, navigating to Apps, and clicking each installed app. Verify: (1) iframe apps load content (not blank white), (2) blocked apps open in new tab cleanly, (3) no mixed-content warnings in console. Log any remaining issues for the next phase.
|
|
|
|
---
|
|
|
|
## Phase 4: IndeedHub + Nostr Signer Integration
|
|
|
|
- [ ] **Verify IndeedHub container is running and accessible**: SSH to 192.168.1.228. Check: `sudo podman ps | grep indeedhub`. If not running, check if the image exists: `sudo podman images | grep indeedhub`. If no image, pull from manifest: the image is `git.tx1138.com/lfg2025/indeedhub:latest` (from `apps/indeedhub/manifest.yml`). Pull and start: `sudo podman pull git.tx1138.com/lfg2025/indeedhub:latest && sudo podman run -d --name indeedhub --restart unless-stopped -p 7777:3000 --cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID --security-opt no-new-privileges --user 1001 git.tx1138.com/lfg2025/indeedhub:latest`. Verify it responds: `curl -sI http://localhost:7777/`. Check nginx proxy works: `curl -sI http://localhost/app/indeedhub/`.
|
|
|
|
- [ ] **Fix IndeedHub port mapping inconsistency**: In `core/archipelago/src/container/docker_packages.rs`, line ~139-141 hardcodes `http://localhost:8190` for IndeedHub. But nginx and the frontend use port 7777. Update `docker_packages.rs` to use port 7777: change `Some("http://localhost:8190".to_string())` to `Some("http://localhost:7777".to_string())`. Also verify `apps/indeedhub/manifest.yml` — if it says port 8190, update to 7777 to match the actual deployment. In `neode-ui/src/stores/appLauncher.ts` line 67, confirm `'7777': '/app/indeedhub/'` is correct. Deploy with `./scripts/deploy-to-target.sh --live` and test.
|
|
|
|
- [ ] **Verify nostr-provider.js injection works for IndeedHub iframe**: The NIP-07 Nostr signer works by nginx injecting `neode-ui/public/nostr-provider.js` into the iframe via `sub_filter`. Check the IndeedHub nginx location block in `image-recipe/configs/nginx-archipelago.conf` (lines 334-367) includes a `sub_filter` that injects `<script src="/nostr-provider.js"></script>` into the HTML response. If missing, add: `sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';` with `sub_filter_once on;` and `sub_filter_types text/html;`. Sync nginx config to server and reload. Verify by loading IndeedHub in the Archy iframe and checking browser dev tools console for `window.nostr` availability — run `JSON.stringify(Object.keys(window.nostr))` in the iframe console, should show `["getPublicKey","signEvent","getRelays","nip04","nip44"]`.
|
|
|
|
- [ ] **Test full NIP-07 signing flow with IndeedHub**: Open Archy at `http://192.168.1.228`, go to Apps, click IndeedHub. Expected flow: (1) NostrIdentityPicker modal appears on first launch asking which identity to use, (2) select an identity with a Nostr key, (3) IndeedHub loads in iframe, (4) when IndeedHub requests `window.nostr.getPublicKey()`, the Archy parent responds with the selected identity's Nostr pubkey, (5) when IndeedHub requests `window.nostr.signEvent(event)`, NostrSignConsent modal appears, (6) user approves, event is signed via `identity.nostr-sign` RPC, (7) signed event returned to IndeedHub. Test each step. If NostrIdentityPicker doesn't show, check `AppSession.vue` line ~302-304 `isIdentityAwareApp()` includes 'indeedhub'. If signing fails, check RPC logs: `ssh archipelago@192.168.1.228 'sudo journalctl -u archipelago --since "5 min ago" | grep -i nostr'`.
|
|
|
|
- [ ] **Ensure IndeedHub content loads fully — all pages, media, navigation**: After the Nostr flow works, navigate through IndeedHub's content inside the iframe. Check: (1) all pages/routes load (no blank screens), (2) media content (videos, images) loads, (3) navigation within IndeedHub works without breaking the iframe, (4) no console errors related to CORS, mixed content, or CSP. If videos don't load, check if the video hosting domain is blocked by CSP headers — may need to add `Content-Security-Policy` adjustments in the nginx location block. If internal navigation causes the iframe to navigate to a bare URL (not under `/app/indeedhub/`), add `sub_filter` rules to rewrite the app's internal links.
|
|
|
|
- [ ] **Test NIP-04 and NIP-44 encryption/decryption**: In IndeedHub (or manually via browser console in the iframe), test the encryption methods: (1) `window.nostr.nip04.encrypt(somePubkey, "test message")` — should return ciphertext, (2) `window.nostr.nip04.decrypt(somePubkey, ciphertext)` — should return "test message", (3) same for `nip44.encrypt` and `nip44.decrypt`. If any fail, check RPC handlers in `core/archipelago/src/api/rpc/identity.rs` — the `handle_identity_nostr_encrypt_nip04/nip44` and decrypt handlers (lines 428-496). Check that the identity manager has the required keys.
|
|
|
|
---
|
|
|
|
## Phase 5: App Installation & Management Polish
|
|
|
|
- [ ] **Verify install flow for every Bitcoin-related marketplace app**: In the Archy UI at `http://192.168.1.228`, go to Marketplace. For each Bitcoin-related app (Bitcoin Knots, LND, Mempool, BTCPay, Electrs, Fedimint), click through to the detail page. Verify: (1) icon loads correctly (not fallback logo), (2) description is accurate, (3) "Install" button appears if not installed, (4) dependency warnings show correctly (Mempool requires Bitcoin Knots + Electrs, BTCPay requires Bitcoin Knots), (5) if already installed, status shows correctly. Fix any issues found in `neode-ui/src/views/MarketplaceAppDetails.vue`. Note: Archy is Bitcoin only — remove any Monero or Liquid entries from `Marketplace.vue` `getCuratedAppList()` if present.
|
|
|
|
- [ ] **Remove non-Bitcoin altcoin entries from marketplace**: Search `neode-ui/src/views/Marketplace.vue` for "monero", "liquid", "litecoin", or any non-Bitcoin cryptocurrency entries in the `getCuratedAppList()` function. Remove them entirely. Archy is a Bitcoin-only platform. Run `cd neode-ui && npm run type-check` after changes.
|
|
|
|
- [ ] **Fix dependency checks — frontend must match backend**: In `neode-ui/src/views/MarketplaceAppDetails.vue`, find the hardcoded dependency definitions (around lines 447-456). Cross-reference with `core/archipelago/src/api/rpc/package.rs` lines 64-96 where backend dependency checks are defined. Ensure they match exactly. If backend checks for `has_bitcoin` before installing `electrs`, the frontend dependency list for `electrs` must show `bitcoin-knots` as a prerequisite. Update the frontend to match the backend. Ideally, add an RPC method `package.get-dependencies` that returns the dependency list from the backend, and have the frontend call it instead of hardcoding — but for now, just make the hardcoded lists match.
|
|
|
|
- [ ] **Verify start/stop/restart works for all installed apps**: In the Archy UI, go to Apps. For each installed app, test: (1) click Stop — container stops, UI updates to "Stopped" state, (2) click Start — container starts, UI updates to "Running" state with health indicator, (3) click the app — it launches (iframe or new tab as appropriate). Check that the container store (`neode-ui/src/stores/container.ts`) correctly polls for status changes after start/stop actions. If status doesn't update, check the WebSocket state broadcasting in `core/archipelago/src/state.rs`.
|
|
|
|
- [ ] **Fix route-to-package-key mapping divergence**: In `neode-ui/src/views/AppDetails.vue` lines 501-529, the route ID to backend container name mapping is hardcoded. Verify every mapping is correct by checking actual container names on the server: `ssh archipelago@192.168.1.228 'sudo podman ps --format "{{.Names}}"'`. Fix any mismatches. Known issues: `mempool` maps to `mempool-web` but backend may use `archy-mempool-web`. Check `electrs` maps to `mempool-electrs` or `archy-electrs`. Run `cd neode-ui && npm run type-check` after changes.
|
|
|
|
---
|
|
|
|
## Phase 6: Backend Critical Fixes
|
|
|
|
- [ ] **Fix session TTL clock bug — use SystemTime instead of Instant**: Read `core/archipelago/src/session.rs`. Find where `Instant::now()` is used for session TTL/expiry (around line 97). `Instant` is monotonic but can drift on sleep/hibernate — common on NUC/Pi hardware. Replace with `SystemTime::now()` for absolute time comparison. The `FULL_SESSION_TTL` (24 hours) and `PENDING_TOTP_TTL` (5 minutes) checks should use `SystemTime::elapsed()` or store `SystemTime` timestamps and compare with `SystemTime::now()`. Run `cargo test --all-features` in `core/` on the dev server.
|
|
|
|
- [ ] **Enforce RBAC in RPC handler**: Read `core/archipelago/src/auth.rs` — find the `UserRole` enum and `can_access()` method. Then read `core/archipelago/src/api/rpc/mod.rs` — find where authenticated requests are dispatched to handlers. Add a role check before dispatching: after validating the session, get the user's role, call `role.can_access(method_name)`, and return an authorization error if denied. For now, all users created via onboarding should default to `Admin` role (single-user system), but this lays the groundwork for multi-user. Run `cargo clippy --all-targets --all-features && cargo test --all-features` on the dev server.
|
|
|
|
- [ ] **Remove dead code and #[allow(dead_code)]**: Search `core/` for all `#[allow(dead_code)]` and `#[allow(unused)]` annotations. For each: (1) if the code is genuinely unused and not part of a planned feature, delete it, (2) if it should be used (like RBAC — now wired up in previous task), remove the allow annotation. Key file: `core/archipelago/src/auth.rs` lines ~70, 83, 88. Run `cargo clippy --all-targets --all-features` to verify no new warnings.
|
|
|
|
- [ ] **Deploy and verify backend fixes**: Run `./scripts/deploy-to-target.sh --live`. After deploy: (1) verify login still works at `http://192.168.1.228` (password: `password123`), (2) verify session persists after navigating between pages, (3) check logs for any new errors: `ssh archipelago@192.168.1.228 'sudo journalctl -u archipelago --since "2 min ago" | grep -i error'`.
|
|
|
|
---
|
|
|
|
## Phase 7: Frontend Cleanup
|
|
|
|
- [ ] **Remove dead dockerode dependency**: Run `cd /Users/dorian/Projects/archy/neode-ui && npm uninstall dockerode` and `npm uninstall @types/dockerode` if it exists. Search the codebase for any remaining imports: `grep -r "dockerode" neode-ui/src/`. Remove any dead imports found. Run `npm run type-check` to verify nothing breaks.
|
|
|
|
- [ ] **Fix the 10 failing frontend tests**: Run `cd /Users/dorian/Projects/archy/neode-ui && npm run test -- --reporter=verbose 2>&1 | head -100` to see which tests fail. Known failures: (1) `src/stores/__tests__/appLauncher.test.ts` — URL rewriting tests expecting different proxy behavior, (2) `src/views/__tests__/settings.test.ts` — heading selector `h1` not finding the heading element. For each failing test, read the test file and the component/store it tests. Update test expectations to match current implementation. Do NOT change the production code to match tests — fix the tests. Run `npm run test` until all pass.
|
|
|
|
- [ ] **Add 404 catch-all route**: In `neode-ui/src/router/index.ts`, add a catch-all route at the end of the routes array: `{ path: '/:pathMatch(.*)*', name: 'not-found', component: () => import('@/views/NotFound.vue') }`. Create `neode-ui/src/views/NotFound.vue` — a simple view using the existing `.glass-card` class with "Page not found" message and a router-link back to `/dashboard`. Use `<script setup lang="ts">`, no props needed. Style with existing global classes only (`.glass-card`, `.glass-button`). Run `npm run type-check`.
|
|
|
|
---
|
|
|
|
## Phase 8: Web5 Identity & Credentials Hardening
|
|
|
|
> **Context**: TBD/Block shut down Nov 2024 — Web5 repos donated to DIF but effectively unmaintained. Archy's custom implementations (did:key, did:dht, VCs, multi-identity) are W3C-compliant and well-tested. SpruceID `ssi` crate (v0.15.0, Feb 2026) is the only mature Rust DID/VC library. DWN spec is stalled — no Rust implementation exists anywhere. Strategy: keep our custom stack (it's good), fix onboarding gaps, encrypt credential storage, validate against W3C specs, evaluate `ssi` for external VC verification only, deprioritize DWN in favor of Nostr + federation. Do NOT adopt dead TBD SDKs.
|
|
|
|
- [ ] **Fix DID onboarding — replace mock signature with real proof-of-control**: In `neode-ui/src/views/OnboardingVerify.vue`, the verification step uses `generateMockSignature()` instead of real cryptographic proof. Replace with a call to `node.signChallenge` RPC (or `identity.sign` if it exists). The flow should be: (1) frontend generates a random challenge string, (2) sends to `identity.sign` RPC with the node's default identity, (3) backend signs with Ed25519 key, (4) frontend displays the signature as proof the node controls the DID. Check `core/archipelago/src/api/rpc/identity.rs` for existing sign handlers — `handle_identity_sign` should work. If `node.signChallenge` RPC doesn't exist, the `identity.sign` endpoint (which takes `{ id?, data }` and returns `{ signature }`) should be sufficient. Update the Vue component to call it. Run `cd neode-ui && npm run type-check`.
|
|
|
|
- [ ] **Fix DID onboarding — real encrypted backup**: In `neode-ui/src/views/OnboardingBackup.vue`, the backup step uses mock JSON data instead of real encrypted key material. Replace with a call to `identity.export` or `backup.create-identity` RPC (check what exists in `core/archipelago/src/api/rpc/identity.rs` and `core/archipelago/src/api/rpc/backup_rpc.rs`). The backup should contain the Ed25519 private key encrypted with the user's password via Argon2 + ChaCha20-Poly1305 (the encryption stack already exists in `core/security/`). If no export RPC exists, create one that: (1) derives a key from the user's password with Argon2, (2) encrypts the identity's private key with ChaCha20-Poly1305, (3) returns base64-encoded ciphertext. The frontend should offer this as a downloadable `.json` file. Run `cargo test --all-features` on the dev server.
|
|
|
|
- [ ] **Fix DID onboarding UX copy**: In `neode-ui/src/views/OnboardingDid.vue`, the copy says "Generate DID" but actually fetches an existing DID from the server (generated at first boot). Update the button text to "View Your DID" or "Retrieve Your DID" and the description to explain that the DID was created when the node was set up. Small change but prevents user confusion. Do NOT change any styling or layout.
|
|
|
|
- [ ] **Validate DID Document structure against W3C spec**: In `core/archipelago/src/identity.rs`, the `generate_did_document()` function builds a DID Document. Verify it includes all required fields per W3C DID Core v1.0: `id`, `verificationMethod` (with correct `type: "Ed25519VerificationKey2020"`), `authentication`, `assertionMethod`, `keyAgreement` (X25519). Check that `@context` includes `["https://www.w3.org/ns/did/v1", "https://w3id.org/security/suites/ed25519-2020/v1"]`. Add a unit test that validates the document structure against these requirements. Run `cargo test --all-features`.
|
|
|
|
- [ ] **Validate Verifiable Credentials against W3C VC 2.0 spec**: In `core/archipelago/src/credentials.rs`, verify the `VerifiableCredential` struct produces output matching W3C VC Data Model 2.0. Check: (1) `@context` includes `https://www.w3.org/ns/credentials/v2`, (2) `type` array starts with `"VerifiableCredential"`, (3) `proof` uses `Ed25519Signature2020` with proper structure (`type`, `created`, `verificationMethod`, `proofPurpose`, `proofValue`), (4) `issuanceDate` is RFC 3339, (5) `credentialSubject` has `id` field with holder DID. Add a test that issues a credential, serializes to JSON, and validates all required fields. Run `cargo test --all-features`.
|
|
|
|
- [ ] **Evaluate SpruceID ssi crate for DID resolution validation**: Add `ssi = "0.15"` to `core/Cargo.toml` as an optional dependency (`[dependencies.ssi] version = "0.15" optional = true`). Create a test (behind `#[cfg(feature = "ssi-compat")]`) that: (1) generates a DID Document with Archy's `identity.rs`, (2) parses it with `ssi::did::Document`, (3) verifies the structure is valid per the `ssi` library's validation. This is a compatibility check — if `ssi` can parse our documents, we're spec-compliant. If it fails, note what's wrong. Do NOT make `ssi` a required dependency — this is for validation only. Run `cargo test --features ssi-compat` on dev server.
|
|
|
|
- [ ] **Evaluate pkarr crate for did:dht enhancement**: Research the `pkarr` crate (v5.0.3, 550K downloads) by reading its documentation. It provides Ed25519-public-key-addressable resource records over the Mainline DHT — essentially did:dht but with better tooling and active maintenance. Compare with Archy's current `did_dht.rs` implementation that uses `mainline` directly. If `pkarr` offers advantages (relay fallback, caching, DNS-packet handling), document them in `docs/pkarr-evaluation.md`. Do NOT switch yet — just evaluate and document findings. Key question: does `pkarr` handle the BEP-44 signed DNS packet encoding that Archy currently does manually in `did_dht.rs`?
|
|
|
|
- [ ] **Clean up DWN — remove dead TBD references and simplify**: Search the codebase for any references to TBD URLs, `@tbd54566975`, `tbd.website`, or TBD-specific terminology. Remove them. In `docs/dwn-protocols.md`, update the context to note that TBD is defunct and Archy's DWN is a custom implementation for peer sync, not a full DWN spec implementation. In `core/archipelago/src/network/dwn_store.rs`, verify the protocol definitions use Archy-specific URLs (`https://archipelago.dev/protocols/...`) not TBD URLs. Keep the DWN store functionality — it works for peer file catalogs and federation state — but stop calling it "Web5 DWN" in user-facing text. In `neode-ui/src/views/Web5.vue`, if there are references to "TBD" or "Web5 by TBD", update to just "Decentralized Identity" or "Web5 Standards".
|
|
|
|
- [ ] **Add did:dht auto-refresh background task**: In `core/archipelago/src/server.rs`, add a background task that refreshes the did:dht publication every 2 hours. DHT records expire if not re-published. The task should: (1) check if the node has a published did:dht, (2) if yes, call `did_dht::create_and_publish()` to re-publish, (3) log success/failure. Use `tokio::spawn` with `tokio::time::interval(Duration::from_secs(7200))`. Only run if `config.nostr_discovery_enabled` is true (the same flag that gates DHT usage). Add the task alongside the existing background tasks (container scanner, peer health, etc.).
|
|
|
|
- [ ] **Encrypt credentials storage at rest**: Read `core/archipelago/src/credentials.rs` — credentials are stored as plaintext JSON in `{data_dir}/credentials/credentials.json`. These may contain sensitive claims about identity holders. Fix: encrypt the file at rest using AES-256-GCM (the `aes-gcm` crate is already a dependency). Follow the pattern used in `core/security/` for secrets encryption — derive a key from the node's master key. On read: detect if file is plaintext JSON (starts with `[` or `{`) vs encrypted (binary/base64), decrypt if needed. On write: always encrypt. This provides a migration path — existing plaintext files get encrypted on first write. Add a test that writes credentials, reads them back, and verifies the file on disk is not plaintext. Run `cargo test --all-features` on dev server.
|
|
|
|
- [ ] **Add identity lifecycle integration tests**: In `core/archipelago/src/identity_manager.rs`, add comprehensive tests for the full lifecycle: (1) create identity with default purpose → verify did:key format matches `did:key:z6Mk...`, (2) create Nostr key → verify npub starts with `npub1`, (3) sign arbitrary data → verify signature with public key, (4) issue a VC from this identity → verify the VC, (5) create a presentation wrapping the VC → verify the presentation, (6) delete identity → verify it's gone and default shifts. Use `tempfile::tempdir()` for storage. Target: 8+ new `#[tokio::test]` cases. Run `cargo test --all-features`.
|
|
|
|
- [ ] **Write ADR for DWN deprioritization**: Create `docs/adr/011-dwn-deprioritization.md`. Document: (1) TBD/Block shut down Nov 2024, donated code to DIF, (2) no maintained Rust DWN SDK exists, (3) DWN spec losing momentum without TBD's backing, (4) Archy's federation over Tor + Nostr relays already serve the peer data sync use case, (5) DWN store code stays in codebase but is not actively developed, (6) re-evaluate if DIF produces a viable Rust SDK. Follow existing ADR format in `docs/adr/`. This is documentation only — no code changes.
|
|
|
|
- [ ] **Deploy to both nodes and test Web5 features**: Deploy with `./scripts/deploy-to-target.sh --both`. Test at `http://192.168.1.228`: (1) navigate to Web5 page — DID displays correctly, (2) click "Publish to DHT" if available — should publish and show status, (3) go to Credentials page — issue a test credential to self, verify it shows in list. Repeat on `http://192.168.1.198`. Check logs on both: `ssh archipelago@192.168.1.228 'sudo journalctl -u archipelago --since "5 min ago" | grep -iE "(did|credential|dwn|identity)"'` and same for .198.
|
|
|
|
- [ ] **Test cross-node DID resolution between .228 and .198**: From .228's Web5 page, get its DID (did:key). SSH to .198 and test resolving .228's DID: `curl -s http://localhost:5678/rpc/v1 -H 'Content-Type: application/json' -d '{"method":"identity.resolve-remote-did","params":{"did":"<.228-did>","onion_address":"<.228-onion>"}}'`. The response should return .228's full DID Document. Test the reverse direction (resolve .198's DID from .228). If resolution fails, check: (1) Tor is running on both nodes (`sudo podman ps | grep tor`), (2) onion addresses are valid (`cat /var/lib/archipelago/tor/*/hostname`), (3) RPC is accessible over Tor. Fix any issues found.
|
|
|
|
- [ ] **Test cross-node credential issuance and verification**: From .228, issue a Verifiable Credential where .228 is the issuer and .198's DID is the subject. Use the Credentials UI or RPC: `curl -s http://localhost:5678/rpc/v1 -H 'Content-Type: application/json' -d '{"method":"identity.issue-credential","params":{"subject_did":"<.198-did>","credential_type":"FederationMember","claims":{"role":"peer","joined":"2026-03-15"}}}'`. Copy the credential ID. From .198, verify the credential: `curl -s http://localhost:5678/rpc/v1 -H 'Content-Type: application/json' -d '{"method":"identity.verify-credential","params":{"credential_id":"<id>"}}'`. If .198 can't verify (it needs .228's public key), test the resolution chain: .198 resolves .228's DID → extracts public key → verifies signature. Fix any issues in the verification flow.
|
|
|
|
- [ ] **Test federation trust via DIDs between .228 and .198**: Verify the federation between the two nodes uses DID-based identity. SSH to .228: `curl -s http://localhost:5678/rpc/v1 -H 'Content-Type: application/json' -d '{"method":"federation.list-nodes"}'`. Check that .198 appears as a peer with its DID. SSH to .198 and verify .228 appears similarly. If federation is not set up between them, establish it: use `federation.invite` on .228 to generate an invite, then `federation.join` on .198. After joining, verify: (1) both nodes see each other in their peer lists, (2) both nodes have each other's DIDs, (3) peer health checks pass between them. Check logs for federation errors: `sudo journalctl -u archipelago --since "10 min ago" | grep -i federation`.
|
|
|
|
- [ ] **Test DWN sync between .228 and .198**: Even though DWN is deprioritized, test the existing sync functionality. On .228, write a test DWN message: `curl -s http://localhost:5678/rpc/v1 -H 'Content-Type: application/json' -d '{"method":"dwn.write","params":{"protocol":"https://archipelago.dev/protocols/file-catalog/v1","data":{"filename":"test.txt","size":1024}}}'`. Check DWN status on both nodes: `curl -s http://localhost:5678/rpc/v1 -d '{"method":"dwn.status"}'`. If sync is working, the message should appear on .198 after a sync cycle. If sync is not working, document what fails and where — this informs whether to invest more or formally pause DWN development. Don't spend more than 15 minutes debugging — document findings either way.
|
|
|
|
---
|
|
|
|
## Phase 9: Factory Reset, Restore, & End-to-End Onboarding Test
|
|
|
|
> **Goal**: Be able to factory reset the node, go through onboarding (DID + Nostr key created together), keys loaded into identity management, sign into IndeedHub with native Nostr signer, content loads. Also: restore from backup on the very first screen.
|
|
|
|
- [ ] **Implement system.factory-reset RPC endpoint**: Create a new RPC handler in `core/archipelago/src/api/rpc/system.rs` (or add to an existing system module). The `system.factory-reset` method should: (1) require authentication (admin only), (2) accept `{ confirm: true }` param as a safety check, (3) stop all running containers via `PodmanClient` (iterate `podman ps -q` and stop each), (4) delete user data: remove `{data_dir}/user.json`, `{data_dir}/onboarding.json`, `{data_dir}/identities/` directory, `{data_dir}/credentials/` directory, `{data_dir}/peers.json`, `{data_dir}/did-cache/` directory, `{data_dir}/dwn/` directory, (5) keep container images (don't re-download), keep the `identity/node_key` (node identity persists — it's the hardware identity), keep nginx and systemd configs, (6) clear all sessions from the session store, (7) restart the Archipelago service: `sd_notify::notify(false, &[sd_notify::NotifyState::Reloading])` then exit the process (systemd will restart it), or alternatively use `std::process::Command::new("sudo").args(["systemctl", "restart", "archipelago"]).spawn()`. Register the handler in `core/archipelago/src/api/rpc/mod.rs`. Run `cargo clippy --all-targets --all-features && cargo test --all-features` on the dev server.
|
|
|
|
- [ ] **Add factory reset button to Settings.vue**: In `neode-ui/src/views/Settings.vue`, add a "Factory Reset" section at the very bottom of the page (after all other settings). Use a `.path-option-card` container with a red-tinted warning. Include: (1) heading "Factory Reset", (2) description "Wipe all user data, identities, and credentials. Container images are preserved. The node will restart and show the onboarding screen.", (3) a `.glass-button` styled with red text/border that says "Factory Reset", (4) on click, show a confirmation dialog (use a simple `v-if` modal with `.glass-card` styling) asking "Are you sure? This will delete all identities, credentials, and settings. This cannot be undone." with Cancel and "Yes, Reset" buttons, (5) on confirm, call `rpcClient.call({ method: 'system.factory-reset', params: { confirm: true } })`, (6) on success, clear all localStorage (`localStorage.clear()`), redirect to `/onboarding/intro`. Use existing glass styles only — no new CSS classes. Run `cd neode-ui && npm run type-check`.
|
|
|
|
- [ ] **Add "Restore from Backup" button to OnboardingIntro.vue (first screen)**: In `neode-ui/src/views/OnboardingIntro.vue`, this is the very first screen a user sees after a fresh install or factory reset. Currently it just has a "Unlock your sovereignty →" button. Add a "Restore from Backup" link below it. Implementation: (1) add `showRestore` and `restoreFile` and `passphrase` refs, (2) below the main CTA button, add a subtle text link "Restore from backup" (style: `text-white/50 hover:text-white/80 underline text-sm cursor-pointer mt-4 block text-center`), (3) clicking it toggles a restore panel (use `.glass-card`) with: a file input (`<input type="file" accept=".json">`) for the `archipelago-did-backup.json` file, a password input for the backup passphrase, and a "Restore" `.glass-button`, (4) on file select, read the JSON with `FileReader`, (5) on Restore click, call `rpcClient.call({ method: 'backup.restore-identity', params: { backup: parsedJson, passphrase: password } })`, (6) on success, show "Identity restored successfully" message, then navigate to `/onboarding/did` — the DID step will now show the restored DID instead of generating a new one. Run `cd neode-ui && npm run type-check`.
|
|
|
|
- [ ] **Implement backup.restore-identity RPC for DID restore**: Check if `core/archipelago/src/api/rpc/backup_rpc.rs` has an identity-specific restore handler. The existing `backup.restore` is for full system backups (tar archives from USB). We need a lighter `backup.restore-identity` that: (1) accepts the JSON blob from `node.createBackup` (the `archipelago-did-backup.json` file), (2) extracts: version, encrypted blob, (3) decrypts with Argon2 + ChaCha20-Poly1305 using the provided passphrase (reverse of `backup::create_encrypted_backup()` in `core/archipelago/src/backup/identity.rs`), (4) writes the decrypted 32-byte Ed25519 private key to `{data_dir}/identity/node_key` with 0o600 permissions, (5) returns `{ did, pubkey }` of the restored identity. If the `backup/identity.rs` module already has a `restore_encrypted_backup()` function, use it. If not, create one following the inverse of `create_encrypted_backup()`. Register the handler in `rpc/mod.rs`. Run `cargo clippy --all-targets --all-features && cargo test --all-features`.
|
|
|
|
- [ ] **Ensure DID + Nostr keypair exist immediately from boot / factory reset**: The node's Ed25519 key is auto-generated at first boot (stored in `identity/node_key`), and `node.did` / `node.nostr-pubkey` RPCs derive from it. But user identities with Nostr keys are only created when the user reaches the Identity step in onboarding. Fix this so keys are available from the very start: (1) In `core/archipelago/src/main.rs` or `server.rs`, during startup (after loading node identity but before starting the HTTP server), check if any identities exist via `IdentityManager::list()`. If the list is empty (fresh boot or factory reset), auto-create a default identity: call `identity_manager.create("Default", IdentityPurpose::Personal)` — this generates Ed25519 + Nostr keypair automatically. (2) Verify `identity_manager.rs` `create()` method calls `create_nostr_key()` automatically — if not, add it after keypair generation. (3) This means when `OnboardingDid.vue` loads, both `node.did` AND `identity.list` already return data with Nostr npub populated. The identity step in onboarding can then let the user rename or create additional identities, but the default is already there. (4) After factory reset (which deletes `{data_dir}/identities/`), the next boot auto-creates the default identity again. Run `cargo test --all-features` on the dev server.
|
|
|
|
- [ ] **Deploy factory reset + restore and test the full cycle**: Deploy with `./scripts/deploy-to-target.sh --live`. Then run the end-to-end test on .228: (1) Login at `http://192.168.1.228`, go to Settings, scroll to bottom, click "Factory Reset", confirm, (2) node restarts — wait 10-15 seconds, refresh browser, (3) should see the onboarding intro screen, (4) go through: Intro → Path → DID (should show new or existing DID + Nostr npub) → Identity (create "Personal" identity) → Backup (download backup file) → Verify (signature verified) → Done → Login, (5) set password, login, (6) navigate to Web5/Identity page — DID and Nostr npub should display, (7) go to Apps → click IndeedHub, (8) NostrIdentityPicker should appear — select the identity just created, (9) IndeedHub should load in iframe, (10) IndeedHub should request `window.nostr.getPublicKey()` — Archy returns the identity's Nostr pubkey, (11) if IndeedHub requires signing, NostrSignConsent appears, approve it, (12) IndeedHub content should load from their API (videos, pages). Check logs: `ssh archipelago@192.168.1.228 'sudo journalctl -u archipelago --since "5 min ago" | grep -iE "(factory|reset|onboard|identity|nostr|indeedhub)"'`.
|
|
|
|
- [ ] **Test restore from backup on fresh state**: After the previous test, do another factory reset on .228. This time: (1) when the first screen appears (Login.vue in setup mode), click "Restore from Backup", (2) select the `archipelago-did-backup.json` file downloaded in the previous test, (3) enter the backup passphrase, (4) click Restore, (5) should see success message, (6) continue onboarding — the DID step should show the SAME DID as before (restored from backup), (7) create identity, complete onboarding, (8) login and verify: same DID, identity management has the restored keys, (9) go to IndeedHub — Nostr signing should work with the restored identity. If any step fails, check: backend logs for restore errors, frontend console for RPC failures, verify the backup file format matches what `backup.restore-identity` expects.
|
|
|
|
---
|
|
|
|
## Phase 10: Final Verification & Deploy
|
|
|
|
- [ ] **Full type-check and lint pass**: Run `cd /Users/dorian/Projects/archy/neode-ui && npm run type-check` — must pass with zero errors. Run `npm run test` — all tests must pass. On dev server, run `cd ~/archy/core && cargo clippy --all-targets --all-features` — zero warnings. Run `cargo test --all-features` — all tests pass.
|
|
|
|
- [ ] **Final deploy and complete smoke test**: Run `./scripts/deploy-to-target.sh --live`. After deploy, test the full user flow at `http://192.168.1.228`: (1) login works, (2) dashboard loads with app list, (3) click each installed app — loads in iframe or new tab correctly, (4) go to Marketplace — all icons load, no broken images, no altcoins, (5) open IndeedHub — identity picker shows, select identity, app loads, Nostr signing works, content from their API loads, (6) start/stop an app — status updates correctly, (7) navigate to a fake URL like `/dashboard/nonexistent` — shows 404 page with back link, (8) Web5 page shows DID + Nostr npub correctly, credentials can be issued and verified, (9) Settings page has Factory Reset at the bottom, (10) factory reset works — node restarts, onboarding appears, (11) restore from backup works on first screen, (12) check server logs for errors: `ssh archipelago@192.168.1.228 'sudo journalctl -u archipelago --since "5 min ago" | grep -i error'`.
|
|
|
|
---
|
|
|
|
## Phase 11: Security & Code Quality Audit Report
|
|
|
|
> Generate a comprehensive written report at `docs/security-code-audit-2026-03.md`. This phase is research and documentation only — no code changes. The report should be honest about strengths and weaknesses so we know exactly where we stand.
|
|
|
|
- [ ] **Audit authentication & session security**: Review `core/archipelago/src/auth.rs` and `core/archipelago/src/session.rs`. Document: (1) password hashing — bcrypt with what cost factor? Is Argon2id a better choice for new installs? Compare bcrypt (current) vs argon2id (already a dependency for backup encryption) — pros/cons, (2) session token generation — is 32-byte random hex sufficient entropy? How does it compare to using a CSPRNG-backed JWT or `tower-sessions`? (3) session storage — in-memory only, lost on restart (unless SQLite was added in earlier phases). Rate the risk, (4) CSRF — 32-byte hex token per login, validated on every request. Is this sufficient? (5) rate limiting — per-method in-memory counters. Document coverage gaps (which endpoints lack rate limiting), (6) TOTP — using `totp-rs` with encrypted secret storage. Rate the implementation quality. Write findings to a "Session & Auth" section of the report.
|
|
|
|
- [ ] **Audit cryptographic implementations**: Review all crypto code across the codebase. For each, compare our implementation against what a library would provide:
|
|
|
|
| Component | Our Implementation | Library Alternative | Verdict |
|
|
|-----------|-------------------|--------------------|---------|
|
|
| Password hashing | `bcrypt` crate, DEFAULT_COST | `argon2` crate (already a dep) | Document: argon2id is newer, memory-hard, better against GPU attacks. bcrypt is battle-tested. Recommendation? |
|
|
| Session tokens | `rand::thread_rng().gen::<[u8; 32]>()` + hex | `tower-sessions` or signed JWTs via `jsonwebtoken` | Document: current is fine for single-instance. JWTs add stateless verification but complexity |
|
|
| DID signing | `ed25519-dalek` direct usage | SpruceID `ssi` crate | Document: our usage is correct and minimal. `ssi` adds 50+ transitive deps for features we don't use |
|
|
| Backup encryption | Argon2 KDF + ChaCha20-Poly1305 | `age` crate (encryption tool) | Document: our stack is standard and correct. `age` is simpler API but less control |
|
|
| VC signatures | Custom Ed25519Signature2020 proof | SpruceID `ssi` VC module | Document: our impl handles one proof type. `ssi` handles many but large dep tree |
|
|
| Nostr encryption | `nostr-sdk` NIP-04/NIP-44 | Direct `chacha20poly1305` + `secp256k1` | Document: `nostr-sdk` is correct choice, actively maintained |
|
|
| TLS | `rustls` via reqwest | `openssl` | Document: `rustls` is the right choice — pure Rust, no C deps, privacy-focused |
|
|
| Key storage | Raw bytes in files with 0o600 perms | `keyring` crate or OS keychain | Document: file-based is correct for headless server. OS keychain not available |
|
|
|
|
Write findings to a "Cryptographic Review" section. For each row, state: is our code correct? Is it secure? Would a library be better? Why or why not?
|
|
|
|
- [ ] **Audit container security**: Review container security across `core/container/src/podman_client.rs`, `core/archipelago/src/api/rpc/package.rs`, and `apps/*/manifest.yml`. Document: (1) are all containers running with `--cap-drop ALL` + only required caps added back? Check each app manifest, (2) `readonly_root: true` — which apps have it, which don't and why, (3) `no-new-privileges` — is it set for all containers? (4) user namespace — are containers running as non-root (UID > 1000)? Check for any running as root, (5) image pinning — are images pinned to specific digests/versions or using `:latest`? List offenders, (6) cosign verification — still a TODO? Document the gap, (7) network isolation — which containers share networks? Is `archy-net` properly scoped? (8) secrets injection — how are secrets passed to containers? Env vars (visible in `podman inspect`) vs mounted files? Write findings to a "Container Security" section.
|
|
|
|
- [ ] **Audit RPC endpoint security**: Review `core/archipelago/src/api/rpc/mod.rs` — the main RPC dispatcher. Document: (1) which endpoints require authentication and which don't? List any unauthenticated endpoints beyond `auth.login`, `auth.setup`, `auth.isSetup`, `auth.isOnboardingComplete`, (2) RBAC enforcement — was it wired up in Phase 6? If yes, verify it works. If no, document the gap and risk, (3) input validation — pick 5 critical endpoints (login, install package, factory reset, backup restore, identity create) and trace the input from RPC params to handler. Are inputs validated? Are there injection risks? (4) error message sanitization — does `sanitize_error_message()` strip file paths and internal details from user-facing errors? Test with a few error cases, (5) path traversal — check `filebrowser-client.ts` `sanitizePath()` and any backend file operations. Can a crafted path escape the data directory? Write findings to an "RPC Security" section.
|
|
|
|
- [ ] **Audit frontend security**: Review the Vue frontend for common web vulnerabilities. Document: (1) XSS — are any user inputs rendered with `v-html`? Search for `v-html` across all `.vue` files. If found, is the content sanitized? (2) CSRF — frontend sends `X-CSRF-Token` header on every RPC call. Verify this in `rpc-client.ts`. Is the token properly scoped to the session? (3) credential storage — what's in localStorage? Search for `localStorage.setItem` across all files. Are any secrets (passwords, keys, tokens) stored client-side? They shouldn't be — only session flags and UI preferences, (4) iframe security — `nostr-provider.js` uses `postMessage('*')` for responses. Is the origin validated on incoming messages? Check `AppSession.vue` and `AppLauncherOverlay.vue` message handlers — do they verify `event.origin`? (5) dependency audit — run `cd neode-ui && npm audit` and document findings. Write findings to a "Frontend Security" section.
|
|
|
|
- [ ] **Assess custom code quality vs library alternatives — full comparison**: This is the core of the report. For each major custom module, write a comparison:
|
|
|
|
**1. HTTP Server (custom hyper 0.14 handler.rs — 813 lines)**
|
|
- Quality: Hand-rolled routing, middleware, CORS, WebSocket upgrade. Works but brittle.
|
|
- Alternative: `axum` (tokio team, built on hyper 1.x). Typed extractors, middleware stack, tower integration.
|
|
- Verdict: Migrate. hyper 0.14 is EOL. axum reduces handler.rs from 813 lines to ~200.
|
|
- Risk: Medium — RPC logic unchanged, only HTTP glue changes.
|
|
|
|
**2. Session Management (custom session.rs — 200 lines)**
|
|
- Quality: In-memory token store, TTL-based expiry, max 5 concurrent sessions, zeroize on drop.
|
|
- Alternative: `tower-sessions` + `tower-sessions-sqlx-store` (SQLite backend).
|
|
- Verdict: If SQLite is added, migrate. If not, keep custom — it's simple and correct for single-instance.
|
|
|
|
**3. Rate Limiting (custom in rpc/mod.rs)**
|
|
- Quality: Per-method in-memory counters. Simple, works, not configurable.
|
|
- Alternative: `governor` crate or `tower::limit::RateLimitLayer`.
|
|
- Verdict: Low priority swap. Current works fine for single-instance appliance.
|
|
|
|
**4. DID Implementation (custom identity.rs — ~300 lines)**
|
|
- Quality: Clean did:key generation, proper W3C DID Document, good test coverage.
|
|
- Alternative: SpruceID `ssi` crate (v0.15.0, 146K downloads).
|
|
- Verdict: Keep custom. Our code is ~300 lines, purpose-built, handles dual-key (Ed25519+secp256k1). `ssi` would add 50+ transitive deps for features we don't need. Use `ssi` only for external VC verification if needed.
|
|
|
|
**5. Verifiable Credentials (custom credentials.rs — ~400 lines)**
|
|
- Quality: W3C VC 2.0 compliant, issue/verify/revoke/present all working, good test coverage.
|
|
- Alternative: SpruceID `ssi` VC module.
|
|
- Verdict: Keep custom for issuance. Consider `ssi` for verification of external VCs (more proof types). Our code handles Ed25519Signature2020 only — sufficient for node-to-node but not for arbitrary external VCs.
|
|
|
|
**6. did:dht (custom did_dht.rs — ~200 lines)**
|
|
- Quality: Works via `mainline` crate, BEP-44 signed records, in-memory cache.
|
|
- Alternative: `pkarr` crate (v5.0.3, 550K downloads) — higher-level abstraction over mainline.
|
|
- Verdict: Evaluate `pkarr`. If it handles BEP-44 encoding we do manually, switch. Otherwise keep custom — it's small and works.
|
|
|
|
**7. DWN Store (custom dwn_store.rs — ~300 lines)**
|
|
- Quality: Basic CRUD, filesystem-backed, protocol registration. Skeletal.
|
|
- Alternative: None production-ready in Rust. `dwn` crate (unavi-xyz) is v0.4.0, 323 downloads.
|
|
- Verdict: Keep custom. No alternative exists. Deprioritize per ADR-011.
|
|
|
|
**8. WebSocket State Broadcasting (custom state.rs — ~200 lines)**
|
|
- Quality: tokio broadcast channel, full model resync on every change. Functional but inefficient.
|
|
- Alternative: `json-patch` crate for RFC 6902 diffs. Frontend already has `fast-json-patch`.
|
|
- Verdict: Add `json-patch` crate. One of the highest-impact improvements — reduces bandwidth dramatically.
|
|
|
|
**9. Form Validation (manual inline in Vue components)**
|
|
- Quality: Scattered, inconsistent, error-prone as forms grow.
|
|
- Alternative: `zod` (TypeScript-first schema validation, 40M weekly npm downloads).
|
|
- Verdict: Add zod. Centralize schemas in `src/types/schemas.ts`. Critical for onboarding where bad input can break key generation.
|
|
|
|
**10. Container Runtime Abstraction (custom runtime.rs + podman_client.rs — ~600 lines)**
|
|
- Quality: Clean trait abstraction (PodmanRuntime, DockerRuntime, AutoRuntime). Well-designed.
|
|
- Alternative: `bollard` crate (Docker/Podman API client, 7M downloads).
|
|
- Verdict: Keep custom. Our abstraction is clean and purpose-built. `bollard` is Docker-first and would need wrapping anyway for our manifest-based approach.
|
|
|
|
Write all comparisons to a "Custom Code vs Libraries" section with a summary table.
|
|
|
|
- [ ] **Write executive summary and next steps**: At the top of `docs/security-code-audit-2026-03.md`, write an executive summary covering: (1) overall security posture (1-10 rating with justification), (2) top 5 risks ranked by severity, (3) top 5 strengths, (4) recommended next actions (ordered by impact). Reference the `docs/refactoring-plan.md` 3-year plan for longer-term items. End with a "What to do next" section listing the 3 most impactful changes from this audit. Commit the report.
|