securewithtrace 0.42.0 → 0.44.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.
- package/README.md +11 -0
- package/dist/index.js +700 -205
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -263,6 +263,9 @@ tracecli pentest findings list --pentest PT-3 --output tsv | cut -f1,4
|
|
|
263
263
|
# Open the pentest detail page in a browser
|
|
264
264
|
tracecli pentest findings list --pentest PT-3 --web
|
|
265
265
|
|
|
266
|
+
# One finding with its full write-up, as markdown for a coding agent
|
|
267
|
+
tracecli pentest findings get --pentest PT-3 --finding PTF-7
|
|
268
|
+
|
|
266
269
|
# Mark a finding in progress, then remediated when the fix is ready for retest
|
|
267
270
|
tracecli pentest findings status-update --pentest PT-3 --finding 7 --status IN_PROGRESS
|
|
268
271
|
tracecli pentest findings status-update --pentest PT-3 --finding 7 --status REMEDIATED
|
|
@@ -284,6 +287,14 @@ Flags for `pentest findings list`:
|
|
|
284
287
|
| `--output <format>` | `-o` | Output format: `table` (default), `json`, `tsv` |
|
|
285
288
|
| `--web` | `-w` | Open the pentest detail page in a browser |
|
|
286
289
|
|
|
290
|
+
Flags for `pentest findings get`:
|
|
291
|
+
|
|
292
|
+
| Flag | Shorthand | Description |
|
|
293
|
+
|------|-----------|-------------|
|
|
294
|
+
| `--pentest <id>` | | Pentest display ID (e.g. `PT-3`) or UUID (required) |
|
|
295
|
+
| `--finding <id>` | | Finding number (e.g. `PTF-7`) or UUID (required) |
|
|
296
|
+
| `--output <format>` | `-o` | Output format: `text` (markdown, default), `json` |
|
|
297
|
+
|
|
287
298
|
Flags for `pentest findings status-update`:
|
|
288
299
|
|
|
289
300
|
| Flag | Shorthand | Description |
|
package/dist/index.js
CHANGED
|
@@ -3683,6 +3683,22 @@ function linkRolesDisplay(roles, role, roleLabel) {
|
|
|
3683
3683
|
// ../../packages/core/src/application/environment-urls.ts
|
|
3684
3684
|
var ENVIRONMENT_ADDITIONAL_URLS_MAX = 20;
|
|
3685
3685
|
|
|
3686
|
+
// ../../packages/core/src/pentest/attack-chain-status.ts
|
|
3687
|
+
var ATTACK_CHAIN_STATUS_VALUES = ["OPEN", "REMEDIATED", "VERIFIED_FIXED"];
|
|
3688
|
+
var ATTACK_CHAIN_STEP_STATE_VALUES = ATTACK_CHAIN_STATUS_VALUES;
|
|
3689
|
+
|
|
3690
|
+
// ../../packages/core/src/pentest/finding-status.ts
|
|
3691
|
+
var PENTEST_FINDING_STATUS_VALUES = [
|
|
3692
|
+
"OPEN",
|
|
3693
|
+
"IN_PROGRESS",
|
|
3694
|
+
"CONTESTED",
|
|
3695
|
+
"REMEDIATED",
|
|
3696
|
+
"VERIFIED_FIXED",
|
|
3697
|
+
"VERIFIED_OPEN",
|
|
3698
|
+
"ACCEPTED_WITH_CONTROLS",
|
|
3699
|
+
"DISMISSED"
|
|
3700
|
+
];
|
|
3701
|
+
|
|
3686
3702
|
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/classic/external.js
|
|
3687
3703
|
var external_exports = {};
|
|
3688
3704
|
__export(external_exports, {
|
|
@@ -21884,16 +21900,7 @@ var PentestStatusValues = ["SCOPING", "IN_PROGRESS", "REMEDIATION", "CLOSED"];
|
|
|
21884
21900
|
var PentestStatus = external_exports.enum(PentestStatusValues);
|
|
21885
21901
|
var PentestTestTypeValues = ["BLACK_BOX", "GREY_BOX", "WHITE_BOX"];
|
|
21886
21902
|
var PentestTestType = external_exports.enum(PentestTestTypeValues);
|
|
21887
|
-
var PentestFindingStatusValues =
|
|
21888
|
-
"OPEN",
|
|
21889
|
-
"IN_PROGRESS",
|
|
21890
|
-
"CONTESTED",
|
|
21891
|
-
"REMEDIATED",
|
|
21892
|
-
"VERIFIED_FIXED",
|
|
21893
|
-
"VERIFIED_OPEN",
|
|
21894
|
-
"ACCEPTED_WITH_CONTROLS",
|
|
21895
|
-
"DISMISSED"
|
|
21896
|
-
];
|
|
21903
|
+
var PentestFindingStatusValues = PENTEST_FINDING_STATUS_VALUES;
|
|
21897
21904
|
var PentestFindingStatus = external_exports.enum(PentestFindingStatusValues);
|
|
21898
21905
|
var PentestFindingDismissReasonValues = [
|
|
21899
21906
|
"FALSE_POSITIVE",
|
|
@@ -22846,6 +22853,78 @@ var ListAgentTasksQuerySchema = external_exports.object({
|
|
|
22846
22853
|
});
|
|
22847
22854
|
var ListAgentTasksResponseSchema = external_exports.array(AgentTaskResponseSchema);
|
|
22848
22855
|
|
|
22856
|
+
// ../../packages/api-contracts/src/schemas/api/v1/inbox.api.ts
|
|
22857
|
+
var InboxLabelSchema = external_exports.string().regex(
|
|
22858
|
+
/^[a-z][a-z0-9-]{0,39}$/,
|
|
22859
|
+
"Inbox label must be lowercase letters/digits/dashes, starting with a letter, at most 40 characters"
|
|
22860
|
+
);
|
|
22861
|
+
var MAX_INBOX_LABEL_LENGTH = 40;
|
|
22862
|
+
var MAX_LOCAL_PART_LENGTH = 64;
|
|
22863
|
+
var MAX_INBOX_ADDRESS_PREFIX_LENGTH = MAX_LOCAL_PART_LENGTH - MAX_INBOX_LABEL_LENGTH - 1;
|
|
22864
|
+
var MANAGED_INBOX_DOMAINS = [
|
|
22865
|
+
"pentest.securewithtrace.com",
|
|
22866
|
+
"pentest.rmrflabs.com"
|
|
22867
|
+
];
|
|
22868
|
+
var ManagedInboxDomainSchema = external_exports.enum(MANAGED_INBOX_DOMAINS);
|
|
22869
|
+
var DEFAULT_MANAGED_INBOX_DOMAIN = MANAGED_INBOX_DOMAINS[0];
|
|
22870
|
+
var ManagedInboxResponseSchema = external_exports.object({
|
|
22871
|
+
/** Full derived address, e.g. `acme-qa-admin@pentest.securewithtrace.com`. */
|
|
22872
|
+
address: external_exports.email(),
|
|
22873
|
+
label: external_exports.string(),
|
|
22874
|
+
createdAt: external_exports.date()
|
|
22875
|
+
});
|
|
22876
|
+
var ListManagedInboxesResponseSchema = external_exports.object({
|
|
22877
|
+
inboxes: external_exports.array(ManagedInboxResponseSchema),
|
|
22878
|
+
quota: external_exports.object({
|
|
22879
|
+
used: external_exports.number().int().nonnegative(),
|
|
22880
|
+
limit: external_exports.number().int().positive()
|
|
22881
|
+
})
|
|
22882
|
+
});
|
|
22883
|
+
var CreateManagedInboxRequestSchema = external_exports.object({
|
|
22884
|
+
label: InboxLabelSchema
|
|
22885
|
+
}).strict();
|
|
22886
|
+
var ManagedInboxLabelParamsSchema = OrgIdParamsSchema.extend({
|
|
22887
|
+
label: external_exports.string().min(1).max(64)
|
|
22888
|
+
});
|
|
22889
|
+
var ManagedInboxMessageParamsSchema = ManagedInboxLabelParamsSchema.extend({
|
|
22890
|
+
messageId: external_exports.string().min(1).max(500)
|
|
22891
|
+
});
|
|
22892
|
+
var ListInboxMessagesQuerySchema = external_exports.object({
|
|
22893
|
+
/** Max messages, newest first. The provider may clamp further. */
|
|
22894
|
+
limit: external_exports.coerce.number().int().positive().max(50).optional(),
|
|
22895
|
+
/** Only messages received strictly after this instant. */
|
|
22896
|
+
since: external_exports.coerce.date().optional()
|
|
22897
|
+
}).strict();
|
|
22898
|
+
var ManagedInboxMessageSummarySchema = external_exports.object({
|
|
22899
|
+
messageId: external_exports.string().min(1).max(500),
|
|
22900
|
+
from: external_exports.string().max(500),
|
|
22901
|
+
/** Recipients as the provider returned them (display-name form allowed). */
|
|
22902
|
+
to: external_exports.array(external_exports.string().max(500)),
|
|
22903
|
+
subject: external_exports.string().max(1e3).nullable(),
|
|
22904
|
+
preview: external_exports.string().max(2e3).nullable(),
|
|
22905
|
+
receivedAt: external_exports.coerce.date(),
|
|
22906
|
+
unread: external_exports.boolean()
|
|
22907
|
+
});
|
|
22908
|
+
var ListInboxMessagesResponseSchema = external_exports.object({
|
|
22909
|
+
address: external_exports.email(),
|
|
22910
|
+
label: external_exports.string(),
|
|
22911
|
+
messages: external_exports.array(ManagedInboxMessageSummarySchema)
|
|
22912
|
+
});
|
|
22913
|
+
var GetInboxMessageResponseSchema = external_exports.object({
|
|
22914
|
+
address: external_exports.email(),
|
|
22915
|
+
label: external_exports.string(),
|
|
22916
|
+
message: ManagedInboxMessageSummarySchema.extend({
|
|
22917
|
+
body: external_exports.string(),
|
|
22918
|
+
truncated: external_exports.boolean(),
|
|
22919
|
+
/**
|
|
22920
|
+
* http(s) links extracted from the message (hrefs first, then inline
|
|
22921
|
+
* text URLs). Button-style mail often carries the magic link only in
|
|
22922
|
+
* `href`, which a tag-stripped body can still miss if extraction fails.
|
|
22923
|
+
*/
|
|
22924
|
+
links: external_exports.array(external_exports.string())
|
|
22925
|
+
})
|
|
22926
|
+
});
|
|
22927
|
+
|
|
22849
22928
|
// ../../packages/api-contracts/src/schemas/api/v1/pentest-attack-chain.api.ts
|
|
22850
22929
|
var PentestAttackChainParamsSchema = OrgIdParamsSchema.extend({
|
|
22851
22930
|
pentestId: external_exports.uuid()
|
|
@@ -22876,6 +22955,23 @@ var UpdatePentestAttackChainRequestSchema = CreatePentestAttackChainRequestSchem
|
|
|
22876
22955
|
var PublishPentestAttackChainRequestSchema = external_exports.object({
|
|
22877
22956
|
expectedUpdatedAt: external_exports.coerce.date()
|
|
22878
22957
|
});
|
|
22958
|
+
var PublishAllAttackChainsRequestSchema = external_exports.object({
|
|
22959
|
+
attackChainIds: external_exports.array(external_exports.uuid()).max(1e3).optional()
|
|
22960
|
+
});
|
|
22961
|
+
var PublishedAttackChainSchema = external_exports.object({
|
|
22962
|
+
attackChainId: external_exports.uuid(),
|
|
22963
|
+
displayId: external_exports.int()
|
|
22964
|
+
});
|
|
22965
|
+
var SkippedAttackChainReason = external_exports.enum(["TOO_SHORT", "FINDING_UNPUBLISHED"]);
|
|
22966
|
+
var SkippedAttackChainSchema = external_exports.object({
|
|
22967
|
+
attackChainId: external_exports.uuid(),
|
|
22968
|
+
title: external_exports.string(),
|
|
22969
|
+
reason: SkippedAttackChainReason
|
|
22970
|
+
});
|
|
22971
|
+
var PublishAllAttackChainsResponseSchema = external_exports.object({
|
|
22972
|
+
published: external_exports.array(PublishedAttackChainSchema),
|
|
22973
|
+
skipped: external_exports.array(SkippedAttackChainSchema)
|
|
22974
|
+
});
|
|
22879
22975
|
var AttackChainStepFindingSchema = external_exports.object({
|
|
22880
22976
|
pentestFindingId: external_exports.uuid(),
|
|
22881
22977
|
displayId: external_exports.number().int().nullable(),
|
|
@@ -22884,12 +22980,15 @@ var AttackChainStepFindingSchema = external_exports.object({
|
|
|
22884
22980
|
status: PentestFindingStatus,
|
|
22885
22981
|
publishedAt: external_exports.coerce.date().nullable()
|
|
22886
22982
|
});
|
|
22983
|
+
var AttackChainStatus = external_exports.enum(ATTACK_CHAIN_STATUS_VALUES);
|
|
22984
|
+
var AttackChainStepState = external_exports.enum(ATTACK_CHAIN_STEP_STATE_VALUES);
|
|
22887
22985
|
var AttackChainStepSchema = external_exports.object({
|
|
22888
22986
|
stepId: external_exports.uuid(),
|
|
22889
22987
|
stepOrder: external_exports.number().int(),
|
|
22890
22988
|
title: external_exports.string(),
|
|
22891
22989
|
narrative: external_exports.string(),
|
|
22892
|
-
findings: external_exports.array(AttackChainStepFindingSchema)
|
|
22990
|
+
findings: external_exports.array(AttackChainStepFindingSchema),
|
|
22991
|
+
remediationState: AttackChainStepState
|
|
22893
22992
|
});
|
|
22894
22993
|
var PentestAttackChainSchema = external_exports.object({
|
|
22895
22994
|
attackChainId: external_exports.uuid(),
|
|
@@ -22900,6 +22999,7 @@ var PentestAttackChainSchema = external_exports.object({
|
|
|
22900
22999
|
severity: PentestFindingSeverity,
|
|
22901
23000
|
cvssVector: external_exports.string().nullable(),
|
|
22902
23001
|
cvssScore: external_exports.number().nullable(),
|
|
23002
|
+
status: AttackChainStatus,
|
|
22903
23003
|
publishedAt: external_exports.coerce.date().nullable(),
|
|
22904
23004
|
createdAt: external_exports.coerce.date(),
|
|
22905
23005
|
updatedAt: external_exports.coerce.date(),
|
|
@@ -23449,6 +23549,14 @@ var FindingRewriteJsonSchema = external_exports.object({
|
|
|
23449
23549
|
* falls back to live `f.status` in either case.
|
|
23450
23550
|
*/
|
|
23451
23551
|
statusAtRewrite: PentestFindingStatus.nullish(),
|
|
23552
|
+
/**
|
|
23553
|
+
* Plain-language lead, two or three sentences, rendered before Description.
|
|
23554
|
+
* The cap is a paragraph, not a section: a summary that needs more room is a
|
|
23555
|
+
* description. `.default(null)` keeps rewrites persisted before the field
|
|
23556
|
+
* shipped parseable (the JSON has no key) while the output type stays
|
|
23557
|
+
* `string | null`, so every reader handles "no summary" the same way.
|
|
23558
|
+
*/
|
|
23559
|
+
summary: external_exports.string().min(1).max(1e3).nullable().default(null),
|
|
23452
23560
|
description: external_exports.string().min(1).max(12e3),
|
|
23453
23561
|
rootCauseAnalysis: external_exports.string().min(1).max(12e3).nullable(),
|
|
23454
23562
|
impact: external_exports.string().min(1).max(8e3),
|
|
@@ -23480,6 +23588,7 @@ var FindingRewritesJsonSchema = external_exports.object({
|
|
|
23480
23588
|
path: ["rewrites"]
|
|
23481
23589
|
});
|
|
23482
23590
|
var FindingRewriteCorrectionFieldValues = [
|
|
23591
|
+
"summary",
|
|
23483
23592
|
"description",
|
|
23484
23593
|
"rootCauseAnalysis",
|
|
23485
23594
|
"impact",
|
|
@@ -24198,6 +24307,9 @@ var PentestFindingRetestIdParamsSchema = PentestFindingIdParamsSchema.extend({
|
|
|
24198
24307
|
var PentestFindingEvidenceIdParamsSchema = PentestFindingIdParamsSchema.extend({
|
|
24199
24308
|
artifactId: external_exports.uuid()
|
|
24200
24309
|
});
|
|
24310
|
+
var PentestFindingTimelineEntryParamsSchema = PentestFindingIdParamsSchema.extend({
|
|
24311
|
+
entryId: external_exports.uuid()
|
|
24312
|
+
});
|
|
24201
24313
|
var CustomerExploitArtifactResponseSchema = external_exports.object({
|
|
24202
24314
|
artifactId: external_exports.uuid(),
|
|
24203
24315
|
kind: PentestExploitArtifactKind,
|
|
@@ -24275,6 +24387,8 @@ var PentestFindingResponseSchema = external_exports.object({
|
|
|
24275
24387
|
*/
|
|
24276
24388
|
displayId: external_exports.number().int().positive().nullable(),
|
|
24277
24389
|
title: external_exports.string(),
|
|
24390
|
+
/** Plain-language lead rendered before `description`; null on findings authored before it existed. */
|
|
24391
|
+
summary: external_exports.string().nullable(),
|
|
24278
24392
|
description: external_exports.string(),
|
|
24279
24393
|
severity: PentestFindingSeverity,
|
|
24280
24394
|
category: external_exports.string(),
|
|
@@ -24310,6 +24424,7 @@ var PentestFindingResponseSchema = external_exports.object({
|
|
|
24310
24424
|
externalTicket: ExternalTicketSummarySchema.nullable().default(null)
|
|
24311
24425
|
});
|
|
24312
24426
|
var PentestFindingListItemSchema = PentestFindingResponseSchema.omit({
|
|
24427
|
+
summary: true,
|
|
24313
24428
|
description: true,
|
|
24314
24429
|
proofOfConcept: true,
|
|
24315
24430
|
remediation: true,
|
|
@@ -24432,14 +24547,29 @@ var ResolutionTimelineVia = external_exports.object({
|
|
|
24432
24547
|
identifier: external_exports.string(),
|
|
24433
24548
|
url: external_exports.url()
|
|
24434
24549
|
}).nullable().default(null);
|
|
24550
|
+
var ResolutionTimelineEntrySource = external_exports.enum(["NOTE", "CONTEST_RESOLUTION", "RETEST"]);
|
|
24551
|
+
var ResolutionTimelineEditHandleSchema = external_exports.object({
|
|
24552
|
+
source: ResolutionTimelineEntrySource,
|
|
24553
|
+
id: external_exports.uuid()
|
|
24554
|
+
}).nullable().default(null);
|
|
24435
24555
|
var ResolutionTimelineEntrySchema = external_exports.discriminatedUnion("kind", [
|
|
24556
|
+
// The finding became customer-visible. Derived from `publishedAt`, not from
|
|
24557
|
+
// an audit row, so every published finding carries exactly one regardless of
|
|
24558
|
+
// which path (batch publish, attack chain, legacy backfill) stamped it.
|
|
24559
|
+
external_exports.object({
|
|
24560
|
+
kind: external_exports.literal("published"),
|
|
24561
|
+
at: external_exports.coerce.date(),
|
|
24562
|
+
actor: external_exports.literal("TRACE").default("TRACE"),
|
|
24563
|
+
actorName: ResolutionTimelineActorName
|
|
24564
|
+
}),
|
|
24436
24565
|
external_exports.object({
|
|
24437
24566
|
kind: external_exports.literal("remediation_reported"),
|
|
24438
24567
|
at: external_exports.coerce.date(),
|
|
24439
24568
|
actor: ResolutionTimelineActor,
|
|
24440
24569
|
actorName: ResolutionTimelineActorName,
|
|
24441
24570
|
note: external_exports.string().nullable(),
|
|
24442
|
-
via: ResolutionTimelineVia
|
|
24571
|
+
via: ResolutionTimelineVia,
|
|
24572
|
+
editHandle: ResolutionTimelineEditHandleSchema
|
|
24443
24573
|
}),
|
|
24444
24574
|
// Retests are run by Trace (staff or a Trace agent), so the actor is fixed;
|
|
24445
24575
|
// the default keeps a web build ahead of the api parsing older payloads.
|
|
@@ -24449,14 +24579,16 @@ var ResolutionTimelineEntrySchema = external_exports.discriminatedUnion("kind",
|
|
|
24449
24579
|
actor: external_exports.literal("TRACE").default("TRACE"),
|
|
24450
24580
|
actorName: ResolutionTimelineActorName,
|
|
24451
24581
|
outcome: RetestOutcome,
|
|
24452
|
-
note: external_exports.string().nullable()
|
|
24582
|
+
note: external_exports.string().nullable(),
|
|
24583
|
+
editHandle: ResolutionTimelineEditHandleSchema
|
|
24453
24584
|
}),
|
|
24454
24585
|
external_exports.object({
|
|
24455
24586
|
kind: external_exports.literal("accepted_with_controls"),
|
|
24456
24587
|
at: external_exports.coerce.date(),
|
|
24457
24588
|
actor: ResolutionTimelineActor,
|
|
24458
24589
|
actorName: ResolutionTimelineActorName,
|
|
24459
|
-
note: external_exports.string()
|
|
24590
|
+
note: external_exports.string(),
|
|
24591
|
+
editHandle: ResolutionTimelineEditHandleSchema
|
|
24460
24592
|
}),
|
|
24461
24593
|
external_exports.object({
|
|
24462
24594
|
kind: external_exports.literal("dismissed"),
|
|
@@ -24464,7 +24596,8 @@ var ResolutionTimelineEntrySchema = external_exports.discriminatedUnion("kind",
|
|
|
24464
24596
|
actor: ResolutionTimelineActor,
|
|
24465
24597
|
actorName: ResolutionTimelineActorName,
|
|
24466
24598
|
reason: PentestFindingDismissReason,
|
|
24467
|
-
note: external_exports.string()
|
|
24599
|
+
note: external_exports.string(),
|
|
24600
|
+
editHandle: ResolutionTimelineEditHandleSchema
|
|
24468
24601
|
}),
|
|
24469
24602
|
// `note` nullable: webhook-driven reopens carry no customer note (the via
|
|
24470
24603
|
// badge is the payload); human reopens always have one.
|
|
@@ -24475,7 +24608,8 @@ var ResolutionTimelineEntrySchema = external_exports.discriminatedUnion("kind",
|
|
|
24475
24608
|
actorName: ResolutionTimelineActorName,
|
|
24476
24609
|
toStatus: external_exports.enum(["OPEN", "IN_PROGRESS"]),
|
|
24477
24610
|
note: external_exports.string().nullable(),
|
|
24478
|
-
via: ResolutionTimelineVia
|
|
24611
|
+
via: ResolutionTimelineVia,
|
|
24612
|
+
editHandle: ResolutionTimelineEditHandleSchema
|
|
24479
24613
|
}),
|
|
24480
24614
|
// Legacy/unattributed note whose transition isn't on the WORM audit log.
|
|
24481
24615
|
external_exports.object({
|
|
@@ -24483,7 +24617,8 @@ var ResolutionTimelineEntrySchema = external_exports.discriminatedUnion("kind",
|
|
|
24483
24617
|
at: external_exports.coerce.date(),
|
|
24484
24618
|
actor: ResolutionTimelineActor,
|
|
24485
24619
|
actorName: ResolutionTimelineActorName,
|
|
24486
|
-
note: external_exports.string()
|
|
24620
|
+
note: external_exports.string(),
|
|
24621
|
+
editHandle: ResolutionTimelineEditHandleSchema
|
|
24487
24622
|
}),
|
|
24488
24623
|
// Customer opened a dispute (PR 3). `note` = the customer's justification.
|
|
24489
24624
|
external_exports.object({
|
|
@@ -24503,7 +24638,8 @@ var ResolutionTimelineEntrySchema = external_exports.discriminatedUnion("kind",
|
|
|
24503
24638
|
actor: ResolutionTimelineActor,
|
|
24504
24639
|
actorName: ResolutionTimelineActorName,
|
|
24505
24640
|
resultStatus: PentestFindingStatus.nullable(),
|
|
24506
|
-
note: external_exports.string()
|
|
24641
|
+
note: external_exports.string(),
|
|
24642
|
+
editHandle: ResolutionTimelineEditHandleSchema
|
|
24507
24643
|
}),
|
|
24508
24644
|
// An external ticket was created from this finding (PR 2). No `note` — the
|
|
24509
24645
|
// identifier + url are the payload; actor is the customer who pushed it.
|
|
@@ -24650,7 +24786,24 @@ var RecoveredFindingResultSchema = external_exports.object({
|
|
|
24650
24786
|
var RecoverStrandedFindingsResponseSchema = external_exports.object({
|
|
24651
24787
|
results: external_exports.array(RecoveredFindingResultSchema)
|
|
24652
24788
|
});
|
|
24789
|
+
var DraftMissingFindingSummariesRequestSchema = external_exports.object({
|
|
24790
|
+
/** Limit the run to these findings; omit for every finding in the pentest missing a summary. */
|
|
24791
|
+
pentestFindingIds: external_exports.array(external_exports.uuid()).max(500).optional()
|
|
24792
|
+
});
|
|
24793
|
+
var DraftedFindingSummaryResultSchema = external_exports.object({
|
|
24794
|
+
pentestFindingId: external_exports.uuid(),
|
|
24795
|
+
displayId: external_exports.int().positive().nullable(),
|
|
24796
|
+
status: external_exports.enum(["written", "failed"]),
|
|
24797
|
+
/** Failure shape only (no prose): what the drafter returned and what was expected. */
|
|
24798
|
+
reason: external_exports.string().optional()
|
|
24799
|
+
});
|
|
24800
|
+
var DraftMissingFindingSummariesResponseSchema = external_exports.object({
|
|
24801
|
+
/** Findings that already had a summary and were not touched. */
|
|
24802
|
+
skipped: external_exports.int().min(0),
|
|
24803
|
+
results: external_exports.array(DraftedFindingSummaryResultSchema)
|
|
24804
|
+
});
|
|
24653
24805
|
var FindingProseSchema = FindingRewriteJsonSchema.pick({
|
|
24806
|
+
summary: true,
|
|
24654
24807
|
description: true,
|
|
24655
24808
|
rootCauseAnalysis: true,
|
|
24656
24809
|
impact: true,
|
|
@@ -24756,6 +24909,11 @@ var ResolvePentestFindingContestRequestSchema = external_exports.object({
|
|
|
24756
24909
|
dismissReason: PentestFindingDismissReason.optional(),
|
|
24757
24910
|
note: external_exports.string().min(1).max(1e4)
|
|
24758
24911
|
});
|
|
24912
|
+
var UpdateResolutionTimelineEntryRequestSchema = external_exports.object({
|
|
24913
|
+
source: ResolutionTimelineEntrySource,
|
|
24914
|
+
note: external_exports.string().trim().min(1).max(1e4)
|
|
24915
|
+
});
|
|
24916
|
+
var UpdateResolutionTimelineEntryResponseSchema = external_exports.array(ResolutionTimelineEntrySchema);
|
|
24759
24917
|
var PENTEST_FINDING_FEEDBACK_COMMENT_MAX_LENGTH = 5e3;
|
|
24760
24918
|
var CreatePentestFindingFeedbackRequestSchema = external_exports.object({
|
|
24761
24919
|
rating: PentestFindingFeedbackRating.nullable().default(null),
|
|
@@ -25531,8 +25689,19 @@ var SshPrivateKeySecretSchema = external_exports.object({
|
|
|
25531
25689
|
privateKeyPem: SshPrivateKeyPemSchema,
|
|
25532
25690
|
passphrase: external_exports.string().min(1).max(SSH_PASSPHRASE_MAX_CHARS).optional()
|
|
25533
25691
|
}).strict();
|
|
25692
|
+
var PasskeySecretSchema = external_exports.object({
|
|
25693
|
+
/** ECDSA P-256 private key, PKCS#8, base64. */
|
|
25694
|
+
privateKey: external_exports.string().min(1).max(4096),
|
|
25695
|
+
/** Opaque RP-assigned user id, base64 (≤64 bytes decoded). */
|
|
25696
|
+
userHandle: external_exports.string().max(128),
|
|
25697
|
+
userName: external_exports.string().max(320).optional(),
|
|
25698
|
+
userDisplayName: external_exports.string().max(320).optional(),
|
|
25699
|
+
/** largeBlob extension payload, base64. */
|
|
25700
|
+
largeBlob: external_exports.string().max(8192).optional()
|
|
25701
|
+
}).strict();
|
|
25534
25702
|
var CredentialMfaKindValues = ["TOTP"];
|
|
25535
25703
|
var CredentialMfaKind = external_exports.enum(CredentialMfaKindValues);
|
|
25704
|
+
var MFA_CAPABLE_CREDENTIAL_TYPES = ["USERNAME_PASSWORD", "MAGIC_AUTH"];
|
|
25536
25705
|
var TotpSetupKeySchema = external_exports.string().min(1).max(TOTP_MAX_SECRET_CHARS * 4).superRefine((value, ctx) => {
|
|
25537
25706
|
const message = totpSetupKeyError(value);
|
|
25538
25707
|
if (message !== null) ctx.addIssue({ code: "custom", message });
|
|
@@ -25999,6 +26168,9 @@ var ValidationResultProseSchema = external_exports.object({
|
|
|
25999
26168
|
// Optional/nullable on the wire for rolling-deploy safety (a pre-title host omits
|
|
26000
26169
|
// it); the api falls back to the candidate hypothesis title. The host tool requires it.
|
|
26001
26170
|
title: external_exports.string().min(1).max(300).nullable().optional(),
|
|
26171
|
+
// Same rolling-deploy shape as `title`: the host tool requires it, the wire
|
|
26172
|
+
// accepts its absence so a pre-summary host image still lands its verdict.
|
|
26173
|
+
summary: external_exports.string().min(1).max(1e3).nullable().optional(),
|
|
26002
26174
|
description: external_exports.string().min(1).max(12e3),
|
|
26003
26175
|
impact: external_exports.string().min(1).max(8e3),
|
|
26004
26176
|
remediation: external_exports.string().min(1).max(12e3),
|
|
@@ -26345,7 +26517,22 @@ var AgentSessionCreateTestEmailRequestSchema = external_exports.object({
|
|
|
26345
26517
|
* `too_big` blob for a purpose it then rephrased to something shorter and
|
|
26346
26518
|
* equivalent. Validate what the target requires, not what our storage prefers.
|
|
26347
26519
|
*/
|
|
26348
|
-
purpose: external_exports.string().min(1).max(200)
|
|
26520
|
+
purpose: external_exports.string().min(1).max(200),
|
|
26521
|
+
/**
|
|
26522
|
+
* Which Trace-managed mail domain to mint on. Omitted means the server's
|
|
26523
|
+
* default (`pentest.securewithtrace.com` unless overridden per environment).
|
|
26524
|
+
*
|
|
26525
|
+
* An enum rather than free text: the address is registered on the customer's
|
|
26526
|
+
* target as a login identity, so the set of domains mail can be minted on is
|
|
26527
|
+
* a Trace decision, not an agent one. The alternates exist for a target that
|
|
26528
|
+
* blocks or rate-limits one domain, or a check that needs two accounts whose
|
|
26529
|
+
* email domains differ.
|
|
26530
|
+
*
|
|
26531
|
+
* Reuse of an unsaved address is per domain: asking for the alternate never
|
|
26532
|
+
* hands back an address minted on the default, since the agent asked for a
|
|
26533
|
+
* different domain precisely because that one did not do.
|
|
26534
|
+
*/
|
|
26535
|
+
domain: ManagedInboxDomainSchema.optional()
|
|
26349
26536
|
});
|
|
26350
26537
|
var AgentSessionCreateTestEmailResponseSchema = external_exports.object({
|
|
26351
26538
|
/**
|
|
@@ -26528,6 +26715,74 @@ var AgentSessionSessionStateResponseSchema = external_exports.object({
|
|
|
26528
26715
|
/** The decrypted, parsed captured browser session. */
|
|
26529
26716
|
state: StorageStateSchema
|
|
26530
26717
|
});
|
|
26718
|
+
var AgentSessionGetSshPrivateKeyRequestSchema = external_exports.object({
|
|
26719
|
+
runVersion: external_exports.number().int().nonnegative(),
|
|
26720
|
+
/** Which SSH_PRIVATE_KEY credential to fetch (from the non-secret env reference). */
|
|
26721
|
+
credentialId: external_exports.uuid()
|
|
26722
|
+
});
|
|
26723
|
+
var AgentSessionSshPrivateKeyResponseSchema = external_exports.object({
|
|
26724
|
+
credentialId: external_exports.uuid(),
|
|
26725
|
+
/** `Credential.updatedAt` (ISO): logged so a run names the revision it loaded. */
|
|
26726
|
+
revision: external_exports.iso.datetime(),
|
|
26727
|
+
/**
|
|
26728
|
+
* The decrypted key material. Returned VERBATIM as stored: the host seeds its
|
|
26729
|
+
* redactor with these exact bytes, and `redactString` matches exact substrings,
|
|
26730
|
+
* so normalizing line endings here would silently defeat redaction downstream.
|
|
26731
|
+
*/
|
|
26732
|
+
secret: SshPrivateKeySecretSchema
|
|
26733
|
+
});
|
|
26734
|
+
var AgentSessionPasskeySchema = external_exports.object({
|
|
26735
|
+
/** Relying party id (a hostname). */
|
|
26736
|
+
rpId: external_exports.string().min(1).max(253),
|
|
26737
|
+
/** WebAuthn credential id, base64 (≤1023 bytes decoded). */
|
|
26738
|
+
webauthnCredentialId: external_exports.string().min(1).max(1400),
|
|
26739
|
+
/** ECDSA P-256 private key, PKCS#8, base64. */
|
|
26740
|
+
privateKey: external_exports.string().min(1).max(4096).optional(),
|
|
26741
|
+
/** Opaque RP-assigned user id, base64. */
|
|
26742
|
+
userHandle: external_exports.string().max(128).optional(),
|
|
26743
|
+
userName: external_exports.string().max(320).optional(),
|
|
26744
|
+
userDisplayName: external_exports.string().max(320).optional(),
|
|
26745
|
+
largeBlob: external_exports.string().max(8192).optional(),
|
|
26746
|
+
/** Signature counter as the authenticator reports it. Never -1 on this wire. */
|
|
26747
|
+
signCount: external_exports.number().int().min(0),
|
|
26748
|
+
backupEligibility: external_exports.boolean(),
|
|
26749
|
+
backupState: external_exports.boolean()
|
|
26750
|
+
});
|
|
26751
|
+
var AgentSessionListPasskeysRequestSchema = external_exports.object({
|
|
26752
|
+
runVersion: external_exports.number().int().nonnegative(),
|
|
26753
|
+
/** Which account (by `Credential.label`). Required: the host persists under it. */
|
|
26754
|
+
credentialLabel: external_exports.string().min(1).max(200)
|
|
26755
|
+
});
|
|
26756
|
+
var AgentSessionStoredPasskeySchema = AgentSessionPasskeySchema.extend({
|
|
26757
|
+
credentialPasskeyId: external_exports.uuid(),
|
|
26758
|
+
privateKey: external_exports.string().min(1).max(4096),
|
|
26759
|
+
userHandle: external_exports.string().max(128)
|
|
26760
|
+
});
|
|
26761
|
+
var AgentSessionListPasskeysResponseSchema = external_exports.object({
|
|
26762
|
+
credentialId: external_exports.uuid(),
|
|
26763
|
+
credentialLabel: external_exports.string().max(200),
|
|
26764
|
+
/** The account types a browser logs in as; the same set that can carry a second factor. */
|
|
26765
|
+
credentialType: external_exports.enum(MFA_CAPABLE_CREDENTIAL_TYPES),
|
|
26766
|
+
passkeys: external_exports.array(AgentSessionStoredPasskeySchema),
|
|
26767
|
+
/**
|
|
26768
|
+
* Stored rows whose envelope could not be read. Reported rather than swallowed so a
|
|
26769
|
+
* partial list is never presented to the agent as the whole store, and rather than
|
|
26770
|
+
* failing the call so the readable passkeys still load.
|
|
26771
|
+
*/
|
|
26772
|
+
unreadable: external_exports.number().int().min(0)
|
|
26773
|
+
});
|
|
26774
|
+
var AgentSessionUpsertPasskeyRequestSchema = external_exports.object({
|
|
26775
|
+
runVersion: external_exports.number().int().nonnegative(),
|
|
26776
|
+
/** From the list response, never agent-supplied. */
|
|
26777
|
+
credentialId: external_exports.uuid(),
|
|
26778
|
+
passkey: AgentSessionPasskeySchema
|
|
26779
|
+
});
|
|
26780
|
+
var AgentSessionUpsertPasskeyResponseSchema = external_exports.object({
|
|
26781
|
+
credentialPasskeyId: external_exports.uuid(),
|
|
26782
|
+
created: external_exports.boolean(),
|
|
26783
|
+
/** The stored counter after the write (never lower than before). */
|
|
26784
|
+
signCount: external_exports.number().int().min(0)
|
|
26785
|
+
});
|
|
26531
26786
|
var ParkAgentSessionRequestSchema = external_exports.object({
|
|
26532
26787
|
runVersion: external_exports.number().int().nonnegative(),
|
|
26533
26788
|
outcome: external_exports.enum(["IDLE", "WAITING_ON_STAFF", "FAILED"]),
|
|
@@ -27239,9 +27494,8 @@ var agentSessionInternalContract = c4.router({
|
|
|
27239
27494
|
// Session type receives no account credentials, or its pinned toolset does
|
|
27240
27495
|
// not carry these tools.
|
|
27241
27496
|
403: ErrorResponseSchema,
|
|
27242
|
-
// RUN_REVOKED
|
|
27243
|
-
//
|
|
27244
|
-
// would read as run revocation and kill the session.
|
|
27497
|
+
// RUN_REVOKED or INBOX_ALREADY_EXISTS. The host treats only RUN_REVOKED
|
|
27498
|
+
// as fatal; a concurrent label claim is a recoverable conflict.
|
|
27245
27499
|
409: ErrorResponseSchema,
|
|
27246
27500
|
// The org is at its managed-inbox quota (INBOX_QUOTA_EXCEEDED) — the agent
|
|
27247
27501
|
// has no delete tool, so it must escalate rather than retry. Also
|
|
@@ -27344,8 +27598,78 @@ var agentSessionInternalContract = c4.router({
|
|
|
27344
27598
|
401: ErrorResponseSchema,
|
|
27345
27599
|
409: ErrorResponseSchema,
|
|
27346
27600
|
// Credential not found, not SESSION_STATE, or outside the run's
|
|
27347
|
-
// org/pentest/environments
|
|
27601
|
+
// org/pentest/environments: no cross-tenant existence leak.
|
|
27602
|
+
404: ErrorResponseSchema,
|
|
27603
|
+
500: ErrorResponseSchema
|
|
27604
|
+
}
|
|
27605
|
+
},
|
|
27606
|
+
// get_ssh_private_key: the SESSION_STATE counterpart for SSH_PRIVATE_KEY. Same
|
|
27607
|
+
// scoping (org + pentest environments from the run token), same 404 for anything
|
|
27608
|
+
// outside it. The host calls this once per reference at boot and pipes the result
|
|
27609
|
+
// into ssh-add; nothing downstream can read the key back.
|
|
27610
|
+
getSshPrivateKey: {
|
|
27611
|
+
method: "POST",
|
|
27612
|
+
path: "/internal/v1/agent-sessions/:agentSessionId/ssh-private-key",
|
|
27613
|
+
pathParams: AgentSessionInternalParamsSchema,
|
|
27614
|
+
body: AgentSessionGetSshPrivateKeyRequestSchema,
|
|
27615
|
+
responses: {
|
|
27616
|
+
200: AgentSessionSshPrivateKeyResponseSchema,
|
|
27617
|
+
400: ErrorResponseSchema,
|
|
27618
|
+
401: ErrorResponseSchema,
|
|
27619
|
+
409: ErrorResponseSchema,
|
|
27620
|
+
// Credential not found, not SSH_PRIVATE_KEY, or outside the run's
|
|
27621
|
+
// org/pentest/environments: no cross-tenant existence leak.
|
|
27622
|
+
404: ErrorResponseSchema,
|
|
27623
|
+
500: ErrorResponseSchema
|
|
27624
|
+
}
|
|
27625
|
+
},
|
|
27626
|
+
// provision_passkey, read half: every passkey Trace holds for one of the run's
|
|
27627
|
+
// accounts, decrypted, so the host can load them into the virtual authenticator
|
|
27628
|
+
// it arms on the agent's browser. Same resolution and ownership gate as
|
|
27629
|
+
// enroll-mfa (org + pentest from the run token; label must resolve to exactly
|
|
27630
|
+
// one launch-visible credential). Returns 200 with an empty array for an
|
|
27631
|
+
// account with no passkeys yet: that is how the host learns the credentialId
|
|
27632
|
+
// it will persist a new one under.
|
|
27633
|
+
listPasskeys: {
|
|
27634
|
+
method: "POST",
|
|
27635
|
+
path: "/internal/v1/agent-sessions/:agentSessionId/passkeys",
|
|
27636
|
+
pathParams: AgentSessionInternalParamsSchema,
|
|
27637
|
+
body: AgentSessionListPasskeysRequestSchema,
|
|
27638
|
+
responses: {
|
|
27639
|
+
200: AgentSessionListPasskeysResponseSchema,
|
|
27640
|
+
// Label on more than one environment (terminal), or a Trace fixture label.
|
|
27641
|
+
400: ErrorResponseSchema,
|
|
27642
|
+
401: ErrorResponseSchema,
|
|
27643
|
+
// Session type receives no account credentials.
|
|
27644
|
+
403: ErrorResponseSchema,
|
|
27645
|
+
// No launch-visible credential with that label; same message for another
|
|
27646
|
+
// tenant's.
|
|
27647
|
+
404: ErrorResponseSchema,
|
|
27648
|
+
// RUN_REVOKED, including the liveness recheck inside the service.
|
|
27649
|
+
409: ErrorResponseSchema,
|
|
27650
|
+
// The credential's type is not an account a browser logs in as.
|
|
27651
|
+
422: ErrorResponseSchema,
|
|
27652
|
+
500: ErrorResponseSchema
|
|
27653
|
+
}
|
|
27654
|
+
},
|
|
27655
|
+
// provision_passkey, write half: the host observed a passkey on the armed
|
|
27656
|
+
// authenticator (new, or its counter moved) and persists it. Keyed by the
|
|
27657
|
+
// credentialId the list call returned, never by label, and idempotent on
|
|
27658
|
+
// (credentialId, rpId, webauthnCredentialId) because the host retries on 5xx.
|
|
27659
|
+
upsertPasskey: {
|
|
27660
|
+
method: "POST",
|
|
27661
|
+
path: "/internal/v1/agent-sessions/:agentSessionId/passkeys/upsert",
|
|
27662
|
+
pathParams: AgentSessionInternalParamsSchema,
|
|
27663
|
+
body: AgentSessionUpsertPasskeyRequestSchema,
|
|
27664
|
+
responses: {
|
|
27665
|
+
200: AgentSessionUpsertPasskeyResponseSchema,
|
|
27666
|
+
// PASSKEY_SECRET_REQUIRED: no row for the natural key and no key in the body.
|
|
27667
|
+
400: ErrorResponseSchema,
|
|
27668
|
+
401: ErrorResponseSchema,
|
|
27669
|
+
403: ErrorResponseSchema,
|
|
27670
|
+
// credentialId not on this run's pentest under this org.
|
|
27348
27671
|
404: ErrorResponseSchema,
|
|
27672
|
+
409: ErrorResponseSchema,
|
|
27349
27673
|
500: ErrorResponseSchema
|
|
27350
27674
|
}
|
|
27351
27675
|
},
|
|
@@ -30704,7 +31028,13 @@ var VulnerabilityTypeValues = [
|
|
|
30704
31028
|
"MISCONFIGURATION"
|
|
30705
31029
|
];
|
|
30706
31030
|
var VulnerabilityType = external_exports.enum(VulnerabilityTypeValues);
|
|
30707
|
-
var DismissReasonValues = [
|
|
31031
|
+
var DismissReasonValues = [
|
|
31032
|
+
"ACCEPTED_RISK",
|
|
31033
|
+
"FALSE_POSITIVE",
|
|
31034
|
+
"OTHER",
|
|
31035
|
+
"INTENDED_BEHAVIOR",
|
|
31036
|
+
"DEFERRED"
|
|
31037
|
+
];
|
|
30708
31038
|
var DismissReason = external_exports.enum(DismissReasonValues);
|
|
30709
31039
|
|
|
30710
31040
|
// ../../packages/api-contracts/src/schemas/api/v1/engagement.api.ts
|
|
@@ -31274,72 +31604,6 @@ var staffGithubContract = c18.router({
|
|
|
31274
31604
|
}
|
|
31275
31605
|
});
|
|
31276
31606
|
|
|
31277
|
-
// ../../packages/api-contracts/src/schemas/api/v1/inbox.api.ts
|
|
31278
|
-
var InboxLabelSchema = external_exports.string().regex(
|
|
31279
|
-
/^[a-z][a-z0-9-]{0,39}$/,
|
|
31280
|
-
"Inbox label must be lowercase letters/digits/dashes, starting with a letter, at most 40 characters"
|
|
31281
|
-
);
|
|
31282
|
-
var MAX_INBOX_LABEL_LENGTH = 40;
|
|
31283
|
-
var MAX_LOCAL_PART_LENGTH = 64;
|
|
31284
|
-
var MAX_INBOX_ADDRESS_PREFIX_LENGTH = MAX_LOCAL_PART_LENGTH - MAX_INBOX_LABEL_LENGTH - 1;
|
|
31285
|
-
var ManagedInboxResponseSchema = external_exports.object({
|
|
31286
|
-
/** Full derived address, e.g. `acme-qa-admin@pentest.securewithtrace.com`. */
|
|
31287
|
-
address: external_exports.email(),
|
|
31288
|
-
label: external_exports.string(),
|
|
31289
|
-
createdAt: external_exports.date()
|
|
31290
|
-
});
|
|
31291
|
-
var ListManagedInboxesResponseSchema = external_exports.object({
|
|
31292
|
-
inboxes: external_exports.array(ManagedInboxResponseSchema),
|
|
31293
|
-
quota: external_exports.object({
|
|
31294
|
-
used: external_exports.number().int().nonnegative(),
|
|
31295
|
-
limit: external_exports.number().int().positive()
|
|
31296
|
-
})
|
|
31297
|
-
});
|
|
31298
|
-
var CreateManagedInboxRequestSchema = external_exports.object({
|
|
31299
|
-
label: InboxLabelSchema
|
|
31300
|
-
}).strict();
|
|
31301
|
-
var ManagedInboxLabelParamsSchema = OrgIdParamsSchema.extend({
|
|
31302
|
-
label: external_exports.string().min(1).max(64)
|
|
31303
|
-
});
|
|
31304
|
-
var ManagedInboxMessageParamsSchema = ManagedInboxLabelParamsSchema.extend({
|
|
31305
|
-
messageId: external_exports.string().min(1).max(500)
|
|
31306
|
-
});
|
|
31307
|
-
var ListInboxMessagesQuerySchema = external_exports.object({
|
|
31308
|
-
/** Max messages, newest first. The provider may clamp further. */
|
|
31309
|
-
limit: external_exports.coerce.number().int().positive().max(50).optional(),
|
|
31310
|
-
/** Only messages received strictly after this instant. */
|
|
31311
|
-
since: external_exports.coerce.date().optional()
|
|
31312
|
-
}).strict();
|
|
31313
|
-
var ManagedInboxMessageSummarySchema = external_exports.object({
|
|
31314
|
-
messageId: external_exports.string().min(1).max(500),
|
|
31315
|
-
from: external_exports.string().max(500),
|
|
31316
|
-
/** Recipients as the provider returned them (display-name form allowed). */
|
|
31317
|
-
to: external_exports.array(external_exports.string().max(500)),
|
|
31318
|
-
subject: external_exports.string().max(1e3).nullable(),
|
|
31319
|
-
preview: external_exports.string().max(2e3).nullable(),
|
|
31320
|
-
receivedAt: external_exports.coerce.date(),
|
|
31321
|
-
unread: external_exports.boolean()
|
|
31322
|
-
});
|
|
31323
|
-
var ListInboxMessagesResponseSchema = external_exports.object({
|
|
31324
|
-
address: external_exports.email(),
|
|
31325
|
-
label: external_exports.string(),
|
|
31326
|
-
messages: external_exports.array(ManagedInboxMessageSummarySchema)
|
|
31327
|
-
});
|
|
31328
|
-
var GetInboxMessageResponseSchema = external_exports.object({
|
|
31329
|
-
address: external_exports.email(),
|
|
31330
|
-
label: external_exports.string(),
|
|
31331
|
-
message: ManagedInboxMessageSummarySchema.extend({
|
|
31332
|
-
body: external_exports.string(),
|
|
31333
|
-
truncated: external_exports.boolean(),
|
|
31334
|
-
/**
|
|
31335
|
-
* http(s) links extracted from the message (hrefs first, then inline
|
|
31336
|
-
* text URLs). Button-style mail often carries the magic link only in
|
|
31337
|
-
* `href`, which a tag-stripped body can still miss if extraction fails.
|
|
31338
|
-
*/
|
|
31339
|
-
links: external_exports.array(external_exports.string())
|
|
31340
|
-
})
|
|
31341
|
-
});
|
|
31342
|
-
|
|
31343
31607
|
// ../../packages/api-contracts/src/contracts/v1/inbox.ts
|
|
31344
31608
|
var c19 = initContract();
|
|
31345
31609
|
var inboxContract = c19.router({
|
|
@@ -32517,9 +32781,35 @@ var PrScanAnalyticsMonthSchema = external_exports.object({
|
|
|
32517
32781
|
/** Same metrics as the month, split by PR author. Sorted by findings, then PRs. */
|
|
32518
32782
|
developers: external_exports.array(PrScanAnalyticsDeveloperSchema)
|
|
32519
32783
|
});
|
|
32784
|
+
var PrScanAnalyticsClassCountSchema = external_exports.object({
|
|
32785
|
+
label: external_exports.string(),
|
|
32786
|
+
count: external_exports.number().int().nonnegative()
|
|
32787
|
+
});
|
|
32788
|
+
var PrScanAnalyticsTimeToFixSchema = external_exports.object({
|
|
32789
|
+
severity: VulnerabilitySeverity,
|
|
32790
|
+
fixedCount: external_exports.number().int().nonnegative(),
|
|
32791
|
+
/** Median minutes from the finding's first detection to the fixing scan; null when nothing was fixed. */
|
|
32792
|
+
medianMinutes: external_exports.number().nonnegative().nullable(),
|
|
32793
|
+
fixedWithinDayCount: external_exports.number().int().nonnegative()
|
|
32794
|
+
});
|
|
32795
|
+
var PrScanAnalyticsSummarySchema = external_exports.object({
|
|
32796
|
+
/** First completed PR scan on a tracked PR in the window; null when none. */
|
|
32797
|
+
since: external_exports.iso.datetime().nullable(),
|
|
32798
|
+
prCount: external_exports.number().int().nonnegative(),
|
|
32799
|
+
findingsCount: external_exports.number().int().nonnegative(),
|
|
32800
|
+
criticalHighCount: external_exports.number().int().nonnegative(),
|
|
32801
|
+
criticalHighFixedBeforeMergeCount: external_exports.number().int().nonnegative(),
|
|
32802
|
+
/** Critical/high fixed on a PR that is still open. */
|
|
32803
|
+
criticalHighFixedAwaitingMergeCount: external_exports.number().int().nonnegative(),
|
|
32804
|
+
/** Critical/high by weakness class, largest first, at most five rows with "Other" last. */
|
|
32805
|
+
criticalHighByClass: external_exports.array(PrScanAnalyticsClassCountSchema),
|
|
32806
|
+
/** One row per severity, CRITICAL first. */
|
|
32807
|
+
timeToFix: external_exports.array(PrScanAnalyticsTimeToFixSchema).length(VulnerabilitySeverityValues.length)
|
|
32808
|
+
});
|
|
32520
32809
|
var PrScanAnalyticsResponseSchema = external_exports.object({
|
|
32521
32810
|
generatedAt: external_exports.iso.datetime(),
|
|
32522
|
-
months: external_exports.array(PrScanAnalyticsMonthSchema).length(12)
|
|
32811
|
+
months: external_exports.array(PrScanAnalyticsMonthSchema).length(12),
|
|
32812
|
+
summary: PrScanAnalyticsSummarySchema
|
|
32523
32813
|
});
|
|
32524
32814
|
var ProductPlanParamsSchema = external_exports.object({
|
|
32525
32815
|
organizationId: external_exports.uuid(),
|
|
@@ -33793,6 +34083,26 @@ var pentestAttackChainContract = c29.router({
|
|
|
33793
34083
|
500: ErrorResponseSchema
|
|
33794
34084
|
}
|
|
33795
34085
|
},
|
|
34086
|
+
/**
|
|
34087
|
+
* The "Publish all" control. Mirrors `pentestFindingContract.publishAllFindings`
|
|
34088
|
+
* (`/findings/publications`): a collection resource of publications rather than a verb
|
|
34089
|
+
* on the chain list.
|
|
34090
|
+
*/
|
|
34091
|
+
publishAllAttackChains: {
|
|
34092
|
+
method: "POST",
|
|
34093
|
+
path: "/organizations/:organizationId/pentests/:pentestId/attack-chains/publications",
|
|
34094
|
+
pathParams: PentestAttackChainParamsSchema,
|
|
34095
|
+
body: PublishAllAttackChainsRequestSchema,
|
|
34096
|
+
responses: {
|
|
34097
|
+
200: PublishAllAttackChainsResponseSchema,
|
|
34098
|
+
400: ErrorResponseSchema,
|
|
34099
|
+
401: ErrorResponseSchema,
|
|
34100
|
+
403: ErrorResponseSchema,
|
|
34101
|
+
404: ErrorResponseSchema,
|
|
34102
|
+
409: ErrorResponseSchema,
|
|
34103
|
+
500: ErrorResponseSchema
|
|
34104
|
+
}
|
|
34105
|
+
},
|
|
33796
34106
|
unpublishAttackChain: {
|
|
33797
34107
|
method: "POST",
|
|
33798
34108
|
path: "/organizations/:organizationId/pentests/:pentestId/attack-chains/:attackChainId/unpublish",
|
|
@@ -34647,6 +34957,22 @@ var pentestFindingContract = c36.router({
|
|
|
34647
34957
|
500: ErrorResponseSchema
|
|
34648
34958
|
}
|
|
34649
34959
|
},
|
|
34960
|
+
draftMissingFindingSummaries: {
|
|
34961
|
+
method: "POST",
|
|
34962
|
+
path: "/organizations/:organizationId/pentests/:pentestId/findings/summaries/draft",
|
|
34963
|
+
pathParams: PentestFindingParamsSchema,
|
|
34964
|
+
body: DraftMissingFindingSummariesRequestSchema,
|
|
34965
|
+
responses: {
|
|
34966
|
+
200: DraftMissingFindingSummariesResponseSchema,
|
|
34967
|
+
400: ErrorResponseSchema,
|
|
34968
|
+
401: ErrorResponseSchema,
|
|
34969
|
+
403: ErrorResponseSchema,
|
|
34970
|
+
404: ErrorResponseSchema,
|
|
34971
|
+
// PENTEST_CLOSED (reopen first).
|
|
34972
|
+
409: ErrorResponseSchema,
|
|
34973
|
+
500: ErrorResponseSchema
|
|
34974
|
+
}
|
|
34975
|
+
},
|
|
34650
34976
|
applyFindingCvss: {
|
|
34651
34977
|
method: "PUT",
|
|
34652
34978
|
path: "/organizations/:organizationId/pentests/:pentestId/findings/:pentestFindingId/cvss",
|
|
@@ -34924,6 +35250,26 @@ var pentestFindingContract = c36.router({
|
|
|
34924
35250
|
},
|
|
34925
35251
|
// Note CRUD removed in PR 2 — the resolution timeline replaces the read
|
|
34926
35252
|
// surface; resolution workflows + the operator agent write notes directly.
|
|
35253
|
+
// ─── Resolution timeline ───
|
|
35254
|
+
// Staff-only: rewrite the customer-visible note behind one history entry.
|
|
35255
|
+
// `entryId` + `body.source` come from the entry's `editHandle` on the staff GET.
|
|
35256
|
+
updateTimelineEntry: {
|
|
35257
|
+
method: "PATCH",
|
|
35258
|
+
path: "/organizations/:organizationId/pentests/:pentestId/findings/:pentestFindingId/timeline-entries/:entryId",
|
|
35259
|
+
pathParams: PentestFindingTimelineEntryParamsSchema,
|
|
35260
|
+
body: UpdateResolutionTimelineEntryRequestSchema,
|
|
35261
|
+
responses: {
|
|
35262
|
+
200: UpdateResolutionTimelineEntryResponseSchema,
|
|
35263
|
+
400: ErrorResponseSchema,
|
|
35264
|
+
401: ErrorResponseSchema,
|
|
35265
|
+
// Non-staff caller, or the entry is customer-authored.
|
|
35266
|
+
403: ErrorResponseSchema,
|
|
35267
|
+
404: ErrorResponseSchema,
|
|
35268
|
+
// PENTEST_CLOSED.
|
|
35269
|
+
409: ErrorResponseSchema,
|
|
35270
|
+
500: ErrorResponseSchema
|
|
35271
|
+
}
|
|
35272
|
+
},
|
|
34927
35273
|
// ─── AI parsing ───
|
|
34928
35274
|
parseFindingMarkdown: {
|
|
34929
35275
|
method: "POST",
|
|
@@ -45425,7 +45771,7 @@ function collectAdditionalUrl(value, previous) {
|
|
|
45425
45771
|
function buildCreateEnvironmentBody(input) {
|
|
45426
45772
|
return {
|
|
45427
45773
|
name: input.name,
|
|
45428
|
-
primaryUrl: input.primaryUrl,
|
|
45774
|
+
...input.primaryUrl !== void 0 ? { primaryUrl: input.primaryUrl } : {},
|
|
45429
45775
|
...input.additionalUrls !== void 0 && input.additionalUrls.length > 0 ? { additionalUrls: input.additionalUrls } : {},
|
|
45430
45776
|
...input.description !== void 0 ? { description: input.description } : {},
|
|
45431
45777
|
...input.isProduction !== void 0 ? { isProduction: input.isProduction } : {}
|
|
@@ -45909,13 +46255,20 @@ async function runDelete4(environmentId, options) {
|
|
|
45909
46255
|
console.log(colorSuccess(`Deleted environment ${environmentId}`));
|
|
45910
46256
|
}
|
|
45911
46257
|
function createCreateCommand4() {
|
|
45912
|
-
return new Command("create").description("Create an environment for an application").argument("<name>", "Environment name (lowercase letters/digits/dashes)").requiredOption("--app <applicationId>", "Parent application UUID").
|
|
46258
|
+
return new Command("create").description("Create an environment for an application").argument("<name>", "Environment name (lowercase letters/digits/dashes)").requiredOption("--app <applicationId>", "Parent application UUID").option(
|
|
46259
|
+
"--url <url>",
|
|
46260
|
+
"Primary URL for the environment (web and API applications only; omit for CLI and desktop)"
|
|
46261
|
+
).option("--additional-url <url>", "Extra probe URL (repeatable)", collectAdditionalUrl).option("--description <text>", "Description").option("--production", "Mark as the production environment").option("-o, --output <format>", "Output format: table, json, tsv (default: table)", "table").addHelpText(
|
|
45913
46262
|
"after",
|
|
45914
46263
|
`
|
|
46264
|
+
A web or API application requires --url. A CLI or desktop application is a
|
|
46265
|
+
credential context with no network target and must omit it.
|
|
46266
|
+
|
|
45915
46267
|
Examples:
|
|
45916
46268
|
tracecli environment create staging --app <appId> --url https://staging.acme.com
|
|
45917
46269
|
tracecli environment create production --app <appId> --url https://acme.com --production
|
|
45918
|
-
tracecli environment create production --app <appId> --url https://app.acme.com --additional-url https://api.acme.com
|
|
46270
|
+
tracecli environment create production --app <appId> --url https://app.acme.com --additional-url https://api.acme.com
|
|
46271
|
+
tracecli environment create pentest-sandbox --app <cliAppId>`
|
|
45919
46272
|
).action(async (name, options) => {
|
|
45920
46273
|
await runCreate4(name, options);
|
|
45921
46274
|
});
|
|
@@ -46485,117 +46838,6 @@ Examples:
|
|
|
46485
46838
|
});
|
|
46486
46839
|
}
|
|
46487
46840
|
|
|
46488
|
-
// src/pentest/questionnaire.ts
|
|
46489
|
-
function buildUpsertQuestionnaireBody(answers) {
|
|
46490
|
-
if (answers === null) {
|
|
46491
|
-
return { answers: null };
|
|
46492
|
-
}
|
|
46493
|
-
return { answers: normalizePentestCustomerIntakeAnswers(answers) };
|
|
46494
|
-
}
|
|
46495
|
-
function parseQuestionnaireAnswersFile(raw, sourceLabel = "file") {
|
|
46496
|
-
let parsed;
|
|
46497
|
-
try {
|
|
46498
|
-
parsed = JSON.parse(raw);
|
|
46499
|
-
} catch {
|
|
46500
|
-
return { error: `Invalid JSON in ${sourceLabel}.` };
|
|
46501
|
-
}
|
|
46502
|
-
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
46503
|
-
return {
|
|
46504
|
-
error: `${sourceLabel} must be a JSON object of questionnaire answers (not an array or null).`
|
|
46505
|
-
};
|
|
46506
|
-
}
|
|
46507
|
-
if ("answers" in parsed && Object.keys(parsed).length === 1) {
|
|
46508
|
-
return {
|
|
46509
|
-
error: `${sourceLabel} must contain the answers object directly, not an { answers } wrapper. The CLI wraps it for the API.`
|
|
46510
|
-
};
|
|
46511
|
-
}
|
|
46512
|
-
const result = PentestCustomerIntakeAnswersSchema.safeParse(parsed);
|
|
46513
|
-
if (!result.success) {
|
|
46514
|
-
const issue2 = result.error.issues[0];
|
|
46515
|
-
const path = issue2?.path.length ? issue2.path.join(".") : "answers";
|
|
46516
|
-
const message = issue2?.message ?? "invalid";
|
|
46517
|
-
return { error: `Invalid questionnaire answers in ${sourceLabel}: ${path} \u2014 ${message}` };
|
|
46518
|
-
}
|
|
46519
|
-
return { answers: normalizePentestCustomerIntakeAnswers(result.data) };
|
|
46520
|
-
}
|
|
46521
|
-
function applyTextIntakeAnswer(current, key, input) {
|
|
46522
|
-
const trimmed = input.trim();
|
|
46523
|
-
if (trimmed.length === 0) {
|
|
46524
|
-
return current;
|
|
46525
|
-
}
|
|
46526
|
-
return { ...current, [key]: trimmed };
|
|
46527
|
-
}
|
|
46528
|
-
function applyMultiSelectIntakeAnswer(current, key, selected) {
|
|
46529
|
-
if (selected.length === 0) {
|
|
46530
|
-
return current;
|
|
46531
|
-
}
|
|
46532
|
-
return {
|
|
46533
|
-
...current,
|
|
46534
|
-
[key]: Array.from(new Set(selected)).sort()
|
|
46535
|
-
};
|
|
46536
|
-
}
|
|
46537
|
-
function formatIssueClasses(values) {
|
|
46538
|
-
return values.map((v) => INTAKE_ISSUE_CLASS_LABELS[v]).join(", ");
|
|
46539
|
-
}
|
|
46540
|
-
function formatSurfaces(values) {
|
|
46541
|
-
return values.map((v) => INTAKE_SURFACE_LABELS[v]).join(", ");
|
|
46542
|
-
}
|
|
46543
|
-
function formatAnswerValue(key, answers) {
|
|
46544
|
-
switch (key) {
|
|
46545
|
-
case "issueClasses": {
|
|
46546
|
-
const values = answers.issueClasses;
|
|
46547
|
-
if (values === void 0 || values.length === 0) {
|
|
46548
|
-
return null;
|
|
46549
|
-
}
|
|
46550
|
-
return formatIssueClasses(values);
|
|
46551
|
-
}
|
|
46552
|
-
case "surfaces": {
|
|
46553
|
-
const values = answers.surfaces;
|
|
46554
|
-
if (values === void 0 || values.length === 0) {
|
|
46555
|
-
return null;
|
|
46556
|
-
}
|
|
46557
|
-
return formatSurfaces(values);
|
|
46558
|
-
}
|
|
46559
|
-
default: {
|
|
46560
|
-
const value = answers[key];
|
|
46561
|
-
if (value === void 0 || value.trim().length === 0) {
|
|
46562
|
-
return null;
|
|
46563
|
-
}
|
|
46564
|
-
return value;
|
|
46565
|
-
}
|
|
46566
|
-
}
|
|
46567
|
-
}
|
|
46568
|
-
function formatQuestionnaireShow(input) {
|
|
46569
|
-
const submitted = input.submitted === true;
|
|
46570
|
-
const editableLine = input.editable ? "yes (scoping window open)" : submitted ? "no (submitted)" : "no (editing closed after scoping)";
|
|
46571
|
-
const lines = [`Editable: ${editableLine}`, `Submitted: ${submitted ? "yes" : "no"}`];
|
|
46572
|
-
if (input.answers === null || Object.keys(input.answers).length === 0) {
|
|
46573
|
-
lines.push("Answers: (none)");
|
|
46574
|
-
return lines.join("\n");
|
|
46575
|
-
}
|
|
46576
|
-
lines.push("Answers:");
|
|
46577
|
-
for (const key of PENTEST_CUSTOMER_INTAKE_ANSWER_KEYS) {
|
|
46578
|
-
const value = formatAnswerValue(key, input.answers);
|
|
46579
|
-
if (value === null) {
|
|
46580
|
-
continue;
|
|
46581
|
-
}
|
|
46582
|
-
lines.push(`${PENTEST_CUSTOMER_INTAKE_LABELS[key]}: ${value}`);
|
|
46583
|
-
}
|
|
46584
|
-
return lines.join("\n");
|
|
46585
|
-
}
|
|
46586
|
-
function issueClassSelectOptions() {
|
|
46587
|
-
return CustomerPriorityCategoryValues.map((value) => ({
|
|
46588
|
-
value,
|
|
46589
|
-
label: INTAKE_ISSUE_CLASS_LABELS[value]
|
|
46590
|
-
}));
|
|
46591
|
-
}
|
|
46592
|
-
function surfaceSelectOptions() {
|
|
46593
|
-
return IntakeSurfaceValues.map((value) => ({
|
|
46594
|
-
value,
|
|
46595
|
-
label: INTAKE_SURFACE_LABELS[value]
|
|
46596
|
-
}));
|
|
46597
|
-
}
|
|
46598
|
-
|
|
46599
46841
|
// src/pentest/status-update.ts
|
|
46600
46842
|
var PENTEST_IDENTIFIER_PATTERN = /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|pt-\d+|\d+)$/i;
|
|
46601
46843
|
var FINDING_IDENTIFIER_PATTERN = /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|ptf-\d+|\d+)$/i;
|
|
@@ -46777,6 +47019,258 @@ Examples:
|
|
|
46777
47019
|
});
|
|
46778
47020
|
}
|
|
46779
47021
|
|
|
47022
|
+
// src/pentest/get-finding.ts
|
|
47023
|
+
var OUTPUT_FORMATS2 = ["text", "json"];
|
|
47024
|
+
function isOutputFormat2(value) {
|
|
47025
|
+
return OUTPUT_FORMATS2.includes(value);
|
|
47026
|
+
}
|
|
47027
|
+
var PROSE_SECTIONS = [
|
|
47028
|
+
{ key: "summary", heading: "Summary" },
|
|
47029
|
+
{ key: "description", heading: "Description" },
|
|
47030
|
+
{ key: "impact", heading: "Impact" },
|
|
47031
|
+
{ key: "attackScenario", heading: "Attack Scenario" },
|
|
47032
|
+
{ key: "proofOfConcept", heading: "Proof of Concept" },
|
|
47033
|
+
{ key: "rootCauseAnalysis", heading: "Root Cause Analysis" },
|
|
47034
|
+
{ key: "remediation", heading: "Remediation" },
|
|
47035
|
+
{ key: "severityConsiderations", heading: "Severity Considerations" },
|
|
47036
|
+
{ key: "verification", heading: "Verification" }
|
|
47037
|
+
];
|
|
47038
|
+
function sanitizeMarkdown(value) {
|
|
47039
|
+
return value.split("\n").map(sanitizeDisplay).join("\n");
|
|
47040
|
+
}
|
|
47041
|
+
function formatFindingMarkdown(finding) {
|
|
47042
|
+
const lines = [];
|
|
47043
|
+
lines.push(`# PTF-${finding.displayId}: ${sanitizeDisplay(finding.title)}`);
|
|
47044
|
+
lines.push("");
|
|
47045
|
+
lines.push(`- Severity: ${sanitizeDisplay(finding.severity)}`);
|
|
47046
|
+
lines.push(`- Status: ${sanitizeDisplay(finding.status)}`);
|
|
47047
|
+
lines.push(`- Category: ${sanitizeDisplay(finding.category)}`);
|
|
47048
|
+
if (finding.cweIds.length > 0) {
|
|
47049
|
+
lines.push(`- CWE: ${finding.cweIds.map(sanitizeDisplay).join(", ")}`);
|
|
47050
|
+
}
|
|
47051
|
+
if (finding.owaspCategory) {
|
|
47052
|
+
lines.push(`- OWASP: ${sanitizeDisplay(finding.owaspCategory)}`);
|
|
47053
|
+
}
|
|
47054
|
+
if (finding.cvssScore !== null || finding.cvssVector) {
|
|
47055
|
+
const parts = [
|
|
47056
|
+
finding.cvssScore !== null ? String(finding.cvssScore) : null,
|
|
47057
|
+
finding.cvssVector === null ? null : sanitizeDisplay(finding.cvssVector)
|
|
47058
|
+
].filter((part) => part !== null);
|
|
47059
|
+
lines.push(`- CVSS: ${parts.join(" ")}`);
|
|
47060
|
+
}
|
|
47061
|
+
if (finding.repositories.length > 0) {
|
|
47062
|
+
lines.push("");
|
|
47063
|
+
lines.push("## Affected Code");
|
|
47064
|
+
lines.push("");
|
|
47065
|
+
for (const repository of finding.repositories) {
|
|
47066
|
+
const repositoryName = sanitizeDisplay(repository.repositoryName);
|
|
47067
|
+
const filePath = repository.filePath === null ? null : sanitizeDisplay(repository.filePath);
|
|
47068
|
+
const location = filePath === null ? repositoryName : `${repositoryName}: ${filePath}${repository.startLine === null ? "" : `:${repository.startLine}${repository.endLine !== null && repository.endLine !== repository.startLine ? `-${repository.endLine}` : ""}`}`;
|
|
47069
|
+
const commitSha = repository.commitSha === null ? null : sanitizeDisplay(repository.commitSha).slice(0, 12);
|
|
47070
|
+
lines.push(`- ${location}${commitSha ? ` (commit ${commitSha})` : ""}`);
|
|
47071
|
+
}
|
|
47072
|
+
}
|
|
47073
|
+
for (const section of PROSE_SECTIONS) {
|
|
47074
|
+
const body = finding[section.key];
|
|
47075
|
+
if (!body) continue;
|
|
47076
|
+
lines.push("");
|
|
47077
|
+
lines.push(`## ${section.heading}`);
|
|
47078
|
+
lines.push("");
|
|
47079
|
+
lines.push(sanitizeMarkdown(body.trim()));
|
|
47080
|
+
}
|
|
47081
|
+
lines.push("");
|
|
47082
|
+
return lines.join("\n");
|
|
47083
|
+
}
|
|
47084
|
+
function failGetFinding(status, body) {
|
|
47085
|
+
const { message } = readErrorFields(body);
|
|
47086
|
+
if (status === UNAUTHORIZED_STATUS2) {
|
|
47087
|
+
exitAuthFailed();
|
|
47088
|
+
}
|
|
47089
|
+
if (status === 404) {
|
|
47090
|
+
console.error(colorError(message ?? "Pentest or finding not found."));
|
|
47091
|
+
process.exit(1);
|
|
47092
|
+
}
|
|
47093
|
+
if (status === 403) {
|
|
47094
|
+
console.error(colorError(message ?? "You do not have access to this finding (status 403)."));
|
|
47095
|
+
process.exit(1);
|
|
47096
|
+
}
|
|
47097
|
+
console.error(
|
|
47098
|
+
colorError(`Failed to fetch finding (status ${status})${message ? `: ${message}` : "."}`)
|
|
47099
|
+
);
|
|
47100
|
+
process.exit(1);
|
|
47101
|
+
}
|
|
47102
|
+
async function runGetFinding(options) {
|
|
47103
|
+
if (!isOutputFormat2(options.output)) {
|
|
47104
|
+
console.error(
|
|
47105
|
+
colorError(`Invalid --output '${sanitizeDisplay(options.output)}'. Use text or json.`)
|
|
47106
|
+
);
|
|
47107
|
+
process.exit(1);
|
|
47108
|
+
}
|
|
47109
|
+
if (!isPentestIdentifier(options.pentest)) {
|
|
47110
|
+
console.error(
|
|
47111
|
+
colorError(
|
|
47112
|
+
`Invalid pentest identifier '${sanitizeDisplay(options.pentest)}'. Use a display ID (e.g. PT-3) or UUID.`
|
|
47113
|
+
)
|
|
47114
|
+
);
|
|
47115
|
+
process.exit(1);
|
|
47116
|
+
}
|
|
47117
|
+
if (!isFindingIdentifier(options.finding)) {
|
|
47118
|
+
console.error(
|
|
47119
|
+
colorError(
|
|
47120
|
+
`Invalid finding identifier '${sanitizeDisplay(options.finding)}'. Use a finding number (e.g. PTF-7) or UUID.`
|
|
47121
|
+
)
|
|
47122
|
+
);
|
|
47123
|
+
process.exit(1);
|
|
47124
|
+
}
|
|
47125
|
+
const organizationId = await resolveOrganizationId();
|
|
47126
|
+
const result = await authenticatedRequest(
|
|
47127
|
+
(client) => client.pentestFinding.getCustomerFinding({
|
|
47128
|
+
params: {
|
|
47129
|
+
organizationId,
|
|
47130
|
+
pentestId: options.pentest,
|
|
47131
|
+
pentestFindingId: options.finding
|
|
47132
|
+
}
|
|
47133
|
+
})
|
|
47134
|
+
);
|
|
47135
|
+
if (result.status !== 200) {
|
|
47136
|
+
failGetFinding(result.status, result.body);
|
|
47137
|
+
}
|
|
47138
|
+
if (options.output === "json") {
|
|
47139
|
+
console.log(JSON.stringify(result.body, null, 2));
|
|
47140
|
+
return;
|
|
47141
|
+
}
|
|
47142
|
+
process.stdout.write(formatFindingMarkdown(result.body));
|
|
47143
|
+
}
|
|
47144
|
+
function createGetFindingCommand() {
|
|
47145
|
+
return new Command("get").description("Fetch one pentest finding with its full write-up").requiredOption("--pentest <id>", "Pentest display ID (e.g. PT-3) or UUID").requiredOption("--finding <id>", "Finding number (e.g. PTF-7) or UUID").option("-o, --output <format>", "Output format: text (markdown), json (default: text)", "text").addHelpText(
|
|
47146
|
+
"after",
|
|
47147
|
+
`
|
|
47148
|
+
One finding, every section: description, impact, attack scenario, proof of
|
|
47149
|
+
concept, root cause, remediation, severity considerations, and verification,
|
|
47150
|
+
plus the affected repositories and lines. The default output is markdown, ready
|
|
47151
|
+
to paste into a coding agent; --output json returns the API response.
|
|
47152
|
+
|
|
47153
|
+
Examples:
|
|
47154
|
+
tracecli pentest findings get --pentest PT-3 --finding PTF-7
|
|
47155
|
+
tracecli pentest findings get --pentest PT-3 --finding 7 --output json
|
|
47156
|
+
tracecli pentest findings get --pentest PT-3 --finding PTF-7 > finding.md
|
|
47157
|
+
`
|
|
47158
|
+
).action(async (options) => {
|
|
47159
|
+
await runGetFinding(options);
|
|
47160
|
+
});
|
|
47161
|
+
}
|
|
47162
|
+
|
|
47163
|
+
// src/pentest/questionnaire.ts
|
|
47164
|
+
function buildUpsertQuestionnaireBody(answers) {
|
|
47165
|
+
if (answers === null) {
|
|
47166
|
+
return { answers: null };
|
|
47167
|
+
}
|
|
47168
|
+
return { answers: normalizePentestCustomerIntakeAnswers(answers) };
|
|
47169
|
+
}
|
|
47170
|
+
function parseQuestionnaireAnswersFile(raw, sourceLabel = "file") {
|
|
47171
|
+
let parsed;
|
|
47172
|
+
try {
|
|
47173
|
+
parsed = JSON.parse(raw);
|
|
47174
|
+
} catch {
|
|
47175
|
+
return { error: `Invalid JSON in ${sourceLabel}.` };
|
|
47176
|
+
}
|
|
47177
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
47178
|
+
return {
|
|
47179
|
+
error: `${sourceLabel} must be a JSON object of questionnaire answers (not an array or null).`
|
|
47180
|
+
};
|
|
47181
|
+
}
|
|
47182
|
+
if ("answers" in parsed && Object.keys(parsed).length === 1) {
|
|
47183
|
+
return {
|
|
47184
|
+
error: `${sourceLabel} must contain the answers object directly, not an { answers } wrapper. The CLI wraps it for the API.`
|
|
47185
|
+
};
|
|
47186
|
+
}
|
|
47187
|
+
const result = PentestCustomerIntakeAnswersSchema.safeParse(parsed);
|
|
47188
|
+
if (!result.success) {
|
|
47189
|
+
const issue2 = result.error.issues[0];
|
|
47190
|
+
const path = issue2?.path.length ? issue2.path.join(".") : "answers";
|
|
47191
|
+
const message = issue2?.message ?? "invalid";
|
|
47192
|
+
return { error: `Invalid questionnaire answers in ${sourceLabel}: ${path} \u2014 ${message}` };
|
|
47193
|
+
}
|
|
47194
|
+
return { answers: normalizePentestCustomerIntakeAnswers(result.data) };
|
|
47195
|
+
}
|
|
47196
|
+
function applyTextIntakeAnswer(current, key, input) {
|
|
47197
|
+
const trimmed = input.trim();
|
|
47198
|
+
if (trimmed.length === 0) {
|
|
47199
|
+
return current;
|
|
47200
|
+
}
|
|
47201
|
+
return { ...current, [key]: trimmed };
|
|
47202
|
+
}
|
|
47203
|
+
function applyMultiSelectIntakeAnswer(current, key, selected) {
|
|
47204
|
+
if (selected.length === 0) {
|
|
47205
|
+
return current;
|
|
47206
|
+
}
|
|
47207
|
+
return {
|
|
47208
|
+
...current,
|
|
47209
|
+
[key]: Array.from(new Set(selected)).sort()
|
|
47210
|
+
};
|
|
47211
|
+
}
|
|
47212
|
+
function formatIssueClasses(values) {
|
|
47213
|
+
return values.map((v) => INTAKE_ISSUE_CLASS_LABELS[v]).join(", ");
|
|
47214
|
+
}
|
|
47215
|
+
function formatSurfaces(values) {
|
|
47216
|
+
return values.map((v) => INTAKE_SURFACE_LABELS[v]).join(", ");
|
|
47217
|
+
}
|
|
47218
|
+
function formatAnswerValue(key, answers) {
|
|
47219
|
+
switch (key) {
|
|
47220
|
+
case "issueClasses": {
|
|
47221
|
+
const values = answers.issueClasses;
|
|
47222
|
+
if (values === void 0 || values.length === 0) {
|
|
47223
|
+
return null;
|
|
47224
|
+
}
|
|
47225
|
+
return formatIssueClasses(values);
|
|
47226
|
+
}
|
|
47227
|
+
case "surfaces": {
|
|
47228
|
+
const values = answers.surfaces;
|
|
47229
|
+
if (values === void 0 || values.length === 0) {
|
|
47230
|
+
return null;
|
|
47231
|
+
}
|
|
47232
|
+
return formatSurfaces(values);
|
|
47233
|
+
}
|
|
47234
|
+
default: {
|
|
47235
|
+
const value = answers[key];
|
|
47236
|
+
if (value === void 0 || value.trim().length === 0) {
|
|
47237
|
+
return null;
|
|
47238
|
+
}
|
|
47239
|
+
return value;
|
|
47240
|
+
}
|
|
47241
|
+
}
|
|
47242
|
+
}
|
|
47243
|
+
function formatQuestionnaireShow(input) {
|
|
47244
|
+
const submitted = input.submitted === true;
|
|
47245
|
+
const editableLine = input.editable ? "yes (scoping window open)" : submitted ? "no (submitted)" : "no (editing closed after scoping)";
|
|
47246
|
+
const lines = [`Editable: ${editableLine}`, `Submitted: ${submitted ? "yes" : "no"}`];
|
|
47247
|
+
if (input.answers === null || Object.keys(input.answers).length === 0) {
|
|
47248
|
+
lines.push("Answers: (none)");
|
|
47249
|
+
return lines.join("\n");
|
|
47250
|
+
}
|
|
47251
|
+
lines.push("Answers:");
|
|
47252
|
+
for (const key of PENTEST_CUSTOMER_INTAKE_ANSWER_KEYS) {
|
|
47253
|
+
const value = formatAnswerValue(key, input.answers);
|
|
47254
|
+
if (value === null) {
|
|
47255
|
+
continue;
|
|
47256
|
+
}
|
|
47257
|
+
lines.push(`${PENTEST_CUSTOMER_INTAKE_LABELS[key]}: ${value}`);
|
|
47258
|
+
}
|
|
47259
|
+
return lines.join("\n");
|
|
47260
|
+
}
|
|
47261
|
+
function issueClassSelectOptions() {
|
|
47262
|
+
return CustomerPriorityCategoryValues.map((value) => ({
|
|
47263
|
+
value,
|
|
47264
|
+
label: INTAKE_ISSUE_CLASS_LABELS[value]
|
|
47265
|
+
}));
|
|
47266
|
+
}
|
|
47267
|
+
function surfaceSelectOptions() {
|
|
47268
|
+
return IntakeSurfaceValues.map((value) => ({
|
|
47269
|
+
value,
|
|
47270
|
+
label: INTAKE_SURFACE_LABELS[value]
|
|
47271
|
+
}));
|
|
47272
|
+
}
|
|
47273
|
+
|
|
46780
47274
|
// src/pentest/commands.ts
|
|
46781
47275
|
var DEFAULT_LIMIT2 = 30;
|
|
46782
47276
|
var MAX_LIMIT2 = 100;
|
|
@@ -47147,6 +47641,7 @@ function renderPentestFindingLines(finding) {
|
|
|
47147
47641
|
function createFindingsCommand() {
|
|
47148
47642
|
const findings = new Command("findings").description("Manage pentest findings");
|
|
47149
47643
|
findings.addCommand(createFindingsListCommand());
|
|
47644
|
+
findings.addCommand(createGetFindingCommand());
|
|
47150
47645
|
findings.addCommand(createStatusUpdateCommand());
|
|
47151
47646
|
findings.addCommand(createAttachEvidenceCommand());
|
|
47152
47647
|
findings.addCommand(createCreateFindingCommand(), { hidden: true });
|