Skip to content

Security hardening, HTTPS + LAN only, self-contained Windows/macOS/Linux packages, signed updates, About & Licences - #22

Merged
AmrasElessar merged 21 commits into
mainfrom
claude/peaceful-galileo-lj97u7
Oct 26, 2026
Merged

AmrasElessar merged 21 commits into
mainfrom
claude/peaceful-galileo-lj97u7

Conversation

@AmrasElessar

Copy link
Copy Markdown
Owner

Summary / Özet

Güvenlik sıkılaştırması, her zaman HTTPS ve yalnızca yerel ağ, kurulum gerektirmeyen Windows / macOS / Linux paketleri, imzalı güncellemeler, VirusTotal taraması ve uygulama içi iki dilli Hakkında ve Lisanslar sayfası.
Security hardening, HTTPS-only and local-network-only access, self-contained Windows / macOS / Linux packages, signed updates, VirusTotal scanning and a bilingual in-app About & Licences page.

Güvenlik / Security

  • Per-device isolation of files and jobs; pairing keys ≥ 12 chars, global attempt limit, 30-day sessions; storage cap (507) and per-device pending-job cap (429); API no-store; /api/docs off by default.
  • LibreOffice runs with a hardened throwaway profile (no macros, active content, link updates, recalculation or network) against SSRF / local-file disclosure.
  • pdf.js 6.4.299 (GHSA-hq66-cqwq-w95j).
  • HTTPS by default: the server's own name-constrained local CA, certificate follows LAN address changes, TLS 1.2+; --trust-ca, /api/session/ca.crt, Devices dialog with QR for phones / tablets.
  • Local network only: public client addresses are always refused; proxied loopback counts as remote.

Özellikler / Features

  • Images → PDF: page size and placement (fit / fill / stretch / original), fixes stretched images.
  • Tool grid: tools that fit the selected file are pastel green and first; user-chosen favourites; shortcut dialog in columns.
  • KVKK / redact: Extra-secure PDF (pages rasterised and rebuilt).
  • Windows package: embedded Python, Tesseract (+ Turkish / English) and Ghostscript bundled and SHA-256 verified; AdminPDFToolkit.exe launcher (component checklist, "System ready", tray, no console window; plain .NET Framework, optional Authenticode via secrets).
  • macOS (Apple Silicon) and Linux x86_64 packages with python-build-standalone and a Tk launcher; macOS ad-hoc signed (no paid Apple account).
  • Updates: optional check at start, user decides; installed only when the Ed25519-signed SHA256SUMS verifies; never downgrades.
  • About & Licences (/api/about): Turkish and English as separate tabs — systems, first-start warnings, every installed Python package, npm packages, bundled tool files, programs, fonts, data, methods. scripts/third_party.py writes THIRD_PARTY_LICENSES.md the same way.

Sürüm ve belgeler / Release & docs

  • Release workflow builds and smoke-tests Windows, macOS, Linux and the Python package; publishes SHA256SUMS (+ .sig), the Ghostscript source (AGPL §6) and release notes with download table and first-start warnings.
  • New virustotal job (scripts/virustotal.py, free API key as VIRUSTOTAL_API_KEY): scans the packages and the launcher exe, results in the release notes and virustotal.json for the README badges; never blocks a release.
  • README: download & first-start section, step-by-step Windows / macOS / Linux installation with SmartScreen, certificate, firewall and Gatekeeper warnings, security badge row, updated highlights / roadmap / D Brand links. OKUBENI files, SECURITY.md, CHANGELOG.

Type of change / Değişiklik tipi

  • 🐛 Bug fix (non-breaking change which fixes an issue)
  • ✨ New feature (non-breaking change which adds functionality)
  • 💥 Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • 📝 Documentation update
  • 🔧 Refactor / chore
  • 🔒 Security fix

