securewithtrace 0.40.1 → 0.42.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.
Files changed (2) hide show
  1. package/dist/index.js +1193 -194
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -6276,7 +6276,7 @@ var $ZodBase64 = /* @__PURE__ */ $constructor("$ZodBase64", (inst, def) => {
6276
6276
  function isValidBase64URL(data) {
6277
6277
  if (!base64url.test(data))
6278
6278
  return false;
6279
- const base643 = data.replace(/[-_]/g, (c59) => c59 === "-" ? "+" : "/");
6279
+ const base643 = data.replace(/[-_]/g, (c62) => c62 === "-" ? "+" : "/");
6280
6280
  const padded = base643.padEnd(Math.ceil(base643.length / 4) * 4, "=");
6281
6281
  return isValidBase64(padded);
6282
6282
  }
@@ -16320,9 +16320,9 @@ var ZodDate = /* @__PURE__ */ $constructor("ZodDate", (inst, def) => {
16320
16320
  inst._zod.processJSONSchema = (ctx, json2, params) => dateProcessor(inst, ctx, json2, params);
16321
16321
  inst.min = (value, params) => inst.check(_gte(value, params));
16322
16322
  inst.max = (value, params) => inst.check(_lte(value, params));
16323
- const c59 = inst._zod.bag;
16324
- inst.minDate = c59.minimum ? new Date(c59.minimum) : null;
16325
- inst.maxDate = c59.maximum ? new Date(c59.maximum) : null;
16323
+ const c62 = inst._zod.bag;
16324
+ inst.minDate = c62.minimum ? new Date(c62.minimum) : null;
16325
+ inst.maxDate = c62.maximum ? new Date(c62.maximum) : null;
16326
16326
  });
16327
16327
  function date3(params) {
16328
16328
  return _date(ZodDate, params);
@@ -21920,6 +21920,17 @@ var ContestReasonValues = [
21920
21920
  "OTHER"
21921
21921
  ];
21922
21922
  var ContestReason = external_exports.enum(ContestReasonValues);
21923
+ var PentestFindingFeedbackRatingValues = ["THUMBS_UP", "THUMBS_DOWN"];
21924
+ var PentestFindingFeedbackRating = external_exports.enum(PentestFindingFeedbackRatingValues);
21925
+ var PentestFindingFeedbackReasonValues = [
21926
+ "FALSE_POSITIVE",
21927
+ "SEVERITY_TOO_HIGH",
21928
+ "SEVERITY_TOO_LOW",
21929
+ "UNCLEAR_WRITEUP",
21930
+ "REMEDIATION_NOT_ACTIONABLE",
21931
+ "OTHER"
21932
+ ];
21933
+ var PentestFindingFeedbackReason = external_exports.enum(PentestFindingFeedbackReasonValues);
21923
21934
  var ManualRetestOutcomeValues = ["FIXED", "PARTIALLY_FIXED", "NOT_FIXED"];
21924
21935
  var ManualRetestOutcome = external_exports.enum(ManualRetestOutcomeValues);
21925
21936
  var RetestOutcomeValues = [...ManualRetestOutcomeValues, "UNABLE_TO_VERIFY"];
@@ -21998,7 +22009,7 @@ var PentestEditActionValues = [
21998
22009
  "DELIVER"
21999
22010
  ];
22000
22011
  var PentestEditAction = external_exports.enum(PentestEditActionValues);
22001
- var PentestAuditActorTypeValues = ["USER", "SYSTEM", "AGENT", "API_KEY"];
22012
+ var PentestAuditActorTypeValues = ["USER", "SYSTEM", "AGENT", "API_KEY", "STAFF"];
22002
22013
  var PentestAuditActorType = external_exports.enum(PentestAuditActorTypeValues);
22003
22014
  var PentestHypothesisStatusValues = [
22004
22015
  "DRAFTED",
@@ -22362,6 +22373,20 @@ var AgentSessionEventPayloadUrlResponseSchema = external_exports.object({
22362
22373
  /** Presigned S3 GET for the overflow payload (short TTL). */
22363
22374
  url: external_exports.url()
22364
22375
  });
22376
+ var ExportAgentSessionTranscriptResponseSchema = external_exports.object({
22377
+ /** Presigned S3 GET with an attachment Content-Disposition (short TTL). */
22378
+ downloadUrl: external_exports.url(),
22379
+ expiresAt: external_exports.coerce.date(),
22380
+ fileName: external_exports.string(),
22381
+ /** Bytes of the exported document. */
22382
+ sizeBytes: external_exports.number().int().nonnegative(),
22383
+ eventCount: external_exports.number().int().nonnegative(),
22384
+ subagentCount: external_exports.number().int().nonnegative(),
22385
+ /** High-water mark captured by the export; a live session keeps moving past it. */
22386
+ lastEventSeq: external_exports.number().int().nonnegative(),
22387
+ /** Overflow payloads whose S3 object no longer exists; the export keeps the inline preview for those. */
22388
+ missingOverflowPayloadCount: external_exports.number().int().nonnegative()
22389
+ });
22365
22390
  var SendAgentSessionMessageResponseSchema = external_exports.object({
22366
22391
  event: AgentSessionEventResponseSchema,
22367
22392
  /** True when the message relaunched a parked session. */
@@ -22507,6 +22532,21 @@ var agentSessionContract = c2.router({
22507
22532
  500: ErrorResponseSchema
22508
22533
  }
22509
22534
  },
22535
+ // POST, not GET: each call assembles the transcript from Postgres + S3 overflow
22536
+ // objects and writes a new export object before presigning it.
22537
+ exportTranscript: {
22538
+ method: "POST",
22539
+ path: "/organizations/:organizationId/pentests/:pentestId/agent-sessions/:agentSessionId/transcript-export",
22540
+ pathParams: AgentSessionIdParamsSchema,
22541
+ body: null,
22542
+ responses: {
22543
+ 200: ExportAgentSessionTranscriptResponseSchema,
22544
+ 401: ErrorResponseSchema,
22545
+ 403: ErrorResponseSchema,
22546
+ 404: ErrorResponseSchema,
22547
+ 500: ErrorResponseSchema
22548
+ }
22549
+ },
22510
22550
  sendMessage: {
22511
22551
  method: "POST",
22512
22552
  path: "/organizations/:organizationId/pentests/:pentestId/agent-sessions/:agentSessionId/messages",
@@ -23731,11 +23771,19 @@ var CustomerReportPdfResponseSchema = ReportPdfResponseSchema.omit({
23731
23771
  canViewInline: external_exports.boolean().optional(),
23732
23772
  /**
23733
23773
  * What the Download action hands the customer: the PDF alone, or one zip
23734
- * carrying the PDF, the markdown report, and the evidence files (partner
23735
- * templates whose tooling consumes the markdown). Preview always opens the
23736
- * PDF. Optional for the deploy-order reason above; consumers default to 'pdf'.
23774
+ * carrying the PDF, the Word document, the markdown report, and the evidence
23775
+ * files (partner templates whose tooling consumes the markdown). Preview
23776
+ * always opens the PDF. Optional for the deploy-order reason above; consumers
23777
+ * default to 'pdf'.
23778
+ */
23779
+ downloadFormat: CustomerReportDownloadFormatSchema.optional(),
23780
+ /**
23781
+ * Whether a Word document (.docx) from the same render as the served PDF is
23782
+ * available through `downloadCustomer?artifact=docx`. Partner templates only;
23783
+ * resolved server-side from storage, never from the template alone. Optional
23784
+ * for the deploy-order reason above; consumers default to false.
23737
23785
  */
23738
- downloadFormat: CustomerReportDownloadFormatSchema.optional()
23786
+ docxAvailable: external_exports.boolean().optional()
23739
23787
  });
23740
23788
  var GetCustomerReportPdfResponseSchema = external_exports.object({
23741
23789
  report: CustomerReportPdfResponseSchema.nullable()
@@ -23749,8 +23797,13 @@ var DeleteReportPdfRequestSchema = external_exports.object({
23749
23797
  var DeleteReportIssueRequestSchema = external_exports.object({
23750
23798
  reason: external_exports.string().min(1).max(1e4).optional()
23751
23799
  }).strict().optional();
23800
+ var ReportPdfDownloadArtifactValues = ["pdf", "markdown", "docx"];
23801
+ var ReportPdfDownloadArtifactSchema = external_exports.enum(ReportPdfDownloadArtifactValues);
23752
23802
  var ReportPdfDownloadQuerySchema = external_exports.object({
23753
- artifact: external_exports.enum(["pdf", "markdown"]).optional()
23803
+ artifact: ReportPdfDownloadArtifactSchema.optional()
23804
+ });
23805
+ var CustomerReportPdfDownloadQuerySchema = PentestPdfDownloadQuerySchema.extend({
23806
+ artifact: external_exports.enum(["docx"]).optional()
23754
23807
  });
23755
23808
  var ReportPdfDownloadResponseSchema = external_exports.object({
23756
23809
  report: ReportPdfResponseSchema,
@@ -24318,6 +24371,10 @@ var OperatorFindingListSchema = PentestFindingListItemSchema.omit({
24318
24371
  var CustomerPentestFindingRepositorySchema = PentestFindingRepositorySchema.omit({
24319
24372
  repositoryId: true
24320
24373
  });
24374
+ var CustomerFindingPendingRetestSchema = external_exports.object({
24375
+ state: external_exports.enum(["QUEUED", "RUNNING"]),
24376
+ since: external_exports.coerce.date()
24377
+ }).nullable().default(null);
24321
24378
  var CustomerPentestFindingResponseSchema = PentestFindingResponseSchema.omit({
24322
24379
  organizationId: true,
24323
24380
  pentestId: true,
@@ -24341,7 +24398,8 @@ var CustomerPentestFindingResponseSchema = PentestFindingResponseSchema.omit({
24341
24398
  // customer "dispute under review" hint + suppresses the re-dispute affordance,
24342
24399
  // even after a retest moved the finding off CONTESTED. Only this derived
24343
24400
  // boolean crosses the trust boundary — never the staff contest list.
24344
- hasOpenContest: external_exports.boolean()
24401
+ hasOpenContest: external_exports.boolean(),
24402
+ pendingRetest: CustomerFindingPendingRetestSchema
24345
24403
  });
24346
24404
  var CustomerPentestFindingListItemSchema = PentestFindingListItemSchema.omit({
24347
24405
  organizationId: true,
@@ -24356,7 +24414,10 @@ var CustomerPentestFindingListItemSchema = PentestFindingListItemSchema.omit({
24356
24414
  // Published-only, same as the customer detail schema above — re-narrowed from the
24357
24415
  // nullable root.
24358
24416
  displayId: external_exports.number().int().positive(),
24359
- repositories: external_exports.array(CustomerPentestFindingRepositorySchema)
24417
+ repositories: external_exports.array(CustomerPentestFindingRepositorySchema),
24418
+ // On the list row too: the findings table is what the customer watches while a
24419
+ // retest they queued runs, and polling it is how the row flips without a reload.
24420
+ pendingRetest: CustomerFindingPendingRetestSchema
24360
24421
  });
24361
24422
  var ListCustomerPentestFindingsResponseSchema = external_exports.array(
24362
24423
  CustomerPentestFindingListItemSchema
@@ -24365,6 +24426,7 @@ var ListCustomerPentestFindingDeliveryResponseSchema = external_exports.array(
24365
24426
  CustomerPentestFindingListItemSchema
24366
24427
  );
24367
24428
  var ResolutionTimelineActor = external_exports.enum(["CUSTOMER", "TRACE"]);
24429
+ var ResolutionTimelineActorName = external_exports.string().nullable().default(null);
24368
24430
  var ResolutionTimelineVia = external_exports.object({
24369
24431
  provider: external_exports.enum(["LINEAR"]),
24370
24432
  identifier: external_exports.string(),
@@ -24375,12 +24437,17 @@ var ResolutionTimelineEntrySchema = external_exports.discriminatedUnion("kind",
24375
24437
  kind: external_exports.literal("remediation_reported"),
24376
24438
  at: external_exports.coerce.date(),
24377
24439
  actor: ResolutionTimelineActor,
24440
+ actorName: ResolutionTimelineActorName,
24378
24441
  note: external_exports.string().nullable(),
24379
24442
  via: ResolutionTimelineVia
24380
24443
  }),
24444
+ // Retests are run by Trace (staff or a Trace agent), so the actor is fixed;
24445
+ // the default keeps a web build ahead of the api parsing older payloads.
24381
24446
  external_exports.object({
24382
24447
  kind: external_exports.literal("retest"),
24383
24448
  at: external_exports.coerce.date(),
24449
+ actor: external_exports.literal("TRACE").default("TRACE"),
24450
+ actorName: ResolutionTimelineActorName,
24384
24451
  outcome: RetestOutcome,
24385
24452
  note: external_exports.string().nullable()
24386
24453
  }),
@@ -24388,12 +24455,14 @@ var ResolutionTimelineEntrySchema = external_exports.discriminatedUnion("kind",
24388
24455
  kind: external_exports.literal("accepted_with_controls"),
24389
24456
  at: external_exports.coerce.date(),
24390
24457
  actor: ResolutionTimelineActor,
24458
+ actorName: ResolutionTimelineActorName,
24391
24459
  note: external_exports.string()
24392
24460
  }),
24393
24461
  external_exports.object({
24394
24462
  kind: external_exports.literal("dismissed"),
24395
24463
  at: external_exports.coerce.date(),
24396
24464
  actor: ResolutionTimelineActor,
24465
+ actorName: ResolutionTimelineActorName,
24397
24466
  reason: PentestFindingDismissReason,
24398
24467
  note: external_exports.string()
24399
24468
  }),
@@ -24403,6 +24472,7 @@ var ResolutionTimelineEntrySchema = external_exports.discriminatedUnion("kind",
24403
24472
  kind: external_exports.literal("reopened"),
24404
24473
  at: external_exports.coerce.date(),
24405
24474
  actor: ResolutionTimelineActor,
24475
+ actorName: ResolutionTimelineActorName,
24406
24476
  toStatus: external_exports.enum(["OPEN", "IN_PROGRESS"]),
24407
24477
  note: external_exports.string().nullable(),
24408
24478
  via: ResolutionTimelineVia
@@ -24412,6 +24482,7 @@ var ResolutionTimelineEntrySchema = external_exports.discriminatedUnion("kind",
24412
24482
  kind: external_exports.literal("note"),
24413
24483
  at: external_exports.coerce.date(),
24414
24484
  actor: ResolutionTimelineActor,
24485
+ actorName: ResolutionTimelineActorName,
24415
24486
  note: external_exports.string()
24416
24487
  }),
24417
24488
  // Customer opened a dispute (PR 3). `note` = the customer's justification.
@@ -24419,6 +24490,7 @@ var ResolutionTimelineEntrySchema = external_exports.discriminatedUnion("kind",
24419
24490
  kind: external_exports.literal("contested"),
24420
24491
  at: external_exports.coerce.date(),
24421
24492
  actor: ResolutionTimelineActor,
24493
+ actorName: ResolutionTimelineActorName,
24422
24494
  reason: ContestReason,
24423
24495
  note: external_exports.string()
24424
24496
  }),
@@ -24429,6 +24501,7 @@ var ResolutionTimelineEntrySchema = external_exports.discriminatedUnion("kind",
24429
24501
  kind: external_exports.literal("contest_resolved"),
24430
24502
  at: external_exports.coerce.date(),
24431
24503
  actor: ResolutionTimelineActor,
24504
+ actorName: ResolutionTimelineActorName,
24432
24505
  resultStatus: PentestFindingStatus.nullable(),
24433
24506
  note: external_exports.string()
24434
24507
  }),
@@ -24438,22 +24511,61 @@ var ResolutionTimelineEntrySchema = external_exports.discriminatedUnion("kind",
24438
24511
  kind: external_exports.literal("ticket_created"),
24439
24512
  at: external_exports.coerce.date(),
24440
24513
  actor: ResolutionTimelineActor,
24514
+ actorName: ResolutionTimelineActorName,
24441
24515
  provider: external_exports.enum(["LINEAR"]),
24442
24516
  identifier: external_exports.string(),
24443
24517
  url: external_exports.url()
24518
+ }),
24519
+ // A reader rated the finding. `reason` is set only on a thumbs down; `note` is
24520
+ // the optional comment left with the vote.
24521
+ external_exports.object({
24522
+ kind: external_exports.literal("feedback_rated"),
24523
+ at: external_exports.coerce.date(),
24524
+ actor: ResolutionTimelineActor,
24525
+ actorName: ResolutionTimelineActorName,
24526
+ rating: PentestFindingFeedbackRating,
24527
+ reason: PentestFindingFeedbackReason.nullable(),
24528
+ note: external_exports.string().nullable()
24529
+ }),
24530
+ // A reader left a comment without a vote.
24531
+ external_exports.object({
24532
+ kind: external_exports.literal("feedback_commented"),
24533
+ at: external_exports.coerce.date(),
24534
+ actor: ResolutionTimelineActor,
24535
+ actorName: ResolutionTimelineActorName,
24536
+ note: external_exports.string()
24444
24537
  })
24445
24538
  ]);
24446
24539
  var ResolutionTimelineSchema = external_exports.array(ResolutionTimelineEntrySchema);
24540
+ var PentestFindingFeedbackSummarySchema = external_exports.object({
24541
+ thumbsUp: external_exports.number().int().nonnegative(),
24542
+ thumbsDown: external_exports.number().int().nonnegative(),
24543
+ viewerRating: PentestFindingFeedbackRating.nullable()
24544
+ });
24545
+ var PentestFindingFeedbackSummaryField = PentestFindingFeedbackSummarySchema.default({
24546
+ thumbsUp: 0,
24547
+ thumbsDown: 0,
24548
+ viewerRating: null
24549
+ });
24447
24550
  var PentestFindingDetailResponseSchema = PentestFindingResponseSchema.extend({
24448
- resolutionTimeline: ResolutionTimelineSchema
24551
+ resolutionTimeline: ResolutionTimelineSchema,
24552
+ feedbackSummary: PentestFindingFeedbackSummaryField
24449
24553
  });
24450
24554
  var CustomerPentestFindingDetailResponseSchema = CustomerPentestFindingResponseSchema.extend({
24451
- resolutionTimeline: ResolutionTimelineSchema
24555
+ resolutionTimeline: ResolutionTimelineSchema,
24556
+ feedbackSummary: PentestFindingFeedbackSummaryField
24452
24557
  });
24453
24558
  var ReadPentestFindingResponseSchema = external_exports.union([
24454
24559
  PentestFindingDetailResponseSchema,
24455
24560
  CustomerPentestFindingDetailResponseSchema
24456
24561
  ]);
24562
+ var PentestFindingPdfExportResponseSchema = external_exports.object({
24563
+ /** Download name the presigned URL carries, e.g. `PTF-12-idor-on-invoices.pdf`. */
24564
+ fileName: external_exports.string(),
24565
+ downloadUrl: external_exports.url(),
24566
+ /** False when an identical export already existed and was reused without a render. */
24567
+ rendered: external_exports.boolean()
24568
+ });
24457
24569
  var ListReadPentestFindingsResponseSchema = external_exports.union([
24458
24570
  ListPentestFindingsResponseSchema,
24459
24571
  ListCustomerPentestFindingsResponseSchema
@@ -24644,6 +24756,18 @@ var ResolvePentestFindingContestRequestSchema = external_exports.object({
24644
24756
  dismissReason: PentestFindingDismissReason.optional(),
24645
24757
  note: external_exports.string().min(1).max(1e4)
24646
24758
  });
24759
+ var PENTEST_FINDING_FEEDBACK_COMMENT_MAX_LENGTH = 5e3;
24760
+ var CreatePentestFindingFeedbackRequestSchema = external_exports.object({
24761
+ rating: PentestFindingFeedbackRating.nullable().default(null),
24762
+ reason: PentestFindingFeedbackReason.nullable().default(null),
24763
+ comment: external_exports.string().trim().max(PENTEST_FINDING_FEEDBACK_COMMENT_MAX_LENGTH).transform((value) => value.length === 0 ? null : value).nullable().default(null)
24764
+ }).refine((input) => input.rating !== null || input.comment !== null, {
24765
+ message: "Feedback needs a rating or a comment",
24766
+ path: ["comment"]
24767
+ }).refine((input) => input.reason === null || input.rating === "THUMBS_DOWN", {
24768
+ message: "A reason only accompanies a thumbs down",
24769
+ path: ["reason"]
24770
+ });
24647
24771
  var PentestFindingRetestResponseSchema = external_exports.object({
24648
24772
  retestId: external_exports.uuid(),
24649
24773
  pentestFindingId: external_exports.uuid(),
@@ -25220,7 +25344,8 @@ var CredentialTypeValues = [
25220
25344
  "USERNAME_PASSWORD",
25221
25345
  "COOKIE",
25222
25346
  "MAGIC_AUTH",
25223
- "SESSION_STATE"
25347
+ "SESSION_STATE",
25348
+ "SSH_PRIVATE_KEY"
25224
25349
  ];
25225
25350
  var CredentialType = external_exports.enum(CredentialTypeValues);
25226
25351
  var BearerAuthConfigSchema = external_exports.object({
@@ -25318,17 +25443,94 @@ var StorageStateSchema = external_exports.object({
25318
25443
  } catch {
25319
25444
  return;
25320
25445
  }
25321
- s.cookies.forEach((c59, i2) => {
25322
- const d = c59.domain.replace(/^\./, "").toLowerCase();
25446
+ s.cookies.forEach((c62, i2) => {
25447
+ const d = c62.domain.replace(/^\./, "").toLowerCase();
25323
25448
  if (d !== host && !host.endsWith(`.${d}`)) {
25324
25449
  ctx.addIssue({
25325
25450
  code: "custom",
25326
25451
  path: ["cookies", i2, "domain"],
25327
- message: `cookie domain "${c59.domain}" is not the capture origin host "${host}" or a parent domain of it`
25452
+ message: `cookie domain "${c62.domain}" is not the capture origin host "${host}" or a parent domain of it`
25328
25453
  });
25329
25454
  }
25330
25455
  });
25331
25456
  });
25457
+ var SSH_DEFAULT_PORT = 22;
25458
+ var SSH_HOST_MAX_CHARS = 253;
25459
+ var SSH_USERNAME_MAX_CHARS = 128;
25460
+ var SSH_PRIVATE_KEY_PEM_MAX_BYTES = 16384;
25461
+ var SSH_PASSPHRASE_MAX_CHARS = 1024;
25462
+ var SSH_HOST_PATTERN = /^[A-Za-z0-9][A-Za-z0-9.:-]*$/;
25463
+ var SSH_USERNAME_PATTERN = /^[^\s\p{Cc}]+$/u;
25464
+ var SSH_HOST_KEY_FINGERPRINT_PATTERN = /^SHA256:[A-Za-z0-9+/]{43}=?$/;
25465
+ var SshPrivateKeyAuthConfigSchema = external_exports.object({
25466
+ type: external_exports.literal("SSH_PRIVATE_KEY"),
25467
+ host: external_exports.string().min(1, "host is required").max(SSH_HOST_MAX_CHARS).regex(SSH_HOST_PATTERN, "host must be a hostname or IP address"),
25468
+ port: external_exports.number().int().min(1).max(65535).default(SSH_DEFAULT_PORT),
25469
+ username: external_exports.string().min(1, "username is required").max(SSH_USERNAME_MAX_CHARS).regex(SSH_USERNAME_PATTERN, "username must not contain whitespace or control characters"),
25470
+ hostKeyFingerprint: external_exports.string().regex(SSH_HOST_KEY_FINGERPRINT_PATTERN, "host key fingerprint must be SHA256:<base64>").optional()
25471
+ }).strict();
25472
+ var SSH_PRIVATE_KEY_PEM_LABELS = [
25473
+ "OPENSSH PRIVATE KEY",
25474
+ "RSA PRIVATE KEY",
25475
+ "EC PRIVATE KEY",
25476
+ "PRIVATE KEY",
25477
+ "ENCRYPTED PRIVATE KEY"
25478
+ ];
25479
+ var PEM_BEGIN_LINE = /^-----BEGIN ([A-Z ]+)-----$/;
25480
+ var PEM_END_LINE = /^-----END ([A-Z ]+)-----$/;
25481
+ var PEM_BASE64_LINE = /^[A-Za-z0-9+/]+={0,2}$/;
25482
+ var PEM_HEADER_LINE = /^[A-Za-z0-9-]+: [\x20-\x7e]*$/;
25483
+ function sshPrivateKeyPemError(value) {
25484
+ if (new TextEncoder().encode(value).length > SSH_PRIVATE_KEY_PEM_MAX_BYTES) {
25485
+ return `private key must be at most ${SSH_PRIVATE_KEY_PEM_MAX_BYTES} bytes`;
25486
+ }
25487
+ if (/[^\P{Cc}\r\n]/u.test(value)) {
25488
+ return "private key contains control characters";
25489
+ }
25490
+ const lines = value.replace(/\r\n/g, "\n").split("\n").map((line) => line.trimEnd());
25491
+ while (lines.length > 0 && lines[0] === "") lines.shift();
25492
+ while (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
25493
+ if (lines.length < 3) {
25494
+ return "private key must be a PEM block with BEGIN and END lines";
25495
+ }
25496
+ const begin = PEM_BEGIN_LINE.exec(lines[0] ?? "");
25497
+ if (begin === null) {
25498
+ return "private key must start with a -----BEGIN \u2026 PRIVATE KEY----- line";
25499
+ }
25500
+ const end = PEM_END_LINE.exec(lines[lines.length - 1] ?? "");
25501
+ if (end === null) {
25502
+ return "private key must end with a -----END \u2026 PRIVATE KEY----- line";
25503
+ }
25504
+ const label = begin[1];
25505
+ if (!SSH_PRIVATE_KEY_PEM_LABELS.includes(label ?? "")) {
25506
+ return `private key label must be one of: ${SSH_PRIVATE_KEY_PEM_LABELS.join(", ")}`;
25507
+ }
25508
+ if (end[1] !== label) {
25509
+ return "private key BEGIN and END labels do not match";
25510
+ }
25511
+ let sawBody = false;
25512
+ for (const line of lines.slice(1, -1)) {
25513
+ if (line === "") continue;
25514
+ if (PEM_BASE64_LINE.test(line)) {
25515
+ sawBody = true;
25516
+ continue;
25517
+ }
25518
+ if (PEM_HEADER_LINE.test(line) && !sawBody) continue;
25519
+ return "private key body must be base64 lines";
25520
+ }
25521
+ if (!sawBody) {
25522
+ return "private key body is empty";
25523
+ }
25524
+ return null;
25525
+ }
25526
+ var SshPrivateKeyPemSchema = external_exports.string().min(1, "private key is required").superRefine((value, ctx) => {
25527
+ const message = sshPrivateKeyPemError(value);
25528
+ if (message !== null) ctx.addIssue({ code: "custom", message });
25529
+ });
25530
+ var SshPrivateKeySecretSchema = external_exports.object({
25531
+ privateKeyPem: SshPrivateKeyPemSchema,
25532
+ passphrase: external_exports.string().min(1).max(SSH_PASSPHRASE_MAX_CHARS).optional()
25533
+ }).strict();
25332
25534
  var CredentialMfaKindValues = ["TOTP"];
25333
25535
  var CredentialMfaKind = external_exports.enum(CredentialMfaKindValues);
25334
25536
  var TotpSetupKeySchema = external_exports.string().min(1).max(TOTP_MAX_SECRET_CHARS * 4).superRefine((value, ctx) => {
@@ -25362,7 +25564,8 @@ var CredentialAuthConfigSchema = external_exports.discriminatedUnion("type", [
25362
25564
  UsernamePasswordAuthConfigSchema,
25363
25565
  PassphraseAuthConfigSchema,
25364
25566
  MagicAuthConfigSchema,
25365
- SessionStateAuthConfigSchema
25567
+ SessionStateAuthConfigSchema,
25568
+ SshPrivateKeyAuthConfigSchema
25366
25569
  ]);
25367
25570
 
25368
25571
  // ../../packages/api-contracts/src/schemas/domain/knowledge-fact.schema.ts
@@ -29594,6 +29797,11 @@ var ResumeCodeUploadResponseSchema = external_exports.object({
29594
29797
  var ListCodeUploadsResponseSchema = external_exports.object({
29595
29798
  uploads: external_exports.array(CodeUploadSchema)
29596
29799
  });
29800
+ var DownloadCodeUploadResponseSchema = external_exports.object({
29801
+ upload: CodeUploadSchema,
29802
+ downloadUrl: external_exports.url(),
29803
+ expiresAt: external_exports.coerce.date()
29804
+ });
29597
29805
 
29598
29806
  // ../../packages/api-contracts/src/contracts/v1/code-upload.ts
29599
29807
  var c12 = initContract();
@@ -29647,6 +29855,15 @@ var codeUploadContract = c12.router({
29647
29855
  ...commonErrors
29648
29856
  }
29649
29857
  },
29858
+ downloadCodeUpload: {
29859
+ method: "GET",
29860
+ path: "/organizations/:organizationId/code-uploads/:codeUploadId/download",
29861
+ pathParams: CodeUploadIdParamsSchema,
29862
+ responses: {
29863
+ 200: DownloadCodeUploadResponseSchema,
29864
+ ...commonErrors
29865
+ }
29866
+ },
29650
29867
  deleteCodeUpload: {
29651
29868
  method: "DELETE",
29652
29869
  path: "/organizations/:organizationId/code-uploads/:codeUploadId",
@@ -29702,7 +29919,13 @@ var SECRET_SHAPES = {
29702
29919
  username: external_exports.string().min(1).max(500),
29703
29920
  password: external_exports.string().min(1).max(2048)
29704
29921
  },
29705
- SESSION_STATE: { secretValue: SessionStateSecretValueSchema }
29922
+ SESSION_STATE: { secretValue: SessionStateSecretValueSchema },
29923
+ // The key and its passphrase are one secret: they are encrypted together and
29924
+ // replaced together (see SshPrivateKeySecretSchema for the stored shape).
29925
+ SSH_PRIVATE_KEY: {
29926
+ privateKeyPem: SshPrivateKeyPemSchema,
29927
+ passphrase: external_exports.string().min(1).max(SSH_PASSPHRASE_MAX_CHARS).optional()
29928
+ }
29706
29929
  };
29707
29930
  var CreateLabelSchema = external_exports.string().max(200).optional();
29708
29931
  var CreateBearerSchema = external_exports.object({
@@ -29783,6 +30006,12 @@ var CreateSessionStateSchema = external_exports.object({
29783
30006
  label: CreateLabelSchema,
29784
30007
  ...SECRET_SHAPES.SESSION_STATE
29785
30008
  }).strict();
30009
+ var CreateSshPrivateKeySchema = external_exports.object({
30010
+ credentialType: external_exports.literal("SSH_PRIVATE_KEY"),
30011
+ label: CreateLabelSchema,
30012
+ ...SECRET_SHAPES.SSH_PRIVATE_KEY,
30013
+ authConfig: SshPrivateKeyAuthConfigSchema
30014
+ }).strict();
29786
30015
  var CreateCredentialRequestSchema = external_exports.discriminatedUnion("credentialType", [
29787
30016
  CreateBearerSchema,
29788
30017
  CreateJwtSchema,
@@ -29791,7 +30020,8 @@ var CreateCredentialRequestSchema = external_exports.discriminatedUnion("credent
29791
30020
  CreateUsernamePasswordSchema,
29792
30021
  CreateCookieSchema,
29793
30022
  CreateMagicAuthSchema,
29794
- CreateSessionStateSchema
30023
+ CreateSessionStateSchema,
30024
+ CreateSshPrivateKeySchema
29795
30025
  ]);
29796
30026
  var CredentialResponseSchema = external_exports.object({
29797
30027
  credentialId: external_exports.uuid(),
@@ -29834,7 +30064,8 @@ var RotateCredentialSecretRequestSchema = external_exports.discriminatedUnion("c
29834
30064
  credentialType: external_exports.literal("USERNAME_PASSWORD"),
29835
30065
  ...SECRET_SHAPES.USERNAME_PASSWORD
29836
30066
  }).strict(),
29837
- external_exports.object({ credentialType: external_exports.literal("SESSION_STATE"), ...SECRET_SHAPES.SESSION_STATE }).strict()
30067
+ external_exports.object({ credentialType: external_exports.literal("SESSION_STATE"), ...SECRET_SHAPES.SESSION_STATE }).strict(),
30068
+ external_exports.object({ credentialType: external_exports.literal("SSH_PRIVATE_KEY"), ...SECRET_SHAPES.SSH_PRIVATE_KEY }).strict()
29838
30069
  ]);
29839
30070
  var SetCredentialMfaRequestSchema = external_exports.object({
29840
30071
  kind: external_exports.literal("TOTP"),
@@ -31186,6 +31417,159 @@ var inboxContract = c19.router({
31186
31417
  }
31187
31418
  });
31188
31419
 
31420
+ // ../../packages/api-contracts/src/schemas/api/v1/member.api.ts
31421
+ var MemberOrgParamsSchema = OrgIdParamsSchema;
31422
+ var MemberRoleSlugSchema = external_exports.enum(["admin", "analyst", "developer"]);
31423
+ var CreateMemberInvitationRequestSchema = external_exports.object({
31424
+ email: external_exports.email().max(320),
31425
+ roleSlug: MemberRoleSlugSchema.default("developer")
31426
+ });
31427
+ var CreateMemberInvitationResponseSchema = external_exports.object({
31428
+ invitationId: external_exports.string().min(1),
31429
+ email: external_exports.email(),
31430
+ roleSlug: MemberRoleSlugSchema
31431
+ });
31432
+
31433
+ // ../../packages/api-contracts/src/schemas/api/v1/invite-link.api.ts
31434
+ var INVITE_LINK_MIN_EXPIRES_IN_HOURS = 1;
31435
+ var INVITE_LINK_MAX_EXPIRES_IN_HOURS = 24 * 30;
31436
+ var INVITE_LINK_DEFAULT_EXPIRES_IN_HOURS = 24 * 7;
31437
+ var INVITE_LINK_MIN_USES = 1;
31438
+ var INVITE_LINK_MAX_USES = 100;
31439
+ var INVITE_LINK_DEFAULT_MAX_USES = 1;
31440
+ var InviteLinkOrgParamsSchema = OrgIdParamsSchema;
31441
+ var InviteLinkParamsSchema = OrgIdParamsSchema.extend({
31442
+ inviteLinkId: external_exports.uuid()
31443
+ });
31444
+ var InviteLinkTokenSchema = external_exports.string().min(32).max(128).regex(/^[A-Za-z0-9_-]+$/, {
31445
+ message: "token must contain only URL-safe base64 characters"
31446
+ });
31447
+ var InviteLinkTokenParamsSchema = external_exports.object({
31448
+ token: InviteLinkTokenSchema
31449
+ });
31450
+ var CreateInviteLinkRequestSchema = external_exports.object({
31451
+ roleSlug: MemberRoleSlugSchema.default("developer"),
31452
+ expiresInHours: external_exports.number().int().min(INVITE_LINK_MIN_EXPIRES_IN_HOURS).max(INVITE_LINK_MAX_EXPIRES_IN_HOURS).default(INVITE_LINK_DEFAULT_EXPIRES_IN_HOURS),
31453
+ maxUses: external_exports.number().int().min(INVITE_LINK_MIN_USES).max(INVITE_LINK_MAX_USES).default(INVITE_LINK_DEFAULT_MAX_USES)
31454
+ });
31455
+ var InviteLinkStatusSchema = external_exports.enum(["active", "expired", "exhausted", "revoked"]);
31456
+ var InviteLinkResponseSchema = external_exports.object({
31457
+ inviteLinkId: external_exports.uuid(),
31458
+ organizationId: external_exports.uuid(),
31459
+ roleSlug: MemberRoleSlugSchema,
31460
+ status: InviteLinkStatusSchema,
31461
+ expiresAt: external_exports.coerce.date(),
31462
+ maxUses: external_exports.number().int(),
31463
+ useCount: external_exports.number().int(),
31464
+ createdById: external_exports.uuid(),
31465
+ createdByName: external_exports.string(),
31466
+ createdByEmail: external_exports.string(),
31467
+ revokedAt: external_exports.coerce.date().nullable(),
31468
+ createdAt: external_exports.coerce.date()
31469
+ });
31470
+ var CreateInviteLinkResponseSchema = InviteLinkResponseSchema.extend({
31471
+ url: external_exports.url()
31472
+ });
31473
+ var ListInviteLinksResponseSchema = external_exports.array(InviteLinkResponseSchema);
31474
+ var RevokeInviteLinkResponseSchema = external_exports.object({
31475
+ inviteLinkId: external_exports.uuid(),
31476
+ revokedAt: external_exports.coerce.date()
31477
+ });
31478
+ var InviteLinkPreviewResponseSchema = external_exports.object({
31479
+ status: InviteLinkStatusSchema,
31480
+ organizationName: external_exports.string(),
31481
+ organizationLogoUrl: external_exports.string().nullable(),
31482
+ roleSlug: MemberRoleSlugSchema,
31483
+ expiresAt: external_exports.coerce.date()
31484
+ });
31485
+ var AcceptInviteLinkResponseSchema = external_exports.object({
31486
+ /** `joined` created a membership; `already_member` found an active one and consumed nothing. */
31487
+ outcome: external_exports.enum(["joined", "already_member"]),
31488
+ organizationSlug: external_exports.string(),
31489
+ organizationName: external_exports.string(),
31490
+ workosOrganizationId: external_exports.string(),
31491
+ roleSlug: MemberRoleSlugSchema
31492
+ });
31493
+ var AcceptInviteLinkRejectedResponseSchema = ErrorResponseSchema.extend({
31494
+ status: InviteLinkStatusSchema.optional()
31495
+ });
31496
+
31497
+ // ../../packages/api-contracts/src/contracts/v1/invite-link.ts
31498
+ var c20 = initContract();
31499
+ var inviteLinkContract = c20.router({
31500
+ createInviteLink: {
31501
+ method: "POST",
31502
+ path: "/organizations/:organizationId/invite-links",
31503
+ pathParams: InviteLinkOrgParamsSchema,
31504
+ body: CreateInviteLinkRequestSchema,
31505
+ responses: {
31506
+ 201: CreateInviteLinkResponseSchema,
31507
+ 400: ErrorResponseSchema,
31508
+ 401: ErrorResponseSchema,
31509
+ 403: ErrorResponseSchema,
31510
+ 404: ErrorResponseSchema,
31511
+ 500: ErrorResponseSchema
31512
+ }
31513
+ },
31514
+ listInviteLinks: {
31515
+ method: "GET",
31516
+ path: "/organizations/:organizationId/invite-links",
31517
+ pathParams: InviteLinkOrgParamsSchema,
31518
+ responses: {
31519
+ 200: ListInviteLinksResponseSchema,
31520
+ 401: ErrorResponseSchema,
31521
+ 403: ErrorResponseSchema,
31522
+ 404: ErrorResponseSchema,
31523
+ 500: ErrorResponseSchema
31524
+ }
31525
+ },
31526
+ revokeInviteLink: {
31527
+ method: "DELETE",
31528
+ path: "/organizations/:organizationId/invite-links/:inviteLinkId",
31529
+ pathParams: InviteLinkParamsSchema,
31530
+ responses: {
31531
+ 200: RevokeInviteLinkResponseSchema,
31532
+ 401: ErrorResponseSchema,
31533
+ 403: ErrorResponseSchema,
31534
+ 404: ErrorResponseSchema,
31535
+ 500: ErrorResponseSchema
31536
+ }
31537
+ }
31538
+ });
31539
+ var inviteLinkPublicContract = c20.router(
31540
+ {
31541
+ previewInviteLink: {
31542
+ method: "GET",
31543
+ path: "/invite-links/:token",
31544
+ pathParams: InviteLinkTokenParamsSchema,
31545
+ responses: {
31546
+ 200: InviteLinkPreviewResponseSchema,
31547
+ 400: ErrorResponseSchema,
31548
+ 404: ErrorResponseSchema,
31549
+ 500: ErrorResponseSchema
31550
+ }
31551
+ },
31552
+ acceptInviteLink: {
31553
+ method: "POST",
31554
+ path: "/invite-links/:token/accept",
31555
+ pathParams: InviteLinkTokenParamsSchema,
31556
+ body: c20.noBody(),
31557
+ responses: {
31558
+ 200: AcceptInviteLinkResponseSchema,
31559
+ 400: ErrorResponseSchema,
31560
+ 401: ErrorResponseSchema,
31561
+ 404: ErrorResponseSchema,
31562
+ 409: AcceptInviteLinkRejectedResponseSchema,
31563
+ 503: ErrorResponseSchema,
31564
+ 500: ErrorResponseSchema
31565
+ }
31566
+ }
31567
+ },
31568
+ {
31569
+ pathPrefix: "/v1"
31570
+ }
31571
+ );
31572
+
31189
31573
  // ../../packages/api-contracts/src/schemas/api/v1/knowledge-base.api.ts
31190
31574
  var KnowledgeBaseAuthorSchema = external_exports.object({
31191
31575
  userId: external_exports.string(),
@@ -31217,8 +31601,8 @@ var UpdateKnowledgeBaseResponseSchema = external_exports.object({
31217
31601
  });
31218
31602
 
31219
31603
  // ../../packages/api-contracts/src/contracts/v1/knowledge-base.ts
31220
- var c20 = initContract();
31221
- var knowledgeBaseContract = c20.router({
31604
+ var c21 = initContract();
31605
+ var knowledgeBaseContract = c21.router({
31222
31606
  getKnowledgeBase: {
31223
31607
  method: "GET",
31224
31608
  path: "/organizations/:organizationId/knowledge-base",
@@ -31439,8 +31823,8 @@ var UpdateRepoDocumentContentResponseSchema = external_exports.object({
31439
31823
  });
31440
31824
 
31441
31825
  // ../../packages/api-contracts/src/contracts/v1/knowledge-document.ts
31442
- var c21 = initContract();
31443
- var knowledgeDocumentContract = c21.router({
31826
+ var c22 = initContract();
31827
+ var knowledgeDocumentContract = c22.router({
31444
31828
  createDocument: {
31445
31829
  method: "POST",
31446
31830
  path: "/organizations/:organizationId/knowledge-documents",
@@ -31766,8 +32150,8 @@ var LinearMetadataResponseSchema = external_exports.object({
31766
32150
  });
31767
32151
 
31768
32152
  // ../../packages/api-contracts/src/contracts/v1/linear-integration.ts
31769
- var c22 = initContract();
31770
- var linearIntegrationContract = c22.router({
32153
+ var c23 = initContract();
32154
+ var linearIntegrationContract = c23.router({
31771
32155
  getLinearIntegration: {
31772
32156
  method: "GET",
31773
32157
  path: "/organizations/:organizationId/integrations/linear",
@@ -31785,7 +32169,7 @@ var linearIntegrationContract = c22.router({
31785
32169
  method: "POST",
31786
32170
  path: "/organizations/:organizationId/integrations/linear/connect",
31787
32171
  pathParams: OrgIdParamsSchema,
31788
- body: c22.noBody(),
32172
+ body: c23.noBody(),
31789
32173
  responses: {
31790
32174
  200: LinearConnectResponseSchema,
31791
32175
  401: ErrorResponseSchema,
@@ -31800,7 +32184,7 @@ var linearIntegrationContract = c22.router({
31800
32184
  method: "POST",
31801
32185
  path: "/organizations/:organizationId/integrations/linear/disconnect",
31802
32186
  pathParams: OrgIdParamsSchema,
31803
- body: c22.noBody(),
32187
+ body: c23.noBody(),
31804
32188
  responses: {
31805
32189
  200: LinearIntegrationResponseSchema,
31806
32190
  401: ErrorResponseSchema,
@@ -31862,8 +32246,8 @@ var linearIntegrationContract = c22.router({
31862
32246
  });
31863
32247
 
31864
32248
  // ../../packages/api-contracts/src/contracts/v1/linear-tickets.ts
31865
- var c23 = initContract();
31866
- var linearTicketContract = c23.router({
32249
+ var c24 = initContract();
32250
+ var linearTicketContract = c24.router({
31867
32251
  /**
31868
32252
  * Push one or more pentest findings to Linear as tickets. Customer-side
31869
32253
  * (VIEW_RESULTS + PENTEST capability); non-admins create into the org's
@@ -31967,8 +32351,8 @@ var LlmProviderKeyResponseSchema = external_exports.object({
31967
32351
  var ListLlmProviderKeysResponseSchema = external_exports.array(LlmProviderKeyResponseSchema);
31968
32352
 
31969
32353
  // ../../packages/api-contracts/src/contracts/v1/llm-provider-key.ts
31970
- var c24 = initContract();
31971
- var llmProviderKeyContract = c24.router({
32354
+ var c25 = initContract();
32355
+ var llmProviderKeyContract = c25.router({
31972
32356
  createLlmProviderKey: {
31973
32357
  method: "POST",
31974
32358
  path: "/organizations/:organizationId/llm-provider-keys",
@@ -32035,22 +32419,9 @@ var llmProviderKeyContract = c24.router({
32035
32419
  }
32036
32420
  });
32037
32421
 
32038
- // ../../packages/api-contracts/src/schemas/api/v1/member.api.ts
32039
- var MemberOrgParamsSchema = OrgIdParamsSchema;
32040
- var MemberRoleSlugSchema = external_exports.enum(["admin", "analyst", "developer"]);
32041
- var CreateMemberInvitationRequestSchema = external_exports.object({
32042
- email: external_exports.email().max(320),
32043
- roleSlug: MemberRoleSlugSchema.default("developer")
32044
- });
32045
- var CreateMemberInvitationResponseSchema = external_exports.object({
32046
- invitationId: external_exports.string().min(1),
32047
- email: external_exports.email(),
32048
- roleSlug: MemberRoleSlugSchema
32049
- });
32050
-
32051
32422
  // ../../packages/api-contracts/src/contracts/v1/member.ts
32052
- var c25 = initContract();
32053
- var memberContract = c25.router({
32423
+ var c26 = initContract();
32424
+ var memberContract = c26.router({
32054
32425
  createInvitation: {
32055
32426
  method: "POST",
32056
32427
  path: "/organizations/:organizationId/members/invitations",
@@ -32350,8 +32721,8 @@ var LogoDeleteRequestSchema = external_exports.object({
32350
32721
  });
32351
32722
 
32352
32723
  // ../../packages/api-contracts/src/contracts/v1/organization.ts
32353
- var c26 = initContract();
32354
- var organizationContract = c26.router({
32724
+ var c27 = initContract();
32725
+ var organizationContract = c27.router({
32355
32726
  listOrganizations: {
32356
32727
  method: "GET",
32357
32728
  path: "/organizations",
@@ -32396,6 +32767,23 @@ var organizationContract = c26.router({
32396
32767
  },
32397
32768
  summary: "Create an organization (Trace staff only)"
32398
32769
  },
32770
+ // Staff-only, cross-org: removes the tenant from Trace and WorkOS together.
32771
+ // 409 when members, repositories, or retained audit history still reference
32772
+ // the organization; those must be removed first (or cannot be, for WORM rows).
32773
+ deleteOrganizationAsStaff: {
32774
+ method: "DELETE",
32775
+ path: "/staff/organizations/:organizationId",
32776
+ pathParams: OrgIdParamsSchema,
32777
+ responses: {
32778
+ 204: DeleteResponseSchema,
32779
+ 401: ErrorResponseSchema,
32780
+ 403: ErrorResponseSchema,
32781
+ 404: ErrorResponseSchema,
32782
+ 409: ErrorResponseSchema,
32783
+ 500: ErrorResponseSchema
32784
+ },
32785
+ summary: "Delete an organization (Trace staff only)"
32786
+ },
32399
32787
  getOrganization: {
32400
32788
  method: "GET",
32401
32789
  path: "/organizations/:organizationId",
@@ -32706,7 +33094,7 @@ var organizationContract = c26.router({
32706
33094
  method: "POST",
32707
33095
  path: "/organizations/:organizationId/logos-from-homepage",
32708
33096
  pathParams: OrgIdParamsSchema,
32709
- body: c26.type(),
33097
+ body: c27.type(),
32710
33098
  responses: {
32711
33099
  200: OrganizationResponseSchema,
32712
33100
  // 400 — no homepage set on the organization.
@@ -33013,8 +33401,8 @@ var ScanReuseSuggestionsResponseSchema = external_exports.object({
33013
33401
  });
33014
33402
 
33015
33403
  // ../../packages/api-contracts/src/contracts/v1/pentest.ts
33016
- var c27 = initContract();
33017
- var pentestContract = c27.router({
33404
+ var c28 = initContract();
33405
+ var pentestContract = c28.router({
33018
33406
  listPentests: {
33019
33407
  method: "GET",
33020
33408
  path: "/organizations/:organizationId/pentests",
@@ -33324,8 +33712,8 @@ var pentestContract = c27.router({
33324
33712
  });
33325
33713
 
33326
33714
  // ../../packages/api-contracts/src/contracts/v1/pentest-attack-chain.ts
33327
- var c28 = initContract();
33328
- var pentestAttackChainContract = c28.router({
33715
+ var c29 = initContract();
33716
+ var pentestAttackChainContract = c29.router({
33329
33717
  listAttackChains: {
33330
33718
  method: "GET",
33331
33719
  path: "/organizations/:organizationId/pentests/:pentestId/attack-chains",
@@ -33424,7 +33812,7 @@ var pentestAttackChainContract = c28.router({
33424
33812
  method: "DELETE",
33425
33813
  path: "/organizations/:organizationId/pentests/:pentestId/attack-chains/:attackChainId",
33426
33814
  pathParams: PentestAttackChainIdParamsSchema,
33427
- body: c28.noBody(),
33815
+ body: c29.noBody(),
33428
33816
  responses: {
33429
33817
  200: DeleteResponseSchema,
33430
33818
  401: ErrorResponseSchema,
@@ -33585,8 +33973,8 @@ var ReadAttestationDownloadResponseSchema = external_exports.union([
33585
33973
  ]);
33586
33974
 
33587
33975
  // ../../packages/api-contracts/src/contracts/v1/pentest-attestation.ts
33588
- var c29 = initContract();
33589
- var pentestAttestationContract = c29.router({
33976
+ var c30 = initContract();
33977
+ var pentestAttestationContract = c30.router({
33590
33978
  getDefaults: {
33591
33979
  method: "GET",
33592
33980
  path: "/organizations/:organizationId/pentests/:pentestId/attestations/defaults",
@@ -33729,8 +34117,8 @@ var ListPentestAuditLogResponseSchema = external_exports.object({
33729
34117
  });
33730
34118
 
33731
34119
  // ../../packages/api-contracts/src/contracts/v1/pentest-audit-log.ts
33732
- var c30 = initContract();
33733
- var pentestAuditLogContract = c30.router({
34120
+ var c31 = initContract();
34121
+ var pentestAuditLogContract = c31.router({
33734
34122
  listAuditLog: {
33735
34123
  method: "GET",
33736
34124
  path: "/organizations/:organizationId/pentests/:pentestId/audit-log",
@@ -33777,8 +34165,8 @@ var CreatePentestAuthorizedDomainRequestSchema = external_exports.object({
33777
34165
  }).strict();
33778
34166
 
33779
34167
  // ../../packages/api-contracts/src/contracts/v1/pentest-authorized-domain.ts
33780
- var c31 = initContract();
33781
- var pentestAuthorizedDomainContract = c31.router({
34168
+ var c32 = initContract();
34169
+ var pentestAuthorizedDomainContract = c32.router({
33782
34170
  getPentestAuthorizedScope: {
33783
34171
  method: "GET",
33784
34172
  path: "/organizations/:organizationId/pentests/:pentestId/authorized-scope",
@@ -33832,8 +34220,8 @@ var PentestEgressResponseSchema = external_exports.object({
33832
34220
  }).strict();
33833
34221
 
33834
34222
  // ../../packages/api-contracts/src/contracts/v1/pentest-egress.ts
33835
- var c32 = initContract();
33836
- var pentestEgressContract = c32.router({
34223
+ var c33 = initContract();
34224
+ var pentestEgressContract = c33.router({
33837
34225
  getPentestEgress: {
33838
34226
  method: "GET",
33839
34227
  path: "/staff/pentest-egress",
@@ -33872,8 +34260,8 @@ var CreatePentestEngagementExclusionRequestSchema = external_exports.object({
33872
34260
  }).strict();
33873
34261
 
33874
34262
  // ../../packages/api-contracts/src/contracts/v1/pentest-engagement-exclusion.ts
33875
- var c33 = initContract();
33876
- var pentestEngagementExclusionContract = c33.router({
34263
+ var c34 = initContract();
34264
+ var pentestEngagementExclusionContract = c34.router({
33877
34265
  listPentestEngagementExclusions: {
33878
34266
  method: "GET",
33879
34267
  path: "/organizations/:organizationId/pentests/:pentestId/engagement-exclusions",
@@ -33937,8 +34325,8 @@ var CreatePentestEnvironmentRequestSchema = external_exports.object({
33937
34325
  }).strict();
33938
34326
 
33939
34327
  // ../../packages/api-contracts/src/contracts/v1/pentest-environment.ts
33940
- var c34 = initContract();
33941
- var pentestEnvironmentContract = c34.router({
34328
+ var c35 = initContract();
34329
+ var pentestEnvironmentContract = c35.router({
33942
34330
  listPentestEnvironments: {
33943
34331
  method: "GET",
33944
34332
  path: "/organizations/:organizationId/pentests/:pentestId/environments",
@@ -33987,8 +34375,8 @@ var pentestEnvironmentContract = c34.router({
33987
34375
  });
33988
34376
 
33989
34377
  // ../../packages/api-contracts/src/contracts/v1/pentest-finding.ts
33990
- var c35 = initContract();
33991
- var pentestFindingContract = c35.router({
34378
+ var c36 = initContract();
34379
+ var pentestFindingContract = c36.router({
33992
34380
  // ─── Findings ───
33993
34381
  listFindings: {
33994
34382
  method: "GET",
@@ -34092,6 +34480,24 @@ var pentestFindingContract = c35.router({
34092
34480
  500: ErrorResponseSchema
34093
34481
  }
34094
34482
  },
34483
+ // Same visibility as getCustomerFinding: published findings only, customer DTO,
34484
+ // so the document never carries staff identity or a draft.
34485
+ exportCustomerFindingPdf: {
34486
+ method: "GET",
34487
+ path: "/organizations/:organizationId/customer-pentests/:pentestId/findings/:pentestFindingId/pdf",
34488
+ pathParams: CustomerPentestFindingIdParamsSchema,
34489
+ summary: "Render one pentest finding as a Trace-branded PDF",
34490
+ description: "Renders the published finding on demand, stores the PDF, and returns a short-lived presigned download URL. An identical export from the same day is reused without a render. The document names the finding, its classification, and its affected repositories only: no people and no organization name appear on it. Rendering shares one browser per API task, so exports are admission-controlled per organization; a 429 means retry after the interval in `details.retryAfterSeconds`.",
34491
+ responses: {
34492
+ 200: PentestFindingPdfExportResponseSchema,
34493
+ 400: ErrorResponseSchema,
34494
+ 401: ErrorResponseSchema,
34495
+ 403: ErrorResponseSchema,
34496
+ 404: ErrorResponseSchema,
34497
+ 429: ErrorResponseSchema,
34498
+ 500: ErrorResponseSchema
34499
+ }
34500
+ },
34095
34501
  // ─── Evidence (PR 2: surface & curate) ───
34096
34502
  listCustomerFindingEvidence: {
34097
34503
  method: "GET",
@@ -34414,6 +34820,25 @@ var pentestFindingContract = c35.router({
34414
34820
  500: ErrorResponseSchema
34415
34821
  }
34416
34822
  },
34823
+ // Reader feedback on a published finding: a thumbs up/down, a comment, or both.
34824
+ // Append-only and never a status change (disputes are `contest`). Same auth as
34825
+ // the other customer writes: VIEW_RESULTS + user principal + customer visibility.
34826
+ submitFeedback: {
34827
+ method: "POST",
34828
+ path: "/organizations/:organizationId/customer-pentests/:pentestId/findings/:pentestFindingId/feedback",
34829
+ pathParams: CustomerPentestFindingIdParamsSchema,
34830
+ body: CreatePentestFindingFeedbackRequestSchema,
34831
+ summary: "Leave feedback on a pentest finding",
34832
+ description: "Records a thumbs up or thumbs down, an optional reason for a thumbs down, and an optional comment. Each reader counts once in the tallies: a new vote replaces the previous one in the count while the change stays in the resolution history. Returns the updated tallies.",
34833
+ responses: {
34834
+ 201: PentestFindingFeedbackSummarySchema,
34835
+ 400: ErrorResponseSchema,
34836
+ 401: ErrorResponseSchema,
34837
+ 403: ErrorResponseSchema,
34838
+ 404: ErrorResponseSchema,
34839
+ 500: ErrorResponseSchema
34840
+ }
34841
+ },
34417
34842
  // ─── Contests ───
34418
34843
  listContests: {
34419
34844
  method: "GET",
@@ -34544,8 +34969,8 @@ var pentestFindingContract = c35.router({
34544
34969
  });
34545
34970
 
34546
34971
  // ../../packages/api-contracts/src/contracts/v1/pentest-hypothesis.ts
34547
- var c36 = initContract();
34548
- var pentestHypothesisContract = c36.router({
34972
+ var c37 = initContract();
34973
+ var pentestHypothesisContract = c37.router({
34549
34974
  listHypotheses: {
34550
34975
  method: "GET",
34551
34976
  path: "/organizations/:organizationId/pentests/:pentestId/hypotheses",
@@ -34647,9 +35072,148 @@ var pentestHypothesisContract = c36.router({
34647
35072
  }
34648
35073
  });
34649
35074
 
35075
+ // ../../packages/api-contracts/src/schemas/api/v1/pentest-intake-document.api.ts
35076
+ var PentestIntakeDocumentTypeValues = [
35077
+ "PREVIOUS_PENTEST",
35078
+ "ARCHITECTURE_DIAGRAM",
35079
+ "THREAT_MODEL",
35080
+ "OTHER"
35081
+ ];
35082
+ var PentestIntakeDocumentTypeSchema = external_exports.enum(PentestIntakeDocumentTypeValues);
35083
+ var MAX_PENTEST_INTAKE_DOCUMENT_BYTES = 50 * 1024 * 1024;
35084
+ var PentestIntakeDocumentIdParamsSchema = CustomerPentestIdParamsSchema.extend({
35085
+ documentId: external_exports.uuid()
35086
+ });
35087
+ var PentestIntakeDocumentResponseSchema = external_exports.object({
35088
+ documentId: external_exports.uuid(),
35089
+ pentestId: external_exports.uuid(),
35090
+ documentType: PentestIntakeDocumentTypeSchema,
35091
+ fileName: external_exports.string(),
35092
+ fileType: external_exports.string(),
35093
+ fileSizeBytes: external_exports.number().int().nonnegative(),
35094
+ uploadedByName: external_exports.string().nullable(),
35095
+ createdAt: external_exports.coerce.date(),
35096
+ updatedAt: external_exports.coerce.date()
35097
+ });
35098
+ var ListPentestIntakeDocumentsResponseSchema = external_exports.object({
35099
+ /** Mirrors the questionnaire's `editable`: uploads and removals are refused once false. */
35100
+ editable: external_exports.boolean(),
35101
+ documents: external_exports.array(PentestIntakeDocumentResponseSchema)
35102
+ });
35103
+ var CreatePentestIntakeDocumentUploadRequestSchema = external_exports.object({
35104
+ fileName: external_exports.string().min(1).max(255),
35105
+ fileSizeBytes: external_exports.number().int().positive().max(MAX_PENTEST_INTAKE_DOCUMENT_BYTES)
35106
+ }).strict();
35107
+ var CreatePentestIntakeDocumentUploadResponseSchema = external_exports.object({
35108
+ /** Server-minted S3 key; hand it back unchanged on attach. */
35109
+ uploadKey: external_exports.string(),
35110
+ uploadUrl: external_exports.url(),
35111
+ expiresAt: external_exports.coerce.date()
35112
+ });
35113
+ var AttachPentestIntakeDocumentRequestSchema = external_exports.object({
35114
+ uploadKey: external_exports.string().min(1).max(1024),
35115
+ documentType: PentestIntakeDocumentTypeSchema,
35116
+ fileName: external_exports.string().min(1).max(255),
35117
+ fileType: external_exports.string().min(1).max(100)
35118
+ }).strict();
35119
+ var UpdatePentestIntakeDocumentRequestSchema = external_exports.object({
35120
+ documentType: PentestIntakeDocumentTypeSchema
35121
+ }).strict();
35122
+ var DownloadPentestIntakeDocumentResponseSchema = external_exports.object({
35123
+ document: PentestIntakeDocumentResponseSchema,
35124
+ downloadUrl: external_exports.url()
35125
+ });
35126
+
35127
+ // ../../packages/api-contracts/src/contracts/v1/pentest-intake-document.ts
35128
+ var c38 = initContract();
35129
+ var pentestIntakeDocumentContract = c38.router({
35130
+ listDocuments: {
35131
+ method: "GET",
35132
+ path: "/organizations/:organizationId/customer-pentests/:pentestId/questionnaire/documents",
35133
+ pathParams: CustomerPentestIdParamsSchema,
35134
+ responses: {
35135
+ 200: ListPentestIntakeDocumentsResponseSchema,
35136
+ 401: ErrorResponseSchema,
35137
+ 403: ErrorResponseSchema,
35138
+ 404: ErrorResponseSchema,
35139
+ 500: ErrorResponseSchema
35140
+ }
35141
+ },
35142
+ createUpload: {
35143
+ method: "POST",
35144
+ path: "/organizations/:organizationId/customer-pentests/:pentestId/questionnaire/documents/uploads",
35145
+ pathParams: CustomerPentestIdParamsSchema,
35146
+ body: CreatePentestIntakeDocumentUploadRequestSchema,
35147
+ responses: {
35148
+ 201: CreatePentestIntakeDocumentUploadResponseSchema,
35149
+ 400: ErrorResponseSchema,
35150
+ 401: ErrorResponseSchema,
35151
+ 403: ErrorResponseSchema,
35152
+ 404: ErrorResponseSchema,
35153
+ 409: ErrorResponseSchema,
35154
+ 500: ErrorResponseSchema
35155
+ }
35156
+ },
35157
+ attachDocument: {
35158
+ method: "POST",
35159
+ path: "/organizations/:organizationId/customer-pentests/:pentestId/questionnaire/documents",
35160
+ pathParams: CustomerPentestIdParamsSchema,
35161
+ body: AttachPentestIntakeDocumentRequestSchema,
35162
+ responses: {
35163
+ 201: PentestIntakeDocumentResponseSchema,
35164
+ 400: ErrorResponseSchema,
35165
+ 401: ErrorResponseSchema,
35166
+ 403: ErrorResponseSchema,
35167
+ 404: ErrorResponseSchema,
35168
+ 409: ErrorResponseSchema,
35169
+ 500: ErrorResponseSchema
35170
+ }
35171
+ },
35172
+ updateDocument: {
35173
+ method: "PATCH",
35174
+ path: "/organizations/:organizationId/customer-pentests/:pentestId/questionnaire/documents/:documentId",
35175
+ pathParams: PentestIntakeDocumentIdParamsSchema,
35176
+ body: UpdatePentestIntakeDocumentRequestSchema,
35177
+ responses: {
35178
+ 200: PentestIntakeDocumentResponseSchema,
35179
+ 400: ErrorResponseSchema,
35180
+ 401: ErrorResponseSchema,
35181
+ 403: ErrorResponseSchema,
35182
+ 404: ErrorResponseSchema,
35183
+ 409: ErrorResponseSchema,
35184
+ 500: ErrorResponseSchema
35185
+ }
35186
+ },
35187
+ downloadDocument: {
35188
+ method: "GET",
35189
+ path: "/organizations/:organizationId/customer-pentests/:pentestId/questionnaire/documents/:documentId/download",
35190
+ pathParams: PentestIntakeDocumentIdParamsSchema,
35191
+ responses: {
35192
+ 200: DownloadPentestIntakeDocumentResponseSchema,
35193
+ 401: ErrorResponseSchema,
35194
+ 403: ErrorResponseSchema,
35195
+ 404: ErrorResponseSchema,
35196
+ 500: ErrorResponseSchema
35197
+ }
35198
+ },
35199
+ deleteDocument: {
35200
+ method: "DELETE",
35201
+ path: "/organizations/:organizationId/customer-pentests/:pentestId/questionnaire/documents/:documentId",
35202
+ pathParams: PentestIntakeDocumentIdParamsSchema,
35203
+ responses: {
35204
+ 204: DeleteResponseSchema,
35205
+ 401: ErrorResponseSchema,
35206
+ 403: ErrorResponseSchema,
35207
+ 404: ErrorResponseSchema,
35208
+ 409: ErrorResponseSchema,
35209
+ 500: ErrorResponseSchema
35210
+ }
35211
+ }
35212
+ });
35213
+
34650
35214
  // ../../packages/api-contracts/src/contracts/v1/pentest-manual-finding.ts
34651
- var c37 = initContract();
34652
- var pentestManualFindingContract = c37.router({
35215
+ var c39 = initContract();
35216
+ var pentestManualFindingContract = c39.router({
34653
35217
  listManualFindings: {
34654
35218
  method: "GET",
34655
35219
  path: "/organizations/:organizationId/pentests/:pentestId/manual-findings",
@@ -34736,8 +35300,8 @@ var pentestManualFindingContract = c37.router({
34736
35300
  });
34737
35301
 
34738
35302
  // ../../packages/api-contracts/src/contracts/v1/pentest-report-pdf.ts
34739
- var c38 = initContract();
34740
- var pentestReportPdfContract = c38.router({
35303
+ var c40 = initContract();
35304
+ var pentestReportPdfContract = c40.router({
34741
35305
  getCurrent: {
34742
35306
  method: "GET",
34743
35307
  path: "/organizations/:organizationId/pentests/:pentestId/report-pdf",
@@ -34830,7 +35394,7 @@ var pentestReportPdfContract = c38.router({
34830
35394
  // No body: the render always reads current DB state (cover derived via
34831
35395
  // deriveReportCover(), narrative/scope from the row's columns). The legacy
34832
35396
  // dialog payload was retired with the Generate dialog (report-session RS5).
34833
- body: c38.noBody(),
35397
+ body: c40.noBody(),
34834
35398
  responses: {
34835
35399
  202: StartReportPdfGenerationTaskResponseSchema,
34836
35400
  400: ErrorResponseSchema,
@@ -34955,7 +35519,8 @@ var pentestReportPdfContract = c38.router({
34955
35519
  403: ErrorResponseSchema,
34956
35520
  404: ErrorResponseSchema,
34957
35521
  // REPORT_NOT_RENDERED — no PDF exists yet (spec-pr-rs1 §2c) — or
34958
- // BUNDLE_NOT_RENDERED for artifact=markdown with no bundle object.
35522
+ // BUNDLE_NOT_RENDERED / DOCX_NOT_RENDERED for artifact=markdown / docx
35523
+ // with no such object beside the current PDF.
34959
35524
  409: ErrorResponseSchema,
34960
35525
  500: ErrorResponseSchema
34961
35526
  }
@@ -35012,20 +35577,23 @@ var pentestReportPdfContract = c38.router({
35012
35577
  method: "GET",
35013
35578
  path: "/organizations/:organizationId/customer-pentests/:pentestId/report-pdf/download",
35014
35579
  pathParams: CustomerReportPdfPentestParamsSchema,
35015
- query: PentestPdfDownloadQuerySchema,
35580
+ query: CustomerReportPdfDownloadQuerySchema,
35016
35581
  responses: {
35017
35582
  200: CustomerReportPdfDownloadResponseSchema,
35018
35583
  401: ErrorResponseSchema,
35019
35584
  403: ErrorResponseSchema,
35020
35585
  404: ErrorResponseSchema,
35586
+ // DOCX_NOT_RENDERED for artifact=docx when no Word document from the
35587
+ // served render exists.
35588
+ 409: ErrorResponseSchema,
35021
35589
  500: ErrorResponseSchema
35022
35590
  }
35023
35591
  }
35024
35592
  });
35025
35593
 
35026
35594
  // ../../packages/api-contracts/src/contracts/v1/pentest-validation.ts
35027
- var c39 = initContract();
35028
- var pentestValidationContract = c39.router({
35595
+ var c41 = initContract();
35596
+ var pentestValidationContract = c41.router({
35029
35597
  listValidationRuns: {
35030
35598
  method: "GET",
35031
35599
  path: "/organizations/:organizationId/pentests/:pentestId/validation-runs",
@@ -35133,8 +35701,8 @@ var pentestValidationContract = c39.router({
35133
35701
  });
35134
35702
 
35135
35703
  // ../../packages/api-contracts/src/contracts/v1/repository.ts
35136
- var c40 = initContract();
35137
- var repositoryContract = c40.router({
35704
+ var c42 = initContract();
35705
+ var repositoryContract = c42.router({
35138
35706
  createRepository: {
35139
35707
  method: "POST",
35140
35708
  path: "/organizations/:organizationId/repositories",
@@ -35320,7 +35888,7 @@ var repositoryContract = c40.router({
35320
35888
  method: "POST",
35321
35889
  path: "/organizations/:organizationId/repositories/scanning-branch/confirm-defaults",
35322
35890
  pathParams: OrgIdParamsSchema,
35323
- body: c40.noBody(),
35891
+ body: c42.noBody(),
35324
35892
  responses: {
35325
35893
  200: ConfirmScanningBranchDefaultsResponseSchema,
35326
35894
  401: ErrorResponseSchema,
@@ -35454,8 +36022,8 @@ var ListScanLogsResponseSchema = external_exports.object({
35454
36022
  });
35455
36023
 
35456
36024
  // ../../packages/api-contracts/src/contracts/v1/scan.ts
35457
- var c41 = initContract();
35458
- var scanContract = c41.router({
36025
+ var c43 = initContract();
36026
+ var scanContract = c43.router({
35459
36027
  createScan: {
35460
36028
  method: "POST",
35461
36029
  path: "/organizations/:organizationId/scans",
@@ -35576,8 +36144,8 @@ var StaffPentestStatsResponseSchema = external_exports.object({
35576
36144
  }).strict();
35577
36145
 
35578
36146
  // ../../packages/api-contracts/src/contracts/v1/staff-pentest-stats.ts
35579
- var c42 = initContract();
35580
- var staffPentestStatsContract = c42.router({
36147
+ var c44 = initContract();
36148
+ var staffPentestStatsContract = c44.router({
35581
36149
  getStaffPentestStats: {
35582
36150
  method: "GET",
35583
36151
  path: "/staff/pentest-stats",
@@ -35591,6 +36159,59 @@ var staffPentestStatsContract = c42.router({
35591
36159
  }
35592
36160
  });
35593
36161
 
36162
+ // ../../packages/api-contracts/src/schemas/api/v1/staff-pentests.api.ts
36163
+ var StaffOrganizationPentestParamsSchema = OrgIdParamsSchema.extend({
36164
+ pentestId: external_exports.uuid()
36165
+ });
36166
+ var StaffOrganizationPentestSummarySchema = external_exports.object({
36167
+ pentestId: external_exports.uuid(),
36168
+ displayId: external_exports.int().positive(),
36169
+ title: external_exports.string(),
36170
+ status: PentestStatus,
36171
+ currentStage: PentestStage,
36172
+ testType: PentestTestType,
36173
+ /** Bound application names, ordinal-ascending. Empty for an unbound draft. */
36174
+ applicationNames: external_exports.array(external_exports.string()),
36175
+ findingCount: external_exports.int().nonnegative(),
36176
+ startedAt: external_exports.iso.datetime().nullable(),
36177
+ endedAt: external_exports.iso.datetime().nullable(),
36178
+ createdAt: external_exports.iso.datetime()
36179
+ }).strict();
36180
+ var ListStaffOrganizationPentestsResponseSchema = external_exports.array(
36181
+ StaffOrganizationPentestSummarySchema
36182
+ );
36183
+
36184
+ // ../../packages/api-contracts/src/contracts/v1/staff-pentests.ts
36185
+ var c45 = initContract();
36186
+ var staffPentestsContract = c45.router({
36187
+ listStaffOrganizationPentests: {
36188
+ method: "GET",
36189
+ path: "/organizations/:organizationId/staff/pentests",
36190
+ pathParams: OrgIdParamsSchema,
36191
+ responses: {
36192
+ 200: ListStaffOrganizationPentestsResponseSchema,
36193
+ 401: ErrorResponseSchema,
36194
+ 403: ErrorResponseSchema,
36195
+ 404: ErrorResponseSchema,
36196
+ 500: ErrorResponseSchema
36197
+ },
36198
+ summary: "List an organization\u2019s pentests (Trace staff only)"
36199
+ },
36200
+ deleteStaffOrganizationPentest: {
36201
+ method: "DELETE",
36202
+ path: "/organizations/:organizationId/staff/pentests/:pentestId",
36203
+ pathParams: StaffOrganizationPentestParamsSchema,
36204
+ responses: {
36205
+ 204: DeleteResponseSchema,
36206
+ 401: ErrorResponseSchema,
36207
+ 403: ErrorResponseSchema,
36208
+ 404: ErrorResponseSchema,
36209
+ 500: ErrorResponseSchema
36210
+ },
36211
+ summary: "Soft-delete a pentest from the admin console (Trace staff only)"
36212
+ }
36213
+ });
36214
+
35594
36215
  // ../../packages/api-contracts/src/schemas/api/v1/staff-queue.api.ts
35595
36216
  var StaffQueueItemKindValues = [
35596
36217
  "RETEST_OWED",
@@ -35698,8 +36319,8 @@ var StaffQueueResponseSchema = external_exports.object({
35698
36319
  }).strict();
35699
36320
 
35700
36321
  // ../../packages/api-contracts/src/contracts/v1/staff-queue.ts
35701
- var c43 = initContract();
35702
- var staffQueueContract = c43.router({
36322
+ var c46 = initContract();
36323
+ var staffQueueContract = c46.router({
35703
36324
  getStaffQueue: {
35704
36325
  method: "GET",
35705
36326
  path: "/staff/queue",
@@ -35769,8 +36390,8 @@ var AttachTestIntegrationRequestSchema = external_exports.object({
35769
36390
  }).strict();
35770
36391
 
35771
36392
  // ../../packages/api-contracts/src/contracts/v1/test-integration.ts
35772
- var c44 = initContract();
35773
- var testIntegrationContract = c44.router({
36393
+ var c47 = initContract();
36394
+ var testIntegrationContract = c47.router({
35774
36395
  // -- Fixture CRUD (org-less, staff-only) ----------------------------------
35775
36396
  listTestIntegrations: {
35776
36397
  method: "GET",
@@ -35902,8 +36523,8 @@ var MyContextResponseSchema = external_exports.object({
35902
36523
  });
35903
36524
 
35904
36525
  // ../../packages/api-contracts/src/contracts/v1/user.ts
35905
- var c45 = initContract();
35906
- var userContract = c45.router({
36526
+ var c48 = initContract();
36527
+ var userContract = c48.router({
35907
36528
  getStaffProfile: {
35908
36529
  method: "GET",
35909
36530
  path: "/staff/profile",
@@ -36018,8 +36639,8 @@ var VantaIntegrationResponseSchema = external_exports.object({
36018
36639
  });
36019
36640
 
36020
36641
  // ../../packages/api-contracts/src/contracts/v1/vanta-integration.ts
36021
- var c46 = initContract();
36022
- var vantaIntegrationContract = c46.router({
36642
+ var c49 = initContract();
36643
+ var vantaIntegrationContract = c49.router({
36023
36644
  createVantaIntegration: {
36024
36645
  method: "POST",
36025
36646
  path: "/organizations/:organizationId/vanta-integration",
@@ -36151,6 +36772,13 @@ var VulnerabilityResponseSchema = external_exports.object({
36151
36772
  updatedAt: external_exports.coerce.date()
36152
36773
  });
36153
36774
  var ListVulnerabilitiesResponseSchema = external_exports.array(VulnerabilityResponseSchema);
36775
+ var VulnerabilityPdfExportResponseSchema = external_exports.object({
36776
+ /** Download name the presigned URL carries, e.g. `TRACE-042-sql-injection.pdf`. */
36777
+ fileName: external_exports.string(),
36778
+ downloadUrl: external_exports.url(),
36779
+ /** False when an identical export already existed and was reused without a render. */
36780
+ rendered: external_exports.boolean()
36781
+ });
36154
36782
  var FilterValueSchema = external_exports.union([external_exports.string(), external_exports.number(), external_exports.boolean()]);
36155
36783
  var FilterConditionSchema = external_exports.object({
36156
36784
  field: external_exports.enum(["repositoryId", "severity", "vulnerabilityType", "status", "cweId", "scanMode"]),
@@ -36351,8 +36979,8 @@ var VerifySharedVulnerabilityEmailCodeResponseSchema = external_exports.object({
36351
36979
  });
36352
36980
 
36353
36981
  // ../../packages/api-contracts/src/contracts/v1/vulnerability.ts
36354
- var c47 = initContract();
36355
- var vulnerabilityContract = c47.router({
36982
+ var c50 = initContract();
36983
+ var vulnerabilityContract = c50.router({
36356
36984
  filterVulnerabilities: {
36357
36985
  method: "POST",
36358
36986
  path: "/organizations/:organizationId/vulnerabilities/filter",
@@ -36404,6 +37032,22 @@ var vulnerabilityContract = c47.router({
36404
37032
  500: ErrorResponseSchema
36405
37033
  }
36406
37034
  },
37035
+ exportVulnerabilityPdf: {
37036
+ method: "GET",
37037
+ path: "/organizations/:organizationId/vulnerabilities/:vulnerabilityId/pdf",
37038
+ pathParams: VulnerabilityLookupIdParamsSchema,
37039
+ summary: "Render one vulnerability as a Trace-branded PDF",
37040
+ description: "Renders the vulnerability on demand, stores the PDF, and returns a short-lived presigned download URL. An identical export from the same day is reused without a render. The document names the finding and its code location only: no people and no organization name appear on it. Rendering shares one browser per API task, so exports are admission-controlled per organization; a 429 means retry after the interval in `details.retryAfterSeconds`.",
37041
+ responses: {
37042
+ 200: VulnerabilityPdfExportResponseSchema,
37043
+ 400: ErrorResponseSchema,
37044
+ 401: ErrorResponseSchema,
37045
+ 403: ErrorResponseSchema,
37046
+ 404: ErrorResponseSchema,
37047
+ 429: ErrorResponseSchema,
37048
+ 500: ErrorResponseSchema
37049
+ }
37050
+ },
36407
37051
  updateVulnerability: {
36408
37052
  method: "PATCH",
36409
37053
  path: "/organizations/:organizationId/vulnerabilities/:vulnerabilityId",
@@ -36560,8 +37204,8 @@ var UpdateVulnerabilityNoteRequestSchema = external_exports.object({
36560
37204
  });
36561
37205
 
36562
37206
  // ../../packages/api-contracts/src/contracts/v1/vulnerability-note.ts
36563
- var c48 = initContract();
36564
- var vulnerabilityNoteContract = c48.router({
37207
+ var c51 = initContract();
37208
+ var vulnerabilityNoteContract = c51.router({
36565
37209
  listNotes: {
36566
37210
  method: "GET",
36567
37211
  path: "/organizations/:organizationId/vulnerabilities/:vulnerabilityId/notes",
@@ -36742,8 +37386,8 @@ var ListWebhookDeliveriesResponseSchema = external_exports.object({
36742
37386
  });
36743
37387
 
36744
37388
  // ../../packages/api-contracts/src/contracts/v1/webhook-settings.ts
36745
- var c49 = initContract();
36746
- var webhookSettingsContract = c49.router({
37389
+ var c52 = initContract();
37390
+ var webhookSettingsContract = c52.router({
36747
37391
  listWebhookEndpoints: {
36748
37392
  method: "GET",
36749
37393
  path: "/organizations/:organizationId/webhook-settings/endpoints",
@@ -36881,8 +37525,8 @@ var webhookSettingsContract = c49.router({
36881
37525
  });
36882
37526
 
36883
37527
  // ../../packages/api-contracts/src/contracts/v1/index.ts
36884
- var c50 = initContract();
36885
- var v1Contract = c50.router(
37528
+ var c53 = initContract();
37529
+ var v1Contract = c53.router(
36886
37530
  {
36887
37531
  admin: adminContract,
36888
37532
  cloudIntegration: cloudIntegrationContract,
@@ -36911,6 +37555,7 @@ var v1Contract = c50.router(
36911
37555
  agentTask: agentTaskContract,
36912
37556
  verifyApplicationSetup: verifyApplicationSetupContract,
36913
37557
  pentest: pentestContract,
37558
+ pentestIntakeDocument: pentestIntakeDocumentContract,
36914
37559
  pentestAttestation: pentestAttestationContract,
36915
37560
  pentestManualFinding: pentestManualFindingContract,
36916
37561
  pentestAuditLog: pentestAuditLogContract,
@@ -36928,10 +37573,12 @@ var v1Contract = c50.router(
36928
37573
  inbox: inboxContract,
36929
37574
  apiKey: apiKeyContract,
36930
37575
  member: memberContract,
37576
+ inviteLink: inviteLinkContract,
36931
37577
  engagement: engagementContract,
36932
37578
  testIntegration: testIntegrationContract,
36933
37579
  pentestEgress: pentestEgressContract,
36934
37580
  staffPentestStats: staffPentestStatsContract,
37581
+ staffPentests: staffPentestsContract,
36935
37582
  staffQueue: staffQueueContract
36936
37583
  },
36937
37584
  {
@@ -36940,8 +37587,8 @@ var v1Contract = c50.router(
36940
37587
  );
36941
37588
 
36942
37589
  // ../../packages/api-contracts/src/contracts/index.ts
36943
- var c51 = initContract();
36944
- var contract = c51.router({
37590
+ var c54 = initContract();
37591
+ var contract = c54.router({
36945
37592
  v1: v1Contract
36946
37593
  });
36947
37594
 
@@ -36951,8 +37598,8 @@ var GitHubWebhookResponseSchema = external_exports.object({
36951
37598
  });
36952
37599
 
36953
37600
  // ../../packages/api-contracts/src/contracts/webhooks/github.ts
36954
- var c52 = initContract();
36955
- var githubWebhookContract = c52.router(
37601
+ var c55 = initContract();
37602
+ var githubWebhookContract = c55.router(
36956
37603
  {
36957
37604
  handleWebhook: {
36958
37605
  method: "POST",
@@ -36970,7 +37617,7 @@ var githubWebhookContract = c52.router(
36970
37617
  pathPrefix: "/webhooks"
36971
37618
  }
36972
37619
  );
36973
- var githubReadonlyWebhookContract = c52.router(
37620
+ var githubReadonlyWebhookContract = c55.router(
36974
37621
  {
36975
37622
  handleWebhook: {
36976
37623
  method: "POST",
@@ -36990,9 +37637,9 @@ var githubReadonlyWebhookContract = c52.router(
36990
37637
  );
36991
37638
 
36992
37639
  // ../../packages/api-contracts/src/contracts/webhooks/linear.ts
36993
- var c53 = initContract();
37640
+ var c56 = initContract();
36994
37641
  var LinearWebhookResponseSchema = external_exports.object({ ok: external_exports.boolean() });
36995
- var linearWebhookContract = c53.router(
37642
+ var linearWebhookContract = c56.router(
36996
37643
  {
36997
37644
  handleWebhook: {
36998
37645
  method: "POST",
@@ -37012,7 +37659,7 @@ var linearWebhookContract = c53.router(
37012
37659
  );
37013
37660
 
37014
37661
  // ../../packages/api-contracts/src/contracts/forge/bitbucket.ts
37015
- var c54 = initContract();
37662
+ var c57 = initContract();
37016
37663
  var InstallEventSchema = external_exports.object({
37017
37664
  eventType: external_exports.enum(["avi:forge:installed:app", "avi:forge:upgraded:app"]),
37018
37665
  context: external_exports.object({ cloudId: external_exports.string().min(1) }).loose(),
@@ -37041,7 +37688,7 @@ var ForgePairingCodeResponseSchema = external_exports.discriminatedUnion("state"
37041
37688
  expiresInSeconds: external_exports.number().int()
37042
37689
  })
37043
37690
  ]);
37044
- var forgeBitbucketContract = c54.router(
37691
+ var forgeBitbucketContract = c57.router(
37045
37692
  {
37046
37693
  lifecycle: {
37047
37694
  method: "POST",
@@ -37088,8 +37735,8 @@ var HealthResponseSchema = external_exports.object({
37088
37735
  });
37089
37736
 
37090
37737
  // ../../packages/api-contracts/src/contracts/health.ts
37091
- var c55 = initContract();
37092
- var healthContract = c55.router({
37738
+ var c58 = initContract();
37739
+ var healthContract = c58.router({
37093
37740
  getHealth: {
37094
37741
  method: "GET",
37095
37742
  path: "/health",
@@ -37101,8 +37748,8 @@ var healthContract = c55.router({
37101
37748
  });
37102
37749
 
37103
37750
  // ../../packages/api-contracts/src/contracts/v1/vulnerability-share.ts
37104
- var c56 = initContract();
37105
- var vulnerabilityShareContract = c56.router(
37751
+ var c59 = initContract();
37752
+ var vulnerabilityShareContract = c59.router(
37106
37753
  {
37107
37754
  resolveShareLink: {
37108
37755
  method: "GET",
@@ -37189,8 +37836,8 @@ var UpdatePrVulnerabilityRequestSchema = external_exports.object({
37189
37836
  });
37190
37837
 
37191
37838
  // ../../packages/api-contracts/src/contracts/v1/pr-vulnerability.ts
37192
- var c57 = initContract();
37193
- var prVulnerabilityContract = c57.router(
37839
+ var c60 = initContract();
37840
+ var prVulnerabilityContract = c60.router(
37194
37841
  {
37195
37842
  listPrVulnerabilities: {
37196
37843
  method: "GET",
@@ -37241,7 +37888,7 @@ async function requestDeviceAuthorization(clientId) {
37241
37888
  }
37242
37889
  return await response.json();
37243
37890
  }
37244
- var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
37891
+ var sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
37245
37892
  async function pollForToken(clientId, deviceCode, options = {}) {
37246
37893
  const expiresIn = options.expiresIn ?? 300;
37247
37894
  let interval = options.interval ?? 5;
@@ -37926,18 +38573,18 @@ var proto = Object.defineProperties(() => {
37926
38573
  }
37927
38574
  }
37928
38575
  });
37929
- var createStyler = (open2, close, parent) => {
38576
+ var createStyler = (open3, close, parent) => {
37930
38577
  let openAll;
37931
38578
  let closeAll;
37932
38579
  if (parent === void 0) {
37933
- openAll = open2;
38580
+ openAll = open3;
37934
38581
  closeAll = close;
37935
38582
  } else {
37936
- openAll = parent.openAll + open2;
38583
+ openAll = parent.openAll + open3;
37937
38584
  closeAll = close + parent.closeAll;
37938
38585
  }
37939
38586
  return {
37940
- open: open2,
38587
+ open: open3,
37941
38588
  close,
37942
38589
  openAll,
37943
38590
  closeAll,
@@ -39899,12 +40546,12 @@ function buildApplicationsTable(apps) {
39899
40546
  repos: String(a2.repositories.length),
39900
40547
  url: a2.applicationUrl ? sanitizeDisplay(a2.applicationUrl) : "\u2014"
39901
40548
  }));
39902
- const idWidth = Math.max(2, ...cells.map((c59) => c59.id.length));
39903
- const nameWidth = Math.max(4, ...cells.map((c59) => c59.name.length));
39904
- const typeWidth = Math.max(4, ...cells.map((c59) => c59.type.length));
40549
+ const idWidth = Math.max(2, ...cells.map((c62) => c62.id.length));
40550
+ const nameWidth = Math.max(4, ...cells.map((c62) => c62.name.length));
40551
+ const typeWidth = Math.max(4, ...cells.map((c62) => c62.type.length));
39905
40552
  const header = `${"ID".padEnd(idWidth)} ${"NAME".padEnd(nameWidth)} ${"TYPE".padEnd(typeWidth)} REPOS URL`;
39906
40553
  const rows = cells.map(
39907
- (c59) => `${c59.id.padEnd(idWidth)} ${c59.name.padEnd(nameWidth)} ${c59.type.padEnd(typeWidth)} ${c59.repos.padStart(5)} ${c59.url}`
40554
+ (c62) => `${c62.id.padEnd(idWidth)} ${c62.name.padEnd(nameWidth)} ${c62.type.padEnd(typeWidth)} ${c62.repos.padStart(5)} ${c62.url}`
39908
40555
  );
39909
40556
  return { header, rows };
39910
40557
  }
@@ -39924,12 +40571,12 @@ function buildApplicationRepositoriesTable(links) {
39924
40571
  path: l2.repositoryPath ? sanitizeDisplay(l2.repositoryPath) : "\u2014",
39925
40572
  status: formatStatus(l2.repositoryStatus)
39926
40573
  }));
39927
- const nameWidth = Math.max(10, ...cells.map((c59) => c59.name.length));
39928
- const roleWidth = Math.max(4, ...cells.map((c59) => c59.role.length));
39929
- const pathWidth = Math.max(4, ...cells.map((c59) => c59.path.length));
40574
+ const nameWidth = Math.max(10, ...cells.map((c62) => c62.name.length));
40575
+ const roleWidth = Math.max(4, ...cells.map((c62) => c62.role.length));
40576
+ const pathWidth = Math.max(4, ...cells.map((c62) => c62.path.length));
39930
40577
  const header = `${"REPOSITORY".padEnd(nameWidth)} SCAN ${"ROLE".padEnd(roleWidth)} ${"PATH".padEnd(pathWidth)} STATUS`;
39931
40578
  const rows = cells.map(
39932
- (c59) => `${c59.name.padEnd(nameWidth)} ${c59.scan.padEnd(4)} ${c59.role.padEnd(roleWidth)} ${c59.path.padEnd(pathWidth)} ${c59.status}`
40579
+ (c62) => `${c62.name.padEnd(nameWidth)} ${c62.scan.padEnd(4)} ${c62.role.padEnd(roleWidth)} ${c62.path.padEnd(pathWidth)} ${c62.status}`
39933
40580
  );
39934
40581
  return { header, rows };
39935
40582
  }
@@ -40714,8 +41361,8 @@ function wrapTextWithPrefix(e, r2, o2, t2 = o2, s = o2, n3) {
40714
41361
  hard: true,
40715
41362
  trim: false
40716
41363
  }).split(`
40717
- `).map((c59, i2, m2) => {
40718
- const d = n3 ? n3(c59, i2) : c59;
41364
+ `).map((c62, i2, m2) => {
41365
+ const d = n3 ? n3(c62, i2) : c62;
40719
41366
  return i2 === 0 ? `${t2}${d}` : i2 === m2.length - 1 ? `${s}${d}` : `${o2}${d}`;
40720
41367
  }).join(`
40721
41368
  `);
@@ -41213,14 +41860,14 @@ var limitOptions = ({
41213
41860
  Math.min(l2 - a2 + 3, e.length - a2),
41214
41861
  0
41215
41862
  ));
41216
- let d = a2 < e.length && f > 0, c59 = a2 < e.length && f + a2 < e.length;
41863
+ let d = a2 < e.length && f > 0, c62 = a2 < e.length && f + a2 < e.length;
41217
41864
  const W = Math.min(
41218
41865
  f + a2,
41219
41866
  e.length
41220
41867
  ), s = [];
41221
41868
  let g = 0;
41222
- d && g++, c59 && g++;
41223
- const T = f + (d ? 1 : 0), y = W - (c59 ? 1 : 0);
41869
+ d && g++, c62 && g++;
41870
+ const T = f + (d ? 1 : 0), y = W - (c62 ? 1 : 0);
41224
41871
  for (let t2 = T; t2 < y; t2++) {
41225
41872
  const n3 = e[t2], o2 = n3 ? w(n3, t2 === l2) : "", h2 = wrapAnsi(o2, i2, {
41226
41873
  hard: true,
@@ -41241,14 +41888,14 @@ var limitOptions = ({
41241
41888
  u4,
41242
41889
  true
41243
41890
  );
41244
- d ? ({ lineCount: o2, removals: t2 } = L(), o2 > u4 && (c59 || (u4 -= 1), { lineCount: o2, removals: n3 } = E())) : (c59 || (u4 -= 1), { lineCount: o2, removals: n3 } = E(), o2 > u4 && (u4 -= 1, { lineCount: o2, removals: t2 } = L())), t2 > 0 && (d = true, s.splice(0, t2)), n3 > 0 && (c59 = true, s.splice(s.length - n3, n3));
41891
+ d ? ({ lineCount: o2, removals: t2 } = L(), o2 > u4 && (c62 || (u4 -= 1), { lineCount: o2, removals: n3 } = E())) : (c62 || (u4 -= 1), { lineCount: o2, removals: n3 } = E(), o2 > u4 && (u4 -= 1, { lineCount: o2, removals: t2 } = L())), t2 > 0 && (d = true, s.splice(0, t2)), n3 > 0 && (c62 = true, s.splice(s.length - n3, n3));
41245
41892
  }
41246
41893
  const x = [];
41247
41894
  d && x.push(M);
41248
41895
  for (const t2 of s)
41249
41896
  for (const n3 of t2)
41250
41897
  x.push(n3);
41251
- return c59 && x.push(M), x;
41898
+ return c62 && x.push(M), x;
41252
41899
  };
41253
41900
  var confirm = (i2) => {
41254
41901
  const a2 = i2.active ?? "Yes", s = i2.inactive ?? "No";
@@ -41267,15 +41914,15 @@ var confirm = (i2) => {
41267
41914
  u4
41268
41915
  ), o2 = `${e ? `${styleText2("gray", S_BAR)}
41269
41916
  ` : ""}${f}
41270
- `, c59 = this.value ? a2 : s;
41917
+ `, c62 = this.value ? a2 : s;
41271
41918
  switch (this.state) {
41272
41919
  case "submit": {
41273
41920
  const r2 = e ? `${styleText2("gray", S_BAR)} ` : "";
41274
- return `${o2}${r2}${styleText2("dim", c59)}`;
41921
+ return `${o2}${r2}${styleText2("dim", c62)}`;
41275
41922
  }
41276
41923
  case "cancel": {
41277
41924
  const r2 = e ? `${styleText2("gray", S_BAR)} ` : "";
41278
- return `${o2}${r2}${styleText2(["strikethrough", "dim"], c59)}${e ? `
41925
+ return `${o2}${r2}${styleText2(["strikethrough", "dim"], c62)}${e ? `
41279
41926
  ${styleText2("gray", S_BAR)}` : ""}`;
41280
41927
  }
41281
41928
  default: {
@@ -41423,13 +42070,13 @@ var multiline = (e) => new h({
41423
42070
  `, m2 = e.placeholder && e.placeholder.length > 0 ? (
41424
42071
  // biome-ignore lint/style/noNonNullAssertion: guarded by placeholder.length > 0
41425
42072
  styleText2("inverse", e.placeholder[0]) + styleText2("dim", e.placeholder.slice(1))
41426
- ) : styleText2(["inverse", "hidden"], "_"), a2 = this.userInput ? this.userInputWithCursor : m2, l2 = this.value ?? "", c59 = e.showSubmit ? `
42073
+ ) : styleText2(["inverse", "hidden"], "_"), a2 = this.userInput ? this.userInputWithCursor : m2, l2 = this.value ?? "", c62 = e.showSubmit ? `
41427
42074
  ${styleText2(this.focused === "submit" ? "cyan" : "dim", "[ submit ]")}` : "";
41428
42075
  switch (this.state) {
41429
42076
  case "error": {
41430
42077
  const n3 = `${styleText2("yellow", S_BAR)} `, r2 = i2 ? wrapTextWithPrefix(e.output, a2, n3, void 0) : a2, u4 = styleText2("yellow", S_BAR_END);
41431
42078
  return `${o2}${r2}
41432
- ${u4} ${styleText2("yellow", this.error)}${c59}
42079
+ ${u4} ${styleText2("yellow", this.error)}${c62}
41433
42080
  `;
41434
42081
  }
41435
42082
  case "submit": {
@@ -41457,7 +42104,7 @@ ${u4} ${styleText2("yellow", this.error)}${c59}
41457
42104
  default: {
41458
42105
  const n3 = i2 ? `${styleText2("cyan", S_BAR)} ` : "", r2 = i2 ? styleText2("cyan", S_BAR_END) : "", u4 = i2 ? wrapTextWithPrefix(e.output, a2, n3) : a2;
41459
42106
  return `${o2}${u4}
41460
- ${r2}${c59}
42107
+ ${r2}${c62}
41461
42108
  `;
41462
42109
  }
41463
42110
  }
@@ -41472,7 +42119,7 @@ var password = (r2) => new u$1({
41472
42119
  render() {
41473
42120
  const e = r2.withGuide ?? settings.withGuide, o2 = `${e ? `${styleText2("gray", S_BAR)}
41474
42121
  ` : ""}${symbol2(this.state)} ${r2.message}
41475
- `, c59 = this.userInputWithCursor, i2 = this.masked;
42122
+ `, c62 = this.userInputWithCursor, i2 = this.masked;
41476
42123
  switch (this.state) {
41477
42124
  case "error": {
41478
42125
  const s = e ? `${styleText2("yellow", S_BAR)} ` : "", n3 = e ? `${styleText2("yellow", S_BAR_END)} ` : "", l2 = i2 ?? "";
@@ -41492,7 +42139,7 @@ ${styleText2("gray", S_BAR)}` : ""}`;
41492
42139
  }
41493
42140
  default: {
41494
42141
  const s = e ? `${styleText2("cyan", S_BAR)} ` : "", n3 = e ? styleText2("cyan", S_BAR_END) : "";
41495
- return `${o2}${s}${c59}
42142
+ return `${o2}${s}${c62}
41496
42143
  ${n3}
41497
42144
  `;
41498
42145
  }
@@ -41508,7 +42155,7 @@ var SELECT_INSTRUCTIONS = [
41508
42155
  `${styleText2("dim", "\u2191/\u2193")} to navigate`,
41509
42156
  `${styleText2("dim", "Enter:")} confirm`
41510
42157
  ];
41511
- var c58 = (t2, o2) => t2.includes(`
42158
+ var c61 = (t2, o2) => t2.includes(`
41512
42159
  `) ? t2.split(`
41513
42160
  `).map((d) => o2(d)).join(`
41514
42161
  `) : o2(t2);
@@ -41519,15 +42166,15 @@ var select = (t2) => {
41519
42166
  const s = n3.label ?? String(n3.value);
41520
42167
  switch (m2) {
41521
42168
  case "disabled":
41522
- return `${styleText2("gray", S_RADIO_INACTIVE)} ${c58(s, (i2) => styleText2("gray", i2))}${n3.hint ? ` ${styleText2("dim", `(${n3.hint ?? "disabled"})`)}` : ""}`;
42169
+ return `${styleText2("gray", S_RADIO_INACTIVE)} ${c61(s, (i2) => styleText2("gray", i2))}${n3.hint ? ` ${styleText2("dim", `(${n3.hint ?? "disabled"})`)}` : ""}`;
41523
42170
  case "selected":
41524
- return `${c58(s, (i2) => styleText2("dim", i2))}`;
42171
+ return `${c61(s, (i2) => styleText2("dim", i2))}`;
41525
42172
  case "active":
41526
42173
  return `${styleText2("green", S_RADIO_ACTIVE)} ${s}${n3.hint ? ` ${styleText2("dim", `(${n3.hint})`)}` : ""}`;
41527
42174
  case "cancelled":
41528
- return `${c58(s, (i2) => styleText2(["strikethrough", "dim"], i2))}`;
42175
+ return `${c61(s, (i2) => styleText2(["strikethrough", "dim"], i2))}`;
41529
42176
  default:
41530
- return `${styleText2("dim", S_RADIO_INACTIVE)} ${c58(s, (i2) => styleText2("dim", i2))}`;
42177
+ return `${styleText2("dim", S_RADIO_INACTIVE)} ${c61(s, (i2) => styleText2("dim", i2))}`;
41531
42178
  }
41532
42179
  }, d = t2.showInstructions ?? true;
41533
42180
  return new n$1({
@@ -42907,7 +43554,7 @@ function extractCommandTree(cmd) {
42907
43554
  description: o2.description,
42908
43555
  takesValue: o2.required || o2.optional
42909
43556
  }));
42910
- const subcommands = cmd.commands.filter((c59) => !c59._hidden).map((c59) => extractCommandTree(c59));
43557
+ const subcommands = cmd.commands.filter((c62) => !c62._hidden).map((c62) => extractCommandTree(c62));
42911
43558
  return {
42912
43559
  name: cmd.name(),
42913
43560
  description: cmd.description(),
@@ -42973,7 +43620,7 @@ function generateBashCompletion(tree, binName) {
42973
43620
  }
42974
43621
  function collectBashCases(tree, prefix) {
42975
43622
  const cases = /* @__PURE__ */ new Map();
42976
- const subcommandNames = tree.subcommands.map((c59) => c59.name);
43623
+ const subcommandNames = tree.subcommands.map((c62) => c62.name);
42977
43624
  const optionNames = tree.options.flatMap((o2) => o2.short ? [o2.long, o2.short] : [o2.long]);
42978
43625
  const extraOptions = prefix === "" ? ["--help", "--version"] : ["--help"];
42979
43626
  const allOptions = [.../* @__PURE__ */ new Set([...optionNames, ...extraOptions])];
@@ -43388,9 +44035,9 @@ function buildInboxesTable(inboxes) {
43388
44035
  address: sanitizeDisplay(inbox.address),
43389
44036
  label: sanitizeDisplay(inbox.label)
43390
44037
  }));
43391
- const addressWidth = Math.max(7, ...cells.map((c59) => c59.address.length));
44038
+ const addressWidth = Math.max(7, ...cells.map((c62) => c62.address.length));
43392
44039
  const header = `${"ADDRESS".padEnd(addressWidth)} LABEL`;
43393
- const rows = cells.map((c59) => `${c59.address.padEnd(addressWidth)} ${c59.label}`);
44040
+ const rows = cells.map((c62) => `${c62.address.padEnd(addressWidth)} ${c62.label}`);
43394
44041
  return { header, rows };
43395
44042
  }
43396
44043
  function formatInboxQuota(quota) {
@@ -43414,14 +44061,14 @@ function buildInboxEmailsTable(messages) {
43414
44061
  subject: sanitizeDisplay(message.subject ?? ""),
43415
44062
  unread: message.unread ? "yes" : "no"
43416
44063
  }));
43417
- const idWidth = Math.max(2, ...cells.map((c59) => c59.id.length));
43418
- const receivedWidth = Math.max(8, ...cells.map((c59) => c59.received.length));
43419
- const fromWidth = Math.max(4, ...cells.map((c59) => c59.from.length));
43420
- const toWidth = Math.max(2, ...cells.map((c59) => c59.to.length));
43421
- const subjectWidth = Math.max(7, ...cells.map((c59) => c59.subject.length));
44064
+ const idWidth = Math.max(2, ...cells.map((c62) => c62.id.length));
44065
+ const receivedWidth = Math.max(8, ...cells.map((c62) => c62.received.length));
44066
+ const fromWidth = Math.max(4, ...cells.map((c62) => c62.from.length));
44067
+ const toWidth = Math.max(2, ...cells.map((c62) => c62.to.length));
44068
+ const subjectWidth = Math.max(7, ...cells.map((c62) => c62.subject.length));
43422
44069
  const header = `${"ID".padEnd(idWidth)} ${"RECEIVED".padEnd(receivedWidth)} ${"FROM".padEnd(fromWidth)} ${"TO".padEnd(toWidth)} ${"SUBJECT".padEnd(subjectWidth)} UNREAD`;
43423
44070
  const rows = cells.map(
43424
- (c59) => `${c59.id.padEnd(idWidth)} ${c59.received.padEnd(receivedWidth)} ${c59.from.padEnd(fromWidth)} ${c59.to.padEnd(toWidth)} ${c59.subject.padEnd(subjectWidth)} ${c59.unread}`
44071
+ (c62) => `${c62.id.padEnd(idWidth)} ${c62.received.padEnd(receivedWidth)} ${c62.from.padEnd(fromWidth)} ${c62.to.padEnd(toWidth)} ${c62.subject.padEnd(subjectWidth)} ${c62.unread}`
43425
44072
  );
43426
44073
  return { header, rows };
43427
44074
  }
@@ -43827,7 +44474,9 @@ var CREDENTIAL_TYPE_BY_INPUT = {
43827
44474
  "magic-auth": "MAGIC_AUTH",
43828
44475
  magic_auth: "MAGIC_AUTH",
43829
44476
  "session-state": "SESSION_STATE",
43830
- session_state: "SESSION_STATE"
44477
+ session_state: "SESSION_STATE",
44478
+ "ssh-private-key": "SSH_PRIVATE_KEY",
44479
+ ssh_private_key: "SSH_PRIVATE_KEY"
43831
44480
  };
43832
44481
  function normalizeCredentialType(input) {
43833
44482
  return CREDENTIAL_TYPE_BY_INPUT[input.trim().toLowerCase()] ?? null;
@@ -43849,7 +44498,11 @@ var AUTH_FLAG_KEBAB = {
43849
44498
  passwordField: "password-field",
43850
44499
  sessionCookieName: "session-cookie-name",
43851
44500
  csrfTokenSelector: "csrf-token-selector",
43852
- csrfFieldName: "csrf-field-name"
44501
+ csrfFieldName: "csrf-field-name",
44502
+ sshHost: "ssh-host",
44503
+ sshPort: "ssh-port",
44504
+ sshUsername: "ssh-username",
44505
+ sshHostKeyFingerprint: "ssh-host-key-fingerprint"
43853
44506
  };
43854
44507
  var ALL_AUTH_KEYS = Object.keys(AUTH_FLAG_KEBAB);
43855
44508
  var ALLOWED_AUTH_KEYS = {
@@ -43869,7 +44522,8 @@ var ALLOWED_AUTH_KEYS = {
43869
44522
  COOKIE: ["cookieName", "cookieDomain", "cookiePath"],
43870
44523
  MAGIC_AUTH: [],
43871
44524
  // SESSION_STATE takes no authConfig flags — origin is server-derived from the blob.
43872
- SESSION_STATE: []
44525
+ SESSION_STATE: [],
44526
+ SSH_PRIVATE_KEY: ["sshHost", "sshPort", "sshUsername", "sshHostKeyFingerprint"]
43873
44527
  };
43874
44528
  function rejectForeignAuthFlags(type, opts) {
43875
44529
  const allowed = new Set(ALLOWED_AUTH_KEYS[type]);
@@ -43913,6 +44567,13 @@ function authConfigToOptions(authConfig) {
43913
44567
  return {};
43914
44568
  case "SESSION_STATE":
43915
44569
  return {};
44570
+ case "SSH_PRIVATE_KEY":
44571
+ return {
44572
+ sshHost: authConfig.host,
44573
+ sshPort: String(authConfig.port),
44574
+ sshUsername: authConfig.username,
44575
+ ...authConfig.hostKeyFingerprint !== void 0 ? { sshHostKeyFingerprint: authConfig.hostKeyFingerprint } : {}
44576
+ };
43916
44577
  case "USERNAME_PASSWORD":
43917
44578
  return {
43918
44579
  method: authConfig.method,
@@ -44006,6 +44667,34 @@ function buildUsernamePasswordAuthConfig(opts) {
44006
44667
  }
44007
44668
  };
44008
44669
  }
44670
+ function buildSshPrivateKeyAuthConfig(opts) {
44671
+ const hasAny = opts.sshHost !== void 0 || opts.sshPort !== void 0 || opts.sshUsername !== void 0 || opts.sshHostKeyFingerprint !== void 0;
44672
+ if (!hasAny) {
44673
+ return { value: void 0 };
44674
+ }
44675
+ if (opts.sshHost === void 0 || opts.sshUsername === void 0) {
44676
+ return { error: "--ssh-host and --ssh-username are required for ssh-private-key." };
44677
+ }
44678
+ let port = SSH_DEFAULT_PORT;
44679
+ if (opts.sshPort !== void 0) {
44680
+ if (!/^\d{1,5}$/.test(opts.sshPort.trim())) {
44681
+ return { error: "--ssh-port must be a whole number from 1 to 65535." };
44682
+ }
44683
+ port = Number(opts.sshPort.trim());
44684
+ if (port < 1 || port > 65535) {
44685
+ return { error: "--ssh-port must be a whole number from 1 to 65535." };
44686
+ }
44687
+ }
44688
+ return {
44689
+ value: {
44690
+ type: "SSH_PRIVATE_KEY",
44691
+ host: opts.sshHost,
44692
+ port,
44693
+ username: opts.sshUsername,
44694
+ ...opts.sshHostKeyFingerprint !== void 0 ? { hostKeyFingerprint: opts.sshHostKeyFingerprint } : {}
44695
+ }
44696
+ };
44697
+ }
44009
44698
  function buildAuthConfig(type, opts) {
44010
44699
  switch (type) {
44011
44700
  case "BEARER_TOKEN":
@@ -44020,6 +44709,8 @@ function buildAuthConfig(type, opts) {
44020
44709
  return buildMagicAuthAuthConfig(opts);
44021
44710
  case "USERNAME_PASSWORD":
44022
44711
  return buildUsernamePasswordAuthConfig(opts);
44712
+ case "SSH_PRIVATE_KEY":
44713
+ return buildSshPrivateKeyAuthConfig(opts);
44023
44714
  case "PASSPHRASE":
44024
44715
  case "SESSION_STATE":
44025
44716
  return { value: void 0 };
@@ -44028,6 +44719,9 @@ function buildAuthConfig(type, opts) {
44028
44719
  function anySecretFlag(opts) {
44029
44720
  return opts.secretStdin === true || opts.secretFile !== void 0 || opts.secretEnv !== void 0 || opts.usernameStdin === true || opts.usernameFile !== void 0 || opts.usernameEnv !== void 0 || opts.passwordStdin === true || opts.passwordFile !== void 0 || opts.passwordEnv !== void 0;
44030
44721
  }
44722
+ function anyPasswordFlag(opts) {
44723
+ return opts.passwordStdin === true || opts.passwordFile !== void 0 || opts.passwordEnv !== void 0;
44724
+ }
44031
44725
  function secretOpts(opts) {
44032
44726
  return { stdin: opts.secretStdin, file: opts.secretFile, env: opts.secretEnv };
44033
44727
  }
@@ -44149,16 +44843,16 @@ function printCredentialsTsv(creds) {
44149
44843
  }
44150
44844
  }
44151
44845
  function buildCredentialsTable(creds) {
44152
- const cells = creds.map((c59) => ({
44153
- id: c59.credentialId,
44154
- label: sanitizeDisplay(c59.label),
44155
- type: c59.credentialType
44846
+ const cells = creds.map((c62) => ({
44847
+ id: c62.credentialId,
44848
+ label: sanitizeDisplay(c62.label),
44849
+ type: c62.credentialType
44156
44850
  }));
44157
- const idWidth = Math.max(2, ...cells.map((c59) => c59.id.length));
44158
- const labelWidth = Math.max(5, ...cells.map((c59) => c59.label.length));
44851
+ const idWidth = Math.max(2, ...cells.map((c62) => c62.id.length));
44852
+ const labelWidth = Math.max(5, ...cells.map((c62) => c62.label.length));
44159
44853
  const header = `${"ID".padEnd(idWidth)} ${"LABEL".padEnd(labelWidth)} TYPE`;
44160
44854
  const rows = cells.map(
44161
- (c59) => `${c59.id.padEnd(idWidth)} ${c59.label.padEnd(labelWidth)} ${c59.type}`
44855
+ (c62) => `${c62.id.padEnd(idWidth)} ${c62.label.padEnd(labelWidth)} ${c62.type}`
44162
44856
  );
44163
44857
  return { header, rows };
44164
44858
  }
@@ -44189,6 +44883,14 @@ function failAuthBuild(result) {
44189
44883
  console.error(colorError(result.error));
44190
44884
  process.exit(1);
44191
44885
  }
44886
+ function requireSshAuthConfig(options) {
44887
+ const ac = buildSshPrivateKeyAuthConfig(options);
44888
+ if ("error" in ac) failAuthBuild(ac);
44889
+ if (ac.value === void 0) {
44890
+ failAuthBuild({ error: "--ssh-host and --ssh-username are required for ssh-private-key." });
44891
+ }
44892
+ return ac.value;
44893
+ }
44192
44894
  async function runCreate3(options) {
44193
44895
  const output = parseOutput(options.output);
44194
44896
  requireEnvId(options.env);
@@ -44212,6 +44914,17 @@ async function runCreate3(options) {
44212
44914
  );
44213
44915
  process.exit(1);
44214
44916
  }
44917
+ if (type === "SSH_PRIVATE_KEY") {
44918
+ requireSshAuthConfig(options);
44919
+ if (usesStdin(secretOpts(options)) && usesStdin(passwordOpts(options))) {
44920
+ console.error(
44921
+ colorError(
44922
+ "Only one secret can come from stdin. Use --password-file/-env for the passphrase."
44923
+ )
44924
+ );
44925
+ process.exit(1);
44926
+ }
44927
+ }
44215
44928
  const organizationId = await resolveOrganizationId();
44216
44929
  const params = { organizationId, environmentId: options.env };
44217
44930
  const label = options.label?.trim() ? options.label.trim() : void 0;
@@ -44284,6 +44997,18 @@ async function buildCreateBody(type, label, options) {
44284
44997
  );
44285
44998
  return { credentialType: "SESSION_STATE", ...labelFields, secretValue };
44286
44999
  }
45000
+ case "SSH_PRIVATE_KEY": {
45001
+ const authConfig = requireSshAuthConfig(options);
45002
+ const privateKeyPem = await resolveSecret("private key PEM", "secret", secretOpts(options));
45003
+ const passphrase = anyPasswordFlag(options) ? await resolveSecret("key passphrase", "password", passwordOpts(options)) : void 0;
45004
+ return {
45005
+ credentialType: "SSH_PRIVATE_KEY",
45006
+ ...labelFields,
45007
+ privateKeyPem,
45008
+ ...passphrase !== void 0 ? { passphrase } : {},
45009
+ authConfig
45010
+ };
45011
+ }
44287
45012
  case "COOKIE": {
44288
45013
  const ac = buildCookieAuthConfig(options);
44289
45014
  if ("error" in ac) failAuthBuild(ac);
@@ -44566,12 +45291,15 @@ function addSecretInputOptions(command) {
44566
45291
  return command.option("--secret-stdin", "Read the secret value from stdin").option("--secret-file <path>", "Read the secret value from a file").option("--secret-env <name>", "Read the secret value from an env var").option("--username-stdin", "Read the username from stdin").option("--username-file <path>", "Read the username from a file").option("--username-env <name>", "Read the username from an env var").option("--password-stdin", "Read the password from stdin").option("--password-file <path>", "Read the password from a file").option("--password-env <name>", "Read the password from an env var");
44567
45292
  }
44568
45293
  function addAuthConfigOptions(command) {
44569
- return command.option("--header-name <name>", "authConfig: header name (bearer-token/jwt-token)").option("--token-prefix <prefix>", "authConfig: token prefix (bearer-token/jwt-token)").option("--placement <header|query>", "authConfig: API key placement (api-key)").option("--key-name <name>", "authConfig: API key name (api-key)").option("--cookie-name <name>", "authConfig: cookie name (cookie)").option("--cookie-domain <domain>", "authConfig: cookie domain (cookie)").option("--cookie-path <path>", "authConfig: cookie path (cookie)").option("--method <form|basic|json>", "authConfig: login method (username-password)").option("--login-url <url>", "authConfig: login URL (username-password)").option("--username-field <name>", "authConfig: username field (username-password)").option("--password-field <name>", "authConfig: password field (username-password)").option("--session-cookie-name <name>", "authConfig: session cookie (username-password)").option("--csrf-token-selector <sel>", "authConfig: CSRF token selector (username-password)").option("--csrf-field-name <name>", "authConfig: CSRF field name (username-password)");
45294
+ return command.option("--header-name <name>", "authConfig: header name (bearer-token/jwt-token)").option("--token-prefix <prefix>", "authConfig: token prefix (bearer-token/jwt-token)").option("--placement <header|query>", "authConfig: API key placement (api-key)").option("--key-name <name>", "authConfig: API key name (api-key)").option("--cookie-name <name>", "authConfig: cookie name (cookie)").option("--cookie-domain <domain>", "authConfig: cookie domain (cookie)").option("--cookie-path <path>", "authConfig: cookie path (cookie)").option("--method <form|basic|json>", "authConfig: login method (username-password)").option("--login-url <url>", "authConfig: login URL (username-password)").option("--username-field <name>", "authConfig: username field (username-password)").option("--password-field <name>", "authConfig: password field (username-password)").option("--session-cookie-name <name>", "authConfig: session cookie (username-password)").option("--csrf-token-selector <sel>", "authConfig: CSRF token selector (username-password)").option("--csrf-field-name <name>", "authConfig: CSRF field name (username-password)").option("--ssh-host <host>", "authConfig: SSH host (ssh-private-key, required)").option("--ssh-port <port>", "authConfig: SSH port, 1-65535 (ssh-private-key, default 22)").option("--ssh-username <user>", "authConfig: SSH username (ssh-private-key, required)").option(
45295
+ "--ssh-host-key-fingerprint <fp>",
45296
+ "authConfig: expected host key, SHA256:<base64> (ssh-private-key)"
45297
+ );
44570
45298
  }
44571
45299
  function createCreateCommand3() {
44572
45300
  const command = new Command("create").description("Create a credential in an environment").requiredOption("--env <environmentId>", "Parent environment UUID").requiredOption(
44573
45301
  "--type <type>",
44574
- "bearer-token, jwt-token, api-key, passphrase, username-password, cookie, magic-auth"
45302
+ "bearer-token, jwt-token, api-key, passphrase, username-password, cookie, magic-auth, session-state, ssh-private-key"
44575
45303
  ).option(
44576
45304
  "--label <label>",
44577
45305
  "Human-readable label (optional; defaults from type / username + environment)"
@@ -44588,7 +45316,9 @@ Examples:
44588
45316
  tracecli credential create --env <envId> --type api-key --label "prod key" --secret-env API_KEY --placement header --key-name X-API-Key
44589
45317
  tracecli credential create --env <envId> --type username-password --username-env U --password-env P --method form --login-url https://acme.com/login
44590
45318
  ADDRESS=$(tracecli inbox create portal -o json | jq -r .address)
44591
- echo "$ADDRESS" | tracecli credential create --env <envId> --type magic-auth --secret-stdin`
45319
+ echo "$ADDRESS" | tracecli credential create --env <envId> --type magic-auth --secret-stdin
45320
+ tracecli credential create --env <envId> --type ssh-private-key --secret-file ~/.ssh/bastion_ed25519 --ssh-host bastion.example.com --ssh-username deploy
45321
+ tracecli credential create --env <envId> --type ssh-private-key --secret-file ./key.pem --password-env KEY_PASSPHRASE --ssh-host 10.0.0.5 --ssh-port 2222 --ssh-username ops`
44592
45322
  );
44593
45323
  return command.action(async (options) => {
44594
45324
  await runCreate3(options);
@@ -44886,14 +45616,14 @@ function buildEnvironmentTable(envs, leading) {
44886
45616
  url: e.primaryUrl != null ? sanitizeDisplay(e.primaryUrl) : "\u2014"
44887
45617
  }));
44888
45618
  const leadingWidths = leading.map(
44889
- (column, i2) => Math.max(column.header.length, ...cells.map((c59) => c59.leading[i2]?.length ?? 0))
45619
+ (column, i2) => Math.max(column.header.length, ...cells.map((c62) => c62.leading[i2]?.length ?? 0))
44890
45620
  );
44891
- const nameWidth = Math.max(4, ...cells.map((c59) => c59.name.length));
45621
+ const nameWidth = Math.max(4, ...cells.map((c62) => c62.name.length));
44892
45622
  const padLeading = (values) => values.map((v, i2) => v.padEnd(leadingWidths[i2] ?? 0)).join(" ");
44893
45623
  return {
44894
- header: `${padLeading(leading.map((c59) => c59.header))} ${"NAME".padEnd(nameWidth)} PROD URL`,
45624
+ header: `${padLeading(leading.map((c62) => c62.header))} ${"NAME".padEnd(nameWidth)} PROD URL`,
44895
45625
  rows: cells.map(
44896
- (c59) => `${padLeading(c59.leading)} ${c59.name.padEnd(nameWidth)} ${c59.prod.padEnd(4)} ${c59.url}`
45626
+ (c62) => `${padLeading(c62.leading)} ${c62.name.padEnd(nameWidth)} ${c62.prod.padEnd(4)} ${c62.url}`
44897
45627
  )
44898
45628
  };
44899
45629
  }
@@ -47291,6 +48021,11 @@ Examples:
47291
48021
  });
47292
48022
  }
47293
48023
 
48024
+ // src/upload/download.ts
48025
+ import { createHash as createHash2 } from "crypto";
48026
+ import { open as open2, rename, rm, stat } from "fs/promises";
48027
+ import { basename as basename4, join as join7, resolve } from "path";
48028
+
47294
48029
  // src/upload/prepare.ts
47295
48030
  import { createHash } from "crypto";
47296
48031
  import { createReadStream, statSync as statSync4 } from "fs";
@@ -47434,6 +48169,128 @@ async function prepareUploadFile(filePath, options = {}) {
47434
48169
  };
47435
48170
  }
47436
48171
 
48172
+ // src/upload/download.ts
48173
+ var CodeUploadDownloadError = class extends Error {
48174
+ constructor(message) {
48175
+ super(message);
48176
+ this.name = "CodeUploadDownloadError";
48177
+ }
48178
+ };
48179
+ function safeDownloadFilename(upload) {
48180
+ const extension = requiredExtensionForKind(upload.artifactKind);
48181
+ const fallback = `app${extension}`;
48182
+ const stripped = Array.from(basename4(upload.originalFilename.replace(/\\/g, "/")), (char) => {
48183
+ const code = char.codePointAt(0) ?? 0;
48184
+ return code <= 31 || code === 127 ? "_" : char;
48185
+ }).join("").trim();
48186
+ if (stripped.length === 0 || stripped === "." || stripped === "..") return fallback;
48187
+ return stripped.toLowerCase().endsWith(extension) ? stripped : `${stripped}${extension}`;
48188
+ }
48189
+ async function resolveDownloadDestination(upload, outOption) {
48190
+ const filename = safeDownloadFilename(upload);
48191
+ if (outOption === void 0) return resolve(process.cwd(), filename);
48192
+ const target = resolve(outOption);
48193
+ try {
48194
+ const info = await stat(target);
48195
+ if (info.isDirectory()) return join7(target, filename);
48196
+ } catch {
48197
+ }
48198
+ return target;
48199
+ }
48200
+ async function pathExists(path) {
48201
+ try {
48202
+ await stat(path);
48203
+ return true;
48204
+ } catch {
48205
+ return false;
48206
+ }
48207
+ }
48208
+ async function downloadCodeUploadToFile(upload, downloadUrl, destination, options = {}) {
48209
+ const fetchImpl = options.fetch ?? ((input, init) => globalThis.fetch(input, init));
48210
+ const partialPath = `${destination}.part`;
48211
+ let response;
48212
+ try {
48213
+ response = await fetchImpl(downloadUrl, { method: "GET", redirect: "manual" });
48214
+ } catch (error48) {
48215
+ const detail = error48 instanceof Error ? error48.message : "network error";
48216
+ throw new CodeUploadDownloadError(`Download from storage failed: ${detail}.`);
48217
+ }
48218
+ if (!response.ok || response.body === null) {
48219
+ if (response.body !== null) await response.body.cancel();
48220
+ throw new CodeUploadDownloadError(
48221
+ `Storage answered HTTP ${response.status} instead of the build bytes. Retry; the download link may have expired.`
48222
+ );
48223
+ }
48224
+ const contentLength = response.headers.get("content-length");
48225
+ if (contentLength !== null) {
48226
+ const declared = Number(contentLength);
48227
+ if (!Number.isSafeInteger(declared) || declared !== upload.sizeBytes) {
48228
+ await response.body.cancel();
48229
+ throw new CodeUploadDownloadError(
48230
+ `Storage declared ${contentLength} bytes; the upload record says ${upload.sizeBytes}.`
48231
+ );
48232
+ }
48233
+ }
48234
+ await rm(partialPath, { force: true });
48235
+ const file2 = await open2(partialPath, "wx");
48236
+ const hash2 = createHash2("sha256");
48237
+ const reader = response.body.getReader();
48238
+ let downloadedBytes = 0;
48239
+ options.onProgress?.(progress(0, upload.sizeBytes));
48240
+ try {
48241
+ for (; ; ) {
48242
+ const chunk = await reader.read();
48243
+ if (chunk.done) break;
48244
+ downloadedBytes += chunk.value.byteLength;
48245
+ if (downloadedBytes > upload.sizeBytes) {
48246
+ await reader.cancel();
48247
+ throw new CodeUploadDownloadError(
48248
+ `Storage sent more than the ${upload.sizeBytes} bytes on record.`
48249
+ );
48250
+ }
48251
+ hash2.update(chunk.value);
48252
+ await writeAll(file2, chunk.value);
48253
+ options.onProgress?.(progress(downloadedBytes, upload.sizeBytes));
48254
+ }
48255
+ await file2.close();
48256
+ if (downloadedBytes !== upload.sizeBytes) {
48257
+ throw new CodeUploadDownloadError(
48258
+ `Download ended after ${downloadedBytes} of ${upload.sizeBytes} bytes.`
48259
+ );
48260
+ }
48261
+ const digest = hash2.digest("hex");
48262
+ if (digest !== upload.archiveSha256) {
48263
+ throw new CodeUploadDownloadError(
48264
+ "Downloaded bytes do not match the SHA-256 recorded at upload time."
48265
+ );
48266
+ }
48267
+ await rename(partialPath, destination);
48268
+ } catch (error48) {
48269
+ await file2.close().catch(() => void 0);
48270
+ await rm(partialPath, { force: true });
48271
+ if (error48 instanceof CodeUploadDownloadError) throw error48;
48272
+ const detail = error48 instanceof Error ? error48.message : "unknown error";
48273
+ throw new CodeUploadDownloadError(`Download from storage failed: ${detail}.`);
48274
+ }
48275
+ }
48276
+ function progress(downloadedBytes, totalBytes) {
48277
+ return {
48278
+ downloadedBytes,
48279
+ totalBytes,
48280
+ fraction: totalBytes === 0 ? 1 : downloadedBytes / totalBytes
48281
+ };
48282
+ }
48283
+ async function writeAll(file2, bytes) {
48284
+ let offset = 0;
48285
+ while (offset < bytes.byteLength) {
48286
+ const { bytesWritten } = await file2.write(bytes, offset, bytes.byteLength - offset);
48287
+ if (bytesWritten === 0) {
48288
+ throw new CodeUploadDownloadError("Writing the build to disk stalled.");
48289
+ }
48290
+ offset += bytesWritten;
48291
+ }
48292
+ }
48293
+
47437
48294
  // src/upload/multipart.ts
47438
48295
  var MAX_CONCURRENT_UPLOADS = 3;
47439
48296
  var MAX_PART_ATTEMPTS = 3;
@@ -47446,7 +48303,7 @@ async function uploadCodeArchiveParts(filePath, totalBytes, partSizeBytes, parts
47446
48303
  if (!Number.isSafeInteger(uploadedBytes) || uploadedBytes < 0 || uploadedBytes > totalBytes) {
47447
48304
  throw new Error("Invalid multipart progress: uploaded byte count is outside the archive.");
47448
48305
  }
47449
- options.onProgress?.(progress(uploadedBytes, totalBytes));
48306
+ options.onProgress?.(progress2(uploadedBytes, totalBytes));
47450
48307
  if (parts.length === 0) return;
47451
48308
  const urls = new Map(parts.map((part) => [part.partNumber, part.uploadUrl]));
47452
48309
  const refreshUrls = createUrlRefresher(urls, options.refreshParts);
@@ -47463,7 +48320,7 @@ async function uploadCodeArchiveParts(filePath, totalBytes, partSizeBytes, parts
47463
48320
  const body = await readFileSlice(filePath, start, end);
47464
48321
  await uploadPartWithRetry(part.partNumber, urls, body, fetchPart, wait, refreshUrls);
47465
48322
  uploadedBytes += body.byteLength;
47466
- options.onProgress?.(progress(uploadedBytes, totalBytes));
48323
+ options.onProgress?.(progress2(uploadedBytes, totalBytes));
47467
48324
  }
47468
48325
  })
47469
48326
  );
@@ -47551,7 +48408,7 @@ function assertPresignedUploadUrl(value) {
47551
48408
  throw new Error("Invalid multipart upload: part URLs must use HTTPS.");
47552
48409
  }
47553
48410
  }
47554
- function progress(uploadedBytes, totalBytes) {
48411
+ function progress2(uploadedBytes, totalBytes) {
47555
48412
  return {
47556
48413
  uploadedBytes,
47557
48414
  totalBytes,
@@ -47559,13 +48416,13 @@ function progress(uploadedBytes, totalBytes) {
47559
48416
  };
47560
48417
  }
47561
48418
  function defaultWait(milliseconds) {
47562
- return new Promise((resolve) => setTimeout(resolve, milliseconds));
48419
+ return new Promise((resolve2) => setTimeout(resolve2, milliseconds));
47563
48420
  }
47564
48421
 
47565
48422
  // src/upload/commands.ts
47566
48423
  var TERMINAL_STATUSES = /* @__PURE__ */ new Set(["READY", "REJECTED", "FAILED", "DELETING"]);
47567
48424
  var DEFAULT_RUNTIME = {
47568
- wait: (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)),
48425
+ wait: (milliseconds) => new Promise((resolve2) => setTimeout(resolve2, milliseconds)),
47569
48426
  pollIntervalMs: 3e3,
47570
48427
  fetch: (input, init) => globalThis.fetch(input, init)
47571
48428
  };
@@ -47678,6 +48535,53 @@ function failReadOp5(action, status, body) {
47678
48535
  );
47679
48536
  process.exit(1);
47680
48537
  }
48538
+ function failDownloadOp(codeUploadId, status, body) {
48539
+ const { message, errorCode } = readErrorFields(body);
48540
+ if (status === UNAUTHORIZED_STATUS2) {
48541
+ exitAuthFailed();
48542
+ }
48543
+ if (status === 403) {
48544
+ console.error(
48545
+ colorError(
48546
+ "Only an organization admin (or Trace staff) on a pentest-enabled organization can download builds."
48547
+ )
48548
+ );
48549
+ process.exit(1);
48550
+ }
48551
+ if (status === 404) {
48552
+ console.error(colorError(message ?? "No code upload with that id."));
48553
+ process.exit(1);
48554
+ }
48555
+ if (status === 409) {
48556
+ if (errorCode === "CODE_UPLOAD_NOT_BUILD") {
48557
+ console.error(
48558
+ colorError(
48559
+ message ?? "Only APK and IPA builds can be downloaded. Source archives stay on the server."
48560
+ )
48561
+ );
48562
+ } else if (errorCode === "CODE_UPLOAD_NOT_READY") {
48563
+ console.error(
48564
+ colorError(
48565
+ message ?? "The build must be ready and pass malware scanning before it can be downloaded."
48566
+ )
48567
+ );
48568
+ console.error(`Wait for it with: tracecli upload wait ${codeUploadId}`);
48569
+ } else {
48570
+ console.error(colorError(message ?? "Conflict (status 409)."));
48571
+ }
48572
+ process.exit(1);
48573
+ }
48574
+ if (status === 503) {
48575
+ console.error(
48576
+ colorError(message ?? "Source archive uploads are not configured on this server.")
48577
+ );
48578
+ process.exit(1);
48579
+ }
48580
+ console.error(
48581
+ colorError(`Failed to download build (status ${status})${message ? `: ${message}` : "."}`)
48582
+ );
48583
+ process.exit(1);
48584
+ }
47681
48585
  function resumeHint(codeUploadId, filePath) {
47682
48586
  return `Resume with: tracecli upload resume ${codeUploadId} ${filePath}`;
47683
48587
  }
@@ -47689,13 +48593,13 @@ function buildUploadsTable(uploads) {
47689
48593
  size: formatArchiveBytes(upload.sizeBytes),
47690
48594
  id: upload.codeUploadId
47691
48595
  }));
47692
- const nameWidth = Math.max(4, ...cells.map((c59) => c59.name.length));
47693
- const kindWidth = Math.max(4, ...cells.map((c59) => c59.kind.length));
47694
- const statusWidth = Math.max(6, ...cells.map((c59) => c59.status.length));
47695
- const sizeWidth = Math.max(4, ...cells.map((c59) => c59.size.length));
48596
+ const nameWidth = Math.max(4, ...cells.map((c62) => c62.name.length));
48597
+ const kindWidth = Math.max(4, ...cells.map((c62) => c62.kind.length));
48598
+ const statusWidth = Math.max(6, ...cells.map((c62) => c62.status.length));
48599
+ const sizeWidth = Math.max(4, ...cells.map((c62) => c62.size.length));
47696
48600
  const header = `${"NAME".padEnd(nameWidth)} ${"KIND".padEnd(kindWidth)} ${"STATUS".padEnd(statusWidth)} ${"SIZE".padEnd(sizeWidth)} ID`;
47697
48601
  const rows = cells.map(
47698
- (c59) => `${c59.name.padEnd(nameWidth)} ${c59.kind.padEnd(kindWidth)} ${c59.status.padEnd(statusWidth)} ${c59.size.padEnd(sizeWidth)} ${c59.id}`
48602
+ (c62) => `${c62.name.padEnd(nameWidth)} ${c62.kind.padEnd(kindWidth)} ${c62.status.padEnd(statusWidth)} ${c62.size.padEnd(sizeWidth)} ${c62.id}`
47699
48603
  );
47700
48604
  return { header, rows };
47701
48605
  }
@@ -47767,11 +48671,18 @@ function outputUpload(upload, output) {
47767
48671
  }
47768
48672
  console.log(renderUploadDetail(upload));
47769
48673
  }
47770
- function writeProgress(progress2) {
48674
+ function writeProgress(progress3) {
48675
+ if (!process.stderr.isTTY) return;
48676
+ const pct = Math.floor(progress3.fraction * 100);
48677
+ process.stderr.write(
48678
+ `\rUploading ${pct}% (${formatArchiveBytes(progress3.uploadedBytes)} / ${formatArchiveBytes(progress3.totalBytes)})`
48679
+ );
48680
+ }
48681
+ function writeDownloadProgress(progress3) {
47771
48682
  if (!process.stderr.isTTY) return;
47772
- const pct = Math.floor(progress2.fraction * 100);
48683
+ const pct = Math.floor(progress3.fraction * 100);
47773
48684
  process.stderr.write(
47774
- `\rUploading ${pct}% (${formatArchiveBytes(progress2.uploadedBytes)} / ${formatArchiveBytes(progress2.totalBytes)})`
48685
+ `\rDownloading ${pct}% (${formatArchiveBytes(progress3.downloadedBytes)} / ${formatArchiveBytes(progress3.totalBytes)})`
47775
48686
  );
47776
48687
  }
47777
48688
  function finishProgress() {
@@ -48085,6 +48996,76 @@ async function runGet4(codeUploadIdArg, options) {
48085
48996
  const upload = await requireUpload(organizationId, codeUploadId);
48086
48997
  outputUpload(upload, output);
48087
48998
  }
48999
+ async function runDownload(codeUploadIdArg, options, runtime) {
49000
+ const output = parseOutput(options.output);
49001
+ const codeUploadId = requireUuid(codeUploadIdArg, "code upload id");
49002
+ const organizationId = await resolveOrganizationId();
49003
+ const result = await authenticatedRequest(
49004
+ (client) => client.codeUpload.downloadCodeUpload({
49005
+ params: { organizationId, codeUploadId }
49006
+ })
49007
+ );
49008
+ if (result.status !== SUCCESS_STATUS) {
49009
+ failDownloadOp(codeUploadId, result.status, result.body);
49010
+ }
49011
+ const { upload, downloadUrl } = result.body;
49012
+ const destination = await resolveDownloadDestination(upload, options.out);
49013
+ if (!options.force && await pathExists(destination)) {
49014
+ console.error(
49015
+ colorError(`${destination} already exists. Pass --force to overwrite it or --out <path>.`)
49016
+ );
49017
+ process.exit(1);
49018
+ }
49019
+ if (output === "table") {
49020
+ console.error(
49021
+ `Downloading ${sanitizeDisplay(upload.displayName)} (${formatArchiveBytes(upload.sizeBytes)}) to ${destination}`
49022
+ );
49023
+ }
49024
+ try {
49025
+ await downloadCodeUploadToFile(upload, downloadUrl, destination, {
49026
+ fetch: runtime.fetch,
49027
+ onProgress: writeDownloadProgress
49028
+ });
49029
+ finishProgress();
49030
+ } catch (error48) {
49031
+ finishProgress();
49032
+ const detail = error48 instanceof Error ? error48.message : "Download from storage failed.";
49033
+ console.error(colorError(detail));
49034
+ process.exit(1);
49035
+ }
49036
+ if (output === "json") {
49037
+ console.log(
49038
+ JSON.stringify(
49039
+ {
49040
+ upload,
49041
+ path: destination,
49042
+ sizeBytes: upload.sizeBytes,
49043
+ archiveSha256: upload.archiveSha256
49044
+ },
49045
+ null,
49046
+ 2
49047
+ )
49048
+ );
49049
+ return;
49050
+ }
49051
+ if (output === "tsv") {
49052
+ console.log(["codeUploadId", "path", "sizeBytes", "archiveSha256"].join(" "));
49053
+ console.log(
49054
+ [
49055
+ tsvSafe(upload.codeUploadId),
49056
+ tsvSafe(destination),
49057
+ String(upload.sizeBytes),
49058
+ tsvSafe(upload.archiveSha256)
49059
+ ].join(" ")
49060
+ );
49061
+ return;
49062
+ }
49063
+ console.log(
49064
+ colorSuccess(
49065
+ `Saved ${sanitizeDisplay(upload.displayName)} to ${destination} (${formatArchiveBytes(upload.sizeBytes)}, SHA-256 verified).`
49066
+ )
49067
+ );
49068
+ }
48088
49069
  async function runWait(codeUploadIdArg, options, runtime) {
48089
49070
  const output = parseOutput(options.output);
48090
49071
  const codeUploadId = requireUuid(codeUploadIdArg, "code upload id");
@@ -48209,6 +49190,23 @@ Examples:
48209
49190
  await runResume(codeUploadId, file2, options, runtime);
48210
49191
  });
48211
49192
  }
49193
+ function createDownloadCommand(runtime) {
49194
+ return new Command("download").description("Download an APK or IPA build that finished malware scanning").argument("<codeUploadId>", "Upload UUID of the build").option("--out <path>", "File to write, or an existing directory (default: working directory)").option("-f, --force", "Overwrite an existing file").option("-o, --output <format>", "Output format: table, json, tsv (default: table)", "table").addHelpText(
49195
+ "after",
49196
+ `
49197
+ Only READY builds with a clean malware scan can be downloaded. Source ZIPs
49198
+ are not downloadable. The file is written under the name it was uploaded
49199
+ with and its size and SHA-256 are checked against the upload record before
49200
+ it is moved into place.
49201
+
49202
+ Examples:
49203
+ tracecli upload download <codeUploadId>
49204
+ tracecli upload download <codeUploadId> --out ./builds/
49205
+ tracecli upload download <codeUploadId> --out ./release.apk --force`
49206
+ ).action(async (codeUploadId, options) => {
49207
+ await runDownload(codeUploadId, options, runtime);
49208
+ });
49209
+ }
48212
49210
  function createWaitCommand(runtime) {
48213
49211
  return new Command("wait").description("Wait until an upload finishes malware scanning and validation").argument("<codeUploadId>", "Upload UUID").option("-o, --output <format>", "Output format: table, json, tsv (default: table)", "table").action(async (codeUploadId, options) => {
48214
49212
  await runWait(codeUploadId, options, runtime);
@@ -48234,11 +49232,12 @@ Examples:
48234
49232
  }
48235
49233
  function createUploadCommand(runtime = {}) {
48236
49234
  const resolved = resolveRuntime(runtime);
48237
- const upload = new Command("upload").alias("code-upload").description("Upload source archives (and separately, mobile builds) for pentest");
49235
+ const upload = new Command("upload").alias("code-upload").description("Upload source archives and mobile builds for pentest; download builds back");
48238
49236
  upload.addCommand(createCreateCommand5(resolved));
48239
49237
  upload.addCommand(createBuildCommand(resolved));
48240
49238
  upload.addCommand(createListCommand7());
48241
49239
  upload.addCommand(createGetCommand4());
49240
+ upload.addCommand(createDownloadCommand(resolved));
48242
49241
  upload.addCommand(createResumeCommand(resolved));
48243
49242
  upload.addCommand(createWaitCommand(resolved));
48244
49243
  upload.addCommand(createDeleteCommand5());