Security Model
NodeDR POS is designed for a trusted local network. Here's exactly what protections are in place.
Design intent
NodeDR POS is designed for a trusted local network — a shop's private LAN or a single machine. The default deployment is HTTP. If you expose it beyond the counter (e.g. over the internet or a VPN), you should terminate HTTPS in front of it with a reverse proxy and set COOKIE_SECURE=true in the backend environment.
Passwords
- All passwords are hashed with bcrypt at cost factor 12. Plaintext is never stored or logged.
- Login returns an identical error for unknown email vs. wrong password — no user enumeration is possible.
- Login is rate-limited to 10 attempts per 15 minutes per IP, on top of a global rate limiter (300 req/min across all routes). After exceeding the limit, the client receives HTTP 429 and must wait before retrying.
Sessions
- Sessions use HttpOnly, SameSite=Lax JWT cookies signed with HS256 and a 256-bit secret auto-generated on first boot.
- The algorithm is pinned to HS256 in the verify call — algorithm confusion attacks are not possible.
- Every authenticated request re-checks that the account still exists and is active in the database — disabling a staff member logs them out immediately on their next request, not just on next login.
- The JWT signing secret is stored in the
nodedr-pos_datavolume with mode 600 — never in the repo or build image.
Authorization
- All data routes require authentication.
- Settings management, staff management, CSV export, and reference data imports require the admin role.
- The last active admin cannot be demoted or disabled — you cannot lock yourself out.
- Authorization is enforced server-side on every request — the frontend UI hiding a button is never the only protection.
Server-authoritative money
This is the most important security property of the checkout flow. The client sends only product IDs, quantities, and intent(e.g. “pay with UPI”, “redeem 50 loyalty points”). The server then:
- Looks up current prices from the catalog — a client cannot submit a manipulated price.
- Computes tax, discount, and loyalty value from the current Settings — a client cannot override these.
- Caps loyalty redemption at the customer’s actual balance.
- Caps the per-sale discount at the configured maximum.
- Decrements stock in the same transaction as creating the invoice — no race condition between “check stock” and “create invoice”.
A tampered request from the browser cannot alter what a sale charges or how much stock is decremented.
Input validation
- Every write endpoint validates with Zod — allowlisted fields only, no mass assignment.
- All database access is through Prisma ORM with parameterized queries — SQL injection is not possible.
- CSV imports are admin-only, capped at 25MB, and parsed in-memory (no temp files, no shell-out).
- HTTP response headers include
X-Content-Type-Options,X-Frame-Options, and other security headers viahelmet.
Network exposure
- Only one port (1994) is exposed to the network — the Next.js frontend.
- The Express backend runs on the internal Docker network only — it is not reachable from the LAN.
- All
/api/*traffic goes through the Next.js server-side proxy — the backend URL is never in the browser bundle. - Neither container runs with
privileged: true. The USB passthrough for ESC/POS printing uses a scopeddevice_cgroup_rulesentry for USB device major 189 only.
No external calls at runtime
Once the images are built, NodeDR POS makes zero outbound network calls. No analytics, no telemetry, no version-check pings, no external CDN for assets. Your sales data and customer information never leave the machine running Docker.
Reporting a vulnerability
If you discover a security issue in NodeDR POS, please report it by opening a GitHub issue with the label security, or email [email protected]. We take security reports seriously and will respond within 48 hours.