securenow 8.25.1 → 8.25.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/NPM_README.md CHANGED
@@ -1472,7 +1472,7 @@ The firewall supports four layers -- Layer 1 is always on, the rest are opt-in:
1472
1472
  | **Layer 1: HTTP** | *(always on)* | Returns 403 (`config.firewall.statusCode`): JSON `{"error":"Forbidden","ip":"..."}` for API clients, a security alert page for browsers. Works with proxy headers. |
1473
1473
  | **Layer 2: TCP** | `config.firewall.tcp=true` | `socket.destroy()` before HTTP parsing -- zero bytes sent back. Sees only the connection's source IP; connections from trusted proxies are left to Layer 1. Checked on each server `connection` **(8.25.0+)**; before, it hooked `listen()` and missed the servers most apps start before the SDK has finished starting |
1474
1474
  | **Layer 3: iptables** | `config.firewall.iptables=true` | Kernel-level DROP in a dedicated `SECURENOW_BLOCK` chain (ip6tables for IPv6), or a `securenow` nft table where iptables is missing **(8.25.0+)**. Linux, root or `CAP_NET_ADMIN`; direct client connections only. 8.25.0 validates every entry, runs without a shell and applies each sync as one batch |
1475
- | **Layer 4: Cloud WAF** | `config.firewall.cloud="cloudflare"` / `"aws"` / `"gcp"` | Pushes the global blocklist to Cloudflare (an account IP list named `securenow-blocklist` that your own WAF rule must reference), AWS WAFv2 (IP set; `npm i @aws-sdk/client-wafv2`) or GCP Cloud Armor (`deny(403)` rules at priority 2000; `npm i @google-cloud/compute`), at most every 30 seconds. `config.firewall.cloudDryRun=true` only logs. 8.25.0 fixes IPv6 ranges, Cloud Armor's 10-range limit and stale rules. Request format tested, not run against real accounts |
1475
+ | **Layer 4: Cloud WAF** | `config.firewall.cloud="cloudflare"` / `"aws"` / `"gcp"` | Pushes the global blocklist to Cloudflare (an account IP list named `securenow-blocklist` that your own WAF rule must reference), AWS WAFv2 (IP set; `npm i @aws-sdk/client-wafv2`) or GCP Cloud Armor (`deny(403)` rules at priority 2000; `npm i @google-cloud/compute`), at most every 30 seconds. `config.firewall.cloudDryRun=true` only logs. 8.25.0 fixes IPv6 ranges, Cloud Armor's 10-range limit and stale rules. AWS tested against real IP sets (8.25.2+); Cloudflare and GCP request format tested only |
1476
1476
 
1477
1477
  Layers 2 to 4 carry the global blocklist; scoped blocks, rate limits and challenges are Layer 1 only. Turn Layers 2 to 4 on with 8.25.0+. In a container, Layer 3 needs `--cap-add NET_ADMIN` and the `iptables` package.
1478
1478
 
