@empire-builder-kit/nx 1.0.0-rc.30 → 1.0.0-rc.32

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.
Files changed (310) hide show
  1. package/executors.json +8 -3
  2. package/package.json +14 -7
  3. package/src/aws/child-environment.d.ts +10 -0
  4. package/src/aws/child-environment.js +77 -0
  5. package/src/aws/child-environment.js.map +1 -0
  6. package/src/aws/identity.d.ts +13 -0
  7. package/src/aws/identity.js +41 -0
  8. package/src/aws/identity.js.map +1 -0
  9. package/src/aws-authority/index.d.ts +1 -0
  10. package/src/aws-authority/index.js +5 -0
  11. package/src/aws-authority/index.js.map +1 -0
  12. package/src/aws-authority/stage-authority.d.ts +240 -0
  13. package/src/aws-authority/stage-authority.js +2968 -0
  14. package/src/aws-authority/stage-authority.js.map +1 -0
  15. package/src/cli/main.d.ts +2 -0
  16. package/src/cli/main.js +11 -0
  17. package/src/cli/main.js.map +1 -1
  18. package/src/developer-agent/generator-planning.js +4 -4
  19. package/src/developer-agent/generator-planning.js.map +1 -1
  20. package/src/developer-agent/operation-registry.js +14 -6
  21. package/src/developer-agent/operation-registry.js.map +1 -1
  22. package/src/developer-agent/workspace-inspection.js +11 -2
  23. package/src/developer-agent/workspace-inspection.js.map +1 -1
  24. package/src/executors/deployment-plan/deployment-plan.js +1 -1
  25. package/src/executors/deployment-plan/deployment-plan.js.map +1 -1
  26. package/src/executors/foundation-preflight/foundation-preflight.d.ts +0 -1
  27. package/src/executors/foundation-preflight/foundation-preflight.js.map +1 -1
  28. package/src/executors/foundation-preflight/schema.json +0 -5
  29. package/src/executors/foundation-profile-preflight/foundation-profile-preflight.d.ts +12 -3
  30. package/src/executors/foundation-profile-preflight/foundation-profile-preflight.js +95 -32
  31. package/src/executors/foundation-profile-preflight/foundation-profile-preflight.js.map +1 -1
  32. package/src/executors/foundation-profile-preflight/production-database-safeguard.d.ts +64 -0
  33. package/src/executors/foundation-profile-preflight/production-database-safeguard.js +221 -0
  34. package/src/executors/foundation-profile-preflight/production-database-safeguard.js.map +1 -0
  35. package/src/executors/foundation-profile-preflight/schema.json +1 -6
  36. package/src/executors/foundation-remove-preflight/foundation-remove-preflight.d.ts +11 -1
  37. package/src/executors/foundation-remove-preflight/foundation-remove-preflight.js +44 -21
  38. package/src/executors/foundation-remove-preflight/foundation-remove-preflight.js.map +1 -1
  39. package/src/executors/foundation-remove-preflight/schema.json +6 -1
  40. package/src/executors/github-bootstrap/github-bootstrap.d.ts +1 -0
  41. package/src/executors/github-bootstrap/github-bootstrap.js +1 -0
  42. package/src/executors/github-bootstrap/github-bootstrap.js.map +1 -1
  43. package/src/executors/github-bootstrap/schema.json +7 -1
  44. package/src/executors/infrastructure-plan/infrastructure-plan.d.ts +27 -369
  45. package/src/executors/infrastructure-plan/infrastructure-plan.js +237 -5470
  46. package/src/executors/infrastructure-plan/infrastructure-plan.js.map +1 -1
  47. package/src/executors/infrastructure-plan/schema.json +8 -64
  48. package/src/executors/local-deploy/federated-credential-transport.d.ts +48 -0
  49. package/src/executors/local-deploy/federated-credential-transport.js +252 -0
  50. package/src/executors/local-deploy/federated-credential-transport.js.map +1 -0
  51. package/src/executors/local-deploy/local-approval.d.ts +4 -9
  52. package/src/executors/local-deploy/local-approval.js +3 -7
  53. package/src/executors/local-deploy/local-approval.js.map +1 -1
  54. package/src/executors/local-deploy/local-deploy.d.ts +60 -43
  55. package/src/executors/local-deploy/local-deploy.js +955 -804
  56. package/src/executors/local-deploy/local-deploy.js.map +1 -1
  57. package/src/executors/local-deploy/schema.json +3 -20
  58. package/src/executors/refresh/refresh.js +2 -2
  59. package/src/executors/refresh/refresh.js.map +1 -1
  60. package/src/executors/remove/remove.js +2 -2
  61. package/src/executors/remove/remove.js.map +1 -1
  62. package/src/executors/router-waf-rehearsal/router-waf-rehearsal.d.ts +27 -1
  63. package/src/executors/router-waf-rehearsal/router-waf-rehearsal.js +907 -22
  64. package/src/executors/router-waf-rehearsal/router-waf-rehearsal.js.map +1 -1
  65. package/src/executors/router-waf-rehearsal/schema.json +17 -2
  66. package/src/executors/secret-initialize/schema.json +42 -0
  67. package/src/executors/secret-initialize/secret-initialize.d.ts +32 -0
  68. package/src/executors/secret-initialize/secret-initialize.js +243 -0
  69. package/src/executors/secret-initialize/secret-initialize.js.map +1 -0
  70. package/src/executors/secret-metadata/secret-metadata.d.ts +4 -1
  71. package/src/executors/secret-metadata/secret-metadata.js +76 -29
  72. package/src/executors/secret-metadata/secret-metadata.js.map +1 -1
  73. package/src/executors/sst-lifecycle/generated-secret-retirement.d.ts +53 -0
  74. package/src/executors/sst-lifecycle/generated-secret-retirement.js +192 -0
  75. package/src/executors/sst-lifecycle/generated-secret-retirement.js.map +1 -0
  76. package/src/executors/sst-lifecycle/governed-native-operation.d.ts +46 -0
  77. package/src/executors/sst-lifecycle/governed-native-operation.js +153 -0
  78. package/src/executors/sst-lifecycle/governed-native-operation.js.map +1 -0
  79. package/src/executors/sst-lifecycle/run-sst-command.d.ts +27 -7
  80. package/src/executors/sst-lifecycle/run-sst-command.js +474 -89
  81. package/src/executors/sst-lifecycle/run-sst-command.js.map +1 -1
  82. package/src/executors/sst-lifecycle/schema.json +0 -5
  83. package/src/executors/sst-lifecycle/secret-preflight.d.ts +2 -0
  84. package/src/executors/sst-lifecycle/secret-preflight.js +13 -7
  85. package/src/executors/sst-lifecycle/secret-preflight.js.map +1 -1
  86. package/src/executors/unlock/unlock.js +2 -2
  87. package/src/executors/unlock/unlock.js.map +1 -1
  88. package/src/foundation/provision-logical-database.js +2 -0
  89. package/src/foundation/provision-logical-database.js.map +1 -1
  90. package/src/foundation/router-waf-rate-rehearsal.d.ts +97 -0
  91. package/src/foundation/router-waf-rate-rehearsal.js +210 -0
  92. package/src/foundation/router-waf-rate-rehearsal.js.map +1 -0
  93. package/src/foundation/router-waf.d.ts +59 -9
  94. package/src/foundation/router-waf.js +427 -32
  95. package/src/foundation/router-waf.js.map +1 -1
  96. package/src/foundation/stages.d.ts +1 -0
  97. package/src/foundation/stages.js +11 -0
  98. package/src/foundation/stages.js.map +1 -1
  99. package/src/generators/formatting.d.ts +8 -0
  100. package/src/generators/formatting.js +118 -0
  101. package/src/generators/formatting.js.map +1 -0
  102. package/src/generators/function/function.js +37 -17
  103. package/src/generators/function/function.js.map +1 -1
  104. package/src/generators/post-generation-install.js +2 -2
  105. package/src/generators/post-generation-install.js.map +1 -1
  106. package/src/generators/preset/generator.js +28 -8
  107. package/src/generators/preset/generator.js.map +1 -1
  108. package/src/generators/preset/schema.d.ts +3 -0
  109. package/src/generators/preset/schema.json +5 -5
  110. package/src/generators/preset/workspace-files-developer-agent.js +15 -7
  111. package/src/generators/preset/workspace-files-developer-agent.js.map +1 -1
  112. package/src/generators/preset/workspace-files-ecr-inspector.d.ts +18 -0
  113. package/src/generators/preset/workspace-files-ecr-inspector.js +298 -0
  114. package/src/generators/preset/workspace-files-ecr-inspector.js.map +1 -0
  115. package/src/generators/preset/workspace-files-performance-cost.d.ts +2 -2
  116. package/src/generators/preset/workspace-files-performance-cost.js +8 -8
  117. package/src/generators/preset/workspace-files-performance-cost.js.map +1 -1
  118. package/src/generators/preset/workspace-files-performance-load.d.ts +1 -1
  119. package/src/generators/preset/workspace-files-performance-load.js +29 -24
  120. package/src/generators/preset/workspace-files-performance-load.js.map +1 -1
  121. package/src/generators/preset/workspace-files-stage-authority.d.ts +6 -0
  122. package/src/generators/preset/workspace-files-stage-authority.js +479 -0
  123. package/src/generators/preset/workspace-files-stage-authority.js.map +1 -0
  124. package/src/generators/preset/workspace-files-thin-deploy.d.ts +40 -0
  125. package/src/generators/preset/workspace-files-thin-deploy.js +1500 -0
  126. package/src/generators/preset/workspace-files-thin-deploy.js.map +1 -0
  127. package/src/generators/preset/workspace-files-thin-docs.d.ts +5 -0
  128. package/src/generators/preset/workspace-files-thin-docs.js +240 -0
  129. package/src/generators/preset/workspace-files-thin-docs.js.map +1 -0
  130. package/src/generators/preset/workspace-files-thin-release.d.ts +14 -0
  131. package/src/generators/preset/workspace-files-thin-release.js +26 -0
  132. package/src/generators/preset/workspace-files-thin-release.js.map +1 -0
  133. package/src/generators/preset/workspace-files.d.ts +2 -1
  134. package/src/generators/preset/workspace-files.js +448 -680
  135. package/src/generators/preset/workspace-files.js.map +1 -1
  136. package/src/generators/repository-policy/repository-policy.js +149 -19
  137. package/src/generators/repository-policy/repository-policy.js.map +1 -1
  138. package/src/generators/repository-policy/schema.d.ts +2 -0
  139. package/src/generators/repository-policy/schema.json +11 -1
  140. package/src/generators/repository-policy/tree-policy.d.ts +3 -0
  141. package/src/generators/repository-policy/tree-policy.js +25 -0
  142. package/src/generators/repository-policy/tree-policy.js.map +1 -0
  143. package/src/generators/slice/built-in-plan-api-files.js +1 -0
  144. package/src/generators/slice/built-in-plan-api-files.js.map +1 -1
  145. package/src/generators/slice/built-in-plan-async-work-files.js +41 -0
  146. package/src/generators/slice/built-in-plan-async-work-files.js.map +1 -1
  147. package/src/generators/slice/built-in-plan-authorization-infrastructure-files.js +6 -0
  148. package/src/generators/slice/built-in-plan-authorization-infrastructure-files.js.map +1 -1
  149. package/src/generators/slice/built-in-plan-data-export-infrastructure-files.js +5 -31
  150. package/src/generators/slice/built-in-plan-data-export-infrastructure-files.js.map +1 -1
  151. package/src/generators/slice/built-in-plan-data-files.d.ts +1 -1
  152. package/src/generators/slice/built-in-plan-data-files.js +89 -30
  153. package/src/generators/slice/built-in-plan-data-files.js.map +1 -1
  154. package/src/generators/slice/built-in-plan-data-lifecycle-runtime-files.js +19 -0
  155. package/src/generators/slice/built-in-plan-data-lifecycle-runtime-files.js.map +1 -1
  156. package/src/generators/slice/built-in-plan-data-retention-launcher-files.js +21 -3
  157. package/src/generators/slice/built-in-plan-data-retention-launcher-files.js.map +1 -1
  158. package/src/generators/slice/built-in-plan-database-retirement-rehearsal-files.js +20 -1
  159. package/src/generators/slice/built-in-plan-database-retirement-rehearsal-files.js.map +1 -1
  160. package/src/generators/slice/built-in-plan-foundation-files.js +3 -5
  161. package/src/generators/slice/built-in-plan-foundation-files.js.map +1 -1
  162. package/src/generators/slice/built-in-plan-frontend-files.js +5 -1
  163. package/src/generators/slice/built-in-plan-frontend-files.js.map +1 -1
  164. package/src/generators/slice/built-in-plan-infrastructure-files.d.ts +1 -1
  165. package/src/generators/slice/built-in-plan-infrastructure-files.js +199 -188
  166. package/src/generators/slice/built-in-plan-infrastructure-files.js.map +1 -1
  167. package/src/generators/slice/built-in-plan-metadata-files.js +13 -0
  168. package/src/generators/slice/built-in-plan-metadata-files.js.map +1 -1
  169. package/src/generators/slice/built-in-plan-native-runtime-authority-files.d.ts +2 -0
  170. package/src/generators/slice/built-in-plan-native-runtime-authority-files.js +307 -0
  171. package/src/generators/slice/built-in-plan-native-runtime-authority-files.js.map +1 -0
  172. package/src/generators/slice/built-in-plan-operations-files.js +65 -51
  173. package/src/generators/slice/built-in-plan-operations-files.js.map +1 -1
  174. package/src/generators/slice/built-in-plan-performance-files.js +97 -19
  175. package/src/generators/slice/built-in-plan-performance-files.js.map +1 -1
  176. package/src/generators/slice/built-in-plan-project-files.js +9 -68
  177. package/src/generators/slice/built-in-plan-project-files.js.map +1 -1
  178. package/src/generators/slice/built-in-plan-secret-runtime-files.d.ts +1 -1
  179. package/src/generators/slice/built-in-plan-secret-runtime-files.js +87 -76
  180. package/src/generators/slice/built-in-plan-secret-runtime-files.js.map +1 -1
  181. package/src/generators/slice/built-in-plan-testing-files.js +94 -47
  182. package/src/generators/slice/built-in-plan-testing-files.js.map +1 -1
  183. package/src/generators/slice/built-in-plan-thin-release-files.d.ts +11 -0
  184. package/src/generators/slice/built-in-plan-thin-release-files.js +129 -0
  185. package/src/generators/slice/built-in-plan-thin-release-files.js.map +1 -0
  186. package/src/generators/slice/built-in-plan-verification-files.js +6 -2
  187. package/src/generators/slice/built-in-plan-verification-files.js.map +1 -1
  188. package/src/generators/slice/built-in-plan-web-files.js +4 -2
  189. package/src/generators/slice/built-in-plan-web-files.js.map +1 -1
  190. package/src/generators/slice/built-in-plan-workload-role-files.js +26 -4
  191. package/src/generators/slice/built-in-plan-workload-role-files.js.map +1 -1
  192. package/src/generators/slice/built-in-plan.d.ts +1 -0
  193. package/src/generators/slice/built-in-plan.js +28 -11
  194. package/src/generators/slice/built-in-plan.js.map +1 -1
  195. package/src/generators/slice/slice.js +30 -32
  196. package/src/generators/slice/slice.js.map +1 -1
  197. package/src/generators/upgrade/upgrade.d.ts +1 -1
  198. package/src/generators/upgrade/upgrade.js +200 -48
  199. package/src/generators/upgrade/upgrade.js.map +1 -1
  200. package/src/generators/workload/performance-registration.d.ts +1 -0
  201. package/src/generators/workload/performance-registration.js +9 -11
  202. package/src/generators/workload/performance-registration.js.map +1 -1
  203. package/src/generators/workload/service-performance-files.d.ts +1 -0
  204. package/src/generators/workload/service-performance-files.js +27 -7
  205. package/src/generators/workload/service-performance-files.js.map +1 -1
  206. package/src/generators/workload/workload.d.ts +1 -1
  207. package/src/generators/workload/workload.js +55 -28
  208. package/src/generators/workload/workload.js.map +1 -1
  209. package/src/git-policy/github-bootstrap.d.ts +28 -7
  210. package/src/git-policy/github-bootstrap.js +205 -163
  211. package/src/git-policy/github-bootstrap.js.map +1 -1
  212. package/src/index.d.ts +1 -0
  213. package/src/index.js +1 -0
  214. package/src/index.js.map +1 -1
  215. package/src/local-dev/private-state.d.ts +3 -0
  216. package/src/local-dev/private-state.js +17 -5
  217. package/src/local-dev/private-state.js.map +1 -1
  218. package/src/local-dev/tls.js +5 -1
  219. package/src/local-dev/tls.js.map +1 -1
  220. package/src/performance/cost-collection.d.ts +1 -1
  221. package/src/performance/cost-collection.js +4 -2
  222. package/src/performance/cost-collection.js.map +1 -1
  223. package/src/performance/cost-controls.d.ts +3 -1
  224. package/src/performance/cost-controls.js +9 -4
  225. package/src/performance/cost-controls.js.map +1 -1
  226. package/src/performance/evidence.d.ts +1 -1
  227. package/src/performance/evidence.js +5 -3
  228. package/src/performance/evidence.js.map +1 -1
  229. package/src/performance/load-safety.js +5 -3
  230. package/src/performance/load-safety.js.map +1 -1
  231. package/src/performance/model.d.ts +2 -2
  232. package/src/performance/model.js +4 -3
  233. package/src/performance/model.js.map +1 -1
  234. package/src/release/index.d.ts +0 -2
  235. package/src/release/index.js +0 -2
  236. package/src/release/index.js.map +1 -1
  237. package/src/release/source-authority.d.ts +4 -5
  238. package/src/release/source-authority.js +13 -123
  239. package/src/release/source-authority.js.map +1 -1
  240. package/src/repository-policy/aurora-preflight.d.ts +1 -1
  241. package/src/repository-policy/aurora-preflight.js +4 -1
  242. package/src/repository-policy/aurora-preflight.js.map +1 -1
  243. package/src/repository-policy/policy.d.ts +21 -2
  244. package/src/repository-policy/policy.js +290 -25
  245. package/src/repository-policy/policy.js.map +1 -1
  246. package/src/schemas/performance-policy-v1.json +3 -1
  247. package/src/schemas/repository-policy-v1.json +46 -2
  248. package/src/secrets/adapters.d.ts +7 -2
  249. package/src/secrets/adapters.js +34 -3
  250. package/src/secrets/adapters.js.map +1 -1
  251. package/src/secrets/aws-control-plane.d.ts +10 -1
  252. package/src/secrets/aws-control-plane.js +67 -0
  253. package/src/secrets/aws-control-plane.js.map +1 -1
  254. package/src/secrets/generated-aws-control-plane.d.ts +23 -0
  255. package/src/secrets/generated-aws-control-plane.js +259 -0
  256. package/src/secrets/generated-aws-control-plane.js.map +1 -0
  257. package/src/secrets/metadata.js +38 -10
  258. package/src/secrets/metadata.js.map +1 -1
  259. package/src/secrets/model.d.ts +8 -1
  260. package/src/secrets/model.js +24 -3
  261. package/src/secrets/model.js.map +1 -1
  262. package/src/secrets/operator.d.ts +89 -3
  263. package/src/secrets/operator.js +422 -9
  264. package/src/secrets/operator.js.map +1 -1
  265. package/src/secrets/registry.d.ts +2 -1
  266. package/src/secrets/registry.js +18 -1
  267. package/src/secrets/registry.js.map +1 -1
  268. package/src/secrets/workload-access.js +10 -2
  269. package/src/secrets/workload-access.js.map +1 -1
  270. package/src/supply-chain/action-pins.js +61 -7
  271. package/src/supply-chain/action-pins.js.map +1 -1
  272. package/src/supply-chain/build-output-bounds.d.ts +2 -0
  273. package/src/supply-chain/build-output-bounds.js +12 -0
  274. package/src/supply-chain/build-output-bounds.js.map +1 -0
  275. package/src/supply-chain/build-output.js +2 -4
  276. package/src/supply-chain/build-output.js.map +1 -1
  277. package/src/supply-chain/ecr-scanning.d.ts +5 -3
  278. package/src/supply-chain/ecr-scanning.js +11 -7
  279. package/src/supply-chain/ecr-scanning.js.map +1 -1
  280. package/src/supply-chain/ecs-deployed-image.d.ts +5 -3
  281. package/src/supply-chain/ecs-deployed-image.js +6 -4
  282. package/src/supply-chain/ecs-deployed-image.js.map +1 -1
  283. package/src/supply-chain/generated-container-release-command.js +849 -44
  284. package/src/supply-chain/generated-container-release-command.js.map +1 -1
  285. package/src/supply-chain/generated-ecr-collector-command.d.ts +0 -1
  286. package/src/supply-chain/generated-ecr-collector-command.js +188 -91
  287. package/src/supply-chain/generated-ecr-collector-command.js.map +1 -1
  288. package/src/supply-chain/generated-files.js +4 -3
  289. package/src/supply-chain/generated-files.js.map +1 -1
  290. package/src/supply-chain/index.d.ts +1 -1
  291. package/src/supply-chain/index.js +2 -1
  292. package/src/supply-chain/index.js.map +1 -1
  293. package/src/workload-roles/index.d.ts +10 -17
  294. package/src/workload-roles/index.js +5 -18
  295. package/src/workload-roles/index.js.map +1 -1
  296. package/src/workspace-deployment/identity.d.ts +3 -0
  297. package/src/workspace-deployment/identity.js +22 -0
  298. package/src/workspace-deployment/identity.js.map +1 -0
  299. package/src/workspace-deployment/index.d.ts +3 -0
  300. package/src/workspace-deployment/index.js +7 -0
  301. package/src/workspace-deployment/index.js.map +1 -0
  302. package/src/workspace-deployment/migration-evidence.d.ts +29 -0
  303. package/src/workspace-deployment/migration-evidence.js +83 -0
  304. package/src/workspace-deployment/migration-evidence.js.map +1 -0
  305. package/src/workspace-deployment/migration.d.ts +27 -0
  306. package/src/workspace-deployment/migration.js +109 -0
  307. package/src/workspace-deployment/migration.js.map +1 -0
  308. package/src/workspace-deployment/ownership.d.ts +81 -0
  309. package/src/workspace-deployment/ownership.js +381 -0
  310. package/src/workspace-deployment/ownership.js.map +1 -0
