@kungfu-tech/buildchain 2.14.17-alpha.9 → 2.14.17
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/actions/promote-buildchain-ref/README.md +0 -8
- package/dist/site/buildchain-contract.json +27 -182
- package/dist/site/buildchain-site.json +16 -43
- package/dist/site/capability-registry.json +2 -2
- package/dist/site/controller-registry.json +4 -85
- package/dist/site/kfd-claims.json +13 -105
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +3 -3
- package/dist/site/node-api-registry.json +4 -4
- package/dist/site/page-registry.json +9 -36
- package/dist/site/public-surface-audit.json +15 -71
- package/dist/site/publication-authority-registry.json +1 -2
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/site-manifest.json +7 -7
- package/dist/site/workflow-registry.json +11 -65
- package/docs/kfd-support.md +0 -6
- package/docs/reusable-build-surface.md +0 -48
- package/docs/versioning.md +0 -1
- package/package.json +1 -1
- package/packages/core/buildchain-contract.js +0 -58
- package/packages/core/buildchain-kfd-claims.js +0 -2
- package/packages/core/controller-evidence.js +2 -9
- package/packages/core/kfd3-surface-register.js +1 -51
- package/scripts/aggregate-build-summary.mjs +2 -7
- package/scripts/check-inventory.mjs +0 -1
- package/scripts/generate-channel-build-workflow.mjs +1 -17
- package/actions/macos-credential-island/README.md +0 -18
- package/scripts/seal-macos-credential-input.mjs +0 -135
|
@@ -22,23 +22,16 @@ const CONTROLLER_SPECS = [
|
|
|
22
22
|
id: "build-lifecycle",
|
|
23
23
|
workflowId: ".build",
|
|
24
24
|
version: 1,
|
|
25
|
-
capabilities: [
|
|
26
|
-
"source-lock",
|
|
27
|
-
"lifecycle-build",
|
|
28
|
-
"credential-island",
|
|
29
|
-
"lifecycle-verify",
|
|
30
|
-
"artifact-admission",
|
|
31
|
-
],
|
|
25
|
+
capabilities: ["source-lock", "lifecycle-build", "lifecycle-verify", "artifact-admission"],
|
|
32
26
|
stages: [
|
|
33
27
|
"resolve-runtime",
|
|
34
28
|
"resolve-source",
|
|
35
29
|
"anchored-release-preflight",
|
|
36
30
|
"build",
|
|
37
|
-
"credential-island",
|
|
38
31
|
"verify",
|
|
39
32
|
"aggregate",
|
|
40
33
|
],
|
|
41
|
-
optionalStages: ["anchored-release-preflight"
|
|
34
|
+
optionalStages: ["anchored-release-preflight"],
|
|
42
35
|
evidence: [
|
|
43
36
|
"platform-manifests",
|
|
44
37
|
"build-summary",
|
|
@@ -124,50 +124,6 @@ function normalizeRegistrySurface(entry) {
|
|
|
124
124
|
};
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
-
function globPatternRegExp(pattern) {
|
|
128
|
-
const normalized = String(pattern || "").replaceAll("\\", "/");
|
|
129
|
-
let source = "";
|
|
130
|
-
for (let index = 0; index < normalized.length; index += 1) {
|
|
131
|
-
const character = normalized[index];
|
|
132
|
-
if (character === "*" && normalized[index + 1] === "*") {
|
|
133
|
-
source += ".*";
|
|
134
|
-
index += 1;
|
|
135
|
-
} else if (character === "*") {
|
|
136
|
-
source += "[^/]*";
|
|
137
|
-
} else if (character === "?") {
|
|
138
|
-
source += "[^/]";
|
|
139
|
-
} else {
|
|
140
|
-
source += character.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
return new RegExp(`^${source}$`);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function distributionBindings(registry, detectedSurfaces) {
|
|
147
|
-
return detectedSurfaces.flatMap((detected) =>
|
|
148
|
-
registry.surfaces.flatMap((declared) =>
|
|
149
|
-
(declared.distribution?.artifacts || [])
|
|
150
|
-
.filter(
|
|
151
|
-
(artifact) =>
|
|
152
|
-
normalizeKind(artifact.kind) === detected.kind &&
|
|
153
|
-
globPatternRegExp(artifact.pathGlob).test(
|
|
154
|
-
String(detected.artifactPath || "").replaceAll("\\", "/"),
|
|
155
|
-
),
|
|
156
|
-
)
|
|
157
|
-
.map((artifact) => ({
|
|
158
|
-
detectedSurfaceId: detected.id,
|
|
159
|
-
declaredSurfaceId: declared.id,
|
|
160
|
-
artifact: {
|
|
161
|
-
kind: artifact.kind,
|
|
162
|
-
platform: artifact.platform,
|
|
163
|
-
pathGlob: artifact.pathGlob,
|
|
164
|
-
...(artifact.sha256 ? { sha256: artifact.sha256 } : {}),
|
|
165
|
-
},
|
|
166
|
-
})),
|
|
167
|
-
),
|
|
168
|
-
);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
127
|
function stableId(value) {
|
|
172
128
|
return String(value || "")
|
|
173
129
|
.toLowerCase()
|
|
@@ -551,11 +507,7 @@ export function auditKfd3Surfaces({
|
|
|
551
507
|
const registry = readKfd3SurfaceRegistry({ cwd, registryPath });
|
|
552
508
|
const detectedIds = new Set(detection.surfaces.map((entry) => entry.id));
|
|
553
509
|
const declaredIds = new Set(registry.surfaces.map((entry) => entry.id));
|
|
554
|
-
const
|
|
555
|
-
const distributedIds = new Set(bindings.map((entry) => entry.detectedSurfaceId));
|
|
556
|
-
const detectedButUnregistered = detection.surfaces.filter(
|
|
557
|
-
(entry) => !declaredIds.has(entry.id) && !distributedIds.has(entry.id),
|
|
558
|
-
);
|
|
510
|
+
const detectedButUnregistered = detection.surfaces.filter((entry) => !declaredIds.has(entry.id));
|
|
559
511
|
const declaredButMissing = registry.surfaces.filter((entry) => !detectedIds.has(entry.id));
|
|
560
512
|
const enforced = registry.surfaces.filter((entry) => entry.state === "enforced" || entry.enforcement === "enforced");
|
|
561
513
|
const issues = [
|
|
@@ -591,12 +543,10 @@ export function auditKfd3Surfaces({
|
|
|
591
543
|
detected: detection.surfaces,
|
|
592
544
|
declared: registry.surfaces,
|
|
593
545
|
enforced,
|
|
594
|
-
distributionBindings: bindings,
|
|
595
546
|
},
|
|
596
547
|
comparison: {
|
|
597
548
|
detectedButUnregistered,
|
|
598
549
|
declaredButMissing,
|
|
599
|
-
distributionBindings: bindings,
|
|
600
550
|
},
|
|
601
551
|
issues,
|
|
602
552
|
};
|
|
@@ -13,18 +13,13 @@ export function aggregateBuildSummaryCli() {
|
|
|
13
13
|
const outputPath = path.resolve(readEnv("BUILDCHAIN_SUMMARY_OUTPUT", ".buildchain/artifacts/build-summary.json"));
|
|
14
14
|
const artifactName = readEnv("BUILDCHAIN_ARTIFACT_NAME", "buildchain-artifact");
|
|
15
15
|
const expectedPlatformCount = Number(readEnv("BUILDCHAIN_PLATFORM_COUNT", "0"));
|
|
16
|
-
const additionalPlatformCount = Number(readEnv("BUILDCHAIN_ADDITIONAL_PLATFORM_COUNT", "0"));
|
|
17
|
-
if (!Number.isInteger(additionalPlatformCount) || additionalPlatformCount < 0) {
|
|
18
|
-
throw new Error("BUILDCHAIN_ADDITIONAL_PLATFORM_COUNT must be a non-negative integer");
|
|
19
|
-
}
|
|
20
|
-
const expectedManifestCount = expectedPlatformCount + additionalPlatformCount;
|
|
21
16
|
const manifestFiles = findJsonFiles(inputRoot)
|
|
22
17
|
.filter((file) => path.basename(file) === "manifest.json")
|
|
23
18
|
.sort();
|
|
24
19
|
const manifests = manifestFiles.map((file) => JSON.parse(fs.readFileSync(file, "utf8")));
|
|
25
|
-
if (
|
|
20
|
+
if (expectedPlatformCount > 0 && manifests.length !== expectedPlatformCount) {
|
|
26
21
|
throw new Error(
|
|
27
|
-
`expected ${
|
|
22
|
+
`expected ${expectedPlatformCount} platform manifests, found ${manifests.length} under ${inputRoot}`,
|
|
28
23
|
);
|
|
29
24
|
}
|
|
30
25
|
const summary = {
|
|
@@ -59,7 +59,6 @@ const requiredPaths = [
|
|
|
59
59
|
"scripts/generate-site-bundle.mjs",
|
|
60
60
|
"scripts/generate-buildchain-kfd-witnesses.mjs",
|
|
61
61
|
"scripts/generate-release-candidate-passport.mjs",
|
|
62
|
-
"scripts/seal-macos-credential-input.mjs",
|
|
63
62
|
"scripts/shifu-gate-profile.mjs",
|
|
64
63
|
"scripts/artifact-relay-s3.mjs",
|
|
65
64
|
"scripts/anchored-version-material.mjs",
|
|
@@ -54,15 +54,7 @@ function routerOutputs(outputBlock) {
|
|
|
54
54
|
.replace("value: ${{ jobs.build.outputs.controller-receipt-artifact }}", "value: ${{ jobs.controller-receipt.outputs.controller-receipt-artifact }}")
|
|
55
55
|
.replace("value: ${{ jobs.build.outputs.controller-receipt-json }}", "value: ${{ jobs.controller-receipt.outputs.controller-receipt-json }}")
|
|
56
56
|
.replace("value: ${{ jobs.build.outputs.controller-receipt-digest }}", "value: ${{ jobs.controller-receipt.outputs.controller-receipt-digest }}")
|
|
57
|
-
.replace("value: ${{ jobs.build.outputs.controller-receipt-status }}", "value: ${{ jobs.controller-receipt.outputs.controller-receipt-status }}")
|
|
58
|
-
.replace(
|
|
59
|
-
/( credential-island-macos-artifact:\n(?: .*\n)*? value:) \$\{\{ jobs\.build\.outputs\.artifact-name \}\}/,
|
|
60
|
-
"$1 ${{ jobs.build.outputs.credential-island-macos-artifact }}",
|
|
61
|
-
)
|
|
62
|
-
.replace(
|
|
63
|
-
/( credential-island-macos-manifest-artifact:\n(?: .*\n)*? value:) \$\{\{ jobs\.build\.outputs\.manifest-artifact-name \}\}/,
|
|
64
|
-
"$1 ${{ jobs.build.outputs.credential-island-macos-manifest-artifact }}",
|
|
65
|
-
);
|
|
57
|
+
.replace("value: ${{ jobs.build.outputs.controller-receipt-status }}", "value: ${{ jobs.controller-receipt.outputs.controller-receipt-status }}");
|
|
66
58
|
return [
|
|
67
59
|
" buildchain-channel:",
|
|
68
60
|
' description: "Resolved Buildchain channel: alpha, stable, or override"',
|
|
@@ -295,10 +287,6 @@ function bindRouterCheckoutToWorkflowSha(workflow) {
|
|
|
295
287
|
|
|
296
288
|
export function generateChannelBuildWorkflow(source) {
|
|
297
289
|
const generated = bindRouterCheckoutToWorkflowSha(generateChannelBuildWorkflowBase(source))
|
|
298
|
-
.replace(
|
|
299
|
-
"\npermissions:\n contents: read\n",
|
|
300
|
-
"\npermissions:\n actions: read\n contents: read\n",
|
|
301
|
-
)
|
|
302
290
|
.replace(
|
|
303
291
|
" contract-lock-path: ${{ steps.lock.outputs.path }}\n",
|
|
304
292
|
[
|
|
@@ -313,10 +301,6 @@ export function generateChannelBuildWorkflow(source) {
|
|
|
313
301
|
.replace(
|
|
314
302
|
" needs: resolve-channel\n uses: ./.github/workflows/.build.yml",
|
|
315
303
|
" needs:\n - resolve-channel\n - controller-plan\n uses: ./.github/workflows/.build.yml",
|
|
316
|
-
)
|
|
317
|
-
.replace(
|
|
318
|
-
" uses: ./.github/workflows/.build.yml\n permissions:\n contents: read\n",
|
|
319
|
-
" uses: ./.github/workflows/.build.yml\n permissions:\n actions: read\n contents: read\n",
|
|
320
304
|
);
|
|
321
305
|
return `${generated.trimEnd()}\n\n${routerControllerReceiptJob()}\n\n${routerAggregateJob()}\n`;
|
|
322
306
|
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
# macOS Credential Island
|
|
2
|
-
|
|
3
|
-
This action signs one sealed macOS application on an isolated macOS runner. It
|
|
4
|
-
does not check out consumer source and does not execute files from the input
|
|
5
|
-
artifact. Buildchain validates the source-bound input manifest, imports one
|
|
6
|
-
exact Developer ID Application identity into a temporary keychain, signs the
|
|
7
|
-
application, submits Apple notarization, staples the application and DMG,
|
|
8
|
-
verifies Gatekeeper, and writes bounded JSON evidence.
|
|
9
|
-
|
|
10
|
-
The reusable Buildchain workflow invokes this action from a job bound to the
|
|
11
|
-
caller's protected GitHub Environment. The job downloads the exact sealed
|
|
12
|
-
Buildchain input and immutable action runtime, then uploads the signed payload
|
|
13
|
-
and its Buildchain platform manifest. It has no consumer checkout. Do not add a
|
|
14
|
-
package manager, lifecycle, hook, or consumer-script execution to that job.
|
|
15
|
-
|
|
16
|
-
The supported `electron-desktop-v1` entitlements profile is owned by
|
|
17
|
-
Buildchain. Consumer-provided entitlement files are intentionally unsupported;
|
|
18
|
-
otherwise pull-request bytes could expand the signing authority.
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { spawnSync } from "node:child_process";
|
|
3
|
-
import fs from "node:fs";
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
import { pathToFileURL } from "node:url";
|
|
6
|
-
|
|
7
|
-
import {
|
|
8
|
-
INPUT_CONTRACT,
|
|
9
|
-
assertContainedSymlinks,
|
|
10
|
-
assertRealPathInside,
|
|
11
|
-
requireRepository,
|
|
12
|
-
requireSha,
|
|
13
|
-
resolveInside,
|
|
14
|
-
sha256File,
|
|
15
|
-
} from "../actions/macos-credential-island/lib.js";
|
|
16
|
-
|
|
17
|
-
function env(name) {
|
|
18
|
-
const value = String(process.env[name] || "").trim();
|
|
19
|
-
if (!value) throw new Error(`${name} is required`);
|
|
20
|
-
return value;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function run(command, args) {
|
|
24
|
-
const result = spawnSync(command, args, {
|
|
25
|
-
encoding: "utf8",
|
|
26
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
27
|
-
});
|
|
28
|
-
if (result.error || result.status !== 0) {
|
|
29
|
-
throw (
|
|
30
|
-
result.error ||
|
|
31
|
-
new Error(
|
|
32
|
-
`${path.basename(command)} failed with status ${result.status}: ${(result.stderr || result.stdout || "").trim().slice(0, 1200)}`,
|
|
33
|
-
)
|
|
34
|
-
);
|
|
35
|
-
}
|
|
36
|
-
return `${result.stdout || ""}${result.stderr || ""}`.trim();
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function plistValue(appPath, key) {
|
|
40
|
-
return run("/usr/bin/plutil", [
|
|
41
|
-
"-extract",
|
|
42
|
-
key,
|
|
43
|
-
"raw",
|
|
44
|
-
"-o",
|
|
45
|
-
"-",
|
|
46
|
-
path.join(appPath, "Contents", "Info.plist"),
|
|
47
|
-
]);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export function sealMacosCredentialInput() {
|
|
51
|
-
if (process.platform !== "darwin")
|
|
52
|
-
throw new Error("credential input sealing requires macOS");
|
|
53
|
-
const workspace = path.resolve(process.cwd());
|
|
54
|
-
const appPath = resolveInside(
|
|
55
|
-
workspace,
|
|
56
|
-
env("BUILDCHAIN_CREDENTIAL_ISLAND_APP_PATH"),
|
|
57
|
-
"credential island app path",
|
|
58
|
-
);
|
|
59
|
-
if (!fs.statSync(appPath).isDirectory() || !appPath.endsWith(".app")) {
|
|
60
|
-
throw new Error("credential island app path must name one .app directory");
|
|
61
|
-
}
|
|
62
|
-
assertRealPathInside(workspace, appPath, "credential island app path");
|
|
63
|
-
assertContainedSymlinks(appPath);
|
|
64
|
-
const repository = requireRepository(env("BUILDCHAIN_SOURCE_REPOSITORY"));
|
|
65
|
-
const sourceSha = requireSha(env("BUILDCHAIN_SOURCE_SHA"), "source SHA");
|
|
66
|
-
const treeSha = requireSha(
|
|
67
|
-
env("BUILDCHAIN_SOURCE_TREE_SHA"),
|
|
68
|
-
"source tree SHA",
|
|
69
|
-
);
|
|
70
|
-
const platformId = env("BUILDCHAIN_PLATFORM_ID");
|
|
71
|
-
const arch = process.arch;
|
|
72
|
-
if (!["arm64", "x64"].includes(arch))
|
|
73
|
-
throw new Error(`unsupported macOS architecture: ${arch}`);
|
|
74
|
-
const outputRoot = path.resolve(env("BUILDCHAIN_CREDENTIAL_ISLAND_OUTPUT"));
|
|
75
|
-
const relativeOutput = path.relative(workspace, outputRoot);
|
|
76
|
-
if (
|
|
77
|
-
!relativeOutput ||
|
|
78
|
-
relativeOutput.startsWith("..") ||
|
|
79
|
-
path.isAbsolute(relativeOutput)
|
|
80
|
-
) {
|
|
81
|
-
throw new Error(
|
|
82
|
-
"credential island output must stay inside the build workspace",
|
|
83
|
-
);
|
|
84
|
-
}
|
|
85
|
-
fs.mkdirSync(outputRoot, { recursive: true });
|
|
86
|
-
const archivePath = path.join(outputRoot, "unsigned-app.zip");
|
|
87
|
-
run("/usr/bin/ditto", [
|
|
88
|
-
"-c",
|
|
89
|
-
"-k",
|
|
90
|
-
"--sequesterRsrc",
|
|
91
|
-
"--keepParent",
|
|
92
|
-
appPath,
|
|
93
|
-
archivePath,
|
|
94
|
-
]);
|
|
95
|
-
const archive = {
|
|
96
|
-
file: path.basename(archivePath),
|
|
97
|
-
format: "ditto-zip",
|
|
98
|
-
bytes: fs.statSync(archivePath).size,
|
|
99
|
-
sha256: sha256File(archivePath),
|
|
100
|
-
};
|
|
101
|
-
const manifest = {
|
|
102
|
-
schema: INPUT_CONTRACT,
|
|
103
|
-
sealedAt: new Date().toISOString(),
|
|
104
|
-
source: { repository, sha: sourceSha, treeSha },
|
|
105
|
-
platform: { id: platformId, os: "macos", arch },
|
|
106
|
-
app: {
|
|
107
|
-
archivePath: path.basename(appPath),
|
|
108
|
-
bundleId: plistValue(appPath, "CFBundleIdentifier"),
|
|
109
|
-
productName: plistValue(appPath, "CFBundleName"),
|
|
110
|
-
version: plistValue(appPath, "CFBundleShortVersionString"),
|
|
111
|
-
buildVersion: plistValue(appPath, "CFBundleVersion"),
|
|
112
|
-
},
|
|
113
|
-
archive,
|
|
114
|
-
};
|
|
115
|
-
const manifestPath = path.join(outputRoot, "credential-input.json");
|
|
116
|
-
fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
117
|
-
process.stdout.write(
|
|
118
|
-
`${JSON.stringify({ manifestPath, archivePath, archive })}\n`,
|
|
119
|
-
);
|
|
120
|
-
return manifest;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
if (
|
|
124
|
-
process.argv[1] &&
|
|
125
|
-
import.meta.url === pathToFileURL(process.argv[1]).href
|
|
126
|
-
) {
|
|
127
|
-
try {
|
|
128
|
-
sealMacosCredentialInput();
|
|
129
|
-
} catch (error) {
|
|
130
|
-
console.error(
|
|
131
|
-
`::error::${String(error?.message || error).replace(/\r?\n/gu, "%0A")}`,
|
|
132
|
-
);
|
|
133
|
-
process.exitCode = 1;
|
|
134
|
-
}
|
|
135
|
-
}
|