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/mcp/catalog.js CHANGED
@@ -10,14 +10,15 @@ const MARKDOWN = 'text/markdown';
10
10
  const UNIVERSAL_SECURENOW_SETUP_PROMPT = `You are working in an existing JavaScript or TypeScript app. Set up SecureNow end-to-end for the framework/runtime already used by this repo. Treat this as a real onboarding, not just a package install.
11
11
 
12
12
  Primary goals:
13
- - Use the latest published SecureNow npm package. Require securenow@7.5.1 or newer.
13
+ - Use the latest published SecureNow npm package. Require securenow@7.8.0 or newer for split admin/runtime credentials.
14
14
  - By default, enable tracing, logs, POST request body capture, multipart metadata capture, and the SecureNow firewall.
15
15
  - If I explicitly ask for firewall-only mode, keep the same install/login/verification gates, but use firewall-only preload and do not add tracing, logging, or OTel instrumentation.
16
- - The firewall must protect the selected SecureNow app, use SecureNow's own blocklist/allowlist/IPDB data, and respect that app's SecureNow IPDB confidence threshold. Do not add custom IP reputation providers or custom auto-blocking.
16
+ - The firewall must protect the selected SecureNow app, use SecureNow's own blocklist/allowlist/IPDB data, and respect that app's SecureNow IPDB confidence threshold. Do not add custom IP reputation providers or custom auto-blocking.
17
+ - Do not confuse IP Allowlist with Trusted IPs. IP Allowlist is restrictive deny-by-default: when any allowlist entry exists for an app/environment, only listed IPs can reach it and all other IPs are blocked. Use Trusted IPs for known-safe monitors, office/VPN traffic, or false-positive suppression. Only use allowlist after explicit human approval to lock the app/environment to known IPs.
17
18
 
18
19
  Safety rules:
19
- - Do not print full API keys, JWTs, tokens, or .securenow/credentials.json. Mask secrets.
20
- - Do not commit secrets. Ignore only local SecureNow credential files (.securenow/credentials.json and .securenow/credentials.*.json); keep the .securenow/ directory itself trackable for repo-owned docs/templates.
20
+ - Do not print full API keys, JWTs, tokens, or local SecureNow credential files (.securenow/admin.json, .securenow/runtime.json, legacy .securenow/credentials.json, or .securenow/credentials.*.json). Mask secrets.
21
+ - Do not commit secrets. Ignore only local SecureNow credential files (.securenow/admin.json, .securenow/runtime.json, .securenow/credentials.json, and .securenow/credentials.*.json); keep the .securenow/ directory itself trackable for repo-owned docs/templates.
21
22
  - Do not manually browse to a SecureNow auth URL. Always start auth with npx securenow login so the CLI generates the required callback and state.
22
23
  - If the browser says "Missing callback parameter", you opened the wrong URL: rerun npx securenow login from the project root.
23
24
  - Do not skip login, app selection, firewall connection, or verification unless I explicitly say to.
@@ -31,33 +32,35 @@ Runbook:
31
32
  2. Install or upgrade SecureNow with the detected package manager, using securenow@latest. Verify the actual installed version with:
32
33
  node -p "require('./node_modules/securenow/package.json').version"
33
34
  npx securenow version
34
- Stop and fix the install if either is below 7.5.1 or npx still resolves an older local package.
35
+ Stop and fix the install if either is below 7.8.0 or npx still resolves an older local package.
35
36
  3. Read the installed package surface before editing files: node_modules/securenow/package.json, README/NPM_README, SKILL-API, SKILL-CLI, npx securenow help, and relevant subcommand help for login/init/firewall/doctor/env/test-span/log/mcp.
36
- 4. Mandatory auth gate:
37
- - Run npx securenow whoami from the project root.
38
- - If not logged in, run npx securenow login from the project root and wait for the browser flow.
39
- - After the CLI exits, rerun npx securenow whoami.
40
- - Do not proceed to app edits or verification until whoami succeeds.
37
+ 4. Mandatory auth/runtime gate:
38
+ - Run npx securenow whoami from the project root.
39
+ - If admin auth is missing, run npx securenow admin login from the project root and wait for the browser flow.
40
+ - If runtime app config is missing, run npx securenow app connect from the project root and wait for the browser flow.
41
+ - After the CLI exits, rerun npx securenow whoami.
42
+ - Do not proceed to app edits or verification until whoami shows the required lane(s). SDK setup needs runtime app config; admin/global MCP operations need admin auth.
41
43
  5. Validate project-local credentials without exposing secrets:
42
- - Confirm .securenow/credentials.json exists.
43
- - Confirm it has SecureNow's default config/explanations block.
44
- - Confirm it has an app key/name/instance and a firewall API key after login/app selection.
45
- - Confirm .securenow/credentials.json and any .securenow/credentials.*.json runtime files are ignored by git, without ignoring the entire .securenow/ directory.
46
- 6. Run npx securenow init. If it fails with ui.header is not a function or another CLI bug, upgrade to securenow@latest, verify >=7.5.1, and retry. Do not silently ignore init failures.
44
+ - Confirm .securenow/runtime.json exists for SDK runtime setup, or legacy .securenow/credentials.json exists for old installs.
45
+ - Confirm the runtime file has SecureNow's default config/explanations block.
46
+ - Confirm the runtime file has an app key/name/instance and a firewall API key after app selection.
47
+ - Confirm .securenow/admin.json exists only when admin CLI/MCP auth is needed.
48
+ - Confirm .securenow/admin.json, .securenow/runtime.json, legacy .securenow/credentials.json, and any .securenow/credentials.*.json runtime files are ignored by git, without ignoring the entire .securenow/ directory.
49
+ 6. Run npx securenow init. If it fails with ui.header is not a function or another CLI bug, upgrade to securenow@latest, verify >=7.8.0, and retry. Do not silently ignore init failures.
47
50
  7. Configure the least invasive framework-specific integration:
48
51
  - Next.js: preserve instrumentation.js/ts. Register securenow/nextjs only when NEXT_RUNTIME is nodejs. In ESM files, use createRequire before require("securenow/nextjs"). Include require("securenow/nextjs-auto-capture") for body capture. For Next 15+, add securenow to serverExternalPackages. For older Next.js, use experimental.serverComponentsExternalPackages. Preserve proxy.js/middleware.js.
49
52
  - Nuxt/Nitro: use the documented securenow/nuxt module or Nitro server plugin.
50
53
  - Express/Fastify/NestJS/Koa/Hapi/Hono/raw Node: preload securenow/register through existing scripts, NODE_OPTIONS, PM2 node_args, Docker CMD, or the process manager already used.
51
54
  - Firewall-only: preload securenow/firewall-only or use the documented securenow run --firewall-only command. Do not add OTel/tracing/logging in this mode.
52
55
  - Vite/browser-only: use only documented browser integration and state that server firewall protection requires a server runtime.
53
- 8. Do not create or require a .env file for local development or production. The SDK reads defaults from .securenow/credentials.json:
56
+ 8. Do not create or require a .env file for local development or production. The SDK reads defaults from .securenow/runtime.json, with legacy .securenow/credentials.json and generated runtime credential files still supported:
54
57
  - config.logging.enabled: true
55
58
  - config.capture.body: true
56
59
  - config.capture.multipart: true
57
60
  - config.firewall.enabled: true
58
61
  - config.firewall.failMode: "open"
59
62
  - config.capture.maxBodySize: 10240
60
- For production, run npx securenow credentials runtime --env production, store the resulting JSON as a deployment secret file, and mount/copy it to <app-root>/.securenow/credentials.json. Do not recommend env vars unless the user explicitly asks for legacy fallbacks.
63
+ For production, run npx securenow credentials runtime --env production, store the resulting JSON as a deployment secret file, and mount/copy it to <app-root>/.securenow/credentials.json or <app-root>/.securenow/credentials.production.json. Do not recommend env vars unless the user explicitly asks for legacy fallbacks.
61
64
  Local credentials should use config.runtime.deploymentEnvironment="local". Production runtime credentials should use "production". The app key stays the same; traces, logs, firewall status, forensics, and CLI/MCP queries are scoped by environment.
62
65
  9. Verify firewall and threshold:
63
66
  - Run npx securenow firewall apps and npx securenow firewall status.
@@ -1108,19 +1111,22 @@ const TOOLS = [
1108
1111
  {
1109
1112
  name: 'securenow_blocklist_add',
1110
1113
  title: 'Add Blocked IP',
1111
- description: 'Add an IP/CIDR to the blocklist. Write action; requires confirmation.',
1114
+ description: 'Add an IP/CIDR to the blocklist, optionally scoped to a route/method. Write action; requires confirmation.',
1112
1115
  scope: 'blocklist:write',
1113
1116
  readOnly: false,
1114
1117
  confirm: true,
1115
1118
  method: 'POST',
1116
1119
  endpoint: '/blocklist',
1117
- bodyFields: ['ip', 'reason', 'expiresAt', 'metadata', 'appKey', 'environment'],
1120
+ bodyFields: ['ip', 'reason', 'expiresAt', 'metadata', 'appKey', 'environment', 'pathPattern', 'pathMatchMode', 'method'],
1118
1121
  inputSchema: objectSchema({
1119
1122
  ip: string('IPv4 address or CIDR.'),
1120
1123
  reason: string('Reason for blocking.'),
1121
1124
  expiresAt: string('Optional expiry time as ISO 8601.'),
1122
1125
  metadata: { type: 'object', additionalProperties: true, description: 'Optional metadata.' },
1123
1126
  appKey: string('Optional application key to scope this block. Omit for all apps.'),
1127
+ pathPattern: string('Optional route/path pattern, for example /admin*. Omit to block all routes.'),
1128
+ pathMatchMode: { type: 'string', enum: ['exact', 'prefix', 'regex'], description: 'Path matching mode. Defaults to prefix.' },
1129
+ method: { type: 'string', enum: ['ALL', 'GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'], description: 'HTTP method scope. Defaults to ALL.' },
1124
1130
  ...environmentInput,
1125
1131
  ...confirmSchema,
1126
1132
  }, ['ip', 'confirm', 'reason']),
@@ -1169,8 +1175,8 @@ const TOOLS = [
1169
1175
  },
1170
1176
  {
1171
1177
  name: 'securenow_allowlist_list',
1172
- title: 'List Allowlist',
1173
- description: 'List allowed IPs.',
1178
+ title: 'List Restrictive Allowlist',
1179
+ description: 'List restrictive allowlist entries. If any active entry exists for an app/environment, only listed IPs can reach it and all other IPs are blocked. This is not the Trusted IP list.',
1174
1180
  scope: 'allowlist:read',
1175
1181
  readOnly: true,
1176
1182
  method: 'GET',
@@ -1184,24 +1190,26 @@ const TOOLS = [
1184
1190
  },
1185
1191
  {
1186
1192
  name: 'securenow_allowlist_add',
1187
- title: 'Add Allowed IP',
1188
- description: 'Add an IP/CIDR to the allowlist. Write action; requires confirmation.',
1193
+ title: 'Add Restrictive Allowlist IP',
1194
+ description: 'Dangerous production action: add an IP/CIDR to the restrictive allowlist. Once active, allowlist mode blocks every IP except listed entries. Do not use this to mark an IP trusted or suppress false positives; use securenow_trusted_add instead. Requires explicit human approval confirming deny-all behavior.',
1189
1195
  scope: 'allowlist:write',
1190
1196
  readOnly: false,
1191
1197
  confirm: true,
1192
1198
  method: 'POST',
1193
1199
  endpoint: '/allowlist',
1194
- bodyFields: ['ip', 'label', 'reason', 'expiresAt', 'applicationsAll', 'applicationKeys', 'environment'],
1200
+ fixedBody: { initiatedBy: 'mcp' },
1201
+ bodyFields: ['ip', 'label', 'reason', 'expiresAt', 'applicationsAll', 'applicationKeys', 'environment', 'allowlistDenyAllApproved'],
1195
1202
  inputSchema: objectSchema({
1196
1203
  ip: string('IPv4 address or CIDR.'),
1197
1204
  label: string('Human-readable label.'),
1198
- reason: string('Reason for allowing.'),
1205
+ reason: string('Required human-approved reason. Must acknowledge that allowlist blocks all non-listed IPs and why Trusted IPs is not the correct action.'),
1199
1206
  expiresAt: string('Optional expiry time as ISO 8601.'),
1200
1207
  applicationsAll: boolean('Apply to all applications.'),
1201
1208
  applicationKeys: arrayOfStrings('Application keys to scope this allowlist entry to.'),
1209
+ allowlistDenyAllApproved: boolean('Must be true only after the user explicitly approves deny-by-default allowlist behavior. Do not set this for trusted IPs, monitors, false positives, or investigation cleanup.'),
1202
1210
  ...environmentInput,
1203
1211
  ...confirmSchema,
1204
- }, ['ip', 'confirm', 'reason']),
1212
+ }, ['ip', 'confirm', 'reason', 'allowlistDenyAllApproved']),
1205
1213
  },
1206
1214
  {
1207
1215
  name: 'securenow_allowlist_remove',
@@ -1221,7 +1229,7 @@ const TOOLS = [
1221
1229
  {
1222
1230
  name: 'securenow_trusted_list',
1223
1231
  title: 'List Trusted IPs',
1224
- description: 'List trusted IPs.',
1232
+ description: 'List trusted IPs. Trusted IPs are for known-safe traffic and do not turn on deny-by-default allowlist mode.',
1225
1233
  scope: 'trusted_ips:read',
1226
1234
  readOnly: true,
1227
1235
  method: 'GET',
@@ -1235,7 +1243,7 @@ const TOOLS = [
1235
1243
  {
1236
1244
  name: 'securenow_trusted_add',
1237
1245
  title: 'Add Trusted IP',
1238
- description: 'Add a trusted IP/CIDR. Write action; requires confirmation.',
1246
+ description: 'Add a trusted IP/CIDR for known-safe infrastructure, monitors, office/VPN traffic, or scoped false-positive suppression. This does not enable deny-by-default allowlist mode and is the correct tool when an IP should be trusted without blocking other visitors. Write action; requires confirmation.',
1239
1247
  scope: 'trusted_ips:write',
1240
1248
  readOnly: false,
1241
1249
  confirm: true,
@@ -1417,7 +1425,7 @@ function promptMessages(name, args = {}) {
1417
1425
  'Verify SecureNow default-on protection for this project.',
1418
1426
  args.appKey ? `App key: ${args.appKey}` : null,
1419
1427
  'Check npx securenow whoami, npx securenow api-key show, npx securenow firewall apps, and npx securenow firewall status.',
1420
- 'Confirm traces, logs, capture.body, capture.multipart, and firewall.enabled are enabled by .securenow/credentials.json defaults unless explicitly set false.',
1428
+ 'Confirm traces, logs, capture.body, capture.multipart, and firewall.enabled are enabled by .securenow/runtime.json defaults unless explicitly set false. Legacy .securenow/credentials.json is still accepted.',
1421
1429
  'Do not print full tokens or API keys.',
1422
1430
  ].filter(Boolean).join('\n'),
1423
1431
  },
@@ -1436,7 +1444,7 @@ function promptMessages(name, args = {}) {
1436
1444
  args.appKeys ? `Scope to app keys: ${args.appKeys}` : null,
1437
1445
  `Environment scope: ${args.environment || 'production'}.`,
1438
1446
  'Use IP intelligence first, then related traces/logs, then recommend remediation.',
1439
- 'Only block, allow, or trust the IP after explicit user confirmation.',
1447
+ 'Only block or trust the IP after explicit user confirmation. Never use IP Allowlist for false positives or trusted traffic; allowlist is deny-by-default and blocks all non-listed IPs.',
1440
1448
  ].filter(Boolean).join('\n'),
1441
1449
  },
1442
1450
  },
@@ -1461,6 +1469,7 @@ function promptMessages(name, args = {}) {
1461
1469
  'Return one clear outcome: Block IP, Rate Limit, False Positive, Rule Tuning Needed, or Ambiguous. If evidence is ambiguous, stop and explain what is missing.',
1462
1470
  'Use rate limiting only as temporary soft remediation for repeated route-specific abuse such as login brute force, credential stuffing, scraping/API bursts, enumeration, recon/probing, or repeated noisy payloads where risk/impact evidence is below the block threshold.',
1463
1471
  'Do not rate-limit instead of blocking when there is confirmed exploit success, token/data exposure, SSRF reachability, file read, RCE, persistence, malware/C2, or riskScore >= 85 with high-confidence malicious evidence. Do not rate-limit benign false positives, trusted monitors, app-server/proxy attribution problems, isolated one-off requests, or broad noisy rules that need alert tuning.',
1472
+ 'Do not create or recommend IP Allowlist entries during investigations unless the user explicitly asks to lock the app/environment to a known set of IPs. For safe monitors, offices, VPNs, or false positives, use Trusted IPs or scoped false-positive exclusions instead.',
1464
1473
  confirmWrites
1465
1474
  ? 'The user requested execution. If evidence supports the decision, call securenow_human_action_block, securenow_rate_limit_create_from_text plus securenow_human_action_decision_report_add(outcome=rate_limited), or securenow_human_action_false_positive with confirm:true, a precise reason, and a decisionReport containing summary, evidence, reviewedHistory, traceIds, and missingProof when relevant. If you skip/mark ambiguous but still need to record the audit trail, call securenow_human_action_decision_report_add.'
1466
1475
  : 'Do not execute write tools yet. Prepare the recommended decision and exact tool call the user can approve.',
@@ -1487,6 +1496,7 @@ function promptMessages(name, args = {}) {
1487
1496
  'For each row choose exactly one outcome: Block IP, Rate Limit, False Positive, Rule Tuning Needed, or Skip because evidence is insufficient. Explain skipped rows.',
1488
1497
  'Use Rate Limit only for repeated route-specific abuse where temporary friction is safer than blocking: login brute force/credential stuffing, password reset/account enumeration, scraping/API bursts, path or ID enumeration, recon/probing, or repeated noisy payloads without confirmed exploit success.',
1489
1498
  'Do not rate-limit confirmed high-risk attacks that should be blocked, benign traffic that should be false-positive scoped, broad noisy rules that need tuning, app-server/proxy attribution problems, or isolated one-off requests.',
1499
+ 'Do not create or recommend IP Allowlist entries while working this queue unless the user explicitly approves deny-by-default allowlist mode for the whole app/environment. Use Trusted IPs for trusted/bypass cases.',
1490
1500
  confirmWrites
1491
1501
  ? 'The user requested execution. For supported decisions, call the correct write tool with confirm:true, a precise reason, and a decisionReport containing summary, evidence, reviewedHistory, traceIds, and missingProof when relevant, then continue. For skipped/ambiguous/rule-tuning-needed rows that should be auditable without changing IP status, use securenow_human_action_decision_report_add.'
1492
1502
  : 'Do not execute write tools yet. Produce a row-by-row action plan and exact MCP write calls for user approval.',
@@ -1595,6 +1605,9 @@ function assertConfirmed(tool, args = {}) {
1595
1605
  if (!args.reason || !String(args.reason).trim()) {
1596
1606
  throw new Error(`${tool.name} requires a non-empty reason.`);
1597
1607
  }
1608
+ if (tool.name === 'securenow_allowlist_add' && args.allowlistDenyAllApproved !== true) {
1609
+ throw new Error('securenow_allowlist_add is deny-by-default: it blocks every non-listed IP for the scoped app/environment. Pass allowlistDenyAllApproved:true only after explicit human approval. Use securenow_trusted_add for trusted IPs or false positives.');
1610
+ }
1598
1611
  }
1599
1612
 
1600
1613
  function buildApiRequest(tool, rawArgs = {}) {
package/mcp/server.js CHANGED
@@ -43,29 +43,83 @@ function fail(id, code, message, data) {
43
43
  });
44
44
  }
45
45
 
46
- function bearerToken() {
47
- return config.getToken() || config.getApiKey();
46
+ const RUNTIME_READ_SCOPES = new Set(['firewall:read', 'blocklist:read', 'allowlist:read']);
47
+
48
+ function toolCanUseRuntimeApiKey(tool) {
49
+ return tool && tool.readOnly !== false && RUNTIME_READ_SCOPES.has(tool.scope);
50
+ }
51
+
52
+ function withRuntimeAppDefaults(tool, args = {}) {
53
+ const runtimeApp = config.getApp();
54
+ if (!runtimeApp?.key) return args;
55
+ const next = { ...args };
56
+ const fields = new Set([
57
+ ...(tool.queryFields || []),
58
+ ...(tool.bodyFields || []),
59
+ ...(tool.pathParams || []),
60
+ ]);
61
+
62
+ if (fields.has('appKey') && !next.appKey) next.appKey = runtimeApp.key;
63
+ if (fields.has('applicationKey') && !next.applicationKey) next.applicationKey = runtimeApp.key;
64
+ if (fields.has('appKeys') && !next.appKeys) next.appKeys = runtimeApp.key;
65
+ return next;
66
+ }
67
+
68
+ function requiresExplicitOrRuntimeApp(tool) {
69
+ const required = new Set(tool?.inputSchema?.required || []);
70
+ return required.has('appKey') || required.has('appKeys') || required.has('applicationKey');
71
+ }
72
+
73
+ function hasAppScopeArg(args = {}) {
74
+ return !!(args.appKey || args.appKeys || args.applicationKey);
75
+ }
76
+
77
+ function bearerForTool(tool) {
78
+ const adminToken = config.getToken();
79
+ if (adminToken) return { token: adminToken, plane: 'admin' };
80
+
81
+ const runtimeKey = config.getApiKey();
82
+ if (runtimeKey && toolCanUseRuntimeApiKey(tool)) {
83
+ return { token: runtimeKey, plane: 'runtime' };
84
+ }
85
+
86
+ return { token: null, plane: toolCanUseRuntimeApiKey(tool) ? 'runtime' : 'admin' };
48
87
  }
49
88
 
50
89
  function localAuthStatus() {
51
90
  const cfg = config.loadConfig();
52
91
  const app = config.getApp();
92
+ const admin = config.loadAdminCredentials();
93
+ const runtime = config.loadRuntimeCredentials();
53
94
  const token = config.getToken();
54
95
  const apiKey = config.getApiKey();
55
96
  const hasBearer = !!(token || apiKey);
56
- const runtimeFirewallWarning = token && !apiKey
57
- ? 'MCP is authenticated with your SecureNow session token. Runtime firewall enforcement key is missing. Run `npx securenow login` or `npx securenow api-key set snk_live_...` to refresh .securenow/credentials.json.'
97
+ const runtimeFirewallWarning = app && !apiKey
98
+ ? 'Runtime app is connected, but the firewall key is missing. Run `npx securenow app connect` or `npx securenow api-key set snk_live_...` to refresh .securenow/runtime.json.'
58
99
  : null;
59
100
  return {
60
101
  authenticated: hasBearer,
102
+ adminAuthenticated: !!token,
103
+ runtimeConnected: !!app?.key,
61
104
  sessionTokenAvailable: !!token,
62
105
  apiKeyAvailable: !!apiKey,
63
106
  runtimeFirewallKeyAvailable: !!apiKey,
64
- authSource: config.getAuthSource(),
107
+ adminAuthSource: token ? config.getAuthSource() : null,
108
+ runtimeSource: config.getRuntimeSource(),
65
109
  apiUrl: config.getApiUrl(),
66
110
  appUrl: config.getAppUrl(),
67
111
  defaultApp: config.getDefaultApp(),
68
112
  app: app || null,
113
+ admin: token ? {
114
+ email: admin.email || null,
115
+ expiresAt: admin.expiresAt || null,
116
+ systemRuleAdmin: 'server-enforced',
117
+ } : null,
118
+ runtime: {
119
+ app: runtime.app || null,
120
+ environment: runtime.config?.runtime?.deploymentEnvironment || null,
121
+ apiKeyAvailable: !!apiKey,
122
+ },
69
123
  token: token ? maskSecret(token) : null,
70
124
  apiKey: apiKey ? maskSecret(apiKey) : null,
71
125
  config: {
@@ -76,10 +130,10 @@ function localAuthStatus() {
76
130
  },
77
131
  warnings: runtimeFirewallWarning ? [runtimeFirewallWarning] : [],
78
132
  nextStep: token
79
- ? runtimeFirewallWarning
133
+ ? (runtimeFirewallWarning || (!app?.key ? 'Run `npx securenow app connect` to connect SDK runtime to an app.' : null))
80
134
  : apiKey
81
- ? 'Using a scoped API key. Run `npx securenow login` for account-wide MCP tools.'
82
- : 'Run `npx securenow login` from the project root.',
135
+ ? 'Using runtime API key for limited runtime read tools. Run `npx securenow admin login` for account-wide MCP tools.'
136
+ : 'Run `npx securenow admin login` for control-plane tools and `npx securenow app connect` for SDK runtime.',
83
137
  };
84
138
  }
85
139
 
@@ -94,12 +148,19 @@ async function callApiTool(tool, args) {
94
148
  throw new Error(`${tool.name} is only available in the local MCP server.`);
95
149
  }
96
150
 
97
- const token = bearerToken();
151
+ const finalArgs = withRuntimeAppDefaults(tool, args);
152
+ if (requiresExplicitOrRuntimeApp(tool) && !hasAppScopeArg(finalArgs)) {
153
+ throw new Error(`${tool.name} requires an app scope. Pass applicationKey/appKey/appKeys explicitly or run \`npx securenow app connect\` to configure runtime app credentials.`);
154
+ }
155
+ const { token, plane } = bearerForTool(tool);
98
156
  if (!token) {
99
- throw new Error('Not authenticated. Run `npx securenow login` from the project root, then retry.');
157
+ if (plane === 'runtime') {
158
+ throw new Error('Runtime credentials are missing. Run `npx securenow app connect` or pass an explicit app key, then retry.');
159
+ }
160
+ throw new Error('Admin auth is missing. Run `npx securenow admin login` from the project root, then retry. Runtime app credentials cannot grant admin/control-plane permissions.');
100
161
  }