Breaking: the server now serves HTTPS by default (http://…:8000 bookmarks stop working; APT_TLS=false only behind your own TLS proxy), public client addresses are refused, pairing keys must be ≥ 12 characters, and in the Windows package the .bat files moved to scripts\ (the launcher is AdminPDFToolkit.exe).

Checklist

  • I have read CONTRIBUTING.md
  • Server checks pass (cd server && ruff check . && ruff format --check . && mypy adminpdf && pytest) — locally 348 passed / 11 skipped; mypy also for --platform win32 and darwin
  • Web checks pass (cd web && npm run check && npm run build)
  • I have added tests that prove my fix is effective or that my feature works
  • I have updated CHANGELOG.md under [Unreleased]
  • User-facing strings are in both Turkish and English (Field(tr=, en=) / t("tr", "en"))
  • The UI stays keyboard-accessible (WCAG 2.1 AA) — left to the CI's axe-core job
  • Documentation is updated (both Turkish and English sections of README.md if applicable)

Linked issues / Bağlı issue'lar

—

Screenshots (UI changes only)

Not attached; the UI changes are the About & Licences dialog, the Devices (QR) dialog, the green / favourite tool grid and the image-placement option.

Test plan / Test planı

  • Server: ruff, mypy (linux / win32 / darwin), pytest — new tests for TLS, LAN-only, owner isolation, office sandbox, bundles, updater, launcher, About.
  • Web: npm run check, npm run build; About dialog checked in TR / EN on desktop and phone widths.
  • Release workflow on this branch (not published): Windows zip unzipped without a system Python — launcher version info, self-check ready, bundled OCR (Turkish) and Ghostscript intact, server answers over HTTPS with 57 tools; macOS app codesign --verify, Linux and macOS packages start and serve over HTTPS; VirusTotal job skips cleanly without a key.
  • Before publishing: add the secrets RELEASE_SIGNING_KEY (and the public key in server/adminpdf/updater.py) and VIRUSTOTAL_API_KEY.

Generated by Claude Code

claude added 19 commits October 26, 2026 07:04
- images_to_pdf: compute the image rect ourselves (PyMuPDF's keep_proportion
  stretched square images over the A4 box) and add a placement option:
  fit / fill (crop) / stretch / original size.
- Tool grid: tint tools usable on the selection pastel green, move them and
  their groups to the front, row-major layout; user-pinned favourites row.
- Shortcuts dialog: up to four columns so it fits on screen.
- SchemaForm: show_if accepts a list of values.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgNH4tzUdde2tgUEgsfWxD
- Files and jobs belong to an owner (this machine, or one paired device);
  other devices get 404. Owner ids are hashes, never the session cookie.
- A loopback request carrying proxy forwarding headers is treated as
  remote, so a local reverse proxy cannot make the LAN 'local'.
- Pairing: keys under 12 characters refused at start-up, a global attempt
  limit on top of the per-client one, bounded limiter memory, sessions
  expire after 30 days and are capped.
- LibreOffice runs with a throwaway hardened profile: no macros or active
  content, links never updated, no recalculation on load, all network
  traffic sent to a dead proxy (tests cover SSRF and local file includes).
- API responses are no-store; extra security headers; uvicorn Server
  header off; /api/docs off unless APT_API_DOCS=true.
- Storage cap (APT_MAX_STORAGE_MB, 507) and per-owner pending job cap (429);
  the sweeper no longer expires inputs of running jobs.
- Docker trusts only the bridge gateway (APT_TRUSTED_NETWORKS=gateway),
  never in LAN mode; CI token is read-only.
- pdfjs-dist 6.4.299 (GHSA-hq66-cqwq-w95j).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgNH4tzUdde2tgUEgsfWxD
Optionally rebuild the redacted document from page images only (no text,
fonts, objects, annotations, attachments or metadata survive), so nothing
under a treatment can be recovered by any tool.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgNH4tzUdde2tgUEgsfWxD
- HTTPS on by default with the server's own name-constrained local CA
  (private addresses and local names only, so a leaked key cannot vouch for
  real websites); the server certificate follows LAN address changes.
  TLS 1.2+, ECDHE + AEAD ciphers; HSTS only for DNS names.
- Clients outside the local network (public addresses) are always refused;
  APT_TRUSTED_NETWORKS may only list local ranges.
- --trust-ca and a one-time OS trust prompt on desktop start; public
  /api/session/ca.crt for devices; 'Connect device' dialog on the server PC
  with a pairing QR code (segno) and the CA fingerprint.
- Mobile: compact header, scrollable viewer modes, KVKK findings usable on
  phones, no keyboard hints on touch, 40 px touch targets, no iOS zoom on
  input focus; opaque KVKK preview boxes.
- Docker health check, CI and release smoke tests over verified HTTPS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgNH4tzUdde2tgUEgsfWxD
The Windows package now ships Tesseract with tur/eng/osd data in ocr/, so
OCR works on PCs whose network blocks downloads. Sources are pinned in
scripts/ocr/sources.json (SHA-256 enforced when set) and the installer's
signature is reported at build time; ocr/manifest.json records the SHA-256
of every bundled file, the app re-checks it before use and refuses a
damaged or altered bundle. 'OCR Onar (Windows).bat' repairs language data
only with files matching those hashes; install-ocr-windows.ps1 verifies
pinned hashes too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgNH4tzUdde2tgUEgsfWxD
- AdminPDFToolkit.exe (C# 5 WinForms, built with the Windows csc): ticks
  off every component via python -m adminpdf.selfcheck, starts the server
  with no console window, shows addresses and the certificate
  fingerprint, network-sharing switch, notification-area icon. Version
  info and asInvoker manifest; Authenticode signing when the
  WINDOWS_SIGN_PFX secret is set. .bat files move to scripts\.
- python -m adminpdf.updater: optional update check; installs only
  releases whose Ed25519-signed SHA256SUMS verifies, matching hash, never
  a downgrade, zip-slip safe; data\ is kept. Release workflow writes
  SHA256SUMS(.sig) and release-manifest.json (scripts/release_signing.py).
- Ghostscript bundled in gs\; Tesseract and Ghostscript resolved to their
  newest release at build time and verified against GitHub's published
  SHA-256; Ghostscript's source tarball is published with the release.
- CI compiles the launcher on Windows with /warnaserror.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgNH4tzUdde2tgUEgsfWxD
- Certificates: cap the common name at 64 characters (long computer
  names broke certificate creation, seen on macOS runners).
- Tests: no POSIX mode check on Windows; a LibreOffice that refuses the
  crafted documents outright also passes (nothing leaks).
- Windows package: unpack the Tesseract / Ghostscript NSIS installers
  with 7-Zip instead of a silent install (which re-launched elevated and
  returned before any file existed); pin Tesseract to its newest release
  v5.4.0.20240606 by SHA-256.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgNH4tzUdde2tgUEgsfWxD
The start-up banner contains Turkish letters that cp1252 cannot encode;
the server died before listening when started from a .bat on such a PC.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgNH4tzUdde2tgUEgsfWxD
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgNH4tzUdde2tgUEgsfWxD
- python -m adminpdf.launcher: the Windows launcher's window in Tk —
  component checklist, server without a terminal (own process group),
  network sharing, update notice, data in the user's folder (macOS may
  run downloaded apps from a read-only location).
- scripts/build_unix_portable.py: python-build-standalone (SHA-256
  checked) + the app; Linux x86_64 tar.gz with a menu-entry script,
  macOS arm64 AdminPDFToolkit.app, ad-hoc signed (no paid Apple account;
  'Open Anyway' once). Release CI builds and smoke-tests both.
- Updater looks for the package of the running system; in-place install
  stays Windows-only. Self-check shows brew / apt / dnf commands for a
  missing Tesseract or Ghostscript.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgNH4tzUdde2tgUEgsfWxD
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgNH4tzUdde2tgUEgsfWxD
- In-app "Hakkında ve Lisanslar / About & Licences" dialog with separate
  Turkish and English tabs: supported systems, first-start warnings,
  every installed Python package (direct and transitive, from metadata),
  web bundle npm packages, bundled tool files with SHA-256 sources,
  programs, runtimes, fonts, data, build tools and methods (/api/about).
- scripts/third_party.py replaces python_licenses.py and writes
  THIRD_PARTY_LICENSES.md as a full Turkish part then a full English part.
- Windows SmartScreen, certificate prompt, firewall, macOS Gatekeeper,
  keychain and browser/phone certificate warnings documented on the
  README main page, release notes, OKUBENI files and in the app.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgNH4tzUdde2tgUEgsfWxD
- scripts/virustotal.py (standard library): uploads the packages and the
  launcher exe to VirusTotal with a free API key (secret
  VIRUSTOTAL_API_KEY), or looks them up by SHA-256; paces itself to the
  free 4-per-minute limit; writes a TR/EN Markdown table and
  virustotal.json. Skips without a key and never fails the release.
- release.yml: virustotal job between the builds and publish; its table
  goes into the release notes and the step summary, virustotal.json is
  published with the release.
- README: "Güvenlik / Security" badge row (VirusTotal results of the
  latest release via shields.io, HTTPS, LAN only, signed updates,
  verified bundles, CSP, code signing). SECURITY.md and CHANGELOG.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgNH4tzUdde2tgUEgsfWxD
…ut a web build

- mypy: named function instead of a lambda returning CompletedProcess;
  the os.startfile ignore is no longer unused on Windows.
- test_stream_json_runs_the_selfcheck: CI has no web build, so the
  self-check is not "ready" there; assert the exit code matches the
  streamed result instead of requiring 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgNH4tzUdde2tgUEgsfWxD
…Brand links

- Highlights (TR/EN): extra-secure KVKK PDF, image-to-PDF page size and
  placement, HTTPS + LAN-only + per-device isolation, green/first tools
  and favourites, phone/tablet QR, in-app About & Licences.
- Phones/tablets: the launcher's Network sharing switch (the old
  'Baslat - Ag Paylasimi (Windows).bat' no longer exists).
