securewithtrace 0.50.0 → 0.52.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 +299 -63
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -17612,14 +17612,16 @@ var SEGMENT = new RegExp(`[A-Za-z0-9_+-]{${MIN_RUN},}`, "g");
17612
17612
 
17613
17613
  // ../../packages/core/src/agent-session/versions.ts
17614
17614
  var COORDINATOR_TOOLSET_VERSION = "coordinator-tools-v4";
17615
- var OPERATOR_REVIEW_PROMPT_VERSION = "operator-review-v7";
17616
- var OPERATOR_REVIEW_FALLBACK_PROMPT_VERSION = "operator-review-v8";
17615
+ var OPERATOR_REVIEW_PROMPT_VERSION = "operator-review-v9";
17616
+ var OPERATOR_REVIEW_FALLBACK_PROMPT_VERSION = "operator-review-v10";
17617
17617
  var OPERATOR_REVIEW_V1_PROMPT_VERSION = "operator-review-v1";
17618
17618
  var OPERATOR_REVIEW_V2_PROMPT_VERSION = "operator-review-v2";
17619
17619
  var OPERATOR_REVIEW_V3_PROMPT_VERSION = "operator-review-v3";
17620
17620
  var OPERATOR_REVIEW_V4_PROMPT_VERSION = "operator-review-v4";
17621
17621
  var OPERATOR_REVIEW_V5_PROMPT_VERSION = "operator-review-v5";
17622
17622
  var OPERATOR_REVIEW_V6_PROMPT_VERSION = "operator-review-v6";
17623
+ var OPERATOR_REVIEW_V7_PROMPT_VERSION = "operator-review-v7";
17624
+ var OPERATOR_REVIEW_V8_PROMPT_VERSION = "operator-review-v8";
17623
17625
  var OPERATOR_REVIEW_PROMPT_VERSIONS = [
17624
17626
  OPERATOR_REVIEW_V1_PROMPT_VERSION,
17625
17627
  OPERATOR_REVIEW_V2_PROMPT_VERSION,
@@ -17627,6 +17629,8 @@ var OPERATOR_REVIEW_PROMPT_VERSIONS = [
17627
17629
  OPERATOR_REVIEW_V4_PROMPT_VERSION,
17628
17630
  OPERATOR_REVIEW_V5_PROMPT_VERSION,
17629
17631
  OPERATOR_REVIEW_V6_PROMPT_VERSION,
17632
+ OPERATOR_REVIEW_V7_PROMPT_VERSION,
17633
+ OPERATOR_REVIEW_V8_PROMPT_VERSION,
17630
17634
  OPERATOR_REVIEW_PROMPT_VERSION,
17631
17635
  OPERATOR_REVIEW_FALLBACK_PROMPT_VERSION
17632
17636
  ];
@@ -18243,6 +18247,16 @@ var REVIEW_OOB_PROFILE_SUCCESSIONS = [
18243
18247
  toolsetVersion: "operator-tools-v10"
18244
18248
  }
18245
18249
  },
18250
+ {
18251
+ successor: {
18252
+ promptVersion: OPERATOR_REVIEW_V7_PROMPT_VERSION,
18253
+ toolsetVersion: "operator-tools-v12"
18254
+ },
18255
+ predecessor: {
18256
+ promptVersion: OPERATOR_REVIEW_V8_PROMPT_VERSION,
18257
+ toolsetVersion: "operator-tools-v10"
18258
+ }
18259
+ },
18246
18260
  {
18247
18261
  successor: {
18248
18262
  promptVersion: OPERATOR_REVIEW_PROMPT_VERSION,
@@ -23193,12 +23207,6 @@ var AgentSessionTypeValues = [
23193
23207
  "SETUP"
23194
23208
  ];
23195
23209
  var AgentSessionType = external_exports.enum(AgentSessionTypeValues);
23196
- var SetupTenantSchema = external_exports.object({
23197
- admin: external_exports.boolean(),
23198
- member: external_exports.boolean(),
23199
- apiKey: external_exports.boolean()
23200
- }).strict();
23201
- var MAX_SETUP_TENANTS = 4;
23202
23210
  var AgentSessionStatusValues = [
23203
23211
  "LAUNCHING",
23204
23212
  "RUNNING",
@@ -23251,6 +23259,7 @@ var MAX_AGENT_SESSION_MAX_USD = 1500;
23251
23259
  var MAX_AGENT_SESSION_MAX_WALL_CLOCK_MIN = 24 * 60;
23252
23260
  var MAX_AGENT_SESSION_FOCUS_CHARS = 8e3;
23253
23261
  var MAX_AGENT_SESSION_FOCUS_BRIEF_CHARS = MAX_AGENT_SESSION_FOCUS_CHARS * 3;
23262
+ var MAX_SETUP_NOTES_CHARS = 8e3;
23254
23263
  var OperatorSessionPurpose = external_exports.enum(OPERATOR_PURPOSES);
23255
23264
  var CreateAgentSessionCommonSchema = external_exports.object({
23256
23265
  provider: AgentSessionProvider.optional(),
@@ -23363,16 +23372,14 @@ var CreateAgentSessionRequestSchema = external_exports.discriminatedUnion("type"
23363
23372
  focus: external_exports.string().min(1).max(MAX_AGENT_SESSION_FOCUS_CHARS).optional()
23364
23373
  }),
23365
23374
  // SETUP: sets up a PoC engagement's application, environment, exclusions and tenant
23366
- // accounts. `focus` carries the staff notes (which environment, rate limits, what is
23367
- // off-limits); `tenants` the accounts to aim for; `shareLink` the customer's logins.
23375
+ // accounts from the setup notes. Strict, so a client still sending the removed `focus`,
23376
+ // `tenants` or `shareLink` gets a 400 instead of a launch that silently lost its input.
23368
23377
  CreateAgentSessionCommonSchema.extend({
23369
23378
  type: external_exports.literal("SETUP"),
23370
- focus: external_exports.string().min(1).max(MAX_AGENT_SESSION_FOCUS_CHARS).optional(),
23371
- /** Absent = the default target, two tenants of admin + member + API key. */
23372
- tenants: external_exports.array(SetupTenantSchema).min(1).max(MAX_SETUP_TENANTS).optional(),
23373
- /** A credential share link. Stored encrypted and never returned by any read route. */
23374
- shareLink: external_exports.url({ protocol: /^https$/ }).max(2048).optional()
23375
- })
23379
+ /** What the customer sent and what staff want: environments, rules, logins, share links.
23380
+ * Stored encrypted and never returned by any read route. */
23381
+ notes: external_exports.string().trim().min(1).max(MAX_SETUP_NOTES_CHARS).optional()
23382
+ }).strict()
23376
23383
  ]);
