{"record":{"id":"06fe1d94a066e94c","repo":"Hmbown/CodeWhale","slug":"invalid-draft-id","errorCode":null,"errorMessage":"invalid draft id","messagePattern":"invalid draft id","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"web/lib/community-agent.ts","lineNumber":65,"sourceCode":"\nexport interface UsageLog {\n  date: string;\n  calls: number;\n  inputTokens: number;\n  outputTokens: number;\n}\n\nexport interface DeepSeekEnv {\n  baseUrl?: string;\n  model?: string;\n}\n\nconst AGENT_DRAFT_TYPE_SET = new Set<string>(AGENT_DRAFT_TYPES);\nconst DRAFT_ID_PATTERN = /^[A-Za-z0-9._-]{1,128}$/;\n\nexport function draftKey(type: AgentDraftType, id: string): string {\n  if (!DRAFT_ID_PATTERN.test(id)) {\n    throw new Error(\"invalid draft id\");\n  }\n  return `draft:${type}:${id}`;\n}\n\nexport function parseDraftKey(key: string): { type: AgentDraftType; id: string } | null {\n  const match = /^draft:([^:]+):([^:]+)$/.exec(key);\n  if (!match || !AGENT_DRAFT_TYPE_SET.has(match[1]) || !DRAFT_ID_PATTERN.test(match[2])) {\n    return null;\n  }\n  return { type: match[1] as AgentDraftType, id: match[2] };\n}\n\nexport function isAgentDraft(value: unknown): value is AgentDraft {\n  if (!value || typeof value !== \"object\") return false;\n  const draft = value as Record<string, unknown>;\n  return (\n    typeof draft.id === \"string\" &&\n    DRAFT_ID_PATTERN.test(draft.id) &&","sourceCodeStart":47,"sourceCodeEnd":83,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/web/lib/community-agent.ts#L47-L83","documentation":"draftKey builds a KV key of the form `draft:<type>:<id>` but first validates the id against DRAFT_ID_PATTERN (/^[A-Za-z0-9._-]{1,128}$/). An id containing colons, slashes, whitespace, unicode, or longer than 128 chars throws \"invalid draft id\". This prevents malformed or injection-prone ids from entering the KV namespace and keeps parseDraftKey round-tripping.","triggerScenarios":"Calling draftKey(type, id) with an id containing ':' or '/' (e.g. a URL path or a UUID with braces), an empty string, whitespace, or a string longer than 128 characters.","commonSituations":"Passing a full key like 'draft:plan:abc' back in as the id; ids derived from user input (names, emails) with illegal characters; untrimmed input carrying newlines; accepting ids from untrusted POST bodies.","solutions":["Sanitize or slugify the id to [A-Za-z0-9._-] before calling draftKey.","If the caller holds a full key, use parseDraftKey to extract the id instead of passing the key string as an id.","Trim and cap input length to 128 characters at the API boundary.","Return a 400 with a clear field error when user-supplied ids fail the pattern instead of letting it throw."],"exampleFix":"// before\ndraftKey(type, rawUserId);\n\n// after\nconst id = rawUserId.trim().replace(/[^A-Za-z0-9._-]/g, '_').slice(0, 128);\ndraftKey(type, id);","handlingStrategy":"validation","validationCode":"const DRAFT_ID_OK = /^[A-Za-z0-9._-]{1,128}$/;\nif (!DRAFT_ID_OK.test(id)) return badRequest('draft id must match [A-Za-z0-9._-]{1,128}');","typeGuard":"const isValidDraftId = (id) => typeof id === 'string' && /^[A-Za-z0-9._-]{1,128}$/.test(id);","tryCatchPattern":null,"preventionTips":["Slugify or hash user-derived ids before constructing draft keys.","Never pass a full KV key where an id is expected — use parseDraftKey.","Trim input at the API boundary and reject with 400 on pattern mismatch.","Cap id length to 128 in the input schema (e.g. zod: z.string().max(128).regex(...))."],"tags":["validation","kv","input"],"backgroundTag":"invalid-argument-format","analyzedSha":"433685b2024e7bc4c99e1e2e326bcad39b4d9d65","analyzedAt":"2026-09-15T12:24:24.634Z","contentChangedAt":"2026-09-15T12:24:24.634Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}