Skip to content

PER-16680: log through an SDK logger that applies the log settings - #163

Open
EliMoshkovich wants to merge 2 commits into
mainfrom
eli/per-16680-sdk-logging
Open

EliMoshkovich wants to merge 2 commits into
mainfrom
eli/per-16680-sdk-logging

Conversation

@EliMoshkovich

@EliMoshkovich EliMoshkovich commented Oct 27, 2026 •

Copy link
Copy Markdown
Collaborator

Linear: PER-16680

This ports #137 (Zeev) to main, with review follow-ups on top. #137 is stacked on the release-CI draft #136; this PR carries only #137's own changes, so the logging fix can ship in a patch release without that stack.

Changes

  • Start-up line. Creating a client no longer logs its config. It logs api_url and pdp at debug, without any user name or password in them.
  • permit.utils.sdk_logger is what the SDK logs through:
    • It applies log.level and log.label to the SDK's own records, without adding a sink or changing the application's loguru setup.
    • level takes a name in any case, or a loguru severity number. An unknown one is warned about once per process.
  • Redaction.
    • Every client's API key is replaced with [REDACTED] in the records the SDK logs and in the error bodies of the exceptions it raises: the enforcer's, and the REST and PDP local API errors from handle_api_error, text or JSON.
    • A secret shorter than 12 characters isn't replaced, so a short test token doesn't mangle the SDK's text.
    • PermitConfig.token is repr=False.
  • wait_for_sync() builds a client of the same class again, so a subclass's own setup runs. The keep_log_settings context variable keeps the process's log settings unchanged while it does.
  • Concurrency. The SDK's enable/disable switch changes under a lock.
  • Lint. ruff bans calling loguru.logger directly inside the package.
  • README. A Logging section on what each option does.
  • Cleanup. Dropped sync.Permit.__init__, which only called super().

Behaviour change

log.level and log.label used to be ignored. With log={"enable": True} and nothing else, the defaults now apply:

  • DEBUG records are dropped (the default level is info).
  • Each SDK message starts with [Permit] .

Set level="debug" or label="" to keep the old output.

Left as is

  • Process-wide settings. loguru's logger is process-wide, so the last client created decides the log settings for every client, as documented. A logger per client would be a larger redesign.
  • No removal of old keys. The set of keys to redact grows with each distinct key a process uses. That's one entry per key, and removing one could un-redact a client still using it.

Tests

tests/test_fix_logging.py: 62 tests, #137's 53 plus 9 for the follow-ups:

  • REST and PDP API errors with an echoed key, in text and JSON bodies
  • URL user info in the start-up line
  • a subclass in wait_for_sync
  • a short token
  • numeric and padded levels
  • the one-time warning

Ran locally:

  • uv run pytest on the offline suites (tests/test_fix_*.py, test_offline_regressions.py, test_sync_client.py, test_typing_surface.py), pydantic v2: 365 passed, 3 skipped. 2 errors need a live PDP_API_KEY.
  • Same offline suites under pydantic v1, without test_sync_client.py and test_typing_surface.py: 362 passed, 3 skipped.
  • pre-commit on the changed files: ruff, mypy and typos pass.

🤖 Generated with Claude Code

Ported to main from #137 (Zeev Manilovich), which is stacked on the release-CI
draft #136; this carries only #137's own changes.

- The client no longer logs its config on creation; it logs the PDP and API URL.
- permit.utils.sdk_logger applies log.level and log.label to the SDK's records
  without touching the application's loguru sinks, and replaces every client's
  API key with [REDACTED] in what the SDK logs and in PDP error bodies.
- PermitConfig.token is repr=False.
- ruff bans logging straight to loguru inside the package.
- README: what each log option does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 27, 2026 23:05
@linear-code

linear-code Bot commented Oct 27, 2026

Copy link
Copy Markdown

PER-16680

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

@github-actions

Copy link
Copy Markdown

Dependency Security Audit

