@dsh-enhanced/plugin-control-plane 0.1.31 → 0.1.34
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/README.md +352 -4
- package/bin/dsh-local-release-adapter.js +80 -16
- package/bin/dsh-npm-registry-adapter.js +613 -0
- package/bin/dsh-source-adoption-authority.js +12 -0
- package/bin/dsh-source-approval-authority.js +16 -0
- package/bin/dsh-source-release-authority.js +12 -0
- package/bin/dsh-systemd-host-attestor.js +707 -0
- package/bin/dsh-task-observation-authority.js +15 -0
- package/lib/adapter-process.d.ts +23 -0
- package/lib/adapter-process.d.ts.map +1 -0
- package/lib/adapter-process.js +305 -0
- package/lib/adapter-process.js.map +1 -0
- package/lib/adoption-coordinator.d.ts +63 -0
- package/lib/adoption-coordinator.d.ts.map +1 -0
- package/lib/adoption-coordinator.js +241 -0
- package/lib/adoption-coordinator.js.map +1 -0
- package/lib/adoption-handoff.d.ts +16 -0
- package/lib/adoption-handoff.d.ts.map +1 -0
- package/lib/adoption-handoff.js +11 -0
- package/lib/adoption-handoff.js.map +1 -0
- package/lib/attestation.d.ts.map +1 -1
- package/lib/attestation.js +49 -1
- package/lib/attestation.js.map +1 -1
- package/lib/catalog.d.ts.map +1 -1
- package/lib/catalog.js +29 -2
- package/lib/catalog.js.map +1 -1
- package/lib/cli.d.ts +33 -9
- package/lib/cli.d.ts.map +1 -1
- package/lib/cli.js +624 -178
- package/lib/cli.js.map +1 -1
- package/lib/deployment-readiness.d.ts +38 -0
- package/lib/deployment-readiness.d.ts.map +1 -0
- package/lib/deployment-readiness.js +154 -0
- package/lib/deployment-readiness.js.map +1 -0
- package/lib/effect-blocked-replay.d.ts +81 -0
- package/lib/effect-blocked-replay.d.ts.map +1 -0
- package/lib/effect-blocked-replay.js +259 -0
- package/lib/effect-blocked-replay.js.map +1 -0
- package/lib/errors.d.ts +6 -0
- package/lib/errors.d.ts.map +1 -0
- package/lib/errors.js +9 -0
- package/lib/errors.js.map +1 -0
- package/lib/foreground-deployment-runtime.d.ts +20 -0
- package/lib/foreground-deployment-runtime.d.ts.map +1 -0
- package/lib/foreground-deployment-runtime.js +79 -0
- package/lib/foreground-deployment-runtime.js.map +1 -0
- package/lib/foreground-deployment.d.ts +17 -0
- package/lib/foreground-deployment.d.ts.map +1 -0
- package/lib/foreground-deployment.js +43 -0
- package/lib/foreground-deployment.js.map +1 -0
- package/lib/host-attestor.d.ts +1 -1
- package/lib/host-attestor.d.ts.map +1 -1
- package/lib/host-attestor.js +51 -57
- package/lib/host-attestor.js.map +1 -1
- package/lib/index.d.ts +25 -2
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +11 -2
- package/lib/index.js.map +1 -1
- package/lib/npm-publish.d.ts +25 -0
- package/lib/npm-publish.d.ts.map +1 -0
- package/lib/npm-publish.js +280 -0
- package/lib/npm-publish.js.map +1 -0
- package/lib/owner-task-gap-types.d.ts +21 -0
- package/lib/owner-task-gap-types.d.ts.map +1 -0
- package/lib/owner-task-gap-types.js +2 -0
- package/lib/owner-task-gap-types.js.map +1 -0
- package/lib/owner-task-gaps.d.ts +24 -0
- package/lib/owner-task-gaps.d.ts.map +1 -0
- package/lib/owner-task-gaps.js +74 -0
- package/lib/owner-task-gaps.js.map +1 -0
- package/lib/post-activation.d.ts +33 -0
- package/lib/post-activation.d.ts.map +1 -0
- package/lib/post-activation.js +219 -0
- package/lib/post-activation.js.map +1 -0
- package/lib/registry-fetch.d.ts +50 -0
- package/lib/registry-fetch.d.ts.map +1 -0
- package/lib/registry-fetch.js +267 -0
- package/lib/registry-fetch.js.map +1 -0
- package/lib/release.d.ts +4 -4
- package/lib/release.d.ts.map +1 -1
- package/lib/release.js +142 -56
- package/lib/release.js.map +1 -1
- package/lib/replay-endpoint-protocol.d.ts +65 -0
- package/lib/replay-endpoint-protocol.d.ts.map +1 -0
- package/lib/replay-endpoint-protocol.js +183 -0
- package/lib/replay-endpoint-protocol.js.map +1 -0
- package/lib/replay-endpoint.d.ts +7 -0
- package/lib/replay-endpoint.d.ts.map +1 -0
- package/lib/replay-endpoint.js +193 -0
- package/lib/replay-endpoint.js.map +1 -0
- package/lib/replay-grant.d.ts +62 -0
- package/lib/replay-grant.d.ts.map +1 -0
- package/lib/replay-grant.js +208 -0
- package/lib/replay-grant.js.map +1 -0
- package/lib/replay-journal.d.ts +25 -0
- package/lib/replay-journal.d.ts.map +1 -0
- package/lib/replay-journal.js +275 -0
- package/lib/replay-journal.js.map +1 -0
- package/lib/runtime-observer-protocol.d.ts +63 -0
- package/lib/runtime-observer-protocol.d.ts.map +1 -0
- package/lib/runtime-observer-protocol.js +280 -0
- package/lib/runtime-observer-protocol.js.map +1 -0
- package/lib/runtime-observer.d.ts +8 -0
- package/lib/runtime-observer.d.ts.map +1 -0
- package/lib/runtime-observer.js +182 -0
- package/lib/runtime-observer.js.map +1 -0
- package/lib/service.d.ts +145 -1
- package/lib/service.d.ts.map +1 -1
- package/lib/service.js +734 -6
- package/lib/service.js.map +1 -1
- package/lib/source-adoption-authority.d.ts +65 -0
- package/lib/source-adoption-authority.d.ts.map +1 -0
- package/lib/source-adoption-authority.js +271 -0
- package/lib/source-adoption-authority.js.map +1 -0
- package/lib/source-adoption-runner.d.ts +28 -0
- package/lib/source-adoption-runner.d.ts.map +1 -0
- package/lib/source-adoption-runner.js +197 -0
- package/lib/source-adoption-runner.js.map +1 -0
- package/lib/source-approval-authority.d.ts +50 -0
- package/lib/source-approval-authority.d.ts.map +1 -0
- package/lib/source-approval-authority.js +412 -0
- package/lib/source-approval-authority.js.map +1 -0
- package/lib/source-approval-client.d.ts +32 -0
- package/lib/source-approval-client.d.ts.map +1 -0
- package/lib/source-approval-client.js +175 -0
- package/lib/source-approval-client.js.map +1 -0
- package/lib/source-build.d.ts +60 -0
- package/lib/source-build.d.ts.map +1 -0
- package/lib/source-build.js +311 -0
- package/lib/source-build.js.map +1 -0
- package/lib/source-context.d.ts +24 -0
- package/lib/source-context.d.ts.map +1 -0
- package/lib/source-context.js +99 -0
- package/lib/source-context.js.map +1 -0
- package/lib/source-job-types.d.ts +87 -0
- package/lib/source-job-types.d.ts.map +1 -0
- package/lib/source-job-types.js +2 -0
- package/lib/source-job-types.js.map +1 -0
- package/lib/source-jobs.d.ts +113 -0
- package/lib/source-jobs.d.ts.map +1 -0
- package/lib/source-jobs.js +535 -0
- package/lib/source-jobs.js.map +1 -0
- package/lib/source-release-authority.d.ts +35 -0
- package/lib/source-release-authority.d.ts.map +1 -0
- package/lib/source-release-authority.js +271 -0
- package/lib/source-release-authority.js.map +1 -0
- package/lib/source-release-client.d.ts +10 -0
- package/lib/source-release-client.d.ts.map +1 -0
- package/lib/source-release-client.js +24 -0
- package/lib/source-release-client.js.map +1 -0
- package/lib/source-release-runner.d.ts +31 -0
- package/lib/source-release-runner.d.ts.map +1 -0
- package/lib/source-release-runner.js +125 -0
- package/lib/source-release-runner.js.map +1 -0
- package/lib/source-versioning.d.ts +24 -0
- package/lib/source-versioning.d.ts.map +1 -0
- package/lib/source-versioning.js +213 -0
- package/lib/source-versioning.js.map +1 -0
- package/lib/source-workspace.d.ts +117 -0
- package/lib/source-workspace.d.ts.map +1 -0
- package/lib/source-workspace.js +458 -0
- package/lib/source-workspace.js.map +1 -0
- package/lib/sqlite.d.ts +1 -1
- package/lib/sqlite.d.ts.map +1 -1
- package/lib/sqlite.js +509 -7
- package/lib/sqlite.js.map +1 -1
- package/lib/store.d.ts +229 -6
- package/lib/store.d.ts.map +1 -1
- package/lib/store.js +2431 -383
- package/lib/store.js.map +1 -1
- package/lib/task-observation-authority.d.ts +29 -0
- package/lib/task-observation-authority.d.ts.map +1 -0
- package/lib/task-observation-authority.js +370 -0
- package/lib/task-observation-authority.js.map +1 -0
- package/lib/task-observation-client.d.ts +4 -0
- package/lib/task-observation-client.d.ts.map +1 -0
- package/lib/task-observation-client.js +20 -0
- package/lib/task-observation-client.js.map +1 -0
- package/lib/task-observation-runtime.d.ts +59 -0
- package/lib/task-observation-runtime.d.ts.map +1 -0
- package/lib/task-observation-runtime.js +331 -0
- package/lib/task-observation-runtime.js.map +1 -0
- package/lib/task-observation-store.d.ts +17 -0
- package/lib/task-observation-store.d.ts.map +1 -0
- package/lib/task-observation-store.js +115 -0
- package/lib/task-observation-store.js.map +1 -0
- package/lib/task-observation-types.d.ts +67 -0
- package/lib/task-observation-types.d.ts.map +1 -0
- package/lib/task-observation-types.js +2 -0
- package/lib/task-observation-types.js.map +1 -0
- package/lib/trust.d.ts +3 -0
- package/lib/trust.d.ts.map +1 -1
- package/lib/trust.js +0 -0
- package/lib/trust.js.map +1 -1
- package/lib/types.d.ts +275 -8
- package/lib/types.d.ts.map +1 -1
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +40 -5
package/lib/release.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { spawn } from 'node:child_process';
|
|
2
1
|
import { createHash, createPublicKey, verify } from 'node:crypto';
|
|
3
2
|
import { constants as fsConstants } from 'node:fs';
|
|
4
3
|
import { lstat, open, realpath } from 'node:fs/promises';
|
|
5
4
|
import { dirname, isAbsolute, posix, resolve } from 'node:path';
|
|
6
5
|
import { catalogAdmissionId, parseCatalog } from './catalog.js';
|
|
6
|
+
import { ControlledProcessError, executeControlledProcess } from './adapter-process.js';
|
|
7
7
|
import { ControlPlaneStoreError } from './store.js';
|
|
8
8
|
import { inheritedReleaseAdapterEnvironment, openTrustedExecutable, verifyOpenTrustedExecutable } from './trust.js';
|
|
9
9
|
const ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,159}$/u;
|
|
@@ -273,7 +273,7 @@ export function parseSourcePublishReconciliationRequest(value) {
|
|
|
273
273
|
}
|
|
274
274
|
return request;
|
|
275
275
|
}
|
|
276
|
-
function
|
|
276
|
+
function parseSourcePublishReconciliationEvidenceV1(value) {
|
|
277
277
|
const item = record(value, 'source publish reconciliation evidence');
|
|
278
278
|
exact(item, ['kind', 'outcome', 'registryId', 'registryReference', 'packageName', 'packageVersion', 'expectedTarballSha256',
|
|
279
279
|
'expectedTarballIntegrity', 'expectedArtifactStatementDigest', 'expectedArtifactSignatureDigest',
|
|
@@ -326,17 +326,90 @@ function parseSourcePublishReconciliationEvidence(value) {
|
|
|
326
326
|
}
|
|
327
327
|
return Object.freeze(evidence);
|
|
328
328
|
}
|
|
329
|
+
function bareHttpsUrl(value, label, allowEncodedSlash = false) {
|
|
330
|
+
const raw = opaqueLine(value, label);
|
|
331
|
+
let parsed;
|
|
332
|
+
try {
|
|
333
|
+
parsed = new URL(raw);
|
|
334
|
+
}
|
|
335
|
+
catch {
|
|
336
|
+
throw new ControlPlaneStoreError('invalid-input', `${label} is invalid`);
|
|
337
|
+
}
|
|
338
|
+
const canonical = raw === parsed.href || (parsed.pathname === '/' && raw === parsed.href.slice(0, -1));
|
|
339
|
+
if (parsed.protocol !== 'https:' || parsed.username !== '' || parsed.password !== '' || parsed.search !== '' || parsed.hash !== ''
|
|
340
|
+
|| !canonical || /%(?:25|5c|2e)|%(?![a-f0-9]{2})/iu.test(parsed.pathname) || (!allowEncodedSlash && /%2f/iu.test(parsed.pathname))
|
|
341
|
+
|| parsed.pathname.includes('//')) {
|
|
342
|
+
throw new ControlPlaneStoreError('invalid-input', `${label} is not a bare canonical HTTPS URL`);
|
|
343
|
+
}
|
|
344
|
+
return raw;
|
|
345
|
+
}
|
|
346
|
+
function parseSourceNpmPublishReconciliationEvidence(value) {
|
|
347
|
+
const item = record(value, 'npm publish reconciliation evidence');
|
|
348
|
+
exact(item, ['kind', 'outcome', 'registryId', 'registryReference', 'packageName', 'packageVersion', 'expectedTarballSha256',
|
|
349
|
+
'expectedTarballIntegrity', 'expectedArtifactStatementDigest', 'expectedArtifactSignatureDigest', 'observedTarballSha256',
|
|
350
|
+
'observedTarballIntegrity', 'ambiguousPublishOperationId', 'ambiguousPublishReceiptDigest', 'detailDigest',
|
|
351
|
+
'metadataReference', 'metadataIntegrity', 'downloadedBytes'], 'npm publish reconciliation evidence');
|
|
352
|
+
if (item.kind !== 'npm-publish-reconciliation' || !['exists-match', 'unknown', 'digest-conflict'].includes(String(item.outcome))) {
|
|
353
|
+
throw new ControlPlaneStoreError('invalid-input', 'npm publish reconciliation evidence outcome is invalid');
|
|
354
|
+
}
|
|
355
|
+
const nullableDigest = (raw, label) => raw === null ? null : digestText(raw, label);
|
|
356
|
+
const nullableIntegrity = (raw, label) => raw === null ? null : integrity(raw, label);
|
|
357
|
+
const nullableUrl = (raw, label, allowEncodedSlash = false) => raw === null ? null
|
|
358
|
+
: bareHttpsUrl(raw, label, allowEncodedSlash);
|
|
359
|
+
const evidence = {
|
|
360
|
+
kind: 'npm-publish-reconciliation',
|
|
361
|
+
outcome: item.outcome,
|
|
362
|
+
registryId: text(item.registryId, 'registryId'), registryReference: nullableUrl(item.registryReference, 'registryReference'),
|
|
363
|
+
packageName: text(item.packageName, 'packageName', PACKAGE), packageVersion: text(item.packageVersion, 'packageVersion', VERSION),
|
|
364
|
+
expectedTarballSha256: digestText(item.expectedTarballSha256, 'expectedTarballSha256'),
|
|
365
|
+
expectedTarballIntegrity: integrity(item.expectedTarballIntegrity, 'expectedTarballIntegrity'),
|
|
366
|
+
expectedArtifactStatementDigest: digestText(item.expectedArtifactStatementDigest, 'expectedArtifactStatementDigest'),
|
|
367
|
+
expectedArtifactSignatureDigest: digestText(item.expectedArtifactSignatureDigest, 'expectedArtifactSignatureDigest'),
|
|
368
|
+
observedTarballSha256: nullableDigest(item.observedTarballSha256, 'observedTarballSha256'),
|
|
369
|
+
observedTarballIntegrity: nullableIntegrity(item.observedTarballIntegrity, 'observedTarballIntegrity'),
|
|
370
|
+
ambiguousPublishOperationId: text(item.ambiguousPublishOperationId, 'ambiguousPublishOperationId'),
|
|
371
|
+
ambiguousPublishReceiptDigest: digestText(item.ambiguousPublishReceiptDigest, 'ambiguousPublishReceiptDigest'),
|
|
372
|
+
detailDigest: digestText(item.detailDigest, 'detailDigest'), metadataReference: nullableUrl(item.metadataReference, 'metadataReference', true),
|
|
373
|
+
metadataIntegrity: nullableIntegrity(item.metadataIntegrity, 'metadataIntegrity'),
|
|
374
|
+
downloadedBytes: item.downloadedBytes === null ? null : integer(item.downloadedBytes, 'downloadedBytes', 1),
|
|
375
|
+
};
|
|
376
|
+
if (evidence.downloadedBytes !== null && evidence.downloadedBytes > 268_435_456) {
|
|
377
|
+
throw new ControlPlaneStoreError('invalid-input', 'downloadedBytes must be a bounded integer');
|
|
378
|
+
}
|
|
379
|
+
const fullyObserved = evidence.registryReference !== null && evidence.metadataReference !== null && evidence.metadataIntegrity !== null
|
|
380
|
+
&& evidence.downloadedBytes !== null && evidence.observedTarballSha256 !== null && evidence.observedTarballIntegrity !== null
|
|
381
|
+
&& evidence.metadataIntegrity === evidence.observedTarballIntegrity;
|
|
382
|
+
if (evidence.outcome === 'exists-match' && (!fullyObserved || evidence.observedTarballSha256 !== evidence.expectedTarballSha256
|
|
383
|
+
|| evidence.observedTarballIntegrity !== evidence.expectedTarballIntegrity)) {
|
|
384
|
+
throw new ControlPlaneStoreError('invalid-input', 'npm exists-match reconciliation does not prove the exact artifact');
|
|
385
|
+
}
|
|
386
|
+
if (evidence.outcome === 'digest-conflict' && (!fullyObserved || (evidence.observedTarballSha256 === evidence.expectedTarballSha256
|
|
387
|
+
&& evidence.observedTarballIntegrity === evidence.expectedTarballIntegrity))) {
|
|
388
|
+
throw new ControlPlaneStoreError('invalid-input', 'npm digest-conflict reconciliation does not prove a conflicting artifact');
|
|
389
|
+
}
|
|
390
|
+
if (evidence.outcome === 'unknown' && (evidence.registryReference !== null || evidence.metadataReference !== null
|
|
391
|
+
|| evidence.metadataIntegrity !== null || evidence.downloadedBytes !== null || evidence.observedTarballSha256 !== null
|
|
392
|
+
|| evidence.observedTarballIntegrity !== null)) {
|
|
393
|
+
throw new ControlPlaneStoreError('invalid-input', 'npm unknown reconciliation contains observed registry state');
|
|
394
|
+
}
|
|
395
|
+
return Object.freeze(evidence);
|
|
396
|
+
}
|
|
329
397
|
export function sourcePublishReconciliationRequestDigest(value) { return digest(value); }
|
|
330
398
|
export function sourcePublishReconciliationEvidenceDigest(value) { return digest(value); }
|
|
331
399
|
export function parseSourcePublishReconciliationReceipt(value) {
|
|
332
400
|
const item = record(value, 'source publish reconciliation receipt');
|
|
333
401
|
exact(item, ['schemaVersion', 'kind', 'receiptId', 'authority', 'keyId', 'installationId', 'planId', 'planDigest', 'releaseId',
|
|
334
402
|
'fence', 'operationId', 'requestDigest', 'evidence', 'evidenceDigest', 'observedAt', 'expiresAt', 'signature'], 'source publish reconciliation receipt');
|
|
335
|
-
if (item.schemaVersion !== 1 || item.kind !== 'dsh-source-publish-reconciliation-receipt') {
|
|
403
|
+
if ((item.schemaVersion !== 1 && item.schemaVersion !== 2) || item.kind !== 'dsh-source-publish-reconciliation-receipt') {
|
|
336
404
|
throw new ControlPlaneStoreError('invalid-input', 'unsupported source publish reconciliation receipt schema');
|
|
337
405
|
}
|
|
338
|
-
const evidence =
|
|
339
|
-
|
|
406
|
+
const evidence = item.schemaVersion === 1 ? parseSourcePublishReconciliationEvidenceV1(item.evidence)
|
|
407
|
+
: parseSourceNpmPublishReconciliationEvidence(item.evidence);
|
|
408
|
+
if ((item.schemaVersion === 1 && evidence.kind !== 'publish-reconciliation')
|
|
409
|
+
|| (item.schemaVersion === 2 && evidence.kind !== 'npm-publish-reconciliation')) {
|
|
410
|
+
throw new ControlPlaneStoreError('invalid-input', 'publish reconciliation receipt schema and evidence kind disagree');
|
|
411
|
+
}
|
|
412
|
+
const receipt = { schemaVersion: item.schemaVersion, kind: 'dsh-source-publish-reconciliation-receipt',
|
|
340
413
|
receiptId: text(item.receiptId, 'receiptId'), authority: text(item.authority, 'authority'), keyId: text(item.keyId, 'keyId'),
|
|
341
414
|
installationId: text(item.installationId, 'installationId', /^[a-f0-9-]{36}$/u), planId: text(item.planId, 'planId'),
|
|
342
415
|
planDigest: digestText(item.planDigest, 'planDigest'), releaseId: text(item.releaseId, 'releaseId'), fence: integer(item.fence, 'fence', 1),
|
|
@@ -355,6 +428,19 @@ function canonicalReconciliationReceipt(receipt) {
|
|
|
355
428
|
export function sourcePublishReconciliationSigningPayload(receipt) {
|
|
356
429
|
return canonicalReconciliationReceipt({ ...receipt, signature: '' });
|
|
357
430
|
}
|
|
431
|
+
function npmRegistryBase(locator) {
|
|
432
|
+
const raw = bareHttpsUrl(locator, 'registry.locator');
|
|
433
|
+
const parsed = new URL(raw);
|
|
434
|
+
if (!parsed.pathname.endsWith('/'))
|
|
435
|
+
parsed.pathname = `${parsed.pathname}/`;
|
|
436
|
+
return parsed;
|
|
437
|
+
}
|
|
438
|
+
function npmUrlUnderBase(reference, base, label) {
|
|
439
|
+
const parsed = new URL(reference);
|
|
440
|
+
if (parsed.origin !== base.origin || !parsed.pathname.startsWith(base.pathname)) {
|
|
441
|
+
throw new ControlPlaneStoreError('conflict', `${label} is not under the configured npm registry`);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
358
444
|
export class Ed25519SourcePublishReconciliationAuthority {
|
|
359
445
|
publicKey;
|
|
360
446
|
expectedAuthority;
|
|
@@ -384,8 +470,9 @@ export class Ed25519SourcePublishReconciliationAuthority {
|
|
|
384
470
|
|| evidence.expectedTarballIntegrity !== request.artifact.tarballIntegrity
|
|
385
471
|
|| evidence.expectedArtifactStatementDigest !== request.expectedArtifactStatementDigest
|
|
386
472
|
|| evidence.expectedArtifactSignatureDigest !== request.expectedArtifactSignatureDigest
|
|
387
|
-
|| (evidence.
|
|
388
|
-
|
|
473
|
+
|| (evidence.kind === 'publish-reconciliation' && evidence.outcome === 'exists-match'
|
|
474
|
+
&& (evidence.observedArtifactStatementDigest !== request.expectedArtifactStatementDigest
|
|
475
|
+
|| evidence.observedArtifactSignatureDigest !== request.expectedArtifactSignatureDigest))
|
|
389
476
|
|| evidence.ambiguousPublishOperationId !== request.ambiguousPublish.operationId
|
|
390
477
|
|| evidence.ambiguousPublishReceiptDigest !== request.ambiguousPublish.receiptDigest
|
|
391
478
|
|| (evidence.registryReference !== null && evidence.registryReference !== request.expectedRegistryReference)) {
|
|
@@ -396,6 +483,18 @@ export class Ed25519SourcePublishReconciliationAuthority {
|
|
|
396
483
|
|| receipt.expiresAt - receipt.observedAt > request.receiptTtlMs) {
|
|
397
484
|
throw new ControlPlaneStoreError('expired', 'publish reconciliation receipt is outside its request-bound validity interval');
|
|
398
485
|
}
|
|
486
|
+
if (evidence.kind === 'npm-publish-reconciliation') {
|
|
487
|
+
const base = npmRegistryBase(request.registry.locator);
|
|
488
|
+
const metadataReference = `${base.href}${encodeURIComponent(request.artifact.packageName)}/${request.artifact.packageVersion}`;
|
|
489
|
+
if (evidence.registryReference !== null)
|
|
490
|
+
npmUrlUnderBase(evidence.registryReference, base, 'registryReference');
|
|
491
|
+
if (evidence.metadataReference !== null)
|
|
492
|
+
npmUrlUnderBase(evidence.metadataReference, base, 'metadataReference');
|
|
493
|
+
if ((evidence.registryReference !== null && evidence.registryReference !== request.expectedRegistryReference)
|
|
494
|
+
|| (evidence.metadataReference !== null && evidence.metadataReference !== metadataReference)) {
|
|
495
|
+
throw new ControlPlaneStoreError('conflict', 'npm reconciliation receipt is not bound to the exact npm registry URLs');
|
|
496
|
+
}
|
|
497
|
+
}
|
|
399
498
|
const signatureBytes = Buffer.from(receipt.signature, 'base64');
|
|
400
499
|
if (!verify(null, Buffer.from(canonicalReconciliationReceipt(receipt)), createPublicKey(this.publicKey), signatureBytes)) {
|
|
401
500
|
throw new ControlPlaneStoreError('invalid-input', 'publish reconciliation receipt signature is invalid');
|
|
@@ -1035,7 +1134,8 @@ export class ReleaseAdapterError extends Error {
|
|
|
1035
1134
|
this.name = 'ReleaseAdapterError';
|
|
1036
1135
|
}
|
|
1037
1136
|
}
|
|
1038
|
-
async function executePinned(executable, interpreter, args, environment, timeoutMs, stdin, maximumOutput, phase, inherited = []) {
|
|
1137
|
+
async function executePinned(executable, interpreter, args, environment, timeoutMs, stdin, maximumOutput, phase, inherited = [], signal) {
|
|
1138
|
+
signal?.throwIfAborted();
|
|
1039
1139
|
if (process.platform !== 'linux')
|
|
1040
1140
|
throw new ReleaseAdapterError('FAILED', phase, 'descriptor-pinned adapters require Linux');
|
|
1041
1141
|
try {
|
|
@@ -1044,50 +1144,32 @@ async function executePinned(executable, interpreter, args, environment, timeout
|
|
|
1044
1144
|
catch {
|
|
1045
1145
|
throw new ReleaseAdapterError('FAILED', phase, 'descriptor-pinned adapters require /proc/self/fd');
|
|
1046
1146
|
}
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
} });
|
|
1071
|
-
child.once('close', code => {
|
|
1072
|
-
if (settled)
|
|
1073
|
-
return;
|
|
1074
|
-
settled = true;
|
|
1075
|
-
if (timedOut)
|
|
1076
|
-
reject(new ReleaseAdapterError('TIMEOUT', phase, 'adapter exceeded its deadline'));
|
|
1077
|
-
else if (outputLimit)
|
|
1078
|
-
reject(new ReleaseAdapterError('OUTPUT_LIMIT', phase, 'adapter exceeded its output bound'));
|
|
1079
|
-
else if (code !== 0)
|
|
1080
|
-
reject(new ReleaseAdapterError('FAILED', phase, 'adapter returned a non-zero status'));
|
|
1081
|
-
else
|
|
1082
|
-
resolvePromise(Buffer.concat(chunks).toString('utf8'));
|
|
1083
|
-
});
|
|
1084
|
-
const timer = setTimeout(() => { timedOut = true; child.kill('SIGKILL'); }, timeoutMs);
|
|
1085
|
-
child.once('close', () => clearTimeout(timer));
|
|
1086
|
-
child.stdin.end(stdin, 'utf8');
|
|
1087
|
-
});
|
|
1147
|
+
const executableFd = 3 + inherited.length;
|
|
1148
|
+
const interpreterFd = interpreter === undefined ? undefined : executableFd + 1;
|
|
1149
|
+
const command = `/proc/self/fd/${interpreterFd ?? executableFd}`;
|
|
1150
|
+
const commandArguments = interpreter === undefined ? [...args] : [`/proc/self/fd/${executableFd}`, ...args];
|
|
1151
|
+
const stdio = ['pipe', 'pipe', 'ignore', ...inherited.map(handle => handle.fd), executable.handle.fd];
|
|
1152
|
+
if (interpreter !== undefined)
|
|
1153
|
+
stdio.push(interpreter.handle.fd);
|
|
1154
|
+
try {
|
|
1155
|
+
return await executeControlledProcess({ command, args: commandArguments, env: environment, stdio,
|
|
1156
|
+
stdin, timeoutMs, maximumOutput, ...(signal ? { signal } : {}) });
|
|
1157
|
+
}
|
|
1158
|
+
catch (error) {
|
|
1159
|
+
if (!(error instanceof ControlledProcessError))
|
|
1160
|
+
throw error;
|
|
1161
|
+
if (error.code === 'ABORTED')
|
|
1162
|
+
throw new ReleaseAdapterError('ABORTED', phase, 'adapter cancelled');
|
|
1163
|
+
if (error.code === 'TIMEOUT')
|
|
1164
|
+
throw new ReleaseAdapterError('TIMEOUT', phase, 'adapter exceeded its deadline');
|
|
1165
|
+
if (error.code === 'OUTPUT_LIMIT')
|
|
1166
|
+
throw new ReleaseAdapterError('OUTPUT_LIMIT', phase, 'adapter exceeded its output bound');
|
|
1167
|
+
throw new ReleaseAdapterError('FAILED', phase, error.code === 'NON_ZERO'
|
|
1168
|
+
? 'adapter returned a non-zero status' : 'adapter could not be safely reclaimed');
|
|
1169
|
+
}
|
|
1088
1170
|
}
|
|
1089
|
-
async function assertPinnedAdapterCapabilities(executable, interpreter, adapter, phase, environment) {
|
|
1090
|
-
const source = await executePinned(executable, interpreter, ['--capabilities'], environment, adapter.timeoutMs, undefined, 1_024, phase);
|
|
1171
|
+
async function assertPinnedAdapterCapabilities(executable, interpreter, adapter, phase, environment, signal) {
|
|
1172
|
+
const source = await executePinned(executable, interpreter, ['--capabilities'], environment, adapter.timeoutMs, undefined, 1_024, phase, [], signal);
|
|
1091
1173
|
let value;
|
|
1092
1174
|
try {
|
|
1093
1175
|
value = JSON.parse(source);
|
|
@@ -1122,7 +1204,8 @@ async function withPinnedAdapter(adapter, phase, action) {
|
|
|
1122
1204
|
await executable.handle.close();
|
|
1123
1205
|
}
|
|
1124
1206
|
}
|
|
1125
|
-
export async function invokeSourceReleaseAdapter(trust, request) {
|
|
1207
|
+
export async function invokeSourceReleaseAdapter(trust, request, signal) {
|
|
1208
|
+
signal?.throwIfAborted();
|
|
1126
1209
|
const parsedRequest = parseSourceReleaseRequest(request);
|
|
1127
1210
|
const adapter = trust.releaseAdapters?.[parsedRequest.phase];
|
|
1128
1211
|
if (adapter === undefined)
|
|
@@ -1131,17 +1214,17 @@ export async function invokeSourceReleaseAdapter(trust, request) {
|
|
|
1131
1214
|
interpreter: adapter.interpreter, authority: adapter.authority, keyId: adapter.keyId };
|
|
1132
1215
|
if (digest(identity) !== digest(parsedRequest.adapter))
|
|
1133
1216
|
throw new ReleaseAdapterError('FAILED', parsedRequest.phase, 'durable request is not bound to the configured adapter');
|
|
1134
|
-
|
|
1217
|
+
const receipt = await withPinnedAdapter(adapter, parsedRequest.phase, async (executable, interpreter) => {
|
|
1135
1218
|
const environment = inheritedReleaseAdapterEnvironment(trust, parsedRequest.phase);
|
|
1136
|
-
const version = (await executePinned(executable, interpreter, ['--version'], environment, adapter.timeoutMs, undefined, 1_024, parsedRequest.phase)).trim();
|
|
1219
|
+
const version = (await executePinned(executable, interpreter, ['--version'], environment, adapter.timeoutMs, undefined, 1_024, parsedRequest.phase, [], signal)).trim();
|
|
1137
1220
|
if (version !== adapter.version)
|
|
1138
1221
|
throw new ReleaseAdapterError('VERSION_MISMATCH', parsedRequest.phase, 'adapter reported a different version');
|
|
1139
|
-
await assertPinnedAdapterCapabilities(executable, interpreter, adapter, parsedRequest.phase, environment);
|
|
1222
|
+
await assertPinnedAdapterCapabilities(executable, interpreter, adapter, parsedRequest.phase, environment, signal);
|
|
1140
1223
|
const artifact = 'artifact' in parsedRequest.input ? await openArtifactSnapshot(parsedRequest.input.artifact) : undefined;
|
|
1141
1224
|
try {
|
|
1142
1225
|
const artifactEnvironment = artifact === undefined ? environment : { ...environment, DSH_RELEASE_TARBALL_FD: '3',
|
|
1143
1226
|
DSH_RELEASE_SBOM_FD: '4', DSH_RELEASE_PROVENANCE_FD: '5' };
|
|
1144
|
-
const source = await executePinned(executable, interpreter, ['release'], artifactEnvironment, adapter.timeoutMs, `${JSON.stringify(parsedRequest)}\n`, 262_144, parsedRequest.phase, artifact?.handles ?? []);
|
|
1227
|
+
const source = await executePinned(executable, interpreter, ['release'], artifactEnvironment, adapter.timeoutMs, `${JSON.stringify(parsedRequest)}\n`, 262_144, parsedRequest.phase, artifact?.handles ?? [], signal);
|
|
1145
1228
|
if (artifact !== undefined)
|
|
1146
1229
|
await verifyOpenArtifactSnapshot(artifact);
|
|
1147
1230
|
let value;
|
|
@@ -1151,6 +1234,7 @@ export async function invokeSourceReleaseAdapter(trust, request) {
|
|
|
1151
1234
|
catch {
|
|
1152
1235
|
throw new ReleaseAdapterError('FAILED', parsedRequest.phase, 'adapter did not return one JSON receipt');
|
|
1153
1236
|
}
|
|
1237
|
+
signal?.throwIfAborted();
|
|
1154
1238
|
return parseSourceReleaseReceipt(value);
|
|
1155
1239
|
}
|
|
1156
1240
|
finally {
|
|
@@ -1158,6 +1242,8 @@ export async function invokeSourceReleaseAdapter(trust, request) {
|
|
|
1158
1242
|
await Promise.all(artifact.handles.map(async (handle) => handle.close()));
|
|
1159
1243
|
}
|
|
1160
1244
|
});
|
|
1245
|
+
signal?.throwIfAborted();
|
|
1246
|
+
return receipt;
|
|
1161
1247
|
}
|
|
1162
1248
|
export async function invokeSourcePublishReconciliationAdapter(trust, request) {
|
|
1163
1249
|
const parsedRequest = parseSourcePublishReconciliationRequest(request);
|