Skip to content

Data-safety fixes, real supply-chain verification, packaging metadata, CI green#20

Merged
offbyonebit merged 1 commit into
mainfrom
clipsync-polish
Jun 18, 2026
Merged

Data-safety fixes, real supply-chain verification, packaging metadata, CI green#20
offbyonebit merged 1 commit into
mainfrom
clipsync-polish

Conversation

@offbyonebit

Copy link
Copy Markdown
Owner

Summary

  • Fixes real bugs: config corruption clobbering, a TOCTOU file-receive race, missing Luhn checksum validation on pasted device IDs, and replaces a near-useless single-entry hardcoded Syncthing binary hash with real verification against the official sha256sum.txt.asc for every published platform/arch.
  • pyproject.toml had no [build-system]/[project] tables at all in this repo's history — added them, reading version from clipsync.__version__. Confirmed uv build now produces offbyonebit-clipsync-0.2.2, matching what's live on PyPI.
  • CI has been broken at the Lint step since 2026-05-22 (and a separate lint break landed on main afterward too) — fixed every ruff lint/format violation and all 12 mypy errors. Also root-caused real (non-flaky) failures in four test files: on a real X11 desktop, ClipboardSync.start() opportunistically starts a genuine XFixes watcher tied to the actual system clipboard, so these tests' faked clipboards never triggered a sync after the initial tick. Fixed by forcing the polling fallback in the test fixtures.
  • Added pure-logic test coverage for crypto, history, pairing, and the new hash verification.

Test plan

  • ruff check clean
  • ruff format --check clean
  • mypy clipsync/ clean (0 errors, down from 12)
  • pytest tests/ -q (excluding the gitignored display-dependent tests/integration/) — 123 passed
  • uv build produces correctly-named/versioned wheel and sdist
  • CI green on the actual GitHub Actions runner

…data, CI

Bug fixes:
- config.py: _load no longer overwrites a corrupted/non-object settings
  file with defaults, and only persists when the on-disk file is actually
  incomplete. Same guard added to reload().
- main.py: _on_file_received uses O_EXCL to atomically claim the
  destination filename, closing a TOCTOU race where two concurrent
  same-named file receives could clobber each other.
- pairing.py: normalize_device_id now validates the Luhn mod-32 checksum
  in all 4 blocks of a device ID, reusing syncthing._luhn32, so a typo'd
  ID is rejected at paste time instead of failing later with a vaguer
  Syncthing error.
- syncthing.py: replaced the single-entry hardcoded binary-hash dict with
  a fetch of Syncthing's official sha256sum.txt.asc, verified against the
  downloaded archive bytes before extraction. Covers every platform/arch
  Syncthing publishes, not just linux/amd64.

Hygiene:
- clipboard.py: added public ClipboardSync.clear_history() so main.py
  doesn't reach into the private _history attribute.
- syncthing.py: demoted the subprocess output pump from INFO to DEBUG.
- history.py: removed a dead TYPE_CHECKING block.
- requirements.txt / requirements-dev.txt: split pyinstaller/ruff/mypy/
  pytest out of the runtime install path; ci.yml and release.yml updated
  to match.
- pyproject.toml: added [build-system] and [project] tables (name,
  version read dynamically from clipsync.__version__, classifiers, deps,
  entry point). There was previously no buildable package metadata at
  all in this repo; `uv build` now produces offbyonebit-clipsync 0.2.2
  matching what's actually live on PyPI.

CI, fully verified green (lint, format, mypy, test all run clean):
- Fixed all remaining ruff lint/format violations and all 12 mypy errors
  (mostly now-redundant `# type: ignore` comments given
  ignore_missing_imports=true, plus two real typing issues: watchdog's
  Observer factory isn't a valid type annotation, use BaseObserver; and a
  loosely-typed `object` callback boundary needed a narrowing cast for
  cv2.cvtColor).
- Root-caused and fixed real failures (not flakiness) in
  test_cross_os_sync.py, test_image_sync.py, test_mac_windows_sync.py,
  and test_linux_paste_freeze.py: on any machine with a real X11 display,
  ClipboardSync.start() opportunistically wires up a genuine XFixes
  watcher tied to the actual desktop clipboard, so the OUT loop waits on
  real selection-owner-change events instead of polling. These tests fake
  the clipboard read/write methods but never stubbed out the XFixes/Xlib
  startup, so on a graphical box the simulated clipboard.set() never
  triggered a sync after the initial tick. Headless CI has no DISPLAY so
  it fell back to polling and likely never showed this, but it broke for
  anyone running tests locally with a desktop session. Fixed by forcing
  CLIPSYNC_NO_XFIXES=1 / CLIPSYNC_NO_XLIB=1 in the affected fixtures so
  the tests are deterministic regardless of host display state.
- Removed tests/test_linux_paste_freeze.py's stale imports were already
  fixed on main; also fixed two more pre-existing lint errors there
  (unsorted imports, unused `types` import) that were silently breaking
  CI lint on main as well.

Tests added (pure-logic, no I/O mocking needed): crypto roundtrips/
legacy-format/wrong-passphrase, history dedup/pruning/encrypted
persistence, pairing Luhn validation, syncthing device-id-from-cert and
sha256sum.txt.asc parsing.

123 passed, 0 lint/format/mypy errors.
@offbyonebit offbyonebit merged commit 19271c6 into main Jun 18, 2026
1 check passed
@offbyonebit offbyonebit deleted the clipsync-polish branch June 18, 2026 15:16
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