securenow 7.0.0-anas.2 → 7.1.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/CONSUMING-APPS-GUIDE.md +17 -11
- package/README.md +204 -282
- package/SKILL-CLI.md +8 -4
- package/app-config.js +21 -1
- package/cli/apiKey.js +55 -0
- package/cli/apps.js +8 -19
- package/cli/auth.js +13 -2
- package/cli/config.js +24 -0
- package/cli/diagnostics.js +3 -1
- package/cli.js +24 -0
- package/docs/ALL-FRAMEWORKS-QUICKSTART.md +8 -0
- package/docs/ENVIRONMENT-VARIABLES.md +24 -15
- package/docs/LOGGING-QUICKSTART.md +25 -38
- package/docs/NEXTJS-QUICKSTART.md +46 -36
- package/docs/NUXT-GUIDE.md +17 -13
- package/examples/nextjs-env-example.txt +32 -34
- package/firewall-only.js +3 -2
- package/nextjs.js +4 -2
- package/package.json +1 -1
- package/postinstall.js +3 -11
package/SKILL-CLI.md
CHANGED
|
@@ -18,13 +18,17 @@ npx securenow <command>
|
|
|
18
18
|
### Authenticate
|
|
19
19
|
|
|
20
20
|
```bash
|
|
21
|
-
securenow login # opens browser OAuth
|
|
21
|
+
securenow login # opens browser OAuth + app picker; writes ./.securenow/credentials.json (project-local by default)
|
|
22
|
+
securenow login --global # save to ~/.securenow/ instead (shared across projects)
|
|
22
23
|
securenow login --token <JWT> # headless / CI login (get token from dashboard Settings)
|
|
23
|
-
securenow
|
|
24
|
-
securenow whoami # verify session (shows auth source)
|
|
24
|
+
securenow whoami # verify session (shows email, app, auth source)
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
**
|
|
27
|
+
**Zero-config flow (v7+):** the browser step lets the user pick (or create) an app. The CLI stores the app's **key (UUID)**, **name**, and **instance URL** in `.securenow/credentials.json`. The SDK reads this file at boot and sends traces/logs to the right app bucket — **no env vars required for local dev**.
|
|
28
|
+
|
|
29
|
+
Credentials resolve in order: `SECURENOW_TOKEN` env var → project `.securenow/credentials.json` → global `~/.securenow/credentials.json`.
|
|
30
|
+
|
|
31
|
+
For CI / Docker / production, set env vars directly (always win over the file): `SECURENOW_APPID=<uuid>`, `SECURENOW_INSTANCE=<url>`, `SECURENOW_API_KEY=<uuid>`.
|
|
28
32
|
|
|
29
33
|
### Integrate With Your App
|
|
30
34
|
|
package/app-config.js
CHANGED
|
@@ -14,6 +14,10 @@
|
|
|
14
14
|
* - What human label to show? (resolveAppName) — display only, never
|
|
15
15
|
* sent to the collector.
|
|
16
16
|
* - Which OTLP collector to hit? (resolveInstance)
|
|
17
|
+
* - Which firewall API key? (resolveApiKey) — the snk_live_... key the
|
|
18
|
+
* firewall sends as Bearer to /api/firewall
|
|
19
|
+
* for blocklist sync. Separate from the
|
|
20
|
+
* app routing key: this one is user-scoped.
|
|
17
21
|
*
|
|
18
22
|
* Resolution order (first non-empty wins):
|
|
19
23
|
*
|
|
@@ -25,7 +29,7 @@
|
|
|
25
29
|
* 5. Hard default / null
|
|
26
30
|
*
|
|
27
31
|
* Credentials file schema:
|
|
28
|
-
* { token, email, expiresAt, app: { key
|
|
32
|
+
* { token, email, expiresAt, apiKey: <snk_live_...>, app: { key, name, instance } }
|
|
29
33
|
*/
|
|
30
34
|
|
|
31
35
|
const fs = require('fs');
|
|
@@ -112,6 +116,21 @@ function resolveAppId() {
|
|
|
112
116
|
return resolveAppKey() || resolveAppName();
|
|
113
117
|
}
|
|
114
118
|
|
|
119
|
+
// Firewall / user-scoped API key (snk_live_...).
|
|
120
|
+
// Distinct from resolveAppKey: that one is the application UUID for OTel
|
|
121
|
+
// routing. This one authenticates the firewall blocklist sync to /api/firewall,
|
|
122
|
+
// so it must look like a real `snk_live_` key — the app UUID won't pass auth.
|
|
123
|
+
function resolveApiKey() {
|
|
124
|
+
const fromEnv = pick(process.env.SECURENOW_API_KEY);
|
|
125
|
+
if (fromEnv && fromEnv.startsWith('snk_live_')) return fromEnv;
|
|
126
|
+
|
|
127
|
+
const creds = loadCredentials();
|
|
128
|
+
const fromCreds = creds && pick(creds.apiKey);
|
|
129
|
+
if (fromCreds && fromCreds.startsWith('snk_live_')) return fromCreds;
|
|
130
|
+
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
|
|
115
134
|
function resolveInstance() {
|
|
116
135
|
const fromEnv =
|
|
117
136
|
pick(process.env.SECURENOW_INSTANCE) ||
|
|
@@ -143,6 +162,7 @@ module.exports = {
|
|
|
143
162
|
resolveAppKey,
|
|
144
163
|
resolveAppName,
|
|
145
164
|
resolveAppId,
|
|
165
|
+
resolveApiKey,
|
|
146
166
|
resolveInstance,
|
|
147
167
|
resolveAll,
|
|
148
168
|
loadCredentials,
|
package/cli/apiKey.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const config = require('./config');
|
|
4
|
+
const ui = require('./ui');
|
|
5
|
+
|
|
6
|
+
function maskKey(key) {
|
|
7
|
+
if (!key || key.length < 16) return key || '';
|
|
8
|
+
return `${key.slice(0, 12)}••••••${key.slice(-4)}`;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async function set(args, flags) {
|
|
12
|
+
const key = args[0];
|
|
13
|
+
if (!key) {
|
|
14
|
+
ui.error('API key is required. Usage: securenow api-key set <snk_live_...>');
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
if (!key.startsWith('snk_live_')) {
|
|
18
|
+
ui.error('API key must start with "snk_live_"');
|
|
19
|
+
ui.info('Create one in the dashboard: Settings → API Keys');
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const local = flags.global ? false : true;
|
|
24
|
+
config.setApiKey(key, { local });
|
|
25
|
+
if (local) config.ensureLocalGitignore();
|
|
26
|
+
|
|
27
|
+
ui.success(`API key saved (${maskKey(key)})`);
|
|
28
|
+
ui.info(local
|
|
29
|
+
? 'Stored in project .securenow/credentials.json (local)'
|
|
30
|
+
: 'Stored in ~/.securenow/credentials.json (global)');
|
|
31
|
+
ui.info('The firewall will now pick it up automatically — no SECURENOW_API_KEY env var needed.');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function clear(args, flags) {
|
|
35
|
+
const local = flags.global ? false : true;
|
|
36
|
+
const existing = config.getApiKey();
|
|
37
|
+
config.clearApiKey({ local });
|
|
38
|
+
if (existing) {
|
|
39
|
+
ui.success(`Cleared API key (${maskKey(existing)})`);
|
|
40
|
+
} else {
|
|
41
|
+
ui.info('No API key was stored');
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function show() {
|
|
46
|
+
const key = config.getApiKey();
|
|
47
|
+
if (!key) {
|
|
48
|
+
ui.info('No API key stored in credentials. Falling back to SECURENOW_API_KEY env var.');
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
console.log(maskKey(key));
|
|
52
|
+
ui.info(`Source: ${config.getAuthSource()}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
module.exports = { set, clear, show };
|
package/cli/apps.js
CHANGED
|
@@ -64,17 +64,10 @@ async function list(args, flags) {
|
|
|
64
64
|
ui.table(['Name', 'Key', 'Instance', 'Created'], rows);
|
|
65
65
|
console.log('');
|
|
66
66
|
|
|
67
|
-
if (apps.length > 0) {
|
|
68
|
-
console.log(` ${ui.c.bold('Add to your .env:')}`);
|
|
69
|
-
const first = apps.find(a => a.key === defaultApp) || apps[0];
|
|
70
|
-
const firstInst = first.instanceId ? instMap[first.instanceId] : null;
|
|
71
|
-
console.log(` SECURENOW_APPID=${first.key}`);
|
|
72
|
-
console.log(` SECURENOW_INSTANCE=${instanceUrl(firstInst)}`);
|
|
73
|
-
console.log('');
|
|
74
|
-
}
|
|
75
|
-
|
|
76
67
|
if (!defaultApp && apps.length > 0) {
|
|
77
|
-
|
|
68
|
+
console.log(` ${ui.c.bold('Use one of these apps in the current project:')}`);
|
|
69
|
+
console.log(` ${ui.c.bold('securenow apps default <key>')}`);
|
|
70
|
+
console.log(` ${ui.c.dim('(or run `securenow login` to pick interactively)')}`);
|
|
78
71
|
console.log('');
|
|
79
72
|
}
|
|
80
73
|
} catch (err) {
|
|
@@ -133,12 +126,9 @@ async function create(args, flags) {
|
|
|
133
126
|
]);
|
|
134
127
|
|
|
135
128
|
console.log('');
|
|
136
|
-
console.log(` ${ui.c.bold('
|
|
137
|
-
console.log(
|
|
138
|
-
console.log(`
|
|
139
|
-
console.log(` SECURENOW_INSTANCE=${envUrl}`);
|
|
140
|
-
console.log('');
|
|
141
|
-
ui.info(`Set as default: ${ui.c.bold(`securenow config set defaultApp ${app.key}`)}`);
|
|
129
|
+
console.log(` ${ui.c.bold('Use this app in the current project:')}`);
|
|
130
|
+
console.log(` ${ui.c.bold(`securenow apps default ${app.key}`)}`);
|
|
131
|
+
console.log(` ${ui.c.dim('(writes .securenow/credentials.json — no env var needed)')}`);
|
|
142
132
|
console.log('');
|
|
143
133
|
|
|
144
134
|
if (flags.json) {
|
|
@@ -251,9 +241,8 @@ async function info(args, flags) {
|
|
|
251
241
|
]);
|
|
252
242
|
|
|
253
243
|
console.log('');
|
|
254
|
-
console.log(` ${ui.c.bold('
|
|
255
|
-
console.log(`
|
|
256
|
-
console.log(` SECURENOW_INSTANCE=${envUrl}`);
|
|
244
|
+
console.log(` ${ui.c.bold('Use in current project:')} ${ui.c.bold(`securenow apps default ${app.key}`)}`);
|
|
245
|
+
console.log(` ${ui.c.bold('Or override via env:')} ${ui.c.dim(`SECURENOW_APPID=${app.key} SECURENOW_INSTANCE=${envUrl}`)}`);
|
|
257
246
|
console.log('');
|
|
258
247
|
} catch (err) {
|
|
259
248
|
s.fail('Failed to fetch application');
|
package/cli/auth.js
CHANGED
|
@@ -45,6 +45,7 @@ async function loginWithBrowser() {
|
|
|
45
45
|
return new Promise((resolve, reject) => {
|
|
46
46
|
let pendingToken = null;
|
|
47
47
|
let pendingApp = null;
|
|
48
|
+
let pendingApiKey = null;
|
|
48
49
|
|
|
49
50
|
const server = http.createServer((req, res) => {
|
|
50
51
|
const url = new URL(req.url, `http://127.0.0.1`);
|
|
@@ -56,6 +57,7 @@ async function loginWithBrowser() {
|
|
|
56
57
|
const appKey = url.searchParams.get('app_key');
|
|
57
58
|
const appName = url.searchParams.get('app_name');
|
|
58
59
|
const appInstance = url.searchParams.get('app_instance');
|
|
60
|
+
const apiKey = url.searchParams.get('api_key');
|
|
59
61
|
|
|
60
62
|
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
61
63
|
|
|
@@ -82,6 +84,9 @@ async function loginWithBrowser() {
|
|
|
82
84
|
instance: appInstance || null,
|
|
83
85
|
};
|
|
84
86
|
}
|
|
87
|
+
if (apiKey && apiKey.startsWith('snk_live_')) {
|
|
88
|
+
pendingApiKey = apiKey;
|
|
89
|
+
}
|
|
85
90
|
const payload = decodeJwtPayload(token);
|
|
86
91
|
const email = payload?.email || 'unknown account';
|
|
87
92
|
const safeEmail = email.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
@@ -139,10 +144,12 @@ async function loginWithBrowser() {
|
|
|
139
144
|
res.end('{"ok":true}');
|
|
140
145
|
const token = pendingToken;
|
|
141
146
|
const app = pendingApp;
|
|
147
|
+
const apiKeyFromLogin = pendingApiKey;
|
|
142
148
|
pendingToken = null;
|
|
143
149
|
pendingApp = null;
|
|
150
|
+
pendingApiKey = null;
|
|
144
151
|
server.close();
|
|
145
|
-
resolve({ token, app });
|
|
152
|
+
resolve({ token, app, apiKey: apiKeyFromLogin });
|
|
146
153
|
return;
|
|
147
154
|
}
|
|
148
155
|
|
|
@@ -219,12 +226,13 @@ async function login(args, flags) {
|
|
|
219
226
|
}
|
|
220
227
|
|
|
221
228
|
try {
|
|
222
|
-
const { token, app } = await loginWithBrowser();
|
|
229
|
+
const { token, app, apiKey } = await loginWithBrowser();
|
|
223
230
|
const payload = decodeJwtPayload(token);
|
|
224
231
|
const email = payload?.email || 'unknown';
|
|
225
232
|
const exp = payload?.exp ? payload.exp * 1000 : null;
|
|
226
233
|
|
|
227
234
|
config.setAuth(token, email, exp, { local, app });
|
|
235
|
+
if (apiKey) config.setApiKey(apiKey, { local });
|
|
228
236
|
if (local) config.ensureLocalGitignore();
|
|
229
237
|
console.log('');
|
|
230
238
|
ui.success(`Logged in as ${ui.c.bold(email)}`);
|
|
@@ -232,6 +240,9 @@ async function login(args, flags) {
|
|
|
232
240
|
if (app && (app.name || app.key)) {
|
|
233
241
|
ui.info(`Linked to app ${ui.c.bold(app.name || app.key)}${app.key ? ` (${ui.c.dim(app.key)})` : ''}`);
|
|
234
242
|
}
|
|
243
|
+
if (apiKey) {
|
|
244
|
+
ui.info(`Firewall API key saved — the firewall will activate automatically on next start`);
|
|
245
|
+
}
|
|
235
246
|
if (exp) {
|
|
236
247
|
const days = Math.ceil((exp - Date.now()) / (1000 * 60 * 60 * 24));
|
|
237
248
|
ui.info(`Session expires in ${days} days`);
|
package/cli/config.js
CHANGED
|
@@ -135,6 +135,27 @@ function getApp() {
|
|
|
135
135
|
return creds && creds.app ? creds.app : null;
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
+
function setApiKey(apiKey, { local } = {}) {
|
|
139
|
+
const useLocal = local === true || (local == null && hasLocalCredentials());
|
|
140
|
+
const targetFile = useLocal ? LOCAL_CREDENTIALS_FILE : CREDENTIALS_FILE;
|
|
141
|
+
const existing = loadJSON(targetFile);
|
|
142
|
+
saveJSON(targetFile, { ...existing, apiKey });
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function clearApiKey({ local } = {}) {
|
|
146
|
+
const useLocal = local === true || (local == null && hasLocalCredentials());
|
|
147
|
+
const targetFile = useLocal ? LOCAL_CREDENTIALS_FILE : CREDENTIALS_FILE;
|
|
148
|
+
const existing = loadJSON(targetFile);
|
|
149
|
+
if (!existing || !existing.apiKey) return;
|
|
150
|
+
delete existing.apiKey;
|
|
151
|
+
saveJSON(targetFile, existing);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function getApiKey() {
|
|
155
|
+
const creds = loadCredentials();
|
|
156
|
+
return creds && creds.apiKey ? creds.apiKey : null;
|
|
157
|
+
}
|
|
158
|
+
|
|
138
159
|
function setApp(app, { local } = {}) {
|
|
139
160
|
const useLocal = local === true || (local == null && hasLocalCredentials());
|
|
140
161
|
const targetFile = useLocal ? LOCAL_CREDENTIALS_FILE : CREDENTIALS_FILE;
|
|
@@ -193,6 +214,9 @@ module.exports = {
|
|
|
193
214
|
setAuth,
|
|
194
215
|
getApp,
|
|
195
216
|
setApp,
|
|
217
|
+
setApiKey,
|
|
218
|
+
clearApiKey,
|
|
219
|
+
getApiKey,
|
|
196
220
|
getAuthSource,
|
|
197
221
|
hasLocalCredentials,
|
|
198
222
|
ensureLocalGitignore,
|
package/cli/diagnostics.js
CHANGED
|
@@ -25,7 +25,9 @@ function resolvedConfig() {
|
|
|
25
25
|
? `${process.env.OTEL_EXPORTER_OTLP_ENDPOINT.replace(/\/$/, '')}/v1/logs`
|
|
26
26
|
: `${instance.replace(/\/$/, '')}/v1/logs`);
|
|
27
27
|
const headers = process.env.OTEL_EXPORTER_OTLP_HEADERS || '';
|
|
28
|
-
|
|
28
|
+
// Resolve firewall API key the same way the SDK does: env, then
|
|
29
|
+
// .securenow/credentials.json (project-local, then global).
|
|
30
|
+
const apiKey = require('../app-config').resolveApiKey() || '';
|
|
29
31
|
const apiUrl = config.getApiUrl();
|
|
30
32
|
const loggingEnabled = !/^(0|false)$/i.test(String(process.env.SECURENOW_LOGGING_ENABLED ?? ''));
|
|
31
33
|
const captureBody = !/^(0|false)$/i.test(String(process.env.SECURENOW_CAPTURE_BODY ?? ''));
|
package/cli.js
CHANGED
|
@@ -73,6 +73,30 @@ const COMMANDS = {
|
|
|
73
73
|
usage: 'securenow whoami',
|
|
74
74
|
run: () => require('./cli/auth').whoami(),
|
|
75
75
|
},
|
|
76
|
+
'api-key': {
|
|
77
|
+
desc: 'Manage the firewall API key stored in .securenow/credentials.json',
|
|
78
|
+
usage: 'securenow api-key <subcommand> [options]',
|
|
79
|
+
sub: {
|
|
80
|
+
set: {
|
|
81
|
+
desc: 'Save an API key (snk_live_...) to the credentials file',
|
|
82
|
+
usage: 'securenow api-key set <snk_live_...> [--global]',
|
|
83
|
+
flags: { global: 'Save to ~/.securenow/ instead of project-local' },
|
|
84
|
+
run: (a, f) => require('./cli/apiKey').set(a, f),
|
|
85
|
+
},
|
|
86
|
+
clear: {
|
|
87
|
+
desc: 'Remove the stored API key',
|
|
88
|
+
usage: 'securenow api-key clear [--global]',
|
|
89
|
+
flags: { global: 'Clear from ~/.securenow/ instead of project-local' },
|
|
90
|
+
run: (a, f) => require('./cli/apiKey').clear(a, f),
|
|
91
|
+
},
|
|
92
|
+
show: {
|
|
93
|
+
desc: 'Print the masked API key currently in use',
|
|
94
|
+
usage: 'securenow api-key show',
|
|
95
|
+
run: () => require('./cli/apiKey').show(),
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
defaultSub: 'show',
|
|
99
|
+
},
|
|
76
100
|
apps: {
|
|
77
101
|
desc: 'Manage applications',
|
|
78
102
|
usage: 'securenow apps <subcommand> [options]',
|
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
Protect any Node.js app in minutes. This guide covers **installation, CLI commands, the forensics chat, and IP blocking** for all 11 supported frameworks.
|
|
4
4
|
|
|
5
|
+
> **v7+ — zero-config shortcut.** For local dev, the short version is:
|
|
6
|
+
> ```bash
|
|
7
|
+
> npm install securenow
|
|
8
|
+
> npx securenow login # pick/create app in the browser
|
|
9
|
+
> node -r securenow/register app.js
|
|
10
|
+
> ```
|
|
11
|
+
> No `.env` setup. Credentials live in `.securenow/credentials.json` (gitignored automatically). Skip "Step 2 — Set Environment Variables" below unless you're configuring CI / Docker / prod.
|
|
12
|
+
|
|
5
13
|
---
|
|
6
14
|
|
|
7
15
|
## Table of Contents
|
|
@@ -2,32 +2,41 @@
|
|
|
2
2
|
|
|
3
3
|
Complete reference for all environment variables supported by SecureNow.
|
|
4
4
|
|
|
5
|
+
> **v7+: env vars are all optional.** For local dev, `npx securenow login` writes `.securenow/credentials.json` and the SDK reads it at boot — no env vars needed. Env vars are still supported (and always take precedence) for CI / Docker / production.
|
|
6
|
+
|
|
7
|
+
> **Resolution order** (first non-empty wins): env var → `./.securenow/credentials.json` → `~/.securenow/credentials.json` → `package.json#name` (label only) → default.
|
|
8
|
+
|
|
5
9
|
---
|
|
6
10
|
|
|
7
11
|
## Quick Reference Table
|
|
8
12
|
|
|
9
13
|
| Variable | Type | Default | Description |
|
|
10
14
|
|----------|------|---------|-------------|
|
|
11
|
-
| **SECURENOW_APPID** |
|
|
12
|
-
| **SECURENOW_INSTANCE** |
|
|
13
|
-
| **
|
|
14
|
-
| **
|
|
15
|
-
| **
|
|
16
|
-
| **
|
|
15
|
+
| **SECURENOW_APPID** | Optional | from credentials file | App routing key (UUID). Sent as OTel `service.name`. |
|
|
16
|
+
| **SECURENOW_INSTANCE** | Optional | `https://freetrial.securenow.ai:4318` | OTLP collector base URL |
|
|
17
|
+
| **SECURENOW_API_KEY** | Optional | from credentials file | API key (same UUID as APPID). Enables firewall. |
|
|
18
|
+
| **SECURENOW_LOGGING_ENABLED** | Optional | `1` (on) | Forward `console.*` as OTLP logs. Set to `0` to disable. |
|
|
19
|
+
| **SECURENOW_CAPTURE_BODY** | Optional | `1` (on) | Capture request body. Set to `0` for Fastify/Hapi/Hono. |
|
|
20
|
+
| **SECURENOW_CAPTURE_MULTIPART** | Optional | `1` (on) | Capture multipart field/file metadata. |
|
|
17
21
|
| **SECURENOW_MAX_BODY_SIZE** | Optional | `10240` | Max body size in bytes |
|
|
18
|
-
| **SECURENOW_SENSITIVE_FIELDS** | Optional | - | Comma-separated
|
|
19
|
-
| **
|
|
20
|
-
| **
|
|
21
|
-
| **
|
|
22
|
-
| **
|
|
22
|
+
| **SECURENOW_SENSITIVE_FIELDS** | Optional | - | Comma-separated extra fields to redact |
|
|
23
|
+
| **SECURENOW_NO_UUID** | Optional | `0` | Disable UUID suffix on `service.instance.id` |
|
|
24
|
+
| **SECURENOW_STRICT** | Optional | `0` | Exit if APPID missing in PM2 cluster mode |
|
|
25
|
+
| **SECURENOW_DISABLE_INSTRUMENTATIONS** | Optional | - | Comma-separated list of OTel instrumentations to disable |
|
|
26
|
+
| **SECURENOW_TEST_SPAN** | Optional | `0` | Emit a single test span on startup (prefer `npx securenow test-span`) |
|
|
27
|
+
| **SECURENOW_HIDE_BANNER** | Optional | `0` | Hide the free-trial banner |
|
|
28
|
+
| **SECURENOW_FIREWALL_ENABLED** | Optional | `1` (on when API key is set) | Firewall master switch |
|
|
29
|
+
| **SECURENOW_ENABLE_MONGODB_INSTRUMENTATION** | Optional | `0` | Opt in to MongoDB instrumentation (off by default since a cursor bug on mongodb@6.6+; safe since SDK v6.0.2) |
|
|
30
|
+
| **OTEL_SERVICE_NAME** | Optional | - | Alternative to SECURENOW_APPID (label only, no routing) |
|
|
23
31
|
| **OTEL_EXPORTER_OTLP_ENDPOINT** | Optional | - | Alternative to SECURENOW_INSTANCE |
|
|
24
|
-
| **OTEL_EXPORTER_OTLP_HEADERS** | Optional | - |
|
|
32
|
+
| **OTEL_EXPORTER_OTLP_HEADERS** | Optional | auto (`x-api-key` injected) | Additional OTLP headers |
|
|
25
33
|
| **OTEL_EXPORTER_OTLP_TRACES_ENDPOINT** | Optional | - | Override traces endpoint |
|
|
26
34
|
| **OTEL_EXPORTER_OTLP_LOGS_ENDPOINT** | Optional | - | Override logs endpoint |
|
|
27
|
-
| **OTEL_LOG_LEVEL** | Optional | `none` | SDK log
|
|
35
|
+
| **OTEL_LOG_LEVEL** | Optional | `none` | SDK log verbosity (`debug`/`info`/`warn`/`error`) |
|
|
28
36
|
| **NODE_ENV** | Optional | `production` | Environment name |
|
|
29
|
-
| **
|
|
30
|
-
| **
|
|
37
|
+
| **SECURENOW_API_URL** | Optional | `https://api.securenow.ai` | Dashboard API base URL |
|
|
38
|
+
| **SECURENOW_APP_URL** | Optional | `https://app.securenow.ai` | Dashboard web base URL |
|
|
39
|
+
| **SECURENOW_TOKEN** | Optional | from credentials file | Auth token (overrides credentials file for CI) |
|
|
31
40
|
| **SECURENOW_FIREWALL_ENABLED** | Optional | `1` | Firewall master kill-switch |
|
|
32
41
|
| **SECURENOW_FIREWALL_SYNC_INTERVAL** | Optional | `60` | Blocklist refresh interval (seconds) |
|
|
33
42
|
| **SECURENOW_FIREWALL_FAIL_MODE** | Optional | `open` | Behavior when API unreachable: open/closed |
|
|
@@ -1,36 +1,23 @@
|
|
|
1
|
-
# SecureNow Logging
|
|
1
|
+
# SecureNow Logging — Quick Start
|
|
2
2
|
|
|
3
|
-
Get logging
|
|
3
|
+
Get logging sent to your SecureNow dashboard in under 2 minutes.
|
|
4
4
|
|
|
5
|
-
**Since
|
|
5
|
+
**Since v7.0.0:** Logging is **on by default**. `console.log` / `warn` / `error` / `info` / `debug` calls are automatically forwarded as OTLP log records. Disable with `SECURENOW_LOGGING_ENABLED=0` if you don't want it.
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
## 1. Install
|
|
9
|
+
## 1. Install + login
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
12
|
npm install securenow
|
|
13
|
+
npx securenow login # pick/create your app in the browser
|
|
13
14
|
```
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
## 2. Configure Environment
|
|
18
|
-
|
|
19
|
-
Create `.env` file or export variables:
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
SECURENOW_LOGGING_ENABLED=1
|
|
23
|
-
SECURENOW_APPID=my-app
|
|
24
|
-
SECURENOW_INSTANCE=http://your-otlp-backend:4318
|
|
25
|
-
|
|
26
|
-
# For SecureNow / hosted OTLP (example):
|
|
27
|
-
# SECURENOW_INSTANCE=https://freetrial.securenow.ai:4318
|
|
28
|
-
# OTEL_EXPORTER_OTLP_HEADERS="x-api-key=<your-key>"
|
|
29
|
-
```
|
|
16
|
+
`login` writes `.securenow/credentials.json` locally. No `.env` setup required.
|
|
30
17
|
|
|
31
18
|
---
|
|
32
19
|
|
|
33
|
-
##
|
|
20
|
+
## 2. Add to Your App
|
|
34
21
|
|
|
35
22
|
**Option A: Automatic Console Logging (Easiest)**
|
|
36
23
|
|
|
@@ -53,7 +40,7 @@ NODE_OPTIONS="-r securenow/register" node app.js
|
|
|
53
40
|
|
|
54
41
|
---
|
|
55
42
|
|
|
56
|
-
##
|
|
43
|
+
## 3. Run Your App
|
|
57
44
|
|
|
58
45
|
```bash
|
|
59
46
|
node app.js
|
|
@@ -70,12 +57,18 @@ You should see:
|
|
|
70
57
|
|
|
71
58
|
---
|
|
72
59
|
|
|
73
|
-
##
|
|
60
|
+
## 4. View Logs in SecureNow
|
|
74
61
|
|
|
75
62
|
1. Open your SecureNow dashboard
|
|
76
63
|
2. Go to **Logs** section
|
|
77
|
-
3.
|
|
78
|
-
4.
|
|
64
|
+
3. Your logs appear under the app you picked during `securenow login`
|
|
65
|
+
4. All logs come with automatic trace correlation
|
|
66
|
+
|
|
67
|
+
Or from the CLI:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
npx securenow logs --minutes 5
|
|
71
|
+
```
|
|
79
72
|
|
|
80
73
|
---
|
|
81
74
|
|
|
@@ -102,20 +95,13 @@ app.listen(3000);
|
|
|
102
95
|
|
|
103
96
|
```typescript
|
|
104
97
|
// instrumentation.ts (in project root)
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
await import('securenow/register');
|
|
109
|
-
}
|
|
98
|
+
import { registerSecureNow } from 'securenow/nextjs';
|
|
99
|
+
export function register() {
|
|
100
|
+
registerSecureNow();
|
|
110
101
|
}
|
|
111
102
|
```
|
|
112
103
|
|
|
113
|
-
|
|
114
|
-
# .env.local
|
|
115
|
-
SECURENOW_LOGGING_ENABLED=1
|
|
116
|
-
SECURENOW_APPID=my-nextjs-app
|
|
117
|
-
SECURENOW_INSTANCE=http://localhost:4318
|
|
118
|
-
```
|
|
104
|
+
No `.env.local` needed — credentials come from `.securenow/credentials.json` after `npx securenow login`.
|
|
119
105
|
|
|
120
106
|
### Fastify
|
|
121
107
|
|
|
@@ -157,9 +143,10 @@ bootstrap();
|
|
|
157
143
|
|
|
158
144
|
**Logs not appearing?**
|
|
159
145
|
|
|
160
|
-
1. Check `
|
|
161
|
-
2.
|
|
162
|
-
3.
|
|
146
|
+
1. Check `.securenow/credentials.json` exists (run `npx securenow whoami`).
|
|
147
|
+
2. Confirm `SECURENOW_LOGGING_ENABLED` is not set to `0`.
|
|
148
|
+
3. Run `npx securenow doctor` — it probes the full pipeline and reports the failure mode.
|
|
149
|
+
4. Enable verbose output: `OTEL_LOG_LEVEL=debug`.
|
|
163
150
|
|
|
164
151
|
**Console logs not forwarding?**
|
|
165
152
|
|
|
@@ -1,67 +1,77 @@
|
|
|
1
|
-
# Next.js + SecureNow
|
|
1
|
+
# Next.js + SecureNow — 30 seconds
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## The whole setup
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
|
+
# 1. Install
|
|
6
7
|
npm install securenow
|
|
7
|
-
```
|
|
8
|
-
|
|
9
|
-
**🎉 The installer will automatically offer to create the instrumentation file!**
|
|
10
8
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
---
|
|
9
|
+
# 2. Pick (or create) your app in the browser — writes .securenow/ locally
|
|
10
|
+
npx securenow login
|
|
14
11
|
|
|
15
|
-
|
|
12
|
+
# 3. Scaffold instrumentation.ts and wrap next.config.js
|
|
13
|
+
npx securenow init
|
|
16
14
|
|
|
17
|
-
|
|
15
|
+
# 4. Run
|
|
16
|
+
npm run dev
|
|
17
|
+
```
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
No `.env.local` edits. No API key copy-paste. The app you picked in step 2 is where your traces land.
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
npx securenow init
|
|
23
|
-
```
|
|
21
|
+
---
|
|
24
22
|
|
|
25
|
-
|
|
23
|
+
## What `npx securenow init` generates
|
|
26
24
|
|
|
27
|
-
|
|
25
|
+
**`instrumentation.ts`** (or `.js`, auto-detected):
|
|
28
26
|
|
|
29
27
|
```typescript
|
|
30
28
|
import { registerSecureNow } from 'securenow/nextjs';
|
|
31
|
-
export function register() {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
### 2. Create `.env.local`:
|
|
35
|
-
|
|
36
|
-
```bash
|
|
37
|
-
SECURENOW_APPID=my-nextjs-app
|
|
38
|
-
SECURENOW_INSTANCE=http://your-securenow:4318
|
|
29
|
+
export function register() {
|
|
30
|
+
registerSecureNow();
|
|
31
|
+
}
|
|
39
32
|
```
|
|
40
33
|
|
|
41
|
-
|
|
34
|
+
It also tells you to wrap `next.config.js`:
|
|
42
35
|
|
|
43
36
|
```javascript
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
37
|
+
const { withSecureNow } = require('securenow/nextjs-webpack-config');
|
|
38
|
+
|
|
39
|
+
module.exports = withSecureNow({
|
|
40
|
+
// your existing config
|
|
41
|
+
});
|
|
47
42
|
```
|
|
48
43
|
|
|
49
|
-
|
|
44
|
+
`withSecureNow()` auto-detects Next.js 14 vs 15 vs 16 and sets the right externalization config.
|
|
50
45
|
|
|
51
|
-
|
|
52
|
-
npm run dev
|
|
53
|
-
```
|
|
46
|
+
---
|
|
54
47
|
|
|
55
48
|
## Verify
|
|
56
49
|
|
|
57
|
-
|
|
50
|
+
Start your app. In the console you should see:
|
|
51
|
+
|
|
58
52
|
```
|
|
59
|
-
[securenow]
|
|
53
|
+
[securenow] Next.js integration loading (pid=…)
|
|
54
|
+
[securenow] ✅ OpenTelemetry started for Next.js → https://freetrial.securenow.ai:4318/v1/traces
|
|
60
55
|
```
|
|
61
56
|
|
|
62
|
-
|
|
57
|
+
Then:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
npx securenow test-span # emit a test span
|
|
61
|
+
npx securenow traces # see it appear
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
If `traces` shows your span under the app name you picked, you're done.
|
|
63
65
|
|
|
64
66
|
---
|
|
65
67
|
|
|
66
|
-
|
|
68
|
+
## Overriding for CI / Docker / Vercel
|
|
69
|
+
|
|
70
|
+
`.securenow/credentials.json` is for local dev. For anywhere you can't run `npx securenow login`, set env vars — they always win:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
SECURENOW_APPID=<app-key-uuid> # from: npx securenow apps
|
|
74
|
+
SECURENOW_INSTANCE=https://freetrial.securenow.ai:4318
|
|
75
|
+
```
|
|
67
76
|
|
|
77
|
+
See [NEXTJS-GUIDE.md](./NEXTJS-GUIDE.md) for Vercel, standalone builds, and edge runtime details.
|