securenow 7.7.13 → 7.7.15
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 +98 -140
- package/README.md +46 -32
- package/SKILL-API.md +495 -491
- package/SKILL-CLI.md +9 -9
- package/app-config.js +99 -42
- package/cli/apps.js +589 -597
- package/cli/auth.js +1 -3
- package/cli/config.js +37 -9
- package/cli/credentials.js +1 -1
- package/cli/diagnostics.js +10 -6
- package/cli/init.js +1 -0
- package/free-trial-banner.js +2 -2
- package/mcp/catalog.js +2 -2
- package/nextjs-webpack-config.js +41 -18
- package/nextjs.d.ts +67 -63
- package/nextjs.js +57 -46
- package/nuxt-server-plugin.mjs +6 -11
- package/nuxt.d.ts +42 -38
- package/nuxt.mjs +1 -1
- package/package.json +1 -1
- package/tracing.d.ts +2 -1
- package/tracing.js +31 -47
- package/web-vite.mjs +102 -15
package/NPM_README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# SecureNow - Complete OpenTelemetry Observability for Node.js
|
|
1
|
+
# SecureNow - Complete OpenTelemetry Observability for Node.js
|
|
2
2
|
|
|
3
3
|
OpenTelemetry instrumentation library for Node.js, Next.js, and Nuxt applications. Send distributed traces and logs to any OTLP-compatible observability backend.
|
|
4
4
|
|
|
@@ -9,7 +9,7 @@ OpenTelemetry instrumentation library for Node.js, Next.js, and Nuxt application
|
|
|
9
9
|
- Built-in sensitive data redaction
|
|
10
10
|
- Request body capture for debugging
|
|
11
11
|
- Multi-layer firewall -- auto-blocks IPs from your SecureNow blocklist
|
|
12
|
-
- `securenow init` scaffolds Next.js instrumentation
|
|
12
|
+
- `securenow init` scaffolds Next.js instrumentation, safe `serverExternalPackages`, and standalone output tracing includes
|
|
13
13
|
- `securenow/firewall-only` entry point for firewall without tracing overhead
|
|
14
14
|
- Local and production configuration via `.securenow/credentials.json`
|
|
15
15
|
- Single `-r securenow/register` flag -- works for both CJS and ESM apps
|
|
@@ -77,7 +77,7 @@ npx securenow init --key snk_live_abc123...
|
|
|
77
77
|
|
|
78
78
|
This detects your framework and:
|
|
79
79
|
- **Credentials**: Ensures `.securenow/credentials.json` has secure defaults and explanations
|
|
80
|
-
- **Next.js**: Creates `instrumentation.ts`, adds `serverExternalPackages: ['securenow']` when safe, or prints a Codex/Claude-ready merge prompt for existing files
|
|
80
|
+
- **Next.js**: Creates `instrumentation.ts`, adds `serverExternalPackages: ['securenow']` plus `outputFileTracingIncludes` when safe, or prints a Codex/Claude-ready merge prompt for existing files
|
|
81
81
|
- **Nuxt 3**: Suggests adding `securenow/nuxt` to modules
|
|
82
82
|
- **Express / Node.js**: Shows how to add `-r securenow/register` to your start script
|
|
83
83
|
- **All**: Stores `--key` in `.securenow/credentials.json`; no local `.env` file is needed
|
|
@@ -86,7 +86,7 @@ This detects your framework and:
|
|
|
86
86
|
|
|
87
87
|
#### Configure Locally
|
|
88
88
|
|
|
89
|
-
Run `npx securenow login` to write `.securenow/credentials.json`. The SDK reads app identity,
|
|
89
|
+
Run `npx securenow login` to write `.securenow/credentials.json`. The SDK reads app identity, firewall key, logging/body-capture defaults, and firewall defaults from that file at boot. Telemetry uses the default SecureNow ingestion gateway and routes by `app.key`, so customer credentials do not expose per-instance collector URLs. Production uses the same file shape via `npx securenow credentials runtime --env production`.
|
|
90
90
|
|
|
91
91
|
#### Run Your Application
|
|
92
92
|
|
|
@@ -127,7 +127,7 @@ const app = express();
|
|
|
127
127
|
You'll see confirmation in the console:
|
|
128
128
|
|
|
129
129
|
```
|
|
130
|
-
[securenow] OTel SDK started ->
|
|
130
|
+
[securenow] OTel SDK started -> https://ingest.securenow.ai/v1/traces
|
|
131
131
|
[securenow] Firewall: ENABLED
|
|
132
132
|
[securenow] Firewall: synced 142 blocked IPs (138 exact + 4 CIDR ranges)
|
|
133
133
|
```
|
|
@@ -138,7 +138,7 @@ You'll see confirmation in the console:
|
|
|
138
138
|
|
|
139
139
|
The `securenow` CLI gives you full access to the SecureNow platform from the terminal -- no browser required for day-to-day workflows. Zero additional dependencies.
|
|
140
140
|
|
|
141
|
-
**Full CLI/SDK parity (v6.1.0+):** every SDK export has a matching CLI command. `redactSensitiveData` -> `securenow redact`, `createMatcher` -> `securenow cidr match`, `getLogger().emit()` -> `securenow log send`,
|
|
141
|
+
**Full CLI/SDK parity (v6.1.0+):** every SDK export has a matching CLI command. `redactSensitiveData` -> `securenow redact`, `createMatcher` -> `securenow cidr match`, `getLogger().emit()` -> `securenow log send`, startup smoke spans -> `securenow test-span`, `node -r securenow/firewall-only` -> `securenow run --firewall-only`. False-positive triage (`fp create`, `fp ai-fill`, `fp mark`) works from the terminal without the web dashboard.
|
|
142
142
|
|
|
143
143
|
### Getting Started
|
|
144
144
|
|
|
@@ -174,7 +174,7 @@ npx securenow init
|
|
|
174
174
|
npx securenow init --key snk_live_abc123...
|
|
175
175
|
```
|
|
176
176
|
|
|
177
|
-
For Next.js projects, `init` creates `instrumentation.ts` (or `.js` if no TypeScript), adds `serverExternalPackages: ['securenow']` when safe, and prints exact merge instructions for Codex/Claude when existing files need judgment. For Nuxt, it suggests adding `securenow/nuxt` to your modules. For Express/Node, it shows the `-r securenow/register` flag.
|
|
177
|
+
For Next.js projects, `init` creates `instrumentation.ts` (or `.js` if no TypeScript), adds `serverExternalPackages: ['securenow']` plus `outputFileTracingIncludes` when safe, and prints exact merge instructions for Codex/Claude when existing files need judgment. For Nuxt, it suggests adding `securenow/nuxt` to your modules. For Express/Node, it shows the `-r securenow/register` flag.
|
|
178
178
|
|
|
179
179
|
### MCP for Codex and Claude
|
|
180
180
|
|
|
@@ -374,7 +374,7 @@ npx securenow test-span
|
|
|
374
374
|
npx securenow test-span "ci.smoke-test" # custom span name
|
|
375
375
|
```
|
|
376
376
|
|
|
377
|
-
Both commands use the resolved
|
|
377
|
+
Both commands use the resolved credentials JSON endpoints and headers. Non-zero exit on HTTP errors so CI/cron can detect failures.
|
|
378
378
|
|
|
379
379
|
### Utilities -- Redaction, CIDR, Diagnostics
|
|
380
380
|
|
|
@@ -434,14 +434,18 @@ Every command supports these flags:
|
|
|
434
434
|
| `--help` | | Show help for the command |
|
|
435
435
|
| `--app <key>` | | Override the default application key |
|
|
436
436
|
|
|
437
|
-
###
|
|
437
|
+
### Legacy CLI Overrides
|
|
438
438
|
|
|
439
|
-
|
|
439
|
+
Normal CLI, SDK, and production runtime setup uses `.securenow/credentials.json`.
|
|
440
|
+
Old per-terminal CLI overrides still exist for operator troubleshooting, but
|
|
441
|
+
they are not part of the SDK runtime configuration path.
|
|
442
|
+
|
|
443
|
+
| Override | Description |
|
|
440
444
|
|----------|-------------|
|
|
441
|
-
| `SECURENOW_TOKEN` |
|
|
442
|
-
| `SECURENOW_API_URL` |
|
|
443
|
-
| `SECURENOW_DEBUG` |
|
|
444
|
-
| `NO_COLOR` | Disable colored output |
|
|
445
|
+
| `SECURENOW_TOKEN` | Legacy CLI auth override for a single terminal session |
|
|
446
|
+
| `SECURENOW_API_URL` | Legacy CLI API base override for testing |
|
|
447
|
+
| `SECURENOW_DEBUG` | CLI stack traces while debugging |
|
|
448
|
+
| `NO_COLOR` | Disable colored CLI output |
|
|
445
449
|
|
|
446
450
|
### Multi-Project Sessions
|
|
447
451
|
|
|
@@ -460,7 +464,7 @@ npx securenow login
|
|
|
460
464
|
npx securenow whoami # Shows auth source: project (.securenow/)
|
|
461
465
|
```
|
|
462
466
|
|
|
463
|
-
For new automation,
|
|
467
|
+
For new automation, use project-local or runtime credentials files.
|
|
464
468
|
|
|
465
469
|
### CI/CD Integration
|
|
466
470
|
|
|
@@ -566,7 +570,7 @@ npx securenow logs --json --level error | jq '.logs'
|
|
|
566
570
|
|
|
567
571
|
## Framework-Specific Setup
|
|
568
572
|
|
|
569
|
-
> **v5.6.0+:** When `
|
|
573
|
+
> **v5.6.0+:** When `config.logging.enabled` is `true`, all `console.log`/`warn`/`error`/`info`/`debug` calls
|
|
570
574
|
> are **automatically** forwarded as OTLP log records. The separate `require('securenow/console-instrumentation')` is no longer needed (but still available for backward compat).
|
|
571
575
|
|
|
572
576
|
### Express.js
|
|
@@ -641,7 +645,7 @@ fastify.listen({ port: 3000 }, (err) => {
|
|
|
641
645
|
});
|
|
642
646
|
```
|
|
643
647
|
|
|
644
|
-
> **Default:** Traces, logs, POST body capture, multipart metadata capture, and firewall protection are on. If a specific Fastify version or plugin stack reports request-stream conflicts, set `
|
|
648
|
+
> **Default:** Traces, logs, POST body capture, multipart metadata capture, and firewall protection are on. If a specific Fastify version or plugin stack reports request-stream conflicts, set `config.capture.body=false` in credentials as a local override.
|
|
645
649
|
|
|
646
650
|
---
|
|
647
651
|
|
|
@@ -764,7 +768,7 @@ const init = async () => {
|
|
|
764
768
|
init().catch((err) => { console.error(err); process.exit(1); });
|
|
765
769
|
```
|
|
766
770
|
|
|
767
|
-
> **Default:** Traces, logs, POST body capture, multipart metadata capture, and firewall protection are on. If a specific Hapi version or payload plugin reports request-stream conflicts, set `
|
|
771
|
+
> **Default:** Traces, logs, POST body capture, multipart metadata capture, and firewall protection are on. If a specific Hapi version or payload plugin reports request-stream conflicts, set `config.capture.body=false` in credentials as a local override.
|
|
768
772
|
|
|
769
773
|
---
|
|
770
774
|
|
|
@@ -957,7 +961,7 @@ Use `npx securenow init` for the current Next.js integration; it creates or prin
|
|
|
957
961
|
|
|
958
962
|
#### Option A: `securenow init` (Recommended)
|
|
959
963
|
|
|
960
|
-
The init command handles the boring parts: credentials defaults, `instrumentation.ts`, body auto-capture,
|
|
964
|
+
The init command handles the boring parts: credentials defaults, `instrumentation.ts`, body auto-capture, `serverExternalPackages`, and standalone output tracing includes for Next 15+. If existing files are custom, it prints a Codex/Claude-ready prompt instead of guessing.
|
|
961
965
|
|
|
962
966
|
```bash
|
|
963
967
|
npx securenow login
|
|
@@ -967,15 +971,13 @@ npx securenow init
|
|
|
967
971
|
**Generated `instrumentation.ts` (or `.js`):**
|
|
968
972
|
|
|
969
973
|
```typescript
|
|
970
|
-
import { createRequire } from 'node:module';
|
|
971
|
-
|
|
972
|
-
const require = createRequire(import.meta.url);
|
|
973
|
-
|
|
974
974
|
export async function register() {
|
|
975
975
|
if (process.env.NEXT_RUNTIME !== 'nodejs') return;
|
|
976
|
-
|
|
976
|
+
|
|
977
|
+
const securenowNext = await import(/* webpackIgnore: true */ 'securenow/nextjs');
|
|
978
|
+
const registerSecureNow = securenowNext.registerSecureNow || securenowNext.default?.registerSecureNow;
|
|
977
979
|
registerSecureNow({ captureBody: true });
|
|
978
|
-
|
|
980
|
+
await import(/* webpackIgnore: true */ 'securenow/nextjs-auto-capture');
|
|
979
981
|
}
|
|
980
982
|
```
|
|
981
983
|
|
|
@@ -984,6 +986,9 @@ export async function register() {
|
|
|
984
986
|
```javascript
|
|
985
987
|
const nextConfig = {
|
|
986
988
|
serverExternalPackages: ['securenow'],
|
|
989
|
+
outputFileTracingIncludes: {
|
|
990
|
+
'/*': ['./node_modules/securenow/**/*'],
|
|
991
|
+
},
|
|
987
992
|
};
|
|
988
993
|
|
|
989
994
|
export default nextConfig;
|
|
@@ -999,6 +1004,9 @@ If you prefer not to run `init`, manually externalize SecureNow:
|
|
|
999
1004
|
// next.config.js (Next.js 15+)
|
|
1000
1005
|
module.exports = {
|
|
1001
1006
|
serverExternalPackages: ['securenow'],
|
|
1007
|
+
outputFileTracingIncludes: {
|
|
1008
|
+
'/*': ['./node_modules/securenow/**/*'],
|
|
1009
|
+
},
|
|
1002
1010
|
};
|
|
1003
1011
|
```
|
|
1004
1012
|
|
|
@@ -1009,10 +1017,13 @@ module.exports = {
|
|
|
1009
1017
|
instrumentationHook: true,
|
|
1010
1018
|
serverComponentsExternalPackages: ['securenow'],
|
|
1011
1019
|
},
|
|
1020
|
+
outputFileTracingIncludes: {
|
|
1021
|
+
'/*': ['./node_modules/securenow/**/*'],
|
|
1022
|
+
},
|
|
1012
1023
|
};
|
|
1013
1024
|
```
|
|
1014
1025
|
|
|
1015
|
-
**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.
|
|
1026
|
+
**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` keeps SecureNow's runtime modules available in standalone/self-hosted output, including the firewall.
|
|
1016
1027
|
|
|
1017
1028
|
---
|
|
1018
1029
|
|
|
@@ -1054,16 +1065,16 @@ The Nuxt server plugin (v5.13.0+) initializes the firewall independently from Op
|
|
|
1054
1065
|
| Framework | Traces | Logs | Body Capture | Firewall | Notes |
|
|
1055
1066
|
|-----------|--------|------|--------------|----------|-------|
|
|
1056
1067
|
| Express | Yes | Yes | Yes | Yes | Fully compatible |
|
|
1057
|
-
| Fastify | Yes | Yes | Yes | Yes | Default on; use `
|
|
1068
|
+
| Fastify | Yes | Yes | Yes | Yes | Default on; use `config.capture.body=false` only for local stream conflicts |
|
|
1058
1069
|
| Koa | Yes | Yes | Yes | Yes | Needs `koa-bodyparser` |
|
|
1059
1070
|
| NestJS | Yes | Yes | Yes | Yes | Use `-r ts-node/register` |
|
|
1060
|
-
| Hapi | Yes | Yes | Yes | Yes | Default on; use `
|
|
1071
|
+
| Hapi | Yes | Yes | Yes | Yes | Default on; use `config.capture.body=false` only for local stream conflicts |
|
|
1061
1072
|
| h3 | Yes | Yes | Yes | Yes | Uses `toNodeListener()` |
|
|
1062
1073
|
| Polka | Yes | Yes | Yes | Yes | Needs manual body parser |
|
|
1063
1074
|
| Micro/HTTP | Yes | Yes | Yes | Yes | Full control |
|
|
1064
1075
|
| Hono | Yes | Yes | Yes | Yes | Use ESM `-r` preload |
|
|
1065
1076
|
| Feathers | Yes | Yes | Yes | Yes | Uses Express transport |
|
|
1066
|
-
| Next.js | Yes | Yes | Yes | Yes | Use `instrumentation.ts` + `serverExternalPackages
|
|
1077
|
+
| Next.js | Yes | Yes | Yes | Yes | Use `instrumentation.ts` + `serverExternalPackages` + `outputFileTracingIncludes` |
|
|
1067
1078
|
| Nuxt 3 | Yes | Yes | Yes | Yes | Use `securenow/nuxt` module |
|
|
1068
1079
|
|
|
1069
1080
|
---
|
|
@@ -1088,7 +1099,7 @@ npx securenow api-key set snk_live_abc123...
|
|
|
1088
1099
|
npx securenow credentials runtime --env production
|
|
1089
1100
|
```
|
|
1090
1101
|
|
|
1091
|
-
The SDK resolves the firewall key from project `./.securenow/credentials.json`, then project named runtime credentials in the fixed staging/production/preview/local/test/development/dev/prod order, then global `~/.securenow/credentials.json`, then global named runtime credentials in the same fixed order.
|
|
1102
|
+
The SDK resolves the firewall key from project `./.securenow/credentials.json`, then project named runtime credentials in the fixed staging/production/preview/local/test/development/dev/prod order, then global `~/.securenow/credentials.json`, then global named runtime credentials in the same fixed order.
|
|
1092
1103
|
|
|
1093
1104
|
On startup, you'll see:
|
|
1094
1105
|
|
|
@@ -1135,7 +1146,7 @@ This is useful when:
|
|
|
1135
1146
|
- You only need IP blocking, not observability
|
|
1136
1147
|
- You want to minimize startup time and memory footprint
|
|
1137
1148
|
- You're adding the firewall to a project that uses a different tracing solution
|
|
1138
|
-
- For Next
|
|
1149
|
+
- For non-Next Node apps, this avoids framework-specific tracing setup entirely
|
|
1139
1150
|
|
|
1140
1151
|
Firewall-only mode uses the same `.securenow/credentials.json` key written by `login`, `api-key set`, or mounted from a runtime credentials file:
|
|
1141
1152
|
|
|
@@ -1148,12 +1159,12 @@ node -r securenow/firewall-only app.js
|
|
|
1148
1159
|
|
|
1149
1160
|
The firewall supports four layers -- Layer 1 is always on, the rest are opt-in:
|
|
1150
1161
|
|
|
1151
|
-
| Layer |
|
|
1162
|
+
| Layer | Credentials key | Description |
|
|
1152
1163
|
|-------|---------|-------------|
|
|
1153
1164
|
| **Layer 1: HTTP** | *(always on)* | Returns 403 Forbidden with a security alert page. Works with proxy headers. |
|
|
1154
|
-
| **Layer 2: TCP** | `
|
|
1155
|
-
| **Layer 3: iptables** | `
|
|
1156
|
-
| **Layer 4: Cloud WAF** | `
|
|
1165
|
+
| **Layer 2: TCP** | `config.firewall.tcp=true` | `socket.destroy()` -- zero bytes sent back |
|
|
1166
|
+
| **Layer 3: iptables** | `config.firewall.iptables=true` | Kernel-level DROP (Linux, requires root) |
|
|
1167
|
+
| **Layer 4: Cloud WAF** | `config.firewall.cloud="cloudflare"` | Pushes to Cloudflare, AWS WAF, or GCP Cloud Armor |
|
|
1157
1168
|
|
|
1158
1169
|
### Blocked Page
|
|
1159
1170
|
|
|
@@ -1185,8 +1196,8 @@ Use `.securenow/credentials.json` as the source of truth. Run `npx securenow env
|
|
|
1185
1196
|
|
|
1186
1197
|
| Field | Description | Default |
|
|
1187
1198
|
|----------|-------------|---------|
|
|
1188
|
-
| `app.key` |
|
|
1189
|
-
| `app.
|
|
1199
|
+
| `app.key` | App routing UUID. The SecureNow ingestion gateway routes telemetry by this key. | selected during login |
|
|
1200
|
+
| `app.name` | Human-readable app label. | selected during login |
|
|
1190
1201
|
| `apiKey` | Scoped firewall key (`snk_live_...`). | minted during login |
|
|
1191
1202
|
| `config.runtime.deploymentEnvironment` | `deployment.environment` trace/log scope. | `local` from init, `production` from runtime credentials |
|
|
1192
1203
|
| `config.logging.enabled` | Automatic console log export. | `true` |
|
|
@@ -1195,90 +1206,18 @@ Use `.securenow/credentials.json` as the source of truth. Run `npx securenow env
|
|
|
1195
1206
|
| `config.firewall.enabled` | Local SDK firewall switch; dashboard toggle is per environment. | `true` |
|
|
1196
1207
|
| `config.otel.*` | Optional custom endpoints, headers, and log level. | empty |
|
|
1197
1208
|
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|----------|-------------|---------|
|
|
1204
|
-
| `SECURENOW_APPID` | Fallback for missing credentials `app.key`. Used as the app routing key/service name. | `<uuid>` |
|
|
1205
|
-
| `SECURENOW_INSTANCE` | Fallback for missing credentials `app.instance`. Base URL of your OTLP collector endpoint. | `https://freetrial.securenow.ai:4318` |
|
|
1206
|
-
|
|
1207
|
-
### Optional Configuration
|
|
1208
|
-
|
|
1209
|
-
#### Service Naming
|
|
1210
|
-
|
|
1211
|
-
| Variable | Description | Default |
|
|
1212
|
-
|----------|-------------|---------|
|
|
1213
|
-
| `OTEL_SERVICE_NAME` | Fallback for missing `app.name`. Standard OpenTelemetry variable. | - |
|
|
1214
|
-
| `SECURENOW_NO_UUID` | Legacy fallback for `config.runtime.noUuid`. | `0` |
|
|
1215
|
-
| `SECURENOW_STRICT` | Legacy fallback for `config.runtime.strict`. | `0` |
|
|
1216
|
-
|
|
1217
|
-
#### Connection Settings
|
|
1218
|
-
|
|
1219
|
-
| Variable | Description | Default |
|
|
1220
|
-
|----------|-------------|---------|
|
|
1221
|
-
| `OTEL_EXPORTER_OTLP_ENDPOINT` | Fallback for `config.otel.endpoint`. | - |
|
|
1222
|
-
| `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` | Fallback for `config.otel.tracesEndpoint`. | `{instance}/v1/traces` |
|
|
1223
|
-
| `OTEL_EXPORTER_OTLP_LOGS_ENDPOINT` | Fallback for `config.otel.logsEndpoint`. | `{instance}/v1/logs` |
|
|
1224
|
-
| `OTEL_EXPORTER_OTLP_HEADERS` | Fallback for `config.otel.headers`. Format: `key1=value1,key2=value2` | - |
|
|
1225
|
-
|
|
1226
|
-
#### Logging
|
|
1227
|
-
|
|
1228
|
-
| Variable | Description | Default |
|
|
1229
|
-
|----------|-------------|---------|
|
|
1230
|
-
| `SECURENOW_LOGGING_ENABLED` | Enable automatic logging to OTLP backend. Set to `0` to disable. | `1` |
|
|
1231
|
-
|
|
1232
|
-
#### Request Body Capture
|
|
1233
|
-
|
|
1234
|
-
| Variable | Description | Default |
|
|
1235
|
-
|----------|-------------|---------|
|
|
1236
|
-
| `SECURENOW_CAPTURE_BODY` | Capture request bodies in traces. Set to `0` to disable. | `1` |
|
|
1237
|
-
| `SECURENOW_MAX_BODY_SIZE` | Maximum body size to capture in bytes. Bodies larger than this are truncated. | `10240` (10KB) |
|
|
1238
|
-
| `SECURENOW_SENSITIVE_FIELDS` | Comma-separated list of additional field names to redact. | - |
|
|
1239
|
-
| `SECURENOW_CAPTURE_MULTIPART` | Capture multipart/form-data metadata. Streams through the request to extract text field values and file metadata (name, filename, content-type, size) without buffering file content. Set to `0` to disable. | `1` |
|
|
1209
|
+
The credentials file is versioned with `_securenow.schemaVersion`. The SDK reads
|
|
1210
|
+
all runtime settings from this JSON plus built-in defaults. Production should
|
|
1211
|
+
mount a tokenless runtime credentials file at `.securenow/credentials.json`.
|
|
1212
|
+
Legacy env fallback is disabled by default and exists only for old deployments
|
|
1213
|
+
that explicitly opt in with `SECURENOW_ENABLE_LEGACY_ENV=1`.
|
|
1240
1214
|
|
|
1241
1215
|
**Default sensitive fields (auto-redacted):** `password`, `passwd`, `pwd`, `secret`, `token`, `api_key`, `apikey`, `access_token`, `auth`, `credentials`, `mysql_pwd`, `stripeToken`, `card`, `cardnumber`, `ccv`, `cvc`, `cvv`, `ssn`, `pin`
|
|
1242
1216
|
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
| `SECURENOW_DISABLE_INSTRUMENTATIONS` | Comma-separated list of instrumentation packages to disable. | - |
|
|
1248
|
-
|
|
1249
|
-
**Example:** `SECURENOW_DISABLE_INSTRUMENTATIONS=fs,dns` disables filesystem and DNS instrumentations.
|
|
1250
|
-
|
|
1251
|
-
#### Firewall
|
|
1252
|
-
|
|
1253
|
-
| Variable | Description | Default |
|
|
1254
|
-
|----------|-------------|---------|
|
|
1255
|
-
| `SECURENOW_API_KEY` | Legacy firewall key override. Prefer `apiKey` in `.securenow/credentials.json`. | from creds file |
|
|
1256
|
-
| `SECURENOW_API_URL` | SecureNow API base URL. Auto-detected for co-located deployments (falls back to `http://localhost:4000` on ECONNREFUSED). | `https://api.securenow.ai` |
|
|
1257
|
-
| `SECURENOW_FIREWALL_VERSION_INTERVAL` | Seconds between lightweight ETag checks. | `10` |
|
|
1258
|
-
| `SECURENOW_FIREWALL_SYNC_INTERVAL` | Safety-net full blocklist refresh interval in seconds. | `3600` |
|
|
1259
|
-
| `SECURENOW_FIREWALL_FAIL_MODE` | `open` (allow when unavailable) or `closed` (block all). | `open` |
|
|
1260
|
-
| `SECURENOW_FIREWALL_STATUS_CODE` | HTTP status code for blocked requests. | `403` |
|
|
1261
|
-
| `SECURENOW_FIREWALL_LOG` | Log blocked requests and sync events to console. Set to `0` to silence. | `1` |
|
|
1262
|
-
| `SECURENOW_FIREWALL_TCP` | Enable Layer 2 TCP blocking. | `0` |
|
|
1263
|
-
| `SECURENOW_FIREWALL_IPTABLES` | Enable Layer 3 iptables blocking. | `0` |
|
|
1264
|
-
| `SECURENOW_FIREWALL_CLOUD` | Cloud WAF provider: `cloudflare`, `aws`, or `gcp`. | - |
|
|
1265
|
-
| `SECURENOW_FIREWALL_CLOUD_DRY_RUN` | Log cloud pushes without applying changes. | `0` |
|
|
1266
|
-
| `SECURENOW_TRUSTED_PROXIES` | Comma-separated trusted proxy IPs. | - |
|
|
1267
|
-
|
|
1268
|
-
Use `npx securenow help firewall` for complete details on all layers.
|
|
1269
|
-
|
|
1270
|
-
#### Debugging
|
|
1271
|
-
|
|
1272
|
-
| Variable | Description | Default |
|
|
1273
|
-
|----------|-------------|---------|
|
|
1274
|
-
| `OTEL_LOG_LEVEL` | OpenTelemetry diagnostic override. Options: `debug`, `info`, `warn`, `error`, `none`. Overrides `config.otel.logLevel` for emergency debugging. | `error` |
|
|
1275
|
-
| `SECURENOW_TEST_SPAN` | Set to `1` to emit a test span on startup. | `0` |
|
|
1276
|
-
|
|
1277
|
-
#### Environment
|
|
1278
|
-
|
|
1279
|
-
| Variable | Description | Default |
|
|
1280
|
-
|----------|-------------|---------|
|
|
1281
|
-
| `SECURENOW_ENVIRONMENT` / `SECURENOW_DEPLOYMENT_ENVIRONMENT` / `NODE_ENV` | Fallback for `config.runtime.deploymentEnvironment`. | `production` |
|
|
1217
|
+
For instrumentation, firewall layers, debugging, trusted proxies, and deployment
|
|
1218
|
+
environment, edit the matching `config.*` keys in `.securenow/credentials.json`.
|
|
1219
|
+
Use `npx securenow env --json` to inspect the resolved values and
|
|
1220
|
+
`npx securenow help firewall` for the firewall command reference.
|
|
1282
1221
|
|
|
1283
1222
|
---
|
|
1284
1223
|
|
|
@@ -1290,7 +1229,7 @@ SecureNow provides multiple entry points depending on your needs:
|
|
|
1290
1229
|
|-------------|-------|-------------------|-------------------|-------|
|
|
1291
1230
|
| `securenow/register` | `node -r securenow/register app.js` | Yes | Yes | Default -- full tracing + firewall |
|
|
1292
1231
|
| `securenow/firewall-only` | `node -r securenow/firewall-only app.js` | No | Yes | Firewall only, no OTel overhead |
|
|
1293
|
-
| `securenow/nextjs` | `
|
|
1232
|
+
| `securenow/nextjs` | `await import(/* webpackIgnore: true */ 'securenow/nextjs')` | Yes | Yes | Next.js instrumentation hook |
|
|
1294
1233
|
| `securenow/nuxt` | `modules: ['securenow/nuxt']` | Yes | Yes | Nuxt 3 module |
|
|
1295
1234
|
| `securenow/nextjs-webpack-config` | `withSecureNow(config)` | - | - | Next.js config wrapper |
|
|
1296
1235
|
| `securenow/firewall` | `require('securenow/firewall').init({...})` | No | Yes | Programmatic firewall API |
|
|
@@ -1303,13 +1242,15 @@ SecureNow provides multiple entry points depending on your needs:
|
|
|
1303
1242
|
|
|
1304
1243
|
### Automatic Console Logging
|
|
1305
1244
|
|
|
1306
|
-
|
|
1245
|
+
Console log forwarding is enabled by default through
|
|
1246
|
+
`config.logging.enabled: true`; all console calls are automatically forwarded as
|
|
1247
|
+
OTLP log records:
|
|
1307
1248
|
|
|
1308
1249
|
```javascript
|
|
1309
1250
|
// At the top of your main file
|
|
1310
1251
|
require('securenow/register');
|
|
1311
1252
|
|
|
1312
|
-
// With
|
|
1253
|
+
// With config.logging.enabled=true, all console logs are automatically sent
|
|
1313
1254
|
console.log('Application started');
|
|
1314
1255
|
console.info('User action', { userId: 123, action: 'login' });
|
|
1315
1256
|
console.warn('Deprecation warning');
|
|
@@ -1324,10 +1265,7 @@ console.debug('Debug info');
|
|
|
1324
1265
|
- `console.error()` -> ERROR
|
|
1325
1266
|
- `console.debug()` -> DEBUG
|
|
1326
1267
|
|
|
1327
|
-
|
|
1328
|
-
```bash
|
|
1329
|
-
SECURENOW_LOGGING_ENABLED=1
|
|
1330
|
-
```
|
|
1268
|
+
Logging is controlled by `config.logging.enabled` in `.securenow/credentials.json`.
|
|
1331
1269
|
|
|
1332
1270
|
### Direct Logger API
|
|
1333
1271
|
|
|
@@ -1370,13 +1308,20 @@ node -r securenow/register app.js
|
|
|
1370
1308
|
|
|
1371
1309
|
## Request Body Capture
|
|
1372
1310
|
|
|
1373
|
-
SecureNow captures HTTP request bodies in traces by default, with sensitive fields automatically redacted. Set `
|
|
1311
|
+
SecureNow captures HTTP request bodies in traces by default, with sensitive fields automatically redacted. Set `config.capture.body=false` in `.securenow/credentials.json` only when you need a local opt-out.
|
|
1374
1312
|
|
|
1375
1313
|
### Body Capture Defaults
|
|
1376
1314
|
|
|
1377
|
-
```
|
|
1378
|
-
|
|
1379
|
-
|
|
1315
|
+
```json
|
|
1316
|
+
{
|
|
1317
|
+
"config": {
|
|
1318
|
+
"capture": {
|
|
1319
|
+
"body": true,
|
|
1320
|
+
"maxBodySize": 10240,
|
|
1321
|
+
"multipart": true
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1380
1325
|
```
|
|
1381
1326
|
|
|
1382
1327
|
### Supported Content Types
|
|
@@ -1384,11 +1329,11 @@ export SECURENOW_MAX_BODY_SIZE=10240 # 10KB (optional)
|
|
|
1384
1329
|
- `application/json`
|
|
1385
1330
|
- `application/x-www-form-urlencoded`
|
|
1386
1331
|
- `application/graphql`
|
|
1387
|
-
- `multipart/form-data` (metadata capture is on unless `
|
|
1332
|
+
- `multipart/form-data` (metadata capture is on unless `config.capture.multipart=false`)
|
|
1388
1333
|
|
|
1389
1334
|
### Multipart Body Capture (v5.8.0+)
|
|
1390
1335
|
|
|
1391
|
-
Multipart/form-data metadata capture is enabled by default. Set `
|
|
1336
|
+
Multipart/form-data metadata capture is enabled by default. Set `config.capture.multipart=false` to disable it. Uses a streaming parser that never buffers file content -- memory stays at ~few KB regardless of upload size.
|
|
1392
1337
|
|
|
1393
1338
|
**What gets captured:**
|
|
1394
1339
|
- **Text fields** -- field name and value (up to 1000 chars), with sensitive fields auto-redacted
|
|
@@ -1415,8 +1360,14 @@ All request bodies are automatically scanned and sensitive fields are redacted:
|
|
|
1415
1360
|
|
|
1416
1361
|
**Add custom fields to redact:**
|
|
1417
1362
|
|
|
1418
|
-
```
|
|
1419
|
-
|
|
1363
|
+
```json
|
|
1364
|
+
{
|
|
1365
|
+
"config": {
|
|
1366
|
+
"capture": {
|
|
1367
|
+
"sensitiveFields": ["custom_secret", "internal_token"]
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1420
1371
|
```
|
|
1421
1372
|
|
|
1422
1373
|
### Example
|
|
@@ -1556,10 +1507,12 @@ const enabled: boolean = isLoggingEnabled();
|
|
|
1556
1507
|
```typescript
|
|
1557
1508
|
// instrumentation.ts
|
|
1558
1509
|
export async function register() {
|
|
1559
|
-
if (process.env.NEXT_RUNTIME
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1510
|
+
if (process.env.NEXT_RUNTIME !== 'nodejs') return;
|
|
1511
|
+
|
|
1512
|
+
const securenowNext = await import(/* webpackIgnore: true */ 'securenow/nextjs');
|
|
1513
|
+
const registerSecureNow = securenowNext.registerSecureNow || securenowNext.default?.registerSecureNow;
|
|
1514
|
+
registerSecureNow({ captureBody: true });
|
|
1515
|
+
await import(/* webpackIgnore: true */ 'securenow/nextjs-auto-capture');
|
|
1563
1516
|
}
|
|
1564
1517
|
```
|
|
1565
1518
|
|
|
@@ -1572,6 +1525,8 @@ module.exports = withSecureNow({
|
|
|
1572
1525
|
});
|
|
1573
1526
|
```
|
|
1574
1527
|
|
|
1528
|
+
`withSecureNow()` adds the server external package config and `outputFileTracingIncludes` for standalone/self-hosted builds.
|
|
1529
|
+
|
|
1575
1530
|
### NestJS with TypeScript
|
|
1576
1531
|
|
|
1577
1532
|
Use `-r securenow/register -r ts-node/register` flags instead of in-code require:
|
|
@@ -1796,6 +1751,9 @@ Bodies larger than `config.capture.maxBodySize` are truncated:
|
|
|
1796
1751
|
```javascript
|
|
1797
1752
|
module.exports = {
|
|
1798
1753
|
serverExternalPackages: ['securenow'],
|
|
1754
|
+
outputFileTracingIncludes: {
|
|
1755
|
+
'/*': ['./node_modules/securenow/**/*'],
|
|
1756
|
+
},
|
|
1799
1757
|
};
|
|
1800
1758
|
```
|
|
1801
1759
|
|
|
@@ -1807,7 +1765,7 @@ For Next.js < 15, add `securenow` to `experimental.serverComponentsExternalPacka
|
|
|
1807
1765
|
|
|
1808
1766
|
**Check 3: Check for OTel MODULE_NOT_FOUND errors**
|
|
1809
1767
|
|
|
1810
|
-
If you see `MODULE_NOT_FOUND` for `@opentelemetry
|
|
1768
|
+
If you see `MODULE_NOT_FOUND` for `@opentelemetry/*`, `./firewall`, or other SecureNow runtime modules, your `next.config.js` is missing the externalization or standalone output include. Run `npx securenow init`; if your config is custom, use the prompt it prints to merge the edit safely.
|
|
1811
1769
|
|
|
1812
1770
|
**Check 4: Restart dev server**
|
|
1813
1771
|
|
|
@@ -1860,10 +1818,10 @@ Do not hardcode configuration in code or deployment dashboards. Use `.securenow/
|
|
|
1860
1818
|
|
|
1861
1819
|
```javascript
|
|
1862
1820
|
// Bad
|
|
1863
|
-
|
|
1821
|
+
const appKey = 'hardcoded-value';
|
|
1864
1822
|
|
|
1865
1823
|
// Good: use .securenow/credentials.json
|
|
1866
|
-
// { "app": { "key": "my-app", "instance": "https://
|
|
1824
|
+
// { "app": { "key": "my-app", "instance": "https://ingest.securenow.ai" } }
|
|
1867
1825
|
```
|
|
1868
1826
|
|
|
1869
1827
|
### 2. Use Structured Logging
|
package/README.md
CHANGED
|
@@ -45,7 +45,7 @@ That's it. No `.env` edits, no API keys to paste, no peer-dep warnings. Your tra
|
|
|
45
45
|
"app": {
|
|
46
46
|
"key": "<uuid>",
|
|
47
47
|
"name": "my-backend",
|
|
48
|
-
"instance": "https://
|
|
48
|
+
"instance": "https://ingest.securenow.ai"
|
|
49
49
|
},
|
|
50
50
|
"config": {
|
|
51
51
|
"runtime": { "deploymentEnvironment": "local" },
|
|
@@ -60,6 +60,24 @@ The SDK reads this file at boot and sends traces/logs directly to the right app
|
|
|
60
60
|
|
|
61
61
|
---
|
|
62
62
|
|
|
63
|
+
## Monorepo / AI-agent setup
|
|
64
|
+
|
|
65
|
+
If you have many apps under one repo, authenticate once from the repo root:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
npx securenow login
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Then ask your coding agent to wire each app with this prompt:
|
|
72
|
+
|
|
73
|
+
```text
|
|
74
|
+
I already ran npx securenow login from the repo root. For every Node.js or Next.js app under this repo: install securenow@latest, run or merge npx securenow init, create or reuse a SecureNow app, write local .securenow/credentials.json plus tokenless .securenow/credentials.production.json, gitignore .securenow, enable traces, logs, body capture, multipart metadata, and firewall, then verify with npx securenow env --json, npx securenow test-span, npx securenow log send, and a local HTTP smoke test where possible. Do not print secrets.
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
For production, deploy the tokenless runtime credentials as a secret file mounted at `<app-root>/.securenow/credentials.json`.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
63
81
|
## Framework integration
|
|
64
82
|
|
|
65
83
|
### Node.js / Express / Fastify / NestJS / Koa / Hapi
|
|
@@ -82,27 +100,28 @@ NODE_OPTIONS="-r securenow/register" npm start
|
|
|
82
100
|
npx securenow init
|
|
83
101
|
```
|
|
84
102
|
|
|
85
|
-
Creates `instrumentation.ts` and
|
|
103
|
+
Creates `instrumentation.ts` and patches `next.config.*` when it can do so safely:
|
|
86
104
|
|
|
87
105
|
```typescript
|
|
88
106
|
// instrumentation.ts
|
|
89
|
-
import { createRequire } from 'node:module';
|
|
90
|
-
|
|
91
|
-
const require = createRequire(import.meta.url);
|
|
92
|
-
|
|
93
107
|
export async function register() {
|
|
94
108
|
if (process.env.NEXT_RUNTIME !== 'nodejs') return;
|
|
95
|
-
|
|
109
|
+
|
|
110
|
+
const securenowNext = await import(/* webpackIgnore: true */ 'securenow/nextjs');
|
|
111
|
+
const registerSecureNow = securenowNext.registerSecureNow || securenowNext.default?.registerSecureNow;
|
|
96
112
|
registerSecureNow({ captureBody: true });
|
|
97
|
-
|
|
113
|
+
await import(/* webpackIgnore: true */ 'securenow/nextjs-auto-capture');
|
|
98
114
|
}
|
|
99
115
|
```
|
|
100
116
|
|
|
101
|
-
For Next.js 15+, `init` adds `securenow` to `serverExternalPackages` when it can safely edit the file:
|
|
117
|
+
For Next.js 15+, `init` adds `securenow` to `serverExternalPackages` and includes the SDK in standalone output when it can safely edit the file:
|
|
102
118
|
|
|
103
119
|
```javascript
|
|
104
120
|
const nextConfig = {
|
|
105
121
|
serverExternalPackages: ['securenow'],
|
|
122
|
+
outputFileTracingIncludes: {
|
|
123
|
+
'/*': ['./node_modules/securenow/**/*'],
|
|
124
|
+
},
|
|
106
125
|
};
|
|
107
126
|
|
|
108
127
|
export default nextConfig;
|
|
@@ -162,7 +181,7 @@ Resolution order:
|
|
|
162
181
|
4. Global named runtime credentials in the same fixed order
|
|
163
182
|
5. `package.json#name` (label only)
|
|
164
183
|
|
|
165
|
-
Legacy environment
|
|
184
|
+
SDK runtime config is credentials-json based. Legacy environment fallbacks are disabled by default and only work when `SECURENOW_ENABLE_LEGACY_ENV=1` is explicitly set for an old deployment.
|
|
166
185
|
|
|
167
186
|
---
|
|
168
187
|
|
|
@@ -225,8 +244,8 @@ Use `.securenow/credentials.json` fields for new local, CI, Docker, and producti
|
|
|
225
244
|
|
|
226
245
|
| Field | Default | Purpose |
|
|
227
246
|
|---|---|---|
|
|
228
|
-
| `app.key` | selected during login | App routing UUID
|
|
229
|
-
| `app.
|
|
247
|
+
| `app.key` | selected during login | App routing UUID; the gateway routes telemetry by this key |
|
|
248
|
+
| `app.name` | selected during login | Human-readable label for CLI and dashboard output |
|
|
230
249
|
| `apiKey` | minted during login | Scoped firewall key (`snk_live_...`) |
|
|
231
250
|
| `config.runtime.deploymentEnvironment` | `local` from `init`, `production` from runtime credentials | Sent as OTel `deployment.environment` |
|
|
232
251
|
| `config.logging.enabled` | `true` | Forward `console.*` as OTLP logs |
|
|
@@ -237,25 +256,20 @@ Use `.securenow/credentials.json` fields for new local, CI, Docker, and producti
|
|
|
237
256
|
| `config.firewall.enabled` | `true` | Local SDK firewall switch; dashboard firewall toggle is scoped per environment |
|
|
238
257
|
| `config.otel.*` | empty | Optional custom OTLP endpoints, headers, and log level |
|
|
239
258
|
|
|
240
|
-
|
|
259
|
+
The credentials file is versioned with `_securenow.schemaVersion`, so future SDK
|
|
260
|
+
versions can migrate defaults without asking customers to manage env vars. For
|
|
261
|
+
production, generate a tokenless runtime file:
|
|
241
262
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
| `SECURENOW_DISABLE_INSTRUMENTATIONS` | - | Comma-separated OTel instrumentations to disable. |
|
|
253
|
-
| `SECURENOW_NO_UUID` | `0` | Don't append a UUID to `service.instance.id`. |
|
|
254
|
-
| `SECURENOW_STRICT` | `0` | Exit with code 1 if `SECURENOW_APPID` is missing in a PM2 cluster. |
|
|
255
|
-
| `OTEL_EXPORTER_OTLP_HEADERS` | - | Raw OTLP headers (e.g. `x-api-key=...`). |
|
|
256
|
-
| `OTEL_LOG_LEVEL` | - | `debug`/`info`/`warn`/`error`. |
|
|
257
|
-
|
|
258
|
-
New installs should use `.securenow/credentials.json`; environment variables remain legacy fallbacks for existing deployments.
|
|
263
|
+
```bash
|
|
264
|
+
npx securenow credentials runtime --env production
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
Mount or copy that JSON as `.securenow/credentials.json` in the deployed app.
|
|
268
|
+
New runtime credentials do not include a per-instance collector URL; the SDK
|
|
269
|
+
uses `https://ingest.securenow.ai` by default and the gateway routes by
|
|
270
|
+
`app.key`.
|
|
271
|
+
Legacy env fallback exists only for old deployments that explicitly opt in with
|
|
272
|
+
`SECURENOW_ENABLE_LEGACY_ENV=1`; new installs should not use it.
|
|
259
273
|
|
|
260
274
|
---
|
|
261
275
|
|
|
@@ -270,7 +284,7 @@ PostgreSQL, MySQL / MySQL2, MongoDB, Redis
|
|
|
270
284
|
### Other
|
|
271
285
|
HTTP/HTTPS, GraphQL, gRPC, and many more via [@opentelemetry/auto-instrumentations-node](https://www.npmjs.com/package/@opentelemetry/auto-instrumentations-node).
|
|
272
286
|
|
|
273
|
-
> MongoDB instrumentation is
|
|
287
|
+
> MongoDB instrumentation is included in the current SDK. To disable it for a service, add `@opentelemetry/instrumentation-mongodb` to `config.otel.disableInstrumentations` in `.securenow/credentials.json`.
|
|
274
288
|
|
|
275
289
|
---
|
|
276
290
|
|
|
@@ -412,7 +426,7 @@ After install, the `securenow` CLI is available via `npx securenow` or globally
|
|
|
412
426
|
| `~/.securenow/credentials.<environment>.json` | Global environment-specific runtime credentials |
|
|
413
427
|
| `~/.securenow/config.json` | API URL, default app, preferences |
|
|
414
428
|
|
|
415
|
-
Resolution order: project `.securenow/credentials.json` -> project named runtime credentials in the fixed staging/production/preview/local/test/development/dev/prod order -> global `~/.securenow/credentials.json` -> global named runtime credentials in the same fixed order -> package name fallback. Legacy env
|
|
429
|
+
Resolution order: project `.securenow/credentials.json` -> project named runtime credentials in the fixed staging/production/preview/local/test/development/dev/prod order -> global `~/.securenow/credentials.json` -> global named runtime credentials in the same fixed order -> package name fallback. Legacy env fallbacks are disabled unless `SECURENOW_ENABLE_LEGACY_ENV=1` is set, and they never choose the credentials filename.
|
|
416
430
|
|
|
417
431
|
Override the dashboard API with `securenow config set apiUrl <url>`.
|
|
418
432
|
|