@newrelic/preflight 1.67.2 → 1.68.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/README.md +2 -1
  2. package/dist/config.d.ts +57 -2
  3. package/dist/config.d.ts.map +1 -1
  4. package/dist/config.js +127 -18
  5. package/dist/config.js.map +1 -1
  6. package/dist/dashboard/routes/api-handler.d.ts.map +1 -1
  7. package/dist/dashboard/routes/api-handler.js +13 -5
  8. package/dist/dashboard/routes/api-handler.js.map +1 -1
  9. package/dist/hooks/event-processor.d.ts.map +1 -1
  10. package/dist/hooks/event-processor.js +7 -4
  11. package/dist/hooks/event-processor.js.map +1 -1
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +90 -121
  14. package/dist/index.js.map +1 -1
  15. package/dist/install/cli.d.ts.map +1 -1
  16. package/dist/install/cli.js +16 -1
  17. package/dist/install/cli.js.map +1 -1
  18. package/dist/install/dashboard-health.js +1 -1
  19. package/dist/install/dashboard-health.js.map +1 -1
  20. package/dist/install/diagnostics.d.ts +2 -0
  21. package/dist/install/diagnostics.d.ts.map +1 -1
  22. package/dist/install/diagnostics.js +117 -27
  23. package/dist/install/diagnostics.js.map +1 -1
  24. package/dist/install/headless-install.d.ts +26 -0
  25. package/dist/install/headless-install.d.ts.map +1 -1
  26. package/dist/install/headless-install.js +44 -1
  27. package/dist/install/headless-install.js.map +1 -1
  28. package/dist/install/install-helper.d.ts +2 -0
  29. package/dist/install/install-helper.d.ts.map +1 -1
  30. package/dist/install/install-helper.js +5 -0
  31. package/dist/install/install-helper.js.map +1 -1
  32. package/dist/lib/child-env.d.ts +11 -0
  33. package/dist/lib/child-env.d.ts.map +1 -0
  34. package/dist/lib/child-env.js +35 -0
  35. package/dist/lib/child-env.js.map +1 -0
  36. package/dist/lib/git-remote.d.ts +7 -0
  37. package/dist/lib/git-remote.d.ts.map +1 -1
  38. package/dist/lib/git-remote.js +13 -0
  39. package/dist/lib/git-remote.js.map +1 -1
  40. package/dist/metrics/cost-estimate-gate.d.ts +20 -0
  41. package/dist/metrics/cost-estimate-gate.d.ts.map +1 -1
  42. package/dist/metrics/cost-estimate-gate.js +21 -1
  43. package/dist/metrics/cost-estimate-gate.js.map +1 -1
  44. package/dist/metrics/cost-tracker-seed.d.ts.map +1 -1
  45. package/dist/metrics/cost-tracker-seed.js +1 -0
  46. package/dist/metrics/cost-tracker-seed.js.map +1 -1
  47. package/dist/metrics/cost-tracker.d.ts +53 -1
  48. package/dist/metrics/cost-tracker.d.ts.map +1 -1
  49. package/dist/metrics/cost-tracker.js +87 -2
  50. package/dist/metrics/cost-tracker.js.map +1 -1
  51. package/dist/metrics/local-session-aggregator.d.ts +13 -0
  52. package/dist/metrics/local-session-aggregator.d.ts.map +1 -1
  53. package/dist/metrics/local-session-aggregator.js +22 -4
  54. package/dist/metrics/local-session-aggregator.js.map +1 -1
  55. package/dist/metrics/model-pricing.d.ts +6 -0
  56. package/dist/metrics/model-pricing.d.ts.map +1 -1
  57. package/dist/metrics/model-pricing.js +19 -2
  58. package/dist/metrics/model-pricing.js.map +1 -1
  59. package/dist/metrics/pricing-init.d.ts +13 -0
  60. package/dist/metrics/pricing-init.d.ts.map +1 -0
  61. package/dist/metrics/pricing-init.js +19 -0
  62. package/dist/metrics/pricing-init.js.map +1 -0
  63. package/dist/metrics/pricing-overlay.d.ts.map +1 -1
  64. package/dist/metrics/pricing-overlay.js +4 -5
  65. package/dist/metrics/pricing-overlay.js.map +1 -1
  66. package/dist/proxy/proxy-manager.d.ts +3 -0
  67. package/dist/proxy/proxy-manager.d.ts.map +1 -1
  68. package/dist/proxy/proxy-manager.js +43 -3
  69. package/dist/proxy/proxy-manager.js.map +1 -1
  70. package/dist/proxy/types.d.ts +7 -0
  71. package/dist/proxy/types.d.ts.map +1 -1
  72. package/dist/proxy/types.js.map +1 -1
  73. package/dist/proxy/upstream-http.d.ts.map +1 -1
  74. package/dist/proxy/upstream-http.js +44 -1
  75. package/dist/proxy/upstream-http.js.map +1 -1
  76. package/dist/proxy/upstream-stdio.d.ts +0 -1
  77. package/dist/proxy/upstream-stdio.d.ts.map +1 -1
  78. package/dist/proxy/upstream-stdio.js +2 -26
  79. package/dist/proxy/upstream-stdio.js.map +1 -1
  80. package/dist/redaction-patterns.d.ts.map +1 -1
  81. package/dist/redaction-patterns.js +1 -0
  82. package/dist/redaction-patterns.js.map +1 -1
  83. package/dist/secret-command.d.ts +57 -0
  84. package/dist/secret-command.d.ts.map +1 -0
  85. package/dist/secret-command.js +117 -0
  86. package/dist/secret-command.js.map +1 -0
  87. package/dist/security/audit-trail.d.ts +7 -1
  88. package/dist/security/audit-trail.d.ts.map +1 -1
  89. package/dist/security/audit-trail.js +24 -0
  90. package/dist/security/audit-trail.js.map +1 -1
  91. package/dist/server.d.ts.map +1 -1
  92. package/dist/server.js +9 -4
  93. package/dist/server.js.map +1 -1
  94. package/dist/storage/historical-costs.d.ts +6 -0
  95. package/dist/storage/historical-costs.d.ts.map +1 -0
  96. package/dist/storage/historical-costs.js +42 -0
  97. package/dist/storage/historical-costs.js.map +1 -0
  98. package/dist/storage/local-store.d.ts +6 -4
  99. package/dist/storage/local-store.d.ts.map +1 -1
  100. package/dist/storage/local-store.js +21 -7
  101. package/dist/storage/local-store.js.map +1 -1
  102. package/dist/storage/session-store.d.ts +23 -1
  103. package/dist/storage/session-store.d.ts.map +1 -1
  104. package/dist/storage/session-store.js +100 -8
  105. package/dist/storage/session-store.js.map +1 -1
  106. package/dist/storage/types.d.ts +7 -11
  107. package/dist/storage/types.d.ts.map +1 -1
  108. package/dist/storage/types.js.map +1 -1
  109. package/dist/tools/cross-session-tools.d.ts +7 -1
  110. package/dist/tools/cross-session-tools.d.ts.map +1 -1
  111. package/dist/tools/cross-session-tools.js +47 -14
  112. package/dist/tools/cross-session-tools.js.map +1 -1
  113. package/dist/tools/session-stats.d.ts +6 -0
  114. package/dist/tools/session-stats.d.ts.map +1 -1
  115. package/dist/tools/session-stats.js +2 -1
  116. package/dist/tools/session-stats.js.map +1 -1
  117. package/dist/transport/audit-record-sender.d.ts +20 -0
  118. package/dist/transport/audit-record-sender.d.ts.map +1 -0
  119. package/dist/transport/audit-record-sender.js +54 -0
  120. package/dist/transport/audit-record-sender.js.map +1 -0
  121. package/dist/transport/nr-ingest.d.ts +3 -0
  122. package/dist/transport/nr-ingest.d.ts.map +1 -1
  123. package/dist/transport/nr-ingest.js +21 -8
  124. package/dist/transport/nr-ingest.js.map +1 -1
  125. package/dist/transport/unforwarded-session-monitor.d.ts +12 -6
  126. package/dist/transport/unforwarded-session-monitor.d.ts.map +1 -1
  127. package/dist/transport/unforwarded-session-monitor.js +24 -5
  128. package/dist/transport/unforwarded-session-monitor.js.map +1 -1
  129. package/package.json +21 -8