Scanned: pyproject.toml dependencies + dev group, resolved at Python 3.10 (the current resolution, and the lowest versions the published specs permit under each pydantic major)

❌ 6 fixable HIGH/CRITICAL advisories -- blocking this build.

Severity Count
‼️ CRITICAL 1
🔴 HIGH 5
🔶 MEDIUM 9
⚪ LOW 3
❔ UNKNOWN 17
Severity Package Installed Fixed in Advisory
‼️ CRITICAL uv 0.12.17 0.13.4 GHSA-hq85-5cpj-mr62
🔴 HIGH aiohttp 3.14.3 3.14.4 CVE-2026-111064
🔴 HIGH aiohttp 3.14.3 3.14.4 CVE-2026-111066
🔴 HIGH aiohttp 3.14.3 3.14.4 GHSA-2g87-pp6c-29x5
🔴 HIGH dnspython 2.8.0 2.9.0 CVE-2026-114605
🔴 HIGH filelock 4.1.0 4.2.1 CVE-2026-112940
🔶 MEDIUM aiohttp 3.14.3 3.14.4 CVE-2026-111063
🔶 MEDIUM aiohttp 3.14.3 3.14.4 CVE-2026-111065
🔶 MEDIUM aiohttp 3.14.3 3.14.4 CVE-2026-111748
🔶 MEDIUM aiohttp 3.14.3 3.14.4 CVE-2026-111750
🔶 MEDIUM filelock 4.1.0 4.2.1 CVE-2026-112906
🔶 MEDIUM filelock 4.1.0 4.2.1 CVE-2026-112914
🔶 MEDIUM filelock 4.1.0 4.2.1 CVE-2026-112937
🔶 MEDIUM uv 0.12.17 0.12.18 CVE-2026-104843
🔶 MEDIUM werkzeug 3.1.8 3.1.9 CVE-2026-102598
⚪ LOW aiohttp 3.14.3 3.14.4 CVE-2026-111749
⚪ LOW filelock 4.1.0 4.2.1 CVE-2026-112907
⚪ LOW filelock 4.1.0 4.2.1 CVE-2026-112913
❔ UNKNOWN aiohttp 3.14.3 3.14.4 CVE-2026-111063 (GHSA-494j-54xm-rwp3)
❔ UNKNOWN aiohttp 3.14.3 3.14.4 CVE-2026-111064 (GHSA-5wr8-gcqf-gqf3)
❔ UNKNOWN aiohttp 3.14.3 3.14.4 CVE-2026-111065 (GHSA-6cjc-53gc-5rqm)
❔ UNKNOWN aiohttp 3.14.3 3.14.4 CVE-2026-111066 (GHSA-9xw5-w5jj-pvcg)
❔ UNKNOWN aiohttp 3.14.3 3.14.4 CVE-2026-111748 (GHSA-f9xw-jh8m-3mwq)
❔ UNKNOWN aiohttp 3.14.3 3.14.4 CVE-2026-111749 (GHSA-gx76-c99x-2c96)
❔ UNKNOWN aiohttp 3.14.3 3.14.4 CVE-2026-111750 (GHSA-pqxq-w44r-73xw)
❔ UNKNOWN dnspython 2.8.0 2.9.0 CVE-2026-114605 (GHSA-45c3-73f7-pv4m)
❔ UNKNOWN filelock 4.1.0 4.2.1 CVE-2026-112906 (GHSA-2g95-wg33-c5x2)
❔ UNKNOWN filelock 4.1.0 4.2.1 CVE-2026-112907 (GHSA-567q-5qvw-pf92)
❔ UNKNOWN filelock 4.1.0 4.2.1 CVE-2026-112913 (GHSA-9vhv-jxww-xfm5)
❔ UNKNOWN filelock 4.1.0 4.2.1 CVE-2026-112914 (GHSA-crv4-6mhj-j5jh)
❔ UNKNOWN filelock 4.1.0 4.2.1 CVE-2026-112937 (GHSA-jqc4-5cvw-7w85)
❔ UNKNOWN filelock 4.1.0 4.2.1 CVE-2026-112940 (GHSA-v4x8-3wr5-3wq8)
❔ UNKNOWN uv 0.12.17 0.12.18 PYSEC-2026-4305 (CVE-2026-104843, GHSA-2cv4-cqwr-gwf7)
❔ UNKNOWN uv 0.12.17 0.13.4 PYSEC-2026-4321 (GHSA-hq85-5cpj-mr62)
❔ UNKNOWN werkzeug 3.1.8 3.1.9 PYSEC-2026-4312 (CVE-2026-102598, GHSA-g6x2-hccm-hh4m)
Advisory details

