{"record":{"id":"0423ec717325de68","repo":"lfnovo/open-notebook","slug":"invalid-kind-name-value-r","errorCode":null,"errorMessage":"Invalid {kind} name: {value!r}","messagePattern":"Invalid (.+?) name: (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":400,"severity":"error","filePath":"open_notebook/database/repository.py","lineNumber":31,"sourceCode":"# Keep the internal SurrealDB websocket out of any configured HTTP proxy\n# (issue #1160). Runs at import time - i.e. before any db_connection() can be\n# opened - so it protects every entrypoint (API + worker) that touches the DB.\nensure_internal_no_proxy()\n\nT = TypeVar(\"T\", Dict[str, Any], List[Dict[str, Any]])\n\n# Bare SurrealDB table/relation identifier: no ':', whitespace, or query\n# syntax. Used to validate the parts of RELATE/UPSERT/UPDATE that name a\n# table or edge-relation and therefore can't be bound as a query parameter\n# (SurrealQL only allows binding record/table *values*, not identifiers in\n# that position).\n_IDENTIFIER_RE = re.compile(r\"^[a-zA-Z_][a-zA-Z0-9_]*$\")\n\n\ndef _ensure_safe_identifier(value: str, kind: str) -> str:\n    \"\"\"Validate a table/relationship name before it is interpolated into a query.\"\"\"\n    if not isinstance(value, str) or not _IDENTIFIER_RE.match(value):\n        raise ValueError(f\"Invalid {kind} name: {value!r}\")\n    return value\n\n\ndef _get_env_or_default(name: str, default: str) -> str:\n    value = os.getenv(name)\n    return value if value else default\n\n\ndef get_database_url() -> str:\n    \"\"\"Get database URL with backward compatibility\"\"\"\n    surreal_url = os.getenv(\"SURREAL_URL\")\n    if surreal_url:\n        return surreal_url\n\n    # Fallback to old format - WebSocket URL format\n    address = os.getenv(\"SURREAL_ADDRESS\", \"localhost\")\n    port = os.getenv(\"SURREAL_PORT\", \"8000\")\n    return f\"ws://{address}/rpc:{port}\"","sourceCodeStart":13,"sourceCodeEnd":49,"githubUrl":"https://github.com/lfnovo/open-notebook/blob/a7de90d38aaf18ee85fd661854d35c11e44613e2/open_notebook/database/repository.py#L13-L49","documentation":"_ensure_safe_identifier() validates table/relationship names interpolated into SurrealQL and rejects anything not matching ^[a-zA-Z_][a-zA-Z0-9_]*$. It is a SQL/SurrealQL injection guard: repo_relate/repo_upsert/repo_update build query strings from these names, so unsafe values (spaces, punctuation, digits first, non-strings, f-string built names) are refused before reaching the database.","triggerScenarios":"Passing a table or relationship name with a space, hyphen, dot, or leading digit to repo_relate/repo_upsert/repo_update; passing None or a non-string (e.g. a RecordID or enum object) where a name is expected; dynamically building relationship names from untrusted input.","commonSituations":"Dynamically constructing relationship names from note types or user input; passing a full record id ('note:xyz') instead of a table name; refactors that change a constant into an f-string; SurrealDB records created with quoted/escaped identifiers that don't match the regex.","solutions":["Fix the caller to pass a plain identifier: letters, digits, underscores, not starting with a digit","If the name comes from user input, whitelist it against known tables/relationships before calling the repo function","Check for accidental RecordID/None being passed where a string name is expected","If you truly need exotic table names, rename the table to a safe identifier at the schema level"],"exampleFix":"// before\nawait repo_relate(src, \"linked-to note\", tgt)\n// after\nawait repo_relate(src, \"linked_to_note\", tgt)","handlingStrategy":"validation","validationCode":"import re\nIDENT = re.compile(r'^[a-zA-Z_][a-zA-Z0-9_]*$')\ndef safe_name(n: str) -> bool:\n    return isinstance(n, str) and bool(IDENT.match(n))\nassert safe_name(relationship) before calling repo_relate","typeGuard":"def is_safe_identifier(value) -> bool:\n    return isinstance(value, str) and bool(_IDENTIFIER_RE.match(value))","tryCatchPattern":"try:\n    await repo_relate(src, rel, tgt)\nexcept ValueError as e:\n    if 'Invalid' in str(e) and 'name' in str(e):\n        raise InvalidInputError(str(e))  # 400 to client\n    raise","preventionTips":["Never build table/relationship names from raw user input","Keep table names as module-level constants","Never pass RecordID objects where a table name is expected"],"tags":["validation","sql-injection","surrealdb","security","identifiers"],"backgroundTag":"unsafe-sql-identifier","analyzedSha":"a7de90d38aaf18ee85fd661854d35c11e44613e2","analyzedAt":"2026-08-27T02:39:58.166Z","schemaVersion":2},"datasetVersion":"2026-08-27T03:17:27.898Z"}