Please report security issues privately, not as a public issue.
- Preferred: GitHub private security advisory
- Or e-mail: k.wlodarski@siecportali.pl
Zgłoszenia po polsku są równie mile widziane.
Helpful details: affected version, operating system, what you did, what happened, and — if you have one — a minimal reproduction. Please do not include real card UIDs or personal data from a production deployment; a redacted example is enough.
Expect an acknowledgement within a few days. Fixes ship in a normal release, and the advisory is published once a fixed version is available. Credit is given unless you prefer otherwise.
This is a small project without long-term branches: only the latest release is supported.
Fixes land on master and go out in the next tagged release.
| Version | Supported |
|---|---|
| latest release | ✅ |
| anything older | ❌ (please update) |
Worth knowing before you assess a finding — several properties are deliberate:
- No telemetry, no accounts, no auto-update. Packaged builds run with a Content Security Policy
restricted to
'self', and the renderer never talks to the network directly. - One optional outbound integration. The Akuvox cloud integration is off until configured and
only talks to the API address you enter. When enabled:
- API credentials are encrypted at rest with the OS keystore (
safeStorage: Keychain / DPAPI / libsecret) in a separate file — never in the plain-JSON card database, - the access token lives in process memory only and is never written to disk,
- the request log shown in the app masks secrets in headers, JSON bodies and form bodies,
- a dry-run mode shows what would be sent without sending it,
- plain
http://outside the local network is rejected by profile validation, so credentials cannot be sent unencrypted by a configuration slip.
- API credentials are encrypted at rest with the OS keystore (
- One optional inbound listener. The phone as scanner feature starts a local HTTP server on the
LAN (default port 8787). It is off by default and must be enabled per session. When enabled:
- every request needs the secret from the pairing URL (32 random hex characters, compared in constant time); wrong secret returns 401 and requests are rate-limited per IP,
- the
Hostheader is validated against the machine's own addresses, which blocks DNS rebinding, - request bodies are capped at 4 KB and only
/s/<secret>and/q/<secret>routes exist, - traffic is plain HTTP, not encrypted — use it on a trusted network only,
- anyone who photographs the pairing QR code can register scans. Regenerate the secret from the app (invalidates earlier pairings) or stop the server when you are done. The phone can only submit a card number; it cannot read or modify the card database.
- No production dependencies.
npm installpulls development tooling only, which keeps the runtime supply-chain surface at Electron itself. - Renderer is sandboxed from Node.
contextIsolationis on,nodeIntegrationis off, and the renderer reaches the main process only through the narrow API inelectron/preload.cjs. External links open in the system browser. - Local data is not encrypted. Cards and scan history live in a plain JSON file in the user's application-data directory, readable by anything running as that user. If your deployment treats card UIDs or owner names as sensitive, protect them at the OS level (disk encryption, file permissions, a dedicated account).
- No authentication in the app. Anyone who can use the window can edit the card database. The app is designed for a single trusted operator station, not for multi-user access control.
- Releases are unsigned. Artifacts carry no Apple or Authenticode signature, so the OS will warn on first launch and you cannot verify authorship from the file alone. Download only from the Releases page of this repository.
- RFID cloning and card cryptography. The reader is a USB HID keyboard: it only ever types the card's UID. Mifare Classic S50/S70 UIDs are not secrets and are trivially cloneable with purpose-built hardware — that is a property of the card standard, not a bug in this app. Do not use UID-only checks as the sole control for anything genuinely valuable.
- Keystroke capture by design. While the scan panel is focused, the app records the characters the reader types. That is the only way a keyboard-wedge reader can be read at all. It captures only within its own focused window — it installs no system-wide keyboard hook.
- Findings that require an attacker to already run code as the user, or physical access to an unlocked machine.