shardstitch 0.0.7 → 0.0.8

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 (2) hide show
  1. package/cli.js +81 -3
  2. package/package.json +1 -1
package/cli.js CHANGED
@@ -16,6 +16,28 @@ const INSTALL_DIR = join(os.homedir(), ".shardstitch", "app");
16
16
  const ACTIVATION_FILE = join(os.homedir(), ".shardstitch", "activation.json");
17
17
  const MACHINE_SALT = "shardstitch-activation/1";
18
18
 
19
+ // --- Supply-chain hardening -------------------------------------------------
20
+ // The binary SHA-256 is baked into this file at release time. No external
21
+ // checksums.json needed — Polar delivers the file, we verify the hash locally.
22
+ // Update these after every build before publishing.
23
+ const CHECKSUMS = {
24
+ windows: "14642911d2f2fee8baf92ad30948b96e01dbd66167bab3ab0cbf443a33ea58a6",
25
+ linux: "14642911d2f2fee8baf92ad30948b96e01dbd66167bab3ab0cbf443a33ea58a6",
26
+ mac: "14642911d2f2fee8baf92ad30948b96e01dbd66167bab3ab0cbf443a33ea58a6",
27
+ };
28
+ const TRUSTED_DOWNLOAD_HOST_SUFFIXES = [
29
+ "shardstitch.com",
30
+ "polar.sh",
31
+ "polarsource.com",
32
+ ];
33
+
34
+ function isTrustedHost(hostname) {
35
+ const h = (hostname || "").toLowerCase();
36
+ return TRUSTED_DOWNLOAD_HOST_SUFFIXES.some(
37
+ (suffix) => h === suffix || h.endsWith("." + suffix)
38
+ );
39
+ }
40
+
19
41
  const PLATFORM = process.platform === "win32" ? "windows"
20
42
  : process.platform === "darwin" ? "mac"
21
43
  : "linux";
@@ -109,8 +131,22 @@ function downloadFile(url, dest) {
109
131
  const tmp = dest + ".tmp";
110
132
  const file = createWriteStream(tmp);
111
133
 
112
- function follow(url) {
113
- https.get(url, { headers: { "User-Agent": "ShardStitch-Installer/1.0" } }, (res) => {
134
+ function follow(rawUrl) {
135
+ let u;
136
+ try {
137
+ u = new URL(rawUrl);
138
+ } catch {
139
+ return reject(new Error(`Refusing to download: invalid URL`));
140
+ }
141
+ // Fail closed on protocol downgrade or untrusted host (blocks open-redirect
142
+ // / MITM from pointing the download at an attacker-controlled binary).
143
+ if (u.protocol !== "https:") {
144
+ return reject(new Error(`Refusing non-HTTPS download (${u.protocol})`));
145
+ }
146
+ if (!isTrustedHost(u.hostname)) {
147
+ return reject(new Error(`Refusing download from untrusted host: ${u.hostname}`));
148
+ }
149
+ https.get(rawUrl, { headers: { "User-Agent": "ShardStitch-Installer/1.0" } }, (res) => {
114
150
  if (res.statusCode === 301 || res.statusCode === 302) {
115
151
  return follow(res.headers.location);
116
152
  }
@@ -138,6 +174,17 @@ function downloadFile(url, dest) {
138
174
  });
139
175
  }
140
176
 
177
+ function sha256File(path) {
178
+ return new Promise((resolve, reject) => {
179
+ const hash = createHash("sha256");
180
+ const stream = fs.createReadStream(path);
181
+ stream.on("data", (chunk) => hash.update(chunk));
182
+ stream.on("end", () => resolve(hash.digest("hex")));
183
+ stream.on("error", reject);
184
+ });
185
+ }
186
+
187
+
141
188
  async function cmdInstall(key) {
142
189
  key = key.trim();
143
190
  console.log();
@@ -191,12 +238,43 @@ async function cmdInstall(key) {
191
238
 
192
239
  try {
193
240
  await downloadFile(downloadUrl, exePath);
194
- if (PLATFORM !== "windows") chmodSync(exePath, 0o755);
195
241
  } catch (e) {
196
242
  console.log(` ✗ Download failed: ${e.message}`);
197
243
  process.exit(1);
198
244
  }
199
245
 
246
+ // Integrity check — verify the downloaded binary's SHA-256 against the manifest
247
+ // on our pinned domain BEFORE making it executable or running it. Fails closed.
248
+ const allowUnverified = process.argv.includes("--allow-unverified");
249
+ try {
250
+ const expected = (CHECKSUMS[PLATFORM] || "").toLowerCase();
251
+ const actual = (await sha256File(exePath)).toLowerCase();
252
+ if (!expected) {
253
+ throw new Error(`no published checksum for platform "${PLATFORM}"`);
254
+ }
255
+ if (actual !== expected) {
256
+ try { fs.unlinkSync(exePath); } catch {}
257
+ console.log(" ✗ Integrity check FAILED — the downloaded file does not match");
258
+ console.log(" the published checksum and was deleted. Do NOT run it.");
259
+ console.log(` expected ${expected}`);
260
+ console.log(` actual ${actual}`);
261
+ console.log(" Report this to support@shardstitch.com.");
262
+ process.exit(1);
263
+ }
264
+ console.log(" ✓ Integrity verified (SHA-256).");
265
+ } catch (e) {
266
+ if (!allowUnverified) {
267
+ try { fs.unlinkSync(exePath); } catch {}
268
+ console.log(` ✗ Could not verify download integrity: ${e.message}`);
269
+ console.log(" Aborting for safety. Re-run with --allow-unverified to override");
270
+ console.log(" (not recommended), or contact support@shardstitch.com.");
271
+ process.exit(1);
272
+ }
273
+ console.log(` ! Skipping integrity check (--allow-unverified): ${e.message}`);
274
+ }
275
+
276
+ if (PLATFORM !== "windows") chmodSync(exePath, 0o755);
277
+
200
278
  saveActivation(key);
201
279
 
202
280
  console.log(` ✓ Installed to ${exePath}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shardstitch",
3
- "version": "0.0.7",
3
+ "version": "0.0.8",
4
4
  "description": "ShardStitch — capture your AI coding session (git diff, dependency graph, intent) and stitch it into the next tool. 22 AI tools supported. 100% local, zero telemetry. https://shardstitch.com",
5
5
  "homepage": "https://shardstitch.com",
6
6
  "bugs": {