LedgerMap · design options · 17/10/2026
Lock LedgerMap to one PC, with no user accounts
Three ways to make the desktop app run only on PCs that you approve. Each option has a diagram, its strong and weak points, and the work it needs. The recommendation and a rollout plan come at the end.
First, the honest truth about hardware locks
The user controls their PC. A skilled person can edit the .exe to skip a check, or fake a hardware ID. No hardware lock that runs only on the PC is safe from this.
LedgerMap already needs our server. It reads PDFs, matches ledgers and keeps the saved work. If the server serves only approved PCs, an edited app gets nothing. The server is our real lock.
Device key with server approval
The PC makes a secret key inside its security chip. The key can never leave that PC. You approve the key once. After that, the PC proves who it is every time the app starts.
4827 19364827 1936What the user sees on a new PC
This PC is not approved yet
Send this code to your LedgerMap contact.
What you do
Later, the same list can be an admin screen in the desktop app. A faster start is also possible: you make a one-time 8-digit setup code for a customer. The first PC that enters it is approved at once, and the code then stops working.
Strong points
- A copy on another PC cannot work. The key cannot leave the chip.
- No login. The app signs in by itself at each start.
- You approve each PC, and you can revoke one at any time. The change works at once.
- An edited
.exedoes not help. The server refuses any PC it does not know. - Ready for the paid plan. Add "paid until" and "number of PCs" to the customer.
Weak points
- It needs the internet. LedgerMap needs it already, so nothing new here.
- Some old PCs have no TPM chip, or it is turned off. Those PCs use a key locked with Windows DPAPI instead. A full disk clone could copy that key.
- A new Windows install or a new motherboard means a new key, so you approve that PC again.
- It is the most work of the three to build.
Licence file locked to a hardware ID
The app reads the PC's hardware (motherboard ID, disk serial, Windows ID) and shows a Machine ID. You sign a licence file for that ID. The app checks the file each time it starts, without the internet.
9F2C-…-41AEasmark.lic with your private key.exe. If someone removes it, the app runs, and the server cannot tell this PC from an approved one.Strong points
- Simple to understand. No server work to check a licence.
- Works offline. This only matters for apps that do not need a server.
- Uses the signing method we already use for app updates (Ed25519).
Weak points
- Only the app checks the lock. An edited app or a faked hardware ID gets past it.
- It does not protect the server. We would still need Option 1 or passwords for that.
- You cannot revoke a file. It works until it expires.
- A new disk or motherboard changes the ID, so the user needs a new file.
- Sending files back and forth by hand is slow for users.
Licensing service (Keygen, Cryptlex, LicenseSpring)
A company that sells licence systems does the PC locking. It gives you a dashboard for customers, PCs and seat limits, and later for payments. Our server must still check that service's tokens.
Strong points
- Ready-made: PC limits, offline grace days, expiry and a dashboard.
- Connects to payments (for example Stripe) when LedgerMap becomes paid.
- Their team keeps the licence system working, not ours.
Weak points
- Two systems to keep in step: the service and our server, which still needs its own check.
- A monthly cost that grows with the number of customers.
- Customer names and PC details go to another company.
- If the service is down, nobody can start the app.
- Most of these services use a PC fingerprint, not a chip key. So copying is easier than in Option 1.
Side by side
| 1 · Device key + approval | 2 · Licence file | 3 · Licensing service | |
|---|---|---|---|
| Copy to another PC is stopped | Yes. The key cannot leave the chip. | Partly. Only the app checks. | Mostly. Fingerprint only. |
| No login for the user | Yes | Yes | A licence key once |
| You approve each PC | Yes, with one command or a setup code | Yes, by signing a file | Yes, in their dashboard |
| Revoke a PC at once | Yes | No. It works until it expires. | Yes |
| Protects our server | Yes | No | Only if our server checks tokens |
| An edited .exe gets in | No | Yes | No, when our server checks |
| Data stays with us | Yes | Yes | No |
| Running cost | None | None | Monthly fee |
| Ready for paid plans | Yes. Add expiry and PC count. | Expiry only | Yes, with payments |
| Work to build | Most (about 3 PRs) | Least (about 2 PRs) | Medium + vendor setup |
How hard is it to get around the lock?
Recommendation: Option 1
LedgerMap already depends on our server, so the server is where the lock belongs. Option 1 meets all three requirements. It costs nothing to run, keeps customer data with us, and later becomes the paid plan without a rebuild. Option 3 is worth a look later, only if you want a ready-made payment and licence dashboard.
Rollout plan
devices, approve, revoke, setup-code. The server records each request, approval and revocation in usage tracking.Build log
Updated as the work goes on. Nothing merges into main until you approve it.
PASSWORD_LOGIN switch. PR #140. All server checks pass, including PostgreSQL integration tests (full flow, setup code used once under 4 parallel requests, revoke ends sessions, limits, cleanup). OpenQodex: passed, no findings.app.ledgermap.in/admin/: Requests, PCs, Customers with setup codes, Activity. Built as #156 (server) and #157 (web page, deploy, nginx). Not deployed. PR #158- You chose Option 1 with 8-digit numeric codes.
- Wrote the shared contract (wire format, routes, admin commands). OpenQodex review: passed, no findings. Opened PR #138.
- Started two sub-agents in their own worktrees: one for the server, one for the desktop app and the screen.
- Server part merged (PR #140):
server/device.go,server/device_admin.go, schema changes, admin commandsdevices / approve / revoke / setup-code / customers. - Contract gap: the contract did not say what a malformed public key returns. The server answers
400 device_key_invalid. The desktop side must handle it; this is checked in the review. - Small gaps the server agent decided: a revoked pending request is never cleaned up;
--customerrefuses a name that already exists; requests that keep racing can get409after 5 tries. All documented indocs/device-access.md. - OpenQodex's golangci-lint scanner failed to run (exit 2). The reviewer itself ran and passed the change.
- Wrote part 4 (PR #141). Rollout order: deploy server → give existing accounts setup codes, or approve with
--user <email>to keep their data → publish 0.4.0 as a required update → setPASSWORD_LOGIN=off. - OpenQodex found a race: two admin commands at the same moment could create two customers with one name. Fixed with a database lock.
- OpenQodex flagged that version 0.4.0 must not ship without the desktop code. So PR #141 merges only after part 3.
- The first PostgreSQL test run used wrong credentials. A rerun with the local development database settings passed.
- Desktop part merged (PR #144). OpenQodex needed 6 fix rounds. Fixed issues included a Windows memory-safety gap in the CNG call wrapper, an ended password session being replaced by the PC's account, and focus moving to a disabled field.
- The TPM and DPAPI code could not run here (Linux). It builds and vets for Windows, but it needs a test on a real Windows PC with a TPM, one without, and a restart.
- Roaming Windows profiles: the DPAPI fallback key lives in
%APPDATA%. On another PC it cannot be decrypted, so the app shows an error instead of making a new key. Rare in small offices; documented. - OpenQodex found that a large PDF upload (over 1 MiB) that got a 401 skipped session renewal, so uploads kept failing. Fixed: the session renews and the row's Retry works; a blocked PC sees the approval screen.
- Updating PR #141 after its rebase would need a force push, which needs your approval. Opened #145 instead and closed #141.
- All 4 parts merged into
device-lock.mainmoved ahead 3 commits; the branch rebases on it with no conflicts. On the rebased code all checks pass: server with PostgreSQL, backend with Windows build, 568 frontend tests, root tooling. - Started the independent review with a Claude Fable 5.1 sub-agent.
- Fable round 1 found no blocker and said the auth design is sound. It confirmed the signature check, single-use challenges, setup-code locking, revoke, client IP handling behind Cloudflare, and that tracking never stores secrets.
- Major (fixed): 3 office PCs waiting behind one internet address used up the per-address status limit, so an approval could show up minutes late. Status checks now count per PC key, with a high per-address cap. The screen also says when the server slows its checks.
- Minor (fixed): Windows key paths. Key files moved out of the roaming profile to
%LOCALAPPDATA%. A PC that never used the TPM falls back to DPAPI on any TPM error. A PC that did use it never switches silently, so its approval is never lost by accident. - Minor (fixed): the schema change locked the users table at every server start; it now runs only once. Device sign-ins got their own rate limit. Browsers hide the password form when password login is closed. The docs quote ssh commands correctly for names like "Ramesh & Co".
- After the fixes, all checks pass: server with PostgreSQL, backend with Windows build, 570 frontend tests. OpenQodex on the whole branch against
main: passed, no findings. Opened PR #151; closed #138. - Started Fable round 2 to check the fixes.
- Fable round 2 confirmed all 8 fixes and found no new defects from them. It checked every Windows key path: no path silently replaces an approved key or leaks a handle.
- Minor (fixed): after a 429 the screen still checked every 5 seconds although its note said otherwise. It now checks once a minute. The Windows key folder now comes from Windows' known-folder API, so a launcher with a cleared environment cannot move it.
mainmoved on again. Its new Settings pop-up (#146) moved the profile page, so "This PC", the hidden email field and the hidden Password section were moved into the newprofile-settings.tsxandsettings-dialog.tsx.- Final checks on the current
main: server with PostgreSQL, backend with the Windows build, 578 frontend tests, root tooling (37). All pass. OpenQodex: passed, no findings. Opened PR #154; closed #151. - Still untested: the TPM and DPAPI code on a real Windows PC, an end-to-end run of the native app, and the 0.3.0 app against the new server. Do this before publishing 0.4.0.
- You asked for an online admin panel instead of SSH commands. Your choices: password-only sign-in,
admin@ledgermap.in, customers and setup codes, usage activity, then merge #154 and the panel PR with no deploy. - The project rule said app.ledgermap.in serves only the API. Your request overrides it; the rule changes to allow exactly this panel at
/admin/. - Risk to note: with password-only sign-in, the admin password is the only thing that guards PC approvals. Use a long, unique password. An authenticator code can be added later.
- Wrote the panel contract (
docs/admin-panel.md). OpenQodex: passed. Started two sub-agents. - Admin panel part 1 (server API) merged into
admin-panel(PR #156):/api/admin/*routes, admin login that works even when password login is closed, shared logic with the SSH commands, acreate-adminthat asks for the password on the terminal, and PostgreSQL tests. OpenQodex: passed. - Found in #154 (fixed): party connections are tied to the session, so the app's 23-hour session renewal broke an open party connection early, and the user had to reconnect the folder. Now the server extends the PC's own live session instead of making a new one. Tests on both sides; OpenQodex on all of #154 against
main: passed, no findings. - Admin panel part 2 (web page and deployment) merged into
admin-panel(PR #157). 596 tests. A test keeps the admin and desktop bundles apart.nginx -tpasses. OpenQodex found one major issue (a failed logout looked like a sign-out); fixed. - The strict Content-Security-Policy blocks the style tag that dialogs add at run time. The panel does the same job from its own stylesheet; the browser may log a harmless console warning.
- Checked #154 together with the latest
main(all checks pass), then merged it (#154). Moved the panel onto the newmainwith no conflicts; all checks pass (612 frontend tests, PostgreSQL server tests, root tooling). OpenQodex: passed, no findings. Merged as #158. Nothing deployed. - To go live, in this order:
bun run server:deploy→ rundeploy/setup-server.shon the server (nginx) →ssh -t ledgermap-server ledger-map-admin create-adminwithadmin@ledgermap.in. Then test the TPM on a real Windows PC before publishing app 0.4.0.
Questions you may have
Is an 8-digit number safe enough?
The request code (PC → you) is only a label for a pending request. The chip key holds the security. The server picks the number at random, no two pending requests share it, and the list shows the name and time beside it. So 8 digits are plenty.
The setup code (you → PC) is a secret. It has 100 million choices, works once, expires after 24 hours, and the server allows only a few wrong tries per address and about 100 wrong tries per hour in total. Against one code, that gives a guesser roughly a 1 in 40,000 chance. Bank OTPs use 6 digits with the same kind of limits.
The user gets a new PC or reinstalls Windows. What then?
The app makes a new key and shows a new code. You approve it for the same customer, and all their work is there. You revoke the old PC.
Can one customer use two PCs?
Yes, if you allow it. Each PC has its own key, and all of them belong to one customer. They share the same parties. When LedgerMap becomes paid, the number of PCs can be part of the plan.
What if a PC has no TPM chip?
All Windows 11 PCs have one. Most Windows 10 PCs from about 2016 on have one too, but it is sometimes turned off. Without it, the key is locked with Windows DPAPI for that PC. Copying the app folder still does not work, but a full disk image clone could carry the key. The server can mark these PCs so you can see them.
Can someone just copy the app folder or the installer?
Yes, but the copy is useless. The key lives in the chip (or in DPAPI), not in the app folder. On the new PC the app has no key and shows a new pending request.
Can an attacker spam the server with requests?
Requests are rate-limited per address, and a pending request expires after a few days. Pending PCs cannot use the API until you approve them.
What changes when we start to charge?
Add "paid until" and "PCs allowed" to each customer. At sign-in, the server checks them and tells the app when a plan has expired. There is no new lock to build.
Words used on this page
- TPM chip
- A small security chip on the motherboard. It can make keys that never leave it.
- DPAPI
- A Windows feature that encrypts data so only that PC can decrypt it. It is the fallback when there is no TPM.
- Public / private key
- A pair of keys. The private key signs and stays secret on the PC. The public key checks signatures and can be shared with the server.
- Challenge
- A random value that the server sends. The PC signs it, so an old signature cannot be used again.
- Fingerprint / Machine ID
- A value made from hardware serial numbers. It is easy to read, so it is also easy to fake.