{"record":{"id":"2d9c9349e2790c15","repo":"koala73/worldmonitor","slug":"invalid-field-touppercase","errorCode":null,"errorMessage":"INVALID_${field.toUpperCase()}","messagePattern":"INVALID_(.+?)","errorType":"validation","errorClass":"ConvexError","httpStatus":null,"severity":"warning","filePath":"convex/companyMonitoring/_shared.ts","lineNumber":24,"sourceCode":"  type NormalizedMonitoredCompanyInput,\n} from \"../../shared/company-monitoring-contract\";\n\nexport const COMPANY_LIMIT = COMPANY_MONITORING_LIMITS.maxCompaniesPerAccount;\nexport const COMPANY_MONITORING_CLAIM_POLICY_VERSION = 1;\nconst CROCKFORD = \"0123456789ABCDEFGHJKMNPQRSTVWXYZ\";\nconst REQUEST_CONTROL = /[\\u0000-\\u001f\\u007f-\\u009f\\u00ad\\u061c\\u180e\\u200b-\\u200f\\u2028-\\u202e\\u2060-\\u206f\\ufeff\\ufff9-\\ufffb]/u;\n\ntype CompanyMonitoringCtx = MutationCtx | QueryCtx;\n\nexport function hasCurrentCompanyMonitoringClaimPolicy(\n  account: Pick<Doc<\"companyMonitoringAccounts\">, \"claimPolicyVersion\">,\n): boolean {\n  return (account.claimPolicyVersion ?? 0) >= COMPANY_MONITORING_CLAIM_POLICY_VERSION;\n}\n\nexport function normalizeRequestId(value: string, field = \"clientRequestId\"): string {\n  if (typeof value !== \"string\" || REQUEST_CONTROL.test(value)) {\n    throw new ConvexError(`INVALID_${field.toUpperCase()}`);\n  }\n  const normalized = value.normalize(\"NFC\").trim();\n  if (!normalized || new TextEncoder().encode(normalized).byteLength > 64) {\n    throw new ConvexError(`INVALID_${field.toUpperCase()}`);\n  }\n  return normalized;\n}\n\nexport async function fingerprint(value: unknown): Promise<string> {\n  const bytes = new TextEncoder().encode(JSON.stringify(value));\n  const digest = await crypto.subtle.digest(\"SHA-256\", bytes);\n  return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\nexport function randomFence(): string {\n  const bytes = new Uint8Array(32);\n  crypto.getRandomValues(bytes);\n  return Array.from(bytes, (byte) => byte.toString(16).padStart(2, \"0\")).join(\"\");","sourceCodeStart":6,"sourceCodeEnd":42,"githubUrl":"https://github.com/koala73/worldmonitor/blob/ffec79ac339946fd2d24e85845da5755dcaa534b/convex/companyMonitoring/_shared.ts#L6-L42","documentation":"Thrown by normalizeRequestId (convex/companyMonitoring/_shared.ts) when the input value is not a string or matches the REQUEST_CONTROL regex (control characters U+0000-U+001F, DEL, bidi/format overrides U+200B-U+202E, zero-width, etc.). The thrown ConvexError message is INVALID_<FIELD> where field defaults to 'clientRequestId', producing 'INVALID_CLIENTREQUESTID'. This is an input-sanitization guard against hidden control/bidi characters that could corrupt logging or indexing.","triggerScenarios":"A client mutation (e.g. in companies.ts:111) calls normalizeRequestId(args.clientRequestId) and the caller passes a string containing a zero-width space, RTL override, or raw control character. The value is not a string at all (e.g. null passed through a loosely typed client).","commonSituations":"A user pastes an id from a rich-text editor or chat client that injects zero-width characters. A bidi-spoofing attempt in a malicious clientRequestId. A client serializes an object instead of a string. Copy-paste from a document with non-breaking hyphens or soft hyphens (U+00AD).","solutions":["Strip or reject control/bidi characters on the client before sending: run the value through the same REQUEST_CONTROL regex (or a general control-char strip) prior to the Convex call.","Ensure the value is a plain string — do not pass objects, numbers, or null as clientRequestId.","If the id is generated client-side, generate it from a safe alphabet (alphanumeric + ._:- ) to avoid embedding problematic characters."],"exampleFix":"// before — client sends raw user input\nconst id = clipboardText;\n// after — strip control/bidi chars client-side\nconst REQUEST_CONTROL = /[\\u0000-\\u001f\\u007f-\\u009f\\u00ad\\u061c\\u180e\\u200b-\\u200f\\u2028-\\u202e\\u2060-\\u206f\\ufeff\\ufff9-\\ufffb]/u;\nconst id = typeof clipboardText === 'string' && !REQUEST_CONTROL.test(clipboardText) ? clipboardText : crypto.randomUUID();","handlingStrategy":"validation","validationCode":"const REQUEST_CONTROL = /[\\u0000-\\u001f\\u007f-\\u009f\\u00ad\\u061c\\u180e\\u200b-\\u200f\\u2028-\\u202e\\u2060-\\u206f\\ufeff\\ufff9-\\ufffb]/u;\nfunction isSafeRequestId(value: unknown): value is string {\n  return typeof value === \"string\" && !REQUEST_CONTROL.test(value);\n}\n// before the Convex call:\nif (!isSafeRequestId(clientRequestId)) clientRequestId = crypto.randomUUID();","typeGuard":"function isSafeRequestId(value: unknown): value is string {\n  const REQUEST_CONTROL = /[\\u0000-\\u001f\\u007f-\\u009f\\u00ad\\u061c\\u180e\\u200b-\\u200f\\u2028-\\u202e\\u2060-\\u206f\\ufeff\\ufff9-\\ufffb]/u;\n  return typeof value === \"string\" && !REQUEST_CONTROL.test(value);\n}","tryCatchPattern":null,"preventionTips":["Generate clientRequestId from a safe alphabet client-side (UUID, ULID, or alphanumeric slug).","Strip control/bidi characters from user-supplied ids before sending.","Never pass objects, numbers, or null as clientRequestId."],"tags":["validation","input-sanitization","convex","security","unicode"],"backgroundTag":null,"analyzedSha":"ffec79ac339946fd2d24e85845da5755dcaa534b","analyzedAt":"2026-08-12T11:24:56.012Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}