{"record":{"id":"0919c3b0b9284585","repo":"koala73/worldmonitor","slug":"invalid-prefix-0919c3","errorCode":"INVALID_PREFIX","errorMessage":"INVALID_PREFIX","messagePattern":"INVALID_PREFIX","errorType":"error_code","errorClass":"ConvexError","httpStatus":null,"severity":"error","filePath":"convex/embedKeys.ts","lineNumber":89,"sourceCode":"    // new, so EVERY row written before this deploy omits it and the predicate\n    // is fail-closed on `undefined`. Gating on the stored value alone would\n    // lock every existing paid subscriber out of the feature until a Dodo\n    // billing event happened to rewrite their row.\n    const merged = entitlement\n      ? {\n          features: mergeEntitlementFeatures(entitlement.planKey, entitlement.features),\n          validUntil: entitlement.validUntil,\n        }\n      : null;\n    if (!hasAccountEmbedAccess(identity?.plan, merged, Date.now())) {\n      throw new ConvexError(\"EMBED_ACCESS_REQUIRED\");\n    }\n\n    if (!args.name.trim()) {\n      throw new ConvexError(\"INVALID_NAME\");\n    }\n    if (!/^wme_[a-f0-9]{5}$/.test(args.keyPrefix)) {\n      throw new ConvexError(\"INVALID_PREFIX\");\n    }\n    if (!/^[a-f0-9]{64}$/.test(args.keyHash)) {\n      throw new ConvexError(\"INVALID_HASH\");\n    }\n    const allowedOrigins = normalizeAllowedOrigins(args.allowedOrigins);\n\n    const active = await ctx.db\n      .query(\"embedKeys\")\n      .withIndex(\"by_userId_revokedAt\", (q) =>\n        q.eq(\"userId\", userId).eq(\"revokedAt\", undefined),\n      )\n      .collect();\n    if (active.length >= MAX_EMBED_KEYS_PER_USER) {\n      throw new ConvexError(\"KEY_LIMIT_REACHED\");\n    }\n\n    // Guard against duplicate hash (astronomically unlikely, but belt-and-suspenders)\n    const dup = await ctx.db","sourceCodeStart":71,"sourceCodeEnd":107,"githubUrl":"https://github.com/koala73/worldmonitor/blob/7d06c8633d256c18e38133030bc3613976a96ec9/convex/embedKeys.ts#L71-L107","documentation":"createEmbedKey validates the display prefix against /^wme_[a-f0-9]{5}$/. The client generates the key and sends only the SHA-256 hash plus a short prefix for UI display (shown-once discipline); the prefix must literally start with \"wme_\" followed by exactly 5 lowercase hex characters. ConvexError(\"INVALID_PREFIX\") is thrown for any other shape.","triggerScenarios":"Calling createEmbedKey with keyPrefix that: uses a different product prefix (e.g. \"wmk_\", \"sk_\"), has fewer/more than 5 hex chars, contains uppercase hex (ABCDEF) instead of lowercase, includes the full key instead of the 5-char prefix, is empty, or was generated by code copied from the API-keys flow (apiKeys.ts) with a different prefix convention.","commonSituations":"Reusing key-generation code from the regular API keys module (different prefix), uppercase hex from a toUpperCase() slip or Number formatting, passing the whole generated key string as the prefix, hand-crafted test payloads guessing the format.","solutions":["Generate the prefix as exactly \"wme_\" + 5 lowercase hex characters, e.g. `\"wme_\" + randomHex(5)` using crypto.getRandomValues.","Lowercase any hex before sending: `prefix.toLowerCase()`.","Send only the first 5 hex chars of the generated key as keyPrefix, never the full key or hash.","Validate client-side with the same regex /^wme_[a-f0-9]{5}$/ before calling the mutation."],"exampleFix":"// before\nconst key = crypto.randomUUID().replace(/-/g, \"\");\nawait api.embedKeys.createEmbedKey({ name, keyPrefix: key.slice(0, 8), keyHash: sha256(key) });\n// after\nconst key = hexRandom(64); // 64 lowercase hex chars\nconst keyPrefix = \"wme_\" + key.slice(0, 5); // matches /^wme_[a-f0-9]{5}$/\nawait api.embedKeys.createEmbedKey({ name, keyPrefix, keyHash: await sha256Hex(key) });","handlingStrategy":"validation","validationCode":"if (!/^wme_[a-f0-9]{5}$/.test(keyPrefix)) throw new Error(`keyPrefix must match wme_ + 5 lowercase hex chars, got: ${keyPrefix}`);","typeGuard":"function isValidEmbedPrefix(p: unknown): p is string {\n  return typeof p === \"string\" && /^wme_[a-f0-9]{5}$/.test(p);\n}","tryCatchPattern":"try {\n  await api.embedKeys.createEmbedKey({ ...args, keyPrefix });\n} catch (e) {\n  if (e instanceof ConvexError && e.data === \"INVALID_PREFIX\") {\n    // regenerate the key client-side with the wme_ + 5-hex prefix format\n  } else throw e;\n}","preventionTips":["Centralize key generation in one helper that emits prefix \"wme_\" + 5 lowercase hex chars","Never reuse the apiKeys.ts prefix convention for embed keys","Lowercase hex before slicing the prefix","Send exactly 5 hex chars, not the full key, as keyPrefix"],"tags":["convex","validation","regex","embed-keys"],"backgroundTag":"invalid-identifier-format","analyzedSha":"7d06c8633d256c18e38133030bc3613976a96ec9","analyzedAt":"2026-09-15T16:44:39.439Z","contentChangedAt":"2026-09-15T16:44:39.439Z","schemaVersion":2},"datasetVersion":"2026-09-15T18:17:12.389Z"}