101
162
 
102
- const request = buildApiRequest(tool, args);
163
+ const request = buildApiRequest(tool, finalArgs);
103
164
  const options = {
104
165
  token,
105
166
  ...(Object.keys(request.query || {}).length > 0 ? { query: request.query } : {}),
@@ -197,7 +258,7 @@ async function handleRequest(message) {
197
258
  const result = await callApiTool(tool, args);
198
259
  return ok(id, asToolResult({
199
260
  tool: name,
200
- arguments: sanitizeArgs(args),
261
+ arguments: sanitizeArgs(withRuntimeAppDefaults(tool, args)),
201
262
  result,
202
263
  }));
203
264
  }
package/nextjs.js CHANGED
@@ -31,10 +31,13 @@
31
31
  */
32
32
 
33
33
  const { randomUUID } = require('crypto');
34
+ const { defaultMetricsExporterToNone } = require('./otel-defaults');
34
35
  const appConfig = require('./app-config');
35
36
  const { resolveClientIpWithDetails } = require('./resolve-ip');
36
37
  const otelResources = require('@opentelemetry/resources');
37
38
 
39
+ defaultMetricsExporterToNone();
40
+
38
41
  let isRegistered = false;
39
42
 
40
43
  function requireRuntimeModule(name) {
@@ -659,7 +662,7 @@ function registerSecureNow(options = {}) {
659
662
  // Key and environment come from .securenow/credentials.json (written by
660
663
  // login/init or credentials runtime), so no .env entry is needed.
661
664
  const firewallOptions = appConfig.resolveFirewallOptions();
662
- if (firewallOptions.apiKey && firewallOptions.enabled) {
665
+ if (firewallOptions.apiKey) {
663
666
  try {
664
667
  requireRuntimeModule('./firewall').init({
665
668
  apiKey: firewallOptions.apiKey,
@@ -7,11 +7,8 @@
7
7
  * This file is registered by the Nuxt module (nuxt.mjs) via addServerPlugin.
8
8
  */
9
9
 
10
- import { NodeSDK } from '@opentelemetry/sdk-node';
11
- import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
12
10
  import * as otelResources from '@opentelemetry/resources';
13
11
  import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';
14
- import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
15
12
  import {
16
13
  context as otelContext,
17
14
  trace as otelTrace,
@@ -23,6 +20,12 @@ import { randomUUID } from 'node:crypto';
23
20
  const nodeRequire = createRequire(import.meta.url);
24
21
  const appConfig = nodeRequire('./app-config');
25
22
  const { resolveClientIpWithDetails } = nodeRequire('./resolve-ip');
23
+ const { defaultMetricsExporterToNone } = nodeRequire('./otel-defaults');
24
+ defaultMetricsExporterToNone();
25
+
26
+ const { NodeSDK } = nodeRequire('@opentelemetry/sdk-node');
27
+ const { OTLPTraceExporter } = nodeRequire('@opentelemetry/exporter-trace-otlp-http');
28
+ const { HttpInstrumentation } = nodeRequire('@opentelemetry/instrumentation-http');
26
29
 
27
30
  // ── Helpers ──
28
31
 
@@ -318,7 +321,7 @@ export default defineNitroPlugin(async (nitroApp) => {
318
321
 
319
322
  // ── Firewall — runs independently from OTel ──
320
323
  const firewallOptions = appConfig.resolveFirewallOptions();
321
- if (firewallOptions.apiKey && firewallOptions.enabled) {
324
+ if (firewallOptions.apiKey) {
322
325
  try {
323
326
  const { init: fwInit } = await import('./firewall.js');
324
327
  fwInit({
@@ -0,0 +1,11 @@
1
+ 'use strict';
2
+
3
+ function defaultMetricsExporterToNone(env = process.env) {
4
+ if (!env) return false;
5
+ const current = env.OTEL_METRICS_EXPORTER;
6
+ if (current != null && String(current).trim() !== '') return false;
7
+ env.OTEL_METRICS_EXPORTER = 'none';
8
+ return true;
9
+ }
10
+
11
+ module.exports = { defaultMetricsExporterToNone };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securenow",
3
- "version": "7.7.16",
3
+ "version": "7.8.1",
4
4
  "description": "OpenTelemetry instrumentation for Node.js, Next.js, and Nuxt - Send traces and logs to any OTLP-compatible backend",
5
5
  "type": "commonjs",
6
6
  "main": "register.js",
@@ -132,6 +132,7 @@
132
132
  "resolve-ip.js",
133
133
  "cidr.js",
134
134
  "firewall.js",
135
+ "otel-defaults.js",
135
136
  "rate-limits.js",
136
137
  "rate-limits.d.ts",
137
138
  "firewall-only.js",
package/tracing.js CHANGED
@@ -14,6 +14,9 @@
14
14
  * Production should mount/copy tokenless runtime credentials to the same path.
15
15
  */
16
16
 
17
+ const { defaultMetricsExporterToNone } = require('./otel-defaults');
18
+ defaultMetricsExporterToNone();
19
+
17
20
  const { diag, DiagConsoleLogger, DiagLogLevel, context, trace } = require('@opentelemetry/api');
18
21
  const { NodeSDK } = require('@opentelemetry/sdk-node');
19
22
  const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http');
@@ -696,7 +699,7 @@ const sdk = new NodeSDK({
696
699
  // resolveApiKey() enforces the prefix, so we skip cleanly when the app has
697
700
  // only an app-routing UUID (or nothing at all) — no 401 polling loops.
698
701
  const firewallOptions = appConfig.resolveFirewallOptions();
699
- if (firewallOptions.apiKey && firewallOptions.enabled) {
702
+ if (firewallOptions.apiKey) {
700
703
  require('./firewall').init({
701
704
  apiKey: firewallOptions.apiKey,
702
705
  appKey: firewallOptions.appKey,