Security & Shield
Explore Niral security defaults, Shield request protection, release integrity checks and encrypted snapshots. Learn recovery tools and their limits.
On this page 8 sections
Niral is hardened by default — and with the Shield, a niral app also watches itself, evicts attackers, and detects tampering. All in-process, no external service, no second machine.
> Honest scope: this protects your app and narrows an attacker's options on the > box. It cannot stop a volumetric DDoS (that needs network-edge capacity) or > someone who already has root. Niral is *self-healing under attack* — never > *unhackable*, and we will never claim otherwise.
Hardened by default
Every production response and request already carries:
- CSP with per-request nonces,
X-Frame-Options: DENY,nosniff, referrer policy - Signed, HttpOnly, SameSite sessions (+
SecurewithNIRAL_SECURE=1) - CSRF blocked structurally — RPC is JSON-only and requires an
x-niral-rpcheader - XSS — templates auto-escape; SQLi —
node:sqliteis parameterized - Rate limiting, body/upload/argument caps, path-traversal blocks, private-file 404s
- Secrets that cannot reach the browser (reading
process.envin client code is a compile error)
The Shield
The Shield inspects every request *before routing* and responds in-process:
- Scanner bans — a request for something a niral app never has (
/wp-admin,
.php, /.env, /.git) is a scanner. A few of those and the IP is banned.
- Brute-force & flood detection — repeated 401/403/404s from one IP raise
strikes; past the threshold, a temporary ban (fail2ban-style, in memory).
- Injection heuristics — traversal, reflected-XSS and SQLi shapes in the URL
are blocked and counted.
- Lockdown — a *sustained* attack freezes writes: POST/PUT/PATCH/DELETE get
503, the site stays up read-only, the attack surface closes.
- Tamper-evident audit log — every event is hash-chained in
data/shield.log.jsonl; altering a past entry breaks the chain (niral shield verify).
- Owner alerts — with
NIRAL_SMTP_URL+NIRAL_ALERT_TOset, the first ban,
lockdown, and any tampering mail you (throttled — an attack can't flood your inbox).
The Shield is on by default. Tune or disable it:
NIRAL_SHIELD=off # disable entirely
NIRAL_SHIELD_STRIKES=6 # strikes before a ban (probes weigh 3)
NIRAL_SHIELD_BAN_MS=900000 # ban duration (15 min)
NIRAL_SHIELD_LOCKDOWN=8 # distinct bans in 5 min → lockdown
NIRAL_TRUST_PROXY=loopback # accept a single visitor IP only from a local proxy
Behind local nginx, use NIRAL_TRUST_PROXY=loopback and have nginx overwrite X-Forwarded-For with $remote_addr, not append client-supplied headers. With Cloudflare in front, configure nginx's real-IP module to accept CF-Connecting-IP only from Cloudflare's published proxy networks, then forward the resolved visitor address. Keep the app port inaccessible from the internet. Without correct visitor identity, a scanner can cause a shared proxy-IP ban. The legacy NIRAL_TRUST_PROXY=1 trusts forwarded headers from any peer; do not use it on an app port reachable by untrusted clients.
Public documentation can stay open to readers, search engines and AI crawlers. Shield checks request behavior, not user-agent names: normal page reads are allowed, while attack probes and repeated error responses accumulate strikes. Claiming to be Googlebot or an AI crawler does not bypass protection.
Release integrity — tamper detection
A niral release is content-hashed, so tamper detection is nearly free. niral build writes integrity.json — a sha256 of every file in the release. The running server re-hashes itself on a timer; if a served file changed on disk (a defaced page, an injected script, a swapped server module), it's logged and mailed.
niral shield integrity # re-hash the current release vs its build manifest
niral shield verify # confirm the audit log's hash chain is unbroken
niral shield log # recent bans, probes and lockdowns
Because deploys are atomic and the last releases are kept, recovering from a tampered release is a one-symlink rollback.
Recover — snapshots, restore, rollback, eviction
Detection is half the story. Niral also revives — all on the same box:
niral snapshot # back up every data/*.db right now
niral snapshot list # what's available (newest first)
niral restore latest # roll databases back to a snapshot (undoable —
# the live state is snapshotted first)
niral rollback # flip dist/current to the previous release
niral rollback --to <hash> # …or a specific one
niral rotate-secret # new NIRAL_SECRET — every session dies at once
Snapshots are automatic at the moments data is most at risk: hourly, before every migration, and before every deploy (the generated deploy.sh snapshots first). They use SQLite's VACUUM INTO — a consistent copy even while the app is writing. The newest 24 are kept; older ones prune.
Encrypted off-box snapshots
Local snapshots protect against bad deploys and corruption. Remote snapshots survive the entire server being lost. Niral supports any S3-compatible object store with no SDK and no plaintext upload:
- package the local snapshot databases
- gzip the package
- derive a 256-bit key with scrypt
- encrypt with AES-256-GCM (tamper authenticated)
- sign the request with native AWS Signature V4
NIRAL_SNAPSHOT_REMOTE_URL=https://s3.example.com
NIRAL_SNAPSHOT_REMOTE_BUCKET=my-backups
NIRAL_SNAPSHOT_REMOTE_REGION=us-east-1
NIRAL_SNAPSHOT_REMOTE_PREFIX=niral/my-app
NIRAL_SNAPSHOT_REMOTE_ACCESS_KEY=…
NIRAL_SNAPSHOT_REMOTE_SECRET_KEY=…
NIRAL_SNAPSHOT_REMOTE_KEY=a-separate-long-encryption-passphrase
NIRAL_SNAPSHOT_REMOTE_KEEP=30
niral snapshot --remote # local snapshot + encrypted upload
niral snapshot push latest # upload an existing local snapshot
niral snapshot list --remote # remote inventory
niral restore latest --remote # download, authenticate, decrypt, restore
The generated deploy script automatically pushes the newest pre-deploy snapshot when NIRAL_SNAPSHOT_REMOTE_URL is configured in server-side app.env. The encryption key is independent of the object-store credentials: losing the bucket credentials does not reveal database contents, but losing NIRAL_SNAPSHOT_REMOTE_KEY makes remote backups intentionally unrecoverable.
Auto-rollback: set NIRAL_AUTO_ROLLBACK=1 and a tampered release reverts to the previous good one and restarts itself — the site heals without you awake.
Session eviction: niral rotate-secret writes a fresh signing key, so every existing cookie stops verifying instantly — an attacker who stole a session is kicked out on the next request.
The watchdog — an independent guardian
The Shield runs inside the app. But an app can't report its own death, and a compromised app could silence its own checks. So Niral also ships a watchdog — a SEPARATE process (its own systemd unit) that guards the app from outside:
- probes
/@niral/health— down repeatedly → alerts you (and catches a
crash-loop systemd can't)
- re-hashes the release independently — catches tampering even if the app's
own integrity check was disabled or the app process was compromised
- verifies the audit chain from outside the app
- with
NIRAL_AUTO_ROLLBACK=1, rolls back a tampered release and restarts the
app unit
If an attacker kills the app, the watchdog survives to act; if they kill the watchdog, systemd brings it back. The two processes guard each other and share no memory — only the files on disk. niral deploy generates the watchdog unit and setup.sh enables it automatically.
> On-box snapshots are the fast rollback path. Encrypted remote snapshots are > the disaster-recovery path when the machine itself is gone.
Audit your setup
niral doctor --security # is the Shield on? proxy IP trusted? cookies
# Secure? alerts wired? integrity + audit intact?
What no other framework ships
Every serious framework hardens your app. Niral is the first that also watches it, evicts attackers, restores it, and heals itself — because it's the only one that owns the server, the deploy, and the database in one zero-dependency codebase.