{"record":{"id":"0cd40567a00a008f","repo":"paperclipai/paperclip","slug":"invalid-discord-form-token","errorCode":null,"errorMessage":"Invalid Discord form token","messagePattern":"Invalid Discord form token","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"server/src/services/chat-discord-question-forms.ts","lineNumber":58,"sourceCode":"}\n\nconst token = (value: unknown, prefix: string, length = 22): value is string =>\n  typeof value === \"string\" &&\n  new RegExp(`^${prefix}[A-Za-z0-9_-]{${length}}$`).test(value);\n\nexport function isDiscordQuestionFormCorrectionId(\n  value: unknown,\n): value is string {\n  return token(value, \"pcfr:\", 43);\n}\n\nfunction correctionId(\n  scope: ChatSdkStateScope,\n  owner: DiscordQuestionFormDraftOwner,\n  submitActionId: string,\n) {\n  if (!token(submitActionId, \"pcfs:\"))\n    throw new Error(\"Invalid Discord form token\");\n  // Includes the high-entropy secret submit token, not merely public IDs. One\n  // overwritable row per form+actor bounds retry storage without persisting a\n  // Discord interaction token or raw callback envelope.\n  return `pcfr:${createHash(\"sha256\")\n    .update(\n      JSON.stringify([\n        scope.companyId,\n        scope.endpointId,\n        submitActionId,\n        owner.principalId,\n        owner.userId,\n        owner.externalUserId,\n      ]),\n    )\n    .digest(\"base64url\")}`;\n}\n\nfunction validDraft(","sourceCodeStart":40,"sourceCodeEnd":76,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/server/src/services/chat-discord-question-forms.ts#L40-L76","documentation":"correctionId() in chat-discord-question-forms.ts derives the storage key for a question-form correction retry from a submitActionId that must be a high-entropy token of the exact form 'pcfs:' + 22 chars of [A-Za-z0-9_-]. Anything else throws 'Invalid Discord form token', refusing to build a correction id from an untrusted identifier.","triggerScenarios":"Calling correctionId (via actionId, key, or loadDiscordQuestionFormCorrection) with a submitActionId that is not a string, lacks the 'pcfs:' prefix, has the wrong length (≠22 chars after the prefix), or contains characters outside [A-Za-z0-9_-] — e.g. a raw Discord interaction/custom-id fragment or a truncated/partially-decoded action id.","commonSituations":"Parsing a Discord component custom_id with the wrong split so the tail still contains separators; passing a legacy/other-format action id; a client submitting a forged or truncated token; storing/round-tripping the action id through something that altered its length or encoding (URL-encoding, trimming).","solutions":["Verify the submitActionId was produced by the form-issuing path (isDiscordQuestionForm-style token check) before calling loadDiscordQuestionFormCorrection.","Fix custom-id parsing so the 'pcfs:...' token is extracted intact (no URL-decoding/trimming/splitting damage).","Reject bad tokens at the interaction handler entry and re-issue a fresh form instead of attempting correction lookup.","Log the received token shape (prefix/length only) to identify where corruption happens."],"exampleFix":"// before\nconst submitActionId = customId.split(\":\").pop(); // may mangle token\n// after\nif (!isDiscordQuestionFormSubmitActionId(customId)) return respondInvalid();\nconst submitActionId = customId.slice(\"pcqf:\".length); // exact token extraction","handlingStrategy":"validation","validationCode":"const isSubmitActionToken = (v: unknown): v is string =>\n  typeof v === \"string\" && /^pcfs:[A-Za-z0-9_-]{22}$/.test(v);\nif (!isSubmitActionToken(submitActionId)) throw new Error(\"malformed pcfs token before correction lookup\");","typeGuard":"const isDiscordFormSubmitToken = (v: unknown): v is string =>\n  typeof v === \"string\" && /^pcfs:[A-Za-z0-9_-]{22}$/.test(v);","tryCatchPattern":"try { const correction = await loadDiscordQuestionFormCorrection(scope, owner, submitActionId); }\ncatch (e) {\n  if ((e as Error).message === \"Invalid Discord form token\") { return respondWithFreshForm(); }\n  throw e;\n}","preventionTips":["Extract pcfs tokens from custom_ids with exact slicing, not lossy split/decode operations.","Validate action ids at the interaction-handler entry before any storage lookup.","Re-issue fresh forms instead of retrying lookups with corrupt tokens.","Never round-trip tokens through encodings that change length or characters."],"tags":["validation","discord","security","identifier"],"backgroundTag":"invalid-identifier-format","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}