ScaleTag
A small local app. Security you can follow.
Ask a neighbourhood question, publish a small task or find what is happening nearby. ScaleTag combines polls, public task replies and a location-aware list and map in an installable web app.
- Ask or offerCreate a poll or a task with a reward.
- Add a placeConsent to an approximate map position.
- Share & respondOpen a link, scan a QR code, vote or reply.
- Keep it currentVoting closes in 7 days. Posts expire in 30.
Task replies publish the contact details people choose to share. Arrangements and payments happen directly between people; ScaleTag does not process payments. Browser cookies support participation limits, not verified identities.
One controlled way in.
Cloudflare Tunnel reaches a local Nginx ingress. The origin needs no public web ports; firewall rules restrict inbound access and server administration uses SSH keys.
- BrowserHTTPS request
- CloudflareTunnel to the origin
- Local NginxPreferred app1; app2 is backup
- ApplicationOrigin, input, size and rate checks
- SQLiteTransactional access to encrypted data
Readable in use. Encrypted on disk.
SQLCipher AES encryption covers the SQLite pages, including schema and post documents. Two application instances share the same database; transactions protect counts and deduplication.
- Accepted writeValidated post, vote or reply
- TransactionCheck and update together
- SQLite cipherEncrypt database pages
- Disk fileEncrypted SQLite storage
- Encrypted pagesRead from the disk file
- Correct keyOpen and decrypt in memory
- ApplicationBuild the permitted response
- BrowserReceive public content over HTTPS
The database engine manages page IVs and salts. Non-root containers and private file permissions add access control around the encrypted file.
The key travels with deployment.
A random 256-bit key starts in a GitHub Actions repository secret. It is delivered at runtime, keeping it out of source code and container images.
- GitHub Secret
SCALETAG_ENCRYPTION_KEY - Deployment jobPass the key on standard input
- Root-owned helperValidate; save a root-only host environment file
- Runtime environmentSystemd and Docker provide the same keyNode app1Node app2
Where is the key visible?
The host environment file is root-only. The running containers receive the key as an environment variable, so host root and Docker administrators can inspect it. This deliberately simple deployment protects against file-only disclosure, not full server control.
Backups stay encrypted, too.
Daily exports use AES-256-GCM with a fresh 12-byte IV and an authentication tag. The backup directory is root-only and is not mounted into the app containers.
- Read transactionExport a consistent database view
- AES-256-GCMFresh IV + authentication tag
- VerifyAuthenticate and decrypt the export
- Publish archiveAtomic publication; 90-day retention
- Encrypted archiveSupply the original key
- AuthenticateReject wrong keys or altered ciphertext
- New staging databaseRestore in a transaction; check integrity
- Controlled swapValidate offline, then stop both instances to replace active storage
Tested with real data: the encryption migration preserved all four existing records. A real encrypted backup restored the same four records into an isolated database and passed integrity checks. Opening the database without a key was rejected.
Test first. Replace one instance at a time.
Routine releases use readiness checks and Nginx failover. The initial encryption migration pauses both writers to convert the database safely.
- CI passesBackend, browser, backup, container and HA tests
- Update app2Verify readiness
- Route to app2Take app1 offline
- Update app1Verify readiness
- Fail backConfirm traffic returns to app1
Know the protection boundary.
Files obtained without the key
The database and backups stay encrypted. Wrong keys cannot open the database; backup authentication detects altered ciphertext.
Control of the running server
Privileged administrators can reach the runtime key and plaintext in memory. Public posts and consented reply contacts remain public.
Active posts expire after 30 days; backups have their own 90-day window. Browser cookies are not identities. File deletion is not forensic erasure, and provider snapshots require a separate policy.