@developer.krd/nexura
v0.2.0
Published
Nexura backend protection for Node.js — request firewall (WAF), file/code integrity, config & dependency auditing, with live reporting to the Nexura dashboard.
Maintainers
Readme
@developer.krd/nexura
Backend security for Node.js, by Nexura.
One dependency-free package that sits in front of your app and:
- Blocks attacks at the request layer — a signature WAF for SQL injection, XSS, path traversal, command injection, and known scanner tooling
- Hardens response headers and strips server banners
- Rate-limits abusive clients per IP
- Audits your deployment continuously — file permissions, leaked keys, code integrity, configuration, and dependencies
- Reports everything to your Nexura dashboard, and takes its enforcement settings back from it live
Zero runtime dependencies. Node ≥20. ESM. Ships TypeScript types.
npm install @developer.krd/nexuraGet an API key from clients.developer.krd → your site → Backend → API Keys.
Quick start
Express
import express from "express";
import { createNexura } from "@developer.krd/nexura";
const app = express();
app.use(express.json()); // mount the body parser first so the WAF can see bodies
const nexura = createNexura({ apiKey: process.env.NEXURA_API_KEY });
app.use(nexura.express()); // headers → rate limit → WAFShorthand:
import { nexuraExpress } from "@developer.krd/nexura/express";
app.use(nexuraExpress({ apiKey: process.env.NEXURA_API_KEY }));Fastify
import { nexuraFastify } from "@developer.krd/nexura/fastify";
await app.register(nexuraFastify, { apiKey: process.env.NEXURA_API_KEY });Fastify is inspected in two passes: onRequest covers the path, query, and headers before any body is read; preHandler inspects the parsed body. Rate limiting is applied once, on the first pass.
Any framework
inspect() is pure — no I/O, no reporting, no side effects. Use it anywhere:
import { createNexura } from "@developer.krd/nexura";
const nexura = createNexura({ apiKey: process.env.NEXURA_API_KEY });
const verdict = nexura.inspect({
method: req.method,
path: req.url,
headers: req.headers,
query: req.query,
body: req.body,
ip: req.socket.remoteAddress,
});
if (verdict.blocked) {
console.warn(verdict.type, verdict.rule); // e.g. "sql_injection"
return res.writeHead(403).end("Forbidden");
}Configuration
| Option | Default | Description |
|---|---|---|
| apiKey | NEXURA_API_KEY | Your nxr_… key. Without it the SDK runs local-only and reports nothing. |
| siteId | NEXURA_SITE_ID | Public site id (nex_…). Not needed for site-scoped keys. |
| apiOrigin | NEXURA_API_ORIGIN or https://clients.developer.krd | Dashboard origin. |
| mode | follow dashboard | "block" or "report" to override enforcement locally. |
| rateLimit | { max: 300, windowMs: 60000 } | Per-IP sliding window, or false to disable. |
| trustProxy | false | Read the client IP from X-Forwarded-For. |
| appDir | process.cwd() | Root directory the audits scan. |
| skipPaths | [] | Paths the WAF must not inspect. Strings match as a prefix, RegExps are tested. |
| allowIps | [] | IPs that bypass the WAF and rate limiter entirely. |
| denyIps | [] | IPs refused before any other check. |
| configRefreshMs | 900000 (15 min) | How often to re-read settings from the dashboard. 0 = fetch once at boot. |
| handleSignals | false | Flush telemetry and stop timers on SIGINT/SIGTERM. |
| disableAudit | false | Turn off the background audit (.audit() still works). |
| disableHealth | false | Turn off periodic health pings. |
| silent | false | Silence the SDK's own console output. |
| onThreat | – | (threat) => void, called for every detection. |
| onConfigChange | – | (next, previous) => void, called when dashboard settings change. |
| onAudit | – | (result) => void, called after each background audit. |
Avoiding false positives
A signature WAF cannot tell a SQL injection attempt from a legitimate request that contains SQL. If you have endpoints that accept queries, markdown, HTML, or raw logs, exclude them — otherwise real traffic gets 403s:
createNexura({
apiKey: process.env.NEXURA_API_KEY,
skipPaths: [
"/admin/query-console", // prefix match
"/api/import",
/^\/webhooks\//, // regexp
],
allowIps: ["10.0.0.5"], // internal health checkers, CI runners
});Excluded requests are skipped entirely: they cannot be blocked, are not reported, and do not consume rate-limit budget.
What the WAF detects
inspect() decodes the path, flattens the query object, and scans up to 8 KB of the body, most-severe-first. The first match wins.
| Type | Severity | Catches |
|---|---|---|
| sql_injection | critical | UNION SELECT, OR 1=1, stacked DROP/ALTER, sleep()/benchmark() time delays, information_schema probing, trailing comments |
| command_injection | critical | Shell metacharacters before cat/curl/wget/nc/bash, $(…), backtick substitution, pipes into interpreters |
| path_traversal | high | ../ sequences, encoded %2e%2e, /etc/passwd, C:\Windows\, file://, /proc/self/environ |
| xss_attempt | high | <script>, javascript: URLs, inline on*= handlers, <iframe>, document.cookie, <svg onload> |
| bad_bot | high | sqlmap, nikto, nmap, masscan, nessus, acunetix, netsparker, wpscan, gobuster, dirbuster, hydra |
| rate_limit | medium | More than max requests per window from one IP |
Rules are deliberately conservative, aimed at low false positives on real API traffic.
Response headers
Applied to every response; headers your app already set are left alone.
Strict-Transport-Security: max-age=31536000; includeSubDomains
Content-Security-Policy: default-src 'self'; frame-ancestors 'none'; base-uri 'self'; object-src 'none'
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
Cross-Origin-Opener-Policy: same-originX-Powered-By and Server are stripped.
What the audit checks
Runs shortly after boot, then every 6 hours, and on demand via nexura.audit() or the CLI. Produces a 0–100 score — each failing check subtracts by severity (critical 30, high 15, medium 7, low 2).
Files & permissions — world-writable files, .env/secret files readable by others, private keys committed into the project (.pem, .key, .pfx, .p12, .jks), sensitive files sitting inside a public directory (public/, static/, www/, dist/, build/), and private-key blocks embedded in config/text files.
Code integrity — SHA-256 manifest of your source (.js .mjs .cjs .ts .mts .cts .jsx .tsx .json .env .sh .sql) compared against a baseline in .nexura/manifest.json. Flags files changed, added, or removed since the baseline. node_modules, .git, and build output are excluded.
Configuration & database — NODE_ENV not production, DEBUG left enabled, NODE_TLS_REJECT_UNAUTHORIZED=0 (certificate validation disabled), weak or default database passwords in DATABASE_URL/POSTGRES_URL/MONGODB_URI/REDIS_URL and friends, and short SESSION_SECRET/JWT_SECRET/COOKIE_SECRET values.
Dependencies — missing package.json or lockfile, packages with known advisories, risky packages, and unpinned version ranges.
CLI
Installed as the nexura bin:
npx nexura init # seed the code-integrity baseline
npx nexura audit # run every audit and print the report
npx nexura audit --send # …and upload it to the dashboard
npx nexura verify # integrity-only check
npx nexura doctor # key, connectivity, and environment
npx nexura status # what settings the dashboard is serving this keyWithout installing first, use the full name: npx @developer.krd/nexura audit --send.
Exit codes
| Code | Meaning |
|---|---|
| 0 | Clean |
| 1 | Usage or connectivity error |
| 2 | audit: a high/critical problem was found · verify: code changed since baseline |
Which makes it a CI gate:
- run: npx nexura verify # fails the build if deployed code was tampered with
- run: npx nexura audit --sendCommit .nexura/manifest.json to version control so tampering is detectable across deploys.
Dashboard integration
Telemetry is buffered and POSTed as NDJSON to /api/nexura/server/ingest every 5 seconds (or immediately at 50 events), authenticated with your API key.
| Event | Sent when | Shows up as |
|---|---|---|
| server_threat | Every WAF or rate-limit detection | Threats, with rule, path, severity, and whether it was blocked |
| server_audit | After each audit | Server score and grouped findings |
| server_health | Every 60s | "Connected" state, Node version, uptime, RSS, requests inspected/blocked |
High and critical threats also trigger your configured notification channels.
Settings flow back, live
At boot — and every 15 minutes after — the SDK re-reads /api/nexura/server/config. Changing your plan or toggling enforcement in the portal reaches running servers without a restart:
createNexura({
apiKey: process.env.NEXURA_API_KEY,
configRefreshMs: 5 * 60 * 1000,
onConfigChange: (next, prev) => {
logger.info({ was: prev.enforce, now: next.enforce }, "nexura enforcement changed");
},
});Call await nexura.refreshConfig() to pull immediately.
Enforcement follows your plan
Plans with backend protection block; free and report-only plans record without blocking, so you can see exactly what would have been stopped before turning it on. mode overrides both:
createNexura({ mode: "report" }); // observe only, whatever the plan says
createNexura({ mode: "block" }); // enforce locallyHealth pings are always accepted so the dashboard shows your server as connected, even on a report-only plan.
Runtime API
const nexura = createNexura({ /* … */ });
nexura.inspect(request) // pure verdict, no side effects
nexura.express() // Express middleware
nexura.fastify // Fastify plugin
await nexura.audit() // run + report an audit now
nexura.reportThreat({ … }) // report a detection from your own code
await nexura.refreshConfig() // re-read dashboard settings now
nexura.status() // live snapshot
nexura.config // effective runtime config
nexura.enforcing // true when blocking
nexura.version
await nexura.destroy() // flush and stop all timersYour own health endpoint
app.get("/internal/security", (req, res) => res.json(nexura.status()));{
"version": "0.2.0",
"reporting": true,
"connected": true,
"enforcing": true,
"config": { "enforce": true, "audits": true, "healthIntervalMs": 60000, "auditIntervalMs": 21600000 },
"requestsInspected": 18402,
"requestsBlocked": 37,
"lastConfigAt": "2026-08-07T10:15:04.881Z"
}Feeding your own logging
createNexura({
onThreat: (t) => logger.warn({ type: t.type, rule: t.rule, path: t.path, blocked: t.blocked }, "attack"),
onAudit: (r) => logger.info({ score: r.score }, "nexura audit"),
});Shutdown
createNexura({ handleSignals: true }); // flush on SIGINT/SIGTERM
// or wire it yourself:
process.on("SIGTERM", () => nexura.destroy().then(() => process.exit(0)));Operational notes
Fail-open by design. Every hook is wrapped — reporting failures, audit errors, and callback exceptions never propagate into your request path. If the dashboard is unreachable, telemetry is retried a few times, then dropped rather than growing unboundedly. Your app keeps serving.
Behind a proxy. Set trustProxy: true, or every client looks like your load balancer — which breaks per-IP rate limiting and makes allowIps/denyIps useless.
Multiple instances. The rate limiter is in-memory and per-process, so N instances allow roughly N × max. Size accordingly, or rely on your edge/CDN limiter for a global bound.
Timers are unref'd. Nothing the SDK schedules will keep your process alive.
Privacy. Raw client IPs are never transmitted. They are hashed with SHA-256 plus a daily-rotating salt, matching the dashboard's own scheme, so an address cannot be recovered and correlation across days is not possible.
Body inspection is capped at 8 KB of scanned text per request, so large uploads don't become a CPU cost.
TypeScript
import { createNexura } from "@developer.krd/nexura";
import type {
NexuraConfig, NexuraStatus, WafVerdict, ThreatReport,
AuditResult, AuditFinding, ServerConfig, Severity, ServerThreatType,
} from "@developer.krd/nexura";Subpath exports: @developer.krd/nexura/express, @developer.krd/nexura/fastify.
MIT © developer.krd