23377
23384
  var SendAgentSessionMessageRequestSchema = external_exports.object({
23378
23385
  clientRequestId: external_exports.uuid().optional(),
@@ -23389,6 +23396,10 @@ var ResumeAgentSessionRequestSchema = external_exports.object({
23389
23396
  * keeps the row's current target. */
23390
23397
  launchTarget: AgentSessionLaunchTarget.optional()
23391
23398
  });
23399
+ var SetAgentSessionBudgetRequestSchema = external_exports.object({
23400
+ /** The session's new lifetime ceiling, past spend included. Must exceed recorded spend. */
23401
+ maxUsd: external_exports.number().positive().max(MAX_AGENT_SESSION_MAX_USD)
23402
+ });
23392
23403
  var ClaimLocalLaunchResponseSchema = external_exports.object({
23393
23404
  agentSessionId: external_exports.uuid(),
23394
23405
  runAttemptId: external_exports.uuid(),
@@ -23551,6 +23562,9 @@ var OperatorPentestContextSchema = external_exports.object({
23551
23562
  customerPriorityCategories: external_exports.array(external_exports.string()),
23552
23563
  /** Staff scope notes; defaulted so an older API's envelope still parses. */
23553
23564
  scopeNotes: external_exports.string().nullable().default(null),
23565
+ /** The rendered staff-notes block (heading and never-quote line included), or null when the
23566
+ * engagement has none or this session's type never receives them. Defaulted like `scopeNotes`. */
23567
+ staffNotes: external_exports.string().nullable().default(null),
23554
23568
  /** The customer's intake answers, rendered as child briefs carry them. */
23555
23569
  customerIntake: external_exports.string().nullable().default(null),
23556
23570
  status: PentestStatus,
@@ -23898,6 +23912,22 @@ var agentSessionContract = c2.router({
23898
23912
  500: ErrorResponseSchema
23899
23913
  }
23900
23914
  },
23915
+ setBudget: {
23916
+ method: "POST",
23917
+ path: "/organizations/:organizationId/pentests/:pentestId/agent-sessions/:agentSessionId/budget",
23918
+ pathParams: AgentSessionIdParamsSchema,
23919
+ body: SetAgentSessionBudgetRequestSchema,
23920
+ responses: {
23921
+ 200: AgentSessionDetailSchema,
23922
+ 400: ErrorResponseSchema,
23923
+ 401: ErrorResponseSchema,
23924
+ 403: ErrorResponseSchema,
23925
+ 404: ErrorResponseSchema,
23926
+ // errorCode names the refusal; SESSION_NOT_LIVE means resume with the budget instead.
23927
+ 409: ErrorResponseSchema,
23928
+ 500: ErrorResponseSchema
23929
+ }
23930
+ },
23901
23931
  // The host image this environment's agent-session task definition runs. Reads
23902
23932
  // config, mints nothing: it exists so a staff CLI can pull the image BEFORE it
23903
23933
  // creates or resumes a LOCAL session, keeping the boot deadline off the pull.
@@ -28487,6 +28517,9 @@ var PentestResponseSchema = external_exports.object({
28487
28517
  /** Customer threat-model context — free text. Used by hypothesis-gen prompt. */
28488
28518
  customerConcernsContext: external_exports.string().nullable(),
28489
28519
  customerPriorityCategories: external_exports.array(CustomerPriorityCategory),
28520
+ /** Staff-only session guidance. Staff response only. `.optional()` for the same
28521
+ * rolling-deploy reason as `purchaseCapUsd`: the panel row reads Unavailable without it. */
28522
+ staffNotes: external_exports.string().nullable().optional(),
28490
28523
  hypothesisBudgetCapUsd: external_exports.number(),
28491
28524
  validationBudgetCapUsd: external_exports.number(),
28492
28525
  perHypothesisBudgetCapUsd: external_exports.number(),
@@ -28712,6 +28745,8 @@ var CreatePentestRequestSchema = external_exports.object({
28712
28745
  perHypothesisBudgetCapUsd: external_exports.number().min(0).max(1e5).optional(),
28713
28746
  purchaseCapUsd: external_exports.number().min(0).max(1e5).optional()
28714
28747
  });
28748
+ var STAFF_NOTES_MAX_CHARS = 1e4;
28749
+ var STAFF_NOTES_BRIEF_MAX_CHARS = STAFF_NOTES_MAX_CHARS * 3;
28715
28750
  var UpdatePentestRequestSchema = external_exports.object({
28716
28751
  title: external_exports.string().min(1).max(500).optional(),
28717
28752
  description: external_exports.string().max(1e4).nullable().optional(),
@@ -28733,6 +28768,8 @@ var UpdatePentestRequestSchema = external_exports.object({
28733
28768
  testType: PentestTestType.optional(),
28734
28769
  customerConcernsContext: external_exports.string().max(2e4).nullable().optional(),
28735
28770
  customerPriorityCategories: external_exports.array(CustomerPriorityCategory).max(20).optional(),
28771
+ /** Staff-only guidance for this engagement's agent sessions; `null` clears. */
28772
+ staffNotes: external_exports.string().max(STAFF_NOTES_MAX_CHARS).nullable().optional(),
28736
28773
  /** Report-cover engagement overrides. `null` clears (reverts to auto-derivation),
28737
28774
  * `undefined` leaves unchanged. Empty SUT is rejected — use `null` to clear. */
28738
28775
  systemUnderTest: external_exports.string().trim().min(1).max(200).nullable().optional(),
@@ -30394,6 +30431,46 @@ var RenewCloudCredentialsResponseSchema = external_exports.object({
30394
30431
  /** The most recently verified ACTIVE resource, whether or not its mint succeeded. Null when none. */
30395
30432
  gcpPrimaryResourceId: GcpPrefixedResourceIdSchema.nullable().optional()
30396
30433
  });
30434
+ var GetSessionCredentialsRequestSchema = external_exports.object({
30435
+ runVersion: external_exports.number().int().nonnegative()
30436
+ });
30437
+ var ParsedWebCredentialSchema = external_exports.union([
30438
+ external_exports.strictObject({
30439
+ reference: external_exports.literal("SESSION_STATE"),
30440
+ credentialId: external_exports.uuid(),
30441
+ environmentId: external_exports.uuid(),
30442
+ origin: external_exports.string()
30443
+ }),
30444
+ external_exports.strictObject({
30445
+ reference: external_exports.literal("SSH_PRIVATE_KEY"),
30446
+ credentialId: external_exports.uuid(),
30447
+ environmentId: external_exports.uuid(),
30448
+ host: external_exports.string(),
30449
+ port: external_exports.number().int(),
30450
+ username: external_exports.string()
30451
+ }),
30452
+ external_exports.strictObject({ username: external_exports.string(), password: external_exports.string() }),
30453
+ external_exports.strictObject({ secretValue: external_exports.string() }),
30454
+ external_exports.strictObject({ loginEmail: external_exports.string() })
30455
+ ]);
30456
+ var WebCredentialEntrySchema = external_exports.object({
30457
+ source: external_exports.literal("customer"),
30458
+ label: external_exports.string(),
30459
+ credentialType: external_exports.enum(CredentialTypeValues),
30460
+ parsed: ParsedWebCredentialSchema,
30461
+ authConfig: external_exports.record(external_exports.string(), external_exports.unknown()).nullable(),
30462
+ mfa: external_exports.object({ kind: external_exports.string() }).optional(),
30463
+ passkeys: external_exports.number().int().positive().optional()
30464
+ });
30465
+ var SessionCredentialsResponseSchema = external_exports.object({
30466
+ /** Empty when the type gets no web credentials or none resolved. */
30467
+ webCredentials: external_exports.array(WebCredentialEntrySchema),
30468
+ vercel: external_exports.object({ token: external_exports.string(), teamId: external_exports.string().nullable() }).nullable(),
30469
+ browserAllowedBaseUrls: external_exports.array(external_exports.string()),
30470
+ browserExclusions: external_exports.array(external_exports.string()),
30471
+ /** API_COVERAGE only; null otherwise. Range checks stay with the host's rate parser. */
30472
+ requestRate: external_exports.object({ perSec: external_exports.number(), burst: external_exports.number() }).nullable()
30473
+ });
30397
30474
  var ConvexReconRequestSchema = external_exports.object({
30398
30475
  runVersion: external_exports.number().int().nonnegative()
30399
30476
  });
@@ -31984,6 +32061,21 @@ var agentSessionInternalContract = c4.router({
31984
32061
  500: ErrorResponseSchema
31985
32062
  }
31986
32063
  },
32064
+ // The session's credentials, fetched by the host at boot (AgentSessionService.getSessionCredentials).
32065
+ getSessionCredentials: {
32066
+ method: "POST",
32067
+ path: "/internal/v1/agent-sessions/:agentSessionId/session-credentials",
32068
+ pathParams: AgentSessionInternalParamsSchema,
32069
+ body: GetSessionCredentialsRequestSchema,
32070
+ responses: {
32071
+ 200: SessionCredentialsResponseSchema,
32072
+ 400: ErrorResponseSchema,
32073
+ 401: ErrorResponseSchema,
32074
+ 403: ErrorResponseSchema,
32075
+ 409: ErrorResponseSchema,
32076
+ 500: ErrorResponseSchema
32077
+ }
32078
+ },
31987
32079
  // get_convex_deployments (Convex integration PR 3): the read-only broker. Every
31988
32080
  // connected Convex deployment for the run's OWN org, each with its function
31989
32081
  // inventory. No request identifier at all — see the schema block above. No 404:
@@ -45146,7 +45238,9 @@ var OPENAI_SESSION_TYPES = [
45146
45238
  "RETEST",
45147
45239
  "REPORT",
45148
45240
  "RECON",
45149
- "API_COVERAGE"
45241
+ "API_COVERAGE",
45242
+ "PRODUCT_RECON",
45243
+ "CODE_REVIEW"
45150
45244
  ];
45151
45245
  var AGENT_SESSION_MODELS = [
45152
45246
  {
@@ -46555,8 +46649,8 @@ var SetupVerifyResponseSchema = external_exports.object({
46555
46649
  })
46556
46650
  )
46557
46651
  });
46558
- var SetupShareLinkRequestSchema = external_exports.object({ runVersion: RunVersionSchema2 });
46559
- var SetupShareLinkResponseSchema = external_exports.object({ shareLink: external_exports.string().nullable() });
46652
+ var SetupNotesRequestSchema = external_exports.object({ runVersion: RunVersionSchema2 });
46653
+ var SetupNotesResponseSchema = external_exports.object({ notes: external_exports.string().nullable() });
46560
46654
 
46561
46655
  // ../../packages/api-contracts/src/contracts/v1/setup-session-internal.ts
46562
46656
  var c81 = initContract();
@@ -46600,12 +46694,12 @@ var setupSessionInternalContract = c81.router({
46600
46694
  body: SetupVerifyRequestSchema,
46601
46695
  responses: { 200: SetupVerifyResponseSchema, ...errors6 }
46602
46696
  },
46603
- getSetupShareLink: {
46697
+ getSetupNotes: {
46604
46698
  method: "POST",
46605
- path: `${base2}/share-link`,
46699
+ path: `${base2}/notes`,
46606
46700
  pathParams: pathParams2,
46607
- body: SetupShareLinkRequestSchema,
46608
- responses: { 200: SetupShareLinkResponseSchema, ...errors6 }
46701
+ body: SetupNotesRequestSchema,
46702
+ responses: { 200: SetupNotesResponseSchema, ...errors6 }
46609
46703
  }
46610
46704
  });
46611
46705
 
@@ -49201,7 +49295,7 @@ function createSetScanningBranchCommand() {
49201
49295
  return new Command("set-scanning-branch").description("Switch the scanning branch for one or every repository in the org").argument(
49202
49296
  "[repositoryUrl]",
49203
49297
  "Repository URL (omit when using --all-repos to switch every repo)"
49204
- ).requiredOption("--branch <name>", "New scanning branch name").option("--all-repos", "Apply the switch to every connected repository in the organization").option(
49298
+ ).requiredOption("--branch <name>", "New scanning branch name").option("--all-repos", "Apply the switch to every GitHub repository in the organization").option(
49205
49299
  "--migrate-open-vulns",
49206
49300
  "Relabel open vulnerabilities from the previous branch to the new branch"
49207
49301
  ).option("-o, --output <format>", "Output format: table (default), json", "table").addHelpText(
@@ -53916,6 +54010,46 @@ function anySecretFlag(opts) {
53916
54010
  function anyPasswordFlag(opts) {
53917
54011
  return opts.passwordStdin === true || opts.passwordFile !== void 0 || opts.passwordEnv !== void 0;
53918
54012
  }
54013
+ function anyUsernameFlag(opts) {
54014
+ return opts.usernameStdin === true || opts.usernameFile !== void 0 || opts.usernameEnv !== void 0;
54015
+ }
54016
+ function anySecretValueFlag(opts) {
54017
+ return opts.secretStdin === true || opts.secretFile !== void 0 || opts.secretEnv !== void 0;
54018
+ }
54019
+ function withArticle(kebab) {
54020
+ return `${/^(api|ssh)-/.test(kebab) ? "An" : "A"} ${kebab}`;
54021
+ }
54022
+ function rotateFlagProblem(type, opts) {
54023
+ const kebab = credentialTypeKebab(type);
54024
+ if (type === "USERNAME_PASSWORD") {
54025
+ if (anySecretValueFlag(opts)) {
54026
+ return `${withArticle(kebab)} credential takes --username-* and --password-*, not --secret-*.`;
54027
+ }
54028
+ if (!anyUsernameFlag(opts) || !anyPasswordFlag(opts)) {
54029
+ return "Pass both --username-* and --password-*: the login is replaced as a pair.";
54030
+ }
54031
+ if (usesStdin(usernameOpts(opts)) && usesStdin(passwordOpts(opts))) {
54032
+ return "Only one secret can come from stdin. Use --username-file/-env for the other.";
54033
+ }
54034
+ return null;
54035
+ }
54036
+ if (anyUsernameFlag(opts)) {
54037
+ return `${withArticle(kebab)} credential has no username. Pass the new secret with --secret-*.`;
54038
+ }
54039
+ if (type === "SSH_PRIVATE_KEY") {
54040
+ if (!anySecretValueFlag(opts)) {
54041
+ return "Pass the key with --secret-* too: an SSH key and its passphrase are replaced together.";
54042
+ }
54043
+ if (usesStdin(secretOpts(opts)) && usesStdin(passwordOpts(opts))) {
54044
+ return "Only one secret can come from stdin. Use --password-file/-env for the passphrase.";
54045
+ }
54046
+ return null;
54047
+ }
54048
+ if (anyPasswordFlag(opts)) {
54049
+ return `${withArticle(kebab)} credential has no password. Pass the new secret with --secret-*.`;
54050
+ }
54051
+ return null;
54052
+ }
53919
54053
  function secretOpts(opts) {
53920
54054
  return { stdin: opts.secretStdin, file: opts.secretFile, env: opts.secretEnv };
53921
54055
  }
@@ -53931,7 +54065,11 @@ function failWriteOp3(action, status, body, notFoundLabel) {
53931
54065
  exitAuthFailed();
53932
54066
  }
53933
54067
  if (status === 403) {
53934
- console.error(colorError(`Only Trace staff can ${action} credentials.`));
54068
+ console.error(
54069
+ colorError(
54070
+ `You can't ${action} credentials here. That needs an organization admin signed in with \`tracecli auth login\`; API keys are rejected.`
54071
+ )
54072
+ );
53935
54073
  process.exit(1);
53936
54074
  }
53937
54075
  if (status === 404) {
@@ -54248,6 +54386,43 @@ async function buildCreateBody(type, label, options) {
54248
54386
  }
54249
54387
  }
54250
54388
  }
54389
+ async function buildRotateBody(type, options) {
54390
+ switch (type) {
54391
+ case "USERNAME_PASSWORD": {
54392
+ const username = await resolveSecret("username", "username", usernameOpts(options));
54393
+ const password2 = await resolveSecret("password", "password", passwordOpts(options));
54394
+ return { credentialType: type, username, password: password2 };
54395
+ }
54396
+ case "SSH_PRIVATE_KEY": {
54397
+ const privateKeyPem = await resolveSecret("private key PEM", "secret", secretOpts(options));
54398
+ const passphrase = anyPasswordFlag(options) ? await resolveSecret("key passphrase", "password", passwordOpts(options)) : void 0;
54399
+ return {
54400
+ credentialType: type,
54401
+ privateKeyPem,
54402
+ ...passphrase !== void 0 ? { passphrase } : {}
54403
+ };
54404
+ }
54405
+ case "SESSION_STATE":
54406
+ return {
54407
+ credentialType: type,
54408
+ secretValue: await resolveSecret("session state capture", "secret", secretOpts(options))
54409
+ };
54410
+ case "MAGIC_AUTH":
54411
+ return {
54412
+ credentialType: type,
54413
+ secretValue: await resolveSecret("test-account email", "secret", secretOpts(options))
54414
+ };
54415
+ case "BEARER_TOKEN":
54416
+ case "JWT_TOKEN":
54417
+ case "API_KEY":
54418
+ case "PASSPHRASE":
54419
+ case "COOKIE":
54420
+ return {
54421
+ credentialType: type,
54422
+ secretValue: await resolveSecret("secret value", "secret", secretOpts(options))
54423
+ };
54424
+ }
54425
+ }
54251
54426
  async function runList3(options) {
54252
54427
  const output = parseOutput(options.output);
54253
54428
  requireEnvId(options.env);
@@ -54298,12 +54473,7 @@ async function runUpdate3(credentialId, options) {
54298
54473
  const output = parseOutput(options.output);
54299
54474
  requireEnvId(options.env);
54300
54475
  requireCredId(credentialId);
54301
- if (anySecretFlag(options)) {
54302
- console.error(
54303
- colorError("Secrets are immutable on update. Delete and recreate the credential to rotate.")
54304
- );
54305
- process.exit(1);
54306
- }
54476
+ const rotating = anySecretFlag(options);
54307
54477
  if (options.clearAuthConfig && anyAuthFlag(options)) {
54308
54478
  console.error(colorError("Pass either --clear-auth-config or auth-config flags, not both."));
54309
54479
  process.exit(1);
@@ -54316,16 +54486,32 @@ async function runUpdate3(credentialId, options) {
54316
54486
  }
54317
54487
  const organizationId = await resolveOrganizationId();
54318
54488
  const params3 = { organizationId, environmentId: options.env, credentialId };
54489
+ const current = rotating || !options.clearAuthConfig && anyAuthFlag(options) ? await authenticatedRequest((client) => client.credential.getCredential({ params: params3 })) : null;
54490
+ if (current && current.status !== SUCCESS_STATUS) {
54491
+ failReadOp3("get credential", current.status, current.body, credentialId);
54492
+ }
54493
+ let rotateBody;
54494
+ if (current && rotating) {
54495
+ const type = current.body.credentialType;
54496
+ const problem = rotateFlagProblem(type, options) ?? (options.clearAuthConfig && (type === "SSH_PRIVATE_KEY" || type === "SESSION_STATE") ? `${withArticle(credentialTypeKebab(type))} credential's auth config can't be cleared, so --clear-auth-config would fail after the secret had already changed.` : null);
54497
+ if (problem) {
54498
+ console.error(colorError(problem));
54499
+ process.exit(1);
54500
+ }
54501
+ try {
54502
+ rotateBody = await buildRotateBody(type, options);
54503
+ } catch (error48) {
54504
+ if (error48 instanceof SecretInputError) {
54505
+ console.error(colorError(error48.message));
54506
+ process.exit(1);
54507
+ }
54508
+ throw error48;
54509
+ }
54510
+ }
54319
54511
  let authConfig;
54320
54512
  if (options.clearAuthConfig) {
54321
54513
  authConfig = null;
54322
- } else if (anyAuthFlag(options)) {
54323
- const current = await authenticatedRequest(
54324
- (client) => client.credential.getCredential({ params: params3 })
54325
- );
54326
- if (current.status !== SUCCESS_STATUS) {
54327
- failReadOp3("get credential", current.status, current.body, credentialId);
54328
- }
54514
+ } else if (current && anyAuthFlag(options)) {
54329
54515
  const type = current.body.credentialType;
54330
54516
  const foreign = rejectForeignAuthFlags(type, options);
54331
54517
  if (foreign) {
@@ -54345,24 +54531,44 @@ async function runUpdate3(credentialId, options) {
54345
54531
  ...options.moveEnv !== void 0 ? { environmentId: options.moveEnv } : {},
54346
54532
  ...authConfig !== void 0 ? { authConfig } : {}
54347
54533
  };
54348
- if (Object.keys(body).length === 0) {
54534
+ const patching = Object.keys(body).length > 0;
54535
+ if (!rotateBody && !patching) {
54349
54536
  console.error(
54350
54537
  colorError(
54351
- "Nothing to update. Pass --label, --move-env, auth-config flags, or --clear-auth-config."
54538
+ "Nothing to update. Pass --label, --move-env, auth-config flags, --clear-auth-config, or a new secret."
54352
54539
  )
54353
54540
  );
54354
54541
  process.exit(1);
54355
54542
  }
54356
- const result = await authenticatedRequest(
54357
- (client) => client.credential.updateCredential({ params: params3, body })
54358
- );
54359
- if (result.status !== SUCCESS_STATUS) {
54360
- failWriteOp3("update", result.status, result.body, credentialId);
54543
+ let updated;
54544
+ if (rotateBody) {
54545
+ const secret = rotateBody;
54546
+ const rotated = await authenticatedRequest(
54547
+ (client) => client.credential.rotateCredentialSecret({ params: params3, body: secret })
54548
+ );
54549
+ if (rotated.status !== SUCCESS_STATUS) {
54550
+ failWriteOp3("update", rotated.status, rotated.body, credentialId);
54551
+ }
54552
+ updated = rotated.body;
54361
54553
  }
54554
+ if (patching) {
54555
+ const result = await authenticatedRequest(
54556
+ (client) => client.credential.updateCredential({ params: params3, body })
54557
+ );
54558
+ if (result.status !== SUCCESS_STATUS) {
54559
+ if (rotateBody) {
54560
+ console.error(colorError("The secret was rotated, but the other changes were not saved."));
54561
+ }
54562
+ failWriteOp3("update", result.status, result.body, credentialId);
54563
+ }
54564
+ updated = result.body;
54565
+ }
54566
+ if (!updated) return;
54362
54567
  if (output === "table") {
54363
- console.log(colorSuccess(`Updated credential ${sanitizeDisplay(result.body.label)}`));
54568
+ const verb = rotateBody ? patching ? "Rotated the secret and updated" : "Rotated the secret of" : "Updated";
54569
+ console.log(colorSuccess(`${verb} credential ${sanitizeDisplay(updated.label)}`));
54364
54570
  }
54365
- outputCredential(result.body, output);
54571
+ outputCredential(updated, output);
54366
54572
  }
54367
54573
  function failMfaOp(action, status, body, credentialId) {
54368
54574
  const { message, errorCode } = readErrorFields(body);
@@ -55043,18 +55249,22 @@ function createGetCommand2() {
55043
55249
  });
55044
55250
  }
55045
55251
  function createUpdateCommand2() {
55046
- const command = new Command("update").description("Update a credential (label, environment, or auth config; secrets are immutable)").argument("<credentialId>", "Credential UUID").requiredOption("--env <environmentId>", "Current parent environment UUID").option("--label <label>", "New label").option("--move-env <environmentId>", "Move to a different environment (same application)").option("--clear-auth-config", "Remove the auth config (sends null)").option("-o, --output <format>", "Output format: table, json, tsv (default: table)", "table");
55252
+ const command = new Command("update").description("Update a credential (label, environment, auth config, or secret)").argument("<credentialId>", "Credential UUID").requiredOption("--env <environmentId>", "Current parent environment UUID").option("--label <label>", "New label").option("--move-env <environmentId>", "Move to a different environment (same application)").option("--clear-auth-config", "Remove the auth config (sends null)").option("-o, --output <format>", "Output format: table, json, tsv (default: table)", "table");
55047
55253
  addSecretInputOptions(command);
55048
55254
  addAuthConfigOptions(command).addHelpText(
55049
55255
  "after",
55050
55256
  `
55051
55257
  Auth-config flags must match the credential's existing type (looked up automatically).
55052
- Secrets cannot be changed here \u2014 delete and recreate to rotate.
55258
+ A secret flag rotates the secret in place: the credential keeps its ID and scan history.
55259
+ A username/password credential takes both --username-* and --password-*; an SSH key
55260
+ takes --secret-* for the key and --password-* for its passphrase, if it has one.
55053
55261
 
55054
55262
  Examples:
55055
55263
  tracecli credential update <id> --env <envId> --label "prod (rotated 2026-06)"
55056
55264
  tracecli credential update <id> --env <envId> --login-url https://acme.com/signin
55057
- tracecli credential update <id> --env <envId> --clear-auth-config`
55265
+ tracecli credential update <id> --env <envId> --clear-auth-config
55266
+ vault read -field=token secret/x | tracecli credential update <id> --env <envId> --secret-stdin
55267
+ tracecli credential update <id> --env <envId> --username-env LOGIN_USER --password-env LOGIN_PASS`
55058
55268
  );
55059
55269
  return command.action(async (credentialId, options) => {
55060
55270
  await runUpdate3(credentialId, options);
@@ -59061,7 +59271,7 @@ function createFindingsCommand() {
59061
59271
  findings.addCommand(createGetFindingCommand());
59062
59272
  findings.addCommand(createGetFindingReproCommand());
59063
59273
  findings.addCommand(createStatusUpdateCommand());
59064
- findings.addCommand(createAttachEvidenceCommand());
59274
+ findings.addCommand(createAttachEvidenceCommand(), { hidden: true });
59065
59275
  findings.addCommand(createCreateFindingCommand(), { hidden: true });
59066
59276
  findings.addCommand(createCopyFindingsCommand(), { hidden: true });
59067
59277
  findings.addCommand(createPublishFindingsCommand(), { hidden: true });
@@ -59077,7 +59287,8 @@ function buildUpdatePentestBody(input) {
59077
59287
  ...input.title !== void 0 ? { title: input.title } : {},
59078
59288
  ...input.description !== void 0 ? { description: input.description } : {},
59079
59289
  ...input.customerConcernsContext !== void 0 ? { customerConcernsContext: input.customerConcernsContext } : {},
59080
- ...input.customerPriorityCategories !== void 0 ? { customerPriorityCategories: input.customerPriorityCategories } : {}
59290
+ ...input.customerPriorityCategories !== void 0 ? { customerPriorityCategories: input.customerPriorityCategories } : {},
59291
+ ...input.staffNotes !== void 0 ? { staffNotes: input.staffNotes } : {}
59081
59292
  };
59082
59293
  }
59083
59294
  function isEmptyPentestUpdate(body) {
@@ -59179,12 +59390,15 @@ function printPentestUpdateDetail(pt) {
59179
59390
  `${"Description:".padEnd(pad)} ${pt.description ? sanitizeDisplay(truncateForDisplay(pt.description)) : "\u2014"}`,
59180
59391
  `${"Priorities:".padEnd(pad)} ${sanitizeDisplay(formatPriorities(pt.customerPriorityCategories))}`,
59181
59392
  `${"Concerns:".padEnd(pad)} ${pt.customerConcernsContext ? sanitizeDisplay(truncateForDisplay(pt.customerConcernsContext)) : "\u2014"}`,
59393
+ `${"Staff notes:".padEnd(pad)} ${pt.staffNotes ? sanitizeDisplay(truncateForDisplay(pt.staffNotes)) : "\u2014"}`,
59182
59394
  `${"Updated:".padEnd(pad)} ${toIso(pt.updatedAt)}`
59183
59395
  ].join("\n")
59184
59396
  );
59185
59397
  }
59186
59398
  function printPentestUpdateTsv(pt) {
59187
- console.log("displayId title status phase description priorities concerns updatedAt");
59399
+ console.log(
59400
+ "displayId title status phase description priorities concerns staffNotes updatedAt"
59401
+ );
59188
59402
  console.log(
59189
59403
  [
59190
59404
  `PT-${pt.displayId}`,
@@ -59194,6 +59408,7 @@ function printPentestUpdateTsv(pt) {
59194
59408
  tsvSafe2(sanitizeDisplay(pt.description ?? "")),
59195
59409
  tsvSafe2(sanitizeDisplay(pt.customerPriorityCategories.join(","))),
59196
59410
  tsvSafe2(sanitizeDisplay(pt.customerConcernsContext ?? "")),
59411
+ tsvSafe2(sanitizeDisplay(pt.staffNotes ?? "")),
59197
59412
  toIso(pt.updatedAt)
59198
59413
  ].join(" ")
59199
59414
  );
@@ -59230,16 +59445,26 @@ async function runUpdate5(pentestIdentifier, options) {
59230
59445
  console.error(colorError(priorities.error));
59231
59446
  process.exit(1);
59232
59447
  }
59448
+ const staffNotes = resolveClearableOption(
59449
+ options.staffNotes,
59450
+ options.clearStaffNotes,
59451
+ "staff-notes"
59452
+ );
59453
+ if ("error" in staffNotes) {
59454
+ console.error(colorError(staffNotes.error));
59455
+ process.exit(1);
59456
+ }
59233
59457
  const body = buildUpdatePentestBody({
59234
59458
  title: options.title,
59235
59459
  description: description.value,
59236
59460
  customerConcernsContext: concerns.value,
59237
- customerPriorityCategories: priorities.value
59461
+ customerPriorityCategories: priorities.value,
59462
+ staffNotes: staffNotes.value
59238
59463
  });
59239
59464
  if (isEmptyPentestUpdate(body)) {
59240
59465
  console.error(
59241
59466
  colorError(
59242
- "Nothing to update. Pass at least one of --title, --description, --clear-description, --concerns, --clear-concerns, --priority, --clear-priorities."
59467
+ "Nothing to update. Pass at least one of --title, --description, --clear-description, --concerns, --clear-concerns, --priority, --clear-priorities, --staff-notes, --clear-staff-notes."
59243
59468
  )
59244
59469
  );
59245
59470
  process.exit(1);
@@ -59252,19 +59477,30 @@ async function runUpdate5(pentestIdentifier, options) {
59252
59477
  if (result.status !== SUCCESS_STATUS) {
59253
59478
  failPentestUpdate(result.status, result.body, pentestIdentifier);
59254
59479
  }
59480
+ if (staffNotesIgnoredByApi(body, result.body)) {
59481
+ console.error(
59482
+ colorError(
59483
+ "This API does not support staff notes yet, so they were not saved. Any other fields you passed were saved."
59484
+ )
59485
+ );
59486
+ process.exit(1);
59487
+ }
59255
59488
  if (output === "table") {
59256
59489
  console.log(colorSuccess(`Updated pentest PT-${result.body.displayId}`));
59257
59490
  }
59258
59491
  outputPentestUpdate(result.body, output);
59259
59492
  }
59493
+ function staffNotesIgnoredByApi(body, response) {
59494
+ return body.staffNotes !== void 0 && !("staffNotes" in response);
59495
+ }
59260
59496
  function createUpdateCommand4() {
59261
59497
  return new Command("update").description(
59262
- "Update pentest details and agent brief (staff-only: title, description, priorities, concerns)"
59498
+ "Update pentest details and agent brief (staff-only: title, description, priorities, concerns, staff notes)"
59263
59499
  ).argument("<pentestId>", "Pentest display ID (e.g. PT-3) or UUID").option("--title <title>", "Engagement title").option("--description <text>", "Engagement description").option("--clear-description", "Clear the engagement description").option("--concerns <text>", "Agent-brief concerns (threat-model context)").option("--clear-concerns", "Clear agent-brief concerns").option(
59264
59500
  "--priority <category>",
59265
59501
  `Agent-brief priority category (repeatable). One of: ${PRIORITY_LIST}`,
59266
59502
  collectPriority
59267
- ).option("--clear-priorities", "Clear all agent-brief priority categories").option("-o, --output <format>", "Output format: table, json, tsv (default: table)", "table").addHelpText(
59503
+ ).option("--clear-priorities", "Clear all agent-brief priority categories").option("--staff-notes <text>", "Guidance for the agent sessions on this pentest").option("--clear-staff-notes", "Clear the staff notes").option("-o, --output <format>", "Output format: table, json, tsv (default: table)", "table").addHelpText(
59268
59504
  "after",
59269
59505
  `
59270
59506
  Staff-only. Requires a browser session from \`tracecli auth login\` with a Trace
@@ -59277,6 +59513,7 @@ Examples:
59277
59513
  tracecli pentest update PT-3 --priority AUTHN_BYPASS --priority AUTHZ_IDOR
59278
59514
  tracecli pentest update PT-3 --concerns "Worried about IDOR on invoice IDs"
59279
59515
  tracecli pentest update PT-3 --clear-concerns --clear-priorities
59516
+ tracecli pentest update PT-3 --staff-notes "Staging is behind a Cloudflare challenge; use headed agent-browser"
59280
59517
  tracecli pentest update PT-3 --title "Acme Q3" -o json`
59281
59518
  ).action(async (pentestId, options) => {
59282
59519
  await runUpdate5(pentestId, options);
@@ -59586,7 +59823,7 @@ Examples:
59586
59823
  function createPentestCommand() {
59587
59824
  const pentest = new Command("pentest").description("Manage pentests");
59588
59825
  pentest.addCommand(createListCommand7());
59589
- pentest.addCommand(createUpdateCommand4());
59826
+ pentest.addCommand(createUpdateCommand4(), { hidden: true });
59590
59827
  pentest.addCommand(createCreatePentestCommand(), { hidden: true });
59591
59828
  pentest.addCommand(createVisibilityCommand(), { hidden: true });
59592
59829
  pentest.addCommand(createQuestionnaireCommand());
@@ -61320,7 +61557,7 @@ function parseVulnerabilityFilter(expression) {
61320
61557
  const match = expression.match(/^([^!=]+)(!=|=)(.+)$/);
61321
61558
  if (!match?.[1] || !match[2] || !match[3]) {
61322
61559
  return {
61323
- error: `Invalid filter expression '${expression}'. Expected format: field=value or field!=value (e.g. cweId=CWE-89).`
61560
+ error: `Invalid filter expression '${expression}'. Expected format: field=value or field!=value (e.g. scanMode=SAST).`
61324
61561
  };
61325
61562
  }
61326
61563
  const field = match[1].trim();
@@ -61561,7 +61798,7 @@ function createListCommand9() {
61561
61798
  }
61562
61799
  ).option(
61563
61800
  "-f, --filter <expression>",
61564
- "Additional filter expression: field=value or field!=value. Repeatable. Supported fields: vulnerabilityType, cweId, scanMode (and any other API-supported field).",
61801
+ "Additional filter expression: field=value or field!=value. Repeatable. Supported fields: scanMode (SAST, SCA, SECRETS, DAST), severity, status, repositoryId.",
61565
61802
  (value, previous) => [...previous, value],
61566
61803
  []
61567
61804
  ).option("-o, --output <format>", "Output format: table, json, tsv (default: table)", "table").option("-w, --web", "Open the vulnerabilities dashboard in a browser").option("--no-pager", "Disable the interactive arrow-key pager for table output").addHelpText(
@@ -61571,8 +61808,7 @@ Examples:
61571
61808
  tracecli vulnerabilities list
61572
61809
  tracecli vulnerabilities list --severity CRITICAL --status OPEN
61573
61810
  tracecli vulnerabilities list --repo securewithtrace/trace --limit 50
61574
- tracecli vulnerabilities list --filter cweId=CWE-89
61575
- tracecli vulnerabilities list --filter vulnerabilityType=SAST --filter scanMode=sast
61811
+ tracecli vulnerabilities list --filter scanMode=SECRETS
61576
61812
  tracecli vulnerabilities list --output json | jq '.[].title'
61577
61813
  tracecli vulnerabilities list --output tsv | cut -f1,7
61578
61814
  tracecli vulnerabilities list --no-pager
@@ -61695,7 +61931,7 @@ program2.addCommand(createEnvironmentCommand());
61695
61931
  program2.addCommand(createCredentialCommand());
61696
61932
  program2.addCommand(createInboxCommand());
61697
61933
  program2.addCommand(createRepoCommand());
61698
- program2.addCommand(createVulnerabilitiesCommand());
61934
+ program2.addCommand(createVulnerabilitiesCommand(), { hidden: true });
61699
61935
  program2.addCommand(createCodeSizeCommand());
61700
61936
  program2.addCommand(createUploadCommand());
61701
61937
  program2.addCommand(createPentestCommand());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securewithtrace",
3
- "version": "0.50.0",
3
+ "version": "0.52.0",
4
4
  "description": "Official Trace CLI for vulnerability intelligence in your terminal",
5
5
  "type": "module",
6
6
  "bin": {