@treeship/cli-linux-x64 0.9.11 → 0.10.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @treeship/cli-linux-x64
2
2
 
3
- Platform-specific binary for the Treeship CLI on Linux x86_64.
3
+ Platform-specific binary for the Treeship CLI on Linux x86_64 (GNU).
4
4
 
5
5
  This package is **not meant to be installed directly**. It is automatically downloaded when you run:
6
6
 
@@ -10,6 +10,19 @@ npm install -g treeship
10
10
 
11
11
  The main `treeship` package detects your platform and pulls the correct binary.
12
12
 
13
+ ## Binary integrity
14
+
15
+ The package ships a single-line `expected-checksum.txt` with the SHA-256 of the binary it downloads from the matching GitHub Release. The postinstall script:
16
+
17
+ 1. Reads the expected hash from the package (delivered via npm).
18
+ 2. Downloads the binary from `https://github.com/zerkerlabs/treeship/releases/download/v<version>/treeship-linux-x86_64`.
19
+ 3. Verifies the downloaded bytes match the expected hash.
20
+ 4. Atomically renames the partial download into place only on match. On mismatch, the partial file is deleted and install fails (exit 1).
21
+
22
+ Because the hash is delivered via npm and the binary is delivered via GitHub Releases, an attacker would need to compromise *both* trust roots simultaneously to slip a tampered binary past install. A compromise of either alone produces a verification failure.
23
+
24
+ If the package was published without `expected-checksum.txt`, the postinstall refuses to install. That's intentional: a binary with no published checksum is unverifiable.
25
+
13
26
  ## Repository
14
27
 
