securenow 7.7.16 → 8.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NPM_README.md +44 -36
- package/README.md +56 -38
- package/SKILL-API.md +51 -27
- package/SKILL-CLI.md +67 -45
- package/app-config.js +90 -160
- package/cli/apiKey.js +21 -12
- package/cli/apps.js +3 -3
- package/cli/auth.js +114 -32
- package/cli/client.js +14 -13
- package/cli/config.js +219 -52
- package/cli/credentials.js +4 -4
- package/cli/diagnostics.js +5 -6
- package/cli/firewall.js +19 -7
- package/cli/human.js +13 -8
- package/cli/init.js +5 -5
- package/cli/run.js +1 -5
- package/cli/security.js +31 -11
- package/cli/utils.js +2 -3
- package/cli.js +68 -35
- package/console-instrumentation.js +1 -1
- package/firewall-only.js +7 -11
- package/firewall.js +110 -35
- package/mcp/catalog.js +582 -45
- package/mcp/server.js +73 -12
- package/nextjs-auto-capture.js +3 -6
- package/nextjs-middleware.js +2 -4
- package/nextjs-wrapper.js +3 -6
- package/nextjs.js +4 -11
- package/nuxt-server-plugin.mjs +7 -4
- package/otel-defaults.js +11 -0
- package/package.json +3 -3
- package/rate-limits.js +0 -2
- package/register-vite.js +5 -12
- package/register.js +5 -13
- package/resolve-ip.js +1 -1
- package/tracing.d.ts +1 -1
- package/tracing.js +6 -3
- package/web-vite.mjs +58 -62
package/app-config.js
CHANGED
|
@@ -3,13 +3,14 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* Shared SecureNow configuration resolver.
|
|
5
5
|
*
|
|
6
|
-
* Local development
|
|
6
|
+
* Local development is driven by ./.securenow/runtime.json.
|
|
7
|
+
* Legacy combined ./.securenow/credentials.json files remain readable.
|
|
7
8
|
* Named runtime files such as ./.securenow/credentials.staging.json and
|
|
8
9
|
* ./.securenow/credentials.production.json are also accepted when the
|
|
9
10
|
* canonical file is not present. Filename selection is deterministic and does
|
|
10
11
|
* not read environment variables.
|
|
11
|
-
*
|
|
12
|
-
*
|
|
12
|
+
* Every SDK setting has a file-backed equivalent so customers do not need
|
|
13
|
+
* .env files.
|
|
13
14
|
*/
|
|
14
15
|
|
|
15
16
|
const fs = require('fs');
|
|
@@ -20,7 +21,6 @@ const FREE_TRIAL_INSTANCE = 'https://ingest.securenow.ai';
|
|
|
20
21
|
const DEFAULT_API_URL = 'https://api.securenow.ai';
|
|
21
22
|
const DEFAULT_FIREWALL_API_URL = FREE_TRIAL_INSTANCE;
|
|
22
23
|
const LEGACY_SECURENOW_GATEWAY = 'https://api.securenow.ai/api/otlp';
|
|
23
|
-
const LEGACY_ENV_FALLBACK_FLAG = 'SECURENOW_ENABLE_LEGACY_ENV';
|
|
24
24
|
const CONFIG_SCHEMA_VERSION = 2;
|
|
25
25
|
const CREDENTIAL_FILE_ENVIRONMENTS = Object.freeze([
|
|
26
26
|
'staging',
|
|
@@ -32,6 +32,8 @@ const CREDENTIAL_FILE_ENVIRONMENTS = Object.freeze([
|
|
|
32
32
|
'dev',
|
|
33
33
|
'prod',
|
|
34
34
|
]);
|
|
35
|
+
const RUNTIME_CREDENTIALS_FILENAME = 'runtime.json';
|
|
36
|
+
const ADMIN_CREDENTIALS_FILENAME = 'admin.json';
|
|
35
37
|
|
|
36
38
|
const DEFAULT_CONFIG = Object.freeze({
|
|
37
39
|
logging: {
|
|
@@ -90,8 +92,8 @@ const DEFAULT_CONFIG = Object.freeze({
|
|
|
90
92
|
});
|
|
91
93
|
|
|
92
94
|
const CONFIG_EXPLANATIONS = Object.freeze({
|
|
93
|
-
'token': 'CLI session token
|
|
94
|
-
'apiKey': 'Scoped
|
|
95
|
+
'token': 'Legacy CLI session token. New admin/control-plane auth is written to admin.json. Secret: do not commit.',
|
|
96
|
+
'apiKey': 'Scoped runtime API key (`snk_live_...`) minted by `securenow app connect` or `securenow api-key set`. It authenticates telemetry ingestion and firewall sync. Secret: do not commit.',
|
|
95
97
|
'app.key': 'SecureNow application routing UUID. The SDK uses this as OTel service.name so dashboard queries match exactly.',
|
|
96
98
|
'app.name': 'Human-readable app label shown in CLI output.',
|
|
97
99
|
'app.routing': 'Telemetry uses the default SecureNow ingestion gateway and routes by app.key; runtime credentials do not expose per-instance collector URLs.',
|
|
@@ -103,7 +105,7 @@ const CONFIG_EXPLANATIONS = Object.freeze({
|
|
|
103
105
|
'config.otel.endpoint': 'Optional OTLP base endpoint override for advanced/self-hosted collectors. Leave null for the SecureNow ingestion gateway.',
|
|
104
106
|
'config.otel.tracesEndpoint': 'Optional full traces endpoint override, for split collectors.',
|
|
105
107
|
'config.otel.logsEndpoint': 'Optional full logs endpoint override, for split collectors.',
|
|
106
|
-
'config.otel.headers': 'Optional OTLP headers. The SDK auto-adds x-
|
|
108
|
+
'config.otel.headers': 'Optional OTLP headers. The SDK auto-adds x-securenow-app-key from app.key and Authorization from apiKey when missing.',
|
|
107
109
|
'config.otel.logLevel': 'OpenTelemetry diagnostic log level: error, warn, info, debug, or none.',
|
|
108
110
|
'config.otel.disableInstrumentations': 'Optional OTel instrumentation package names to disable.',
|
|
109
111
|
'config.runtime.deploymentEnvironment': 'deployment.environment resource attribute. Set this in the credentials file for production.',
|
|
@@ -111,7 +113,7 @@ const CONFIG_EXPLANATIONS = Object.freeze({
|
|
|
111
113
|
'config.runtime.strict': 'If true, PM2/cluster workers exit when no app identity is resolvable.',
|
|
112
114
|
'config.runtime.testSpan': 'If true, emit a startup smoke span. Prefer `npx securenow test-span` for manual checks.',
|
|
113
115
|
'config.runtime.hideBanner': 'Hide the free-trial response banner when using the managed free-trial collector.',
|
|
114
|
-
'config.firewall.enabled': '
|
|
116
|
+
'config.firewall.enabled': 'Deprecated local hint. Runtime firewall enforcement is controlled by the SecureNow dashboard/API app environment toggle; the SDK starts sync whenever apiKey is present.',
|
|
115
117
|
'config.firewall.apiUrl': 'Optional SecureNow firewall control-plane base URL. Leave unset/default so hosted SDKs sync through the SecureNow ingest gateway.',
|
|
116
118
|
'config.firewall.versionCheckInterval': 'Seconds between lightweight firewall version/ETag checks.',
|
|
117
119
|
'config.firewall.syncInterval': 'Seconds between safety-net full firewall blocklist syncs.',
|
|
@@ -128,45 +130,6 @@ const CONFIG_EXPLANATIONS = Object.freeze({
|
|
|
128
130
|
'config.networking.trustedProxies': 'Additional proxy IPs whose X-Forwarded-For headers should be trusted.',
|
|
129
131
|
});
|
|
130
132
|
|
|
131
|
-
const ENV_TO_CONFIG_PATH = Object.freeze({
|
|
132
|
-
SECURENOW_LOGGING_ENABLED: 'logging.enabled',
|
|
133
|
-
SECURENOW_CAPTURE_BODY: 'capture.body',
|
|
134
|
-
SECURENOW_CAPTURE_MULTIPART: 'capture.multipart',
|
|
135
|
-
SECURENOW_MAX_BODY_SIZE: 'capture.maxBodySize',
|
|
136
|
-
SECURENOW_SENSITIVE_FIELDS: 'capture.sensitiveFields',
|
|
137
|
-
SECURENOW_DISABLE_INSTRUMENTATIONS: 'otel.disableInstrumentations',
|
|
138
|
-
OTEL_EXPORTER_OTLP_ENDPOINT: 'otel.endpoint',
|
|
139
|
-
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: 'otel.tracesEndpoint',
|
|
140
|
-
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT: 'otel.logsEndpoint',
|
|
141
|
-
OTEL_EXPORTER_OTLP_HEADERS: 'otel.headers',
|
|
142
|
-
OTEL_LOG_LEVEL: 'otel.logLevel',
|
|
143
|
-
SECURENOW_ENVIRONMENT: 'runtime.deploymentEnvironment',
|
|
144
|
-
SECURENOW_DEPLOYMENT_ENVIRONMENT: 'runtime.deploymentEnvironment',
|
|
145
|
-
NODE_ENV: 'runtime.deploymentEnvironment',
|
|
146
|
-
SECURENOW_NO_UUID: 'runtime.noUuid',
|
|
147
|
-
SECURENOW_STRICT: 'runtime.strict',
|
|
148
|
-
SECURENOW_TEST_SPAN: 'runtime.testSpan',
|
|
149
|
-
SECURENOW_HIDE_BANNER: 'runtime.hideBanner',
|
|
150
|
-
SECURENOW_API_URL: 'firewall.apiUrl',
|
|
151
|
-
SECURENOW_FIREWALL_VERSION_INTERVAL: 'firewall.versionCheckInterval',
|
|
152
|
-
SECURENOW_FIREWALL_SYNC_INTERVAL: 'firewall.syncInterval',
|
|
153
|
-
SECURENOW_FIREWALL_FAIL_MODE: 'firewall.failMode',
|
|
154
|
-
SECURENOW_FIREWALL_STATUS_CODE: 'firewall.statusCode',
|
|
155
|
-
SECURENOW_FIREWALL_LOG: 'firewall.log',
|
|
156
|
-
SECURENOW_FIREWALL_TCP: 'firewall.tcp',
|
|
157
|
-
SECURENOW_FIREWALL_IPTABLES: 'firewall.iptables',
|
|
158
|
-
SECURENOW_FIREWALL_CLOUD: 'firewall.cloud',
|
|
159
|
-
SECURENOW_FIREWALL_CLOUD_DRY_RUN: 'firewall.cloudDryRun',
|
|
160
|
-
CLOUDFLARE_API_TOKEN: 'firewall.cloudflare.apiToken',
|
|
161
|
-
CLOUDFLARE_ACCOUNT_ID: 'firewall.cloudflare.accountId',
|
|
162
|
-
AWS_WAF_IP_SET_ID: 'firewall.aws.wafIpSetId',
|
|
163
|
-
AWS_WAF_IP_SET_NAME: 'firewall.aws.wafIpSetName',
|
|
164
|
-
AWS_WAF_SCOPE: 'firewall.aws.wafScope',
|
|
165
|
-
GCP_PROJECT_ID: 'firewall.gcp.projectId',
|
|
166
|
-
GCP_SECURITY_POLICY: 'firewall.gcp.securityPolicy',
|
|
167
|
-
SECURENOW_TRUSTED_PROXIES: 'networking.trustedProxies',
|
|
168
|
-
});
|
|
169
|
-
|
|
170
133
|
function clone(value) {
|
|
171
134
|
return value == null ? value : JSON.parse(JSON.stringify(value));
|
|
172
135
|
}
|
|
@@ -244,7 +207,7 @@ function uniq(values) {
|
|
|
244
207
|
return out;
|
|
245
208
|
}
|
|
246
209
|
|
|
247
|
-
function
|
|
210
|
+
function legacyCredentialRelativePaths() {
|
|
248
211
|
return uniq([
|
|
249
212
|
path.join('.securenow', 'credentials.json'),
|
|
250
213
|
...CREDENTIAL_FILE_ENVIRONMENTS.map((envName) =>
|
|
@@ -253,7 +216,25 @@ function credentialRelativePaths() {
|
|
|
253
216
|
]);
|
|
254
217
|
}
|
|
255
218
|
|
|
256
|
-
function
|
|
219
|
+
function runtimeCredentialRelativePaths() {
|
|
220
|
+
return uniq([
|
|
221
|
+
path.join('.securenow', RUNTIME_CREDENTIALS_FILENAME),
|
|
222
|
+
...legacyCredentialRelativePaths(),
|
|
223
|
+
]);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function adminCredentialRelativePaths() {
|
|
227
|
+
return uniq([
|
|
228
|
+
path.join('.securenow', ADMIN_CREDENTIALS_FILENAME),
|
|
229
|
+
...legacyCredentialRelativePaths(),
|
|
230
|
+
]);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function credentialRelativePaths() {
|
|
234
|
+
return runtimeCredentialRelativePaths();
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function resolveLocalFileFromRelativePaths(relativePaths) {
|
|
257
238
|
const starts = [];
|
|
258
239
|
try {
|
|
259
240
|
if (typeof process !== 'undefined' && process.cwd) starts.push(process.cwd());
|
|
@@ -263,7 +244,7 @@ function resolveLocalCredentialsFile() {
|
|
|
263
244
|
if (process.argv && process.argv[1]) starts.push(path.dirname(process.argv[1]));
|
|
264
245
|
if (require.main && require.main.filename) starts.push(path.dirname(require.main.filename));
|
|
265
246
|
|
|
266
|
-
const candidates =
|
|
247
|
+
const candidates = relativePaths;
|
|
267
248
|
for (const start of uniq(starts)) {
|
|
268
249
|
const found = findUpFirstFile(start, candidates);
|
|
269
250
|
if (found) return found;
|
|
@@ -272,7 +253,15 @@ function resolveLocalCredentialsFile() {
|
|
|
272
253
|
return null;
|
|
273
254
|
}
|
|
274
255
|
|
|
275
|
-
function
|
|
256
|
+
function resolveLocalCredentialsFile() {
|
|
257
|
+
return resolveLocalFileFromRelativePaths(runtimeCredentialRelativePaths());
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function resolveLocalAdminCredentialsFile() {
|
|
261
|
+
return resolveLocalFileFromRelativePaths(adminCredentialRelativePaths());
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function resolveGlobalFileFromRelativePaths(relativePaths) {
|
|
276
265
|
let home;
|
|
277
266
|
try {
|
|
278
267
|
home = os.homedir();
|
|
@@ -280,7 +269,7 @@ function resolveGlobalCredentialsFile() {
|
|
|
280
269
|
return null;
|
|
281
270
|
}
|
|
282
271
|
|
|
283
|
-
for (const relativePath of
|
|
272
|
+
for (const relativePath of relativePaths) {
|
|
284
273
|
const found = fileIfReadable(path.join(home, relativePath));
|
|
285
274
|
if (found) return found;
|
|
286
275
|
}
|
|
@@ -288,6 +277,36 @@ function resolveGlobalCredentialsFile() {
|
|
|
288
277
|
return null;
|
|
289
278
|
}
|
|
290
279
|
|
|
280
|
+
function resolveGlobalCredentialsFile() {
|
|
281
|
+
return resolveGlobalFileFromRelativePaths(runtimeCredentialRelativePaths());
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function resolveGlobalAdminCredentialsFile() {
|
|
285
|
+
return resolveGlobalFileFromRelativePaths(adminCredentialRelativePaths());
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function runtimeCredentialsFromDocument(credentials) {
|
|
289
|
+
if (!credentials || typeof credentials !== 'object' || Array.isArray(credentials)) return null;
|
|
290
|
+
if (!credentials.runtime || typeof credentials.runtime !== 'object' || Array.isArray(credentials.runtime)) {
|
|
291
|
+
return credentials;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const runtime = clone(credentials.runtime);
|
|
295
|
+
if (runtime.environment) {
|
|
296
|
+
runtime.config = runtime.config || {};
|
|
297
|
+
runtime.config.runtime = runtime.config.runtime || {};
|
|
298
|
+
runtime.config.runtime.deploymentEnvironment = runtime.environment;
|
|
299
|
+
delete runtime.environment;
|
|
300
|
+
}
|
|
301
|
+
if (runtime.instanceUrl) {
|
|
302
|
+
runtime.config = runtime.config || {};
|
|
303
|
+
runtime.config.otel = runtime.config.otel || {};
|
|
304
|
+
if (!runtime.config.otel.endpoint) runtime.config.otel.endpoint = runtime.instanceUrl;
|
|
305
|
+
delete runtime.instanceUrl;
|
|
306
|
+
}
|
|
307
|
+
return runtime;
|
|
308
|
+
}
|
|
309
|
+
|
|
291
310
|
function resolvePackageJsonFile() {
|
|
292
311
|
const starts = [];
|
|
293
312
|
try {
|
|
@@ -308,7 +327,7 @@ function resolvePackageJsonFile() {
|
|
|
308
327
|
|
|
309
328
|
function loadLocalCredentials() {
|
|
310
329
|
try {
|
|
311
|
-
return withCredentialDefaults(readJsonSafe(resolveLocalCredentialsFile()));
|
|
330
|
+
return withCredentialDefaults(runtimeCredentialsFromDocument(readJsonSafe(resolveLocalCredentialsFile())));
|
|
312
331
|
} catch {
|
|
313
332
|
return null;
|
|
314
333
|
}
|
|
@@ -316,7 +335,7 @@ function loadLocalCredentials() {
|
|
|
316
335
|
|
|
317
336
|
function loadGlobalCredentials() {
|
|
318
337
|
try {
|
|
319
|
-
return withCredentialDefaults(readJsonSafe(resolveGlobalCredentialsFile()));
|
|
338
|
+
return withCredentialDefaults(runtimeCredentialsFromDocument(readJsonSafe(resolveGlobalCredentialsFile())));
|
|
320
339
|
} catch {
|
|
321
340
|
return null;
|
|
322
341
|
}
|
|
@@ -326,7 +345,7 @@ function loadCredentials() {
|
|
|
326
345
|
try {
|
|
327
346
|
const localFile = resolveLocalCredentialsFile();
|
|
328
347
|
if (localFile) {
|
|
329
|
-
return withCredentialDefaults(readJsonSafe(localFile));
|
|
348
|
+
return withCredentialDefaults(runtimeCredentialsFromDocument(readJsonSafe(localFile)));
|
|
330
349
|
}
|
|
331
350
|
return loadGlobalCredentials();
|
|
332
351
|
} catch {
|
|
@@ -401,7 +420,7 @@ function withCredentialDefaults(credentials) {
|
|
|
401
420
|
out._securenow = mergeMissing(out._securenow, {
|
|
402
421
|
schemaVersion: CONFIG_SCHEMA_VERSION,
|
|
403
422
|
note: 'Local SecureNow credentials and secure SDK defaults. This file may contain secrets; keep .securenow/ in .gitignore.',
|
|
404
|
-
precedence:
|
|
423
|
+
precedence: 'The same credentials file works in local development and production. SDK runtime config is read from credentials JSON only.',
|
|
405
424
|
explanations: CONFIG_EXPLANATIONS,
|
|
406
425
|
});
|
|
407
426
|
return out;
|
|
@@ -417,19 +436,6 @@ function getPath(obj, dotted) {
|
|
|
417
436
|
return cur;
|
|
418
437
|
}
|
|
419
438
|
|
|
420
|
-
function legacyEnvFallbackEnabled() {
|
|
421
|
-
const raw =
|
|
422
|
-
process.env[LEGACY_ENV_FALLBACK_FLAG] ??
|
|
423
|
-
process.env.SECURENOW_ALLOW_ENV_CONFIG ??
|
|
424
|
-
process.env.SECURENOW_LEGACY_ENV;
|
|
425
|
-
return /^(1|true|yes)$/i.test(String(raw || '').trim());
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
function rawEnv(key) {
|
|
429
|
-
if (!legacyEnvFallbackEnabled()) return undefined;
|
|
430
|
-
return process.env[key] ?? process.env[key.toUpperCase()] ?? process.env[key.toLowerCase()];
|
|
431
|
-
}
|
|
432
|
-
|
|
433
439
|
function normalizeInstanceEndpoint(value) {
|
|
434
440
|
if (value === undefined || value === null || value === '') return value;
|
|
435
441
|
const endpoint = String(value).trim().replace(/\/$/, '');
|
|
@@ -567,24 +573,11 @@ function headersToString(headers) {
|
|
|
567
573
|
.join(',');
|
|
568
574
|
}
|
|
569
575
|
|
|
570
|
-
function
|
|
571
|
-
if (value === undefined || value === null || value === '') return undefined;
|
|
572
|
-
if (typeof value === 'boolean') return value ? '1' : '0';
|
|
573
|
-
if (Array.isArray(value)) return value.join(',');
|
|
574
|
-
if (typeof value === 'object') return headersToString(value);
|
|
575
|
-
return String(value);
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
function resolveConfigPath(configPath, envKeys = [], fallback) {
|
|
576
|
+
function resolveConfigPath(configPath, _envKeys = [], fallback) {
|
|
579
577
|
const creds = loadCredentials();
|
|
580
578
|
const fromCreds = pick(getPath(creds && creds.config, configPath));
|
|
581
579
|
if (fromCreds != null) return fromCreds;
|
|
582
580
|
|
|
583
|
-
for (const key of envKeys) {
|
|
584
|
-
const fromEnv = pick(rawEnv(key));
|
|
585
|
-
if (fromEnv != null) return fromEnv;
|
|
586
|
-
}
|
|
587
|
-
|
|
588
581
|
const fromDefault = pick(getPath(DEFAULT_CONFIG, configPath));
|
|
589
582
|
if (fromDefault != null) return fromDefault;
|
|
590
583
|
|
|
@@ -610,28 +603,12 @@ function listConfig(configPath) {
|
|
|
610
603
|
function resolveAppKey() {
|
|
611
604
|
const creds = loadCredentials();
|
|
612
605
|
if (creds && creds.app && pick(creds.app.key)) return String(pick(creds.app.key));
|
|
613
|
-
|
|
614
|
-
const fromEnv = pick(rawEnv('SECURENOW_APPID')) || pick(rawEnv('securenow'));
|
|
615
|
-
if (fromEnv) return String(fromEnv);
|
|
616
|
-
|
|
617
|
-
// Legacy compatibility: older docs sometimes used SECURENOW_API_KEY as the
|
|
618
|
-
// app routing UUID. Real firewall keys start with snk_live_ and are handled
|
|
619
|
-
// by resolveApiKey(), not as service.name.
|
|
620
|
-
const legacyApiKey = pick(rawEnv('SECURENOW_API_KEY'));
|
|
621
|
-
if (legacyApiKey && !String(legacyApiKey).startsWith('snk_live_')) {
|
|
622
|
-
return String(legacyApiKey);
|
|
623
|
-
}
|
|
624
|
-
|
|
625
606
|
return null;
|
|
626
607
|
}
|
|
627
608
|
|
|
628
609
|
function resolveAppName() {
|
|
629
610
|
const creds = loadCredentials();
|
|
630
611
|
if (creds && creds.app && pick(creds.app.name)) return String(pick(creds.app.name));
|
|
631
|
-
|
|
632
|
-
const fromEnv = pick(rawEnv('OTEL_SERVICE_NAME'));
|
|
633
|
-
if (fromEnv) return String(fromEnv);
|
|
634
|
-
|
|
635
612
|
return loadPackageJsonName();
|
|
636
613
|
}
|
|
637
614
|
|
|
@@ -643,23 +620,12 @@ function resolveApiKey() {
|
|
|
643
620
|
const creds = loadCredentials();
|
|
644
621
|
const fromCreds = creds && pick(creds.apiKey);
|
|
645
622
|
if (fromCreds && String(fromCreds).startsWith('snk_live_')) return String(fromCreds);
|
|
646
|
-
|
|
647
|
-
const fromEnv = pick(rawEnv('SECURENOW_API_KEY'));
|
|
648
|
-
if (fromEnv && String(fromEnv).startsWith('snk_live_')) return String(fromEnv);
|
|
649
|
-
|
|
650
623
|
return null;
|
|
651
624
|
}
|
|
652
625
|
|
|
653
626
|
function resolveInstance() {
|
|
654
627
|
const fromConfig = pick(resolveConfigPath('otel.endpoint'));
|
|
655
628
|
if (fromConfig) return normalizeInstanceEndpoint(fromConfig);
|
|
656
|
-
|
|
657
|
-
const fromEnv =
|
|
658
|
-
pick(rawEnv('SECURENOW_INSTANCE')) ||
|
|
659
|
-
pick(rawEnv('securenow_instance')) ||
|
|
660
|
-
pick(rawEnv('OTEL_EXPORTER_OTLP_ENDPOINT'));
|
|
661
|
-
if (fromEnv) return normalizeInstanceEndpoint(fromEnv);
|
|
662
|
-
|
|
663
629
|
return FREE_TRIAL_INSTANCE;
|
|
664
630
|
}
|
|
665
631
|
|
|
@@ -684,51 +650,18 @@ function resolveNoUuid(opts = {}) {
|
|
|
684
650
|
const fromConfig = pick(getPath(loadCredentials()?.config, 'runtime.noUuid'));
|
|
685
651
|
if (fromConfig !== null) return parseBool(fromConfig, false);
|
|
686
652
|
|
|
687
|
-
const raw = pick(rawEnv('SECURENOW_NO_UUID'));
|
|
688
|
-
if (raw !== null) return parseBool(raw, false);
|
|
689
|
-
|
|
690
653
|
return !!resolveAppKey();
|
|
691
654
|
}
|
|
692
655
|
|
|
693
|
-
function resolveEnvKey(key) {
|
|
694
|
-
const upper = String(key).toUpperCase();
|
|
695
|
-
|
|
696
|
-
if (upper === 'SECURENOW_APPID') return resolveAppKey();
|
|
697
|
-
if (upper === 'OTEL_SERVICE_NAME') return resolveAppName();
|
|
698
|
-
if (upper === 'SECURENOW_API_KEY') return resolveApiKey();
|
|
699
|
-
if (upper === 'SECURENOW_INSTANCE' || upper === 'OTEL_EXPORTER_OTLP_ENDPOINT') return resolveInstance();
|
|
700
|
-
if (upper === 'SECURENOW_FIREWALL_ENABLED') return resolveFirewallEnabled();
|
|
701
|
-
|
|
702
|
-
const configPath = ENV_TO_CONFIG_PATH[upper];
|
|
703
|
-
if (configPath) return resolveConfigPath(configPath);
|
|
704
|
-
|
|
705
|
-
const fromEnv = pick(rawEnv(upper));
|
|
706
|
-
if (fromEnv != null) return fromEnv;
|
|
707
|
-
|
|
708
|
-
return undefined;
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
function env(key) {
|
|
712
|
-
return toEnvString(resolveEnvKey(key));
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
function boolEnv(key, fallback = false) {
|
|
716
|
-
return parseBool(resolveEnvKey(key), fallback);
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
function numberEnv(key, fallback, min) {
|
|
720
|
-
return parseNumber(resolveEnvKey(key), fallback, min);
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
function listEnv(key) {
|
|
724
|
-
return parseList(resolveEnvKey(key));
|
|
725
|
-
}
|
|
726
|
-
|
|
727
656
|
function resolveOtlpHeaders() {
|
|
728
657
|
const headers = parseHeaders(configValue('otel.headers', {}));
|
|
729
658
|
const appKey = resolveAppKey();
|
|
730
|
-
|
|
731
|
-
|
|
659
|
+
const apiKey = resolveApiKey();
|
|
660
|
+
if (appKey && !headers['x-securenow-app-key']) {
|
|
661
|
+
headers['x-securenow-app-key'] = appKey;
|
|
662
|
+
}
|
|
663
|
+
if (apiKey && !headers.authorization) {
|
|
664
|
+
headers.authorization = `Bearer ${apiKey}`;
|
|
732
665
|
}
|
|
733
666
|
const deploymentEnvironment = resolveDeploymentEnvironment();
|
|
734
667
|
if (deploymentEnvironment && !headers['x-securenow-environment']) {
|
|
@@ -754,10 +687,7 @@ function resolveEndpoints(options = {}) {
|
|
|
754
687
|
}
|
|
755
688
|
|
|
756
689
|
function resolveFirewallEnabled() {
|
|
757
|
-
|
|
758
|
-
if (fromConfig != null) return parseBool(fromConfig, true);
|
|
759
|
-
|
|
760
|
-
return parseBool(getPath(DEFAULT_CONFIG, 'firewall.enabled'), true);
|
|
690
|
+
return true;
|
|
761
691
|
}
|
|
762
692
|
|
|
763
693
|
function resolveFirewallOptions() {
|
|
@@ -799,12 +729,12 @@ module.exports = {
|
|
|
799
729
|
DEFAULT_API_URL,
|
|
800
730
|
DEFAULT_FIREWALL_API_URL,
|
|
801
731
|
LEGACY_SECURENOW_GATEWAY,
|
|
802
|
-
LEGACY_ENV_FALLBACK_FLAG,
|
|
803
732
|
CONFIG_SCHEMA_VERSION,
|
|
804
733
|
CREDENTIAL_FILE_ENVIRONMENTS,
|
|
734
|
+
RUNTIME_CREDENTIALS_FILENAME,
|
|
735
|
+
ADMIN_CREDENTIALS_FILENAME,
|
|
805
736
|
DEFAULT_CONFIG,
|
|
806
737
|
CONFIG_EXPLANATIONS,
|
|
807
|
-
ENV_TO_CONFIG_PATH,
|
|
808
738
|
withCredentialDefaults,
|
|
809
739
|
mergeCredentials,
|
|
810
740
|
resolveConfigPath,
|
|
@@ -818,7 +748,6 @@ module.exports = {
|
|
|
818
748
|
resolveApiKey,
|
|
819
749
|
resolveInstance,
|
|
820
750
|
normalizeDeploymentEnvironment,
|
|
821
|
-
legacyEnvFallbackEnabled,
|
|
822
751
|
resolveDeploymentEnvironment,
|
|
823
752
|
resolveAll,
|
|
824
753
|
resolveNoUuid,
|
|
@@ -830,17 +759,18 @@ module.exports = {
|
|
|
830
759
|
resolveFirewallApiUrl,
|
|
831
760
|
resolveFirewallApiFallbacks,
|
|
832
761
|
resolveFirewallOptions,
|
|
833
|
-
env,
|
|
834
|
-
boolEnv,
|
|
835
|
-
numberEnv,
|
|
836
|
-
listEnv,
|
|
837
762
|
parseHeaders,
|
|
838
763
|
normalizeInstanceEndpoint,
|
|
839
764
|
normalizeSignalEndpoint,
|
|
840
765
|
headersToString,
|
|
766
|
+
legacyCredentialRelativePaths,
|
|
767
|
+
runtimeCredentialRelativePaths,
|
|
768
|
+
adminCredentialRelativePaths,
|
|
841
769
|
credentialRelativePaths,
|
|
842
770
|
resolveLocalCredentialsFile,
|
|
843
771
|
resolveGlobalCredentialsFile,
|
|
772
|
+
resolveLocalAdminCredentialsFile,
|
|
773
|
+
resolveGlobalAdminCredentialsFile,
|
|
844
774
|
loadCredentials,
|
|
845
775
|
loadLocalCredentials,
|
|
846
776
|
loadGlobalCredentials,
|
package/cli/apiKey.js
CHANGED
|
@@ -12,16 +12,24 @@ function maskKey(key) {
|
|
|
12
12
|
async function create(args, flags) {
|
|
13
13
|
requireAuth();
|
|
14
14
|
|
|
15
|
-
const name = flags.name || args.join(' ').trim() || 'CLI
|
|
16
|
-
const preset = flags.preset || '
|
|
15
|
+
const name = flags.name || args.join(' ').trim() || 'CLI runtime';
|
|
16
|
+
const preset = flags.preset || 'runtime_app';
|
|
17
17
|
const local = flags.global ? false : true;
|
|
18
|
+
const app = config.getApp();
|
|
18
19
|
|
|
19
20
|
const s = ui.spinner(`Creating ${preset} API key`);
|
|
20
21
|
try {
|
|
21
|
-
const
|
|
22
|
+
const body = { name, preset };
|
|
23
|
+
if (flags.app) {
|
|
24
|
+
body.apps = [flags.app];
|
|
25
|
+
} else if (preset === 'runtime_app' && app?.key) {
|
|
26
|
+
body.apps = [app.key];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const result = await api.post('/api-keys', body);
|
|
22
30
|
const key = result && result.key;
|
|
23
31
|
if (!key || !key.startsWith('snk_live_')) {
|
|
24
|
-
throw new Error('API did not return a valid
|
|
32
|
+
throw new Error('API did not return a valid runtime key');
|
|
25
33
|
}
|
|
26
34
|
|
|
27
35
|
config.setApiKey(key, { local });
|
|
@@ -34,16 +42,17 @@ async function create(args, flags) {
|
|
|
34
42
|
name: result.apiKey?.name || name,
|
|
35
43
|
preset,
|
|
36
44
|
key: maskKey(key),
|
|
37
|
-
|
|
45
|
+
appScope: body.apps || [],
|
|
46
|
+
savedTo: local ? 'project .securenow/runtime.json' : '~/.securenow/runtime.json',
|
|
38
47
|
});
|
|
39
48
|
return;
|
|
40
49
|
}
|
|
41
50
|
|
|
42
51
|
ui.success(`API key saved (${maskKey(key)})`);
|
|
43
52
|
ui.info(local
|
|
44
|
-
? 'Stored in project .securenow/
|
|
45
|
-
: 'Stored in ~/.securenow/
|
|
46
|
-
ui.info('The plaintext key is not printed. The SDK will read it from the credentials file.');
|
|
53
|
+
? 'Stored in project .securenow/runtime.json (local)'
|
|
54
|
+
: 'Stored in ~/.securenow/runtime.json (global)');
|
|
55
|
+
ui.info('The plaintext key is not printed. The SDK will read it from the runtime credentials file.');
|
|
47
56
|
} catch (err) {
|
|
48
57
|
s.fail('Failed to create API key');
|
|
49
58
|
throw err;
|
|
@@ -68,8 +77,8 @@ async function set(args, flags) {
|
|
|
68
77
|
|
|
69
78
|
ui.success(`API key saved (${maskKey(key)})`);
|
|
70
79
|
ui.info(local
|
|
71
|
-
? 'Stored in project .securenow/
|
|
72
|
-
: 'Stored in ~/.securenow/
|
|
80
|
+
? 'Stored in project .securenow/runtime.json (local)'
|
|
81
|
+
: 'Stored in ~/.securenow/runtime.json (global)');
|
|
73
82
|
ui.info('The firewall will now pick it up automatically — no SECURENOW_API_KEY env var needed.');
|
|
74
83
|
}
|
|
75
84
|
|
|
@@ -87,11 +96,11 @@ async function clear(args, flags) {
|
|
|
87
96
|
async function show() {
|
|
88
97
|
const key = config.getApiKey();
|
|
89
98
|
if (!key) {
|
|
90
|
-
ui.info('Runtime
|
|
99
|
+
ui.info('Runtime API key is missing. Run `npx securenow app connect` or `npx securenow api-key create` to refresh .securenow/runtime.json.');
|
|
91
100
|
return;
|
|
92
101
|
}
|
|
93
102
|
console.log(maskKey(key));
|
|
94
|
-
ui.info(`Source: ${config.
|
|
103
|
+
ui.info(`Source: ${config.getRuntimeSource()}`);
|
|
95
104
|
}
|
|
96
105
|
|
|
97
106
|
module.exports = { create, set, clear, show };
|
package/cli/apps.js
CHANGED
|
@@ -66,7 +66,7 @@ async function list(args, flags) {
|
|
|
66
66
|
if (!defaultApp && apps.length > 0) {
|
|
67
67
|
console.log(` ${ui.c.bold('Use one of these apps in the current project:')}`);
|
|
68
68
|
console.log(` ${ui.c.bold('securenow apps default <key>')}`);
|
|
69
|
-
console.log(` ${ui.c.dim('(or run `securenow
|
|
69
|
+
console.log(` ${ui.c.dim('(or run `securenow app connect` to pick interactively)')}`);
|
|
70
70
|
console.log('');
|
|
71
71
|
}
|
|
72
72
|
} catch (err) {
|
|
@@ -127,7 +127,7 @@ async function create(args, flags) {
|
|
|
127
127
|
console.log('');
|
|
128
128
|
console.log(` ${ui.c.bold('Use this app in the current project:')}`);
|
|
129
129
|
console.log(` ${ui.c.bold(`securenow apps default ${app.key}`)}`);
|
|
130
|
-
console.log(` ${ui.c.dim('(writes .securenow/
|
|
130
|
+
console.log(` ${ui.c.dim('(writes .securenow/runtime.json - no env var needed)')}`);
|
|
131
131
|
console.log('');
|
|
132
132
|
|
|
133
133
|
if (flags.json) {
|
|
@@ -285,7 +285,7 @@ async function setDefault(args) {
|
|
|
285
285
|
}
|
|
286
286
|
config.setConfigValue('defaultApp', key);
|
|
287
287
|
|
|
288
|
-
// Mirror into credentials
|
|
288
|
+
// Mirror into runtime credentials so the SDK picks this app up with zero env vars.
|
|
289
289
|
try {
|
|
290
290
|
requireAuth();
|
|
291
291
|
const appData = await api.get(`/applications/${key}`);
|