CRITICAL -- GHSA-hq85-5cpj-mr62 (uv 0.12.17)

Found by: trivy:dev-ceiling

uv's bundled CA store trusts an emSign root whose intermediate CA keys are compromised

HIGH -- CVE-2026-111064 (aiohttp 3.14.3)

Found by: trivy:runtime-floor, trivy:runtime-floor-pydantic-v2

aiohttp: aiohttp: Denial of Service via missing initial request timeout

HIGH -- CVE-2026-111066 (aiohttp 3.14.3)

Found by: trivy:runtime-floor, trivy:runtime-floor-pydantic-v2

aiohttp: aiohttp: Denial of Service via file descriptor exhaustion in multipart request processing

HIGH -- GHSA-2g87-pp6c-29x5 (aiohttp 3.14.3)

Found by: pip-audit:runtime-floor, pip-audit:runtime-floor-pydantic-v2, trivy:runtime-floor, trivy:runtime-floor-pydantic-v2

aiohttp: Multiple instances of excessive or unbounded memory/CPU use

HIGH -- CVE-2026-114605 (dnspython 2.8.0)

Found by: trivy:dev-ceiling, trivy:runtime-ceiling, trivy:runtime-floor, trivy:runtime-floor-pydantic-v2

dnspython: dnspython: Denial of Service via excessive DNS compression pointer traversal

HIGH -- CVE-2026-112940 (filelock 4.1.0)

Found by: trivy:dev-ceiling

filelock: filelock: Arbitrary file overwrite via insecure database file reopening

MEDIUM -- CVE-2026-111063 (aiohttp 3.14.3)

Found by: trivy:runtime-floor, trivy:runtime-floor-pydantic-v2

aiohttp: aiohttp: Information disclosure via unenforced Secure attribute on shared cookies

MEDIUM -- CVE-2026-111065 (aiohttp 3.14.3)

Found by: trivy:runtime-floor, trivy:runtime-floor-pydantic-v2

aiohttp: aiohttp: Denial of Service via Max-Age processing overflow

MEDIUM -- CVE-2026-111748 (aiohttp 3.14.3)

Found by: trivy:runtime-floor, trivy:runtime-floor-pydantic-v2

aiohttp: aiohttp: Security control bypass via request scheme spoofing

MEDIUM -- CVE-2026-111750 (aiohttp 3.14.3)

Found by: trivy:runtime-floor, trivy:runtime-floor-pydantic-v2

aiohttp: aiohttp: HTTP request smuggling via ambiguous request framing

MEDIUM -- CVE-2026-112906 (filelock 4.1.0)

Found by: trivy:dev-ceiling

filelock: filelock: Mutual exclusion bypass via improper stale lock removal

MEDIUM -- CVE-2026-112914 (filelock 4.1.0)

Found by: trivy:dev-ceiling

filelock: filelock: Unauthorized file exposure via symbolic link manipulation in SoftFileLock

MEDIUM -- CVE-2026-112937 (filelock 4.1.0)

Found by: trivy:dev-ceiling

filelock is a platform-independent file lock for Python. From 4.1.0 un ...

MEDIUM -- CVE-2026-104843 (uv 0.12.17)

Found by: trivy:dev-ceiling

uv: uv: Arbitrary code execution via malicious package extraction on Windows