- English install: macOS / Linux packages; launcher row in the stack;
  350+ tests, VirusTotal and signed SHA256SUMS; APT_OCR_DIR/APT_GS_DIR.
- Roadmap: portable packages, HTTPS/LAN pairing, signed updates and
  VirusTotal done; MSI installer left open. D Brand: public sibling repos.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgNH4tzUdde2tgUEgsfWxD
…arning

- README Installation: numbered macOS steps with the exact Gatekeeper
  message (incl. macOS 15 wording), Done (not Move to Trash) > Privacy &
  Security > Open Anyway, xattr alternative, keychain password, brew;
  numbered Linux steps with the browser certificate import and apt/dnf.
  Same in the English part.
- Launcher menu names as they are in the app.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgNH4tzUdde2tgUEgsfWxD
Comment thread server/tests/test_security.py Fixed
Comment thread scripts/third_party.py Fixed
Comment thread scripts/virustotal.py Fixed
Comment thread scripts/virustotal.py Fixed
Comment thread server/adminpdf/core/security.py Fixed
Comment thread server/adminpdf/launcher.py Fixed
Comment thread server/adminpdf/launcher.py Fixed
Comment thread server/adminpdf/launcher.py Fixed
Comment thread server/adminpdf/launcher.py Fixed
Comment thread server/adminpdf/selfcheck.py Fixed
- security.default_gateway reads /proc/net/route with Path.read_text
  (file always closed).
- launcher: say why each ignored OSError is safe to ignore.
- selfcheck: one import style for importlib.
- third_party / virustotal: explicit parentheses around intentionally
  joined strings in lists.
- test_security: requests made before the assert, not inside it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgNH4tzUdde2tgUEgsfWxD
On a slow macOS runner the server had not printed its banner after 6 s.
Read stdout line by line in a thread and wait up to 90 s for the banner
(or the process ending); stderr goes to a file so the pipe cannot fill.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgNH4tzUdde2tgUEgsfWxD
@AmrasElessar
AmrasElessar merged commit 428a40d into main Oct 26, 2026
39 of 45 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.

3 participants