The packaged exe crashed with ModuleNotFoundError: No module named 'PySide6'.
Root cause: `pip install -e .` aborted because netifaces has no wheel for the
build's Python and needs MSVC to compile from source, so NO dependencies were
installed — and the workflow didn't catch it (native-command failures don't
trip $ErrorActionPreference, and the next pip command succeeded).
- Replace netifaces with a dependency-free socket-based LAN IP detection in the
GUI; remove netifaces from pyproject and all PyInstaller specs.
- Make pip failures fatal (check $LASTEXITCODE) and add a "Verify runtime
imports" step that fails the build before bundling if any dep is missing.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Delete the fully-commented, GitHub-hosted release.yml (superseded by the
self-hosted build-windows.yml).
- Bump VERSION/version.txt to 1.1.0 for the release covering the security
hardening, web macro editor, media keys, and UI redesign.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
upload-artifact@v4 uses the @actions/artifact v2 backend, which Gitea (reported
to the action as GHES) does not support. Pin to @v3, which uses the artifact
protocol Gitea implements. The exe itself now builds successfully; this was the
only remaining failure.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The per-user MSI installer is stateful across runs on a persistent VM: once a
version is registered, a later /quiet install no-ops and never lands at the new
TargetDir, so python.exe went missing. Switch to a standalone CPython from
nuget cached under LOCALAPPDATA — no registry/PATH changes, idempotent, reused
across runs. A pre-existing Python 3.11 on PATH is still honoured.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PyInstaller on Windows requires an .ico (or .exe) for the executable icon and
its PNG->ICO auto-conversion did not engage on the runner. Add a multi-size
Macro Pad.ico (16-256px) generated from Macro Pad.png and point macropad.spec
at it. The PNG stays bundled for the runtime window/tray icon.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- The runner has Windows PowerShell 5.1, not pwsh/PowerShell 7 — switch all
steps from `shell: pwsh` to `shell: powershell`.
- Make the scripts 5.1-safe: force TLS 1.2 for HTTPS, -UseBasicParsing on
Invoke-WebRequest, and write GITHUB_ENV as ascii (no BOM).
- Add a fast `preflight` job that validates the runner (OS, PowerShell, Python
availability) in seconds; build-windows now `needs: [preflight]`.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
actions/setup-python@v5 hangs inside its Windows tool-cache install script on
the self-hosted runner. Replace it with an "Ensure Python 3.11" step that uses
an existing py -3.11 / PATH python if present, otherwise silently installs
Python per-user (no elevation, cannot prompt). Invoke pip/PyInstaller via the
resolved interpreter path, and add a 30-minute job timeout so a hang can no
longer run indefinitely.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds .gitea/workflows/build-windows.yml targeting the org's self-hosted
`windows-latest` runner: sets up Python 3.11, installs the project +
PyInstaller, builds dist/macropad.exe, uploads it as an artifact, and on a
v* tag creates/updates a Gitea release and attaches the exe via the Gitea
API. Runs entirely on Windows (no Linux runner required).
Also declare relay_client + aiohttp as PyInstaller hidden imports so the
relay feature (lazily imported in the GUI) is bundled into the exe.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Uniform auth responses remove the session-enumeration oracle: unknown
session and wrong password now return an identical 401 at the API layer and
an identical WS handshake/close; desktop-status and the 503 "not connected"
signal are only exposed after successful auth.
- Session-count cap re-checked after bcrypt.hash so concurrent creates can't
overshoot maxSessions.
- Relay web client reconnect backoff resets only after a successful auth
response (an accept-then-close server no longer defeats the backoff).
- idGenerator comment corrected to match the rejection-sampling; drop unused
recordFailure return value.
- DEPLOY.md: document ALLOWED_ORIGINS for reverse-proxy deployments.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Minor PR-review follow-ups for the local PWA:
- Macro images load via a header-authenticated fetch -> blob object URL in both
token and relay modes, so no credential ever sits in an image URL; prior
object URLs are revoked on re-render.
- Local-mode auth failure now shows a clear "Access token required" state and
stops the reconnect/toast loop (one-shot), instead of retrying forever.
- CSP connect-src tightened from 'self' ws: wss: to 'self' (same-origin WS is
covered); verified the live WebSocket still connects.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Remote (relay) devices browse and trigger macros; macro authoring is
intentionally kept to the local network so what runs on the machine isn't
editable from the public internet.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Addresses two Major findings from PR review:
- Auth lockout no longer griefable: the throttle key is namespaced per path
and includes the client IP (web:<session>:<ip> vs desktop:<session>:<ip>),
so a flood of bad web-client auths can neither lock other clients of the
same session nor block the desktop from (re)authenticating. Per-socket
failure close and brute-force lockout are preserved.
- Relay web client XSS eliminated: app.html tab/macro rendering rebuilt with
createElement/textContent (no macro/tab value reaches innerHTML); inline
scripts/handlers externalized to /static/app.js and /static/login.js so the
helmet CSP now uses script-src 'self' (dropped 'unsafe-inline' for scripts).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Update README for the new security model and features: local API access
token embedded in the URL/QR (and web.allow_lan), app commands executing
without a shell, relay password in OS keyring, web-based macro creation,
and media/system keys.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
frame-ancestors is ignored inside a <meta> CSP, so move clickjacking
protection to real response headers (X-Frame-Options: DENY) and add
X-Content-Type-Options: nosniff and Referrer-Policy: no-referrer on all
responses. Drop the ineffective frame-ancestors token from the meta CSP.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Full semantic design-token layer (surfaces, spacing scale, radii, elevation,
type scale) as the single source of truth; electric-violet signature accent
instead of default blue.
- Light + dark themes via prefers-color-scheme; muted text now meets WCAG AA
(replaces low-contrast #aaa/#888).
- Macro cards read as physical keys (raised, hairline highlight, per-macro
--macro-color wash + placeholder, press/executing feedback, 2-line clamp).
- Proper modal sheet (blurred scrim, sticky header/footer, focus rings),
thumb-reachable floating action button, 44px tap targets, app-wide
:focus-visible, and prefers-reduced-motion support.
- manifest + theme-color meta updated to the new palette.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
macro_manager:
- All macro reads/writes guarded by a lock; macros.json written atomically
(temp + fsync + os.replace). Commands are copied under the lock and executed
outside it so wait/keyboard steps don't block other threads.
- last_used disk writes debounced (>=5s) instead of a full rewrite per press.
- Validate key/hotkey names against pyautogui.KEYBOARD_KEYS and clamp wait ms;
invalid input is skipped/logged, not blindly executed.
web_server:
- WebSocket receive loop always disconnects (fixes dead-socket leak); broadcast
prunes failed sockets.
- POST /api/execute offloaded to a thread executor so a long macro no longer
blocks the event loop.
- Image upload hardened: 5MB cap (413), real-image validation via Pillow,
server-generated name under macro_images/, returns a relative path only,
cleans up on failure.
gui:
- Execute macros off the Qt main thread (signal back to status bar); debounce
resizeEvent and only rebuild when the column count changes.
- Relay password stored in the OS keyring when available (plaintext JSON blanked;
graceful fallback if no backend); delete dialogs name the macro; honor the
minimize-to-tray setting; surface save failures.
- Add media/system keys (volume, play/pause, track nav) to the macro editor.
- THEME gains shared danger/accent-hover colors; drop dead imports.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Security:
- Eliminate DOM-based XSS: renderMacros/renderTabs rebuilt with
createElement/textContent/setAttribute + addEventListener. No macro name,
id, image_path, or category ever reaches innerHTML.
- Remove all inline on*= handlers; add a strict Content-Security-Policy meta
(script-src 'self', object-src 'none', base-uri 'none', frame-ancestors
'none').
- Guard JSON.parse on WebSocket frames.
Robustness:
- Service worker: network-first for the app shell + JS/CSS so fixes actually
ship; synthetic 503 for offline API calls; drop no-op /ws intercept; bump
cache version.
- WebSocket reconnect: exponential backoff (1s→30s), tear down old socket
before reconnect, stop on pagehide/beforeunload.
- Tab-switch race fixed with a request-id guard; executing state cleared in
finally.
Accessibility:
- Macro cards and tabs are real buttons with aria-labels/roles; toast +
connection status are aria-live; removed user-scalable=no.
Feature — web-based macro creation/editing:
- Floating "+" button + modal command-sequence builder (text/key/hotkey/
wait/app) with reorder/remove; create via POST, edit via PUT (prefilled
from GET /api/macro/{id}), delete with a named confirm. Works in both local
and relay auth modes. Empty state now offers "Create your first macro".
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Closes the account-takeover chain and related issues found in audit:
- WS auth brute-force protection: per-session lockout (authThrottle) + per-
socket failure cap that closes the socket (1008); applied to both web-client
and desktop auth. Failed auth no longer leaves the socket open for unlimited
guesses.
- WS upgrades now pass an IP-based rate limiter and Origin allowlist before
handleUpgrade (previously bypassed all HTTP middleware); trust proxy set so
limiting keys on the real client IP.
- /health no longer leaks live session IDs (counts only).
- .env.example rate-limit fixed (900000ms / 300) from the accidental ~11k rps.
- Credentials moved out of URLs into the X-MacroPad-Password header; images
fetched via header + blob URLs; login stores creds in sessionStorage.
- Session creation bounded (max sessions, min password length) and TTL-pruned;
session store writes are now atomic (temp+rename) and debounced.
- Session IDs lengthened to 12 chars with rejection sampling (no modulo bias).
- Enable helmet CSP; restrict CORS to configured origins.
- Request IDs use crypto.randomUUID; drop postinstall build hook and uuid dep.
- Uniform response for unknown session IDs (removes enumeration oracle).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The local FastAPI server previously exposed macro create/execute over the
network with no authentication while bound to 0.0.0.0, and macro `app`
commands ran via subprocess with shell=True — an unauthenticated RCE for
anyone on the LAN (and, via the relay, the internet).
- Mandatory per-install auth token (secrets.token_urlsafe) required on all
/api/* routes and the /ws handshake; timing-safe hmac.compare_digest.
PWA shell + /static assets remain public to bootstrap.
- Token auto-generated and persisted in settings.json (now chmod 0600).
- Bind host is configurable (web.allow_lan); token is the security boundary.
- macro `app` launch no longer uses shell=True — commands are tokenized with
shlex and exec'd directly, killing ;/&&/|/$() shell chaining.
- Relay client injects the local token and only forwards /api/* paths, and no
longer forwards attacker-supplied headers to the local server.
- Fixed path traversal in /api/image/{path} (realpath-confined to macro_images).
- GUI embeds the token in the LAN URL/QR; PWA reads it and sends it on API,
WebSocket, and image requests.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Add relay server documentation and setup instructions
- Add relay_client.py to project structure
- Add aiohttp to core dependencies
- Update version.txt to 1.0.0
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove version number from window title (still shown in About)
- Show "Copied!" over URL text with dimmed style instead of status bar message
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Cleaner interface - URL text now highlights on hover and copies to
clipboard when clicked.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix window restore when clicking Show from system tray or double-clicking
- Add Copy button for web interface URL
- Fix image loading when app starts with Windows startup
- Remove debug print statements
- Bump version to 1.0.0
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add wake lock (keep screen awake) functionality
- Add fullscreen toggle button
- Add dynamic PWA manifest generation
- Add favicon and icons for all relay pages
- Copy icons from main web folder
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
QTimer.singleShot doesn't work properly from non-Qt threads.
Use Qt signals instead which are thread-safe and properly marshal
calls to the main thread.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Always trigger on_session_id callback when server returns session ID
- Add debug prints to trace URL update flow
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add password query parameter to image URLs in the relay server's
app.html - this file has its own inline JavaScript separate from
the main web/js/app.js
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Update index.html links to repo.anhonesthost.net
- Adjust rate limit defaults in .env.example
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1. Images not showing through relay:
- Use getApiUrl() for image paths in relay mode
- Add password as query param for img tags (can't use headers)
2. URL not updating in desktop app:
- Set _connected=True before on_session_id callback fires
- Ensures update_ip_label() shows relay URL immediately
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add index.html explaining what MacroPad Relay is
- Add /ping endpoint for container health checks
- Add route for index page
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add postinstall script to build TypeScript automatically
- Move typescript to dependencies (needed during npm install)
- Update main and start to point to dist/index.js
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Node.js/TypeScript relay server that enables remote access to MacroPad:
- WebSocket-based communication between desktop and relay
- Password authentication with bcrypt hashing
- Session management with consistent IDs
- REST API proxying to desktop app
- Web client WebSocket relay
- Login page and PWA-ready app page
- Designed for cloud-node-container deployment
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
## New Features
### Minimize to Tray
- Window minimizes to system tray instead of taskbar
- Tray notification shown when minimized
- Double-click tray icon to restore
### Settings System
- New settings dialog (Edit > Settings or Ctrl+,)
- JSON-based settings persistence
- General tab: minimize to tray toggle
- Relay Server tab: enable/configure relay connection
### Relay Server Support
- New relay_client.py for connecting to relay server
- WebSocket client with auto-reconnection
- Forwards API requests to local server
- Updates QR code/URL when relay connected
### PWA Updates
- Added relay mode detection and authentication
- Password passed via header for API requests
- WebSocket authentication for relay connections
- Desktop status handling (connected/disconnected)
- Wake lock icon now always visible with status indicator
## Files Added
- gui/settings_manager.py
- gui/settings_dialog.py
- relay_client.py
## Dependencies
- Added aiohttp>=3.9.0 for relay client
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add interval parameter (50ms) between key presses in pyautogui.hotkey()
- Add small delay before hotkey execution for better Windows compatibility
- Add defensive check to handle keys stored as string instead of list
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Author link: shadowdao.com
- Updates link: shadowdao.com
- Donate link: liberapay.com/GoTakeAKnapp/
- Links are clickable and open in default browser
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Show platform-specific examples when adding or editing an App command:
- Windows: notepad.exe, paths with quotes
- Linux: firefox with URL
- macOS: open -a Safari
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
## Features
- Fullscreen button (⛶) in header to toggle fullscreen mode
- Wake Lock API integration to keep screen on while using the app
- Sun icon (☀) indicator shows wake lock status (bright = active)
## Wake Lock behavior
- Automatically requests wake lock when page loads
- Re-acquires wake lock when returning to the page
- Visual indicator pulses when active
- Gracefully hidden if Wake Lock API not supported
## Fullscreen
- Works on Android Chrome and desktop browsers
- iOS Safari has limited support (no fullscreen API)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
## manifest.json
- Add `id` and `scope` fields for proper PWA identification
- Split icon purposes into separate entries (was "any maskable", now separate)
- Add `prefer_related_applications: false`
## index.html
- Add `viewport-fit=cover` for notched devices
- Add `mobile-web-app-capable` meta tag
- Add `application-name` and `msapplication` meta tags
- Add both 192px and 512px apple-touch-icon sizes
## styles.css
- Add safe-area-inset padding for notched devices (iPhone X+)
- Use 100dvh for proper mobile viewport height
- Add bottom safe area to toast container and macro grid
## service-worker.js
- Bump cache version to v2 to force update
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Replace QLineEdit with editable QComboBox for category field
- Populate dropdown with existing categories from saved macros
- Allow typing new category names (editable combo box)
- Add styled dropdown arrow and item view to match dark theme
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The commands list was stored as a shallow copy, meaning the individual
command dicts were shared references with the CommandBuilder widget.
When the dialog closed, these shared objects could be affected.
Fix: Use copy.deepcopy() when storing commands in add_macro() and
update_macro() to ensure the macro manager has its own independent
copy of all command data.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Set fixed height of 28px on all command action buttons
- Increase vertical padding from 4px to 6px
- Add min-height: 20px in stylesheet for safety
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>