{"record":{"id":"dec5f9bd84e85a30","repo":"ruvnet/ruflo","slug":"invalid-schema-name-schema-must-contain-onl","errorCode":null,"errorMessage":"Invalid schema name: \"${schema}\". Must contain only letters, digits, and underscores, and start with a letter or underscore.","messagePattern":"Invalid schema name: \"(.+?)\"\\. Must contain only letters, digits, and underscores, and start with a letter or underscore\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/cli/src/commands/ruvector/pg-utils.ts","lineNumber":28,"sourceCode":" * Allows only ASCII letters, digits, and underscores.\n * Must start with a letter or underscore.\n */\nconst VALID_PG_IDENTIFIER = /^[a-zA-Z_][a-zA-Z0-9_]*$/;\n\n/**\n * Validate a PostgreSQL schema name.\n * Throws if the name contains characters that could enable SQL injection.\n * Safe names are returned as-is (no quoting needed since they match the identifier pattern).\n */\nexport function validateSchemaName(schema: string): string {\n  if (!schema || schema.length === 0) {\n    throw new Error('Schema name must not be empty');\n  }\n  if (schema.length > 63) {\n    throw new Error(`Schema name too long (${schema.length} chars, max 63): \"${schema}\"`);\n  }\n  if (!VALID_PG_IDENTIFIER.test(schema)) {\n    throw new Error(\n      `Invalid schema name: \"${schema}\". Must contain only letters, digits, and underscores, and start with a letter or underscore.`\n    );\n  }\n  return schema;\n}\n\n/**\n * Validate a PostgreSQL timestamp string.\n * Only allows ISO 8601 format to prevent SQL injection via timestamp fields.\n */\nconst VALID_TIMESTAMP = /^\\d{4}-\\d{2}-\\d{2}[T ]\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:?\\d{2})?$/;\n\nexport function validateTimestamp(value: string): string {\n  if (!VALID_TIMESTAMP.test(value)) {\n    throw new Error(`Invalid timestamp format: \"${value}\". Expected ISO 8601.`);\n  }\n  return value;\n}","sourceCodeStart":10,"sourceCodeEnd":46,"githubUrl":"https://github.com/ruvnet/ruflo/blob/6b01dc5a687b26b3e218f796de45ec51f8fa9e8c/v3/@claude-flow/cli/src/commands/ruvector/pg-utils.ts#L10-L46","documentation":"Thrown by validateSchemaName() when the schema fails VALID_PG_IDENTIFIER (`^[a-zA-Z_][a-zA-Z0-9_]*$`). This is a primary SQL-injection guard: schemas are interpolated into DDL/DML, so any character outside the identifier grammar (quotes, semicolons, dashes, dots) is refused rather than escaped.","triggerScenarios":"Passing a schema containing dots (namespace.schema), dashes (kebab-case tenant), quotes, semicolons, spaces, or non-ASCII characters; or a schema starting with a digit.","commonSituations":"Tenant identifiers in kebab-case ('my-org'), fully-qualified names ('ruvector.public'), user input containing whitespace, or internationalized names with non-ASCII characters.","solutions":["Use only ASCII letters, digits, and underscores; start with a letter or underscore.","Convert kebab-case to snake_case: `name.replace(/-/g,'_')`.","Split qualified names and validate each segment separately rather than passing `a.b`."],"exampleFix":"// before\nvalidateSchemaName('my-org')\n// after\nvalidateSchemaName('my_org')","handlingStrategy":"validation","validationCode":"const VALID_PG_IDENTIFIER = /^[a-zA-Z_][a-zA-Z0-9_]*$/;\nfunction toPgIdentifier(name: string): string {\n  const cleaned = name.replace(/[^a-zA-Z0-9_]/g, '_').replace(/^[0-9]+/, '_');\n  if (!VALID_PG_IDENTIFIER.test(cleaned)) {\n    throw new Error(`Invalid schema name: ${name}`);\n  }\n  return cleaned;\n}","typeGuard":"const isPgIdentifier = (v: unknown): v is string =>\n  typeof v === 'string' && /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(v);","tryCatchPattern":"try {\n  validateSchemaName(schema);\n} catch (e) {\n  const msg = e instanceof Error ? e.message : String(e);\n  if (msg.startsWith('Invalid schema name')) {\n    schema = schema.replace(/-/g, '_');\n    validateSchemaName(schema); // retry once, cleaned\n  } else throw e;\n}","preventionTips":["Use snake_case for schema names; never kebab-case.","Validate the identifier at config load, not at SQL time.","Split qualified names and validate each segment."],"tags":["validation","postgres","security","pg-utils","sql-injection"],"backgroundTag":null,"analyzedSha":"6b01dc5a687b26b3e218f796de45ec51f8fa9e8c","analyzedAt":"2026-08-12T13:20:50.148Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}