promptkit-os 1.10.1 → 2.0.0
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 +6 -2
- package/bin/promptkit-os.js +72 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ If `.promptkit/` already exists and is not empty, the courier **refuses to overl
|
|
|
28
28
|
# git-submodule install
|
|
29
29
|
git submodule update --remote --merge .promptkit && bash .promptkit/init.sh
|
|
30
30
|
|
|
31
|
-
# courier install (
|
|
31
|
+
# courier install (replace the existing tree; new couriers verify the archive)
|
|
32
32
|
rm -rf .promptkit
|
|
33
33
|
npx promptkit-os@latest --balanced
|
|
34
34
|
```
|
|
@@ -37,7 +37,11 @@ The courier detects which case applies (a submodule install has `.promptkit/.git
|
|
|
37
37
|
|
|
38
38
|
## Version pinning
|
|
39
39
|
|
|
40
|
-
`npx promptkit-os@X.Y.Z` fetches
|
|
40
|
+
`npx promptkit-os@X.Y.Z` fetches the GitHub archive for tag `vX.Y.Z`, not a branch head. New couriers require a valid SHA-256 digest for their own version before download and verify the archive **before** creating `.promptkit/`, extracting files, or running the installer. The release workflow independently checks the tagged archive, injects the digest, and checks the actual npm-packed courier before publication. A missing or mismatched digest stops installation; it is not a warning-only path.
|
|
41
|
+
|
|
42
|
+
**Historical exception:** the already-published immutable npm courier `promptkit-os@1.11.0` did not contain its own archive pin and warns/proceeds by default. npm package integrity/provenance authenticates that npm tarball, **not** the second-stage GitHub archive it downloads. Existing installed files are not retroactively checked or changed by the new policy. For `1.11.0`, an operator can independently obtain a trusted archive SHA-256 and set both `PROMPTKIT_TARBALL_SHA256=<trusted-64-hex-sha256>` and `PROMPTKIT_REQUIRE_INTEGRITY_PIN=1` (in PowerShell, set `$env:PROMPTKIT_TARBALL_SHA256` and `$env:PROMPTKIT_REQUIRE_INTEGRITY_PIN`); the published courier verifies the override before extraction. New couriers also accept an explicitly supplied trusted `PROMPTKIT_TARBALL_SHA256` for archive rotation. The override is an **operator-controlled trust input**: do not derive it from the same untrusted download, and investigate a mismatch rather than disabling verification.
|
|
43
|
+
|
|
44
|
+
GitHub-generated tag archives are not guaranteed byte-for-byte immutable forever. A legitimate archive-regeneration change may require a separately verified override or a new courier release; a fixed tag name alone does not guarantee reproducible archive bytes.
|
|
41
45
|
|
|
42
46
|
## Removal
|
|
43
47
|
|
package/bin/promptkit-os.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"use strict";
|
|
10
10
|
|
|
11
11
|
const { spawnSync } = require("node:child_process");
|
|
12
|
+
const crypto = require("node:crypto");
|
|
12
13
|
const fs = require("node:fs");
|
|
13
14
|
const path = require("node:path");
|
|
14
15
|
const os = require("node:os");
|
|
@@ -17,6 +18,31 @@ const PKG = require("../package.json");
|
|
|
17
18
|
const REPO = "lowqualityloey/promptkit-os";
|
|
18
19
|
const KIT_DIR = ".promptkit";
|
|
19
20
|
|
|
21
|
+
// Pinned SHA-256 (hex) of the GitHub release tarball per courier version.
|
|
22
|
+
// This lives here — not in a sidecar file — because release-npm.yml enforces an
|
|
23
|
+
// npm-pack allowlist of exactly { package.json, README.md, bin/promptkit-os.js };
|
|
24
|
+
// a sidecar would either be excluded from the published package (useless) or
|
|
25
|
+
// require a workflow change to allowlist.
|
|
26
|
+
//
|
|
27
|
+
// Release pins are inserted and checked by the repository release workflow after
|
|
28
|
+
// it checks out the tag and before npm publication. Do not mint pins by hand. A
|
|
29
|
+
// missing or invalid own-version pin fails closed before download. An explicitly
|
|
30
|
+
// supplied PROMPTKIT_TARBALL_SHA256 overrides the map as an operator-controlled
|
|
31
|
+
// trust input for testing or archive rotation.
|
|
32
|
+
//
|
|
33
|
+
// CAVEAT: these digests pin GitHub's *generated* archive for a tag. That archive
|
|
34
|
+
// is reproducible today (verified by double download when each pin was minted)
|
|
35
|
+
// but is not contractually immutable — a future GitHub-side change to archive
|
|
36
|
+
// generation would change the bytes and make this pin reject an untampered
|
|
37
|
+
// download. On mismatch, confirm the release tag and the release evidence
|
|
38
|
+
// record before treating it as tampering; rotate with PROMPTKIT_TARBALL_SHA256.
|
|
39
|
+
const TARBALL_SHA256_BY_VERSION = {
|
|
40
|
+
"1.10.1": "1e55459914a1c46aa71df8f9cb8ce4c9ff37d1c02d67f70e5607b3bfd4f48d4b",
|
|
41
|
+
"1.11.0": "6503fdfcde286055b8c053aa0ffa8bdf244cf77df3321cccd6a8d8f780195ade",
|
|
42
|
+
"2.0.0": "4fb5b9d85e0f19bfe333c361e00465705b60636a8026edd62c15c6f2d61cbf4b",
|
|
43
|
+
// @pin-insert
|
|
44
|
+
};
|
|
45
|
+
|
|
20
46
|
function die(msg) {
|
|
21
47
|
process.stderr.write(`[promptkit-os] ${msg}\n`);
|
|
22
48
|
process.exit(1);
|
|
@@ -78,6 +104,38 @@ function projectRootFrom(args) {
|
|
|
78
104
|
return path.resolve(positional[0] || process.cwd());
|
|
79
105
|
}
|
|
80
106
|
|
|
107
|
+
function normalizedDigest(digest, version) {
|
|
108
|
+
if (typeof digest !== "string" || !/^[0-9a-fA-F]{64}$/.test(digest)) {
|
|
109
|
+
die(`invalid SHA-256 digest for v${version} — expected exactly 64 hexadecimal characters; refusing to download, extract or execute`);
|
|
110
|
+
}
|
|
111
|
+
return digest.toLowerCase();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function expectedTarballDigest(version) {
|
|
115
|
+
const override = process.env.PROMPTKIT_TARBALL_SHA256;
|
|
116
|
+
const digest = override === undefined ? TARBALL_SHA256_BY_VERSION[version] : override;
|
|
117
|
+
if (digest === undefined) {
|
|
118
|
+
die(`no integrity pin for v${version} — refusing to download, extract or execute; supply a trusted PROMPTKIT_TARBALL_SHA256`);
|
|
119
|
+
}
|
|
120
|
+
return normalizedDigest(digest, version);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Aborts (non-zero exit, no extract, no exec) unless `tarball` hashes to
|
|
124
|
+
// `expectedHex`. Comparison is constant-time. Must be called after download()
|
|
125
|
+
// and before extract().
|
|
126
|
+
function verifyTarballIntegrity(tarball, expectedHex, version) {
|
|
127
|
+
const expected = Buffer.from(normalizedDigest(expectedHex, version), "hex");
|
|
128
|
+
const actual = crypto.createHash("sha256").update(tarball).digest();
|
|
129
|
+
if (expected.length !== actual.length || !crypto.timingSafeEqual(actual, expected)) {
|
|
130
|
+
die(
|
|
131
|
+
`tarball integrity mismatch for v${version} ` +
|
|
132
|
+
`(expected sha256 ${expectedHex}, got ${actual.toString("hex")}): ` +
|
|
133
|
+
"refusing to extract or execute — the download may have been tampered with. " +
|
|
134
|
+
"If this version was just released, the pin in TARBALL_SHA256_BY_VERSION needs updating (see above)."
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
81
139
|
async function download(url) {
|
|
82
140
|
process.stderr.write(`[promptkit-os] fetching ${url}\n`);
|
|
83
141
|
const res = await fetch(url, { redirect: "follow" });
|
|
@@ -111,6 +169,10 @@ function runInstaller(kitDir, args) {
|
|
|
111
169
|
}
|
|
112
170
|
}
|
|
113
171
|
|
|
172
|
+
function installerArgs(args, version) {
|
|
173
|
+
return [...args, `--engine-version=v${version}`];
|
|
174
|
+
}
|
|
175
|
+
|
|
114
176
|
async function main() {
|
|
115
177
|
const version = PKG.version;
|
|
116
178
|
if (version === "0.0.0") {
|
|
@@ -147,13 +209,20 @@ async function main() {
|
|
|
147
209
|
const url = `https://github.com/${REPO}/archive/refs/tags/v${version}.tar.gz`;
|
|
148
210
|
const kitDir = path.join(root, KIT_DIR);
|
|
149
211
|
assertInstallTargetIsClean(kitDir, force);
|
|
150
|
-
fs.mkdirSync(kitDir, { recursive: true });
|
|
151
212
|
|
|
213
|
+
const expectedDigest = expectedTarballDigest(version);
|
|
152
214
|
const tarball = await download(url);
|
|
215
|
+
verifyTarballIntegrity(tarball, expectedDigest, version);
|
|
216
|
+
process.stderr.write(`[promptkit-os] verified release tarball integrity (sha256) for v${version}\n`);
|
|
217
|
+
fs.mkdirSync(kitDir, { recursive: true });
|
|
153
218
|
await extract(tarball, kitDir);
|
|
154
219
|
process.stderr.write(`[promptkit-os] extracted release v${version} into ${KIT_DIR}/\n`);
|
|
155
220
|
|
|
156
|
-
runInstaller(kitDir, args);
|
|
221
|
+
runInstaller(kitDir, installerArgs(args, version));
|
|
157
222
|
}
|
|
158
223
|
|
|
159
|
-
|
|
224
|
+
if (require.main === module) {
|
|
225
|
+
main().catch((err) => die(err && err.message ? err.message : String(err)));
|
|
226
|
+
} else {
|
|
227
|
+
module.exports = { verifyTarballIntegrity, expectedTarballDigest, TARBALL_SHA256_BY_VERSION, installerArgs };
|
|
228
|
+
}
|
package/package.json
CHANGED