@indigoai-us/hq-cli 5.33.0 → 5.34.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/dist/commands/__fixtures__/make-tar.d.ts +46 -0
- package/dist/commands/__fixtures__/make-tar.js +105 -0
- package/dist/commands/master-sync.d.ts +11 -0
- package/dist/commands/master-sync.js +15 -0
- package/dist/commands/pack-install.d.ts +187 -1
- package/dist/commands/pack-install.js +405 -16
- package/dist/commands/packs.js +9 -4
- package/dist/commands/publish.d.ts +186 -0
- package/dist/commands/publish.js +375 -0
- package/dist/commands/rescue.d.ts +33 -0
- package/dist/commands/rescue.js +161 -0
- package/dist/commands/safe-extract.d.ts +154 -0
- package/dist/commands/safe-extract.js +347 -0
- package/dist/index.js +17 -2
- package/dist/lib/local-tree-diff.d.ts +21 -0
- package/dist/lib/local-tree-diff.js +18 -3
- package/dist/types.d.ts +22 -0
- package/dist/utils/vault-api.d.ts +11 -0
- package/dist/utils/vault-api.js +39 -2
- package/package.json +2 -2
- package/src/commands/__fixtures__/make-tar.ts +126 -0
- package/src/commands/artifact-verify.test.ts +177 -0
- package/src/commands/marketplace-install.test.ts +414 -0
- package/src/commands/marketplace-security.test.ts +646 -0
- package/src/commands/master-sync.ts +23 -0
- package/src/commands/pack-install.test.ts +209 -1
- package/src/commands/pack-install.ts +617 -15
- package/src/commands/packs.ts +8 -1
- package/src/commands/publish.test.ts +538 -0
- package/src/commands/publish.ts +517 -0
- package/src/commands/rescue.test.ts +39 -0
- package/src/commands/rescue.ts +210 -0
- package/src/commands/safe-extract.test.ts +459 -0
- package/src/commands/safe-extract.ts +444 -0
- package/src/index.ts +18 -0
- package/src/lib/local-tree-diff.test.ts +19 -0
- package/src/lib/local-tree-diff.ts +17 -1
- package/src/types.ts +23 -0
- package/src/utils/vault-api.ts +41 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic, platform-independent malicious-tar fixture builder.
|
|
3
|
+
*
|
|
4
|
+
* WHY THIS EXISTS (cross-platform zip-slip coverage). The adversarial
|
|
5
|
+
* safe-extract / marketplace-security suites need archives that GENUINELY carry
|
|
6
|
+
* hostile entries — a `../` traversal name, an absolute path, an escaping
|
|
7
|
+
* symlink/hardlink. The obvious approach (shell out to the system `tar` to
|
|
8
|
+
* CREATE the archive) is NOT portable: GNU tar (Linux / CI / prod Lambdas)
|
|
9
|
+
* STRIPS the leading `../` and absolute `/` when it WRITES an archive, so the
|
|
10
|
+
* resulting "malicious" fixture contains a benign `evil-escape` entry and the
|
|
11
|
+
* guard has nothing to reject — the test passes on macOS bsdtar (which
|
|
12
|
+
* preserves them) but fails on Linux. The guard itself is correct; only the
|
|
13
|
+
* fixture construction was platform-dependent.
|
|
14
|
+
*
|
|
15
|
+
* Fix: WRITE THE TAR BYTES DIRECTLY here. A tar archive is just a sequence of
|
|
16
|
+
* 512-byte ustar headers, each optionally followed by the file's content padded
|
|
17
|
+
* to a 512-byte boundary, terminated by two zero blocks. By emitting the bytes
|
|
18
|
+
* ourselves we control the recorded entry names verbatim — `../evil-escape`,
|
|
19
|
+
* `/tmp/evil`, a symlink with linkname `../../etc/passwd` — identically on every
|
|
20
|
+
* platform, with no create-time stripping. The reading side (`tar -tvf`, which
|
|
21
|
+
* safeExtractTarball's pre-flight uses) does NOT strip anything; it faithfully
|
|
22
|
+
* lists whatever bytes we wrote, so the guard sees the real attack everywhere.
|
|
23
|
+
*/
|
|
24
|
+
/** ustar typeflag values we use. */
|
|
25
|
+
export type TarType = 'file' | 'symlink' | 'hardlink';
|
|
26
|
+
export interface TarEntrySpec {
|
|
27
|
+
/** Recorded entry name — written VERBATIM (may contain `..`, be absolute…). */
|
|
28
|
+
name: string;
|
|
29
|
+
type?: TarType;
|
|
30
|
+
/** For sym/hardlinks: the recorded link target (also written verbatim). */
|
|
31
|
+
linkname?: string;
|
|
32
|
+
/** File content (regular files only). */
|
|
33
|
+
content?: string | Buffer;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Build one 512-byte ustar header (plus content blocks for regular files) for a
|
|
37
|
+
* single entry. The header checksum is computed exactly per spec: sum every
|
|
38
|
+
* header byte treating the 8 checksum bytes themselves as ASCII spaces, then
|
|
39
|
+
* write that sum as 6 octal digits + NUL + space.
|
|
40
|
+
*/
|
|
41
|
+
export declare function makeTarEntry(spec: TarEntrySpec): Buffer;
|
|
42
|
+
/** Concatenate entries and append the two zero blocks that end every tar. */
|
|
43
|
+
export declare function makeTar(entries: TarEntrySpec[]): Buffer;
|
|
44
|
+
/** Build the tar, gzip it, and write it to `outPath`. Returns `outPath`. */
|
|
45
|
+
export declare function writeMaliciousTarGz(outPath: string, entries: TarEntrySpec[]): string;
|
|
46
|
+
//# sourceMappingURL=make-tar.d.ts.map
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic, platform-independent malicious-tar fixture builder.
|
|
3
|
+
*
|
|
4
|
+
* WHY THIS EXISTS (cross-platform zip-slip coverage). The adversarial
|
|
5
|
+
* safe-extract / marketplace-security suites need archives that GENUINELY carry
|
|
6
|
+
* hostile entries — a `../` traversal name, an absolute path, an escaping
|
|
7
|
+
* symlink/hardlink. The obvious approach (shell out to the system `tar` to
|
|
8
|
+
* CREATE the archive) is NOT portable: GNU tar (Linux / CI / prod Lambdas)
|
|
9
|
+
* STRIPS the leading `../` and absolute `/` when it WRITES an archive, so the
|
|
10
|
+
* resulting "malicious" fixture contains a benign `evil-escape` entry and the
|
|
11
|
+
* guard has nothing to reject — the test passes on macOS bsdtar (which
|
|
12
|
+
* preserves them) but fails on Linux. The guard itself is correct; only the
|
|
13
|
+
* fixture construction was platform-dependent.
|
|
14
|
+
*
|
|
15
|
+
* Fix: WRITE THE TAR BYTES DIRECTLY here. A tar archive is just a sequence of
|
|
16
|
+
* 512-byte ustar headers, each optionally followed by the file's content padded
|
|
17
|
+
* to a 512-byte boundary, terminated by two zero blocks. By emitting the bytes
|
|
18
|
+
* ourselves we control the recorded entry names verbatim — `../evil-escape`,
|
|
19
|
+
* `/tmp/evil`, a symlink with linkname `../../etc/passwd` — identically on every
|
|
20
|
+
* platform, with no create-time stripping. The reading side (`tar -tvf`, which
|
|
21
|
+
* safeExtractTarball's pre-flight uses) does NOT strip anything; it faithfully
|
|
22
|
+
* lists whatever bytes we wrote, so the guard sees the real attack everywhere.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="3b6b9745-1cda-547f-a99d-34b70ea052c8")}catch(e){}}();
|
|
26
|
+
import { gzipSync } from 'node:zlib';
|
|
27
|
+
import * as fs from 'node:fs';
|
|
28
|
+
const BLOCK = 512;
|
|
29
|
+
const TYPEFLAG = {
|
|
30
|
+
file: '0', // regular file
|
|
31
|
+
symlink: '2', // symbolic link
|
|
32
|
+
hardlink: '1', // hard link
|
|
33
|
+
};
|
|
34
|
+
/** Write an ASCII string into a fixed-width field at `offset`, NUL-padded. */
|
|
35
|
+
function writeField(buf, offset, width, value) {
|
|
36
|
+
// Truncate defensively; ustar fields are fixed-width.
|
|
37
|
+
const s = value.slice(0, width);
|
|
38
|
+
buf.write(s, offset, 'ascii');
|
|
39
|
+
// Remaining bytes are already 0 from Buffer.alloc.
|
|
40
|
+
}
|
|
41
|
+
/** Write an octal numeric field: `width-1` octal digits, space-or-NUL terminated. */
|
|
42
|
+
function writeOctal(buf, offset, width, value) {
|
|
43
|
+
// Classic ustar numeric field: zero-padded octal in (width-1) chars + NUL.
|
|
44
|
+
const digits = width - 1;
|
|
45
|
+
const oct = value.toString(8).padStart(digits, '0').slice(-digits);
|
|
46
|
+
buf.write(oct, offset, 'ascii');
|
|
47
|
+
buf[offset + digits] = 0; // NUL terminator
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Build one 512-byte ustar header (plus content blocks for regular files) for a
|
|
51
|
+
* single entry. The header checksum is computed exactly per spec: sum every
|
|
52
|
+
* header byte treating the 8 checksum bytes themselves as ASCII spaces, then
|
|
53
|
+
* write that sum as 6 octal digits + NUL + space.
|
|
54
|
+
*/
|
|
55
|
+
export function makeTarEntry(spec) {
|
|
56
|
+
const type = spec.type ?? 'file';
|
|
57
|
+
const content = type === 'file'
|
|
58
|
+
? Buffer.isBuffer(spec.content)
|
|
59
|
+
? spec.content
|
|
60
|
+
: Buffer.from(spec.content ?? '', 'utf-8')
|
|
61
|
+
: Buffer.alloc(0); // links carry no content payload
|
|
62
|
+
const header = Buffer.alloc(BLOCK); // zero-filled
|
|
63
|
+
writeField(header, 0, 100, spec.name); // name
|
|
64
|
+
writeOctal(header, 100, 8, 0o644); // mode
|
|
65
|
+
writeOctal(header, 108, 8, 0); // uid
|
|
66
|
+
writeOctal(header, 116, 8, 0); // gid
|
|
67
|
+
writeOctal(header, 124, 12, content.length); // size (0 for links)
|
|
68
|
+
writeOctal(header, 136, 12, 0); // mtime (deterministic: epoch)
|
|
69
|
+
// checksum field (148, 8) — filled below; start as spaces for the sum.
|
|
70
|
+
header.fill(' '.charCodeAt(0), 148, 156);
|
|
71
|
+
writeField(header, 156, 1, TYPEFLAG[type]); // typeflag
|
|
72
|
+
if (spec.linkname !== undefined) {
|
|
73
|
+
writeField(header, 157, 100, spec.linkname); // linkname (verbatim)
|
|
74
|
+
}
|
|
75
|
+
writeField(header, 257, 6, 'ustar'); // magic "ustar\0"
|
|
76
|
+
writeField(header, 263, 2, '00'); // version "00"
|
|
77
|
+
// uname/gname left empty; devmajor/devminor zero (already NUL).
|
|
78
|
+
// Header checksum: unsigned sum of all 512 bytes (with the checksum field as
|
|
79
|
+
// spaces, which we set above). Written as 6 octal digits, NUL, space.
|
|
80
|
+
let sum = 0;
|
|
81
|
+
for (let i = 0; i < BLOCK; i++)
|
|
82
|
+
sum += header[i];
|
|
83
|
+
const cksum = sum.toString(8).padStart(6, '0').slice(-6);
|
|
84
|
+
header.write(cksum, 148, 'ascii');
|
|
85
|
+
header[154] = 0; // NUL
|
|
86
|
+
header[155] = ' '.charCodeAt(0); // space
|
|
87
|
+
if (type !== 'file' || content.length === 0)
|
|
88
|
+
return header;
|
|
89
|
+
// Content padded up to a 512-byte boundary.
|
|
90
|
+
const pad = (BLOCK - (content.length % BLOCK)) % BLOCK;
|
|
91
|
+
return Buffer.concat([header, content, Buffer.alloc(pad)]);
|
|
92
|
+
}
|
|
93
|
+
/** Concatenate entries and append the two zero blocks that end every tar. */
|
|
94
|
+
export function makeTar(entries) {
|
|
95
|
+
const blocks = entries.map(makeTarEntry);
|
|
96
|
+
blocks.push(Buffer.alloc(BLOCK * 2)); // end-of-archive marker
|
|
97
|
+
return Buffer.concat(blocks);
|
|
98
|
+
}
|
|
99
|
+
/** Build the tar, gzip it, and write it to `outPath`. Returns `outPath`. */
|
|
100
|
+
export function writeMaliciousTarGz(outPath, entries) {
|
|
101
|
+
fs.writeFileSync(outPath, gzipSync(makeTar(entries)));
|
|
102
|
+
return outPath;
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=make-tar.js.map
|
|
105
|
+
//# debugId=3b6b9745-1cda-547f-a99d-34b70ea052c8
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hq master-sync — surface namespaced skills, mirror the personal overlay into
|
|
3
|
+
* core/, and regenerate the workers registry.
|
|
4
|
+
*
|
|
5
|
+
* Thin wrapper over @indigoai-us/hq-cloud's masterSync(), which execs the
|
|
6
|
+
* bundled scripts/master-sync.sh against the HQ root. This command is what the
|
|
7
|
+
* hq-core master-sync hook shim calls on Stop / PostToolUse.
|
|
8
|
+
*/
|
|
9
|
+
import { Command } from 'commander';
|
|
10
|
+
export declare function registerMasterSyncCommand(program: Command): void;
|
|
11
|
+
//# sourceMappingURL=master-sync.d.ts.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
|
|
2
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="c5486cda-a7fa-5274-ab0a-a61e1958aac7")}catch(e){}}();
|
|
3
|
+
import { masterSync } from '@indigoai-us/hq-cloud';
|
|
4
|
+
export function registerMasterSyncCommand(program) {
|
|
5
|
+
program
|
|
6
|
+
.command('master-sync')
|
|
7
|
+
.description('Surface namespaced skills, mirror the personal overlay into core/, and regenerate the workers registry')
|
|
8
|
+
.option('--repo-root <path>', 'HQ root to operate on (defaults to the current directory)')
|
|
9
|
+
.action((opts) => {
|
|
10
|
+
const { status } = masterSync({ repoRoot: opts.repoRoot });
|
|
11
|
+
process.exit(status);
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=master-sync.js.map
|
|
15
|
+
//# debugId=c5486cda-a7fa-5274-ab0a-a61e1958aac7
|
|
@@ -33,8 +33,11 @@
|
|
|
33
33
|
* registry file under the v12 layout. (`hq update <pack>` re-resolves source
|
|
34
34
|
* from each pack's package.yaml; rationale lives in the layout-fix PR.)
|
|
35
35
|
*/
|
|
36
|
+
import { type KeyObject } from 'node:crypto';
|
|
36
37
|
import type { PackManifest } from '../types.js';
|
|
37
|
-
export type Transport = 'npm' | 'git' | 'local';
|
|
38
|
+
export type Transport = 'npm' | 'git' | 'local' | 'marketplace';
|
|
39
|
+
/** Prefix that routes a source through the HQ marketplace transport (US-006). */
|
|
40
|
+
export declare const MARKETPLACE_PREFIX = "marketplace:";
|
|
38
41
|
export declare function classify(source: string): Transport;
|
|
39
42
|
/**
|
|
40
43
|
* Parse a git source's '#<...>' fragment into { url, subpath, ref }.
|
|
@@ -52,6 +55,112 @@ export declare function parseGitFragment(source: string): {
|
|
|
52
55
|
* back to the legacy registry flow.
|
|
53
56
|
*/
|
|
54
57
|
export declare function sourceMatchesPackPattern(source: string): boolean;
|
|
58
|
+
interface FetchResult {
|
|
59
|
+
payloadDir: string;
|
|
60
|
+
resolvedSource: string;
|
|
61
|
+
resolvedSha?: string;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Optional marketplace integrity metadata for a tarball-based install (US-021).
|
|
65
|
+
* When present, `verifyArtifact` runs over the downloaded tarball bytes BEFORE
|
|
66
|
+
* `safeExtractTarball` — a hash/signature mismatch aborts the install before
|
|
67
|
+
* any byte is unpacked. US-006 populates this from the listing detail; the npm
|
|
68
|
+
* fetch path also honors it so a registry-fetched tarball can be verified.
|
|
69
|
+
*/
|
|
70
|
+
export interface ArtifactIntegrity {
|
|
71
|
+
expectedHash: string;
|
|
72
|
+
signature?: string;
|
|
73
|
+
publicKey?: string;
|
|
74
|
+
requireSignature?: boolean;
|
|
75
|
+
}
|
|
76
|
+
/** Parsed `marketplace:<slug>[@version]` source. */
|
|
77
|
+
export declare function parseMarketplaceSource(source: string): {
|
|
78
|
+
slug: string;
|
|
79
|
+
version?: string;
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* A resolved marketplace listing detail — the public `GET /v1/listings/{id}`
|
|
83
|
+
* shape (US-005) reduced to the fields the installer needs. The presigned
|
|
84
|
+
* `downloadUrl` is short-lived; `listingId` lets us re-resolve a fresh one if
|
|
85
|
+
* it expires mid-download.
|
|
86
|
+
*/
|
|
87
|
+
export interface MarketplaceListing {
|
|
88
|
+
listingId: string;
|
|
89
|
+
slug: string;
|
|
90
|
+
version?: string;
|
|
91
|
+
/** Short-lived presigned S3 URL for the tarball. */
|
|
92
|
+
downloadUrl: string;
|
|
93
|
+
/** Lowercase-hex sha256 the listing pinned + approval is bound to (US-021). */
|
|
94
|
+
contentHash: string;
|
|
95
|
+
/** Hash algorithm the server reported (e.g. `sha256`), when present. */
|
|
96
|
+
contentHashAlg?: string;
|
|
97
|
+
/** Base64 Ed25519 signature over the contentHash (optional during key-defer). */
|
|
98
|
+
signature?: string;
|
|
99
|
+
/** Identifier of the signing key the server used, when present. */
|
|
100
|
+
signingKeyId?: string;
|
|
101
|
+
/** Platform public key (PEM/SPKI) to verify the signature against. */
|
|
102
|
+
publicKey?: string;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Network seams for the marketplace transport, dependency-injected so unit
|
|
106
|
+
* tests can mock resolve + download without real HTTP/S3. Production wiring
|
|
107
|
+
* lives in `defaultMarketplaceDeps`.
|
|
108
|
+
*/
|
|
109
|
+
export interface MarketplaceDeps {
|
|
110
|
+
/**
|
|
111
|
+
* Resolve `slug[@version]` to a listing detail (id + presigned URL + the
|
|
112
|
+
* approved hash/signature/publicKey). Throws if no approved listing matches.
|
|
113
|
+
*/
|
|
114
|
+
resolveListing: (slug: string, version?: string) => Promise<MarketplaceListing>;
|
|
115
|
+
/** Re-fetch the listing detail by id to mint a FRESH presigned URL. */
|
|
116
|
+
refreshListing: (listingId: string) => Promise<MarketplaceListing>;
|
|
117
|
+
/** Download the tarball bytes from a presigned URL. Returns null on 403/expired. */
|
|
118
|
+
download: (url: string) => Promise<Uint8Array | {
|
|
119
|
+
expired: true;
|
|
120
|
+
}>;
|
|
121
|
+
}
|
|
122
|
+
interface RawListingSummary {
|
|
123
|
+
listingId?: string;
|
|
124
|
+
id?: string;
|
|
125
|
+
slug?: string;
|
|
126
|
+
version?: string;
|
|
127
|
+
latestVersion?: string;
|
|
128
|
+
status?: string;
|
|
129
|
+
}
|
|
130
|
+
interface RawListingDetailFields extends RawListingSummary {
|
|
131
|
+
downloadUrl?: string;
|
|
132
|
+
url?: string;
|
|
133
|
+
contentHash?: string;
|
|
134
|
+
contentHashAlg?: string;
|
|
135
|
+
sha256?: string;
|
|
136
|
+
signature?: string;
|
|
137
|
+
signingKeyId?: string;
|
|
138
|
+
publicKey?: string;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* `GET /v1/listings/{id}` returns the listing WRAPPED in an envelope
|
|
142
|
+
* (`{ listing: { id, downloadUrl, contentHash, ... } }`). Older/back-compat
|
|
143
|
+
* responses returned the listing fields at the top level, so we accept both.
|
|
144
|
+
*/
|
|
145
|
+
interface RawListingDetail extends RawListingDetailFields {
|
|
146
|
+
listing?: RawListingDetailFields;
|
|
147
|
+
}
|
|
148
|
+
/** Map a raw `GET /v1/listings/{id}` body into a MarketplaceListing. */
|
|
149
|
+
export declare function toMarketplaceListing(raw: RawListingDetail): MarketplaceListing;
|
|
150
|
+
/** Default production marketplace deps — public listings API + presigned S3. */
|
|
151
|
+
export declare function defaultMarketplaceDeps(): MarketplaceDeps;
|
|
152
|
+
/**
|
|
153
|
+
* Fetch + verify + safe-extract a marketplace pack. Mirrors `fetchNpm`'s
|
|
154
|
+
* contract (returns a payloadDir the caller mvs into core/packages/), but
|
|
155
|
+
* resolves the tarball from the marketplace and ALWAYS verifies it against the
|
|
156
|
+
* listing's approved hash/signature BEFORE extraction (US-021 → US-020).
|
|
157
|
+
*
|
|
158
|
+
* @param requireSignature production posture once signing keys exist; during
|
|
159
|
+
* the key-deferral window callers pass false (hash still always checked).
|
|
160
|
+
*/
|
|
161
|
+
export declare function fetchMarketplace(source: string, tmpDir: string, deps: MarketplaceDeps, opts?: {
|
|
162
|
+
requireSignature?: boolean;
|
|
163
|
+
}): Promise<FetchResult>;
|
|
55
164
|
export interface LatestResult {
|
|
56
165
|
transport: Transport;
|
|
57
166
|
/** Identifier of the currently-installed pack (sha for git, version for npm). */
|
|
@@ -72,6 +181,63 @@ export interface LatestResult {
|
|
|
72
181
|
* @param installedVersion the installed pack's manifest `version` (npm compare)
|
|
73
182
|
*/
|
|
74
183
|
export declare function resolveLatest(source: string, installedVersion?: string): LatestResult;
|
|
184
|
+
/**
|
|
185
|
+
* Async marketplace update probe (US-006): resolve the slug's latest approved
|
|
186
|
+
* listing version and compare it to the installed version. Reuses the same
|
|
187
|
+
* `MarketplaceDeps.resolveListing` seam as the install path (resolving with NO
|
|
188
|
+
* version pin returns the latest listing). Never throws — network/parse
|
|
189
|
+
* failures return `{ updateAvailable: null, error }` so `hq packs update`
|
|
190
|
+
* stays resilient.
|
|
191
|
+
*
|
|
192
|
+
* @param source the stamped `marketplace:<slug>[@version]` source
|
|
193
|
+
* @param installedVersion the installed pack's manifest version (semver compare)
|
|
194
|
+
* @param deps injected for tests; defaults to the public API
|
|
195
|
+
*/
|
|
196
|
+
export declare function resolveLatestMarketplace(source: string, installedVersion?: string, deps?: MarketplaceDeps): Promise<LatestResult>;
|
|
197
|
+
/** Thrown when a downloaded artifact fails hash or signature verification. */
|
|
198
|
+
export declare class ArtifactVerificationError extends Error {
|
|
199
|
+
constructor(message: string);
|
|
200
|
+
}
|
|
201
|
+
/** Hash algorithm for the content hash — pinned to sha256 (matches publish). */
|
|
202
|
+
export declare const ARTIFACT_HASH_ALG: "sha256";
|
|
203
|
+
export interface VerifyArtifactInput {
|
|
204
|
+
/** The downloaded gzipped tarball bytes (canonical bytes). */
|
|
205
|
+
tarballBytes: Uint8Array;
|
|
206
|
+
/** Lowercase-hex sha256 the listing pinned at publish (approval bound to it). */
|
|
207
|
+
expectedHash: string;
|
|
208
|
+
/**
|
|
209
|
+
* Base64 Ed25519 signature over the lowercase-hex `expectedHash`. Optional:
|
|
210
|
+
* when the listing carries no signature (e.g. published before signing-key
|
|
211
|
+
* provisioning), hash verification still runs but signature verification is
|
|
212
|
+
* skipped UNLESS `requireSignature` is set.
|
|
213
|
+
*/
|
|
214
|
+
signature?: string;
|
|
215
|
+
/**
|
|
216
|
+
* The platform PUBLIC key (Ed25519) as a PEM/SPKI string or a pre-parsed
|
|
217
|
+
* KeyObject. Required to verify a signature. The public key is distributable
|
|
218
|
+
* (embedded in / fetched by the installer).
|
|
219
|
+
*/
|
|
220
|
+
publicKey?: string | KeyObject;
|
|
221
|
+
/**
|
|
222
|
+
* When true, a missing signature OR missing public key is a hard failure
|
|
223
|
+
* (production posture once signing keys are provisioned). Default false so an
|
|
224
|
+
* unsigned listing still hash-verifies during the deferred-key window.
|
|
225
|
+
*/
|
|
226
|
+
requireSignature?: boolean;
|
|
227
|
+
}
|
|
228
|
+
/** Lowercase-hex sha256 of the given bytes — the install-side content hash. */
|
|
229
|
+
export declare function computeArtifactHash(tarballBytes: Uint8Array): string;
|
|
230
|
+
/**
|
|
231
|
+
* Verify a downloaded marketplace artifact's integrity (hash) and authenticity
|
|
232
|
+
* (signature). Throws `ArtifactVerificationError` on ANY mismatch — the caller
|
|
233
|
+
* MUST let it propagate so the install aborts before extraction.
|
|
234
|
+
*
|
|
235
|
+
* Constant-time-ish hash comparison: hashes are fixed-length lowercase hex, and
|
|
236
|
+
* the early-exit risk is negligible (the hash is public), but we still compare
|
|
237
|
+
* full strings rather than prefixes.
|
|
238
|
+
*/
|
|
239
|
+
export declare function verifyArtifact(input: VerifyArtifactInput): void;
|
|
240
|
+
export declare function validateManifest(payloadDir: string, hqVersion: string | null): PackManifest;
|
|
75
241
|
/**
|
|
76
242
|
* Install the fetched payload to `<hqRoot>/core/packages/<pkg.name>/` (HQ
|
|
77
243
|
* v12+ layout). The HQ template (`hq-core` / `hq-core-staging`) ships
|
|
@@ -136,6 +302,26 @@ export interface InstallPackOptions {
|
|
|
136
302
|
* `rsync -a`, `git clone` progress -> stderr), so this is sufficient.
|
|
137
303
|
*/
|
|
138
304
|
quiet?: boolean;
|
|
305
|
+
/**
|
|
306
|
+
* US-021 marketplace artifact integrity. When provided (a marketplace listing
|
|
307
|
+
* carries a pinned content hash + signature + public key), the downloaded
|
|
308
|
+
* tarball is verified BEFORE extraction; a mismatch aborts the install. US-006
|
|
309
|
+
* populates this from the listing detail response.
|
|
310
|
+
*/
|
|
311
|
+
integrity?: ArtifactIntegrity;
|
|
312
|
+
/**
|
|
313
|
+
* US-006 marketplace transport seams (resolve/refresh/download), injected for
|
|
314
|
+
* tests. When omitted, `defaultMarketplaceDeps()` (public listings API +
|
|
315
|
+
* presigned S3) is used. Only consulted when `source` is `marketplace:...`.
|
|
316
|
+
*/
|
|
317
|
+
marketplaceDeps?: MarketplaceDeps;
|
|
318
|
+
/**
|
|
319
|
+
* US-006: require a valid signature for marketplace artifacts. Default false
|
|
320
|
+
* during the signing-key-deferral window (the content hash is still ALWAYS
|
|
321
|
+
* verified); set true once platform signing keys are provisioned.
|
|
322
|
+
*/
|
|
323
|
+
requireSignature?: boolean;
|
|
139
324
|
}
|
|
140
325
|
export declare function installPack(source: string, opts?: InstallPackOptions): Promise<void>;
|
|
326
|
+
export {};
|
|
141
327
|
//# sourceMappingURL=pack-install.d.ts.map
|