@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.
- package/README.md +2 -1
- package/dist/config.d.ts +57 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +127 -18
- package/dist/config.js.map +1 -1
- package/dist/dashboard/routes/api-handler.d.ts.map +1 -1
- package/dist/dashboard/routes/api-handler.js +13 -5
- package/dist/dashboard/routes/api-handler.js.map +1 -1
- package/dist/hooks/event-processor.d.ts.map +1 -1
- package/dist/hooks/event-processor.js +7 -4
- package/dist/hooks/event-processor.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +90 -121
- package/dist/index.js.map +1 -1
- package/dist/install/cli.d.ts.map +1 -1
- package/dist/install/cli.js +16 -1
- package/dist/install/cli.js.map +1 -1
- package/dist/install/dashboard-health.js +1 -1
- package/dist/install/dashboard-health.js.map +1 -1
- package/dist/install/diagnostics.d.ts +2 -0
- package/dist/install/diagnostics.d.ts.map +1 -1
- package/dist/install/diagnostics.js +117 -27
- package/dist/install/diagnostics.js.map +1 -1
- package/dist/install/headless-install.d.ts +26 -0
- package/dist/install/headless-install.d.ts.map +1 -1
- package/dist/install/headless-install.js +44 -1
- package/dist/install/headless-install.js.map +1 -1
- package/dist/install/install-helper.d.ts +2 -0
- package/dist/install/install-helper.d.ts.map +1 -1
- package/dist/install/install-helper.js +5 -0
- package/dist/install/install-helper.js.map +1 -1
- package/dist/lib/child-env.d.ts +11 -0
- package/dist/lib/child-env.d.ts.map +1 -0
- package/dist/lib/child-env.js +35 -0
- package/dist/lib/child-env.js.map +1 -0
- package/dist/lib/git-remote.d.ts +7 -0
- package/dist/lib/git-remote.d.ts.map +1 -1
- package/dist/lib/git-remote.js +13 -0
- package/dist/lib/git-remote.js.map +1 -1
- package/dist/metrics/cost-estimate-gate.d.ts +20 -0
- package/dist/metrics/cost-estimate-gate.d.ts.map +1 -1
- package/dist/metrics/cost-estimate-gate.js +21 -1
- package/dist/metrics/cost-estimate-gate.js.map +1 -1
- package/dist/metrics/cost-tracker-seed.d.ts.map +1 -1
- package/dist/metrics/cost-tracker-seed.js +1 -0
- package/dist/metrics/cost-tracker-seed.js.map +1 -1
- package/dist/metrics/cost-tracker.d.ts +53 -1
- package/dist/metrics/cost-tracker.d.ts.map +1 -1
- package/dist/metrics/cost-tracker.js +87 -2
- package/dist/metrics/cost-tracker.js.map +1 -1
- package/dist/metrics/local-session-aggregator.d.ts +13 -0
- package/dist/metrics/local-session-aggregator.d.ts.map +1 -1
- package/dist/metrics/local-session-aggregator.js +22 -4
- package/dist/metrics/local-session-aggregator.js.map +1 -1
- package/dist/metrics/model-pricing.d.ts +6 -0
- package/dist/metrics/model-pricing.d.ts.map +1 -1
- package/dist/metrics/model-pricing.js +19 -2
- package/dist/metrics/model-pricing.js.map +1 -1
- package/dist/metrics/pricing-init.d.ts +13 -0
- package/dist/metrics/pricing-init.d.ts.map +1 -0
- package/dist/metrics/pricing-init.js +19 -0
- package/dist/metrics/pricing-init.js.map +1 -0
- package/dist/metrics/pricing-overlay.d.ts.map +1 -1
- package/dist/metrics/pricing-overlay.js +4 -5
- package/dist/metrics/pricing-overlay.js.map +1 -1
- package/dist/proxy/proxy-manager.d.ts +3 -0
- package/dist/proxy/proxy-manager.d.ts.map +1 -1
- package/dist/proxy/proxy-manager.js +43 -3
- package/dist/proxy/proxy-manager.js.map +1 -1
- package/dist/proxy/types.d.ts +7 -0
- package/dist/proxy/types.d.ts.map +1 -1
- package/dist/proxy/types.js.map +1 -1
- package/dist/proxy/upstream-http.d.ts.map +1 -1
- package/dist/proxy/upstream-http.js +44 -1
- package/dist/proxy/upstream-http.js.map +1 -1
- package/dist/proxy/upstream-stdio.d.ts +0 -1
- package/dist/proxy/upstream-stdio.d.ts.map +1 -1
- package/dist/proxy/upstream-stdio.js +2 -26
- package/dist/proxy/upstream-stdio.js.map +1 -1
- package/dist/redaction-patterns.d.ts.map +1 -1
- package/dist/redaction-patterns.js +1 -0
- package/dist/redaction-patterns.js.map +1 -1
- package/dist/secret-command.d.ts +57 -0
- package/dist/secret-command.d.ts.map +1 -0
- package/dist/secret-command.js +117 -0
- package/dist/secret-command.js.map +1 -0
- package/dist/security/audit-trail.d.ts +7 -1
- package/dist/security/audit-trail.d.ts.map +1 -1
- package/dist/security/audit-trail.js +24 -0
- package/dist/security/audit-trail.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +9 -4
- package/dist/server.js.map +1 -1
- package/dist/storage/historical-costs.d.ts +6 -0
- package/dist/storage/historical-costs.d.ts.map +1 -0
- package/dist/storage/historical-costs.js +42 -0
- package/dist/storage/historical-costs.js.map +1 -0
- package/dist/storage/local-store.d.ts +6 -4
- package/dist/storage/local-store.d.ts.map +1 -1
- package/dist/storage/local-store.js +21 -7
- package/dist/storage/local-store.js.map +1 -1
- package/dist/storage/session-store.d.ts +23 -1
- package/dist/storage/session-store.d.ts.map +1 -1
- package/dist/storage/session-store.js +100 -8
- package/dist/storage/session-store.js.map +1 -1
- package/dist/storage/types.d.ts +7 -11
- package/dist/storage/types.d.ts.map +1 -1
- package/dist/storage/types.js.map +1 -1
- package/dist/tools/cross-session-tools.d.ts +7 -1
- package/dist/tools/cross-session-tools.d.ts.map +1 -1
- package/dist/tools/cross-session-tools.js +47 -14
- package/dist/tools/cross-session-tools.js.map +1 -1
- package/dist/tools/session-stats.d.ts +6 -0
- package/dist/tools/session-stats.d.ts.map +1 -1
- package/dist/tools/session-stats.js +2 -1
- package/dist/tools/session-stats.js.map +1 -1
- package/dist/transport/audit-record-sender.d.ts +20 -0
- package/dist/transport/audit-record-sender.d.ts.map +1 -0
- package/dist/transport/audit-record-sender.js +54 -0
- package/dist/transport/audit-record-sender.js.map +1 -0
- package/dist/transport/nr-ingest.d.ts +3 -0
- package/dist/transport/nr-ingest.d.ts.map +1 -1
- package/dist/transport/nr-ingest.js +21 -8
- package/dist/transport/nr-ingest.js.map +1 -1
- package/dist/transport/unforwarded-session-monitor.d.ts +12 -6
- package/dist/transport/unforwarded-session-monitor.d.ts.map +1 -1
- package/dist/transport/unforwarded-session-monitor.js +24 -5
- package/dist/transport/unforwarded-session-monitor.js.map +1 -1
- 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
|
|
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
|
-
|
|
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
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAMjE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;
|
|
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
|
-
|
|
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
|
-
//
|
|
583
|
-
// config must fail closed instead of silently
|
|
584
|
-
|
|
585
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
|
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
|
-
//
|
|
734
|
+
// redactIdentifier then catches any token elsewhere in the value.
|
|
681
735
|
const strippedRepoUrl = stripRemoteCredentials(rawRepoUrl);
|
|
682
|
-
const resolvedRepoUrl = sanitizeOrgField(strippedRepoUrl === null ? null :
|
|
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:
|
|
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:
|
|
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
|