MEDIUM -- CVE-2026-102598 (werkzeug 3.1.8)

Found by: trivy:dev-ceiling

werkzeug: werkzeug: Denial of Service via safe_join() Windows special device paths

LOW -- CVE-2026-111749 (aiohttp 3.14.3)

Found by: trivy:runtime-floor, trivy:runtime-floor-pydantic-v2

aiohttp: aiohttp: Host-Only cookie protection bypass via untrusted subdomain

LOW -- CVE-2026-112907 (filelock 4.1.0)

Found by: trivy:dev-ceiling

filelock: On Windows an oplock on a soft lock's marker holds SoftFileLock.acquire() past its timeout and blocks the stale break

LOW -- CVE-2026-112913 (filelock 4.1.0)

Found by: trivy:dev-ceiling

filelock: ReadWriteLock on Windows acts on SQLite side files another local user places beside its database

UNKNOWN -- CVE-2026-111063 (GHSA-494j-54xm-rwp3) (aiohttp 3.14.3)

Found by: pip-audit:runtime-floor, pip-audit:runtime-floor-pydantic-v2

### Summary The Secure attribute was not being enforced for the shared cookie. ### Impact It may be possible to send a shared cookie marked Secure through an unencrypted request.

UNKNOWN -- CVE-2026-111064 (GHSA-5wr8-gcqf-gqf3) (aiohttp 3.14.3)

Found by: pip-audit:runtime-floor, pip-audit:runtime-floor-pydantic-v2

### Summary The keepalive timeout was not used during the initial portion of a request. ### Impact An attacker could keep an idle connection open indefinitely before sending any data. ### Workaround Use a reverse proxy (as recommended for all production deployments) with sensible keepalive limits.

UNKNOWN -- CVE-2026-111065 (GHSA-6cjc-53gc-5rqm) (aiohttp 3.14.3)

Found by: pip-audit:runtime-floor, pip-audit:runtime-floor-pydantic-v2

### Summary An overflow in Max-Age processing can skip closing a connection. ### Impact An attacker may be able to exhaust the connection pool temporarily, until garbage collection resolves the connections.

UNKNOWN -- CVE-2026-111066 (GHSA-9xw5-w5jj-pvcg) (aiohttp 3.14.3)

Found by: pip-audit:runtime-floor, pip-audit:runtime-floor-pydantic-v2

### Summary An attacker can exhaust file descriptors with specially crafted multipart requests. ### Impact If a handler uses `Request.post()`, it may be possible for an attacker to use an unlimited number of file descriptors, potentially resulting in resource exhaustion.

UNKNOWN -- CVE-2026-111748 (GHSA-f9xw-jh8m-3mwq) (aiohttp 3.14.3)

Found by: pip-audit:runtime-floor, pip-audit:runtime-floor-pydantic-v2

### Summary An attacker sending an absolute-form request can spoof the scheme used. ### Impact If user code depends on the `Request.scheme` attribute for anything security related, an attacker may be able to circumvent this logic, tricking the application into thinking a connection is encrypted when it is not, or vice versa. ### Workaround A WAF rule to reject absolute-form requests (legitimate requests should only use this form for proxies).

UNKNOWN -- CVE-2026-111749 (GHSA-gx76-c99x-2c96) (aiohttp 3.14.3)

Found by: pip-audit:runtime-floor, pip-audit:runtime-floor-pydantic-v2

### Summary A subdomain can remove the Host-Only flag from an existing cookie to a parent domain. ### Impact A user accessing untrusted subdomains could see their Host-Only protections stripped from cookies. Given most usecases won't reflect these cookies to a browser, the impact is limited.

UNKNOWN -- CVE-2026-111750 (GHSA-pqxq-w44r-73xw) (aiohttp 3.14.3)

Found by: pip-audit:runtime-floor, pip-audit:runtime-floor-pydantic-v2

