securenow 8.22.0 → 8.24.0
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 +58 -49
- package/README.md +21 -6
- package/SKILL-API.md +34 -30
- package/SKILL-CLI.md +18 -0
- package/app-config.js +71 -14
- package/cli/credentials.js +11 -0
- package/cli/init.js +45 -3
- package/cli/today.js +176 -0
- package/cli.js +15 -2
- package/crash-capture.js +13 -1
- package/esm-detect.js +5 -1
- package/fastify-hooks.js +67 -0
- package/mcp/catalog.js +28 -0
- package/nextjs-auto-capture.d.ts +5 -2
- package/nextjs-auto-capture.js +77 -220
- package/nextjs.js +46 -176
- package/nuxt-server-plugin.mjs +30 -478
- package/nuxt.mjs +15 -14
- package/package.json +5 -2
- package/pm2-env.js +47 -0
- package/register-vite.js +6 -2
- package/request-capture.js +487 -0
- package/tracing.js +27 -443
package/NPM_README.md
CHANGED
|
@@ -584,6 +584,10 @@ npx securenow credentials runtime --env production
|
|
|
584
584
|
# also works when the canonical credentials.json file is absent.
|
|
585
585
|
# The SDK checks named files in a fixed order and does not use env vars
|
|
586
586
|
# to pick the credentials filename.
|
|
587
|
+
# Hosts without file mounts (Vercel, Railway, Render, Heroku, Fly):
|
|
588
|
+
npx securenow credentials runtime --env production --format env
|
|
589
|
+
# -> SECURENOW_CREDENTIALS=<base64>; store it as a secret environment variable.
|
|
590
|
+
# It takes precedence over credential files.
|
|
587
591
|
|
|
588
592
|
# Use --json for machine-readable output
|
|
589
593
|
npx securenow logs --json --level error | jq '.logs'
|
|
@@ -719,12 +723,16 @@ module.exports = {
|
|
|
719
723
|
apps: [{
|
|
720
724
|
name: 'my-app',
|
|
721
725
|
script: './app.js',
|
|
722
|
-
|
|
726
|
+
instances: 'max', // or leave out for fork mode
|
|
727
|
+
// An absolute path, resolved when PM2 loads this file: in cluster mode Node resolves a bare
|
|
728
|
+
// '-r securenow/register' from the PM2 daemon's folder, so workers started by `pm2 resurrect`
|
|
729
|
+
// at boot crash-loop with "Cannot find module 'securenow/register'".
|
|
730
|
+
node_args: ['-r', require.resolve('securenow/register')],
|
|
723
731
|
}]
|
|
724
732
|
};
|
|
725
733
|
```
|
|
726
734
|
|
|
727
|
-
> **Important:** Put `.securenow/runtime.json` in the PM2 app root for local/dev deployments, or mount the production runtime export as `.securenow/credentials.json`.
|
|
735
|
+
> **Important:** Put `.securenow/runtime.json` in the PM2 app root for local/dev deployments, or mount the production runtime export as `.securenow/credentials.json`. PM2 restarts will then load the SDK, traces/logs, body capture, and firewall without any SecureNow env vars. Fork and cluster mode both read the app folder PM2 started (`pm_cwd`), whichever folder the PM2 daemon itself was started from (before 8.24.0 a cluster worker searched the daemon's folder and could start without credentials).
|
|
728
736
|
|
|
729
737
|
---
|
|
730
738
|
|
|
@@ -840,7 +848,7 @@ PM2 config:
|
|
|
840
848
|
name: 'my-nestjs-app',
|
|
841
849
|
script: 'app.ts',
|
|
842
850
|
interpreter: 'node',
|
|
843
|
-
node_args: '-r securenow/register -r ts-node/register',
|
|
851
|
+
node_args: ['-r', require.resolve('securenow/register'), '-r', require.resolve('ts-node/register')],
|
|
844
852
|
}
|
|
845
853
|
```
|
|
846
854
|
|
|
@@ -1082,12 +1090,16 @@ npx securenow init
|
|
|
1082
1090
|
|
|
1083
1091
|
```typescript
|
|
1084
1092
|
export async function register() {
|
|
1085
|
-
if (process.env.NEXT_RUNTIME
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1093
|
+
if (process.env.NEXT_RUNTIME === 'nodejs') {
|
|
1094
|
+
try {
|
|
1095
|
+
const securenowNext = await import('securenow/nextjs');
|
|
1096
|
+
const registerSecureNow = securenowNext.registerSecureNow || securenowNext.default?.registerSecureNow;
|
|
1097
|
+
registerSecureNow({ captureBody: true });
|
|
1098
|
+
await import('securenow/nextjs-auto-capture');
|
|
1099
|
+
} catch (error) {
|
|
1100
|
+
console.error('[securenow] SecureNow did not start; the app is running without it:', error);
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1091
1103
|
}
|
|
1092
1104
|
```
|
|
1093
1105
|
|
|
@@ -1104,7 +1116,9 @@ const nextConfig = {
|
|
|
1104
1116
|
export default nextConfig;
|
|
1105
1117
|
```
|
|
1106
1118
|
|
|
1107
|
-
No `.env.local` is needed locally or in production. Local setup reads `.securenow/runtime.json`; production should use `npx securenow credentials runtime --env production` and mount/copy the resulting JSON as `.securenow/credentials.json
|
|
1119
|
+
No `.env.local` is needed locally or in production. Local setup reads `.securenow/runtime.json`; production should use `npx securenow credentials runtime --env production` and mount/copy the resulting JSON as `.securenow/credentials.json` (in a standalone Docker image, next to `server.js`). On Vercel or another host without file mounts, run `npx securenow credentials runtime --env production --format env` and store the printed `SECURENOW_CREDENTIALS` value as a secret environment variable.
|
|
1120
|
+
|
|
1121
|
+
Keep the imports inside the `nodejs` block and without `webpackIgnore`: Next.js then drops them from the Edge build and traces `securenow` and its dependencies into `output: 'standalone'`. An instrumentation file written by `init` before 8.24.0 used `webpackIgnore`, so standalone images shipped without OpenTelemetry and every request returned 500; run `npx securenow init` again to replace it.
|
|
1108
1122
|
|
|
1109
1123
|
#### Option B: Manual configuration
|
|
1110
1124
|
|
|
@@ -1133,11 +1147,11 @@ module.exports = {
|
|
|
1133
1147
|
};
|
|
1134
1148
|
```
|
|
1135
1149
|
|
|
1136
|
-
**Why is this needed?** Next.js bundles server code with webpack, which can break OpenTelemetry's dynamic `require()` calls and monkey-patching. Externalizing `securenow` keeps the SDK as normal Node.js runtime code. `outputFileTracingIncludes`
|
|
1150
|
+
**Why is this needed?** Next.js bundles server code with webpack or Turbopack, which can break OpenTelemetry's dynamic `require()` calls and monkey-patching. Externalizing `securenow` keeps the SDK as normal Node.js runtime code, and Next.js traces its dependencies into standalone output. `outputFileTracingIncludes` adds SecureNow's own files that are loaded at runtime, including the firewall.
|
|
1137
1151
|
|
|
1138
1152
|
---
|
|
1139
1153
|
|
|
1140
|
-
### Nuxt 3
|
|
1154
|
+
### Nuxt 3 and 4
|
|
1141
1155
|
|
|
1142
1156
|
```bash
|
|
1143
1157
|
npm install securenow
|
|
@@ -1151,22 +1165,11 @@ export default defineNuxtConfig({
|
|
|
1151
1165
|
});
|
|
1152
1166
|
```
|
|
1153
1167
|
|
|
1154
|
-
Local app identity, runtime API key, and secure defaults come from `.securenow/runtime.json`; production can mount the generated tokenless runtime file as `.securenow/credentials.json` or `.securenow/credentials.<env>.json`.
|
|
1168
|
+
Local app identity, runtime API key, and secure defaults come from `.securenow/runtime.json`; production can mount the generated tokenless runtime file as `.securenow/credentials.json` or `.securenow/credentials.<env>.json` next to the `.output` server, or set `SECURENOW_CREDENTIALS` (see `credentials runtime --format env`).
|
|
1155
1169
|
|
|
1156
|
-
That's it
|
|
1157
|
-
|
|
1158
|
-
```typescript
|
|
1159
|
-
export default defineNuxtConfig({
|
|
1160
|
-
modules: ['securenow/nuxt'],
|
|
1161
|
-
securenow: {
|
|
1162
|
-
captureBody: true,
|
|
1163
|
-
logging: true,
|
|
1164
|
-
noUuid: true,
|
|
1165
|
-
},
|
|
1166
|
-
});
|
|
1167
|
-
```
|
|
1170
|
+
That's it: the module's Nitro plugin loads the same runtime as `node -r securenow/register` (tracing with header and body capture, logs, crash capture, firewall, rate limits, challenges) and keeps `securenow` external, so Nitro copies it with its dependencies into `.output/server/node_modules`. It also records the matched route as `http.route` and handler errors as exceptions. Settings go in `.securenow/runtime.json` (`config.capture.body`, `config.logging.enabled`, ...); `securenow: { ... }` options in `nuxt.config` are no longer used, and the build prints a warning if you set them.
|
|
1168
1171
|
|
|
1169
|
-
|
|
1172
|
+
Before 8.24.0 the module's plugin crashed the `nuxt build` server at startup. Starting `.output/server/index.mjs` with `node -r securenow/register` and no module also works.
|
|
1170
1173
|
|
|
1171
1174
|
---
|
|
1172
1175
|
|
|
@@ -1174,18 +1177,19 @@ The Nuxt server plugin (v5.13.0+) initializes the firewall independently from Op
|
|
|
1174
1177
|
|
|
1175
1178
|
| Framework | Traces | Logs | Body Capture | Firewall | Notes |
|
|
1176
1179
|
|-----------|--------|------|--------------|----------|-------|
|
|
1177
|
-
| Express | Yes | Yes | Yes | Yes | Fully compatible |
|
|
1178
|
-
| Fastify | Yes | Yes | Yes | Yes |
|
|
1179
|
-
| Koa | Yes | Yes | Yes | Yes |
|
|
1180
|
-
| NestJS | Yes | Yes | Yes | Yes |
|
|
1181
|
-
| Hapi | Yes | Yes | Yes | Yes |
|
|
1182
|
-
| h3 | Yes | Yes | Yes | Yes | Uses `toNodeListener()` |
|
|
1183
|
-
| Polka | Yes | Yes | Yes | Yes | Needs
|
|
1184
|
-
| Micro/HTTP | Yes | Yes | Yes | Yes | Full control |
|
|
1185
|
-
| Hono | Yes | Yes | Yes | Yes |
|
|
1186
|
-
| Feathers | Yes | Yes | Yes | Yes |
|
|
1187
|
-
| Next.js | Yes | Yes | Yes | Yes |
|
|
1188
|
-
| Nuxt 3 | Yes | Yes | Yes | Yes |
|
|
1180
|
+
| Express 4 and 5 | Yes | Yes | Yes | Yes | Fully compatible (CommonJS, ESM, TypeScript via tsx) |
|
|
1181
|
+
| Fastify 5 | Yes | Yes | Yes | Yes | `http.route` and handler exceptions recorded through Fastify's `fastify.initialization` channel (8.24.0+) |
|
|
1182
|
+
| Koa 3 | Yes | Yes | Yes | Yes | Body needs a body parser; `http.route` needs `@koa/router` |
|
|
1183
|
+
| NestJS 11 | Yes | Yes | Yes | Yes | `-r securenow/register` before `-r ts-node/register` for TypeScript |
|
|
1184
|
+
| Hapi 21 | Yes | Yes | Yes | Yes | Fully compatible |
|
|
1185
|
+
| h3 1 | Yes | Yes | Yes | Yes | Uses `toNodeListener()`; no `http.route` (raw path) |
|
|
1186
|
+
| Polka | Yes | Yes | Yes | Yes | Needs a body parser; no `http.route`; pass errors to `next(err)` (a thrown error crashes Polka) |
|
|
1187
|
+
| Micro/HTTP | Yes | Yes | Yes | Yes | Full control; no route templates |
|
|
1188
|
+
| Hono 4 (`@hono/node-server`) | Yes | Yes | Yes | Yes | No `http.route` (raw path); errors reach the logs through Hono's default handler |
|
|
1189
|
+
| Feathers 5 | Yes | Yes | Yes | Yes | Koa or Express transport; no `http.route` |
|
|
1190
|
+
| Next.js 15 and 16 | Yes | Yes | Yes | Yes | `instrumentation.ts` + `serverExternalPackages` + `outputFileTracingIncludes`; `next start` and `output: 'standalone'` |
|
|
1191
|
+
| Nuxt 3 and 4 | Yes | Yes | Yes | Yes | `securenow/nuxt` module; `nuxt build` output |
|
|
1192
|
+
| Bun 1.3 (`node:http` apps) | Yes | Yes | Yes | Yes | `bun --preload securenow/register`; `Bun.serve` is not supported |
|
|
1189
1193
|
|
|
1190
1194
|
---
|
|
1191
1195
|
|
|
@@ -1339,8 +1343,9 @@ SecureNow provides multiple entry points depending on your needs:
|
|
|
1339
1343
|
|-------------|-------|-------------------|-------------------|-------|
|
|
1340
1344
|
| `securenow/register` | `node -r securenow/register app.js` | Yes | Yes | Default -- full tracing + firewall |
|
|
1341
1345
|
| `securenow/firewall-only` | `node -r securenow/firewall-only app.js` | No | Yes | Firewall only, no OTel overhead |
|
|
1342
|
-
| `securenow/nextjs` | `await import(
|
|
1343
|
-
| `securenow/nuxt` | `modules: ['securenow/nuxt']` | Yes | Yes | Nuxt 3 module |
|
|
1346
|
+
| `securenow/nextjs` | `await import('securenow/nextjs')` inside the `nodejs` block of `instrumentation.ts` | Yes | Yes | Next.js instrumentation hook |
|
|
1347
|
+
| `securenow/nuxt` | `modules: ['securenow/nuxt']` | Yes | Yes | Nuxt 3 and 4 module |
|
|
1348
|
+
| `securenow/register-vite` | `node -r securenow/register-vite node_modules/vite/bin/vite.js preview` | Yes | Yes | Same as `securenow/register`, for the Node process serving a Vite app |
|
|
1344
1349
|
| `securenow/nextjs-webpack-config` | `withSecureNow(config)` | - | - | Next.js config wrapper |
|
|
1345
1350
|
| `securenow/firewall` | `require('securenow/firewall').init({...})` | No | Yes | Programmatic firewall API |
|
|
1346
1351
|
| `securenow/rate-limits` | `require('securenow/rate-limits').parseRateLimitText(...)` | No | Yes | Rate-limit remediation API helper |
|
|
@@ -1619,12 +1624,16 @@ const enabled: boolean = isLoggingEnabled();
|
|
|
1619
1624
|
```typescript
|
|
1620
1625
|
// instrumentation.ts
|
|
1621
1626
|
export async function register() {
|
|
1622
|
-
if (process.env.NEXT_RUNTIME
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1627
|
+
if (process.env.NEXT_RUNTIME === 'nodejs') {
|
|
1628
|
+
try {
|
|
1629
|
+
const securenowNext = await import('securenow/nextjs');
|
|
1630
|
+
const registerSecureNow = securenowNext.registerSecureNow || securenowNext.default?.registerSecureNow;
|
|
1631
|
+
registerSecureNow({ captureBody: true });
|
|
1632
|
+
await import('securenow/nextjs-auto-capture');
|
|
1633
|
+
} catch (error) {
|
|
1634
|
+
console.error('[securenow] SecureNow did not start; the app is running without it:', error);
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1628
1637
|
}
|
|
1629
1638
|
```
|
|
1630
1639
|
|
|
@@ -1759,7 +1768,7 @@ SecureNow trusts private, loopback, and same-host proxy peers automatically. For
|
|
|
1759
1768
|
|
|
1760
1769
|
**Check 6: Using PM2?**
|
|
1761
1770
|
|
|
1762
|
-
Make sure `node_args: '-r securenow/register'` is in your `ecosystem.config.cjs`. Without it, PM2 restarts skip the SDK entirely
|
|
1771
|
+
Make sure `node_args: ['-r', require.resolve('securenow/register')]` is in your `ecosystem.config.cjs`. Without it, PM2 restarts skip the SDK entirely; with a bare `'-r securenow/register'`, cluster workers started from another folder (`pm2 resurrect` at boot) crash with `Cannot find module 'securenow/register'`.
|
|
1763
1772
|
|
|
1764
1773
|
### Logs Not Appearing
|
|
1765
1774
|
|
|
@@ -1913,12 +1922,12 @@ module.exports = {
|
|
|
1913
1922
|
name: 'my-app',
|
|
1914
1923
|
script: './app.js',
|
|
1915
1924
|
instances: 4,
|
|
1916
|
-
node_args: '-r securenow/register',
|
|
1925
|
+
node_args: ['-r', require.resolve('securenow/register')],
|
|
1917
1926
|
}]
|
|
1918
1927
|
};
|
|
1919
1928
|
```
|
|
1920
1929
|
|
|
1921
|
-
Without `node_args`, PM2 starts your script directly without the securenow preload, so neither tracing nor the firewall will be active.
|
|
1930
|
+
Without `node_args`, PM2 starts your script directly without the securenow preload, so neither tracing nor the firewall will be active. Use the absolute path: in cluster mode Node resolves a bare `-r securenow/register` from the PM2 daemon's folder, and the workers crash-loop when the daemon was started elsewhere (as `pm2 resurrect` does at boot).
|
|
1922
1931
|
|
|
1923
1932
|
---
|
|
1924
1933
|
|
package/README.md
CHANGED
|
@@ -75,7 +75,7 @@ npx securenow login
|
|
|
75
75
|
node -r securenow/register src/index.js
|
|
76
76
|
```
|
|
77
77
|
|
|
78
|
-
No `.env` edits, no API keys to paste. Runtime config lives in `.securenow/runtime.json
|
|
78
|
+
No `.env` edits, no API keys to paste. Runtime config lives in `.securenow/runtime.json`. The only environment variables the SDK reads are `SECURENOW_CREDENTIALS`, for hosts that cannot mount a file (see Production), and the `SECURENOW_DISABLED` test switch.
|
|
79
79
|
|
|
80
80
|
```json
|
|
81
81
|
"scripts": {
|
|
@@ -86,9 +86,9 @@ No `.env` edits, no API keys to paste. Runtime config lives in `.securenow/runti
|
|
|
86
86
|
|
|
87
87
|
Can't change the script? Use `NODE_OPTIONS="-r securenow/register" npm start`, or the wrapper: `npx securenow run src/index.js`.
|
|
88
88
|
|
|
89
|
-
**Next.js** — `npx securenow init` creates `instrumentation.ts` and patches `next.config.*` when it can do so safely (adds `securenow` to `serverExternalPackages` on Next.js 15+). If your config needs human judgment, `init` prints an agent-ready prompt with the exact edits to merge.
|
|
89
|
+
**Next.js** — `npx securenow init` creates `instrumentation.ts` and patches `next.config.*` when it can do so safely (adds `securenow` to `serverExternalPackages` on Next.js 15+). If your config needs human judgment, `init` prints an agent-ready prompt with the exact edits to merge. `next start` and `output: 'standalone'` (Docker) both work. If `init` wrote your instrumentation file before 8.24.0, run it again: it replaces that file, whose `webpackIgnore` imports left SecureNow's dependencies out of standalone builds.
|
|
90
90
|
|
|
91
|
-
**Nuxt 3** — add the module, then run `npx securenow init
|
|
91
|
+
**Nuxt 3 and 4** — add the module, then run `npx securenow init`. The `nuxt build` output is fully covered (tested on Nuxt 3.21 and 4.6, with `.output` running on its own like a Docker image). In `nuxt dev`, requests reach Nitro through Nuxt's dev proxy, so the client IP is unknown there and IP blocking only applies to the built server:
|
|
92
92
|
|
|
93
93
|
```ts
|
|
94
94
|
export default defineNuxtConfig({
|
|
@@ -96,15 +96,30 @@ export default defineNuxtConfig({
|
|
|
96
96
|
});
|
|
97
97
|
```
|
|
98
98
|
|
|
99
|
-
|
|
99
|
+
Settings come from `.securenow/runtime.json` like every other integration; `securenow: { ... }` options in `nuxt.config` are ignored.
|
|
100
100
|
|
|
101
|
-
**
|
|
101
|
+
**PM2** — in `ecosystem.config.cjs`, pass the preload as an absolute path: `node_args: ['-r', require.resolve('securenow/register')]`. That works in fork and cluster mode (`instances: 'max'`) wherever the PM2 daemon was started. (In cluster mode Node resolves a bare `-r securenow/register` from the daemon's folder, so after `pm2 resurrect` at boot the workers crash-loop with `Cannot find module 'securenow/register'`.) Each process reads the `.securenow/` folder of the app PM2 started.
|
|
102
|
+
|
|
103
|
+
**Bun** — `bun --preload securenow/register app.js` works for apps served through `node:http` (tested with Express 5 on Bun 1.3). `Bun.serve` is not traced or protected. Deno is not supported.
|
|
104
|
+
|
|
105
|
+
**What's captured automatically:** HTTP spans (Express, Fastify, NestJS, Koa, Hapi, Hono, Polka, Feathers, h3, Next.js, Nuxt, raw `http`), database spans (Postgres, MySQL, MongoDB, Redis), `console.*` as logs with trace correlation, crashes (uncaught exceptions and unhandled rejections) as `FATAL` logs with their stack, plus the spans that ended just before the crash, request headers and bodies with auto-redaction, multipart metadata (never file content), plus the firewall. `http.route` is recorded for Express, Fastify, NestJS, Koa with `@koa/router`, Hapi, Nuxt, and Next.js 16 (on Next.js's own span; Next.js 15 records `next.route`); Hono, Polka, Feathers and plain h3 show the raw path. Tune everything in `.securenow/runtime.json` under `config`.
|
|
106
|
+
|
|
107
|
+
**Production** — same file structure. Generate a tokenless runtime credentials file and mount it as a deployment secret (Docker, Kubernetes, VMs):
|
|
102
108
|
|
|
103
109
|
```bash
|
|
104
110
|
npx securenow credentials runtime --env production
|
|
105
111
|
# store the output, mount it at <app-root>/.securenow/credentials.json
|
|
106
112
|
```
|
|
107
113
|
|
|
114
|
+
**Hosts without file mounts** (Vercel, Railway, Render, Heroku, Fly) — print the same credentials as one environment variable and store it as a secret there:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
npx securenow credentials runtime --env production --format env
|
|
118
|
+
# SECURENOW_CREDENTIALS=eyJ... -> add as a secret environment variable
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
`SECURENOW_CREDENTIALS` (raw JSON or base64) takes precedence over credential files.
|
|
122
|
+
|
|
108
123
|
**Verify** any environment with:
|
|
109
124
|
|
|
110
125
|
```bash
|
|
@@ -113,7 +128,7 @@ npx securenow doctor # diagnose config + connectivity
|
|
|
113
128
|
npx securenow status # dashboard summary
|
|
114
129
|
```
|
|
115
130
|
|
|
116
|
-
**Tests** — test runs otherwise report to your real app with the project's `.securenow` credentials. `SECURENOW_DISABLED=1 npm test` turns the SDK off for that process: no traces, logs, events, firewall or session sync, and `guard()` just calls `next()`.
|
|
131
|
+
**Tests** — test runs otherwise report to your real app with the project's `.securenow` credentials. `SECURENOW_DISABLED=1 npm test` turns the SDK off for that process: no traces, logs, events, firewall or session sync, and `guard()` just calls `next()`. Apart from `SECURENOW_CREDENTIALS`, this is the only setting read from the environment.
|
|
117
132
|
|
|
118
133
|
---
|
|
119
134
|
|
package/SKILL-API.md
CHANGED
|
@@ -119,8 +119,8 @@ operator needs to ensure those defaults immediately.
|
|
|
119
119
|
| `securenow/cidr` | CIDR utilities; exports `createMatcher()`, `ipToInt()`, `parseCidr()`, `matchesCidr()` | CJS |
|
|
120
120
|
| `securenow/resolve-ip` | IP resolution; exports `resolveClientIp()`, `resolveSocketIp()`, `isFromTrustedProxy()` | CJS |
|
|
121
121
|
| `securenow/console-instrumentation` | Console→OTLP bridge; exports `originalConsole`, `restoreConsole()` | CJS |
|
|
122
|
-
| `securenow/web-vite` | Browser OTel (document load, fetch, XHR, user interaction); default export `startSecurenowWeb()
|
|
123
|
-
| `securenow/register-vite` |
|
|
122
|
+
| `securenow/web-vite` | Browser OTel (document load, fetch, XHR, user interaction); default export `startSecurenowWeb()`. Sending browser telemetry to SecureNow is disabled since 8.0 (no browser-safe auth yet) | ESM |
|
|
123
|
+
| `securenow/register-vite` | Preload: the same runtime as `securenow/register`, for the Node process serving a Vite app (`vite preview`, Vite SSR) | Preload (`-r`) |
|
|
124
124
|
|
|
125
125
|
---
|
|
126
126
|
|
|
@@ -149,8 +149,10 @@ module.exports = {
|
|
|
149
149
|
name: 'my-app',
|
|
150
150
|
script: './app.js',
|
|
151
151
|
instances: 4,
|
|
152
|
-
|
|
153
|
-
//
|
|
152
|
+
// Absolute path: in cluster mode Node resolves a bare '-r securenow/register' from the PM2
|
|
153
|
+
// daemon's folder, and workers started by `pm2 resurrect` crash with "Cannot find module".
|
|
154
|
+
node_args: ['-r', require.resolve('securenow/register')],
|
|
155
|
+
// Reads .securenow/runtime.json from the project root (pm_cwd), wherever the daemon runs.
|
|
154
156
|
}],
|
|
155
157
|
};
|
|
156
158
|
```
|
|
@@ -187,12 +189,16 @@ For Next.js < 15, add `securenow` to `experimental.serverComponentsExternalPacka
|
|
|
187
189
|
|
|
188
190
|
```typescript
|
|
189
191
|
export async function register() {
|
|
190
|
-
if (process.env.NEXT_RUNTIME
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
192
|
+
if (process.env.NEXT_RUNTIME === 'nodejs') {
|
|
193
|
+
try {
|
|
194
|
+
const securenowNext = await import('securenow/nextjs');
|
|
195
|
+
const registerSecureNow = securenowNext.registerSecureNow || securenowNext.default?.registerSecureNow;
|
|
196
|
+
registerSecureNow({ captureBody: true });
|
|
197
|
+
await import('securenow/nextjs-auto-capture');
|
|
198
|
+
} catch (error) {
|
|
199
|
+
console.error('[securenow] SecureNow did not start; the app is running without it:', error);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
196
202
|
}
|
|
197
203
|
```
|
|
198
204
|
|
|
@@ -236,12 +242,16 @@ Add to your `instrumentation.ts`:
|
|
|
236
242
|
|
|
237
243
|
```typescript
|
|
238
244
|
export async function register() {
|
|
239
|
-
if (process.env.NEXT_RUNTIME
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
+
if (process.env.NEXT_RUNTIME === 'nodejs') {
|
|
246
|
+
try {
|
|
247
|
+
const securenowNext = await import('securenow/nextjs');
|
|
248
|
+
const registerSecureNow = securenowNext.registerSecureNow || securenowNext.default?.registerSecureNow;
|
|
249
|
+
registerSecureNow({ captureBody: true });
|
|
250
|
+
await import('securenow/nextjs-auto-capture');
|
|
251
|
+
} catch (error) {
|
|
252
|
+
console.error('[securenow] SecureNow did not start; the app is running without it:', error);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
245
255
|
}
|
|
246
256
|
```
|
|
247
257
|
|
|
@@ -270,39 +280,33 @@ npx securenow app connect
|
|
|
270
280
|
npx securenow init
|
|
271
281
|
```
|
|
272
282
|
|
|
273
|
-
Auto-detects Next.js, creates `instrumentation.ts`, adds `serverExternalPackages: ['securenow']` plus `outputFileTracingIncludes` when safe, and reuses the app, instance, runtime API key, and secure defaults in `.securenow/runtime.json`.
|
|
283
|
+
Auto-detects Next.js, creates `instrumentation.ts`, adds `serverExternalPackages: ['securenow']` plus `outputFileTracingIncludes` when safe, and reuses the app, instance, runtime API key, and secure defaults in `.securenow/runtime.json`. An instrumentation file it generated before 8.24.0 is replaced automatically (its `webpackIgnore` imports left SecureNow's dependencies out of standalone builds); for any other existing file it prints an agent-ready prompt with the exact edits to propose.
|
|
274
284
|
|
|
275
285
|
---
|
|
276
286
|
|
|
277
|
-
### Nuxt 3
|
|
287
|
+
### Nuxt 3 and 4
|
|
278
288
|
|
|
279
289
|
**`nuxt.config.ts`:**
|
|
280
290
|
|
|
281
291
|
```typescript
|
|
282
292
|
export default defineNuxtConfig({
|
|
283
293
|
modules: ['securenow/nuxt'],
|
|
284
|
-
securenow: {
|
|
285
|
-
// optional overrides (defaults come from .securenow/runtime.json)
|
|
286
|
-
},
|
|
287
294
|
});
|
|
288
295
|
```
|
|
289
296
|
|
|
290
|
-
The Nuxt module
|
|
297
|
+
The Nuxt module keeps `securenow` external (Nitro copies it with its dependencies into `.output/server/node_modules`) and adds a server plugin that loads the same runtime as `node -r securenow/register`: tracing with header/body capture, logging, crash capture, firewall, rate limits and challenges, plus `http.route` from the matched route and handler exceptions. Settings come from `.securenow/runtime.json` (production: tokenless `.securenow/credentials.<env>.json` or `SECURENOW_CREDENTIALS`); `securenow: { ... }` options in `nuxt.config` are no longer used. Before 8.24.0 this plugin crashed the `nuxt build` server at startup.
|
|
291
298
|
|
|
292
299
|
---
|
|
293
300
|
|
|
294
|
-
### Vite
|
|
301
|
+
### Vite
|
|
295
302
|
|
|
296
|
-
|
|
297
|
-
import startSecurenowWeb from 'securenow/web-vite';
|
|
303
|
+
Instrument the Node process that serves the app (`vite preview`, a Vite SSR server):
|
|
298
304
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
endpoint: 'https://ingest.securenow.ai',
|
|
302
|
-
});
|
|
305
|
+
```bash
|
|
306
|
+
node -r securenow/register-vite node_modules/vite/bin/vite.js preview
|
|
303
307
|
```
|
|
304
308
|
|
|
305
|
-
|
|
309
|
+
`securenow/register-vite` is the same runtime as `securenow/register`. Browser-side telemetry (`securenow/web-vite`) is not sent to SecureNow: browser ingestion is disabled since 8.0 until browser-safe auth exists.
|
|
306
310
|
|
|
307
311
|
---
|
|
308
312
|
|
package/SKILL-CLI.md
CHANGED
|
@@ -208,10 +208,28 @@ Auto-detects framework (Next.js, Nuxt, Express, Fastify, Koa, Hapi, Node) from `
|
|
|
208
208
|
### Dashboard & Status
|
|
209
209
|
|
|
210
210
|
```bash
|
|
211
|
+
securenow today [--since 24h|7d] [--attackers 20] [--json] # the Overview: what happened, what SecureNow did, what needs you
|
|
212
|
+
securenow attacker <ip> [--json] # why an address was flagged: verdict, signals, traffic vs normal, detections, timeline
|
|
211
213
|
securenow status [--app <key>] [--env local|production|all] # dashboard overview
|
|
212
214
|
securenow analytics [--app <key>] # response analytics
|
|
213
215
|
```
|
|
214
216
|
|
|
217
|
+
`today` is the dashboard's home in the terminal:
|
|
218
|
+
- whether each app is covered (data flowing, firewall on, detection monitoring or blocking);
|
|
219
|
+
- attack requests, attackers, what the firewall stopped and what got through;
|
|
220
|
+
- the decisions waiting for you, the top attackers, and what SecureNow did (the agent's actions, automatic blocks,
|
|
221
|
+
closed probes).
|
|
222
|
+
|
|
223
|
+
`attacker <ip>` explains one address:
|
|
224
|
+
- the verdict and score with the signals behind them, and its reputation;
|
|
225
|
+
- where it is blocked;
|
|
226
|
+
- its last 3 days of traffic against your apps' normal (error rate, share of traffic, scanner probes, attack
|
|
227
|
+
payloads answered 2xx);
|
|
228
|
+
- the detections it triggered and what happened to each, a timeline, and questions to paste into
|
|
229
|
+
`securenow forensics chat`.
|
|
230
|
+
|
|
231
|
+
MCP: `securenow_overview` and `securenow_attacker` (read).
|
|
232
|
+
|
|
215
233
|
---
|
|
216
234
|
|
|
217
235
|
### Traces
|
package/app-config.js
CHANGED
|
@@ -8,14 +8,16 @@
|
|
|
8
8
|
* Named runtime files such as ./.securenow/credentials.staging.json and
|
|
9
9
|
* ./.securenow/credentials.production.json are also accepted when the
|
|
10
10
|
* canonical file is not present. Filename selection is deterministic and does
|
|
11
|
-
* not
|
|
11
|
+
* not depend on NODE_ENV.
|
|
12
12
|
* Every SDK setting has a file-backed equivalent so customers do not need
|
|
13
|
-
* .env files.
|
|
13
|
+
* .env files. Hosts that cannot mount a file (Vercel, Railway, Render, Heroku,
|
|
14
|
+
* Fly) pass the same JSON in one variable, SECURENOW_CREDENTIALS.
|
|
14
15
|
*/
|
|
15
16
|
|
|
16
17
|
const fs = require('fs');
|
|
17
18
|
const path = require('path');
|
|
18
19
|
const os = require('os');
|
|
20
|
+
const { readPm2Env } = require('./pm2-env');
|
|
19
21
|
|
|
20
22
|
// Shared SecureNow Cloud ingestion gateway (production collector for every
|
|
21
23
|
// paying account and for the 14-day trial).
|
|
@@ -240,7 +242,20 @@ function credentialRelativePaths() {
|
|
|
240
242
|
return runtimeCredentialRelativePaths();
|
|
241
243
|
}
|
|
242
244
|
|
|
243
|
-
|
|
245
|
+
// Where to look for .securenow/ files and package.json. A PM2 process is searched from the app
|
|
246
|
+
// folder PM2 recorded and nowhere else: in cluster mode the worker's working directory is still
|
|
247
|
+
// the PM2 daemon's when the preload runs, so searching from it found nothing, or another
|
|
248
|
+
// project's credentials if the daemon was started inside that project.
|
|
249
|
+
function searchStartDirs() {
|
|
250
|
+
const pm2 = readPm2Env();
|
|
251
|
+
if (pm2 && (pm2.cwd || pm2.execPath)) {
|
|
252
|
+
const appDir = pm2.cwd ? path.resolve(pm2.cwd) : null;
|
|
253
|
+
return uniq([
|
|
254
|
+
appDir,
|
|
255
|
+
pm2.execPath ? path.dirname(path.resolve(appDir || '.', pm2.execPath)) : null,
|
|
256
|
+
]);
|
|
257
|
+
}
|
|
258
|
+
|
|
244
259
|
const starts = [];
|
|
245
260
|
try {
|
|
246
261
|
if (typeof process !== 'undefined' && process.cwd) starts.push(process.cwd());
|
|
@@ -250,8 +265,12 @@ function resolveLocalFileFromRelativePaths(relativePaths) {
|
|
|
250
265
|
if (process.argv && process.argv[1]) starts.push(path.dirname(process.argv[1]));
|
|
251
266
|
if (require.main && require.main.filename) starts.push(path.dirname(require.main.filename));
|
|
252
267
|
|
|
268
|
+
return uniq(starts);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function resolveLocalFileFromRelativePaths(relativePaths) {
|
|
253
272
|
const candidates = relativePaths;
|
|
254
|
-
for (const start of
|
|
273
|
+
for (const start of searchStartDirs()) {
|
|
255
274
|
const found = findUpFirstFile(start, candidates);
|
|
256
275
|
if (found) return found;
|
|
257
276
|
}
|
|
@@ -314,16 +333,7 @@ function runtimeCredentialsFromDocument(credentials) {
|
|
|
314
333
|
}
|
|
315
334
|
|
|
316
335
|
function resolvePackageJsonFile() {
|
|
317
|
-
const
|
|
318
|
-
try {
|
|
319
|
-
if (typeof process !== 'undefined' && process.cwd) starts.push(process.cwd());
|
|
320
|
-
} catch {}
|
|
321
|
-
|
|
322
|
-
if (process.env.INIT_CWD) starts.push(process.env.INIT_CWD);
|
|
323
|
-
if (process.argv && process.argv[1]) starts.push(path.dirname(process.argv[1]));
|
|
324
|
-
if (require.main && require.main.filename) starts.push(path.dirname(require.main.filename));
|
|
325
|
-
|
|
326
|
-
for (const start of uniq(starts)) {
|
|
336
|
+
for (const start of searchStartDirs()) {
|
|
327
337
|
const found = findUpFile(start, 'package.json');
|
|
328
338
|
if (found) return found;
|
|
329
339
|
}
|
|
@@ -347,7 +357,50 @@ function loadGlobalCredentials() {
|
|
|
347
357
|
}
|
|
348
358
|
}
|
|
349
359
|
|
|
360
|
+
const ENV_CREDENTIALS_VAR = 'SECURENOW_CREDENTIALS';
|
|
361
|
+
let _envCredentials = { raw: undefined, value: null };
|
|
362
|
+
let _warnedBadEnvCredentials = false;
|
|
363
|
+
|
|
364
|
+
// Runtime credentials passed in the environment, for hosts that cannot mount a file. The value is
|
|
365
|
+
// the JSON written by `npx securenow credentials runtime`, raw or base64-encoded (`--format env`
|
|
366
|
+
// prints it ready to paste). When set it takes precedence over credential files.
|
|
367
|
+
function loadEnvCredentials() {
|
|
368
|
+
const raw = typeof process !== 'undefined' && process.env ? process.env[ENV_CREDENTIALS_VAR] : undefined;
|
|
369
|
+
if (raw !== _envCredentials.raw) {
|
|
370
|
+
let value = null;
|
|
371
|
+
const text = raw == null ? '' : String(raw).trim();
|
|
372
|
+
if (text) {
|
|
373
|
+
try {
|
|
374
|
+
const json = text.startsWith('{') ? text : Buffer.from(text, 'base64').toString('utf8');
|
|
375
|
+
value = withCredentialDefaults(runtimeCredentialsFromDocument(JSON.parse(json.replace(/^\uFEFF/, ''))));
|
|
376
|
+
} catch {
|
|
377
|
+
value = null;
|
|
378
|
+
}
|
|
379
|
+
if (!value && !_warnedBadEnvCredentials) {
|
|
380
|
+
_warnedBadEnvCredentials = true;
|
|
381
|
+
try {
|
|
382
|
+
console.warn('[securenow] %s is set but is not runtime credentials JSON (raw or base64); ignoring it.', ENV_CREDENTIALS_VAR);
|
|
383
|
+
} catch {}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
_envCredentials = { raw, value };
|
|
387
|
+
}
|
|
388
|
+
return _envCredentials.value ? clone(_envCredentials.value) : null;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Where the runtime credentials came from, for the startup log: never their content.
|
|
392
|
+
function describeCredentialsSource() {
|
|
393
|
+
try {
|
|
394
|
+
if (loadEnvCredentials()) return `env:${ENV_CREDENTIALS_VAR}`;
|
|
395
|
+
return resolveLocalCredentialsFile() || resolveGlobalCredentialsFile() || 'none';
|
|
396
|
+
} catch {
|
|
397
|
+
return 'none';
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
350
401
|
function loadCredentials() {
|
|
402
|
+
const fromEnv = loadEnvCredentials();
|
|
403
|
+
if (fromEnv) return fromEnv;
|
|
351
404
|
try {
|
|
352
405
|
const localFile = resolveLocalCredentialsFile();
|
|
353
406
|
if (localFile) {
|
|
@@ -781,6 +834,10 @@ module.exports = {
|
|
|
781
834
|
resolveLocalAdminCredentialsFile,
|
|
782
835
|
resolveGlobalAdminCredentialsFile,
|
|
783
836
|
loadCredentials,
|
|
837
|
+
loadEnvCredentials,
|
|
838
|
+
describeCredentialsSource,
|
|
839
|
+
searchStartDirs,
|
|
840
|
+
ENV_CREDENTIALS_VAR,
|
|
784
841
|
loadLocalCredentials,
|
|
785
842
|
loadGlobalCredentials,
|
|
786
843
|
loadPackageJsonName,
|
package/cli/credentials.js
CHANGED
|
@@ -73,6 +73,17 @@ async function runtime(_args, flags) {
|
|
|
73
73
|
warn('Runtime API key is missing. Run `npx securenow app connect` or `npx securenow api-key create` before generating production runtime credentials.');
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
// For hosts that inject environment variables but cannot mount a file (Vercel, Railway,
|
|
77
|
+
// Render, Heroku, Fly): one line to paste as a secret. The SDK reads SECURENOW_CREDENTIALS
|
|
78
|
+
// (raw JSON or base64) before any credentials file.
|
|
79
|
+
if (flags.format === 'env' || flags['env-var']) {
|
|
80
|
+
const { ENV_CREDENTIALS_VAR } = appConfig;
|
|
81
|
+
const encoded = Buffer.from(JSON.stringify(creds), 'utf8').toString('base64');
|
|
82
|
+
console.log(`${ENV_CREDENTIALS_VAR}=${encoded}`);
|
|
83
|
+
console.error(`! This line contains the runtime API key. Store it as a secret variable named ${ENV_CREDENTIALS_VAR}; do not commit it.`);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
76
87
|
if (flags.stdout) {
|
|
77
88
|
console.log(JSON.stringify(creds, null, 2));
|
|
78
89
|
return;
|