@enactprotocol/trust 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,564 @@
1
+ /**
2
+ * Cosign CLI integration for interactive OIDC signing
3
+ *
4
+ * The sigstore-js library is designed for CI environments where OIDC tokens
5
+ * are available via environment variables. For interactive local signing,
6
+ * we shell out to the cosign CLI which handles the browser-based OAuth flow.
7
+ */
8
+
9
+ import { execSync, spawn } from "node:child_process";
10
+ import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
11
+ import { tmpdir } from "node:os";
12
+ import { join } from "node:path";
13
+ import type { SigstoreBundle } from "./types";
14
+
15
+ /**
16
+ * Check if cosign CLI is available
17
+ */
18
+ export function isCosignAvailable(): boolean {
19
+ try {
20
+ execSync("which cosign", { encoding: "utf-8", stdio: "pipe" });
21
+ return true;
22
+ } catch {
23
+ return false;
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Get cosign version information
29
+ */
30
+ export function getCosignVersion(): string | undefined {
31
+ try {
32
+ const output = execSync("cosign version", { encoding: "utf-8", stdio: "pipe" });
33
+ const match = output.match(/GitVersion:\s+v?([\d.]+)/);
34
+ return match?.[1];
35
+ } catch {
36
+ return undefined;
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Options for cosign signing
42
+ */
43
+ export interface CosignSignOptions {
44
+ /** Timeout in milliseconds for the OIDC flow */
45
+ timeout?: number;
46
+ /** Output bundle path (if not provided, a temp file is used) */
47
+ outputPath?: string;
48
+ /** Whether to run in verbose mode */
49
+ verbose?: boolean;
50
+ }
51
+
52
+ /**
53
+ * Result of cosign signing
54
+ */
55
+ export interface CosignSignResult {
56
+ /** The Sigstore bundle */
57
+ bundle: SigstoreBundle;
58
+ /** Path where the bundle was saved */
59
+ bundlePath: string;
60
+ /** Signer identity (email) extracted from the bundle */
61
+ signerIdentity: string | undefined;
62
+ }
63
+
64
+ /**
65
+ * Sign a blob (file or buffer) using cosign with interactive OIDC
66
+ *
67
+ * This opens a browser for OAuth authentication with Sigstore's public
68
+ * OIDC provider. The signature, certificate, and Rekor entry are bundled
69
+ * together in the Sigstore bundle format.
70
+ *
71
+ * @param data - The data to sign (Buffer or path to file)
72
+ * @param options - Signing options
73
+ * @returns The signing result with bundle
74
+ */
75
+ export async function signWithCosign(
76
+ data: Buffer | string,
77
+ options: CosignSignOptions = {}
78
+ ): Promise<CosignSignResult> {
79
+ if (!isCosignAvailable()) {
80
+ throw new Error(
81
+ "cosign CLI is not installed. Install it with: brew install cosign\n" +
82
+ "See: https://docs.sigstore.dev/cosign/system_config/installation/"
83
+ );
84
+ }
85
+
86
+ const { timeout = 120000, outputPath, verbose = false } = options;
87
+
88
+ // Create temp directory for working files
89
+ const tempDir = join(tmpdir(), `enact-sign-${Date.now()}`);
90
+ mkdirSync(tempDir, { recursive: true });
91
+
92
+ const blobPath = join(tempDir, "blob");
93
+ const bundlePath = outputPath ?? join(tempDir, "bundle.json");
94
+
95
+ try {
96
+ // Write data to temp file if it's a buffer
97
+ if (Buffer.isBuffer(data)) {
98
+ writeFileSync(blobPath, data);
99
+ } else if (typeof data === "string" && existsSync(data)) {
100
+ // It's a file path, copy to temp location
101
+ const content = readFileSync(data);
102
+ writeFileSync(blobPath, content);
103
+ } else {
104
+ // It's string content
105
+ writeFileSync(blobPath, data);
106
+ }
107
+
108
+ // Run cosign sign-blob with bundle output
109
+ // The --yes flag auto-confirms the OIDC consent prompt
110
+ const args = [
111
+ "sign-blob",
112
+ "--yes", // Auto-confirm OIDC consent
113
+ "--bundle",
114
+ bundlePath,
115
+ "--output-signature",
116
+ "/dev/null", // We only want the bundle
117
+ "--output-certificate",
118
+ "/dev/null", // Bundle includes the cert
119
+ blobPath,
120
+ ];
121
+
122
+ if (verbose) {
123
+ console.log(`Running: cosign ${args.join(" ")}`);
124
+ }
125
+
126
+ await new Promise<void>((resolve, reject) => {
127
+ const proc = spawn("cosign", args, {
128
+ stdio: verbose ? "inherit" : ["inherit", "pipe", "pipe"],
129
+ timeout,
130
+ });
131
+
132
+ let stderr = "";
133
+
134
+ if (!verbose) {
135
+ proc.stderr?.on("data", (data) => {
136
+ stderr += data.toString();
137
+ });
138
+ }
139
+
140
+ proc.on("error", (err) => {
141
+ reject(new Error(`Failed to run cosign: ${err.message}`));
142
+ });
143
+
144
+ proc.on("close", (code) => {
145
+ if (code === 0) {
146
+ resolve();
147
+ } else {
148
+ // Check for common error patterns
149
+ if (stderr.includes("context deadline exceeded") || stderr.includes("timeout")) {
150
+ reject(
151
+ new Error(
152
+ "OIDC authentication timed out. Please try again and complete the browser flow."
153
+ )
154
+ );
155
+ } else if (stderr.includes("cancelled")) {
156
+ reject(new Error("Signing was cancelled."));
157
+ } else {
158
+ reject(new Error(`cosign exited with code ${code}: ${stderr || "(no output)"}`));
159
+ }
160
+ }
161
+ });
162
+ });
163
+
164
+ // Read the bundle
165
+ if (!existsSync(bundlePath)) {
166
+ throw new Error("cosign did not produce a bundle file");
167
+ }
168
+
169
+ const bundleContent = readFileSync(bundlePath, "utf-8");
170
+ const bundle = JSON.parse(bundleContent) as SigstoreBundle;
171
+
172
+ // Extract signer identity from the bundle if possible
173
+ const signerIdentity = extractSignerFromBundle(bundle);
174
+
175
+ return {
176
+ bundle,
177
+ bundlePath,
178
+ signerIdentity,
179
+ };
180
+ } finally {
181
+ // Clean up temp files (but not the output bundle if specified)
182
+ try {
183
+ if (existsSync(blobPath)) {
184
+ unlinkSync(blobPath);
185
+ }
186
+ if (!outputPath && existsSync(bundlePath)) {
187
+ unlinkSync(bundlePath);
188
+ }
189
+ // Try to remove temp dir
190
+ if (existsSync(tempDir)) {
191
+ const { rmdirSync } = require("node:fs");
192
+ rmdirSync(tempDir, { recursive: true });
193
+ }
194
+ } catch {
195
+ // Ignore cleanup errors
196
+ }
197
+ }
198
+ }
199
+
200
+ /**
201
+ * Sign an in-toto attestation using cosign
202
+ *
203
+ * For in-toto attestations, we use cosign attest-blob which wraps the
204
+ * attestation in a DSSE envelope.
205
+ *
206
+ * @param attestation - The in-toto statement to sign
207
+ * @param options - Signing options
208
+ * @returns The signing result with bundle
209
+ */
210
+ export async function attestWithCosign(
211
+ attestation: Record<string, unknown>,
212
+ options: CosignSignOptions = {}
213
+ ): Promise<CosignSignResult> {
214
+ if (!isCosignAvailable()) {
215
+ throw new Error(
216
+ "cosign CLI is not installed. Install it with: brew install cosign\n" +
217
+ "See: https://docs.sigstore.dev/cosign/system_config/installation/"
218
+ );
219
+ }
220
+
221
+ const { timeout = 120000, outputPath, verbose = false } = options;
222
+
223
+ // Create temp directory for working files
224
+ const tempDir = join(tmpdir(), `enact-attest-${Date.now()}`);
225
+ mkdirSync(tempDir, { recursive: true });
226
+
227
+ const predicatePath = join(tempDir, "predicate.json");
228
+ const bundlePath = outputPath ?? join(tempDir, "bundle.json");
229
+ // cosign attest-blob needs a subject file (the thing being attested)
230
+ // For tool attestations, we'll create a dummy subject file
231
+ const subjectPath = join(tempDir, "subject");
232
+
233
+ try {
234
+ // Extract the predicate from the in-toto statement
235
+ // cosign attest-blob takes the predicate separately
236
+ const statement = attestation as {
237
+ _type: string;
238
+ subject: Array<{ name: string; digest: Record<string, string> }>;
239
+ predicateType: string;
240
+ predicate: unknown;
241
+ };
242
+
243
+ // Write the predicate to a file
244
+ writeFileSync(predicatePath, JSON.stringify(statement.predicate, null, 2));
245
+
246
+ // Create a subject file with the expected content
247
+ // The subject should be the content that matches the digest in the statement
248
+ // For now, we'll just create a placeholder and rely on the predicate
249
+ const subjectName = statement.subject?.[0]?.name ?? "tool.yaml";
250
+ writeFileSync(subjectPath, subjectName);
251
+
252
+ // Use cosign attest-blob
253
+ // Note: attest-blob is for custom predicates, which is what we have
254
+ const args = [
255
+ "attest-blob",
256
+ "--yes", // Auto-confirm OIDC consent
257
+ "--bundle",
258
+ bundlePath,
259
+ "--predicate",
260
+ predicatePath,
261
+ "--type",
262
+ statement.predicateType,
263
+ subjectPath,
264
+ ];
265
+
266
+ if (verbose) {
267
+ console.log(`Running: cosign ${args.join(" ")}`);
268
+ }
269
+
270
+ await new Promise<void>((resolve, reject) => {
271
+ const proc = spawn("cosign", args, {
272
+ stdio: verbose ? "inherit" : ["inherit", "pipe", "pipe"],
273
+ timeout,
274
+ });
275
+
276
+ let stderr = "";
277
+
278
+ if (!verbose) {
279
+ proc.stderr?.on("data", (data) => {
280
+ stderr += data.toString();
281
+ });
282
+ }
283
+
284
+ proc.on("error", (err) => {
285
+ reject(new Error(`Failed to run cosign: ${err.message}`));
286
+ });
287
+
288
+ proc.on("close", (code) => {
289
+ if (code === 0) {
290
+ resolve();
291
+ } else {
292
+ if (stderr.includes("context deadline exceeded") || stderr.includes("timeout")) {
293
+ reject(
294
+ new Error(
295
+ "OIDC authentication timed out. Please try again and complete the browser flow."
296
+ )
297
+ );
298
+ } else if (stderr.includes("cancelled")) {
299
+ reject(new Error("Signing was cancelled."));
300
+ } else {
301
+ reject(new Error(`cosign exited with code ${code}: ${stderr || "(no output)"}`));
302
+ }
303
+ }
304
+ });
305
+ });
306
+
307
+ // Read the bundle
308
+ if (!existsSync(bundlePath)) {
309
+ throw new Error("cosign did not produce a bundle file");
310
+ }
311
+
312
+ const bundleContent = readFileSync(bundlePath, "utf-8");
313
+ const bundle = JSON.parse(bundleContent) as SigstoreBundle;
314
+
315
+ // Extract signer identity from the bundle
316
+ const signerIdentity = extractSignerFromBundle(bundle);
317
+
318
+ return {
319
+ bundle,
320
+ bundlePath,
321
+ signerIdentity,
322
+ };
323
+ } finally {
324
+ // Clean up temp files
325
+ try {
326
+ for (const file of [predicatePath, subjectPath]) {
327
+ if (existsSync(file)) {
328
+ unlinkSync(file);
329
+ }
330
+ }
331
+ if (!outputPath && existsSync(bundlePath)) {
332
+ unlinkSync(bundlePath);
333
+ }
334
+ if (existsSync(tempDir)) {
335
+ const { rmdirSync } = require("node:fs");
336
+ rmdirSync(tempDir, { recursive: true });
337
+ }
338
+ } catch {
339
+ // Ignore cleanup errors
340
+ }
341
+ }
342
+ }
343
+
344
+ /**
345
+ * Verify a blob signature using cosign
346
+ *
347
+ * @param data - The data that was signed
348
+ * @param bundle - The Sigstore bundle
349
+ * @param expectedIdentity - Expected signer identity (email)
350
+ * @param expectedIssuer - Expected OIDC issuer
351
+ * @returns Whether verification succeeded
352
+ */
353
+ export async function verifyWithCosign(
354
+ data: Buffer | string,
355
+ bundle: SigstoreBundle,
356
+ expectedIdentity?: string,
357
+ expectedIssuer?: string
358
+ ): Promise<{ verified: boolean; error?: string | undefined; identity?: string | undefined }> {
359
+ if (!isCosignAvailable()) {
360
+ throw new Error("cosign CLI is not installed");
361
+ }
362
+
363
+ const tempDir = join(tmpdir(), `enact-verify-${Date.now()}`);
364
+ mkdirSync(tempDir, { recursive: true });
365
+
366
+ const blobPath = join(tempDir, "blob");
367
+ const bundlePath = join(tempDir, "bundle.json");
368
+
369
+ try {
370
+ // Write data and bundle to temp files
371
+ if (Buffer.isBuffer(data)) {
372
+ writeFileSync(blobPath, data);
373
+ } else {
374
+ writeFileSync(blobPath, data);
375
+ }
376
+ writeFileSync(bundlePath, JSON.stringify(bundle, null, 2));
377
+
378
+ // Build cosign verify-blob command
379
+ const args = ["verify-blob", "--bundle", bundlePath];
380
+
381
+ if (expectedIdentity) {
382
+ args.push("--certificate-identity", expectedIdentity);
383
+ } else {
384
+ // Use regex to match any identity
385
+ args.push("--certificate-identity-regexp", ".*");
386
+ }
387
+
388
+ if (expectedIssuer) {
389
+ args.push("--certificate-oidc-issuer", expectedIssuer);
390
+ } else {
391
+ // Match common Sigstore OIDC issuers
392
+ args.push(
393
+ "--certificate-oidc-issuer-regexp",
394
+ "(https://accounts.google.com|https://github.com/login/oauth|https://oauth2.sigstore.dev/auth)"
395
+ );
396
+ }
397
+
398
+ args.push(blobPath);
399
+
400
+ execSync(`cosign ${args.join(" ")}`, {
401
+ encoding: "utf-8",
402
+ stdio: "pipe",
403
+ });
404
+
405
+ const identity = extractSignerFromBundle(bundle);
406
+ return {
407
+ verified: true,
408
+ error: undefined,
409
+ identity,
410
+ };
411
+ } catch (err) {
412
+ const error = err instanceof Error ? err.message : String(err);
413
+ return {
414
+ verified: false,
415
+ error,
416
+ };
417
+ } finally {
418
+ // Clean up
419
+ try {
420
+ for (const file of [blobPath, bundlePath]) {
421
+ if (existsSync(file)) {
422
+ unlinkSync(file);
423
+ }
424
+ }
425
+ if (existsSync(tempDir)) {
426
+ const { rmdirSync } = require("node:fs");
427
+ rmdirSync(tempDir, { recursive: true });
428
+ }
429
+ } catch {
430
+ // Ignore cleanup errors
431
+ }
432
+ }
433
+ }
434
+
435
+ /**
436
+ * Extract signer identity (email) from a Sigstore bundle
437
+ *
438
+ * The certificate in the bundle contains the signer's email in the
439
+ * Subject Alternative Name (SAN) extension.
440
+ */
441
+ function extractSignerFromBundle(bundle: SigstoreBundle): string | undefined {
442
+ try {
443
+ // The certificate is in verificationMaterial.certificate.rawBytes (base64)
444
+ const certB64 = (
445
+ bundle as unknown as {
446
+ verificationMaterial?: {
447
+ certificate?: {
448
+ rawBytes?: string;
449
+ };
450
+ };
451
+ }
452
+ )?.verificationMaterial?.certificate?.rawBytes;
453
+
454
+ if (!certB64) {
455
+ return undefined;
456
+ }
457
+
458
+ // Decode the certificate
459
+ const certDer = Buffer.from(certB64, "base64");
460
+
461
+ // Simple extraction of email from certificate
462
+ // Look for the email pattern in the SAN extension
463
+ // This is a simplified extraction - a proper implementation would parse X.509
464
+ const certStr = certDer.toString("latin1");
465
+
466
+ // Look for email pattern - match word chars, dots, hyphens, plus before @
467
+ // and domain after, but stop at non-word characters
468
+ const emailMatch = certStr.match(/[\w.+-]+@[\w.-]+\.[a-zA-Z]{2,}/);
469
+ return emailMatch?.[0];
470
+ } catch {
471
+ return undefined;
472
+ }
473
+ }
474
+
475
+ /**
476
+ * Verify an attestation bundle using cosign
477
+ *
478
+ * @param bundle - The Sigstore bundle containing a DSSE-wrapped attestation
479
+ * @param expectedIdentity - Expected signer identity (email)
480
+ * @param expectedIssuer - Expected OIDC issuer
481
+ * @param predicateType - The attestation predicate type (optional)
482
+ * @returns Verification result
483
+ */
484
+ export async function verifyAttestationWithCosign(
485
+ bundle: SigstoreBundle,
486
+ expectedIdentity?: string,
487
+ expectedIssuer?: string,
488
+ predicateType?: string
489
+ ): Promise<{ verified: boolean; error?: string | undefined; identity?: string | undefined }> {
490
+ if (!isCosignAvailable()) {
491
+ throw new Error("cosign CLI is not installed");
492
+ }
493
+
494
+ const tempDir = join(tmpdir(), `enact-verify-attest-${Date.now()}`);
495
+ mkdirSync(tempDir, { recursive: true });
496
+
497
+ const bundlePath = join(tempDir, "bundle.json");
498
+
499
+ try {
500
+ writeFileSync(bundlePath, JSON.stringify(bundle, null, 2));
501
+
502
+ // Build cosign verify-blob-attestation command
503
+ const args = ["verify-blob-attestation", "--bundle", bundlePath];
504
+
505
+ if (expectedIdentity) {
506
+ args.push("--certificate-identity", expectedIdentity);
507
+ } else {
508
+ args.push("--certificate-identity-regexp", ".*");
509
+ }
510
+
511
+ if (expectedIssuer) {
512
+ args.push("--certificate-oidc-issuer", expectedIssuer);
513
+ } else {
514
+ // Match common Sigstore OIDC issuers
515
+ args.push("--certificate-oidc-issuer-regexp", ".*");
516
+ }
517
+
518
+ if (predicateType) {
519
+ args.push("--type", predicateType);
520
+ }
521
+
522
+ // Don't check claims against a subject file
523
+ args.push("--check-claims=false");
524
+
525
+ // Use /dev/null as the "subject" - attestation verification doesn't need it
526
+ args.push("/dev/null");
527
+
528
+ // Use spawnSync to avoid shell escaping issues
529
+ const { spawnSync } = require("node:child_process");
530
+ const result = spawnSync("cosign", args, {
531
+ encoding: "utf-8",
532
+ stdio: "pipe",
533
+ });
534
+
535
+ if (result.status !== 0) {
536
+ throw new Error(result.stderr || result.stdout || `cosign exited with code ${result.status}`);
537
+ }
538
+
539
+ const identity = extractSignerFromBundle(bundle);
540
+ return {
541
+ verified: true,
542
+ error: undefined,
543
+ identity,
544
+ };
545
+ } catch (err) {
546
+ const error = err instanceof Error ? err.message : String(err);
547
+ return {
548
+ verified: false,
549
+ error,
550
+ };
551
+ } finally {
552
+ try {
553
+ if (existsSync(bundlePath)) {
554
+ unlinkSync(bundlePath);
555
+ }
556
+ if (existsSync(tempDir)) {
557
+ const { rmdirSync } = require("node:fs");
558
+ rmdirSync(tempDir, { recursive: true });
559
+ }
560
+ } catch {
561
+ // Ignore cleanup errors
562
+ }
563
+ }
564
+ }
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Sigstore integration for Enact
3
+ *
4
+ * This module provides Sigstore-based attestation signing and verification
5
+ * capabilities for the Enact tool ecosystem.
6
+ */
7
+
8
+ // Types
9
+ export type {
10
+ // OIDC types
11
+ OIDCProvider,
12
+ OIDCIdentity,
13
+ OIDCOptions,
14
+ // Certificate types
15
+ FulcioCertificate,
16
+ FulcioCertificateOptions,
17
+ // Rekor types
18
+ RekorEntry,
19
+ RekorInclusionProof,
20
+ RekorEntryOptions,
21
+ // Attestation types
22
+ InTotoStatement,
23
+ InTotoSubject,
24
+ SLSAProvenancePredicate,
25
+ SLSAResourceDescriptor,
26
+ // Bundle types
27
+ SigstoreBundle,
28
+ TransparencyLogEntry,
29
+ // Signing/verification types
30
+ SigningOptions,
31
+ SigningResult,
32
+ VerificationOptions,
33
+ VerificationResult,
34
+ VerificationDetails,
35
+ ExpectedIdentity,
36
+ // Trust types
37
+ TrustRoot,
38
+ CertificateAuthority,
39
+ TransparencyLog,
40
+ TimestampAuthority,
41
+ TrustPolicy,
42
+ TrustedIdentityRule,
43
+ TrustPolicyResult,
44
+ VerifiedAttestation,
45
+ // Enact-specific types
46
+ EnactToolPredicate,
47
+ EnactAttestationBundle,
48
+ } from "./types";
49
+
50
+ // Signing
51
+ export {
52
+ signArtifact,
53
+ signAttestation,
54
+ extractOIDCIdentity,
55
+ extractCertificateFromBundle,
56
+ extractIdentityFromBundle,
57
+ detectOIDCProvider,
58
+ getOIDCTokenFromEnvironment,
59
+ FULCIO_PUBLIC_URL,
60
+ REKOR_PUBLIC_URL,
61
+ TSA_PUBLIC_URL,
62
+ OIDC_ISSUERS,
63
+ } from "./signing";
64
+
65
+ // OAuth Identity Provider (for interactive signing)
66
+ export {
67
+ OAuthIdentityProvider,
68
+ CallbackServer,
69
+ OAuthClient,
70
+ initializeOAuthClient,
71
+ SIGSTORE_OAUTH_ISSUER,
72
+ SIGSTORE_CLIENT_ID,
73
+ } from "./oauth";
74
+ export type {
75
+ OAuthIdentityProviderOptions,
76
+ IdentityProvider,
77
+ } from "./oauth";
78
+
79
+ // Cosign CLI integration (fallback for interactive signing)
80
+ export {
81
+ isCosignAvailable,
82
+ getCosignVersion,
83
+ signWithCosign,
84
+ attestWithCosign,
85
+ verifyWithCosign,
86
+ verifyAttestationWithCosign,
87
+ } from "./cosign";
88
+ export type { CosignSignOptions, CosignSignResult } from "./cosign";
89
+
90
+ // Verification
91
+ export {
92
+ verifyBundle,
93
+ createBundleVerifier,
94
+ isVerified,
95
+ } from "./verification";
96
+
97
+ // Attestation creation
98
+ export {
99
+ createSubjectFromContent,
100
+ createSubjectFromFile,
101
+ createSubjectWithMultipleDigests,
102
+ createStatement,
103
+ createSLSAProvenance,
104
+ createSLSAProvenanceStatement,
105
+ createEnactToolPredicate,
106
+ createEnactToolStatement,
107
+ createEnactAuditPredicate,
108
+ createEnactAuditStatement,
109
+ createResourceDescriptorFromFile,
110
+ createResourceDescriptorFromContent,
111
+ // Constants
112
+ ENACT_BASE_URL,
113
+ INTOTO_STATEMENT_TYPE,
114
+ SLSA_PROVENANCE_TYPE,
115
+ ENACT_TOOL_TYPE,
116
+ ENACT_AUDIT_TYPE,
117
+ ENACT_BUILD_TYPE,
118
+ } from "./attestation";
119
+
120
+ // Trust policy
121
+ export {
122
+ createTrustPolicy,
123
+ createIdentityRule,
124
+ evaluateTrustPolicy,
125
+ isTrusted,
126
+ serializeTrustPolicy,
127
+ deserializeTrustPolicy,
128
+ DEFAULT_TRUST_POLICY,
129
+ PERMISSIVE_POLICY,
130
+ STRICT_POLICY,
131
+ } from "./policy";
132
+
133
+ // Re-export attestation option types
134
+ export type {
135
+ SLSAProvenanceOptions,
136
+ EnactToolAttestationOptions,
137
+ EnactAuditAttestationOptions,
138
+ EnactAuditPredicate,
139
+ } from "./attestation";