### Summary The pure Python parser is vulnerable to an HTTP request smuggling attack. ### Impact If users are using the Python parser (`AIOHTTP_NO_EXTENSIONS`, or not a standard wheel installed), then an application may be vulnerable to a request smuggling attack. ### Workaround Switch to the default C parser.

UNKNOWN -- CVE-2026-114605 (GHSA-45c3-73f7-pv4m) (dnspython 2.8.0)

Found by: pip-audit:dev-ceiling, pip-audit:runtime-ceiling, pip-audit:runtime-floor, pip-audit:runtime-floor-pydantic-v2

### Impact A maliciously crafted DNS message may cause dns.name.from_wire_parser() to use excessive CPU for DNS name decompression. Applications that process untrusted DNS wire form messages with dnspython may be degraded or DoS'd by the extra CPU use required. ### Patches This problem is fixed in dnspython 2.9.0. ### Workarounds There are no known workarounds. ### Credit Evgenios Gkritsis, Department of Informatics, University of Piraeus & Athena Research Center, Greece Constantinos Patsakis, Department of Informatics, University of Piraeus & Athena Research Center, Greece George Stergiopoulos, Department of Informatics, Athens University of Economics and Business, Greece

UNKNOWN -- CVE-2026-112906 (GHSA-2g95-wg33-c5x2) (filelock 4.1.0)

Found by: pip-audit:dev-ceiling

`SoftFileLock` and `MarkerSoftFileLock` evict an unparsable marker (the lock file at `<path>`, whose presence says a process holds the lock) once a waiting contender has watched it unchanged for two seconds. `SoftFileLease` expires a lease the same way. The watch tells two looks apart by the marker's modification time (mtime) and inode number (the kernel's identity for a file, independent of its name) alone. Another local user (the other user below) who can create files in the lock's directory, a sticky `/tmp` included (the sticky bit is a directory flag that lets only a file's owner remove it), parks an empty file of its own at the lock path. It dates that file to a future tick of the kernel's coarse clock (a clock that advances in steps of 4 ms), lets the waiters of the user running the lock (the lock user below) watch it past two seconds, and unlinks it in that tick. On a filesystem that hands a freed inode number straight back (ext4, overlayfs on ext4) the lock user's next marker repeats both values. It inherits the time spent watching the other user's file, and a second waiter removes it while its owner holds the lock. Two of the lock user's processes then run the critical se…

UNKNOWN -- CVE-2026-112907 (GHSA-567q-5qvw-pf92) (filelock 4.1.0)

Found by: pip-audit:dev-ceiling

On Windows the soft locks (`SoftFileLock`, `MarkerSoftFileLock`, `SoftFileLease`, their async forms, and a `FileLock` that fell back to a soft lock) read their marker by path. The marker is the lock file whose presence says a process holds the lock. Each `acquire()` attempt that finds a marker opens it with `os.open` to read the holder's process id, and the lock renames and deletes a marker it judges stale, by path. On Windows each of these calls waits for an oplock. An oplock (opportunistic lock) is a request by a process that holds a handle on a file to be told before another process opens, renames or deletes that file; the other process waits until the holder answers the break or closes its handle. Another local account that can open the marker for reading can take a Level 1 oplock on it while no process holds it open, and leave the break unanswered. Every `acquire()` by the user running the lock (below: the lock user) then waits inside the open, past its `timeout`, `blocking=False` and `cancel_check`, and a stale marker under that oplock is not broken. Mutual exclusion holds and the lock touches no other file. Affected versions are filelock 3.22.0 through 4.2.0 on Windows. The…

UNKNOWN -- CVE-2026-112913 (GHSA-9vhv-jxww-xfm5) (filelock 4.1.0)

Found by: pip-audit:dev-ceiling

