From ee806d6dda036befbefc17d29399492e86955f63 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 24 Oct 2026 14:29:09 +0000 Subject: [PATCH] sqlitedb: _is_safe_uid delegates to did.file.is_safe_uid The docstring of did.file.is_safe_uid said SQLiteDB's _is_safe_uid delegated to it, but it was a separate copy of the same predicate. The two bodies agreed, so behaviour is unchanged. _is_safe_uid now calls is_safe_uid, as MATLAB's sqlitedb.isSafeUid calls did.file.isSafeUid, so the check lives in one place. Both bridge entries are updated to match. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_012Dn4gCNCU2jUGdmfPVCAGK --- src/did/did_matlab_python_bridge_file.yaml | 2 +- ..._matlab_python_bridge_implementations.yaml | 11 ++++----- src/did/implementations/sqlitedb.py | 23 ++++++++----------- 3 files changed, 15 insertions(+), 21 deletions(-) diff --git a/src/did/did_matlab_python_bridge_file.yaml b/src/did/did_matlab_python_bridge_file.yaml index 3928c14..08be096 100644 --- a/src/did/did_matlab_python_bridge_file.yaml +++ b/src/did/did_matlab_python_bridge_file.yaml @@ -1047,7 +1047,7 @@ functions: empty, whitespace-padded, '.'/'..', any path separator, NUL, or anything fileparts / os.path.basename splits. The public form; SQLiteDB._is_safe_uid pre-existed for the same purpose (per - DID-python #58) and both now enforce the same predicate. + DID-python #58) and, since 2026-10-24, delegates here. # ========================================================================= # Not tracked by a bridge entry diff --git a/src/did/did_matlab_python_bridge_implementations.yaml b/src/did/did_matlab_python_bridge_implementations.yaml index d3c84e7..eec508d 100644 --- a/src/did/did_matlab_python_bridge_implementations.yaml +++ b/src/did/did_matlab_python_bridge_implementations.yaml @@ -723,12 +723,11 @@ classes: python_name: _is_safe_uid kind: static decision_log: > - A module-level function in did/implementations/sqlitedb.py. MATLAB - delegates to did.file.isSafeUid; Python's _is_safe_uid is a - separate copy of the same predicate as did.file.is_safe_uid (see - the isSafeUid entry in did_matlab_python_bridge_file.yaml). The - is_safe_uid docstring says the sqlitedb one delegates to it; it - does not, though the two bodies agree today. + A module-level function in did/implementations/sqlitedb.py that + delegates to did.file.is_safe_uid, as MATLAB's delegates to + did.file.isSafeUid (see the isSafeUid entry in + did_matlab_python_bridge_file.yaml). It was a separate copy of the + same predicate until 2026-10-24. - name: containsTraversal python_name: ~ diff --git a/src/did/implementations/sqlitedb.py b/src/did/implementations/sqlitedb.py index fdc3189..8e17c80 100644 --- a/src/did/implementations/sqlitedb.py +++ b/src/did/implementations/sqlitedb.py @@ -12,6 +12,7 @@ from datetime import datetime, timezone from ..database import Database +from ..file import is_safe_uid def _sqlite_regexp(pattern, string): @@ -163,23 +164,17 @@ def _is_safe_uid(uid): """Is ``uid`` safe to use as a filename inside FileDir? A uid stands in for a file basename under ``/``, so any - value that would leave that directory once joined -- a path separator, - a dot segment, a NUL, an empty or whitespace-padded string -- has to - be refused. Otherwise a document whose ``file_list[i].locations[j].uid`` - is ``"../../.ssh/authorized_keys"`` reaches ``os.path.join(file_dir, uid)`` + value that would leave that directory once joined has to be refused. + Otherwise a document whose ``file_list[i].locations[j].uid`` is + ``"../../.ssh/authorized_keys"`` reaches ``os.path.join(file_dir, uid)`` and either writes to, or reads from, an attacker-chosen path outside FileDir. See DID-python issue #58. + + Delegates to :func:`did.file.is_safe_uid`, as MATLAB's + ``sqlitedb.isSafeUid`` delegates to ``did.file.isSafeUid``, so the + predicate lives in one place. """ - if not isinstance(uid, str) or not uid: - return False - if uid.strip() != uid: - return False - if uid in (".", ".."): - return False - if "/" in uid or "\\" in uid or "\x00" in uid: - return False - # Catches any other separator the running OS honours (e.g. NT altsep). - return os.path.basename(uid) == uid + return is_safe_uid(uid) def _is_within(root, path):