securenow 7.5.0 → 7.6.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 +2 -0
- package/NPM_README.md +201 -237
- package/README.md +73 -26
- package/SKILL-API.md +209 -205
- package/SKILL-CLI.md +71 -64
- package/app-config.js +479 -83
- package/cli/apiKey.js +1 -1
- package/cli/apps.js +1 -1
- package/cli/config.js +31 -12
- package/cli/credentials.js +88 -0
- package/cli/diagnostics.js +81 -98
- package/cli/firewall.js +29 -14
- package/cli/init.js +246 -201
- package/cli/monitor.js +107 -43
- package/cli/security.js +24 -12
- package/cli/ui.js +22 -12
- package/cli/utils.js +2 -1
- package/cli.js +71 -39
- package/console-instrumentation.js +1 -1
- package/docs/ENVIRONMENT-VARIABLES.md +137 -863
- package/docs/ENVIRONMENTS.md +60 -0
- package/docs/EXPRESS-SETUP-GUIDE.md +3 -0
- package/docs/FIREWALL-GUIDE.md +3 -0
- package/docs/INDEX.md +6 -8
- package/docs/LOGGING-GUIDE.md +3 -0
- package/docs/MCP-GUIDE.md +8 -0
- package/docs/NEXTJS-GUIDE.md +3 -0
- package/docs/NEXTJS-QUICKSTART.md +24 -16
- package/docs/NUXT-GUIDE.md +3 -0
- package/docs/QUICKSTART-BODY-CAPTURE.md +3 -0
- package/docs/REQUEST-BODY-CAPTURE.md +3 -0
- package/firewall-cloud.js +10 -10
- package/firewall-only.js +25 -23
- package/firewall.js +47 -29
- package/free-trial-banner.js +1 -1
- package/mcp/catalog.js +104 -17
- package/nextjs-auto-capture.d.ts +7 -4
- package/nextjs-auto-capture.js +7 -7
- package/nextjs-middleware.js +4 -3
- package/nextjs-wrapper.js +6 -6
- package/nextjs.d.ts +36 -25
- package/nextjs.js +47 -55
- package/nuxt-server-plugin.mjs +35 -51
- package/nuxt.d.ts +29 -23
- package/package.json +1 -1
- package/postinstall.js +27 -61
- package/register.d.ts +19 -33
- package/register.js +8 -8
- package/resolve-ip.js +4 -5
- package/tracing.d.ts +21 -19
- package/tracing.js +34 -42
package/cli/config.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const os = require('os');
|
|
6
|
+
const appConfig = require('../app-config');
|
|
6
7
|
|
|
7
8
|
const CONFIG_DIR = path.join(os.homedir(), '.securenow');
|
|
8
9
|
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
@@ -44,6 +45,10 @@ function saveJSON(filepath, data) {
|
|
|
44
45
|
}
|
|
45
46
|
}
|
|
46
47
|
|
|
48
|
+
function credentialsFileForLocal(local) {
|
|
49
|
+
return local ? LOCAL_CREDENTIALS_FILE : CREDENTIALS_FILE;
|
|
50
|
+
}
|
|
51
|
+
|
|
47
52
|
function hasLocalCredentials() {
|
|
48
53
|
return fs.existsSync(LOCAL_CREDENTIALS_FILE);
|
|
49
54
|
}
|
|
@@ -86,12 +91,21 @@ function setConfigValue(key, value) {
|
|
|
86
91
|
}
|
|
87
92
|
|
|
88
93
|
function loadCredentials() {
|
|
89
|
-
|
|
94
|
+
const global = loadJSON(CREDENTIALS_FILE);
|
|
95
|
+
const local = fs.existsSync(LOCAL_CREDENTIALS_FILE) ? loadJSON(LOCAL_CREDENTIALS_FILE) : null;
|
|
96
|
+
return appConfig.withCredentialDefaults(appConfig.mergeCredentials(global, local)) || {};
|
|
90
97
|
}
|
|
91
98
|
|
|
92
99
|
function saveCredentials(creds, { local = false } = {}) {
|
|
93
|
-
const targetFile = local
|
|
94
|
-
saveJSON(targetFile, creds);
|
|
100
|
+
const targetFile = credentialsFileForLocal(local);
|
|
101
|
+
saveJSON(targetFile, appConfig.withCredentialDefaults(creds) || {});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function ensureCredentialDefaults({ local } = {}) {
|
|
105
|
+
const useLocal = local === true || (local == null && hasLocalCredentials());
|
|
106
|
+
const targetFile = credentialsFileForLocal(useLocal);
|
|
107
|
+
const existing = loadJSON(targetFile);
|
|
108
|
+
saveJSON(targetFile, appConfig.withCredentialDefaults(existing || {}) || {});
|
|
95
109
|
}
|
|
96
110
|
|
|
97
111
|
function clearCredentials({ local } = {}) {
|
|
@@ -119,7 +133,8 @@ function getToken() {
|
|
|
119
133
|
}
|
|
120
134
|
|
|
121
135
|
function setAuth(token, email, expiresAt, { local = false, app = null } = {}) {
|
|
122
|
-
const
|
|
136
|
+
const targetFile = credentialsFileForLocal(local);
|
|
137
|
+
const payload = { ...loadJSON(targetFile), token, email, expiresAt };
|
|
123
138
|
if (app && (app.key || app.name || app.instance)) {
|
|
124
139
|
payload.app = {
|
|
125
140
|
key: app.key || null,
|
|
@@ -137,18 +152,18 @@ function getApp() {
|
|
|
137
152
|
|
|
138
153
|
function setApiKey(apiKey, { local } = {}) {
|
|
139
154
|
const useLocal = local === true || (local == null && hasLocalCredentials());
|
|
140
|
-
const targetFile = useLocal
|
|
155
|
+
const targetFile = credentialsFileForLocal(useLocal);
|
|
141
156
|
const existing = loadJSON(targetFile);
|
|
142
|
-
saveJSON(targetFile, { ...existing, apiKey });
|
|
157
|
+
saveJSON(targetFile, appConfig.withCredentialDefaults({ ...existing, apiKey }) || { apiKey });
|
|
143
158
|
}
|
|
144
159
|
|
|
145
160
|
function clearApiKey({ local } = {}) {
|
|
146
161
|
const useLocal = local === true || (local == null && hasLocalCredentials());
|
|
147
|
-
const targetFile = useLocal
|
|
162
|
+
const targetFile = credentialsFileForLocal(useLocal);
|
|
148
163
|
const existing = loadJSON(targetFile);
|
|
149
164
|
if (!existing || !existing.apiKey) return;
|
|
150
165
|
delete existing.apiKey;
|
|
151
|
-
saveJSON(targetFile, existing);
|
|
166
|
+
saveJSON(targetFile, appConfig.withCredentialDefaults(existing) || existing);
|
|
152
167
|
}
|
|
153
168
|
|
|
154
169
|
function getApiKey() {
|
|
@@ -158,16 +173,16 @@ function getApiKey() {
|
|
|
158
173
|
|
|
159
174
|
function setApp(app, { local } = {}) {
|
|
160
175
|
const useLocal = local === true || (local == null && hasLocalCredentials());
|
|
161
|
-
const targetFile = useLocal
|
|
176
|
+
const targetFile = credentialsFileForLocal(useLocal);
|
|
162
177
|
const existing = loadJSON(targetFile);
|
|
163
|
-
saveJSON(targetFile, {
|
|
178
|
+
saveJSON(targetFile, appConfig.withCredentialDefaults({
|
|
164
179
|
...existing,
|
|
165
180
|
app: {
|
|
166
181
|
key: app.key || null,
|
|
167
182
|
name: app.name || null,
|
|
168
183
|
instance: app.instance || null,
|
|
169
184
|
},
|
|
170
|
-
});
|
|
185
|
+
}) || {});
|
|
171
186
|
}
|
|
172
187
|
|
|
173
188
|
function ensureLocalGitignore() {
|
|
@@ -186,7 +201,10 @@ function ensureLocalGitignore() {
|
|
|
186
201
|
}
|
|
187
202
|
|
|
188
203
|
function getApiUrl() {
|
|
189
|
-
|
|
204
|
+
if (process.env.SECURENOW_API_URL) return process.env.SECURENOW_API_URL;
|
|
205
|
+
const cfg = loadConfig();
|
|
206
|
+
if (cfg.apiUrl && cfg.apiUrl !== DEFAULTS.apiUrl) return cfg.apiUrl;
|
|
207
|
+
return appConfig.env('SECURENOW_API_URL') || cfg.apiUrl;
|
|
190
208
|
}
|
|
191
209
|
|
|
192
210
|
function getAppUrl() {
|
|
@@ -219,6 +237,7 @@ module.exports = {
|
|
|
219
237
|
getApiKey,
|
|
220
238
|
getAuthSource,
|
|
221
239
|
hasLocalCredentials,
|
|
240
|
+
ensureCredentialDefaults,
|
|
222
241
|
ensureLocalGitignore,
|
|
223
242
|
getApiUrl,
|
|
224
243
|
getAppUrl,
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const config = require('./config');
|
|
6
|
+
const ui = require('./ui');
|
|
7
|
+
const appConfig = require('../app-config');
|
|
8
|
+
|
|
9
|
+
function maskSecret(value) {
|
|
10
|
+
if (!value) return '';
|
|
11
|
+
const text = String(value);
|
|
12
|
+
if (text.length <= 12) return '***';
|
|
13
|
+
return `${text.slice(0, 8)}...${text.slice(-4)}`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function buildRuntimeCredentials(options = {}) {
|
|
17
|
+
const creds = config.loadCredentials() || {};
|
|
18
|
+
const deploymentEnvironment =
|
|
19
|
+
options.environment ||
|
|
20
|
+
options.env ||
|
|
21
|
+
appConfig.resolveDeploymentEnvironment() ||
|
|
22
|
+
'production';
|
|
23
|
+
const runtime = appConfig.withCredentialDefaults({
|
|
24
|
+
apiKey: creds.apiKey || null,
|
|
25
|
+
app: {
|
|
26
|
+
key: creds.app?.key || null,
|
|
27
|
+
name: creds.app?.name || null,
|
|
28
|
+
instance: creds.app?.instance || appConfig.FREE_TRIAL_INSTANCE,
|
|
29
|
+
},
|
|
30
|
+
config: {
|
|
31
|
+
...(creds.config || {}),
|
|
32
|
+
runtime: {
|
|
33
|
+
...(creds.config?.runtime || {}),
|
|
34
|
+
deploymentEnvironment,
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
_securenow: {
|
|
38
|
+
...(creds._securenow || {}),
|
|
39
|
+
note: 'Runtime SecureNow credentials and SDK defaults. Mount or copy this JSON as .securenow/credentials.json in production. Do not commit it.',
|
|
40
|
+
runtimeOnly: 'This file intentionally omits CLI OAuth fields: token, email, and expiresAt.',
|
|
41
|
+
production: 'Production can use this same file shape instead of environment variables.',
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
delete runtime.token;
|
|
46
|
+
delete runtime.email;
|
|
47
|
+
delete runtime.expiresAt;
|
|
48
|
+
return runtime;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function writeFileSafe(filepath, data) {
|
|
52
|
+
fs.mkdirSync(path.dirname(filepath), { recursive: true, mode: 0o700 });
|
|
53
|
+
fs.writeFileSync(filepath, JSON.stringify(data, null, 2), { encoding: 'utf8', mode: 0o600 });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function runtime(_args, flags) {
|
|
57
|
+
const creds = buildRuntimeCredentials(flags);
|
|
58
|
+
const envName = creds.config?.runtime?.deploymentEnvironment || 'production';
|
|
59
|
+
const defaultOutput = envName === 'production'
|
|
60
|
+
? path.join('.securenow', 'credentials.production.json')
|
|
61
|
+
: path.join('.securenow', `credentials.${envName}.json`);
|
|
62
|
+
const output = flags.out || flags.output || defaultOutput;
|
|
63
|
+
const warn = flags.stdout ? (msg) => console.error(`! ${msg}`) : ui.warn;
|
|
64
|
+
|
|
65
|
+
if (!creds.app || !creds.app.key) {
|
|
66
|
+
warn('No app key found. Run `npx securenow login` first so telemetry routes to the selected app.');
|
|
67
|
+
}
|
|
68
|
+
if (!creds.apiKey) {
|
|
69
|
+
warn('No firewall API key found. Run `npx securenow login` or `npx securenow api-key set snk_live_...` first.');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (flags.stdout) {
|
|
73
|
+
console.log(JSON.stringify(creds, null, 2));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
writeFileSafe(path.resolve(process.cwd(), output), creds);
|
|
78
|
+
config.ensureLocalGitignore();
|
|
79
|
+
|
|
80
|
+
ui.success(`Wrote runtime credentials to ${output}`);
|
|
81
|
+
ui.info('Deploy this JSON as .securenow/credentials.json on the server/container.');
|
|
82
|
+
ui.info(`Environment: ${envName}`);
|
|
83
|
+
ui.info(`App: ${creds.app?.name || '(unnamed)'} ${creds.app?.key ? `(${creds.app.key})` : ''}`);
|
|
84
|
+
ui.info(`Firewall key: ${creds.apiKey ? maskSecret(creds.apiKey) : '(missing)'}`);
|
|
85
|
+
ui.info('OAuth token/email were not included.');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
module.exports = { runtime, buildRuntimeCredentials };
|
package/cli/diagnostics.js
CHANGED
|
@@ -4,76 +4,61 @@ const crypto = require('crypto');
|
|
|
4
4
|
const url = require('url');
|
|
5
5
|
const ui = require('./ui');
|
|
6
6
|
const config = require('./config');
|
|
7
|
+
const appConfig = require('../app-config');
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
function resolvedConfig() {
|
|
11
|
-
// Mirror the SDK's resolution: env → credentials file → package.json#name.
|
|
12
|
-
// Doing it here means `securenow doctor` reports what the SDK will actually
|
|
13
|
-
// send — critical for diagnosing "my traces don't show up" (the common
|
|
14
|
-
// cause is a service.name mismatch with the dashboard's exact-match filter).
|
|
15
|
-
const appConfig = require('../app-config');
|
|
9
|
+
function resolvedConfig(options = {}) {
|
|
16
10
|
const resolvedApp = appConfig.resolveAll();
|
|
11
|
+
const endpoints = appConfig.resolveEndpoints();
|
|
12
|
+
const firewall = appConfig.resolveFirewallOptions();
|
|
17
13
|
const noUuid = appConfig.resolveNoUuid();
|
|
14
|
+
const deploymentEnvironment = appConfig.normalizeDeploymentEnvironment(
|
|
15
|
+
options.environment || options.env || resolvedApp.deploymentEnvironment
|
|
16
|
+
);
|
|
18
17
|
|
|
19
|
-
const baseName = (
|
|
18
|
+
const baseName = (resolvedApp.appId || '').trim().replace(/^['"]|['"]$/g, '') || null;
|
|
20
19
|
const serviceName = baseName
|
|
21
20
|
? (noUuid ? baseName : `${baseName}-<uuid-per-worker>`)
|
|
22
21
|
: '(auto-generated)';
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
const tracesEndpoint =
|
|
26
|
-
process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||
|
|
27
|
-
(process.env.OTEL_EXPORTER_OTLP_ENDPOINT
|
|
28
|
-
? `${process.env.OTEL_EXPORTER_OTLP_ENDPOINT.replace(/\/$/, '')}/v1/traces`
|
|
29
|
-
: `${instance.replace(/\/$/, '')}/v1/traces`);
|
|
30
|
-
const logsEndpoint =
|
|
31
|
-
process.env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT ||
|
|
32
|
-
(process.env.OTEL_EXPORTER_OTLP_ENDPOINT
|
|
33
|
-
? `${process.env.OTEL_EXPORTER_OTLP_ENDPOINT.replace(/\/$/, '')}/v1/logs`
|
|
34
|
-
: `${instance.replace(/\/$/, '')}/v1/logs`);
|
|
35
|
-
const headers = process.env.OTEL_EXPORTER_OTLP_HEADERS || '';
|
|
36
|
-
// Resolve firewall API key the same way the SDK does: env, then
|
|
37
|
-
// .securenow/credentials.json (project-local, then global).
|
|
38
|
-
const apiKey = require('../app-config').resolveApiKey() || '';
|
|
39
|
-
const apiUrl = config.getApiUrl();
|
|
40
|
-
const loggingEnabled = !/^(0|false)$/i.test(String(process.env.SECURENOW_LOGGING_ENABLED ?? ''));
|
|
41
|
-
const captureBody = !/^(0|false)$/i.test(String(process.env.SECURENOW_CAPTURE_BODY ?? ''));
|
|
42
|
-
const firewallEnabled =
|
|
43
|
-
!!apiKey && process.env.SECURENOW_FIREWALL_ENABLED !== '0';
|
|
22
|
+
const apiKey = firewall.apiKey || '';
|
|
23
|
+
const firewallEnabled = !!apiKey && firewall.enabled;
|
|
44
24
|
|
|
45
25
|
return {
|
|
46
26
|
serviceName,
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
27
|
+
appKey: resolvedApp.appKey || null,
|
|
28
|
+
deploymentEnvironment,
|
|
29
|
+
instance: endpoints.endpointBase,
|
|
30
|
+
tracesEndpoint: endpoints.tracesUrl,
|
|
31
|
+
logsEndpoint: endpoints.logsUrl,
|
|
32
|
+
headers: endpoints.headers,
|
|
51
33
|
apiKey,
|
|
52
|
-
apiUrl,
|
|
53
|
-
loggingEnabled,
|
|
54
|
-
captureBody,
|
|
34
|
+
apiUrl: config.getApiUrl(),
|
|
35
|
+
loggingEnabled: appConfig.boolEnv('SECURENOW_LOGGING_ENABLED', true),
|
|
36
|
+
captureBody: appConfig.boolEnv('SECURENOW_CAPTURE_BODY', true),
|
|
37
|
+
captureMultipart: appConfig.boolEnv('SECURENOW_CAPTURE_MULTIPART', true),
|
|
55
38
|
firewallEnabled,
|
|
56
39
|
firewallLayers: {
|
|
57
40
|
http: firewallEnabled,
|
|
58
|
-
tcp: firewallEnabled &&
|
|
59
|
-
iptables: firewallEnabled &&
|
|
60
|
-
cloud: firewallEnabled ?
|
|
41
|
+
tcp: firewallEnabled && firewall.tcp,
|
|
42
|
+
iptables: firewallEnabled && firewall.iptables,
|
|
43
|
+
cloud: firewallEnabled ? firewall.cloud : null,
|
|
61
44
|
},
|
|
62
45
|
};
|
|
63
46
|
}
|
|
64
47
|
|
|
65
|
-
function
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
if (!k) continue;
|
|
71
|
-
out[k.trim()] = rest.join('=').trim();
|
|
72
|
-
}
|
|
73
|
-
return out;
|
|
48
|
+
function maskSecret(value) {
|
|
49
|
+
if (!value) return '';
|
|
50
|
+
const text = String(value);
|
|
51
|
+
if (text.length <= 12) return '***';
|
|
52
|
+
return `${text.slice(0, 12)}...${text.slice(-4)}`;
|
|
74
53
|
}
|
|
75
54
|
|
|
76
|
-
|
|
55
|
+
function redactConfig(cfg) {
|
|
56
|
+
return {
|
|
57
|
+
...cfg,
|
|
58
|
+
headers: cfg.headers && Object.keys(cfg.headers).length ? '***' : '',
|
|
59
|
+
apiKey: cfg.apiKey ? maskSecret(cfg.apiKey) : '',
|
|
60
|
+
};
|
|
61
|
+
}
|
|
77
62
|
|
|
78
63
|
function httpRequest({ method = 'POST', endpoint, headers = {}, body, timeoutMs = 5000 }) {
|
|
79
64
|
return new Promise((resolve, reject) => {
|
|
@@ -104,8 +89,6 @@ function httpRequest({ method = 'POST', endpoint, headers = {}, body, timeoutMs
|
|
|
104
89
|
});
|
|
105
90
|
}
|
|
106
91
|
|
|
107
|
-
// ── OTLP/HTTP JSON payloads ──
|
|
108
|
-
|
|
109
92
|
function attr(key, value) {
|
|
110
93
|
if (typeof value === 'number') {
|
|
111
94
|
if (Number.isInteger(value)) return { key, value: { intValue: String(value) } };
|
|
@@ -118,7 +101,7 @@ function attr(key, value) {
|
|
|
118
101
|
function resourceAttrs(cfg, extra = {}) {
|
|
119
102
|
const base = {
|
|
120
103
|
'service.name': cfg.serviceName,
|
|
121
|
-
'deployment.environment':
|
|
104
|
+
'deployment.environment': cfg.deploymentEnvironment || appConfig.resolveDeploymentEnvironment(),
|
|
122
105
|
'telemetry.sdk.name': 'securenow-cli',
|
|
123
106
|
'telemetry.sdk.language': 'nodejs',
|
|
124
107
|
...extra,
|
|
@@ -184,18 +167,19 @@ function buildLogPayload(cfg, { message, level = 'info', attributes = {} }) {
|
|
|
184
167
|
};
|
|
185
168
|
}
|
|
186
169
|
|
|
187
|
-
// ── Commands ──
|
|
188
|
-
|
|
189
170
|
async function testSpan(args, flags) {
|
|
190
|
-
const cfg = resolvedConfig();
|
|
171
|
+
const cfg = resolvedConfig(flags);
|
|
191
172
|
const spanName = args[0] || 'securenow.cli.test-span';
|
|
192
173
|
const headers = {
|
|
193
174
|
'Content-Type': 'application/json',
|
|
194
|
-
...
|
|
175
|
+
...cfg.headers,
|
|
195
176
|
};
|
|
196
177
|
const payload = buildTracePayload(cfg, {
|
|
197
178
|
name: spanName,
|
|
198
|
-
attributes: {
|
|
179
|
+
attributes: {
|
|
180
|
+
'test.source': 'securenow-cli',
|
|
181
|
+
'securenow.environment': cfg.deploymentEnvironment,
|
|
182
|
+
},
|
|
199
183
|
});
|
|
200
184
|
|
|
201
185
|
const spin = ui.spinner(`Sending test span to ${cfg.tracesEndpoint}`);
|
|
@@ -207,7 +191,7 @@ async function testSpan(args, flags) {
|
|
|
207
191
|
});
|
|
208
192
|
if (res.status >= 200 && res.status < 300) {
|
|
209
193
|
spin.stop(`Span accepted (HTTP ${res.status})`);
|
|
210
|
-
if (flags.json) ui.json({ ok: true, status: res.status, endpoint: cfg.tracesEndpoint });
|
|
194
|
+
if (flags.json) ui.json({ ok: true, status: res.status, endpoint: cfg.tracesEndpoint, environment: cfg.deploymentEnvironment });
|
|
211
195
|
return;
|
|
212
196
|
}
|
|
213
197
|
spin.fail(`Collector returned HTTP ${res.status}`);
|
|
@@ -229,9 +213,9 @@ async function logSend(args, flags) {
|
|
|
229
213
|
process.exit(1);
|
|
230
214
|
}
|
|
231
215
|
|
|
232
|
-
const cfg = resolvedConfig();
|
|
216
|
+
const cfg = resolvedConfig(flags);
|
|
233
217
|
if (!cfg.loggingEnabled) {
|
|
234
|
-
ui.warn('Logging is disabled
|
|
218
|
+
ui.warn('Logging is disabled in SecureNow credentials/config. Sending anyway.');
|
|
235
219
|
}
|
|
236
220
|
|
|
237
221
|
const level = (flags.level || 'info').toString();
|
|
@@ -245,9 +229,16 @@ async function logSend(args, flags) {
|
|
|
245
229
|
|
|
246
230
|
const headers = {
|
|
247
231
|
'Content-Type': 'application/json',
|
|
248
|
-
...
|
|
232
|
+
...cfg.headers,
|
|
249
233
|
};
|
|
250
|
-
const payload = buildLogPayload(cfg, {
|
|
234
|
+
const payload = buildLogPayload(cfg, {
|
|
235
|
+
message,
|
|
236
|
+
level,
|
|
237
|
+
attributes: {
|
|
238
|
+
...attributes,
|
|
239
|
+
'securenow.environment': cfg.deploymentEnvironment,
|
|
240
|
+
},
|
|
241
|
+
});
|
|
251
242
|
|
|
252
243
|
const spin = ui.spinner(`Sending log to ${cfg.logsEndpoint}`);
|
|
253
244
|
try {
|
|
@@ -258,7 +249,7 @@ async function logSend(args, flags) {
|
|
|
258
249
|
});
|
|
259
250
|
if (res.status >= 200 && res.status < 300) {
|
|
260
251
|
spin.stop(`Log accepted (HTTP ${res.status})`);
|
|
261
|
-
if (flags.json) ui.json({ ok: true, status: res.status, endpoint: cfg.logsEndpoint });
|
|
252
|
+
if (flags.json) ui.json({ ok: true, status: res.status, endpoint: cfg.logsEndpoint, environment: cfg.deploymentEnvironment });
|
|
262
253
|
return;
|
|
263
254
|
}
|
|
264
255
|
spin.fail(`Collector returned HTTP ${res.status}`);
|
|
@@ -272,12 +263,8 @@ async function logSend(args, flags) {
|
|
|
272
263
|
}
|
|
273
264
|
}
|
|
274
265
|
|
|
275
|
-
// ── doctor / env ──
|
|
276
|
-
|
|
277
266
|
async function probe(endpoint, timeoutMs = 3000) {
|
|
278
267
|
try {
|
|
279
|
-
// A HEAD or empty POST is safer than sending real OTLP. Most collectors
|
|
280
|
-
// return 400/405 for a malformed request — that still proves reachability.
|
|
281
268
|
const res = await httpRequest({
|
|
282
269
|
method: 'POST',
|
|
283
270
|
endpoint,
|
|
@@ -293,43 +280,43 @@ async function probe(endpoint, timeoutMs = 3000) {
|
|
|
293
280
|
|
|
294
281
|
function env(_args, flags) {
|
|
295
282
|
const cfg = resolvedConfig();
|
|
296
|
-
const
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
SECURENOW_FIREWALL_CLOUD: process.env.SECURENOW_FIREWALL_CLOUD || null,
|
|
312
|
-
NODE_ENV: process.env.NODE_ENV || 'development',
|
|
283
|
+
const resolved = {
|
|
284
|
+
appKey: cfg.appKey,
|
|
285
|
+
serviceName: cfg.serviceName,
|
|
286
|
+
deploymentEnvironment: cfg.deploymentEnvironment,
|
|
287
|
+
instance: cfg.instance,
|
|
288
|
+
tracesEndpoint: cfg.tracesEndpoint,
|
|
289
|
+
logsEndpoint: cfg.logsEndpoint,
|
|
290
|
+
otlpHeaders: Object.keys(cfg.headers || {}).length ? '***' : null,
|
|
291
|
+
firewallApiKey: cfg.apiKey ? `${cfg.apiKey.slice(0, 12)}...` : null,
|
|
292
|
+
apiUrl: cfg.apiUrl,
|
|
293
|
+
loggingEnabled: cfg.loggingEnabled,
|
|
294
|
+
captureBody: cfg.captureBody,
|
|
295
|
+
captureMultipart: cfg.captureMultipart,
|
|
296
|
+
noUuid: appConfig.resolveNoUuid(),
|
|
297
|
+
firewallLayers: cfg.firewallLayers,
|
|
313
298
|
};
|
|
314
299
|
|
|
315
300
|
if (flags.json) {
|
|
316
|
-
ui.json({ resolved: cfg,
|
|
301
|
+
ui.json({ resolved: redactConfig(cfg), config: resolved });
|
|
317
302
|
return;
|
|
318
303
|
}
|
|
319
304
|
|
|
320
305
|
ui.heading('Resolved configuration');
|
|
321
306
|
ui.keyValue([
|
|
322
307
|
['Service name', cfg.serviceName],
|
|
308
|
+
['Environment', cfg.deploymentEnvironment],
|
|
323
309
|
['Traces endpoint', cfg.tracesEndpoint],
|
|
324
310
|
['Logs endpoint', cfg.logsEndpoint],
|
|
325
311
|
['Logging', cfg.loggingEnabled ? ui.c.green('enabled') : ui.c.dim('disabled')],
|
|
326
312
|
['Body capture', cfg.captureBody ? ui.c.green('enabled') : ui.c.dim('disabled')],
|
|
313
|
+
['Multipart capture', cfg.captureMultipart ? ui.c.green('enabled') : ui.c.dim('disabled')],
|
|
327
314
|
['Firewall', cfg.firewallEnabled ? ui.c.green('enabled') : ui.c.dim('disabled (no API key)')],
|
|
328
315
|
]);
|
|
329
316
|
|
|
330
|
-
ui.heading('
|
|
317
|
+
ui.heading('Resolved credentials');
|
|
331
318
|
ui.keyValue(
|
|
332
|
-
Object.entries(
|
|
319
|
+
Object.entries(resolved).map(([k, v]) => [k, v == null ? ui.c.dim('(not set)') : typeof v === 'object' ? JSON.stringify(v) : String(v)])
|
|
333
320
|
);
|
|
334
321
|
console.log('');
|
|
335
322
|
}
|
|
@@ -338,21 +325,18 @@ async function doctor(_args, flags) {
|
|
|
338
325
|
const cfg = resolvedConfig();
|
|
339
326
|
const checks = [];
|
|
340
327
|
|
|
341
|
-
// Collector traces
|
|
342
328
|
const spin1 = ui.spinner(`Probing traces endpoint ${cfg.tracesEndpoint}`);
|
|
343
329
|
const traces = await probe(cfg.tracesEndpoint);
|
|
344
330
|
if (traces.ok) spin1.stop(`Traces endpoint reachable (HTTP ${traces.status})`);
|
|
345
331
|
else spin1.fail(`Traces endpoint unreachable: ${traces.error}`);
|
|
346
332
|
checks.push({ name: 'traces', ...traces });
|
|
347
333
|
|
|
348
|
-
// Collector logs
|
|
349
334
|
const spin2 = ui.spinner(`Probing logs endpoint ${cfg.logsEndpoint}`);
|
|
350
335
|
const logs = await probe(cfg.logsEndpoint);
|
|
351
336
|
if (logs.ok) spin2.stop(`Logs endpoint reachable (HTTP ${logs.status})`);
|
|
352
337
|
else spin2.fail(`Logs endpoint unreachable: ${logs.error}`);
|
|
353
338
|
checks.push({ name: 'logs', ...logs });
|
|
354
339
|
|
|
355
|
-
// SecureNow API (only if API key or logged-in token)
|
|
356
340
|
const token = config.getToken();
|
|
357
341
|
if (cfg.apiKey || token) {
|
|
358
342
|
const spin3 = ui.spinner(`Probing SecureNow API ${cfg.apiUrl}`);
|
|
@@ -362,22 +346,21 @@ async function doctor(_args, flags) {
|
|
|
362
346
|
checks.push({ name: 'api', ...api });
|
|
363
347
|
}
|
|
364
348
|
|
|
365
|
-
// Config sanity
|
|
366
349
|
const warnings = [];
|
|
367
|
-
if (!
|
|
368
|
-
warnings.push('No
|
|
350
|
+
if (!cfg.appKey) {
|
|
351
|
+
warnings.push('No app key resolved. Run `npx securenow login` or set app.key in .securenow/credentials.json.');
|
|
369
352
|
}
|
|
370
353
|
if (cfg.instance === 'https://freetrial.securenow.ai:4318') {
|
|
371
|
-
warnings.push('Using the free-trial collector
|
|
354
|
+
warnings.push('Using the free-trial collector. For production, set app.instance in .securenow/credentials.json.');
|
|
372
355
|
}
|
|
373
|
-
if (!cfg.apiKey
|
|
374
|
-
warnings.push('
|
|
356
|
+
if (!cfg.apiKey) {
|
|
357
|
+
warnings.push('No snk_live firewall API key is resolvable. Run `npx securenow login` or `npx securenow api-key set`.');
|
|
375
358
|
}
|
|
376
359
|
|
|
377
360
|
const ok = checks.every((c) => c.ok);
|
|
378
361
|
|
|
379
362
|
if (flags.json) {
|
|
380
|
-
ui.json({ ok, resolved: cfg, checks, warnings });
|
|
363
|
+
ui.json({ ok, resolved: redactConfig(cfg), checks, warnings });
|
|
381
364
|
process.exit(ok ? 0 : 1);
|
|
382
365
|
}
|
|
383
366
|
|
package/cli/firewall.js
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { api, requireAuth } = require('./client');
|
|
4
|
-
const ui = require('./ui');
|
|
3
|
+
const { api, requireAuth } = require('./client');
|
|
4
|
+
const ui = require('./ui');
|
|
5
|
+
|
|
6
|
+
function resolveEnvironment(flags) {
|
|
7
|
+
return flags.env || flags.environment || 'production';
|
|
8
|
+
}
|
|
5
9
|
|
|
6
10
|
async function status(args, flags) {
|
|
7
11
|
requireAuth();
|
|
8
12
|
const s = ui.spinner('Checking firewall status');
|
|
9
13
|
|
|
10
14
|
try {
|
|
11
|
-
const
|
|
15
|
+
const environment = resolveEnvironment(flags);
|
|
16
|
+
const data = await api.get('/firewall/status', { query: { environment } });
|
|
12
17
|
|
|
13
18
|
s.stop('Firewall status retrieved');
|
|
14
19
|
|
|
@@ -21,7 +26,8 @@ async function status(args, flags) {
|
|
|
21
26
|
console.log(` ${ui.c.bold(ui.c.green('Firewall: ENABLED'))}`);
|
|
22
27
|
console.log('');
|
|
23
28
|
ui.keyValue([
|
|
24
|
-
['Blocked IPs', `${data.totalIps} total (${data.exactCount} exact + ${data.cidrCount} CIDR ranges)`],
|
|
29
|
+
['Blocked IPs', `${data.totalIps} total (${data.exactCount} exact + ${data.cidrCount} CIDR ranges)`],
|
|
30
|
+
['Environment', data.environment || environment],
|
|
25
31
|
['Last updated', data.updatedAt || 'unknown'],
|
|
26
32
|
['Allowed IPs', data.allowlistCount != null ? `${data.allowlistCount} total (${data.allowlistExactCount} exact + ${data.allowlistCidrCount} CIDR ranges)` : '0'],
|
|
27
33
|
['Allowlist updated', data.allowlistUpdatedAt || 'never'],
|
|
@@ -59,7 +65,8 @@ async function testIp(args, flags) {
|
|
|
59
65
|
const s = ui.spinner(`Testing IP ${ip}`);
|
|
60
66
|
|
|
61
67
|
try {
|
|
62
|
-
const
|
|
68
|
+
const environment = resolveEnvironment(flags);
|
|
69
|
+
const data = await api.get(`/firewall/check/${encodeURIComponent(ip)}`, { query: { environment } });
|
|
63
70
|
|
|
64
71
|
s.stop(`IP ${ip} checked`);
|
|
65
72
|
|
|
@@ -115,10 +122,11 @@ async function setEnabled(args, flags, enabled) {
|
|
|
115
122
|
process.exit(1);
|
|
116
123
|
}
|
|
117
124
|
|
|
118
|
-
const
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
125
|
+
const environment = resolveEnvironment(flags);
|
|
126
|
+
const verb = enabled ? 'Enabling' : 'Disabling';
|
|
127
|
+
const s = ui.spinner(`${verb} firewall for ${appKey} (${environment})`);
|
|
128
|
+
try {
|
|
129
|
+
const data = await api.patch(`/firewall/app/${encodeURIComponent(appKey)}`, { enabled, environment });
|
|
122
130
|
s.stop(`Firewall ${enabled ? 'enabled' : 'disabled'}`);
|
|
123
131
|
|
|
124
132
|
if (flags.json) { ui.json(data); return; }
|
|
@@ -126,7 +134,8 @@ async function setEnabled(args, flags, enabled) {
|
|
|
126
134
|
const app = data.app || {};
|
|
127
135
|
console.log('');
|
|
128
136
|
console.log(` ${ui.c.bold(enabled ? ui.c.green('Firewall: ENABLED') : ui.c.yellow('Firewall: DISABLED'))} — ${app.name || appKey}`);
|
|
129
|
-
console.log(` ${ui.c.dim('App key:')} ${app.key || appKey}`);
|
|
137
|
+
console.log(` ${ui.c.dim('App key:')} ${app.key || appKey}`);
|
|
138
|
+
console.log(` ${ui.c.dim('Environment:')} ${app.environment || environment}`);
|
|
130
139
|
console.log('');
|
|
131
140
|
console.log(` ${ui.c.dim('Running SDK instances pick up the change within ~10s.')}`);
|
|
132
141
|
console.log('');
|
|
@@ -157,10 +166,16 @@ async function appsList(args, flags) {
|
|
|
157
166
|
}
|
|
158
167
|
|
|
159
168
|
console.log('');
|
|
160
|
-
for (const a of apps) {
|
|
161
|
-
const tag = a.firewallEnabled ? ui.c.green('ON ') : ui.c.yellow('OFF');
|
|
162
|
-
console.log(` ${tag} ${ui.c.bold(a.name)} ${ui.c.dim(a.key)}`);
|
|
163
|
-
|
|
169
|
+
for (const a of apps) {
|
|
170
|
+
const tag = a.firewallEnabled ? ui.c.green('ON ') : ui.c.yellow('OFF');
|
|
171
|
+
console.log(` ${tag} ${ui.c.bold(a.name)} ${ui.c.dim(a.key)}`);
|
|
172
|
+
if (a.environments) {
|
|
173
|
+
const envLine = Object.entries(a.environments)
|
|
174
|
+
.map(([env, state]) => `${env}:${state.firewallEnabled ? 'on' : 'off'}`)
|
|
175
|
+
.join(' ');
|
|
176
|
+
if (envLine) console.log(` ${ui.c.dim(envLine)}`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
164
179
|
console.log('');
|
|
165
180
|
console.log(` ${ui.c.dim('Toggle:')} securenow firewall enable --app <key>`);
|
|
166
181
|
console.log(` ${ui.c.dim(' :')} securenow firewall disable --app <key>`);
|