@kungfu-tech/buildchain 2.14.17-alpha.8 → 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.
@@ -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", "credential-island"],
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 bindings = distributionBindings(registry, detection.surfaces);
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 (expectedManifestCount > 0 && manifests.length !== expectedManifestCount) {
20
+ if (expectedPlatformCount > 0 && manifests.length !== expectedPlatformCount) {
26
21
  throw new Error(
27
- `expected ${expectedManifestCount} platform manifests, found ${manifests.length} under ${inputRoot}`,
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
- }