@scrymore/scry-deployer 0.6.1-nightly.20260925022633 → 0.6.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/bin/cli.js +5 -3
- package/lib/telemetry.js +67 -0
- package/package.json +1 -1
package/bin/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
const { initTelemetry, captureCliError, flushTelemetry } = require('../lib/telemetry.js');
|
|
3
|
+
const { initTelemetry, captureCliError, flushTelemetry, redactArgv } = require('../lib/telemetry.js');
|
|
4
4
|
const yargs = require('yargs/yargs');
|
|
5
5
|
const { hideBin } = require('yargs/helpers');
|
|
6
6
|
const fs = require('fs');
|
|
@@ -25,7 +25,8 @@ const { resolveBuildGitContext } = require('../lib/gitContext.js');
|
|
|
25
25
|
async function runAnalysis(argv) {
|
|
26
26
|
const logger = createLogger(argv);
|
|
27
27
|
logger.info('📊 Starting Storybook analysis...');
|
|
28
|
-
|
|
28
|
+
// Credentials masked: this line is also a Sentry breadcrumb.
|
|
29
|
+
logger.debug(`Received arguments: ${JSON.stringify(redactArgv(argv))}`);
|
|
29
30
|
|
|
30
31
|
const outPath = path.join(os.tmpdir(), `storybook-analysis-${Date.now()}.zip`);
|
|
31
32
|
|
|
@@ -85,7 +86,8 @@ async function runAnalysis(argv) {
|
|
|
85
86
|
async function runDeployment(argv) {
|
|
86
87
|
const logger = createLogger(argv);
|
|
87
88
|
logger.info('🚀 Starting deployment...');
|
|
88
|
-
|
|
89
|
+
// Credentials masked: this line is also a Sentry breadcrumb.
|
|
90
|
+
logger.debug(`Received arguments: ${JSON.stringify(redactArgv(argv))}`);
|
|
89
91
|
|
|
90
92
|
const outPath = path.join(os.tmpdir(), `storybook-deployment-${Date.now()}.zip`);
|
|
91
93
|
let metadataZipPath = null;
|
package/lib/telemetry.js
CHANGED
|
@@ -21,6 +21,8 @@ const Sentry = require('@sentry/node');
|
|
|
21
21
|
* None of that is needed to know that an upload failed with EAI_AGAIN.
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
|
+
const { version: PACKAGE_VERSION } = require('../package.json');
|
|
25
|
+
|
|
24
26
|
const DSN =
|
|
25
27
|
'https://c66ce229a1db2289f145eebd02436d9c@o4507889391828992.ingest.us.sentry.io/4510699330732032';
|
|
26
28
|
|
|
@@ -41,8 +43,58 @@ const SECRET_PATTERNS = [
|
|
|
41
43
|
[/scry_proj_[A-Za-z0-9_\-]+/g, 'scry_proj_<redacted>'],
|
|
42
44
|
[/(X-Amz-Signature=)[^&\s]+/gi, '$1<redacted>'],
|
|
43
45
|
[/(Bearer\s+)[A-Za-z0-9._\-]+/gi, '$1<redacted>'],
|
|
46
|
+
// A credential flag echoed on a command line: --api-key X, --api-key=X,
|
|
47
|
+
// -k X, --token X, --openai-api-key X, ...
|
|
48
|
+
[/(--[\w-]*(?:api-key|apikey|token|secret|password)[\w-]*[=\s]+|(?:^|\s)-k[=\s]+)(?!<redacted>)[^\s"',]+/gi, '$1<redacted>'],
|
|
49
|
+
// A credential field inside serialized JSON: "apiKey":"X", "token": "X".
|
|
50
|
+
[/("[\w-]*(?:api[-_]?key|token|secret|password)[\w-]*"\s*:\s*)"[^"]*"/gi, '$1"<redacted>"'],
|
|
51
|
+
// An env echo: SCRY_API_KEY=X, STORYBOOK_DEPLOYER_API_KEY=X, JINA_API_KEY=X.
|
|
52
|
+
[/(\b[A-Z][A-Z0-9_]*(?:KEY|TOKEN|SECRET|PASSWORD)[A-Z0-9_]*=)(?!<redacted>)[^\s"',]+/g, '$1<redacted>'],
|
|
44
53
|
];
|
|
45
54
|
|
|
55
|
+
/** Field names whose values are credentials, in any spelling yargs produces. */
|
|
56
|
+
const SECRET_FIELD = /(^k$|api[-_]?key|token|secret|password)/i;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* A copy of argv with every credential-named field masked, at any depth.
|
|
60
|
+
*
|
|
61
|
+
* For logging argv whole (the --verbose "Received arguments" line), which is
|
|
62
|
+
* also a Sentry breadcrumb. Unlike sanitizeArgv this keeps every field, so it
|
|
63
|
+
* must only be used for local diagnostics, never as event context.
|
|
64
|
+
*/
|
|
65
|
+
function redactArgv(value, depth = 0) {
|
|
66
|
+
if (depth > 5 || value === null || typeof value !== 'object') return value;
|
|
67
|
+
if (Array.isArray(value)) return value.map((v) => redactArgv(v, depth + 1));
|
|
68
|
+
const out = {};
|
|
69
|
+
for (const [k, v] of Object.entries(value)) {
|
|
70
|
+
out[k] = SECRET_FIELD.test(k) && v !== undefined && v !== null && v !== '' ? '<redacted>' : redactArgv(v, depth + 1);
|
|
71
|
+
}
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Scrub every string in a JSON-like value (breadcrumb data), in place-safe copy. */
|
|
76
|
+
function scrubDeep(value, depth = 0) {
|
|
77
|
+
if (typeof value === 'string') return scrub(value);
|
|
78
|
+
if (depth > 5 || value === null || typeof value !== 'object') return value;
|
|
79
|
+
if (Array.isArray(value)) return value.map((v) => scrubDeep(v, depth + 1));
|
|
80
|
+
const out = {};
|
|
81
|
+
for (const [k, v] of Object.entries(value)) {
|
|
82
|
+
out[k] = SECRET_FIELD.test(k) && typeof v === 'string' && v ? '<redacted>' : scrubDeep(v, depth + 1);
|
|
83
|
+
}
|
|
84
|
+
return out;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Breadcrumbs are sent with every event and beforeSend does not see them as
|
|
89
|
+
* separate items, so they get the same scrubbing on the way in.
|
|
90
|
+
*/
|
|
91
|
+
function scrubBreadcrumb(breadcrumb) {
|
|
92
|
+
if (!breadcrumb) return breadcrumb;
|
|
93
|
+
if (breadcrumb.message) breadcrumb.message = scrub(breadcrumb.message);
|
|
94
|
+
if (breadcrumb.data) breadcrumb.data = scrubDeep(breadcrumb.data);
|
|
95
|
+
return breadcrumb;
|
|
96
|
+
}
|
|
97
|
+
|
|
46
98
|
function scrub(value) {
|
|
47
99
|
if (typeof value !== 'string') return value;
|
|
48
100
|
return SECRET_PATTERNS.reduce((acc, [pattern, replacement]) => acc.replace(pattern, replacement), value);
|
|
@@ -77,6 +129,9 @@ function initTelemetry() {
|
|
|
77
129
|
Sentry.init({
|
|
78
130
|
dsn: DSN,
|
|
79
131
|
environment: process.env.NODE_ENV || 'production',
|
|
132
|
+
// Which published deployer produced the event, so a fix can be matched to
|
|
133
|
+
// the versions still in the wild.
|
|
134
|
+
release: `@scrymore/scry-deployer@${PACKAGE_VERSION}`,
|
|
80
135
|
|
|
81
136
|
// Errors only. Traces from a CLI describe the customer's build pipeline,
|
|
82
137
|
// which is more than is needed to fix a crash.
|
|
@@ -86,8 +141,18 @@ function initTelemetry() {
|
|
|
86
141
|
sendDefaultPii: false,
|
|
87
142
|
serverName: false,
|
|
88
143
|
|
|
144
|
+
// The logger turns every line (including debug) into a breadcrumb, and
|
|
145
|
+
// Sentry's console integration does the same for console.*. None of that
|
|
146
|
+
// passed through beforeSend's scrubbing before.
|
|
147
|
+
beforeBreadcrumb(breadcrumb) {
|
|
148
|
+
return scrubBreadcrumb(breadcrumb);
|
|
149
|
+
},
|
|
150
|
+
|
|
89
151
|
beforeSend(event) {
|
|
90
152
|
if (event.message) event.message = scrub(event.message);
|
|
153
|
+
// Belt and braces for breadcrumbs added before init or by integrations
|
|
154
|
+
// that bypass beforeBreadcrumb.
|
|
155
|
+
if (Array.isArray(event.breadcrumbs)) event.breadcrumbs = event.breadcrumbs.map(scrubBreadcrumb);
|
|
91
156
|
|
|
92
157
|
for (const entry of event.exception?.values ?? []) {
|
|
93
158
|
if (entry.value) entry.value = scrub(entry.value);
|
|
@@ -135,6 +200,8 @@ module.exports = {
|
|
|
135
200
|
flushTelemetry,
|
|
136
201
|
telemetryDisabled,
|
|
137
202
|
sanitizeArgv,
|
|
203
|
+
redactArgv,
|
|
138
204
|
scrub,
|
|
205
|
+
scrubBreadcrumb,
|
|
139
206
|
SAFE_ARGV_FIELDS,
|
|
140
207
|
};
|