@cloudhal/release-verify 0.0.0-placeholder.0 → 0.10.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/LICENSE +202 -0
- package/README.md +81 -2
- package/dist/src/attestation.d.ts +42 -0
- package/dist/src/attestation.js +180 -0
- package/dist/src/crypto.d.ts +15 -0
- package/dist/src/crypto.js +87 -0
- package/dist/src/documents.d.ts +23 -0
- package/dist/src/documents.js +167 -0
- package/dist/src/errors.d.ts +13 -0
- package/dist/src/errors.js +48 -0
- package/dist/src/generated/release-keys.d.ts +67 -0
- package/dist/src/generated/release-keys.js +72 -0
- package/dist/src/generated/release-manifest.d.ts +211 -0
- package/dist/src/generated/release-manifest.js +237 -0
- package/dist/src/generated/release-statement.d.ts +61 -0
- package/dist/src/generated/release-statement.js +66 -0
- package/dist/src/generated/release-yank-list.d.ts +73 -0
- package/dist/src/generated/release-yank-list.js +78 -0
- package/dist/src/generated/validators.d.ts +22 -0
- package/dist/src/generated/validators.js +5656 -0
- package/dist/src/index.d.ts +22 -0
- package/dist/src/index.js +48 -0
- package/dist/src/keys.d.ts +28 -0
- package/dist/src/keys.js +84 -0
- package/dist/src/limits.d.ts +24 -0
- package/dist/src/limits.js +30 -0
- package/dist/src/paths.d.ts +14 -0
- package/dist/src/paths.js +59 -0
- package/dist/src/scopes.d.ts +10 -0
- package/dist/src/scopes.js +22 -0
- package/dist/src/tar.d.ts +31 -0
- package/dist/src/tar.js +398 -0
- package/dist/src/trust.d.ts +67 -0
- package/dist/src/trust.js +248 -0
- package/dist/src/urls.d.ts +8 -0
- package/dist/src/urls.js +37 -0
- package/dist/src/verify.d.ts +68 -0
- package/dist/src/verify.js +171 -0
- package/dist/src/yank-memory.d.ts +22 -0
- package/dist/src/yank-memory.js +106 -0
- package/package.json +50 -6
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@cloudhal/release-verify` (RFC-0046 §11a): the one verifier of Cloudhal release bundles for
|
|
3
|
+
* hosts, tools and the CLI. Worker-safe: WebCrypto Ed25519, `DecompressionStream`, and an
|
|
4
|
+
* incremental SHA-256 (`crypto.DigestStream`, else @noble/hashes). The second factor, the GitHub
|
|
5
|
+
* build attestation, is the Node-only subpath `@cloudhal/release-verify/attestation`.
|
|
6
|
+
*/
|
|
7
|
+
export { type IncrementalSha256, incrementalSha256, sha256Hex, verifyEd25519 } from "./crypto.js";
|
|
8
|
+
export type { ListedPart, Worker } from "./documents.js";
|
|
9
|
+
export { ERROR_CODES, ReleaseVerifyError, type ReleaseVerifyErrorCode } from "./errors.js";
|
|
10
|
+
export type { ListedKey, ReleaseKeys as KeyListDocument } from "./generated/release-keys.js";
|
|
11
|
+
export type { ReleaseManifest } from "./generated/release-manifest.js";
|
|
12
|
+
export type { ReleaseStatement } from "./generated/release-statement.js";
|
|
13
|
+
export type { ReleaseYankList } from "./generated/release-yank-list.js";
|
|
14
|
+
export { type KeyStatus, PINNED_KEYS, type ReleaseKey, type ReleaseKeys } from "./keys.js";
|
|
15
|
+
export { channelOf, LIMITS, MANIFEST_FILE, SIGNATURE_FILE, STATEMENT_FILE, sigstoreName, tarballName, } from "./limits.js";
|
|
16
|
+
export { foldPath, type PathKind, unsafePathReason } from "./paths.js";
|
|
17
|
+
export { IMPLIED_SCOPES, missingScopes } from "./scopes.js";
|
|
18
|
+
export type { ByteSource } from "./tar.js";
|
|
19
|
+
export { addMonths, KEYS_FILE, mergeTrust, PINNED_ROOTS, type RootKeys, TRUST_BASE_URL, type VerifiedKeyList, type VerifiedYankList, verifyKeys, verifyYanked, YANKED_FILE, yankOf, } from "./trust.js";
|
|
20
|
+
export { RELEASE_REPOSITORY, releaseAssetNames, releaseAssetUrls } from "./urls.js";
|
|
21
|
+
export { type VerifiedEntry, type VerifiedRelease, type VerifyReleaseInput, verifyRelease, } from "./verify.js";
|
|
22
|
+
export { rememberYanks, type YankMemory } from "./yank-memory.js";
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@cloudhal/release-verify` (RFC-0046 §11a): the one verifier of Cloudhal release bundles for
|
|
3
|
+
* hosts, tools and the CLI. Worker-safe: WebCrypto Ed25519, `DecompressionStream`, and an
|
|
4
|
+
* incremental SHA-256 (`crypto.DigestStream`, else @noble/hashes). The second factor, the GitHub
|
|
5
|
+
* build attestation, is the Node-only subpath `@cloudhal/release-verify/attestation`.
|
|
6
|
+
*/
|
|
7
|
+
export { incrementalSha256, sha256Hex, verifyEd25519 } from "./crypto.js";
|
|
8
|
+
|
|
9
|
+
export { ERROR_CODES, ReleaseVerifyError, } from "./errors.js";
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
export { PINNED_KEYS, } from "./keys.js";
|
|
15
|
+
export {
|
|
16
|
+
channelOf,
|
|
17
|
+
LIMITS,
|
|
18
|
+
MANIFEST_FILE,
|
|
19
|
+
SIGNATURE_FILE,
|
|
20
|
+
STATEMENT_FILE,
|
|
21
|
+
sigstoreName,
|
|
22
|
+
tarballName,
|
|
23
|
+
} from "./limits.js";
|
|
24
|
+
export { foldPath, unsafePathReason } from "./paths.js";
|
|
25
|
+
export { IMPLIED_SCOPES, missingScopes } from "./scopes.js";
|
|
26
|
+
|
|
27
|
+
export {
|
|
28
|
+
addMonths,
|
|
29
|
+
KEYS_FILE,
|
|
30
|
+
mergeTrust,
|
|
31
|
+
PINNED_ROOTS,
|
|
32
|
+
|
|
33
|
+
TRUST_BASE_URL,
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
verifyKeys,
|
|
37
|
+
verifyYanked,
|
|
38
|
+
YANKED_FILE,
|
|
39
|
+
yankOf,
|
|
40
|
+
} from "./trust.js";
|
|
41
|
+
export { RELEASE_REPOSITORY, releaseAssetNames, releaseAssetUrls } from "./urls.js";
|
|
42
|
+
export {
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
verifyRelease,
|
|
47
|
+
} from "./verify.js";
|
|
48
|
+
export { rememberYanks, } from "./yank-memory.js";
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export type KeyStatus = "active" | "retiring" | "revoked";
|
|
2
|
+
export interface ReleaseKey {
|
|
3
|
+
/** The raw 32-byte Ed25519 public key, base64. */
|
|
4
|
+
readonly publicKey: string;
|
|
5
|
+
readonly status: KeyStatus;
|
|
6
|
+
/** The key signs statements created in `[notBefore, notAfter]` (RFC 3339, UTC). */
|
|
7
|
+
readonly notBefore?: string;
|
|
8
|
+
readonly notAfter?: string;
|
|
9
|
+
/**
|
|
10
|
+
* Set by `mergeTrust` when a fresh, root-signed `keys.json` lists this key (and does not revoke
|
|
11
|
+
* it). Only then may a `retiring` key verify after its `notAfter` (RFC-0046 §9).
|
|
12
|
+
*/
|
|
13
|
+
readonly listed?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export type ReleaseKeys = Readonly<Record<string, ReleaseKey>>;
|
|
16
|
+
export declare const PINNED_KEYS: ReleaseKeys;
|
|
17
|
+
/** The raw public key `keyId` verifies with and its record, or a refusal (unknown, revoked, malformed). */
|
|
18
|
+
export declare function trustedKey(keys: ReleaseKeys, revoked: readonly string[], keyId: string): {
|
|
19
|
+
raw: Uint8Array;
|
|
20
|
+
key: ReleaseKey;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* RFC-0046 §9's expiry rule for the key that signed a statement created at `createdAt`: it must
|
|
24
|
+
* lie within the key's `[notBefore, notAfter]`, and after `notAfter` the key verifies only while a
|
|
25
|
+
* fresh `keys.json` lists it as `retiring` (`listed`). Pinned keys alone refuse after `notAfter`.
|
|
26
|
+
*/
|
|
27
|
+
export declare function checkKeyWindow(keyId: string, key: ReleaseKey, createdAt: string, now: Date): void;
|
|
28
|
+
export declare function fromBase64(s: string): Uint8Array | undefined;
|
package/dist/src/keys.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The release keys this package trusts (RFC-0046 §9, §11a): the table of
|
|
3
|
+
* docs/security/release-signing-keys.md, kept equal to it by test/package.test.ts.
|
|
4
|
+
*
|
|
5
|
+
* `PINNED_KEYS` is frozen into each published version and cannot learn about a revocation by
|
|
6
|
+
* itself. A caller may only reduce trust: `revoked` withdraws key ids. A caller that trusts a key
|
|
7
|
+
* outside this table passes a whole `keys` map, which is a reviewed change on its side. Once the
|
|
8
|
+
* signed `keys.json` of #226 exists, it feeds the same reduce-only path.
|
|
9
|
+
*/
|
|
10
|
+
import { refuse } from "./errors.js";
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
export const PINNED_KEYS = Object.freeze({
|
|
31
|
+
"cloudhal-release-2026a": Object.freeze({
|
|
32
|
+
publicKey: "7az3pPc4oT0AOfRcdfOZZQkM6XAGHo2jXEdDZ1qEVR8=",
|
|
33
|
+
status: "active",
|
|
34
|
+
notBefore: "2026-11-12T00:00:00Z",
|
|
35
|
+
notAfter: "2027-11-12T00:00:00Z",
|
|
36
|
+
}),
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
/** The raw public key `keyId` verifies with and its record, or a refusal (unknown, revoked, malformed). */
|
|
40
|
+
export function trustedKey(
|
|
41
|
+
keys ,
|
|
42
|
+
revoked ,
|
|
43
|
+
keyId ,
|
|
44
|
+
) {
|
|
45
|
+
const key = Object.hasOwn(keys, keyId) ? keys[keyId] : undefined;
|
|
46
|
+
if (!key) return refuse("key-unknown", `no trusted release key ${keyId}`);
|
|
47
|
+
if (key.status === "revoked" || revoked.includes(keyId))
|
|
48
|
+
return refuse("key-revoked", `release key ${keyId} is revoked`);
|
|
49
|
+
const raw = fromBase64(key.publicKey);
|
|
50
|
+
if (raw?.length !== 32)
|
|
51
|
+
return refuse("key-unknown", `the trusted key ${keyId} is not a 32-byte Ed25519 key`);
|
|
52
|
+
return { raw, key };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* RFC-0046 §9's expiry rule for the key that signed a statement created at `createdAt`: it must
|
|
57
|
+
* lie within the key's `[notBefore, notAfter]`, and after `notAfter` the key verifies only while a
|
|
58
|
+
* fresh `keys.json` lists it as `retiring` (`listed`). Pinned keys alone refuse after `notAfter`.
|
|
59
|
+
*/
|
|
60
|
+
export function checkKeyWindow(keyId , key , createdAt , now ) {
|
|
61
|
+
const created = Date.parse(createdAt);
|
|
62
|
+
if (key.notBefore !== undefined && created < Date.parse(key.notBefore))
|
|
63
|
+
refuse("key-expired", `${keyId} was not yet valid when this release was created`);
|
|
64
|
+
if (key.notAfter === undefined) return;
|
|
65
|
+
const notAfter = Date.parse(key.notAfter);
|
|
66
|
+
if (created > notAfter)
|
|
67
|
+
refuse("key-expired", `${keyId} had expired when this release was created`);
|
|
68
|
+
if (now.getTime() > notAfter && !(key.status === "retiring" && key.listed === true))
|
|
69
|
+
refuse(
|
|
70
|
+
"key-expired",
|
|
71
|
+
`${keyId} expired on ${key.notAfter.slice(0, 10)}; a fresh keys.json must list it as retiring`,
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function fromBase64(s ) {
|
|
76
|
+
try {
|
|
77
|
+
const bin = atob(s);
|
|
78
|
+
const out = new Uint8Array(bin.length);
|
|
79
|
+
for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
|
|
80
|
+
return out;
|
|
81
|
+
} catch {
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RFC-0046 §1 asset names and §7's normative limits. `scripts/release-bundle/manifest.ts` (the
|
|
3
|
+
* producer) holds the same numbers; test/package.test.ts keeps the two equal.
|
|
4
|
+
*/
|
|
5
|
+
export declare const LIMITS: {
|
|
6
|
+
readonly manifestBytes: 1900000;
|
|
7
|
+
readonly statementBytes: 4096;
|
|
8
|
+
readonly tarballBytes: 104857600;
|
|
9
|
+
readonly partBytes: number;
|
|
10
|
+
readonly assetBytes: number;
|
|
11
|
+
readonly assetsPerWorker: 20000;
|
|
12
|
+
readonly tarEntries: 30000;
|
|
13
|
+
};
|
|
14
|
+
/** The largest decompressed tar a release may hold: every part at its limit is far beyond it. */
|
|
15
|
+
export declare const MAX_TAR_BYTES: number;
|
|
16
|
+
/** The longest path, in UTF-8 bytes, a manifest listing or a tar entry may carry (§11a). */
|
|
17
|
+
export declare const MAX_PATH_BYTES = 512;
|
|
18
|
+
export declare const STATEMENT_FILE = "release-statement.json";
|
|
19
|
+
export declare const SIGNATURE_FILE = "release-statement.sig";
|
|
20
|
+
export declare const MANIFEST_FILE = "manifest.json";
|
|
21
|
+
export declare const tarballName: (version: string) => string;
|
|
22
|
+
export declare const sigstoreName: (version: string) => string;
|
|
23
|
+
/** `X.Y.Z-<pre>` ⇔ `next` (§2 invariant 3, §6). */
|
|
24
|
+
export declare const channelOf: (version: string) => "stable" | "next";
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RFC-0046 §1 asset names and §7's normative limits. `scripts/release-bundle/manifest.ts` (the
|
|
3
|
+
* producer) holds the same numbers; test/package.test.ts keeps the two equal.
|
|
4
|
+
*/
|
|
5
|
+
export const LIMITS = {
|
|
6
|
+
manifestBytes: 1_900_000,
|
|
7
|
+
statementBytes: 4096,
|
|
8
|
+
tarballBytes: 104_857_600,
|
|
9
|
+
partBytes: 64 * 1024 * 1024,
|
|
10
|
+
assetBytes: 25 * 1024 * 1024,
|
|
11
|
+
assetsPerWorker: 20_000,
|
|
12
|
+
tarEntries: 30_000,
|
|
13
|
+
} ;
|
|
14
|
+
|
|
15
|
+
/** The largest decompressed tar a release may hold: every part at its limit is far beyond it. */
|
|
16
|
+
export const MAX_TAR_BYTES = 1024 * 1024 * 1024;
|
|
17
|
+
|
|
18
|
+
/** The longest path, in UTF-8 bytes, a manifest listing or a tar entry may carry (§11a). */
|
|
19
|
+
export const MAX_PATH_BYTES = 512;
|
|
20
|
+
|
|
21
|
+
export const STATEMENT_FILE = "release-statement.json";
|
|
22
|
+
export const SIGNATURE_FILE = "release-statement.sig";
|
|
23
|
+
export const MANIFEST_FILE = "manifest.json";
|
|
24
|
+
export const tarballName = (version ) => `cloudhal-release-${version}.tar.gz`;
|
|
25
|
+
export const sigstoreName = (version ) =>
|
|
26
|
+
`cloudhal-release-${version}.sigstore.json`;
|
|
27
|
+
|
|
28
|
+
/** `X.Y.Z-<pre>` ⇔ `next` (§2 invariant 3, §6). */
|
|
29
|
+
export const channelOf = (version ) =>
|
|
30
|
+
version.includes("-") ? "next" : "stable";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** `module` and `entry` paths are relative; an `asset` path is `/<relative path>` (§3). */
|
|
2
|
+
export type PathKind = "module" | "asset" | "entry";
|
|
3
|
+
/** Why `path` is unsafe as a `kind` path, or undefined when it is safe. */
|
|
4
|
+
export declare function unsafePathReason(path: string, kind: PathKind): string | undefined;
|
|
5
|
+
/** Refuses `path` (named `where` in the message) unless it is safe as a `kind` path. */
|
|
6
|
+
export declare function checkPath(path: string, kind: PathKind, where: string): void;
|
|
7
|
+
/**
|
|
8
|
+
* Case folding as case-insensitive file systems apply it, approximately: NFC, lower, upper, lower
|
|
9
|
+
* again, NFC (so "ß", "ẞ" and "SS" meet, and so do "ΐ" and "Ϊ́"). Two listed paths that fold
|
|
10
|
+
* equal would collide on disk.
|
|
11
|
+
*/
|
|
12
|
+
export declare const foldPath: (path: string) => string;
|
|
13
|
+
/** Refuses two `paths` that are equal after case folding. */
|
|
14
|
+
export declare function checkNoFoldedDuplicates(paths: Iterable<string>, where: string): void;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entry-path safety (RFC-0046 §11a): every path in the manifest's listings and every tar entry
|
|
3
|
+
* name is checked before any byte of that entry is used. One pass over the characters plus a
|
|
4
|
+
* `split("/")`, no regular expression, so the check is linear in the path's length (AGENTS.md
|
|
5
|
+
* rule 13). A failure is the refusal `entry-path`.
|
|
6
|
+
*/
|
|
7
|
+
import { refuse } from "./errors.js";
|
|
8
|
+
import { MAX_PATH_BYTES } from "./limits.js";
|
|
9
|
+
|
|
10
|
+
/** `module` and `entry` paths are relative; an `asset` path is `/<relative path>` (§3). */
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
const utf8 = new TextEncoder();
|
|
14
|
+
|
|
15
|
+
/** Why `path` is unsafe as a `kind` path, or undefined when it is safe. */
|
|
16
|
+
export function unsafePathReason(path , kind ) {
|
|
17
|
+
if (utf8.encode(path).length > MAX_PATH_BYTES) return `longer than ${MAX_PATH_BYTES} bytes`;
|
|
18
|
+
for (let i = 0; i < path.length; i++) {
|
|
19
|
+
const c = path.charCodeAt(i);
|
|
20
|
+
if (c < 0x20 || c === 0x7f) return "a control character";
|
|
21
|
+
if (c === 0x5c) return "a backslash";
|
|
22
|
+
}
|
|
23
|
+
let rel = path;
|
|
24
|
+
if (kind === "asset") {
|
|
25
|
+
if (!path.startsWith("/")) return "an asset path that does not start with /";
|
|
26
|
+
rel = path.slice(1);
|
|
27
|
+
} else if (path.startsWith("/")) return "a leading /";
|
|
28
|
+
for (const segment of rel.split("/")) {
|
|
29
|
+
if (segment === "") return "an empty segment";
|
|
30
|
+
if (segment === "." || segment === "..") return `a ${segment} segment`;
|
|
31
|
+
}
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Refuses `path` (named `where` in the message) unless it is safe as a `kind` path. */
|
|
36
|
+
export function checkPath(path , kind , where ) {
|
|
37
|
+
const reason = unsafePathReason(path, kind);
|
|
38
|
+
if (reason) refuse("entry-path", `${where}: ${JSON.stringify(path)} has ${reason}`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Case folding as case-insensitive file systems apply it, approximately: NFC, lower, upper, lower
|
|
43
|
+
* again, NFC (so "ß", "ẞ" and "SS" meet, and so do "ΐ" and "Ϊ́"). Two listed paths that fold
|
|
44
|
+
* equal would collide on disk.
|
|
45
|
+
*/
|
|
46
|
+
export const foldPath = (path ) =>
|
|
47
|
+
path.normalize("NFC").toLowerCase().toUpperCase().toLowerCase().normalize("NFC");
|
|
48
|
+
|
|
49
|
+
/** Refuses two `paths` that are equal after case folding. */
|
|
50
|
+
export function checkNoFoldedDuplicates(paths , where ) {
|
|
51
|
+
const seen = new Map ();
|
|
52
|
+
for (const p of paths) {
|
|
53
|
+
const f = foldPath(p);
|
|
54
|
+
const other = seen.get(f);
|
|
55
|
+
if (other !== undefined)
|
|
56
|
+
refuse("entry-path", `${where}: ${JSON.stringify(other)} and ${JSON.stringify(p)} collide`);
|
|
57
|
+
seen.set(f, p);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The scopes rule of RFC-0046 §3: scope ids are compared exactly, with one exception. Workers
|
|
3
|
+
* Admin (`workers-scripts.admin`) covers Workers Editor (`workers-scripts.edit`), which manifests
|
|
4
|
+
* up to and including v0.9.0 name; an Editor cannot create the Workers a release installs (#225).
|
|
5
|
+
*/
|
|
6
|
+
export declare const IMPLIED_SCOPES: Readonly<Record<string, readonly string[]>>;
|
|
7
|
+
/** The manifest's `requiredScopes` that `granted` does not hold, in the manifest's order. */
|
|
8
|
+
export declare function missingScopes(manifest: {
|
|
9
|
+
readonly requiredScopes: readonly string[];
|
|
10
|
+
}, granted: Iterable<string>): string[];
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The scopes rule of RFC-0046 §3: scope ids are compared exactly, with one exception. Workers
|
|
3
|
+
* Admin (`workers-scripts.admin`) covers Workers Editor (`workers-scripts.edit`), which manifests
|
|
4
|
+
* up to and including v0.9.0 name; an Editor cannot create the Workers a release installs (#225).
|
|
5
|
+
*/
|
|
6
|
+
export const IMPLIED_SCOPES = Object.freeze({
|
|
7
|
+
"workers-scripts.admin": Object.freeze(["workers-scripts.edit"]),
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
/** The manifest's `requiredScopes` that `granted` does not hold, in the manifest's order. */
|
|
11
|
+
export function missingScopes(
|
|
12
|
+
manifest ,
|
|
13
|
+
granted ,
|
|
14
|
+
) {
|
|
15
|
+
const held = new Set ();
|
|
16
|
+
for (const s of granted) {
|
|
17
|
+
held.add(s);
|
|
18
|
+
const implied = Object.hasOwn(IMPLIED_SCOPES, s) ? IMPLIED_SCOPES[s] : undefined;
|
|
19
|
+
for (const i of implied ?? []) held.add(i);
|
|
20
|
+
}
|
|
21
|
+
return manifest.requiredScopes.filter((s) => !held.has(s));
|
|
22
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export type ByteSource = ReadableStream<Uint8Array> | AsyncIterable<Uint8Array> | Uint8Array;
|
|
2
|
+
/** The chunks of `source`, whatever its form. */
|
|
3
|
+
export declare function chunksOf(source: ByteSource): AsyncGenerator<Uint8Array>;
|
|
4
|
+
/** Refuses a gzip stream whose first ten bytes are not the fixed header (§7). */
|
|
5
|
+
export declare function checkGzipHeader(head: Uint8Array): void;
|
|
6
|
+
/**
|
|
7
|
+
* The decompressed bytes of a gzip `source` whose header is the fixed one. Every exit (the end, a
|
|
8
|
+
* refusal, a consumer that stops early) cancels the chain and releases the caller's stream.
|
|
9
|
+
*/
|
|
10
|
+
export declare function gunzip(source: ByteSource, max?: number): AsyncGenerator<Uint8Array>;
|
|
11
|
+
export interface TarEntry {
|
|
12
|
+
readonly path: string;
|
|
13
|
+
readonly bytes: Uint8Array;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Whether `path` needs a PAX `path` record: longer than the 100-byte ustar name field in UTF-8,
|
|
17
|
+
* or not printable ASCII. The producer (scripts/release-bundle/tar.ts) imports this rule.
|
|
18
|
+
*/
|
|
19
|
+
export declare function needsPax(path: string): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* The ustar name field of `path`: its longest suffix whose UTF-8 is at most 100 bytes, cut on a
|
|
22
|
+
* code-point boundary (so it is valid UTF-8). For a path of at most 100 ASCII bytes that is the
|
|
23
|
+
* path itself. One byte-based rule for the producer, which imports it, and the verifier.
|
|
24
|
+
*/
|
|
25
|
+
export declare function ustarName(path: string): string;
|
|
26
|
+
/**
|
|
27
|
+
* The entries of a decompressed tar stream, in archive order. `admit(path, size)` runs on each
|
|
28
|
+
* entry's header, before its bytes are buffered, so an unlisted or oversized entry is refused
|
|
29
|
+
* without reading it into memory.
|
|
30
|
+
*/
|
|
31
|
+
export declare function readTar(source: AsyncIterable<Uint8Array>, mtime: number, admit: (path: string, size: number) => void): AsyncGenerator<TarEntry>;
|