package/SKILL-API.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  > **Node.js security:** Use a supported, patched runtime: Node.js 22.23.4+, 24.22.1+, or 26.12.1+ on its respective release line. Earlier patches are affected by [CVE-2026-111497](https://nodejs.org/en/blog/vulnerability/october-2026-security-releases). Upgrading the SecureNow package does not patch Node.js; upgrade the runtime and restart every application process.
4
4
 
5
- The integration playbook for the `securenow` npm package. A coding agent that reads only this file can plug SecureNow into a Node.js app, turn on every feature, deploy it and prove it works. It describes 8.25.1; items marked **(8.25.0+)** are new in 8.25.0, so an older install needs `npm install securenow@latest` for them (check with `npx securenow version`). The terminal reference is [SKILL-CLI.md](./SKILL-CLI.md). Both files ship in `node_modules/securenow/`.
5
+ The integration playbook for the `securenow` npm package. A coding agent that reads only this file can plug SecureNow into a Node.js app, turn on every feature, deploy it and prove it works. It describes 8.25.2; items marked **(8.25.0+)** are new in 8.25.0, so an older install needs `npm install securenow@latest` for them (check with `npx securenow version`). The terminal reference is [SKILL-CLI.md](./SKILL-CLI.md). Both files ship in `node_modules/securenow/`.
6
6
 
7
7
  ## Rules for the agent
8
8
 
@@ -384,7 +384,7 @@ So a `runtime.json` left on a server beats the `credentials.production.json` nex
384
384
  | 1 HTTP | always, once the runtime API key is present | 403 JSON `{"error":"Forbidden","ip":"..."}`, or an HTML page with the masked IP for browsers; status from `firewall.statusCode` | Works behind proxies (forwarding headers) |
385
385
  | 2 TCP | `"tcp": true` | Destroys the socket before HTTP parsing, zero bytes sent | Sees only the connection's source IP; connections from trusted proxies are left to layer 1. Use 8.25.0+: before, it hooked `listen()` and missed the servers most apps start before the SDK has finished starting |
386
386
  | 3 OS | `"iptables": true` | Kernel DROP: a `SECURENOW_BLOCK` chain in iptables (ip6tables for IPv6), or a `securenow` nft table where iptables is missing **(8.25.0+)** | Linux, root or `CAP_NET_ADMIN`, `iptables`/`nft` installed; direct client connections only. Use 8.25.0+: it validates every entry, runs without a shell and applies each sync as one batch, and an IPv6 entry no longer stops the sync |
387
- | 4 Cloud WAF | `"cloud": "cloudflare"`, `"aws"` or `"gcp"` | Pushes the global blocklist to the edge, at most every 30 s | Cloudflare: fills an account IP list `securenow-blocklist` that your own WAF rule must reference. AWS: WAFv2 IP set. GCP: Cloud Armor rules at priority 2000, `deny(403)`. Use 8.25.0+ (IPv6 ranges, Cloud Armor's 10-range limit, stale rules fixed). Request format tested, not run against real accounts |
387
+ | 4 Cloud WAF | `"cloud": "cloudflare"`, `"aws"` or `"gcp"` | Pushes the global blocklist to the edge, at most every 30 s | Cloudflare: fills an account IP list `securenow-blocklist` that your own WAF rule must reference. AWS: WAFv2 IP set. GCP: Cloud Armor rules at priority 2000, `deny(403)`. Use 8.25.2+ (IPv6 ranges, Cloud Armor's 10-range limit, stale rules, IPv4-mapped entries fixed). AWS tested against real IP sets; Cloudflare and GCP request format tested only |
388
388
 
389
389
  Layers 2 to 4 carry the global blocklist. Scoped blocks, rate limits and challenges are layer 1 only.
390
390
  - **On:** automatic with the runtime API key. Each app and environment also has a remote switch, `npx securenow firewall enable|disable --app <key> --env production`, applied without a deploy. `config.firewall.enabled` is ignored.
@@ -806,7 +806,7 @@ Bad signs: no `[securenow]` line at all (the preload is not on the command that
806
806
 
807
807
  **Not supported:** Edge runtimes (Cloudflare Workers, Vercel Edge, Next.js Edge middleware and routes), Deno, `Bun.serve`, browser telemetry.
808
808
  **Not tested:** Vercel (firewall unverified on its serverless functions), AWS Lambda and other serverless platforms.
809
- **Layers 2 to 4:** turn them on with 8.25.0+ only (in 8.24.0 layer 2 dropped nothing for most apps and layers 3 and 4 mishandled IPv6). **Cloud WAF (layer 4):** request format tested, not run against real Cloudflare, AWS or GCP accounts; on Cloudflare your own WAF rule must reference the `securenow-blocklist` list.
809
+ **Layers 2 to 4:** turn them on with 8.25.0+ only (in 8.24.0 layer 2 dropped nothing for most apps and layers 3 and 4 mishandled IPv6). **Cloud WAF (layer 4):** AWS WAF is tested against real IP sets (IPv4 and IPv6, emptied list) from 8.25.2; Cloudflare and GCP are tested on the request format only, not against real accounts. On Cloudflare your own WAF rule must reference the `securenow-blocklist` list.
810
810
  **IPv6:** the blocklist, allowlist, trusted IPs, rate limits, challenges and API-key IP limits take IPv6 addresses and CIDR ranges, stored in canonical form (`2001:DB8::1` = `2001:db8::1`); layer 1 and trusted proxies match them **(8.25.0+; older SDKs match exact IPv6 addresses but ignore IPv6 ranges)**. A trusted-IP range can be no wider than /8 (IPv4) or /32 (IPv6).
811
811
  **`nuxt dev`:** before 8.25.0 the client IP was unknown, so IP blocking only applied to the built server.
812
812
  **Log redaction (8.25.0+)** goes by field names and value shapes: a secret in free text with no recognizable shape is exported as written.
package/firewall-cloud.js CHANGED
@@ -11,7 +11,7 @@
11
11
  */
12
12
 
13
13
  const https = require('https');
14
- const net = require('net');
14
+ const { parseIp, parseCidr, parseCidr6, intToIp, bigIntToIpv6 } = require('./cidr');
15
15
 
16
16
  let _options = null;
17
17
  let _active = false;
@@ -24,21 +24,38 @@ const GCP_MAX_RULES = 100;
24
24
 
25
25
  const MIN_PUSH_INTERVAL_MS = 30000;
26
26
 
27
- // Blocklist entries as CIDRs, per address family: an exact address becomes /32 (IPv4) or /128 (IPv6).
28
- // (Appending "/32" to everything turned one IPv6 address into a /32 covering billions of addresses.)
27
+ // Blocklist entries as canonical CIDRs, per address family: an exact address becomes /32 (IPv4) or /128
28
+ // (IPv6), an IPv4-mapped address (::ffff:1.2.3.4) is IPv4, a range is written from its network address,
29
+ // and anything a provider would refuse (not an IP, a /0) is left out. One bad entry used to make the
30
+ // provider refuse the whole push; the app still blocks what is left out here.
29
31
  function toCidrs(ips) {
30
- const v4 = [];
31
- const v6 = [];
32
+ const v4 = new Set();
33
+ const v6 = new Set();
32
34
  for (const raw of ips || []) {
33
35
  const entry = String(raw || '').trim();
34
36
  if (!entry) continue;
35
- const slash = entry.indexOf('/');
36
- const addr = slash === -1 ? entry : entry.slice(0, slash);
37
- const family = net.isIP(addr);
38
- if (family === 4) v4.push(slash === -1 ? `${addr}/32` : entry);
39
- else if (family === 6) v6.push(slash === -1 ? `${addr}/128` : entry);
37
+ if (!entry.includes('/')) {
38
+ const ip = parseIp(entry);
39
+ if (ip) (ip.family === 4 ? v4 : v6).add(`${ip.key}/${ip.family === 4 ? 32 : 128}`);
40
+ continue;
41
+ }
42
+ const bits = Number(entry.slice(entry.indexOf('/') + 1));
43
+ const c4 = parseCidr(entry);
44
+ if (c4) {
45
+ if (bits > 0) v4.add(`${intToIp(c4.network)}/${bits}`);
46
+ continue;
47
+ }
48
+ const c6 = parseCidr6(entry);
49
+ if (!c6 || c6.bits === 0) continue;
50
+ if (c6.bits >= 96 && (c6.network >> 32n) === 0xffffn) {
51
+ // ::ffff:0:0/96 and narrower are IPv4 ranges written in IPv6 form.
52
+ const b4 = c6.bits - 96;
53
+ if (b4 > 0) v4.add(`${intToIp(Number(c6.network & 0xffffffffn) >>> 0)}/${b4}`);
54
+ } else {
55
+ v6.add(`${bigIntToIpv6(c6.network)}/${c6.bits}`);
56
+ }
40
57
  }
41
- return { v4, v6 };
58
+ return { v4: [...v4], v6: [...v6] };
42
59
  }
43
60
 
44
61
  function prefixLength(cidr) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securenow",
3
- "version": "8.25.1",
3
+ "version": "8.25.2",
4
4
  "mcpName": "ai.securenow/securenow",
5
5
  "description": "OpenTelemetry instrumentation for Node.js, Next.js, and Nuxt - Send traces and logs to any OTLP-compatible backend",
6
6
  "type": "commonjs",