@@ -5,6 +5,64 @@ exports.renderContainerReleaseCommand = renderContainerReleaseCommand;
5
5
  const generated_ecr_collector_command_js_1 = require("./generated-ecr-collector-command.js");
6
6
  const safe_reference_1 = require("@empire-builder-kit/runtime/safe-reference");
7
7
  exports.CRANE_VERSION = '0.21.7';
8
+ const WINDOWS_HARDEN_PRIVATE_ACL = String.raw `
9
+ $ErrorActionPreference = 'Stop'
10
+ $path = $env:EBK_PRIVATE_PATH
11
+ $kind = $env:EBK_PRIVATE_KIND
12
+ $identity = [System.Security.Principal.WindowsIdentity]::GetCurrent()
13
+ if ($kind -eq 'directory') {
14
+ $info = [System.IO.DirectoryInfo]::new($path)
15
+ } else {
16
+ $info = [System.IO.FileInfo]::new($path)
17
+ }
18
+ $acl = $info.GetAccessControl()
19
+ $acl.SetAccessRuleProtection($true, $false)
20
+ foreach ($rule in @($acl.Access)) {
21
+ [void]$acl.RemoveAccessRuleSpecific($rule)
22
+ }
23
+ $rights = [System.Security.AccessControl.FileSystemRights]::FullControl
24
+ $type = [System.Security.AccessControl.AccessControlType]::Allow
25
+ if ($kind -eq 'directory') {
26
+ $inheritance = [System.Security.AccessControl.InheritanceFlags]'ContainerInherit,ObjectInherit'
27
+ $propagation = [System.Security.AccessControl.PropagationFlags]::None
28
+ $rule = [System.Security.AccessControl.FileSystemAccessRule]::new(
29
+ $identity.User, $rights, $inheritance, $propagation, $type
30
+ )
31
+ } else {
32
+ $rule = [System.Security.AccessControl.FileSystemAccessRule]::new(
33
+ $identity.User, $rights, $type
34
+ )
35
+ }
36
+ $acl.AddAccessRule($rule)
37
+ $info.SetAccessControl($acl)
38
+ `;
39
+ const WINDOWS_ASSERT_PRIVATE_ACL = String.raw `
40
+ $ErrorActionPreference = 'Stop'
41
+ $path = $env:EBK_PRIVATE_PATH
42
+ $kind = $env:EBK_PRIVATE_KIND
43
+ $identity = [System.Security.Principal.WindowsIdentity]::GetCurrent()
44
+ if ($kind -eq 'directory') {
45
+ $info = [System.IO.DirectoryInfo]::new($path)
46
+ } else {
47
+ $info = [System.IO.FileInfo]::new($path)
48
+ }
49
+ $acl = $info.GetAccessControl()
50
+ if (-not $acl.AreAccessRulesProtected) {
51
+ throw 'ACL inheritance is enabled.'
52
+ }
53
+ $allowRules = @($acl.Access | Where-Object {
54
+ $_.AccessControlType -eq [System.Security.AccessControl.AccessControlType]::Allow
55
+ })
56
+ $unsafeRules = @($allowRules | Where-Object {
57
+ $_.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier]).Value -ne $identity.User.Value
58
+ })
59
+ $selfRules = @($allowRules | Where-Object {
60
+ $_.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier]).Value -eq $identity.User.Value
61
+ })
62
+ if ($unsafeRules.Count -ne 0 -or $selfRules.Count -eq 0) {
63
+ throw 'ACL grants access beyond the current user or omits the current user.'
64
+ }
65
+ `;
8
66
  exports.CRANE_RELEASES = Object.freeze({
9
67
  'darwin-arm64': {
10
68
  archiveFormat: 'tar-gzip',
@@ -59,8 +117,8 @@ function renderContainerReleaseCommand(options = {}) {
59
117
  !/^\d+\.\d+\.\d+$/.test(craneVersion)) {
60
118
  throw new Error('The generated container release configuration is invalid.');
61
119
  }
62
- return `import { createHash } from 'node:crypto';
63
- import { execFileSync, spawnSync } from 'node:child_process';
120
+ return `import { createHash, randomBytes } from 'node:crypto';
121
+ import { execFileSync, spawn, spawnSync } from 'node:child_process';
64
122
  import {
65
123
  chmodSync,
66
124
  existsSync,
@@ -71,10 +129,11 @@ import {
71
129
  realpathSync,
72
130
  readdirSync,
73
131
  rmSync,
132
+ unlinkSync,
74
133
  writeFileSync,
75
134
  } from 'node:fs';
76
135
  import { basename, isAbsolute, join, relative, resolve, sep } from 'node:path';
77
- import { tmpdir } from 'node:os';
136
+ import { platform as hostOperatingSystem, tmpdir } from 'node:os';
78
137
  import {
79
138
  CONTAINER_NATIVE_LIFECYCLE_PROOFS,
80
139
  CONTAINER_RELEASE_PLATFORMS,
@@ -82,6 +141,7 @@ import {
82
141
  createContainerNativeRunProof,
83
142
  createContainerPlatformEvidence,
84
143
  createContainerReleaseBinding,
144
+ hasSensitiveSupplyChainShape,
85
145
  resolveContainerReleaseArtifact,
86
146
  supplyChainDigest,
87
147
  validateContainerArtifactIdentity,
@@ -95,6 +155,8 @@ const SYFT_VERSION = ${JSON.stringify(syftVersion)};
95
155
  const SYFT_RELEASES = Object.freeze(${JSON.stringify(releases)});
96
156
  const CRANE_VERSION = ${JSON.stringify(craneVersion)};
97
157
  const CRANE_RELEASES = Object.freeze(${JSON.stringify(craneReleases)});
158
+ const WINDOWS_HARDEN_PRIVATE_ACL = ${JSON.stringify(WINDOWS_HARDEN_PRIVATE_ACL)};
159
+ const WINDOWS_ASSERT_PRIVATE_ACL = ${JSON.stringify(WINDOWS_ASSERT_PRIVATE_ACL)};
98
160
  const SHA256 = /^[a-f0-9]{64}$/;
99
161
  const IMAGE_DIGEST = /^sha256:[a-f0-9]{64}$/;
100
162
  const COMMIT = /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/;
@@ -144,23 +206,43 @@ const packageRegistryInput =
144
206
  const localFederated =
145
207
  process.env.GITHUB_ACTIONS !== 'true' &&
146
208
  process.env.EBK_LOCAL_DEPLOYMENT_MODE === 'federated';
147
- if (localFederated) {
148
- const policy = JSON.parse(
149
- readFileSync(join(repositoryRoot, 'ebk.repository-policy.json'), 'utf8'),
209
+ const localRunId = process.env.EBK_LOCAL_CONTAINER_RUN_ID?.trim() ?? '';
210
+ const policy = JSON.parse(
211
+ readFileSync(join(repositoryRoot, 'ebk.repository-policy.json'), 'utf8'),
212
+ );
213
+ const selectedDeploymentProfile =
214
+ process.env.EBK_DEPLOYMENT_PROFILE?.trim() ?? '';
215
+ const activeStages = {
216
+ starter: ['development', 'production'],
217
+ 'production-isolated': ['development', 'staging', 'production'],
218
+ 'fully-isolated': ['development', 'staging', 'production'],
219
+ }[policy.deploymentProfile];
220
+ const locallyAdmittedStages = {
221
+ starter: ['development', 'production'],
222
+ 'production-isolated': ['development', 'staging'],
223
+ 'fully-isolated': ['development'],
224
+ }[policy.deploymentProfile];
225
+ if (
226
+ selectedDeploymentProfile !== policy.deploymentProfile ||
227
+ !activeStages ||
228
+ !activeStages.includes(stage) ||
229
+ policy.deploymentAccounts?.[stage] !== accountId ||
230
+ policy.deploymentRegion !== region
231
+ ) {
232
+ throw new Error(
233
+ 'Container release requires the exact committed profile, active stage, account, and Region.',
234
+ );
235
+ }
236
+ if (
237
+ localFederated &&
238
+ (!locallyAdmittedStages || !locallyAdmittedStages.includes(stage))
239
+ ) {
240
+ throw new Error(
241
+ 'Local container release requires one of the committed profile locally admitted stages.',
150
242
  );
151
- if (
152
- policy.deploymentProfile !== 'starter' ||
153
- stage === 'production' ||
154
- policy.deploymentAccounts?.[stage] !== accountId ||
155
- policy.deploymentRegion !== region
156
- ) {
157
- throw new Error(
158
- 'Local container release requires a locally admitted exact starter deployment target; production requires protected GitHub authority.',
159
- );
160
- }
161
243
  }
162
244
  const runId = localFederated
163
- ? (BigInt('0x' + sourceCommit.slice(0, 15)) + 1n).toString()
245
+ ? localRunId
164
246
  : process.env.GITHUB_RUN_ID?.trim() ?? '';
165
247
  const runAttempt = localFederated
166
248
  ? '1'
@@ -359,12 +441,657 @@ const execute = (command, args, label, options = {}) => {
359
441
  stdio: ['pipe', 'pipe', 'pipe'],
360
442
  });
361
443
  } catch (error) {
444
+ if (options.failureDiagnostics === 'bounded-closure') {
445
+ const diagnosticStream = (value) => {
446
+ const raw = value === undefined || value === null ? '' : String(value);
447
+ if (raw.length === 0) return '[empty]';
448
+ if (hasSensitiveSupplyChainShape(raw)) {
449
+ return '[redacted-sensitive-output]';
450
+ }
451
+ const bytes = Buffer.from(raw, 'utf8');
452
+ const truncated = bytes.byteLength > 1_024;
453
+ const tail = truncated
454
+ ? bytes.subarray(bytes.byteLength - 1_024).toString('utf8')
455
+ : raw;
456
+ return JSON.stringify((truncated ? '[truncated]\\n' : '') + tail);
457
+ };
458
+ const status = Number.isSafeInteger(error?.status)
459
+ ? String(error.status)
460
+ : 'unavailable';
461
+ const signal =
462
+ typeof error?.signal === 'string' && /^[A-Z0-9]+$/.test(error.signal)
463
+ ? error.signal
464
+ : 'none';
465
+ throw new Error(
466
+ label +
467
+ ' failed without complete evidence: ' +
468
+ 'status=' +
469
+ status +
470
+ '; signal=' +
471
+ signal +
472
+ '; stdout=' +
473
+ diagnosticStream(error?.stdout) +
474
+ '; stderr=' +
475
+ diagnosticStream(error?.stderr),
476
+ );
477
+ }
362
478
  throw new Error(
363
479
  label + ' failed without complete evidence: ' +
364
480
  String(error?.stderr ?? error?.message ?? error).slice(0, 2_000),
365
481
  );
366
482
  }
367
483
  };
484
+ let releaseBuilder;
485
+ let releaseBuildChild;
486
+ let releaseBuildStopPromise;
487
+ let releaseBuilderSignal;
488
+ const releaseBuilderSignalHandlers = new Map();
489
+ const releaseToolTemporaryDirectories = new Set();
490
+ const yieldToReleaseSignals = () =>
491
+ new Promise((resolve) => setImmediate(resolve));
492
+ const stopExactReleaseBuildChild = async (signal = 'SIGTERM') => {
493
+ if (releaseBuildStopPromise) return releaseBuildStopPromise;
494
+ const observed = releaseBuildChild;
495
+ if (!observed) return;
496
+ releaseBuildStopPromise = (async () => {
497
+ const child = observed.child;
498
+ if (hostOperatingSystem() === 'win32') {
499
+ const taskkill = join(process.env.SystemRoot ?? '', 'System32', 'taskkill.exe');
500
+ if (!isAbsolute(taskkill) || !existsSync(taskkill) || realpathSync(taskkill) !== taskkill) {
501
+ throw new Error('Exact Windows Buildx cleanup requires canonical SystemRoot.');
502
+ }
503
+ const stopped = spawnSync(
504
+ taskkill,
505
+ ['/PID', String(child.pid), '/T', '/F'],
506
+ {
507
+ cwd: repositoryRoot,
508
+ encoding: 'utf8',
509
+ env: process.env,
510
+ maxBuffer: 1024 * 1024,
511
+ stdio: ['ignore', 'pipe', 'pipe'],
512
+ windowsHide: true,
513
+ },
514
+ );
515
+ if (
516
+ child.exitCode === null &&
517
+ (stopped.error || stopped.signal || stopped.status !== 0)
518
+ ) {
519
+ throw new Error('Exact Windows Buildx process-tree cleanup failed.');
520
+ }
521
+ } else if (child.pid) {
522
+ try {
523
+ process.kill(-child.pid, signal);
524
+ } catch (error) {
525
+ if (error?.code !== 'ESRCH') throw error;
526
+ }
527
+ }
528
+ if (child.exitCode === null) {
529
+ const closed = await Promise.race([
530
+ observed.close.then(() => true),
531
+ new Promise((resolve) => setTimeout(() => resolve(false), 5_000)),
532
+ ]);
533
+ if (!closed && hostOperatingSystem() !== 'win32' && child.pid) {
534
+ try {
535
+ process.kill(-child.pid, 'SIGKILL');
536
+ } catch (error) {
537
+ if (error?.code !== 'ESRCH') throw error;
538
+ }
539
+ const killed = await Promise.race([
540
+ observed.close.then(() => true),
541
+ new Promise((resolve) => setTimeout(() => resolve(false), 5_000)),
542
+ ]);
543
+ if (!killed) {
544
+ throw new Error('Exact Buildx process group did not terminate.');
545
+ }
546
+ }
547
+ }
548
+ })().finally(() => {
549
+ if (releaseBuildChild === observed) releaseBuildChild = undefined;
550
+ releaseBuildStopPromise = undefined;
551
+ });
552
+ return releaseBuildStopPromise;
553
+ };
554
+ const executeObservedBuildx = async (args, label) => {
555
+ if (releaseBuildChild) {
556
+ throw new Error('Only one invocation-owned Buildx child may run at a time.');
557
+ }
558
+ const child = spawn('docker', args, {
559
+ cwd: repositoryRoot,
560
+ detached: hostOperatingSystem() !== 'win32',
561
+ env: process.env,
562
+ stdio: ['ignore', 'pipe', 'pipe'],
563
+ windowsHide: true,
564
+ });
565
+ const stdout = [];
566
+ const stderr = [];
567
+ let outputBytes = 0;
568
+ const retain = (chunks, stream, chunk) => {
569
+ outputBytes += chunk.byteLength;
570
+ if (outputBytes > MAX_OUTPUT_BYTES) {
571
+ void stopExactReleaseBuildChild('SIGTERM');
572
+ return;
573
+ }
574
+ chunks.push(chunk);
575
+ stream.write(chunk);
576
+ };
577
+ child.stdout.on('data', (chunk) => retain(stdout, process.stdout, chunk));
578
+ child.stderr.on('data', (chunk) => retain(stderr, process.stderr, chunk));
579
+ const close = new Promise((resolve) => {
580
+ child.once('error', (error) => resolve({ error }));
581
+ child.once('close', (status, signal) => resolve({ signal, status }));
582
+ });
583
+ releaseBuildChild = { child, close };
584
+ const result = await close;
585
+ await stopExactReleaseBuildChild();
586
+ if (
587
+ outputBytes > MAX_OUTPUT_BYTES ||
588
+ result.error ||
589
+ result.signal ||
590
+ result.status !== 0
591
+ ) {
592
+ throw new Error(
593
+ label + ' failed without complete evidence: ' +
594
+ Buffer.concat(stderr).toString('utf8').slice(-2_000),
595
+ );
596
+ }
597
+ return Buffer.concat(stdout).toString('utf8');
598
+ };
599
+ const runWindowsPrivateAcl = (script, path, kind) => {
600
+ const systemRoot = process.env.SystemRoot;
601
+ if (!systemRoot || !isAbsolute(systemRoot)) {
602
+ throw new Error('Windows private-path hardening requires an absolute SystemRoot.');
603
+ }
604
+ const powershell = join(
605
+ systemRoot,
606
+ 'System32',
607
+ 'WindowsPowerShell',
608
+ 'v1.0',
609
+ 'powershell.exe',
610
+ );
611
+ const result = spawnSync(
612
+ powershell,
613
+ [
614
+ '-NoLogo',
615
+ '-NoProfile',
616
+ '-NonInteractive',
617
+ '-EncodedCommand',
618
+ Buffer.from(script, 'utf16le').toString('base64'),
619
+ ],
620
+ {
621
+ cwd: repositoryRoot,
622
+ encoding: 'utf8',
623
+ env: {
624
+ ...process.env,
625
+ EBK_PRIVATE_KIND: kind,
626
+ EBK_PRIVATE_PATH: path,
627
+ },
628
+ maxBuffer: 1024 * 1024,
629
+ stdio: ['ignore', 'pipe', 'pipe'],
630
+ timeout: 15_000,
631
+ windowsHide: true,
632
+ },
633
+ );
634
+ if (result.error || result.signal || result.status !== 0) {
635
+ throw new Error(
636
+ 'Windows private-path ACL operation failed closed: ' +
637
+ String(result.stderr || result.error?.message || result.signal || result.status)
638
+ .slice(-2_000),
639
+ );
640
+ }
641
+ };
642
+ const assertPrivateReleaseBuilderPathIdentity = (path, kind) => {
643
+ const metadata = lstatSync(path);
644
+ if (
645
+ metadata.isSymbolicLink() ||
646
+ (kind === 'directory' ? !metadata.isDirectory() : !metadata.isFile()) ||
647
+ realpathSync(path) !== path
648
+ ) {
649
+ throw new Error(
650
+ 'Container-release builder ownership path failed canonical type validation.',
651
+ );
652
+ }
653
+ return metadata;
654
+ };
655
+ const assertPrivateReleaseBuilderPath = (path, kind) => {
656
+ const metadata = assertPrivateReleaseBuilderPathIdentity(path, kind);
657
+ if (hostOperatingSystem() === 'win32') {
658
+ runWindowsPrivateAcl(WINDOWS_ASSERT_PRIVATE_ACL, path, kind);
659
+ return metadata;
660
+ }
661
+ const expectedMode = kind === 'directory' ? 0o700 : 0o600;
662
+ if (
663
+ (metadata.mode & 0o777) !== expectedMode ||
664
+ (typeof process.getuid === 'function' && metadata.uid !== process.getuid())
665
+ ) {
666
+ throw new Error(
667
+ 'Container-release builder ownership path is not current-user private.',
668
+ );
669
+ }
670
+ return metadata;
671
+ };
672
+ const hardenPrivateReleaseBuilderPath = (path, kind) => {
673
+ assertPrivateReleaseBuilderPathIdentity(path, kind);
674
+ if (hostOperatingSystem() === 'win32') {
675
+ runWindowsPrivateAcl(WINDOWS_HARDEN_PRIVATE_ACL, path, kind);
676
+ } else {
677
+ chmodSync(path, kind === 'directory' ? 0o700 : 0o600);
678
+ }
679
+ return assertPrivateReleaseBuilderPath(path, kind);
680
+ };
681
+ const assertCanonicalAncestorsBeforeCreate = (requested) => {
682
+ const requestedRelative = relative(repositoryRoot, requested);
683
+ if (
684
+ requestedRelative === '' ||
685
+ requestedRelative === '..' ||
686
+ requestedRelative.startsWith('..' + sep) ||
687
+ isAbsolute(requestedRelative)
688
+ ) {
689
+ throw new Error('Container-release builder ownership path escapes its repository.');
690
+ }
691
+ let cursor = repositoryRoot;
692
+ for (const segment of requestedRelative.split(sep)) {
693
+ cursor = join(cursor, segment);
694
+ if (!existsSync(cursor)) break;
695
+ const metadata = lstatSync(cursor);
696
+ if (metadata.isSymbolicLink() || realpathSync(cursor) !== cursor) {
697
+ throw new Error(
698
+ 'Container-release builder ownership path has a symbolic or noncanonical ancestor.',
699
+ );
700
+ }
701
+ }
702
+ };
703
+ const releaseBuilderLeaseRoot = () => {
704
+ const requested = resolve(
705
+ repositoryRoot,
706
+ '.ebk/cache/container-release-builders',
707
+ );
708
+ assertCanonicalAncestorsBeforeCreate(requested);
709
+ mkdirSync(requested, { mode: 0o700, recursive: true });
710
+ hardenPrivateReleaseBuilderPath(requested, 'directory');
711
+ const canonical = realpathSync(requested);
712
+ if (canonical !== requested) {
713
+ throw new Error(
714
+ 'Container-release builder leases require one repository-private owned directory.',
715
+ );
716
+ }
717
+ return canonical;
718
+ };
719
+ const readReleaseBuilderLease = (leasePath, expectedName) => {
720
+ const metadata = assertPrivateReleaseBuilderPath(leasePath, 'file');
721
+ if (
722
+ metadata.nlink !== 1 ||
723
+ metadata.size < 2 ||
724
+ metadata.size > 16 * 1024
725
+ ) {
726
+ throw new Error('Container-release builder lease is not one private file.');
727
+ }
728
+ let lease;
729
+ try {
730
+ lease = JSON.parse(readFileSync(leasePath, 'utf8'));
731
+ } catch {
732
+ throw new Error('Container-release builder lease is invalid JSON.');
733
+ }
734
+ const keys = Object.keys(lease ?? {}).sort();
735
+ if (
736
+ JSON.stringify(keys) !==
737
+ JSON.stringify([
738
+ 'builder',
739
+ 'cleanup',
740
+ 'createdAt',
741
+ 'driver',
742
+ 'kind',
743
+ 'ownerPid',
744
+ 'schemaVersion',
745
+ ]) ||
746
+ lease.kind !== 'ebk-container-release-builder-lease' ||
747
+ lease.schemaVersion !== 1 ||
748
+ lease.builder !== expectedName ||
749
+ !/^ebk-release-[a-f0-9]{32}$/.test(lease.builder) ||
750
+ lease.driver !== 'docker-container' ||
751
+ JSON.stringify(lease.cleanup) !==
752
+ JSON.stringify(['docker', 'buildx', 'rm', lease.builder]) ||
753
+ !Number.isSafeInteger(lease.ownerPid) ||
754
+ lease.ownerPid <= 0 ||
755
+ !Number.isFinite(Date.parse(lease.createdAt)) ||
756
+ new Date(lease.createdAt).toISOString() !== lease.createdAt
757
+ ) {
758
+ throw new Error('Container-release builder lease has an invalid identity.');
759
+ }
760
+ return lease;
761
+ };
762
+ const removeReleaseBuilderSignalHandlers = () => {
763
+ for (const [signal, handler] of releaseBuilderSignalHandlers) {
764
+ process.removeListener(signal, handler);
765
+ }
766
+ releaseBuilderSignalHandlers.clear();
767
+ };
768
+ const ensureReleaseBuilderSignalHandlers = () => {
769
+ if (releaseBuilderSignalHandlers.size > 0) return;
770
+ for (const signal of ['SIGINT', 'SIGTERM']) {
771
+ const handler = () => void handleReleaseBuilderSignal(signal);
772
+ releaseBuilderSignalHandlers.set(signal, handler);
773
+ process.on(signal, handler);
774
+ }
775
+ };
776
+ const removeReleaseBuilderSignalHandlersWhenIdle = () => {
777
+ if (!releaseBuilder && releaseToolTemporaryDirectories.size === 0) {
778
+ removeReleaseBuilderSignalHandlers();
779
+ }
780
+ };
781
+ const createReleaseToolTemporaryDirectory = (prefix) => {
782
+ const created = mkdtempSync(join(tmpdir(), prefix));
783
+ const metadata = lstatSync(created);
784
+ if (
785
+ !metadata.isDirectory() ||
786
+ metadata.isSymbolicLink() ||
787
+ !isAbsolute(created)
788
+ ) {
789
+ throw new Error('Container-release tool temporary directory is not exact.');
790
+ }
791
+ const temporary = realpathSync(created);
792
+ const canonicalMetadata = lstatSync(temporary);
793
+ if (
794
+ !isAbsolute(temporary) ||
795
+ !canonicalMetadata.isDirectory() ||
796
+ canonicalMetadata.isSymbolicLink() ||
797
+ canonicalMetadata.dev !== metadata.dev ||
798
+ canonicalMetadata.ino !== metadata.ino
799
+ ) {
800
+ throw new Error('Container-release tool temporary directory is not canonical.');
801
+ }
802
+ releaseToolTemporaryDirectories.add(temporary);
803
+ ensureReleaseBuilderSignalHandlers();
804
+ return temporary;
805
+ };
806
+ const removeExactReleaseToolTemporaryDirectory = (temporary) => {
807
+ if (!releaseToolTemporaryDirectories.has(temporary)) {
808
+ throw new Error('Container-release tool temporary directory is not owned.');
809
+ }
810
+ const metadata = lstatSync(temporary);
811
+ if (
812
+ !metadata.isDirectory() ||
813
+ metadata.isSymbolicLink() ||
814
+ !isAbsolute(temporary) ||
815
+ realpathSync(temporary) !== temporary
816
+ ) {
817
+ throw new Error(
818
+ 'Container-release tool temporary directory changed identity before cleanup.',
819
+ );
820
+ }
821
+ rmSync(temporary, { force: true, recursive: true });
822
+ if (existsSync(temporary)) {
823
+ throw new Error('Container-release tool temporary directory cleanup failed.');
824
+ }
825
+ releaseToolTemporaryDirectories.delete(temporary);
826
+ removeReleaseBuilderSignalHandlersWhenIdle();
827
+ };
828
+ const removeAllReleaseToolTemporaryDirectories = () => {
829
+ const cleanupErrors = [];
830
+ for (const temporary of [...releaseToolTemporaryDirectories]) {
831
+ try {
832
+ removeExactReleaseToolTemporaryDirectory(temporary);
833
+ } catch (error) {
834
+ cleanupErrors.push(error);
835
+ }
836
+ }
837
+ if (cleanupErrors.length > 0) {
838
+ throw new AggregateError(
839
+ cleanupErrors,
840
+ 'Exact container-release tool temporary cleanup failed.',
841
+ );
842
+ }
843
+ };
844
+ const releaseBuilderCleanupDiagnostic = (builder) =>
845
+ 'docker buildx rm ' + builder.name +
846
+ ' (ownership lease: ' + builder.leasePath + ')';
847
+ const listExactReleaseBuilderNames = ({ signal } = {}) => {
848
+ const inventory = spawnSync(
849
+ 'docker',
850
+ ['buildx', 'ls', '--format', '{{json .Name}}'],
851
+ {
852
+ cwd: repositoryRoot,
853
+ encoding: 'utf8',
854
+ env: process.env,
855
+ maxBuffer: 1024 * 1024,
856
+ stdio: ['ignore', 'pipe', 'pipe'],
857
+ timeout: signal ? 5_000 : undefined,
858
+ },
859
+ );
860
+ if (inventory.error || inventory.signal || inventory.status !== 0) {
861
+ throw new Error(
862
+ 'Invocation-owned Buildx inventory failed while proving exact cleanup: ' +
863
+ String(
864
+ inventory.stderr ||
865
+ inventory.error?.message ||
866
+ inventory.signal ||
867
+ inventory.status,
868
+ ).slice(-2_000),
869
+ );
870
+ }
871
+ const lines = inventory.stdout.trim() === ''
872
+ ? []
873
+ : inventory.stdout.trim().split(/\\r?\\n/u);
874
+ if (lines.length > 256) {
875
+ throw new Error('Invocation-owned Buildx inventory is unbounded.');
876
+ }
877
+ const names = new Set();
878
+ for (const line of lines) {
879
+ let name;
880
+ try {
881
+ name = JSON.parse(line);
882
+ } catch {
883
+ throw new Error('Invocation-owned Buildx inventory is not exact JSON.');
884
+ }
885
+ if (
886
+ typeof name !== 'string' ||
887
+ name.length === 0 ||
888
+ name.length > 256 ||
889
+ names.has(name)
890
+ ) {
891
+ throw new Error('Invocation-owned Buildx inventory has an invalid identity.');
892
+ }
893
+ names.add(name);
894
+ }
895
+ return names;
896
+ };
897
+ const removeExactReleaseBuilder = (builder, { signal } = {}) => {
898
+ readReleaseBuilderLease(builder.leasePath, builder.name);
899
+ const cleanup = spawnSync(
900
+ 'docker',
901
+ ['buildx', 'rm', builder.name],
902
+ {
903
+ cwd: repositoryRoot,
904
+ encoding: 'utf8',
905
+ env: process.env,
906
+ maxBuffer: 1024 * 1024,
907
+ stdio: ['ignore', 'pipe', 'pipe'],
908
+ timeout: signal ? 5_000 : undefined,
909
+ },
910
+ );
911
+ if (cleanup.error || cleanup.signal || cleanup.status !== 0) {
912
+ let builderNames;
913
+ try {
914
+ builderNames = listExactReleaseBuilderNames({ signal });
915
+ } catch (inventoryError) {
916
+ const error = new AggregateError(
917
+ [
918
+ cleanup.error ?? new Error(String(cleanup.stderr || cleanup.status)),
919
+ inventoryError,
920
+ ],
921
+ 'Invocation-owned Buildx cleanup and exact absence proof both failed; ' +
922
+ 'retain ' + releaseBuilderCleanupDiagnostic(builder) + '.',
923
+ );
924
+ if (signal) {
925
+ process.stderr.write(error.message + '\\n');
926
+ return false;
927
+ }
928
+ throw error;
929
+ }
930
+ if (!builderNames.has(builder.name)) {
931
+ unlinkSync(builder.leasePath);
932
+ process.stderr.write(
933
+ 'ebk-release-builder-already-absent ' +
934
+ JSON.stringify({ builder: builder.name, leasePath: builder.leasePath }) +
935
+ '\\n',
936
+ );
937
+ return true;
938
+ }
939
+ const diagnostic = [cleanup.stdout, cleanup.stderr]
940
+ .filter(Boolean)
941
+ .join('\\n')
942
+ .slice(-2_000);
943
+ const error = new Error(
944
+ 'Invocation-owned Buildx cleanup failed; use only the exact retained ' +
945
+ 'hard-kill cleanup command ' + releaseBuilderCleanupDiagnostic(builder) +
946
+ (diagnostic ? ': ' + diagnostic : ''),
947
+ );
948
+ if (signal) {
949
+ process.stderr.write(error.message + '\\n');
950
+ return false;
951
+ }
952
+ throw error;
953
+ }
954
+ unlinkSync(builder.leasePath);
955
+ return true;
956
+ };
957
+ const recoverReleaseBuilders = () => {
958
+ const leaseRoot = releaseBuilderLeaseRoot();
959
+ const entries = readdirSync(leaseRoot, { withFileTypes: true }).sort(
960
+ (left, right) =>
961
+ left.name < right.name ? -1 : left.name > right.name ? 1 : 0,
962
+ );
963
+ if (entries.length > 64) {
964
+ throw new Error('Container-release builder lease inventory is unbounded.');
965
+ }
966
+ for (const entry of entries) {
967
+ const matched = /^(ebk-release-[a-f0-9]{32})\\.json$/.exec(entry.name);
968
+ if (!entry.isFile() || !matched) {
969
+ throw new Error(
970
+ 'Container-release builder lease directory contains an unexpected entry.',
971
+ );
972
+ }
973
+ const leasePath = join(leaseRoot, entry.name);
974
+ const lease = readReleaseBuilderLease(leasePath, matched[1]);
975
+ const builderNames = listExactReleaseBuilderNames();
976
+ if (builderNames.has(lease.builder)) {
977
+ throw new Error(
978
+ 'A retained invocation-owned Buildx builder still exists; inspect it and use only the exact cleanup command ' +
979
+ releaseBuilderCleanupDiagnostic({ leasePath, name: lease.builder }) + '.',
980
+ );
981
+ }
982
+ unlinkSync(leasePath);
983
+ process.stderr.write(
984
+ 'ebk-release-builder-recovered ' +
985
+ JSON.stringify({ builder: lease.builder, leasePath }) +
986
+ '\\n',
987
+ );
988
+ }
989
+ };
990
+ const cleanupReleaseBuilder = ({ signal } = {}) => {
991
+ const builder = releaseBuilder;
992
+ if (!builder) {
993
+ removeReleaseBuilderSignalHandlersWhenIdle();
994
+ return;
995
+ }
996
+ releaseBuilder = undefined;
997
+ const removed = removeExactReleaseBuilder(builder, { signal });
998
+ removeReleaseBuilderSignalHandlersWhenIdle();
999
+ return removed;
1000
+ };
1001
+ const handleReleaseBuilderSignal = async (signal) => {
1002
+ if (releaseBuilderSignal) return;
1003
+ releaseBuilderSignal = signal;
1004
+ removeReleaseBuilderSignalHandlers();
1005
+ const cleanupErrors = [];
1006
+ let childStopped = false;
1007
+ try {
1008
+ await stopExactReleaseBuildChild(signal);
1009
+ childStopped = true;
1010
+ } catch (error) {
1011
+ cleanupErrors.push(error);
1012
+ }
1013
+ if (childStopped) {
1014
+ try {
1015
+ cleanupReleaseBuilder({ signal });
1016
+ } catch (error) {
1017
+ cleanupErrors.push(error);
1018
+ }
1019
+ }
1020
+ try {
1021
+ removeAllReleaseToolTemporaryDirectories();
1022
+ } catch (error) {
1023
+ cleanupErrors.push(error);
1024
+ }
1025
+ if (cleanupErrors.length > 0) {
1026
+ process.stderr.write(
1027
+ 'Invocation-owned Buildx signal cleanup failed closed; the exact ' +
1028
+ 'ownership lease was retained: ' +
1029
+ cleanupErrors
1030
+ .map((error) => String(error?.message ?? error))
1031
+ .join('; ')
1032
+ .slice(-2_000) + '\\n',
1033
+ );
1034
+ }
1035
+ try {
1036
+ removeReleaseBuilderSignalHandlers();
1037
+ } finally {
1038
+ process.kill(process.pid, signal);
1039
+ }
1040
+ };
1041
+ const ensureReleaseBuilder = () => {
1042
+ if (releaseBuilder) return releaseBuilder;
1043
+ const name = 'ebk-release-' + randomBytes(16).toString('hex');
1044
+ const leaseRoot = releaseBuilderLeaseRoot();
1045
+ const leasePath = join(leaseRoot, name + '.json');
1046
+ const builder = {
1047
+ leasePath,
1048
+ leaseRoot,
1049
+ name,
1050
+ };
1051
+ writeFileSync(
1052
+ leasePath,
1053
+ JSON.stringify({
1054
+ builder: name,
1055
+ cleanup: ['docker', 'buildx', 'rm', name],
1056
+ createdAt: new Date().toISOString(),
1057
+ driver: 'docker-container',
1058
+ kind: 'ebk-container-release-builder-lease',
1059
+ ownerPid: process.pid,
1060
+ schemaVersion: 1,
1061
+ }) + '\\n',
1062
+ { encoding: 'utf8', flag: 'wx', mode: 0o600 },
1063
+ );
1064
+ hardenPrivateReleaseBuilderPath(leasePath, 'file');
1065
+ releaseBuilder = builder;
1066
+ ensureReleaseBuilderSignalHandlers();
1067
+ process.stderr.write(
1068
+ 'ebk-release-builder-ownership ' +
1069
+ JSON.stringify({
1070
+ builder: name,
1071
+ hardKillCleanup: ['docker', 'buildx', 'rm', name],
1072
+ leasePath,
1073
+ }) +
1074
+ '\\n',
1075
+ );
1076
+ try {
1077
+ execute(
1078
+ 'docker',
1079
+ ['buildx', 'create', '--name', name, '--driver', 'docker-container'],
1080
+ 'Invocation-owned native Buildx builder creation',
1081
+ );
1082
+ } catch (error) {
1083
+ try {
1084
+ cleanupReleaseBuilder();
1085
+ } catch (cleanupError) {
1086
+ throw new AggregateError(
1087
+ [error, cleanupError],
1088
+ 'Invocation-owned Buildx creation and cleanup both failed.',
1089
+ );
1090
+ }
1091
+ throw error;
1092
+ }
1093
+ return builder;
1094
+ };
368
1095
  const awsJson = (args, label) => {
369
1096
  const raw = execute('aws', args, label);
370
1097
  try {
@@ -419,6 +1146,18 @@ const caller = awsJson(
419
1146
  if (caller?.Account !== accountId) {
420
1147
  throw new Error('Container release AWS caller differs from the target account.');
421
1148
  }
1149
+ execute(
1150
+ process.execPath,
1151
+ [
1152
+ safePath(
1153
+ 'tools/stage-authority-render.mjs',
1154
+ 'Workspace ownership doctor',
1155
+ ),
1156
+ '--stage=' + stage,
1157
+ '--ownership-doctor=true',
1158
+ ],
1159
+ 'Workspace stage ownership',
1160
+ );
422
1161
  const controls = readJson(
423
1162
  safePath('operations/release.json', 'Release controls', { base: sliceRoot }),
424
1163
  'Release controls',
@@ -760,7 +1499,8 @@ const acquireSyft = async () => {
760
1499
  if (!release) {
761
1500
  throw new Error('No reviewed Syft release exists for ' + platformKey + '.');
762
1501
  }
763
- const temporary = mkdtempSync(join(tmpdir(), 'ebk-container-syft-'));
1502
+ const temporary = createReleaseToolTemporaryDirectory('ebk-container-syft-');
1503
+ try {
764
1504
  const supplied = process.env.EBK_SYFT_ARCHIVE_PATH?.trim();
765
1505
  const archive = supplied
766
1506
  ? safePath(relative(repositoryRoot, resolve(repositoryRoot, supplied)), 'Supplied Syft archive')
@@ -798,6 +1538,7 @@ const acquireSyft = async () => {
798
1538
  chmodSync(path, 0o700);
799
1539
  }
800
1540
  const version = String(execute(path, ['version'], 'Syft version'));
1541
+ await yieldToReleaseSignals();
801
1542
  if (
802
1543
  !new RegExp('(?:Version|version):?\\\\s*' + SYFT_VERSION.replaceAll('.', '\\\\.')).test(
803
1544
  version,
@@ -805,7 +1546,18 @@ const acquireSyft = async () => {
805
1546
  ) {
806
1547
  throw new Error('Syft runtime version differs from reviewed policy.');
807
1548
  }
808
- return { path, temporary };
1549
+ return { path, temporary };
1550
+ } catch (error) {
1551
+ try {
1552
+ removeExactReleaseToolTemporaryDirectory(temporary);
1553
+ } catch (cleanupError) {
1554
+ throw new AggregateError(
1555
+ [error, cleanupError],
1556
+ 'Syft acquisition and exact temporary cleanup both failed.',
1557
+ );
1558
+ }
1559
+ throw error;
1560
+ }
809
1561
  };
810
1562
  const acquireCrane = async () => {
811
1563
  const platformKey = process.platform + '-' + process.arch;
@@ -813,7 +1565,8 @@ const acquireCrane = async () => {
813
1565
  if (!release) {
814
1566
  throw new Error('No reviewed Crane release exists for ' + platformKey + '.');
815
1567
  }
816
- const temporary = mkdtempSync(join(tmpdir(), 'ebk-container-crane-'));
1568
+ const temporary = createReleaseToolTemporaryDirectory('ebk-container-crane-');
1569
+ try {
817
1570
  const supplied = process.env.EBK_CRANE_ARCHIVE_PATH?.trim();
818
1571
  const archive = supplied
819
1572
  ? safePath(
@@ -850,6 +1603,7 @@ const acquireCrane = async () => {
850
1603
  const path = join(temporary, 'crane');
851
1604
  chmodSync(path, 0o700);
852
1605
  const version = String(execute(path, ['version'], 'Crane version')).trim();
1606
+ await yieldToReleaseSignals();
853
1607
  if (
854
1608
  !new RegExp('(?:^|\\s|v)' + CRANE_VERSION.replaceAll('.', '\\.') + '(?:$|\\s)').test(
855
1609
  version,
@@ -857,7 +1611,18 @@ const acquireCrane = async () => {
857
1611
  ) {
858
1612
  throw new Error('Crane runtime version differs from reviewed policy.');
859
1613
  }
860
- return { path, temporary };
1614
+ return { path, temporary };
1615
+ } catch (error) {
1616
+ try {
1617
+ removeExactReleaseToolTemporaryDirectory(temporary);
1618
+ } catch (cleanupError) {
1619
+ throw new AggregateError(
1620
+ [error, cleanupError],
1621
+ 'Crane acquisition and exact temporary cleanup both failed.',
1622
+ );
1623
+ }
1624
+ throw error;
1625
+ }
861
1626
  };
862
1627
  const parseCycloneDx = (value, label) => {
863
1628
  if (
@@ -957,8 +1722,8 @@ const npmClosureFor = (syftDocument, options = {}) => {
957
1722
  }
958
1723
  return unique;
959
1724
  };
960
- const deriveExpectedProductionClosure = (syft, input) => {
961
- const temporary = mkdtempSync(join(tmpdir(), 'ebk-container-closure-'));
1725
+ const deriveExpectedProductionClosure = async (syft, input) => {
1726
+ const temporary = createReleaseToolTemporaryDirectory('ebk-container-closure-');
962
1727
  const deployed = join(temporary, 'deployed');
963
1728
  try {
964
1729
  execute(
@@ -977,16 +1742,24 @@ const deriveExpectedProductionClosure = (syft, input) => {
977
1742
  deployed,
978
1743
  ],
979
1744
  'Independent production closure deployment',
980
- { env: { COREPACK_ENABLE_DOWNLOAD_PROMPT: '0' } },
1745
+ {
1746
+ env: { COREPACK_ENABLE_DOWNLOAD_PROMPT: '0' },
1747
+ failureDiagnostics: 'bounded-closure',
1748
+ },
981
1749
  );
1750
+ await yieldToReleaseSignals();
982
1751
  const raw = String(
983
1752
  execute(
984
1753
  syft.path,
985
1754
  ['scan', 'dir:' + deployed, '--output', 'syft-json'],
986
1755
  'Independent production closure inventory',
987
- { env: { SYFT_CHECK_FOR_APP_UPDATE: 'false' } },
1756
+ {
1757
+ env: { SYFT_CHECK_FOR_APP_UPDATE: 'false' },
1758
+ failureDiagnostics: 'bounded-closure',
1759
+ },
988
1760
  ),
989
1761
  );
1762
+ await yieldToReleaseSignals();
990
1763
  let inventory;
991
1764
  try {
992
1765
  inventory = JSON.parse(raw);
@@ -1041,7 +1814,7 @@ const deriveExpectedProductionClosure = (syft, input) => {
1041
1814
  }
1042
1815
  return { digest: supplyChainDigest(packages), packages };
1043
1816
  } finally {
1044
- rmSync(temporary, { force: true, recursive: true });
1817
+ removeExactReleaseToolTemporaryDirectory(temporary);
1045
1818
  }
1046
1819
  };
1047
1820
  const validateRetainedSbom = (entry, label) => {
@@ -1719,6 +2492,7 @@ if (mode === 'promote') {
1719
2492
  slice,
1720
2493
  'binding.json',
1721
2494
  );
2495
+ await yieldToReleaseSignals();
1722
2496
  immutableWrite(output, binding);
1723
2497
  process.stdout.write(
1724
2498
  JSON.stringify({
@@ -1731,21 +2505,26 @@ if (mode === 'promote') {
1731
2505
  }) + '\\n',
1732
2506
  );
1733
2507
  } finally {
1734
- rmSync(crane.temporary, { force: true, recursive: true });
2508
+ removeExactReleaseToolTemporaryDirectory(crane.temporary);
1735
2509
  }
1736
2510
  process.exit(0);
1737
2511
  }
1738
2512
 
1739
2513
  if (mode === 'platform') {
1740
- const syft = await acquireSyft();
1741
- const crane = stage === 'development' ? await acquireCrane() : undefined;
2514
+ let syft;
2515
+ let crane;
2516
+ let platformError;
1742
2517
  try {
1743
- const expectedClosures = new Map(
1744
- containerInputs.map((input) => [
2518
+ syft = await acquireSyft();
2519
+ crane = stage === 'development' ? await acquireCrane() : undefined;
2520
+ recoverReleaseBuilders();
2521
+ const expectedClosures = new Map();
2522
+ for (const input of containerInputs) {
2523
+ expectedClosures.set(
1745
2524
  input.declared.id,
1746
- deriveExpectedProductionClosure(syft, input),
1747
- ]),
1748
- );
2525
+ await deriveExpectedProductionClosure(syft, input),
2526
+ );
2527
+ }
1749
2528
  requireReviewedCheckout();
1750
2529
  for (const input of containerInputs) requireCandidateAuthority(input);
1751
2530
  login();
@@ -1788,11 +2567,13 @@ if (mode === 'platform') {
1788
2567
  'Exact staging retirement child promotion',
1789
2568
  );
1790
2569
  } else {
1791
- execute(
1792
- 'docker',
1793
- [
2570
+ const platformBuilder = ensureReleaseBuilder();
2571
+ try {
2572
+ await executeObservedBuildx([
1794
2573
  'buildx',
1795
2574
  'build',
2575
+ '--builder',
2576
+ platformBuilder.name,
1796
2577
  ...(packageRegistry
1797
2578
  ? [
1798
2579
  '--build-arg',
@@ -1814,9 +2595,10 @@ if (mode === 'platform') {
1814
2595
  '--tag',
1815
2596
  taggedReference,
1816
2597
  relative(repositoryRoot, input.context) || '.',
1817
- ],
1818
- 'Native container build and push',
1819
- );
2598
+ ], 'Native container build and push');
2599
+ } finally {
2600
+ cleanupReleaseBuilder();
2601
+ }
1820
2602
  }
1821
2603
  const imageDigest = exactImageByTag(repositoryName, tag);
1822
2604
  const imageReference = registry + '/' + repositoryName + '@' + imageDigest;
@@ -1991,6 +2773,7 @@ if (mode === 'platform') {
1991
2773
  evidence,
1992
2774
  );
1993
2775
  }
2776
+ await yieldToReleaseSignals();
1994
2777
  process.stdout.write(
1995
2778
  JSON.stringify({
1996
2779
  artifactCount: containerInputs.length,
@@ -1999,12 +2782,33 @@ if (mode === 'platform') {
1999
2782
  stage,
2000
2783
  }) + '\\n',
2001
2784
  );
2785
+ } catch (error) {
2786
+ platformError = error;
2002
2787
  } finally {
2003
- rmSync(syft.temporary, { force: true, recursive: true });
2004
- if (crane) {
2005
- rmSync(crane.temporary, { force: true, recursive: true });
2788
+ const cleanupErrors = [];
2789
+ for (const cleanup of [
2790
+ () => cleanupReleaseBuilder(),
2791
+ ...(syft
2792
+ ? [() => removeExactReleaseToolTemporaryDirectory(syft.temporary)]
2793
+ : []),
2794
+ ...(crane
2795
+ ? [() => removeExactReleaseToolTemporaryDirectory(crane.temporary)]
2796
+ : []),
2797
+ ]) {
2798
+ try {
2799
+ cleanup();
2800
+ } catch (error) {
2801
+ cleanupErrors.push(error);
2802
+ }
2803
+ }
2804
+ if (cleanupErrors.length > 0) {
2805
+ throw new AggregateError(
2806
+ [...(platformError ? [platformError] : []), ...cleanupErrors],
2807
+ 'Container platform release and independent owned-resource cleanup failed.',
2808
+ );
2006
2809
  }
2007
2810
  }
2811
+ if (platformError) throw platformError;
2008
2812
  process.exit(0);
2009
2813
  }
2010
2814
 
@@ -2216,6 +3020,7 @@ const output = resolve(
2216
3020
  slice,
2217
3021
  'binding.json',
2218
3022
  );
3023
+ await yieldToReleaseSignals();
2219
3024
  immutableWrite(output, binding);
2220
3025
  process.stdout.write(
2221
3026
  JSON.stringify({
@@ -2228,7 +3033,7 @@ process.stdout.write(
2228
3033
  );
2229
3034
  } finally {
2230
3035
  if (assemblyCrane) {
2231
- rmSync(assemblyCrane.temporary, { force: true, recursive: true });
3036
+ removeExactReleaseToolTemporaryDirectory(assemblyCrane.temporary);
2232
3037
  }
2233
3038
  }
2234
3039
  `;