securenow 7.7.16 → 7.8.1
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 +35 -22
- package/README.md +50 -32
- package/SKILL-API.md +48 -24
- package/SKILL-CLI.md +61 -40
- package/app-config.js +79 -16
- package/cli/apiKey.js +7 -7
- package/cli/apps.js +3 -3
- package/cli/auth.js +113 -31
- package/cli/client.js +14 -13
- package/cli/config.js +219 -45
- package/cli/credentials.js +3 -3
- package/cli/diagnostics.js +5 -6
- package/cli/firewall.js +19 -7
- package/cli/init.js +5 -5
- package/cli/security.js +31 -11
- package/cli.js +57 -22
- package/firewall-only.js +3 -4
- package/firewall.js +110 -35
- package/mcp/catalog.js +43 -30
- package/mcp/server.js +73 -12
- package/nextjs.js +4 -1
- package/nuxt-server-plugin.mjs +7 -4
- package/otel-defaults.js +11 -0
- package/package.json +2 -1
- package/tracing.js +4 -1
package/SKILL-CLI.md
CHANGED
|
@@ -24,24 +24,27 @@ codex mcp add securenow -- npx securenow mcp
|
|
|
24
24
|
npx -p securenow securenow-mcp
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
The MCP server
|
|
27
|
+
The MCP server reads admin/control-plane auth from `.securenow/admin.json` and SDK runtime app credentials from `.securenow/runtime.json`, with legacy `.securenow/credentials.json` still supported. Admin/global tools use admin auth; runtime-scoped read tools can use the runtime firewall key where its scopes allow it. Write tools require `confirm:true` plus a reason.
|
|
28
28
|
|
|
29
29
|
### Authenticate
|
|
30
30
|
|
|
31
31
|
```bash
|
|
32
|
-
securenow login #
|
|
33
|
-
securenow login
|
|
34
|
-
securenow
|
|
35
|
-
securenow
|
|
36
|
-
|
|
32
|
+
securenow login # friendly flow: admin auth + app runtime connection
|
|
33
|
+
securenow admin login # admin/control-plane CLI and MCP auth only
|
|
34
|
+
securenow app connect # app/runtime SDK connection only
|
|
35
|
+
securenow admin login --token <JWT> # headless / CI admin auth
|
|
36
|
+
securenow whoami # verify both admin auth and runtime app status
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**Two-lane credentials (v7.8+):** admin/control-plane auth lives in `.securenow/admin.json`; SDK runtime app config and the firewall API key live in `.securenow/runtime.json`. `securenow login` can run both lanes for onboarding, but `securenow admin login` never replaces runtime app config and `securenow app connect` never replaces admin auth. Legacy combined `.securenow/credentials.json` files are still read.
|
|
37
40
|
|
|
38
|
-
**Zero-config flow
|
|
41
|
+
**Zero-config runtime flow:** the browser step lets the user pick (or create) an app. The CLI stores the app's **key (UUID)** and **name** in `.securenow/runtime.json`. The SDK sends traces/logs to the default SecureNow ingestion gateway, which routes by app key, so no env vars or per-instance collector URLs are required for local dev or production.
|
|
39
42
|
|
|
40
|
-
**Default-on security (v7.5.1+):** after picking or creating the app, `securenow
|
|
43
|
+
**Default-on security (v7.5.1+):** after picking or creating the app, `securenow app connect` turns on that app's firewall toggle, mints an API key with `firewall:read + blocklist:read + allowlist:read` scopes, and writes it into `.securenow/runtime.json`. Traces, logs, POST body capture, multipart metadata capture, and the firewall are enabled by default. No `SECURENOW_API_KEY` env var is needed. To add or rotate a key later without re-running app connect, use `securenow api-key set snk_live_...` (see [API Key Management](#api-key-management) below).
|
|
41
44
|
|
|
42
|
-
|
|
45
|
+
Runtime credentials resolve in order: project `.securenow/runtime.json` -> legacy project `.securenow/credentials.json` -> project named runtime credentials -> global `.securenow/runtime.json` -> legacy global credentials -> global named runtime credentials. Admin auth resolves from `admin.json` first, then legacy `credentials.json`. Runtime config is credentials-json based; legacy env fallbacks are disabled unless `SECURENOW_ENABLE_LEGACY_ENV=1` is set and never choose the credentials filename.
|
|
43
46
|
|
|
44
|
-
The **firewall API key** should live in
|
|
47
|
+
The **firewall API key** should live in runtime credentials as `apiKey`.
|
|
45
48
|
|
|
46
49
|
For CI / Docker / production, use `securenow credentials runtime --env production` to generate a tokenless runtime file, then mount/copy it as `.securenow/credentials.json`. Since v7.7.2, mounting the generated `.securenow/credentials.production.json` filename directly also works when `credentials.json` is absent.
|
|
47
50
|
|
|
@@ -62,11 +65,11 @@ node -r securenow/register src/index.js
|
|
|
62
65
|
For Next.js, run the interactive scaffolding:
|
|
63
66
|
|
|
64
67
|
```bash
|
|
65
|
-
securenow login
|
|
66
|
-
securenow init
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
This auto-detects your framework, creates the necessary `instrumentation.ts` and `next.config.js` changes, and reuses the app, instance, and firewall key written by login to `.securenow/
|
|
68
|
+
securenow login
|
|
69
|
+
securenow init
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
This auto-detects your framework, creates the necessary `instrumentation.ts` and `next.config.js` changes, and reuses the app, instance, and firewall key written by login or `app connect` to `.securenow/runtime.json`.
|
|
70
73
|
|
|
71
74
|
### Install This Skill in Cursor
|
|
72
75
|
|
|
@@ -76,16 +79,19 @@ Save this file as `.cursor/skills/securenow-cli/SKILL.md` in your project. Your
|
|
|
76
79
|
|
|
77
80
|
Config lives in `~/.securenow/` (global) and optionally `.securenow/` (per-project):
|
|
78
81
|
|
|
79
|
-
| File | Content |
|
|
80
|
-
|------|---------|
|
|
81
|
-
| `~/.securenow/config.json` | `apiUrl`, `appUrl`, `defaultApp`, `output` |
|
|
82
|
-
| `~/.securenow/
|
|
83
|
-
|
|
|
82
|
+
| File | Content |
|
|
83
|
+
|------|---------|
|
|
84
|
+
| `~/.securenow/config.json` | `apiUrl`, `appUrl`, `defaultApp`, `output` |
|
|
85
|
+
| `~/.securenow/admin.json` | `token`, `email`, `expiresAt` for global admin/control-plane CLI and MCP auth |
|
|
86
|
+
| `~/.securenow/runtime.json` | `apiKey`, `app`, `config` for global SDK runtime |
|
|
87
|
+
| `.securenow/admin.json` | `token`, `email`, `expiresAt` for project-local admin/control-plane CLI and MCP auth |
|
|
88
|
+
| `.securenow/runtime.json` | `apiKey`, `app`, `config`, `_securenow.explanations` for project-local SDK runtime |
|
|
89
|
+
| `.securenow/credentials.json` | Legacy combined credentials; still read for backward compatibility |
|
|
84
90
|
| `.securenow/credentials.<environment>.json` | Tokenless runtime credentials generated by `securenow credentials runtime --env <environment>`; read in a fixed order, not selected from env vars |
|
|
85
91
|
|
|
86
|
-
**Credential resolution order:** `.securenow/
|
|
92
|
+
**Credential resolution order:** runtime config resolves from project `.securenow/runtime.json` -> legacy project `.securenow/credentials.json` -> project named runtime credentials in the fixed staging/production/preview/local/test/development/dev/prod order -> global `.securenow/runtime.json` -> legacy global credentials -> global named runtime credentials. Admin auth resolves from `admin.json` first, then legacy `credentials.json`. Legacy env fallbacks are disabled unless `SECURENOW_ENABLE_LEGACY_ENV=1` is set.
|
|
87
93
|
|
|
88
|
-
**Firewall API key resolution (v7.
|
|
94
|
+
**Firewall API key resolution (v7.8+):** project `.securenow/runtime.json` -> legacy project credentials -> project named runtime credentials in the fixed staging/production/preview/local/test/development/dev/prod order -> global runtime credentials -> legacy global credentials -> global named runtime credentials. Use `securenow app connect` for runtime setup or `securenow api-key set` to rotate a key without touching admin auth or env vars.
|
|
89
95
|
|
|
90
96
|
```bash
|
|
91
97
|
securenow config set apiUrl https://api.securenow.ai
|
|
@@ -128,12 +134,13 @@ Spawns `node --require securenow/register [--import otel/hook.mjs] <script>`. ES
|
|
|
128
134
|
### Authentication
|
|
129
135
|
|
|
130
136
|
```bash
|
|
131
|
-
securenow login #
|
|
132
|
-
securenow login
|
|
133
|
-
securenow login --
|
|
134
|
-
securenow
|
|
135
|
-
securenow logout
|
|
136
|
-
securenow
|
|
137
|
+
securenow login # friendly flow: admin auth + app runtime connection
|
|
138
|
+
securenow admin login # admin/control-plane CLI and MCP auth only
|
|
139
|
+
securenow admin login --token <JWT> # headless / CI admin auth
|
|
140
|
+
securenow app connect # app/runtime SDK connection only
|
|
141
|
+
securenow logout # clear admin auth; runtime app config remains
|
|
142
|
+
securenow logout --global # clear global admin auth only
|
|
143
|
+
securenow whoami # show admin auth and runtime app status separately
|
|
137
144
|
```
|
|
138
145
|
|
|
139
146
|
### Applications
|
|
@@ -151,7 +158,7 @@ securenow apps scan [--yes] # scan all app domains for new subd
|
|
|
151
158
|
|
|
152
159
|
### API Key Management
|
|
153
160
|
|
|
154
|
-
Manage the firewall API key stored in
|
|
161
|
+
Manage the firewall API key stored in runtime credentials. Since v7.8 the app runtime flow writes `snk_live_...` keys to `.securenow/runtime.json` by default, so no env var is required for local dev.
|
|
155
162
|
|
|
156
163
|
```bash
|
|
157
164
|
securenow api-key create --name "CLI firewall" # mint + save a firewall key with your logged-in session
|
|
@@ -163,7 +170,7 @@ securenow api-key clear --global # same, but from the global file
|
|
|
163
170
|
securenow credentials runtime --env production # write .securenow/credentials.production.json for production secret-file deploys
|
|
164
171
|
```
|
|
165
172
|
|
|
166
|
-
The key must start with `snk_live_`. `securenow
|
|
173
|
+
The key must start with `snk_live_`. `securenow app connect` writes the key automatically; use `api-key create` when you have admin auth but no runtime key yet, or `api-key set` when you already have a key from the dashboard.
|
|
167
174
|
|
|
168
175
|
### Init — Project Setup
|
|
169
176
|
|
|
@@ -172,12 +179,12 @@ securenow init [--env local] [--key <API_KEY>]
|
|
|
172
179
|
```
|
|
173
180
|
|
|
174
181
|
Auto-detects framework (Next.js, Nuxt, Express, Fastify, Koa, Hapi, Node) from `package.json`. Then:
|
|
175
|
-
- **Credentials**: ensures `.securenow/
|
|
182
|
+
- **Credentials**: ensures `.securenow/runtime.json` exists, has secure defaults/explanations, and local credential JSON files are gitignored without ignoring the whole `.securenow/` directory
|
|
176
183
|
- **Next.js**: creates `instrumentation.ts/js` with `securenow/nextjs` + `securenow/nextjs-auto-capture`, and adds `serverExternalPackages: ['securenow']` plus `outputFileTracingIncludes` when the config can be patched safely
|
|
177
184
|
- **Existing Next.js files**: prints a Codex/Claude-ready merge prompt when instrumentation or config already exists or is too custom to safely patch
|
|
178
185
|
- **Nuxt**: tells you to add `securenow/nuxt` to modules
|
|
179
186
|
- **Node/Express/etc.**: suggests adding `-r securenow/register` to start script
|
|
180
|
-
- Reuses `.securenow/
|
|
187
|
+
- Reuses `.securenow/runtime.json` written by login/app connect; production should mount/copy the tokenless runtime file generated by `securenow credentials runtime --env production`
|
|
181
188
|
|
|
182
189
|
### Dashboard & Status
|
|
183
190
|
|
|
@@ -305,7 +312,7 @@ securenow firewall status --app <key> --env production # app/env toggle, sync
|
|
|
305
312
|
securenow firewall test-ip <ip> --app <key> --env local # check if IP would be blocked
|
|
306
313
|
```
|
|
307
314
|
|
|
308
|
-
**Zero-config setup
|
|
315
|
+
**Zero-config setup:** running `securenow app connect` enables the selected app's firewall toggle, auto-mints an API key (scoped `firewall:read + blocklist:read + allowlist:read`), and writes it to `.securenow/runtime.json` after the app is selected. No `SECURENOW_API_KEY` env var needed. If the user already has a key, `securenow api-key set snk_live_...` achieves the same thing. See [the landing firewall page](https://securenow.ai/firewall) for an overview.
|
|
309
316
|
|
|
310
317
|
### Blocklist — Block Malicious IPs
|
|
311
318
|
|
|
@@ -313,12 +320,17 @@ securenow firewall test-ip <ip> --app <key> --env local # check if IP would be
|
|
|
313
320
|
securenow blocklist # list blocked IPs
|
|
314
321
|
securenow blocklist list
|
|
315
322
|
securenow blocklist add <ip> --app <key> --env production --reason "Brute force"
|
|
323
|
+
securenow blocklist add <ip> --route /admin* --mode prefix --method ALL --reason "Admin probing"
|
|
316
324
|
securenow blocklist unblock <id> --reason "False alarm after review"
|
|
317
325
|
securenow blocklist remove <id> # compatibility alias for unblock
|
|
318
326
|
securenow blocklist list --status removed # audit retained unblocks
|
|
319
327
|
securenow blocklist stats # block counts, top reasons
|
|
320
328
|
```
|
|
321
329
|
|
|
330
|
+
Scoped blocks use the same model as rate limits: `--route`/`--path`/`--pattern`
|
|
331
|
+
sets `pathPattern`, `--mode`/`--path-mode` chooses `prefix`, `exact`, or
|
|
332
|
+
`regex`, and `--method` limits enforcement to one HTTP method or `ALL`.
|
|
333
|
+
|
|
322
334
|
Unblock stops firewall enforcement but preserves the block report, history, and
|
|
323
335
|
unblock audit fields. Reblocking the same IP later creates a fresh active block
|
|
324
336
|
without erasing the previous investigation trail.
|
|
@@ -368,7 +380,13 @@ supporting evidence, not the primary automation score.
|
|
|
368
380
|
|
|
369
381
|
### Allowlist — Restrict to Known IPs
|
|
370
382
|
|
|
371
|
-
|
|
383
|
+
Dangerous production mode: IP Allowlist is deny-by-default. Once any active
|
|
384
|
+
allowlist entry exists for an app/environment, only listed IPs can reach it and
|
|
385
|
+
all other IPs are blocked. Do not use allowlist to mark an IP trusted, suppress
|
|
386
|
+
false positives, or clean up investigations. Use `securenow trusted add` for
|
|
387
|
+
known-safe monitors, office/VPN traffic, or trusted bypass cases.
|
|
388
|
+
|
|
389
|
+
```bash
|
|
372
390
|
securenow allowlist # list allowed IPs
|
|
373
391
|
securenow allowlist list
|
|
374
392
|
securenow allowlist add <ip> --app <key> --env local --label "Office" --reason "Corporate VPN"
|
|
@@ -376,10 +394,13 @@ securenow allowlist remove <id>
|
|
|
376
394
|
securenow allowlist stats
|
|
377
395
|
```
|
|
378
396
|
|
|
379
|
-
### Trusted
|
|
397
|
+
### Trusted IPs
|
|
380
398
|
|
|
381
|
-
|
|
382
|
-
|
|
399
|
+
Trusted IPs are the safe bypass/suppression mechanism for known infrastructure
|
|
400
|
+
and do not enable deny-by-default allowlist mode.
|
|
401
|
+
|
|
402
|
+
```bash
|
|
403
|
+
securenow trusted # list trusted IPs
|
|
383
404
|
securenow trusted list
|
|
384
405
|
securenow trusted add <ip> [--label "CloudFlare edge"]
|
|
385
406
|
securenow trusted remove <id>
|
|
@@ -473,7 +494,7 @@ securenow test-span
|
|
|
473
494
|
securenow test-span "ci.smoke-test" # custom span name
|
|
474
495
|
```
|
|
475
496
|
|
|
476
|
-
Both commands use the default SecureNow ingestion gateway plus optional advanced `config.otel.*` overrides from `.securenow/
|
|
497
|
+
Both commands use the default SecureNow ingestion gateway plus optional advanced `config.otel.*` overrides from `.securenow/runtime.json` or legacy runtime credentials, and return non-zero on HTTP errors so CI/cron can detect failures.
|
|
477
498
|
|
|
478
499
|
### Utilities — Redaction, CIDR, Diagnostics
|
|
479
500
|
|
|
@@ -581,8 +602,8 @@ All commands support `--json` for structured output. When piping to other tools
|
|
|
581
602
|
|
|
582
603
|
| Exit code / Error | Meaning | Recovery |
|
|
583
604
|
|------------------|---------|----------|
|
|
584
|
-
| `Session expired` | JWT expired | `securenow login` (or `login --global` for shared
|
|
585
|
-
| `Not logged in` | No token found | `securenow login
|
|
605
|
+
| `Session expired` | Admin JWT expired | `securenow admin login` (or `admin login --global` for shared admin auth) |
|
|
606
|
+
| `Not logged in` | No admin token found | `securenow admin login`; runtime `.securenow/runtime.json` is unrelated |
|
|
586
607
|
| `Access denied (403)` | Insufficient plan or permissions | Upgrade plan or check user role |
|
|
587
608
|
| `Cannot connect` | API unreachable | Check `securenow config get apiUrl` or network |
|
|
588
609
|
| `Unknown command` | Typo or unrecognized command | `securenow help` |
|
package/app-config.js
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
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
|
|
@@ -32,6 +33,8 @@ const CREDENTIAL_FILE_ENVIRONMENTS = Object.freeze([
|
|
|
32
33
|
'dev',
|
|
33
34
|
'prod',
|
|
34
35
|
]);
|
|
36
|
+
const RUNTIME_CREDENTIALS_FILENAME = 'runtime.json';
|
|
37
|
+
const ADMIN_CREDENTIALS_FILENAME = 'admin.json';
|
|
35
38
|
|
|
36
39
|
const DEFAULT_CONFIG = Object.freeze({
|
|
37
40
|
logging: {
|
|
@@ -90,8 +93,8 @@ const DEFAULT_CONFIG = Object.freeze({
|
|
|
90
93
|
});
|
|
91
94
|
|
|
92
95
|
const CONFIG_EXPLANATIONS = Object.freeze({
|
|
93
|
-
'token': 'CLI session token
|
|
94
|
-
'apiKey': 'Scoped firewall API key (`snk_live_...`) minted by
|
|
96
|
+
'token': 'Legacy CLI session token. New admin/control-plane auth is written to admin.json. Secret: do not commit.',
|
|
97
|
+
'apiKey': 'Scoped firewall API key (`snk_live_...`) minted by `securenow app connect` or `securenow api-key set`. Secret: do not commit.',
|
|
95
98
|
'app.key': 'SecureNow application routing UUID. The SDK uses this as OTel service.name so dashboard queries match exactly.',
|
|
96
99
|
'app.name': 'Human-readable app label shown in CLI output.',
|
|
97
100
|
'app.routing': 'Telemetry uses the default SecureNow ingestion gateway and routes by app.key; runtime credentials do not expose per-instance collector URLs.',
|
|
@@ -111,7 +114,7 @@ const CONFIG_EXPLANATIONS = Object.freeze({
|
|
|
111
114
|
'config.runtime.strict': 'If true, PM2/cluster workers exit when no app identity is resolvable.',
|
|
112
115
|
'config.runtime.testSpan': 'If true, emit a startup smoke span. Prefer `npx securenow test-span` for manual checks.',
|
|
113
116
|
'config.runtime.hideBanner': 'Hide the free-trial response banner when using the managed free-trial collector.',
|
|
114
|
-
'config.firewall.enabled': '
|
|
117
|
+
'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
118
|
'config.firewall.apiUrl': 'Optional SecureNow firewall control-plane base URL. Leave unset/default so hosted SDKs sync through the SecureNow ingest gateway.',
|
|
116
119
|
'config.firewall.versionCheckInterval': 'Seconds between lightweight firewall version/ETag checks.',
|
|
117
120
|
'config.firewall.syncInterval': 'Seconds between safety-net full firewall blocklist syncs.',
|
|
@@ -244,7 +247,7 @@ function uniq(values) {
|
|
|
244
247
|
return out;
|
|
245
248
|
}
|
|
246
249
|
|
|
247
|
-
function
|
|
250
|
+
function legacyCredentialRelativePaths() {
|
|
248
251
|
return uniq([
|
|
249
252
|
path.join('.securenow', 'credentials.json'),
|
|
250
253
|
...CREDENTIAL_FILE_ENVIRONMENTS.map((envName) =>
|
|
@@ -253,7 +256,25 @@ function credentialRelativePaths() {
|
|
|
253
256
|
]);
|
|
254
257
|
}
|
|
255
258
|
|
|
256
|
-
function
|
|
259
|
+
function runtimeCredentialRelativePaths() {
|
|
260
|
+
return uniq([
|
|
261
|
+
path.join('.securenow', RUNTIME_CREDENTIALS_FILENAME),
|
|
262
|
+
...legacyCredentialRelativePaths(),
|
|
263
|
+
]);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function adminCredentialRelativePaths() {
|
|
267
|
+
return uniq([
|
|
268
|
+
path.join('.securenow', ADMIN_CREDENTIALS_FILENAME),
|
|
269
|
+
...legacyCredentialRelativePaths(),
|
|
270
|
+
]);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function credentialRelativePaths() {
|
|
274
|
+
return runtimeCredentialRelativePaths();
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function resolveLocalFileFromRelativePaths(relativePaths) {
|
|
257
278
|
const starts = [];
|
|
258
279
|
try {
|
|
259
280
|
if (typeof process !== 'undefined' && process.cwd) starts.push(process.cwd());
|
|
@@ -263,7 +284,7 @@ function resolveLocalCredentialsFile() {
|
|
|
263
284
|
if (process.argv && process.argv[1]) starts.push(path.dirname(process.argv[1]));
|
|
264
285
|
if (require.main && require.main.filename) starts.push(path.dirname(require.main.filename));
|
|
265
286
|
|
|
266
|
-
const candidates =
|
|
287
|
+
const candidates = relativePaths;
|
|
267
288
|
for (const start of uniq(starts)) {
|
|
268
289
|
const found = findUpFirstFile(start, candidates);
|
|
269
290
|
if (found) return found;
|
|
@@ -272,7 +293,15 @@ function resolveLocalCredentialsFile() {
|
|
|
272
293
|
return null;
|
|
273
294
|
}
|
|
274
295
|
|
|
275
|
-
function
|
|
296
|
+
function resolveLocalCredentialsFile() {
|
|
297
|
+
return resolveLocalFileFromRelativePaths(runtimeCredentialRelativePaths());
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function resolveLocalAdminCredentialsFile() {
|
|
301
|
+
return resolveLocalFileFromRelativePaths(adminCredentialRelativePaths());
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function resolveGlobalFileFromRelativePaths(relativePaths) {
|
|
276
305
|
let home;
|
|
277
306
|
try {
|
|
278
307
|
home = os.homedir();
|
|
@@ -280,7 +309,7 @@ function resolveGlobalCredentialsFile() {
|
|
|
280
309
|
return null;
|
|
281
310
|
}
|
|
282
311
|
|
|
283
|
-
for (const relativePath of
|
|
312
|
+
for (const relativePath of relativePaths) {
|
|
284
313
|
const found = fileIfReadable(path.join(home, relativePath));
|
|
285
314
|
if (found) return found;
|
|
286
315
|
}
|
|
@@ -288,6 +317,36 @@ function resolveGlobalCredentialsFile() {
|
|
|
288
317
|
return null;
|
|
289
318
|
}
|
|
290
319
|
|
|
320
|
+
function resolveGlobalCredentialsFile() {
|
|
321
|
+
return resolveGlobalFileFromRelativePaths(runtimeCredentialRelativePaths());
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function resolveGlobalAdminCredentialsFile() {
|
|
325
|
+
return resolveGlobalFileFromRelativePaths(adminCredentialRelativePaths());
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function runtimeCredentialsFromDocument(credentials) {
|
|
329
|
+
if (!credentials || typeof credentials !== 'object' || Array.isArray(credentials)) return null;
|
|
330
|
+
if (!credentials.runtime || typeof credentials.runtime !== 'object' || Array.isArray(credentials.runtime)) {
|
|
331
|
+
return credentials;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const runtime = clone(credentials.runtime);
|
|
335
|
+
if (runtime.environment) {
|
|
336
|
+
runtime.config = runtime.config || {};
|
|
337
|
+
runtime.config.runtime = runtime.config.runtime || {};
|
|
338
|
+
runtime.config.runtime.deploymentEnvironment = runtime.environment;
|
|
339
|
+
delete runtime.environment;
|
|
340
|
+
}
|
|
341
|
+
if (runtime.instanceUrl) {
|
|
342
|
+
runtime.config = runtime.config || {};
|
|
343
|
+
runtime.config.otel = runtime.config.otel || {};
|
|
344
|
+
if (!runtime.config.otel.endpoint) runtime.config.otel.endpoint = runtime.instanceUrl;
|
|
345
|
+
delete runtime.instanceUrl;
|
|
346
|
+
}
|
|
347
|
+
return runtime;
|
|
348
|
+
}
|
|
349
|
+
|
|
291
350
|
function resolvePackageJsonFile() {
|
|
292
351
|
const starts = [];
|
|
293
352
|
try {
|
|
@@ -308,7 +367,7 @@ function resolvePackageJsonFile() {
|
|
|
308
367
|
|
|
309
368
|
function loadLocalCredentials() {
|
|
310
369
|
try {
|
|
311
|
-
return withCredentialDefaults(readJsonSafe(resolveLocalCredentialsFile()));
|
|
370
|
+
return withCredentialDefaults(runtimeCredentialsFromDocument(readJsonSafe(resolveLocalCredentialsFile())));
|
|
312
371
|
} catch {
|
|
313
372
|
return null;
|
|
314
373
|
}
|
|
@@ -316,7 +375,7 @@ function loadLocalCredentials() {
|
|
|
316
375
|
|
|
317
376
|
function loadGlobalCredentials() {
|
|
318
377
|
try {
|
|
319
|
-
return withCredentialDefaults(readJsonSafe(resolveGlobalCredentialsFile()));
|
|
378
|
+
return withCredentialDefaults(runtimeCredentialsFromDocument(readJsonSafe(resolveGlobalCredentialsFile())));
|
|
320
379
|
} catch {
|
|
321
380
|
return null;
|
|
322
381
|
}
|
|
@@ -326,7 +385,7 @@ function loadCredentials() {
|
|
|
326
385
|
try {
|
|
327
386
|
const localFile = resolveLocalCredentialsFile();
|
|
328
387
|
if (localFile) {
|
|
329
|
-
return withCredentialDefaults(readJsonSafe(localFile));
|
|
388
|
+
return withCredentialDefaults(runtimeCredentialsFromDocument(readJsonSafe(localFile)));
|
|
330
389
|
}
|
|
331
390
|
return loadGlobalCredentials();
|
|
332
391
|
} catch {
|
|
@@ -754,10 +813,7 @@ function resolveEndpoints(options = {}) {
|
|
|
754
813
|
}
|
|
755
814
|
|
|
756
815
|
function resolveFirewallEnabled() {
|
|
757
|
-
|
|
758
|
-
if (fromConfig != null) return parseBool(fromConfig, true);
|
|
759
|
-
|
|
760
|
-
return parseBool(getPath(DEFAULT_CONFIG, 'firewall.enabled'), true);
|
|
816
|
+
return true;
|
|
761
817
|
}
|
|
762
818
|
|
|
763
819
|
function resolveFirewallOptions() {
|
|
@@ -802,6 +858,8 @@ module.exports = {
|
|
|
802
858
|
LEGACY_ENV_FALLBACK_FLAG,
|
|
803
859
|
CONFIG_SCHEMA_VERSION,
|
|
804
860
|
CREDENTIAL_FILE_ENVIRONMENTS,
|
|
861
|
+
RUNTIME_CREDENTIALS_FILENAME,
|
|
862
|
+
ADMIN_CREDENTIALS_FILENAME,
|
|
805
863
|
DEFAULT_CONFIG,
|
|
806
864
|
CONFIG_EXPLANATIONS,
|
|
807
865
|
ENV_TO_CONFIG_PATH,
|
|
@@ -838,9 +896,14 @@ module.exports = {
|
|
|
838
896
|
normalizeInstanceEndpoint,
|
|
839
897
|
normalizeSignalEndpoint,
|
|
840
898
|
headersToString,
|
|
899
|
+
legacyCredentialRelativePaths,
|
|
900
|
+
runtimeCredentialRelativePaths,
|
|
901
|
+
adminCredentialRelativePaths,
|
|
841
902
|
credentialRelativePaths,
|
|
842
903
|
resolveLocalCredentialsFile,
|
|
843
904
|
resolveGlobalCredentialsFile,
|
|
905
|
+
resolveLocalAdminCredentialsFile,
|
|
906
|
+
resolveGlobalAdminCredentialsFile,
|
|
844
907
|
loadCredentials,
|
|
845
908
|
loadLocalCredentials,
|
|
846
909
|
loadGlobalCredentials,
|
package/cli/apiKey.js
CHANGED
|
@@ -34,15 +34,15 @@ async function create(args, flags) {
|
|
|
34
34
|
name: result.apiKey?.name || name,
|
|
35
35
|
preset,
|
|
36
36
|
key: maskKey(key),
|
|
37
|
-
savedTo: local ? 'project .securenow/
|
|
37
|
+
savedTo: local ? 'project .securenow/runtime.json' : '~/.securenow/runtime.json',
|
|
38
38
|
});
|
|
39
39
|
return;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
ui.success(`API key saved (${maskKey(key)})`);
|
|
43
43
|
ui.info(local
|
|
44
|
-
? 'Stored in project .securenow/
|
|
45
|
-
: 'Stored in ~/.securenow/
|
|
44
|
+
? 'Stored in project .securenow/runtime.json (local)'
|
|
45
|
+
: 'Stored in ~/.securenow/runtime.json (global)');
|
|
46
46
|
ui.info('The plaintext key is not printed. The SDK will read it from the credentials file.');
|
|
47
47
|
} catch (err) {
|
|
48
48
|
s.fail('Failed to create API key');
|
|
@@ -68,8 +68,8 @@ async function set(args, flags) {
|
|
|
68
68
|
|
|
69
69
|
ui.success(`API key saved (${maskKey(key)})`);
|
|
70
70
|
ui.info(local
|
|
71
|
-
? 'Stored in project .securenow/
|
|
72
|
-
: 'Stored in ~/.securenow/
|
|
71
|
+
? 'Stored in project .securenow/runtime.json (local)'
|
|
72
|
+
: 'Stored in ~/.securenow/runtime.json (global)');
|
|
73
73
|
ui.info('The firewall will now pick it up automatically — no SECURENOW_API_KEY env var needed.');
|
|
74
74
|
}
|
|
75
75
|
|
|
@@ -87,11 +87,11 @@ async function clear(args, flags) {
|
|
|
87
87
|
async function show() {
|
|
88
88
|
const key = config.getApiKey();
|
|
89
89
|
if (!key) {
|
|
90
|
-
ui.info('Runtime firewall enforcement key is missing. Run `npx securenow
|
|
90
|
+
ui.info('Runtime firewall enforcement key is missing. Run `npx securenow app connect` or `npx securenow api-key set snk_live_...` to refresh .securenow/runtime.json.');
|
|
91
91
|
return;
|
|
92
92
|
}
|
|
93
93
|
console.log(maskKey(key));
|
|
94
|
-
ui.info(`Source: ${config.
|
|
94
|
+
ui.info(`Source: ${config.getRuntimeSource()}`);
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
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}`);
|