@wipcomputer/wip-ldm-os 0.4.85-alpha.6 → 0.4.85-alpha.8

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipcomputer/wip-ldm-os",
3
- "version": "0.4.85-alpha.6",
3
+ "version": "0.4.85-alpha.8",
4
4
  "type": "module",
5
5
  "description": "LDM OS: identity, memory, and sovereignty infrastructure for AI agents",
6
6
  "engines": {
@@ -29,6 +29,7 @@
29
29
  "test:bin-manifest": "node scripts/test-bin-manifest.mjs",
30
30
  "test:crc-agentid-tenant-boundary": "node scripts/test-crc-agentid-tenant-boundary.mjs",
31
31
  "test:crc-pair-login-flow": "node scripts/test-crc-pair-login-flow.mjs",
32
+ "test:crc-pair-status-poll-token": "node scripts/test-crc-pair-status-poll-token.mjs",
32
33
  "test:crc-e2ee-session-route": "node scripts/test-crc-e2ee-session-route.mjs",
33
34
  "fmt": "npx prettier --write 'src/**/*.{ts,mjs}' 'lib/**/*.mjs' 'bin/**/*.js'",
34
35
  "fmt:check": "npx prettier --check 'src/**/*.{ts,mjs}' 'lib/**/*.mjs' 'bin/**/*.js'"
@@ -17,15 +17,15 @@ function assertNotContains(needle, label) {
17
17
 
18
18
  assertContains('const ACCOUNT_TENANT_PREFIX = "acct:";', "account tenant prefix");
19
19
  assertContains('const LEGACY_API_KEY_TENANT_PREFIX = "key:";', "legacy key tenant prefix");
20
- assertContains('const RESERVED_AGENT_HANDLES = new Set([', "reserved handle set");
21
- assertContains('"parker-smoke-test",', "reserved parker smoke handle");
22
20
  assertContains('function accountTenantIdForUserId(userId)', "account tenant helper");
23
21
  assertContains('function identityForApiKey(key)', "api key identity helper");
24
22
  assertContains('return identityForApiKey(key);', "http auth uses identity helper");
25
23
  assertContains("const agentId = accountTenantIdForUserId(stored.userId);", "registration uses immutable account tenant");
24
+ assertContains("function sanitizeDisplayLabel(raw)", "display label sanitizer");
25
+ assertContains('replace(/[\\u0000-\\u001f\\u007f]/g, "").replace(/\\s+/g, " ").trim().slice(0, 64)', "display label sanitizer preserves label semantics");
26
+ assertContains("const displayLabel = sanitizeDisplayLabel(body?.displayName || body?.username);", "registration treats entered name as display label");
27
+ assertContains("displayLabel,", "registration challenge stores display label");
26
28
  assertContains("await saveApiKey(apiKey, agentId, { handle: credentialLabel });", "registration stores handle separately");
27
- assertContains('json(res, 409, { error: "reserved_handle"', "reserved handle rejected");
28
- assertContains('json(res, 409, { error: "handle_taken"', "duplicate handle rejected");
29
29
  assertContains("p.handle = identity.handle;", "pair stores display handle separately");
30
30
  assertContains("handle: identity.handle,", "relay metadata returns display handle");
31
31
  assertContains("codexDaemons.has(identity.agentId)", "daemon presence uses tenant id");
@@ -37,6 +37,10 @@ assertContains("const key = codexRelayKey(identity.agentId, threadId);", "web ws
37
37
  assertContains("const daemonWs = codexDaemons.get(identity.agentId);", "web sends to tenant daemon");
38
38
  assertNotContains("const agentId = stored.username || (\"passkey-\"", "registration must not use chosen handle as tenant");
39
39
  assertNotContains("const existingKey = Object.entries(API_KEYS).find(([k, v]) => v === agentId);", "oauth must not reuse chosen handle as tenant");
40
+ assertNotContains("function isUsernameTaken", "display labels must not be globally unique usernames");
41
+ assertNotContains("function sanitizeUsername", "display labels must not be modeled as usernames");
42
+ assertNotContains('json(res, 409, { error: "reserved_handle"', "display labels must not be blocked as reserved security handles");
43
+ assertNotContains('json(res, 409, { error: "handle_taken"', "duplicate display labels must be allowed");
40
44
 
41
45
  function legacyTenantIdForApiKey(key) {
42
46
  return "key:" + createHash("sha256").update(key).digest("base64url").slice(0, 32);
@@ -47,11 +51,16 @@ function accountTenantIdForUserId(userId) {
47
51
  }
48
52
 
49
53
  const chosenHandle = "parker-smoke-test";
54
+ const sharedDisplayLabel = "Parker";
50
55
  const accountA = accountTenantIdForUserId("user-a");
51
56
  const accountB = accountTenantIdForUserId("user-b");
57
+ const threadId = "thread-019dfa";
52
58
  if (accountA === accountB) {
53
59
  throw new Error("different user ids collapsed to one account tenant");
54
60
  }
61
+ if (`${sharedDisplayLabel}:${threadId}` === `${accountA}:${threadId}` || `${sharedDisplayLabel}:${threadId}` === `${accountB}:${threadId}`) {
62
+ throw new Error("display label was used as a relay route key");
63
+ }
55
64
 
56
65
  const legacyA = legacyTenantIdForApiKey("ck-a");
57
66
  const legacyB = legacyTenantIdForApiKey("ck-b");
@@ -59,7 +68,6 @@ if (legacyA === legacyB) {
59
68
  throw new Error("legacy API-key tenants collided");
60
69
  }
61
70
 
62
- const threadId = "thread-019dfa";
63
71
  const webKeyA = `${accountA}:${threadId}`;
64
72
  const webKeyB = `${accountB}:${threadId}`;
65
73
  if (webKeyA === webKeyB) {
@@ -0,0 +1,73 @@
1
+ import { readFileSync } from "node:fs";
2
+
3
+ const server = readFileSync("src/hosted-mcp/server.mjs", "utf8");
4
+
5
+ function assertContains(needle, label) {
6
+ if (!server.includes(needle)) {
7
+ throw new Error(`${label} missing expected text: ${needle}`);
8
+ }
9
+ }
10
+
11
+ function assertNotContains(needle, label) {
12
+ if (server.includes(needle)) {
13
+ throw new Error(`${label} still contains forbidden text: ${needle}`);
14
+ }
15
+ }
16
+
17
+ assertContains("function generateCodexPairPollToken()", "pair poll token generator");
18
+ assertContains('return "ppt_" + randomBytes(32).toString("base64url");', "pair poll token entropy");
19
+ assertContains("function getBearerToken(req)", "bearer token helper");
20
+ assertContains("const pollToken = generateCodexPairPollToken();", "pair-init mints poll token");
21
+ assertContains("poll_token: pollToken,", "pair state stores poll token");
22
+ assertContains("poll_token_used: false,", "pair state tracks token consumption");
23
+ assertContains("pair_poll_token: pollToken,", "pair-init returns poll token to daemon");
24
+ assertContains('json(res, 401, { error: "pair_poll_token_expired" });', "expired token rejected");
25
+ assertContains('json(res, 401, { error: "invalid_pair_poll_token" });', "missing or wrong token rejected");
26
+ assertContains("if (!pollToken || pollToken !== p.poll_token || p.poll_token_used)", "pair-status validates token");
27
+ assertContains("p.poll_token_used = true;", "completed credential response consumes token");
28
+
29
+ function pairStatusModel(pair, bearer, now) {
30
+ if (now > pair.expires) return { code: 401, body: { error: "pair_poll_token_expired" } };
31
+ if (!bearer || bearer !== pair.poll_token || pair.poll_token_used) {
32
+ return { code: 401, body: { error: "invalid_pair_poll_token" } };
33
+ }
34
+ if (pair.status === "completed") {
35
+ pair.poll_token_used = true;
36
+ return { code: 200, body: { status: "completed", api_key: pair.apiKey, handle: pair.handle } };
37
+ }
38
+ return { code: 200, body: { status: pair.status } };
39
+ }
40
+
41
+ const pair = {
42
+ status: "pending",
43
+ expires: 10_000,
44
+ poll_token: "ppt_good",
45
+ poll_token_used: false,
46
+ apiKey: "ck_secret",
47
+ handle: "Parker",
48
+ };
49
+
50
+ if (pairStatusModel({ ...pair }, null, 1).code !== 401) {
51
+ throw new Error("missing poll token should fail");
52
+ }
53
+ if (pairStatusModel({ ...pair }, "ppt_wrong", 1).code !== 401) {
54
+ throw new Error("wrong poll token should fail");
55
+ }
56
+ if (pairStatusModel({ ...pair }, "ppt_good", 20_000).code !== 401) {
57
+ throw new Error("expired poll token should fail");
58
+ }
59
+ if (pairStatusModel({ ...pair }, "ppt_good", 1).body.status !== "pending") {
60
+ throw new Error("correct poll token should return pending before completion");
61
+ }
62
+
63
+ const completedPair = { ...pair, status: "completed" };
64
+ const completed = pairStatusModel(completedPair, "ppt_good", 1);
65
+ if (completed.code !== 200 || completed.body.api_key !== "ck_secret") {
66
+ throw new Error("correct poll token should return completed credential once");
67
+ }
68
+ const replay = pairStatusModel(completedPair, "ppt_good", 1);
69
+ if (replay.code !== 401) {
70
+ throw new Error("reused completed poll token should fail");
71
+ }
72
+
73
+ console.log("crc pair-status poll token checks passed");
@@ -110,9 +110,6 @@ const API_KEY_HANDLES = {};
110
110
  const ACCOUNT_TENANT_PREFIX = "acct:";
111
111
  const LEGACY_API_KEY_TENANT_PREFIX = "key:";
112
112
  const OAUTH_API_KEY_TENANT_PREFIX = "oauth:";
113
- const RESERVED_AGENT_HANDLES = new Set([
114
- "parker-smoke-test",
115
- ]);
116
113
 
117
114
  function isInternalTenantId(id) {
118
115
  return typeof id === "string"
@@ -519,21 +516,12 @@ function esc(s) {
519
516
  return s.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
520
517
  }
521
518
 
522
- function sanitizeUsername(raw) {
519
+ function sanitizeDisplayLabel(raw) {
523
520
  if (!raw || typeof raw !== "string") return null;
524
- const cleaned = raw.toLowerCase().replace(/[^a-z0-9-]/g, "").slice(0, 30);
521
+ const cleaned = raw.replace(/[\u0000-\u001f\u007f]/g, "").replace(/\s+/g, " ").trim().slice(0, 64);
525
522
  return cleaned.length > 0 ? cleaned : null;
526
523
  }
527
524
 
528
- async function isUsernameTaken(username) {
529
- if (!username) return false;
530
- if (usePrisma) {
531
- const existing = await prisma.user.findFirst({ where: { name: username } });
532
- return !!existing;
533
- }
534
- return passkeys.some((entry) => entry.handle === username || entry.agentId === username);
535
- }
536
-
537
525
  // ---------- Session cleanup ----------
538
526
 
539
527
  function touchSession(sid) {
@@ -655,22 +643,17 @@ async function handleRegisterOptions(req, res) {
655
643
  let body;
656
644
  try { body = await readBody(req); } catch { body = {}; }
657
645
 
658
- // Accept optional username from request body
659
- const username = sanitizeUsername(body?.username);
660
- if (username && RESERVED_AGENT_HANDLES.has(username)) {
661
- json(res, 409, { error: "reserved_handle", error_description: "This handle is reserved." });
662
- return;
663
- }
664
- if (username && await isUsernameTaken(username)) {
665
- json(res, 409, { error: "handle_taken", error_description: "This handle is already in use." });
666
- return;
667
- }
646
+ // Accept the existing `username` field for wire compatibility, but
647
+ // treat it only as a display label for the passkey prompt. It is not
648
+ // a public username, account handle, or relay tenant boundary.
649
+ // Duplicate display labels are allowed.
650
+ const displayLabel = sanitizeDisplayLabel(body?.displayName || body?.username);
668
651
 
669
652
  const userId = randomBytes(16);
670
653
  const userIdB64 = userId.toString("base64url");
671
654
 
672
- const userName = username || ("user-" + userIdB64.slice(0, 8));
673
- const displayName = username || "Memory Crystal User";
655
+ const userName = displayLabel || ("user-" + userIdB64.slice(0, 8));
656
+ const displayName = displayLabel || "Memory Crystal User";
674
657
 
675
658
  let options;
676
659
  try {
@@ -699,7 +682,7 @@ async function handleRegisterOptions(req, res) {
699
682
  challenge: options.challenge,
700
683
  type: "registration",
701
684
  userId: userIdB64,
702
- username: username,
685
+ displayLabel,
703
686
  expires: Date.now() + 120000,
704
687
  };
705
688
 
@@ -752,8 +735,8 @@ async function handleRegisterVerify(req, res) {
752
735
 
753
736
  const { credential: cred, credentialDeviceType, credentialBackedUp } = verification.registrationInfo;
754
737
 
755
- // Internal tenancy is the immutable WebAuthn user id. The chosen
756
- // username is display metadata only and never owns a relay namespace.
738
+ // Internal tenancy is the immutable WebAuthn user id. The user-entered
739
+ // display label is metadata only and never owns a relay namespace.
757
740
  const agentId = accountTenantIdForUserId(stored.userId);
758
741
  // credentialLabel matches the userName passed to
759
742
  // generateRegistrationOptions in handleRegisterOptions, which is what
@@ -761,7 +744,7 @@ async function handleRegisterVerify(req, res) {
761
744
  // welcome view should display this, not agentId. Auth semantics are
762
745
  // unchanged; only the user-facing label is aligned with the saved
763
746
  // credential.
764
- const credentialLabel = stored.username || ("user-" + stored.userId.slice(0, 8));
747
+ const credentialLabel = stored.displayLabel || ("user-" + stored.userId.slice(0, 8));
765
748
  const apiKey = generateApiKey();
766
749
 
767
750
  const entry = {
@@ -2616,7 +2599,7 @@ const httpServer = createServer(async (req, res) => {
2616
2599
 
2617
2600
  const CODEX_PAIR_EXPIRY_MS = 5 * 60 * 1000;
2618
2601
  const CODEX_PAIR_ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
2619
- const codexPairings = {}; // pairing_id -> { code, status, expires, daemon_info, apiKey?, agentId?, handle?, daemon_public_key?, crypto_versions? }
2602
+ const codexPairings = {}; // pairing_id -> { code, status, expires, poll_token, poll_token_used?, daemon_info, apiKey?, agentId?, handle?, daemon_public_key?, crypto_versions? }
2620
2603
  const codexPairingByCode = {}; // code -> pairing_id (only while pending)
2621
2604
  const codexDaemons = new Map(); // agentId -> ws
2622
2605
  const codexWebClients = new Map(); // `${agentId}:${threadId}` -> Set<ws>
@@ -2706,16 +2689,30 @@ function generateCodexPairingCode() {
2706
2689
  throw new Error("Could not generate unique codex-relay pairing code");
2707
2690
  }
2708
2691
 
2692
+ function generateCodexPairPollToken() {
2693
+ return "ppt_" + randomBytes(32).toString("base64url");
2694
+ }
2695
+
2696
+ function getBearerToken(req) {
2697
+ const auth = req.headers["authorization"];
2698
+ if (typeof auth !== "string" || !auth.startsWith("Bearer ")) return null;
2699
+ const token = auth.slice(7).trim();
2700
+ return token || null;
2701
+ }
2702
+
2709
2703
  async function handleCodexPairInit(req, res) {
2710
2704
  let body = {};
2711
2705
  try { body = (await readBody(req)) || {}; } catch {}
2712
2706
  const code = generateCodexPairingCode();
2713
2707
  const pairingId = randomUUID();
2708
+ const pollToken = generateCodexPairPollToken();
2714
2709
  const expires = Date.now() + CODEX_PAIR_EXPIRY_MS;
2715
2710
  codexPairings[pairingId] = {
2716
2711
  code,
2717
2712
  status: "pending",
2718
2713
  expires,
2714
+ poll_token: pollToken,
2715
+ poll_token_used: false,
2719
2716
  daemon_info: {
2720
2717
  hostname: typeof body.hostname === "string" ? body.hostname.slice(0, 64) : null,
2721
2718
  platform: typeof body.platform === "string" ? body.platform.slice(0, 32) : null,
@@ -2738,6 +2735,7 @@ async function handleCodexPairInit(req, res) {
2738
2735
  json(res, 200, {
2739
2736
  code,
2740
2737
  pairing_id: pairingId,
2738
+ pair_poll_token: pollToken,
2741
2739
  web_url: ISSUER_URL + "/login?next=" + encodeURIComponent("/pair/" + code),
2742
2740
  expires_at: new Date(expires).toISOString(),
2743
2741
  });
@@ -2746,11 +2744,19 @@ async function handleCodexPairInit(req, res) {
2746
2744
  function handleCodexPairStatus(req, res, pairingId) {
2747
2745
  const p = codexPairings[pairingId];
2748
2746
  if (!p) { json(res, 404, { error: "pairing not found" }); return; }
2749
- if (p.status === "pending" && Date.now() > p.expires) {
2747
+ if (Date.now() > p.expires) {
2750
2748
  p.status = "expired";
2751
2749
  if (codexPairingByCode[p.code] === pairingId) delete codexPairingByCode[p.code];
2750
+ json(res, 401, { error: "pair_poll_token_expired" });
2751
+ return;
2752
+ }
2753
+ const pollToken = getBearerToken(req);
2754
+ if (!pollToken || pollToken !== p.poll_token || p.poll_token_used) {
2755
+ json(res, 401, { error: "invalid_pair_poll_token" });
2756
+ return;
2752
2757
  }
2753
2758
  if (p.status === "completed") {
2759
+ p.poll_token_used = true;
2754
2760
  json(res, 200, { status: "completed", api_key: p.apiKey, handle: p.handle || p.agentId });
2755
2761
  } else {
2756
2762
  json(res, 200, { status: p.status });
@@ -2784,8 +2790,8 @@ async function handleCodexPairComplete(req, res) {
2784
2790
  p.agentId = identity.agentId;
2785
2791
  p.handle = identity.handle;
2786
2792
  // Phase 2.5: register the daemon's E2EE public key against the
2787
- // authenticated handle. Replaces any previous key for this handle
2788
- // (rotate-key implicitly happens here on a re-pair).
2793
+ // authenticated immutable tenant id. The display handle is returned
2794
+ // as metadata only.
2789
2795
  if (p.daemon_public_key) {
2790
2796
  codexDaemonPubkeys.set(identity.agentId, {
2791
2797
  pubkey: p.daemon_public_key,