15
- [github.com/nicholasgriffintn/treeship](https://github.com/nicholasgriffintn/treeship)
28
+ [github.com/zerkerlabs/treeship](https://github.com/zerkerlabs/treeship)
@@ -0,0 +1 @@
1
+ 479f2fafc2de1fc48598c3e71615d8d41548964fe36220389f7b7553b74a68a8
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@treeship/cli-linux-x64",
3
- "version": "0.9.11",
3
+ "version": "0.10.1",
4
4
  "description": "Treeship CLI binary for linux x64",
5
5
  "os": [
6
6
  "linux"
@@ -9,6 +9,12 @@
9
9
  "x64"
10
10
  ],
11
11
  "license": "Apache-2.0",
12
+ "files": [
13
+ "postinstall.js",
14
+ "binary.js",
15
+ "expected-checksum.txt",
16
+ "README.md"
17
+ ],
12
18
  "scripts": {
13
19
  "postinstall": "node postinstall.js"
14
20
  },
package/postinstall.js CHANGED
@@ -1,41 +1,138 @@
1
- const https = require('https');
1
+ // @treeship/cli-darwin-arm64 -- platform binary postinstall.
2
+ //
3
+ // Downloads the Treeship CLI binary from the matching GitHub Release
4
+ // and verifies its SHA-256 against the expected hash that ships
5
+ // inside this npm package. Both halves of the trust path matter:
6
+ //
7
+ // 1. The expected hash arrives via npm (signed by npm's package
8
+ // integrity, separate trust root from GitHub).
9
+ // 2. The binary arrives via GitHub Releases.
10
+ // 3. If either is tampered, the hashes don't match and we abort.
11
+ //
12
+ // Earlier versions (<= 0.10.0) downloaded the binary, chmod +x'd it,
13
+ // and exited 0 -- no integrity check, and an install failure also
14
+ // exited 0 (claiming success). This file is the v0.10.1 hardening.
15
+
16
+ 'use strict';
17
+
2
18
  const fs = require('fs');
3
19
  const path = require('path');
20
+ const https = require('https');
21
+ const crypto = require('crypto');
4
22
 
5
23
  const VERSION = require('./package.json').version;
6
24
  const BINARY_NAME = 'treeship-linux-x86_64';
7
25
  const URL = 'https://github.com/zerkerlabs/treeship/releases/download/v' + VERSION + '/' + BINARY_NAME;
8
- const DEST = path.join(__dirname, 'bin', 'treeship');
26
+ const BIN_DIR = path.join(__dirname, 'bin');
27
+ const DEST = path.join(BIN_DIR, 'treeship');
28
+ const PARTIAL = DEST + '.partial';
29
+ const CHECKSUM_FILE = path.join(__dirname, 'expected-checksum.txt');
9
30
 
10
- if (fs.existsSync(DEST)) process.exit(0);
31
+ // Read the expected SHA-256 that the release pipeline embedded in this
32
+ // package. Format: a single line, lowercase hex, 64 chars. If absent
33
+ // or malformed, refuse to install -- a binary without a published
34
+ // checksum is an unverifiable binary.
35
+ function readExpectedChecksum() {
36
+ let raw;
37
+ try {
38
+ raw = fs.readFileSync(CHECKSUM_FILE, 'utf8');
39
+ } catch (e) {
40
+ return null;
41
+ }
42
+ const hex = raw.trim().toLowerCase();
43
+ if (!/^[0-9a-f]{64}$/.test(hex)) return null;
44
+ return hex;
45
+ }
11
46
 
12
- fs.mkdirSync(path.join(__dirname, 'bin'), { recursive: true });
47
+ function abort(reason, code) {
48
+ // Always delete the partial download so a retry doesn't think it
49
+ // already has the binary.
50
+ try { if (fs.existsSync(PARTIAL)) fs.unlinkSync(PARTIAL); } catch {}
51
+ console.error('treeship: install failed -- ' + reason);
52
+ console.error(' Recover:');
53
+ console.error(' 1. Re-run the install (transient network/CDN issues clear up):');
54
+ console.error(' npm install -g treeship');
55
+ console.error(' 2. Or download the binary directly and place it on PATH:');
56
+ console.error(' https://github.com/zerkerlabs/treeship/releases/tag/v' + VERSION);
57
+ console.error(' 3. Or build from source:');
58
+ console.error(' git clone https://github.com/zerkerlabs/treeship');
59
+ console.error(' cd treeship && cargo build --release -p treeship-cli');
60
+ process.exit(code || 1);
61
+ }
13
62
 
14
- console.log('treeship: downloading ' + BINARY_NAME + '...');
63
+ const expected = readExpectedChecksum();
64
+ if (!expected) {
65
+ abort(
66
+ 'expected-checksum.txt missing or malformed in ' + path.basename(__dirname) +
67
+ '. This npm package was published without a binary integrity hash and ' +
68
+ 'cannot be installed safely. File an issue at ' +
69
+ 'https://github.com/zerkerlabs/treeship/issues so we can re-publish it.',
70
+ 1,
71
+ );
72
+ }
15
73
 
16
- function download(url) {
17
- https.get(url, (res) => {
18
- if (res.statusCode === 302 || res.statusCode === 301) {
19
- download(res.headers.location);
20
- return;
74
+ if (fs.existsSync(DEST)) {
75
+ // Already installed and verified previously -- nothing to do.
76
+ process.exit(0);
77
+ }
78
+
79
+ fs.mkdirSync(BIN_DIR, { recursive: true });
80
+ console.log('treeship: downloading ' + BINARY_NAME + ' v' + VERSION + ' ...');
81
+
82
+ function download(url, redirects) {
83
+ if (redirects > 5) return abort('too many redirects', 1);
84
+
85
+ const req = https.get(url, (res) => {
86
+ if (res.statusCode === 301 || res.statusCode === 302 || res.statusCode === 307 || res.statusCode === 308) {
87
+ res.resume();
88
+ return download(res.headers.location, redirects + 1);
21
89
  }
22
90
  if (res.statusCode !== 200) {
23
- console.error('treeship: download failed (' + res.statusCode + ')');
24
- console.error('Install manually: cargo install treeship-cli');
25
- process.exit(0);
91
+ res.resume();
92
+ return abort('download HTTP ' + res.statusCode + ' from ' + url, 1);
26
93
  }
27
- const file = fs.createWriteStream(DEST);
28
- res.pipe(file);
29
- file.on('finish', () => {
30
- file.close();
31
- fs.chmodSync(DEST, 0o755);
32
- console.log('treeship: installed');
94
+
95
+ const hash = crypto.createHash('sha256');
96
+ const file = fs.createWriteStream(PARTIAL);
97
+
98
+ res.on('data', (chunk) => {
99
+ hash.update(chunk);
100
+ file.write(chunk);
101
+ });
102
+ res.on('end', () => {
103
+ file.end(() => {
104
+ const got = hash.digest('hex');
105
+ if (got !== expected) {
106
+ // Delete the partial file before aborting so a retry can't
107
+ // pick up the bad bytes.
108
+ try { fs.unlinkSync(PARTIAL); } catch {}
109
+ return abort(
110
+ 'SHA-256 mismatch.\n' +
111
+ ' expected: ' + expected + '\n' +
112
+ ' got: ' + got + '\n' +
113
+ ' This means either the GitHub Release was tampered with, ' +
114
+ 'a CDN cached a stale or malicious binary, or the npm package ' +
115
+ 'and the release are out of sync. Do not run the binary.',
116
+ 1,
117
+ );
118
+ }
119
+ try {
120
+ fs.renameSync(PARTIAL, DEST);
121
+ fs.chmodSync(DEST, 0o755);
122
+ } catch (e) {
123
+ return abort('could not finalize binary: ' + e.message, 1);
124
+ }
125
+ console.log('treeship: installed (sha256 verified)');
126
+ });
33
127
  });
34
- }).on('error', () => {
35
- console.error('treeship: download failed');
36
- console.error('Install manually: cargo install treeship-cli');
37
- process.exit(0);
128
+ res.on('error', (e) => abort('stream error: ' + e.message, 1));
129
+ file.on('error', (e) => abort('write error: ' + e.message, 1));
130
+ });
131
+ req.on('error', (e) => abort('connection error: ' + e.message, 1));
132
+ req.setTimeout(60_000, () => {
133
+ req.destroy();
134
+ abort('download timed out after 60s', 1);
38
135
  });
39
136
  }
40
137
 
41
- download(URL);
138
+ download(URL, 0);