Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/did/did_matlab_python_bridge_file.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 5 additions & 6 deletions src/did/did_matlab_python_bridge_implementations.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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: ~
Expand Down
23 changes: 9 additions & 14 deletions src/did/implementations/sqlitedb.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from datetime import datetime, timezone

from ..database import Database
from ..file import is_safe_uid


def _sqlite_regexp(pattern, string):
Expand Down Expand Up @@ -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 ``<FileDir>/<uid>``, 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):
Expand Down
Loading