@treeship/cli-darwin-arm64 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 +14 -1
- package/expected-checksum.txt +1 -0
- package/package.json +7 -1
- package/postinstall.js +121 -24
package/README.md
CHANGED
|
@@ -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-darwin-aarch64`.
|
|
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/
|
|
28
|
+
[github.com/zerkerlabs/treeship](https://github.com/zerkerlabs/treeship)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
e4c809e2094fcf8dee0df60734bb06c510cda7d41282c7cb8f6c1610bb57ff61
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@treeship/cli-darwin-arm64",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.1",
|
|
4
4
|
"description": "Treeship CLI binary for darwin arm64",
|
|
5
5
|
"os": [
|
|
6
6
|
"darwin"
|
|
@@ -9,6 +9,12 @@
|
|
|
9
9
|
"arm64"
|
|
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
|
-
|
|
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-darwin-aarch64';
|
|
7
25
|
const URL = 'https://github.com/zerkerlabs/treeship/releases/download/v' + VERSION + '/' + BINARY_NAME;
|
|
8
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
process.exit(0);
|
|
91
|
+
res.resume();
|
|
92
|
+
return abort('download HTTP ' + res.statusCode + ' from ' + url, 1);
|
|
26
93
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
file.
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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);
|