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/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 reuses `.securenow/credentials.json` and exposes apps, traces, logs, firewall, IP intelligence, forensics, notifications, remediation tools, alert-rule review/tuning tools, bundled docs resources, and setup prompts. Write tools require `confirm:true` plus a reason.
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 # opens browser OAuth + app picker; writes ./.securenow/credentials.json (project-local by default)
33
- securenow login --global # save to ~/.securenow/ instead (shared across projects)
34
- securenow login --token <JWT> # headless / CI login (get token from dashboard Settings)
35
- securenow whoami # verify session (shows email, app, auth source)
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 (v7+):** the browser step lets the user pick (or create) an app. The CLI stores the app's **key (UUID)** and **name** in `.securenow/credentials.json`. The SDK sends traces/logs to the default SecureNow ingestion gateway, which routes by app key **no env vars or per-instance collector URLs required for local dev or production**.
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 login` turns on that app's firewall toggle, mints an API key with `firewall:read + blocklist:read + allowlist:read` scopes, and writes it into `.securenow/credentials.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 login, use `securenow api-key set snk_live_...` (see [API Key Management](#api-key-management) below).
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
- Credentials resolve in order: project `.securenow/credentials.json` -> project named runtime credentials in the fixed staging/production/preview/local/test/development/dev/prod order -> global `~/.securenow/credentials.json` -> global named runtime credentials in the same fixed order. 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.
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 the same credentials file as `apiKey`.
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/credentials.json`.
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/credentials.json` | `token`, `email`, `expiresAt`, `apiKey`, `app`, `config` (global, use `login --global`) |
83
- | `.securenow/credentials.json` | `token`, `email`, `expiresAt`, `apiKey`, `app`, `config`, `_securenow.explanations` (project-local default) |
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/credentials.json` (project) -> project named runtime credentials in the fixed staging/production/preview/local/test/development/dev/prod order -> `~/.securenow/credentials.json` (global) -> global named runtime credentials in the same fixed order. Legacy env fallbacks are disabled unless `SECURENOW_ENABLE_LEGACY_ENV=1` is set.
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.5.1+):** project `.securenow/credentials.json` -> project named runtime credentials in the fixed staging/production/preview/local/test/development/dev/prod order -> global `~/.securenow/credentials.json` -> global named runtime credentials in the same fixed order. Use `securenow login` for default setup or `securenow api-key set` to rotate a key without touching env vars.
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 # browser-based OAuth (stores in project ./.securenow/)
132
- securenow login --token <JWT> # headless / CI login
133
- securenow login --global # save credentials to ~/.securenow/ instead
134
- securenow logout # clear active credentials (local if present, else global)
135
- securenow logout --global # clear global credentials only
136
- securenow whoami # show email, user ID, API URL, auth source, expiry, default app
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 the credentials file. Since v7.5.1 the login flow writes `snk_live_...` keys to `.securenow/credentials.json` by default, so no env var is required for local dev.
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 login` with firewall enabled writes the key automatically; use `api-key create` when you have an account session but no runtime key yet, or `api-key set` when you already have a key from the dashboard.
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/credentials.json` exists, has secure defaults/explanations, and local credential JSON files are gitignored without ignoring the whole `.securenow/` directory
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/credentials.json` written by login; production should mount/copy the tokenless runtime file generated by `securenow credentials runtime --env production`
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 (v7.5.1+):** running `securenow login` enables the selected app's firewall toggle, auto-mints an API key (scoped `firewall:read + blocklist:read + allowlist:read`), and writes it to the credentials file 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.
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
- ```bash
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 Proxies
397
+ ### Trusted IPs
380
398
 
381
- ```bash
382
- securenow trusted # list trusted IPs
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/credentials.json`, and return non-zero on HTTP errors so CI/cron can detect failures.
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 credentials) |
585
- | `Not logged in` | No token found | `securenow login` or mount/copy the right `.securenow/credentials.json` |
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 and production are driven by ./.securenow/credentials.json.
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 written by `npx securenow login`. Secret: do not commit.',
94
- 'apiKey': 'Scoped firewall API key (`snk_live_...`) minted by login or `securenow api-key set`. Secret: do not commit.',
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': 'Secure default: app firewall enforcement starts when apiKey is present and the dashboard toggle is on.',
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 credentialRelativePaths() {
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 resolveLocalCredentialsFile() {
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 = credentialRelativePaths();
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 resolveGlobalCredentialsFile() {
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 credentialRelativePaths()) {
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
- const fromConfig = pick(getPath(loadCredentials()?.config, 'firewall.enabled'));
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/credentials.json' : '~/.securenow/credentials.json',
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/credentials.json (local)'
45
- : 'Stored in ~/.securenow/credentials.json (global)');
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/credentials.json (local)'
72
- : 'Stored in ~/.securenow/credentials.json (global)');
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 login` or `npx securenow api-key set snk_live_...` to refresh .securenow/credentials.json.');
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.getAuthSource()}`);
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 login` to pick interactively)')}`);
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/credentials.json no env var needed)')}`);
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.json so the SDK picks this app up with zero env vars.
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}`);