package/README.md CHANGED
@@ -77,7 +77,7 @@ See cost breakdown, efficiency scoring, anti-patterns, and live session tracking
77
77
  npm install -g @newrelic/preflight
78
78
  ```
79
79
 
80
- > **Using [Smithery](https://smithery.ai)?** Installing Preflight from the Smithery MCP registry wires up the MCP server for you, but Smithery has no mechanism to write Claude Code hooks. After install, ask Claude Code to call the `nr_observe_install_hooks` MCP tool (it will offer to do this automatically once `nr_observe_health` reports `setup_required: true`), then restart Claude Code to activate monitoring.
80
+ > **Using [Smithery](https://smithery.ai)?** Installing Preflight from the Smithery MCP registry wires up the MCP server for you, but Smithery has no mechanism to write Claude Code hooks. After install, either run `preflight install` in a terminal, or set `"allowHeadlessInstall": true` in your Preflight config (or `NEW_RELIC_AI_ALLOW_HEADLESS_INSTALL=true`) and ask Claude Code to call the `nr_observe_install_hooks` MCP tool. The tool refuses by default, because any connected MCP client can call it. With the flag on, it offers to do this automatically once `nr_observe_health` reports `setup_required: true`. Then restart Claude Code to activate monitoring.
81
81
 
82
82
  ### 2. Run setup
83
83
 
@@ -218,6 +218,7 @@ Add `--project` to `install`/`uninstall` to scope changes to the current directo
218
218
  - [**SCORECARDS.md**](docs/SCORECARDS.md) — New Relic Scorecard rules for team attribution
219
219
  - [**ARCHITECTURE.md**](docs/ARCHITECTURE.md) — Data flow, component reference, and operating modes
220
220
  - [**ADAPTERS.md**](docs/ADAPTERS.md) — Per-platform integration mechanism, setup steps, and known gaps
221
+ - [**FLEET_DEPLOYMENT.md**](docs/FLEET_DEPLOYMENT.md) — Roll out the Claude Code plugin to many machines with managed settings and MDM
221
222
  - [**TROUBLESHOOTING.md**](docs/TROUBLESHOOTING.md) — Common setup and connection problems, and how to fix them
222
223
  - [**CONTRIBUTING.md**](CONTRIBUTING.md) — Development, testing, submitting PRs
223
224
  - [**SECURITY.md**](./SECURITY.md) — Security guidelines and best practices
package/dist/config.d.ts CHANGED
@@ -4,6 +4,8 @@ import type { CliOptions } from './types.js';
4
4
  import type { UpstreamConfig } from './proxy/types.js';
5
5
  import type { PersonalAlertThresholds } from './alerts/types.js';
6
6
  import type { ResolvedTier } from './transport/tier-types.js';
7
+ import { SecretCommandError } from './secret-command.js';
8
+ import type { SecretSources } from './secret-command.js';
7
9
  export declare const VALID_MODES: readonly ["cloud", "local", "both"];
8
10
  export type Mode = (typeof VALID_MODES)[number];
9
11
  export type CompanionModeSource = 'env NR_AI_COMPANION_MODE' | 'config file' | 'detected Claude Code OTel export' | 'default';
@@ -43,6 +45,14 @@ export interface McpServerConfig {
43
45
  * (session_id === OTel's session.id). See docs/ADVANCED.md § Companion mode.
44
46
  */
45
47
  readonly companionMode: boolean;
48
+ /**
49
+ * Lets the `nr_observe_install_hooks` MCP tool rewrite ~/.claude/settings.json.
50
+ * Default false: any connected MCP client can call the tool, so it is opt-in.
51
+ * See docs/ADVANCED.md § allowHeadlessInstall.
52
+ */
53
+ readonly allowHeadlessInstall: boolean;
54
+ /** Source of the resolved companionMode value (env, config file, detected, or default). */
55
+ readonly companionModeSource: CompanionModeSource;
46
56
  readonly redactionPatterns: readonly RegExp[];
47
57
  readonly hookBufferPath: string;
48
58
  readonly storagePath: string;
@@ -58,6 +68,8 @@ export interface McpServerConfig {
58
68
  readonly collectorHost: string | null;
59
69
  readonly proxyUpstreams: readonly UpstreamConfig[];
60
70
  readonly nrApiKey: string | null;
71
+ /** Which source supplied each secret. Never the value. */
72
+ readonly secretSources: SecretSources;
61
73
  /** Path to a custom pricing JSON file, applied via `initPricing()`. See `NEW_RELIC_AI_CUSTOM_PRICING_FILE`. */
62
74
  readonly customPricingFile: string | null;
63
75
  /**
@@ -154,6 +166,7 @@ export interface McpServerConfig {
154
166
  }
155
167
  export declare const DEFAULT_STORAGE_PATH: string;
156
168
  export type PlatformTarget = 'native' | 'wsl-windows-cc' | 'wsl-linux-cc';
169
+ export declare const SecretCommandSchema: z.ZodArray<z.ZodString>;
157
170
  export declare const ConfigFileSchema: z.ZodObject<{
158
171
  configVersion: z.ZodOptional<z.ZodNumber>;
159
172
  licenseKey: z.ZodOptional<z.ZodString>;
@@ -171,6 +184,7 @@ export declare const ConfigFileSchema: z.ZodObject<{
171
184
  recordContent: z.ZodOptional<z.ZodBoolean>;
172
185
  pricingRefresh: z.ZodOptional<z.ZodBoolean>;
173
186
  companionMode: z.ZodOptional<z.ZodBoolean>;
187
+ allowHeadlessInstall: z.ZodOptional<z.ZodBoolean>;
174
188
  storagePath: z.ZodOptional<z.ZodString>;
175
189
  hookBufferPath: z.ZodOptional<z.ZodString>;
176
190
  harvestEventsMs: z.ZodOptional<z.ZodNumber>;
@@ -189,6 +203,9 @@ export declare const ConfigFileSchema: z.ZodObject<{
189
203
  proxyUpstreams: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
190
204
  tiers: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
191
205
  nrApiKey: z.ZodOptional<z.ZodNullable<z.ZodString>>;
206
+ licenseKeyCommand: z.ZodOptional<z.ZodArray<z.ZodString>>;
207
+ nrApiKeyCommand: z.ZodOptional<z.ZodArray<z.ZodString>>;
208
+ digestWebhookUrlCommand: z.ZodOptional<z.ZodArray<z.ZodString>>;
192
209
  customPricingFile: z.ZodOptional<z.ZodNullable<z.ZodString>>;
193
210
  costRateMultiplier: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
194
211
  dataResidencyPremium: z.ZodOptional<z.ZodBoolean>;
@@ -273,7 +290,32 @@ export declare class MissingCloudCredentialError extends Error {
273
290
  readonly mode: Mode;
274
291
  constructor(missingField: 'licenseKey' | 'accountId', mode: Mode, configFilePath: string);
275
292
  }
276
- export declare function loadMcpConfig(cliOptions?: Partial<CliOptions>): Readonly<McpServerConfig>;
293
+ /**
294
+ * A `SecretCommandError` raised while resolving a secret that cloud export
295
+ * needs. It keeps the helper's own message for stdio users and adds the mode
296
+ * the config asked for, so `--local` can tell a failing helper from a missing
297
+ * credential and still fall back to local mode.
298
+ */
299
+ export declare class CloudSecretCommandError extends SecretCommandError {
300
+ readonly mode: Mode;
301
+ constructor(original: SecretCommandError, mode: Mode);
302
+ }
303
+ /**
304
+ * The startup-resolved digest webhook URL when a source that outranks the
305
+ * file's `digestWebhookUrl` (env var or digestWebhookUrlCommand) supplied it.
306
+ * Undefined means "read the file at send time".
307
+ */
308
+ export declare function digestWebhookUrlOverride(config: Pick<McpServerConfig, 'digestWebhookUrl' | 'secretSources'>): string | undefined;
309
+ export interface LoadMcpConfigOptions {
310
+ /**
311
+ * Run the `*Command` secret helpers. Pass false from callers that poll or
312
+ * only read non-secret fields (dashboard diagnostics, host/port lookup):
313
+ * `secretSources` still reports `command`, but the secrets resolve to
314
+ * undefined/null and `tiers` stays empty unless explicit.
315
+ */
316
+ readonly resolveSecrets?: boolean;
317
+ }
318
+ export declare function loadMcpConfig(cliOptions?: Partial<CliOptions>, loadOptions?: LoadMcpConfigOptions): Readonly<McpServerConfig>;
277
319
  export interface ConfigValidationResult {
278
320
  readonly filePath: string;
279
321
  readonly fileExists: boolean;
@@ -285,7 +327,7 @@ export interface ConfigValidationResult {
285
327
  readonly storagePath?: string;
286
328
  /** The `companionMode` field from the parsed config, if present and a boolean. */
287
329
  readonly companionMode?: boolean;
288
- /** True when a non-blank licenseKey is present in the parsed config. Raw value is intentionally not exposed. */
330
+ /** True when a non-blank licenseKey or a licenseKeyCommand is present in the parsed config. Raw value is intentionally not exposed. */
289
331
  readonly hasLicenseKey: boolean;
290
332
  /** Fatal problems that will prevent the MCP server from starting. */
291
333
  readonly errors: readonly string[];
@@ -299,4 +341,17 @@ export interface ConfigValidationResult {
299
341
  */
300
342
  export declare function validateConfigFile(filePath: string): ConfigValidationResult;
301
343
  export declare function redactSensitive(value: string, patterns?: readonly RegExp[]): string;
344
+ /**
345
+ * redactSensitive() for identifiers (project_id, repo_url, repo names), which
346
+ * carry a token only as a whole path segment or a segment's prefix. A match that
347
+ * starts right after a letter or digit is part of an ordinary name
348
+ * (`acme/risk-assessment-…` holds `sk-assessment-…`) and is left alone. The scan
349
+ * resumes one character later, so a token further into that span
350
+ * (`acme/risk-ghp_…`) is still caught.
351
+ *
352
+ * Free-form content keeps redactSensitive()'s plain matching: there a token
353
+ * routinely follows a letter or digit, after a JSON escape (`\nghp_…`), an ANSI
354
+ * colour code (`ESC[32m`) or `%3A`.
355
+ */
356
+ export declare function redactIdentifier(value: string): string;
302
357
  //# sourceMappingURL=config.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
package/dist/config.js CHANGED
@@ -9,6 +9,7 @@ import { REDACTION_PATTERNS as DEFAULT_REDACTION_PATTERNS } from './redaction-pa
9
9
  import { resolveRecordContent } from './record-content-gate.js';
10
10
  import { projectIdFromRemote, stripRemoteCredentials } from './lib/git-remote.js';
11
11
  import { validateTiers, DEFAULT_TIER_NAME, WILDCARD_EVENT_TYPE } from './transport/tier-types.js';
12
+ import { SecretCommandError, isSecretConfigured, resolveSecret } from './secret-command.js';
12
13
  const logger = createLogger('mcp-config');
13
14
  export const VALID_MODES = ['cloud', 'local', 'both'];
14
15
  function otlpEndpointIsNewRelic(endpoint) {
@@ -59,6 +60,9 @@ const DEFAULT_RETAIN_SESSIONS_DAYS = 90;
59
60
  * rather than a breaking rename — so this has never moved past 1.
60
61
  */
61
62
  const CURRENT_CONFIG_VERSION = 1;
63
+ // argv array only: a single string would invite shell-style quoting that
64
+ // Preflight never interprets, since the command runs without a shell.
65
+ export const SecretCommandSchema = z.array(z.string().min(1)).min(1);
62
66
  export const ConfigFileSchema = z
63
67
  .object({
64
68
  configVersion: z.number().int().optional(),
@@ -77,6 +81,7 @@ export const ConfigFileSchema = z
77
81
  recordContent: z.boolean().optional(),
78
82
  pricingRefresh: z.boolean().optional(),
79
83
  companionMode: z.boolean().optional(),
84
+ allowHeadlessInstall: z.boolean().optional(),
80
85
  storagePath: z.string().optional(),
81
86
  hookBufferPath: z.string().optional(),
82
87
  harvestEventsMs: z.number().optional(),
@@ -94,6 +99,9 @@ export const ConfigFileSchema = z
94
99
  // generic union-mismatch issue.
95
100
  tiers: z.array(z.unknown()).optional(),
96
101
  nrApiKey: z.string().nullable().optional(),
102
+ licenseKeyCommand: SecretCommandSchema.optional(),
103
+ nrApiKeyCommand: SecretCommandSchema.optional(),
104
+ digestWebhookUrlCommand: SecretCommandSchema.optional(),
97
105
  customPricingFile: z.string().nullable().optional(),
98
106
  costRateMultiplier: z.number().nullable().optional(),
99
107
  dataResidencyPremium: z.boolean().optional(),
@@ -494,7 +502,34 @@ export class MissingCloudCredentialError extends Error {
494
502
  this.mode = mode;
495
503
  }
496
504
  }
497
- export function loadMcpConfig(cliOptions) {
505
+ /**
506
+ * A `SecretCommandError` raised while resolving a secret that cloud export
507
+ * needs. It keeps the helper's own message for stdio users and adds the mode
508
+ * the config asked for, so `--local` can tell a failing helper from a missing
509
+ * credential and still fall back to local mode.
510
+ */
511
+ export class CloudSecretCommandError extends SecretCommandError {
512
+ mode;
513
+ constructor(original, mode) {
514
+ super(original.field, original.failure);
515
+ this.message = original.message;
516
+ this.cause = original;
517
+ this.mode = mode;
518
+ }
519
+ }
520
+ /**
521
+ * The startup-resolved digest webhook URL when a source that outranks the
522
+ * file's `digestWebhookUrl` (env var or digestWebhookUrlCommand) supplied it.
523
+ * Undefined means "read the file at send time".
524
+ */
525
+ export function digestWebhookUrlOverride(config) {
526
+ const source = config.secretSources.digestWebhookUrl;
527
+ return (source === 'env' || source === 'command') && config.digestWebhookUrl !== null
528
+ ? config.digestWebhookUrl
529
+ : undefined;
530
+ }
531
+ export function loadMcpConfig(cliOptions, loadOptions = {}) {
532
+ const skipCommand = loadOptions.resolveSecrets === false;
498
533
  const configFilePath = cliOptions?.config ?? resolve(DEFAULT_STORAGE_PATH, 'config.json');
499
534
  const file = loadConfigFile(configFilePath);
500
535
  // Warn (don't crash) when the user's config file contains keys we don't
@@ -578,11 +613,17 @@ export function loadMcpConfig(cliOptions) {
578
613
  });
579
614
  }
580
615
  }
581
- // --- licenseKey: CLI has no flag for this, so env > file ---
582
- // Hoisted ahead of mode resolution: an ambiguous licenseKey-without-mode
583
- // config must fail closed instead of silently defaulting to 'cloud'.
584
- const licenseKeyRaw = process.env.NEW_RELIC_LICENSE_KEY ??
585
- (typeof file.licenseKey === 'string' ? file.licenseKey : undefined);
616
+ // --- licenseKey: CLI has no flag for this, so env > licenseKeyCommand > file ---
617
+ // Presence is checked ahead of mode resolution: an ambiguous
618
+ // licenseKey-without-mode config must fail closed instead of silently
619
+ // defaulting to 'cloud'. The command itself runs only after mode resolves,
620
+ // so a local-mode process never runs it.
621
+ const licenseKeySources = {
622
+ env: process.env.NEW_RELIC_LICENSE_KEY,
623
+ command: file.licenseKeyCommand,
624
+ file: typeof file.licenseKey === 'string' ? file.licenseKey : undefined,
625
+ };
626
+ const licenseKeyConfigured = isSecretConfigured(licenseKeySources);
586
627
  // --- Resolve mode early so we can gate licenseKey/accountId requirements ---
587
628
  // File mode is already validated by the zod schema in loadConfigFile.
588
629
  const isValidMode = (v) => v !== undefined && VALID_MODES.includes(v);
@@ -598,7 +639,7 @@ export function loadMcpConfig(cliOptions) {
598
639
  else if (fileMode !== undefined) {
599
640
  mode = fileMode;
600
641
  }
601
- else if (licenseKeyRaw) {
642
+ else if (licenseKeyConfigured) {
602
643
  // Local-first default (README): telemetry export is opt-in, so a config
603
644
  // with credentials but no explicit mode must not silently export.
604
645
  throw new Error('Config has a licenseKey but no explicit mode. Telemetry export is opt-in: set "mode": "cloud" (or "both") in the config file, set NR_AI_MODE, or remove the credentials for local-only use. Previous versions implicitly defaulted to cloud.');
@@ -606,7 +647,19 @@ export function loadMcpConfig(cliOptions) {
606
647
  else {
607
648
  mode = 'local';
608
649
  }
609
- if (mode !== 'local' && !licenseKeyRaw) {
650
+ let licenseKeySecret;
651
+ try {
652
+ licenseKeySecret = resolveSecret('licenseKey', mode === 'local' ? { ...licenseKeySources, command: undefined } : licenseKeySources, { skipCommand });
653
+ }
654
+ catch (err) {
655
+ if (err instanceof SecretCommandError)
656
+ throw new CloudSecretCommandError(err, mode);
657
+ throw err;
658
+ }
659
+ const licenseKeyRaw = licenseKeySecret.value;
660
+ if (mode !== 'local' &&
661
+ !licenseKeyRaw &&
662
+ !(skipCommand && licenseKeySecret.source === 'command')) {
610
663
  throw new MissingCloudCredentialError('licenseKey', mode, configFilePath);
611
664
  }
612
665
  // Reject the literal string "null" — it's truthy but not a valid key
@@ -617,8 +670,8 @@ export function loadMcpConfig(cliOptions) {
617
670
  }
618
671
  // In local mode, undefined if licenseKey is missing (NR transport won't be used)
619
672
  const licenseKey = licenseKeyRaw;
620
- // --- accountId: env > file ---
621
- const accountIdRaw = process.env.NEW_RELIC_ACCOUNT_ID ??
673
+ // --- accountId: env > file; a blank env var counts as unset, as for the secrets ---
674
+ const accountIdRaw = (process.env.NEW_RELIC_ACCOUNT_ID?.trim() ? process.env.NEW_RELIC_ACCOUNT_ID : undefined) ??
622
675
  (typeof file.accountId === 'string' ? file.accountId : undefined);
623
676
  if (mode !== 'local' && !accountIdRaw) {
624
677
  throw new MissingCloudCredentialError('accountId', mode, configFilePath);
@@ -662,8 +715,9 @@ export function loadMcpConfig(cliOptions) {
662
715
  (typeof file.storagePath === 'string' ? file.storagePath : DEFAULT_STORAGE_PATH);
663
716
  // highSecurity must be resolved before recordContent so it can override it
664
717
  const highSecurity = envBool('NEW_RELIC_AI_HIGH_SECURITY', typeof file.highSecurity === 'boolean' ? file.highSecurity : false);
665
- const resolvedProjectId = sanitizeOrgField(process.env.NEW_RELIC_AI_PROJECT_ID ??
666
- (typeof file.projectId === 'string' ? file.projectId : inferProjectId()));
718
+ const rawProjectId = process.env.NEW_RELIC_AI_PROJECT_ID ??
719
+ (typeof file.projectId === 'string' ? file.projectId : inferProjectId());
720
+ const resolvedProjectId = sanitizeOrgField(rawProjectId === null ? null : redactIdentifier(rawProjectId));
667
721
  // Dedicated opt-out, independent of projectId's — repo_url can carry an
668
722
  // internal git host that org/repo alone doesn't, so it gets its own toggle
669
723
  // rather than riding along on projectId's. Defaults on; the setup wizard
@@ -677,17 +731,32 @@ export function loadMcpConfig(cliOptions) {
677
731
  const rawRepoUrl = process.env.NEW_RELIC_AI_REPO_URL ??
678
732
  (typeof file.repoUrl === 'string' ? file.repoUrl : getGitRemoteUrl());
679
733
  // stripRemoteCredentials drops URL userinfo and query strings;
680
- // redactSensitive then catches any token elsewhere in the value.
734
+ // redactIdentifier then catches any token elsewhere in the value.
681
735
  const strippedRepoUrl = stripRemoteCredentials(rawRepoUrl);
682
- const resolvedRepoUrl = sanitizeOrgField(strippedRepoUrl === null ? null : redactSensitive(strippedRepoUrl));
736
+ const resolvedRepoUrl = sanitizeOrgField(strippedRepoUrl === null ? null : redactIdentifier(strippedRepoUrl));
683
737
  const resolvedCompanionMode = resolveCompanionMode(process.env, typeof file.companionMode === 'boolean' ? file.companionMode : undefined);
684
738
  logger.info('Companion mode resolved', {
685
739
  value: resolvedCompanionMode.value,
686
740
  source: resolvedCompanionMode.source,
687
741
  });
742
+ const nrApiKeySecret = resolveSecret('nrApiKey', {
743
+ env: process.env.NEW_RELIC_API_KEY,
744
+ command: mode === 'local' ? undefined : file.nrApiKeyCommand,
745
+ file: typeof file.nrApiKey === 'string' ? file.nrApiKey : undefined,
746
+ }, { optional: true, skipCommand });
747
+ const digestWebhookUrlSecret = resolveSecret('digestWebhookUrl', {
748
+ env: process.env.NEW_RELIC_AI_DIGEST_WEBHOOK_URL,
749
+ command: file.digestWebhookUrlCommand,
750
+ file: typeof file.digestWebhookUrl === 'string' ? file.digestWebhookUrl : undefined,
751
+ }, { optional: true, skipCommand });
688
752
  const config = {
689
753
  licenseKey,
690
754
  accountId,
755
+ secretSources: {
756
+ licenseKey: licenseKeySecret.source,
757
+ nrApiKey: nrApiKeySecret.source,
758
+ digestWebhookUrl: digestWebhookUrlSecret.source,
759
+ },
691
760
  configVersion: typeof file.configVersion === 'number' ? file.configVersion : CURRENT_CONFIG_VERSION,
692
761
  appName: process.env.NEW_RELIC_AI_MCP_APP_NAME ??
693
762
  (typeof file.appName === 'string' ? file.appName : 'preflight'),
@@ -707,6 +776,10 @@ export function loadMcpConfig(cliOptions) {
707
776
  ? false
708
777
  : envBool('NEW_RELIC_AI_PRICING_REFRESH', typeof file.pricingRefresh === 'boolean' ? file.pricingRefresh : true),
709
778
  companionMode: resolvedCompanionMode.value,
779
+ companionModeSource: resolvedCompanionMode.source,
780
+ allowHeadlessInstall: highSecurity
781
+ ? false
782
+ : envBool('NEW_RELIC_AI_ALLOW_HEADLESS_INSTALL', typeof file.allowHeadlessInstall === 'boolean' ? file.allowHeadlessInstall : false),
710
783
  redactionPatterns: DEFAULT_REDACTION_PATTERNS,
711
784
  hookBufferPath: process.env.NEW_RELIC_AI_MCP_BUFFER_PATH ??
712
785
  (typeof file.hookBufferPath === 'string'
@@ -772,7 +845,7 @@ export function loadMcpConfig(cliOptions) {
772
845
  collectorHost: resolveCollectorHost(licenseKey, process.env.NEW_RELIC_HOST ??
773
846
  (typeof file.collectorHost === 'string' ? file.collectorHost : null)),
774
847
  proxyUpstreams: parseProxyUpstreams(process.env.NEW_RELIC_AI_MCP_PROXY_UPSTREAMS, file.proxyUpstreams),
775
- nrApiKey: process.env.NEW_RELIC_API_KEY ?? (typeof file.nrApiKey === 'string' ? file.nrApiKey : null),
848
+ nrApiKey: nrApiKeySecret.value ?? null,
776
849
  customPricingFile: process.env.NEW_RELIC_AI_CUSTOM_PRICING_FILE ??
777
850
  (typeof file.customPricingFile === 'string' ? file.customPricingFile : null),
778
851
  costRateMultiplier: (() => {
@@ -789,8 +862,7 @@ export function loadMcpConfig(cliOptions) {
789
862
  : null;
790
863
  })(),
791
864
  dataResidencyPremium: envBool('NEW_RELIC_AI_DATA_RESIDENCY_PREMIUM', typeof file.dataResidencyPremium === 'boolean' ? file.dataResidencyPremium : false),
792
- digestWebhookUrl: process.env.NEW_RELIC_AI_DIGEST_WEBHOOK_URL ??
793
- (typeof file.digestWebhookUrl === 'string' ? file.digestWebhookUrl : null),
865
+ digestWebhookUrl: digestWebhookUrlSecret.value ?? null,
794
866
  digestSchedule: process.env.NEW_RELIC_AI_DIGEST_SCHEDULE ??
795
867
  (typeof file.digestSchedule === 'string' ? file.digestSchedule : '0 9 * * 1'),
796
868
  retainSessionsDays: (() => {
@@ -1177,7 +1249,8 @@ export function validateConfigFile(filePath) {
1177
1249
  mode: typeof file.mode === 'string' ? file.mode : undefined,
1178
1250
  storagePath: typeof file.storagePath === 'string' ? file.storagePath : undefined,
1179
1251
  companionMode: typeof file.companionMode === 'boolean' ? file.companionMode : undefined,
1180
- hasLicenseKey: typeof file.licenseKey === 'string' && file.licenseKey.trim().length > 0,
1252
+ hasLicenseKey: (typeof file.licenseKey === 'string' && file.licenseKey.trim().length > 0) ||
1253
+ Array.isArray(file.licenseKeyCommand),
1181
1254
  errors,
1182
1255
  warnings,
1183
1256
  };
@@ -1224,4 +1297,40 @@ export function redactSensitive(value, patterns) {
1224
1297
  }
1225
1298
  return result;
1226
1299
  }
1300
+ const WORD_CHAR_RE = /[A-Za-z0-9]/;
1301
+ /**
1302
+ * redactSensitive() for identifiers (project_id, repo_url, repo names), which
1303
+ * carry a token only as a whole path segment or a segment's prefix. A match that
1304
+ * starts right after a letter or digit is part of an ordinary name
1305
+ * (`acme/risk-assessment-…` holds `sk-assessment-…`) and is left alone. The scan
1306
+ * resumes one character later, so a token further into that span
1307
+ * (`acme/risk-ghp_…`) is still caught.
1308
+ *
1309
+ * Free-form content keeps redactSensitive()'s plain matching: there a token
1310
+ * routinely follows a letter or digit, after a JSON escape (`\nghp_…`), an ANSI
1311
+ * colour code (`ESC[32m`) or `%3A`.
1312
+ */
1313
+ export function redactIdentifier(value) {
1314
+ let result = value;
1315
+ for (const pattern of DEFAULT_REDACTION_PATTERNS) {
1316
+ const re = new RegExp(pattern.source, pattern.flags.includes('g') ? pattern.flags : `${pattern.flags}g`);
1317
+ let out = '';
1318
+ let last = 0;
1319
+ let match;
1320
+ while ((match = re.exec(result)) !== null) {
1321
+ if (match[0] === '') {
1322
+ re.lastIndex++;
1323
+ continue;
1324
+ }
1325
+ if (match.index > 0 && WORD_CHAR_RE.test(result[match.index - 1])) {
1326
+ re.lastIndex = match.index + 1;
1327
+ continue;
1328
+ }
1329
+ out += `${result.slice(last, match.index)}[REDACTED]`;
1330
+ last = re.lastIndex;
1331
+ }
1332
+ result = out + result.slice(last);
1333
+ }
1334
+ return result;
1335
+ }
1227
1336
  //# sourceMappingURL=config.js.map