Skip to content

Claude/jolly kirch 89a1f6#15

Merged
jirhiker merged 2 commits into
mainfrom
claude/jolly-kirch-89a1f6
May 10, 2026
Merged

Claude/jolly kirch 89a1f6#15
jirhiker merged 2 commits into
mainfrom
claude/jolly-kirch-89a1f6

Conversation

@jirhiker
Copy link
Copy Markdown

Summary

  • Describe the user-facing or developer-facing change.
  • Keep this focused on what changed and why.

Context

  • Why is this change needed?
  • What problem does it solve?

Verification

  • Tests added or updated where appropriate
  • Local verification completed
  • CI is expected to pass

Verification details:

  • ...

Risk

  • Note behavioral risk, migration risk, hardware risk, or data risk.
  • If low risk, say so explicitly.

Target Branch Check

  • This PR targets the branch it was created from logically (develop, the active dev/* stream, release/*, or hotfix/*)

Follow-ups

  • Optional follow-up work, cleanup, or known limitations.

jirhiker and others added 2 commits May 9, 2026 17:22
Adds a QR encoding of `verification_url_complete` (the verification URL
with the user_code pre-filled) to the device-code enrollment pane.
Admin scans the workstation screen with a phone instead of typing the
URL + short code by hand — useful when the workstation and admin are
in the same room but no shared input device.

- pychron/cloud/qr.py — new module. `make_qr_png` writes a segno-
  generated PNG (error-correction M, scale 8, border 2 — readable from
  arm's length) to a caller-supplied path with 0600 permissions on
  POSIX. `make_qr_for_device_code` is the convenience wrapper that
  emits `~/.pychron/qr/device_<lab>.png`, overwriting any prior file
  for that lab so a re-enrollment does not pick up a stale QR.

- pychron/cloud/tasks/preferences.py — new `_pending_qr_path = File`
  trait (excluded from .cfg), populated in `_on_device_code_user_code`
  alongside the existing user_code + URL fields. QR generation
  failure is non-fatal — the typed code + URL still work. Reset in
  the start handler and in `_reset_pending` so the QR clears on
  cancel / success / terminal failure.

- pyproject.toml — adds `segno>=1.6,<2` (pure Python, ~24KB, writes
  PNG via stdlib zlib — no PIL required).

- test/cloud/test_qr.py — 5 unit tests: PNG magic bytes, file lands
  under `~/.pychron/qr/`, re-enrollment overwrites rather than
  accumulating, empty-URL rejected, default host slug.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Address path-traversal finding from /security-review on PR #14.

`make_qr_for_device_code(host_slug=...)` previously interpolated the
slug directly into the output filename (`device_<slug>.png`). The
caller in `tasks/preferences.py` passes `self.lab_name`, which can
hold an attacker-influenced value (server-issued after a prior
enrollment, or a hand-edited preference) carrying `..` / `/` / null
bytes. The resulting file would land outside the scoped
`~/.pychron/qr/` directory.

Two-layer fix:

1. `_sanitize_slug` whitelists `[A-Za-z0-9_-]` at function entry; any
   other byte becomes `_`. This collapses traversal payloads to
   inert filenames before they reach the path layer.

2. After path construction, `os.path.realpath(out_path)` is asserted
   to live under `os.path.realpath(qr_dir())`. A defense-in-depth
   guard so any future slug-handling regression cannot escape the
   scoped directory.

Tests cover the traversal payloads (`../../etc/passwd`, `..`,
`../../tmp/owned`, `a/b/c`, `lab.name`, `foo\x00bar`) plus the
preservation of normal slugs (`NMGRL`, `lab-2024_NM`).

138/138 cloud tests green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jirhiker jirhiker merged commit 286ae40 into main May 10, 2026
3 of 4 checks passed
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.

1 participant