{"record":{"id":"5c475e6a1a41c6b2","repo":"cocoindex-io/cocoindex","slug":"invalid-neo4j-kind-name-r-must-match-a-za-z","errorCode":null,"errorMessage":"Invalid Neo4j {kind}: {name!r}. Must match [a-zA-Z_][a-zA-Z0-9_]*.","messagePattern":"Invalid Neo4j (.+?): (.+?)\\. Must match \\[a-zA-Z_\\]\\[a-zA-Z0-9_\\]\\*\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"python/cocoindex/connectors/neo4j/_cypher.py","lineNumber":52,"sourceCode":"    \"constraint_name\",\n    \"index_name\",\n    \"validate_identifier\",\n    \"vector_index_name\",\n]\n\n\nIDENTIFIER_RE = re.compile(r\"^[a-zA-Z_][a-zA-Z0-9_]*$\")\n\n\ndef validate_identifier(name: str, kind: str) -> None:\n    \"\"\"Reject anything that isn't ``[a-zA-Z_][a-zA-Z0-9_]*``.\n\n    Cypher labels, property names, and index names cannot be parameter-bound,\n    so untrusted names must be validated at API entry — never escaped at query\n    construction time.\n    \"\"\"\n    if not IDENTIFIER_RE.match(name):\n        raise ValueError(\n            f\"Invalid Neo4j {kind}: {name!r}. Must match [a-zA-Z_][a-zA-Z0-9_]*.\"\n        )\n\n\ndef _quote(name: str) -> str:\n    \"\"\"Backtick-wrap an already-validated identifier for inline use in Cypher.\"\"\"\n    return f\"`{name}`\"\n\n\ndef _key_clause(prefix: str, fields: Sequence[str]) -> str:\n    \"\"\"Build ``{<f1>: $<prefix>_0, <f2>: $<prefix>_1, ...}`` for a MATCH/MERGE pattern.\"\"\"\n    parts = [f\"{_quote(f)}: ${prefix}_{i}\" for i, f in enumerate(fields)]\n    return \"{\" + \", \".join(parts) + \"}\"\n\n\ndef index_name(kind: str, label: str, fields: Sequence[str]) -> str:\n    \"\"\"Deterministic index name for a (kind, label, fields) triple.\n","sourceCodeStart":34,"sourceCodeEnd":70,"githubUrl":"https://github.com/cocoindex-io/cocoindex/blob/e84aa99b3292c5270a4b313b2a7137ad9ce8ab3b/python/cocoindex/connectors/neo4j/_cypher.py#L34-L70","documentation":"Cypher cannot parameter-bind labels, relationship types, property names, or index names — they must be interpolated into the query string. validate_identifier rejects any name not matching ^[a-zA-Z_][a-zA-Z0-9_]*$ at API entry, preventing both malformed Cypher and Cypher injection from untrusted names.","triggerScenarios":"Passing a label, relationship type, property key, or index name containing characters outside [a-zA-Z_][a-zA-Z0-9_]* (spaces, hyphens, dots, backticks, unicode, or an empty string) to any Neo4j connector API that builds Cypher (node/relationship upsert, delete, index create).","commonSituations":"Label derived from a filename or table name containing hyphens ('my-table'); namespaced property names ('meta.title'); user-supplied labels; empty label after string slicing; non-ASCII labels.","solutions":["Rename the label/type/property to match [a-zA-Z_][a-zA-Z0-9_]* (replace '-' and '.' with '_')","Sanitize/normalize dynamic names (e.g. re.sub(r'[^A-Za-z0-9_]', '_', name)) and reject empty results before calling the connector","Use a fixed literal label in code instead of deriving it from untrusted input","Validate early with the same regex (IDENTIFIER_RE) at config-load time"],"exampleFix":"// before\nlabel = f\"doc-{file_ext}\"  # 'doc-pdf' fails validation\n// after\nimport re\nlabel = \"doc_\" + re.sub(r\"[^A-Za-z0-9_]\", \"_\", file_ext)\nfrom cocoindex.connectors.neo4j._cypher import validate_identifier\nvalidate_identifier(label, \"label\")  # passes","handlingStrategy":"validation","validationCode":"import re\nIDENTIFIER_RE = re.compile(r\"^[a-zA-Z_][a-zA-Z0-9_]*$\")\n\ndef safe_label(name: str) -> str:\n    if not IDENTIFIER_RE.match(name):\n        raise ValueError(f\"invalid Neo4j label: {name!r}\")\n    return name","typeGuard":"import re\n_ID = re.compile(r\"^[a-zA-Z_][a-zA-Z0-9_]*$\")\ndef is_valid_identifier(name) -> bool:\n    return isinstance(name, str) and bool(_ID.match(name))","tryCatchPattern":"try:\n    validate_identifier(label, \"label\")\nexcept ValueError as e:\n    logger.error(\"sanitize label before use: %s\", e)\n    label = re.sub(r\"[^A-Za-z0-9_]\", \"_\", label) or \"_default\"\n    validate_identifier(label, \"label\")","preventionTips":["Derive identifiers from trusted constants, never raw filenames or user input","Normalize dynamic names with re.sub(r'[^A-Za-z0-9_]', '_', name) and reject empty results","Validate all label/type/property/index names at config load, before any Cypher is built","Never accept hyphens or dots in Neo4j identifiers — map them to underscores"],"tags":["neo4j","cypher","injection","identifier"],"backgroundTag":"invalid-identifier-format","analyzedSha":"e84aa99b3292c5270a4b313b2a7137ad9ce8ab3b","analyzedAt":"2026-09-08T15:59:19.997Z","contentChangedAt":"2026-09-08T15:59:19.997Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}