On Windows, `ReadWriteLock` and `AsyncReadWriteLock` (the SQLite-backed reader/writer lock) connect SQLite to the lock database by its path, in every directory. SQLite derives three side files from that path: `<database>-journal`, the rollback journal (pages saved to undo an interrupted write); `<database>-wal`, the write-ahead log (changes kept outside the main file); and `<database>-shm`, the shared-memory index of the WAL. SQLite acts on whichever of these it finds beside the database before the lock runs any statement. Another local account that can create files in the database's directory, but not delete files of other accounts there, places such a side file beside the database and denies everyone `DELETE` on it, which a file's creator may do. `C:\Windows\Temp` (the temporary directory of services, granting `BUILTIN\Users` create but not delete) and `C:\Users\Public` are such directories. With a `-wal` in place, every `read_lock()` and `write_lock()` of the user running the lock (below: the lock user) raises `sqlite3.OperationalError: disk I/O error` at once, whatever `timeout` says, until that account removes its file; the lock user cannot. With a `-journal` beside a lock da…

UNKNOWN -- CVE-2026-112914 (GHSA-crv4-6mhj-j5jh) (filelock 4.1.0)

Found by: pip-audit:dev-ceiling

When a soft-lock stale break finds that the marker (the lock file at `<path>`) it renamed aside was a live one recreated by a successor, the restore branch of `break_lock_file()` checks the break name with `lstat` (a check that reads a name without following a symbolic link) and then calls `os.link(break_path, lock_file)` by name. On CPython 3.12 and later on Linux (and on macOS) that call follows a symbolic link (a file that redirects to another path). A local user (the other user below) who can write a shared lock directory without the sticky bit (a directory flag that lets only a file's owner rename or remove it) replaces the break name with a symbolic link between those two calls. The `SoftFileLock`, `MarkerSoftFileLock`, `SoftFileLease` or `lifetime` break of the user running the lock (the lock user below) then creates a hard link (a second name for the same file), in that directory, to any of the lock user's own files on the same filesystem. That includes files inside directories the other user cannot enter and links that `fs.protected_hardlinks` (a Linux setting that forbids hard links to files one does not own) refuses the other user when it asks itself. The link exposes t…

UNKNOWN -- CVE-2026-112937 (GHSA-jqc4-5cvw-7w85) (filelock 4.1.0)

Found by: pip-audit:dev-ceiling

`ReadWriteLock` and `AsyncReadWriteLock` (the SQLite-backed reader/writer lock) remove their private `.filelock-*` link directories with `shutil.rmtree` (a recursive delete) on a remembered path. They do not check that the path still names the directory they created. This happens on the hard-link route (a hard link is a second name for the same file). That route is every connection on macOS since 4.1.1 and any POSIX process without a usable `/proc/self/fd` (a Linux path that names the process's own open files), such as a chroot or container that does not mount `/proc`. Another local user who can rename entries in the directory that holds the lock database can rename one of the directories of the user running the lock (below: the lock user) onto the link directory's name, or onto a forged dead-holder name. Such a lock directory is a `0777` or group-writable cache, build or project directory without the sticky bit (a directory flag that lets only a file's owner remove or rename it). The lock user's process then deletes that tree and everything in it with the lock user's permissions, and reports no error. Stock Linux with `/proc` mounted is not affected. Affected versions are fileloc…

UNKNOWN -- CVE-2026-112940 (GHSA-v4x8-3wr5-3wq8) (filelock 4.1.0)

Found by: pip-audit:dev-ceiling

`ReadWriteLock` and `AsyncReadWriteLock` (the SQLite-backed reader/writer lock) hand SQLite a name they do not control. On Linux the lock validates its database through a descriptor (the number the kernel gives a process for an open file). It then connects SQLite to `/proc/self/fd/<n>`, a Linux path that names the process's own open file `n`. SQLite turns that path back into a plain path and reopens it by name, following symlinks (a symlink is a file that points at another path). On every POSIX route SQLite also acts on `-journal`, `-wal` and `-shm` files it finds beside that name. Those are SQLite's side files: the rollback journal (pages saved to undo an interrupted write), the write-ahead log or WAL (changes kept outside the main file), and the shared-memory index for the WAL. Another local user who can create or rename entries in the directory that holds the lock database can turn this against the user running the lock (below: the lock user). Such a directory is a `0777` or group-writable cache, build or project directory without the sticky bit (a directory flag that lets only a file's owner remove or rename it). That other user can make the lock user's ordinary `read_lock()` …

UNKNOWN -- PYSEC-2026-4305 (CVE-2026-104843, GHSA-2cv4-cqwr-gwf7) (uv 0.12.17)

Found by: pip-audit:dev-ceiling

### Impact In versions of uv from 0.12.7 to 0.12.18 on Windows, uv could be induced into writing a file outside of the installation prefix during wheel installation. A malicious wheel could use this to place an executable outside of the intended environment, including in a directory already present on the user's PATH. This vulnerability only affects Windows hosts; no other platforms are affected. ### Patches uv 0.12.18 and newer address this vulnerability. Users are encouraged to upgrade to 0.12.18. ### Workarounds There is no workaround other than upgrading to uv 0.12.18.

UNKNOWN -- PYSEC-2026-4321 (GHSA-hq85-5cpj-mr62) (uv 0.12.17)

Found by: pip-audit:dev-ceiling

By default, uv verifies TLS connections against a vendored copy of the `webpki-root-certs` bundle. Versions before 0.13.4 include the `emSign ECC Root CA - G3` root. Eight eMudhra intermediate CA certificates that chain to that root have compromised private keys, and the CA has revoked them. An attacker with one of the compromised keys can issue certificates that uv accepts for any hostname, and can be used to issue certificates to enable MITM attacks. uv 0.13.4 removes the root from its vendored bundle, following `webpki-root-certs` 1.0.10. Upgrade to 0.13.4 or later.

UNKNOWN -- PYSEC-2026-4312 (CVE-2026-102598, GHSA-g6x2-hccm-hh4m) (werkzeug 3.1.8)

Found by: pip-audit:dev-ceiling

Werkzeug's `safe_join` function allows Windows device names as filenames when they have an empty ADS marker on NTFS. This was previously reported as https://github.com/pallets/werkzeug/security/advisories/GHSA-hgf8-39gv-g3f2, but the added filtering failed to account for the fact Windows allows special device names with an empty ADS marker, such as `NUL:`. `send_from_directory` uses `safe_join` to safely serve files at user-specified paths under a directory. If the application is running on Windows and NTFS, and the requested path ends with a special device name, the file will be opened successfully, but reading will hang indefinitely.

How to fix

Raise the affected lower bound in pyproject.toml ([project].dependencies, or the pin in the dev dependency group) to at least the Fixed in version above, then run uv lock. Because this package publishes open >= ranges, the floor is what consumers can actually install -- bumping only the resolved version does not close the hole.

This audit resolves without the 7-day publish-age cooldown (exclude-newer) in [tool.uv], but uv lock applies it. If uv lock says the fixed version was filtered by exclude-newer, add exclude-newer-package = { <package> = false } under [tool.uv] in pyproject.toml, run uv lock and commit both files. Once the release is 7 days old, remove the entry and run uv lock again.

An advisory with no fix available does not block the build -- it is reported here so it can be tracked, but no version bump can resolve it. Suppression files are deliberately not honoured: the scan runs with --ignorefile /dev/null so nothing can disappear from this report silently.

… info, wait_for_sync

- handle_api_error scrubs the registered keys from the body it puts in an
  exception, text or JSON, so the REST API and the PDP's local API raise
  without them, as the enforcer already did.
- The start-up line logs api_url and pdp without a user name or password.
- wait_for_sync builds a client of the same class again, so a subclass's own
  setup runs, and keeps the process's log settings through the
  keep_log_settings context variable.
- A secret shorter than 12 characters is not redacted, so a short test token
  doesn't mangle the SDK's text.
- log.level takes a loguru severity number and surrounding whitespace; an
  unknown level is warned about once per process.
- The SDK's enable/disable switch changes under a lock.
- Drop sync.Permit.__init__, which only called super().
- README and docstrings match.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants