{"record":{"id":"44e2327361b8b70d","repo":"cocoindex-io/cocoindex","slug":"invalid-kind-name-r","errorCode":null,"errorMessage":"Invalid {kind}: {name!r}","messagePattern":"Invalid (.+?): (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"python/cocoindex/connectors/postgres/_target.py","lineNumber":87,"sourceCode":"\n# asyncpg enforces a protocol limit of 32767 bind parameters per query.\n_BIND_LIMIT: int = 32767\n\n\n_IDENTIFIER_RE = re.compile(r\"^[A-Za-z_][A-Za-z0-9_]*$\")\n\n\ndef _validate_identifier(name: str, kind: str = \"identifier\") -> None:\n    \"\"\"Reject identifiers outside the unquoted-identifier allow-list.\n\n    PostgreSQL identifiers are quoted with double quotes when interpolated, but\n    quoting alone does not prevent injection if the input itself contains a\n    double-quote character. Mirroring the Doris connector's approach\n    (CVE-2026-28438), we error out immediately on anything that isn't a plain\n    unquoted identifier.\n    \"\"\"\n    if not isinstance(name, str) or not _IDENTIFIER_RE.match(name):\n        raise ValueError(f\"Invalid {kind}: {name!r}\")\n\n\ndef _qualified_table_name(table_name: str, pg_schema_name: str | None) -> str:\n    \"\"\"Return a properly quoted (optionally schema-qualified) table name.\"\"\"\n\n    if pg_schema_name:\n        return f'\"{pg_schema_name}\".\"{table_name}\"'\n    return f'\"{table_name}\"'\n\n\nclass PgType(NamedTuple):\n    \"\"\"\n    Annotation to specify a PostgreSQL column type.\n\n    Use with `typing.Annotated` to override the default type mapping:\n\n    ```python\n    from typing import Annotated","sourceCodeStart":69,"sourceCodeEnd":105,"githubUrl":"https://github.com/cocoindex-io/cocoindex/blob/e84aa99b3292c5270a4b313b2a7137ad9ce8ab3b/python/cocoindex/connectors/postgres/_target.py#L69-L105","documentation":"Table and schema identifiers used in generated SQL must be plain unquoted SQL identifiers (matching _IDENTIFIER_RE). Because identifier quoting alone cannot neutralize an embedded double-quote, anything that is not a plain identifier is rejected to prevent SQL injection (mirroring the Doris connector's CVE fix).","triggerScenarios":"Calling table_target (or constructing a PgTableTarget) with a table_name or pg_schema_name containing characters like quotes, spaces, dots, or hyphens, or passing a non-string.","commonSituations":"Building table names by string concatenation from user input; including schema in table_name like 'myschema.table' instead of using pg_schema_name; names copied from quoted DDL.","solutions":["Use a plain identifier: letters, digits, underscores, not starting with a digit.","Pass the schema separately via pg_schema_name instead of 'schema.table'.","Sanitize or reject user-supplied names against a regex like ^[A-Za-z_][A-Za-z0-9_]*$ before calling the API."],"exampleFix":"// before\ntable_target(\"orders-2024\")\n// after\ntable_target(\"orders_2024\")","handlingStrategy":"validation","validationCode":"import re\n_ID = re.compile(r\"^[A-Za-z_][A-Za-z0-9_]*$\")\nassert _ID.match(table_name), f\"Invalid table name: {table_name!r}\"","typeGuard":"import re\n_IDENTIFIER_RE = re.compile(r\"^[A-Za-z_][A-Za-z0-9_]*$\")\ndef is_safe_identifier(name: object) -> bool:\n    return isinstance(name, str) and bool(_IDENTIFIER_RE.match(name))","tryCatchPattern":"try:\n    target = table_target(user_supplied_name)\nexcept ValueError as e:\n    if e.args and e.args[0].startswith(\"Invalid \"):\n        raise ValueError(\"Table name must be a plain identifier\") from e","preventionTips":["Never interpolate user input into table/schema names; whitelist allowed names.","Use pg_schema_name for the schema instead of 'schema.table' strings.","Normalize generated names (e.g. replace '-' with '_') before passing them."],"tags":["python","sql","injection","validation"],"backgroundTag":"invalid-identifier","analyzedSha":"e84aa99b3292c5270a4b313b2a7137ad9ce8ab3b","analyzedAt":"2026-09-08T15:59:19.997Z","contentChangedAt":"2026-09-08T15:59:19.997Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}