securewithtrace 0.30.0 → 0.31.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/dist/index.js +225 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -35495,6 +35495,15 @@ function exitAuthFailed() {
|
|
|
35495
35495
|
console.error(colorError("Authentication failed. Run `tracecli auth login` to re-authenticate."));
|
|
35496
35496
|
process.exit(1);
|
|
35497
35497
|
}
|
|
35498
|
+
function resolveClearableOption(value, clear, flag) {
|
|
35499
|
+
if (value !== void 0 && clear) {
|
|
35500
|
+
return { error: `Pass only one of --${flag} / --clear-${flag}.` };
|
|
35501
|
+
}
|
|
35502
|
+
if (clear) {
|
|
35503
|
+
return { value: null };
|
|
35504
|
+
}
|
|
35505
|
+
return { value };
|
|
35506
|
+
}
|
|
35498
35507
|
function formatDate(value) {
|
|
35499
35508
|
if (!value) {
|
|
35500
35509
|
return "\u2014";
|
|
@@ -39540,9 +39549,225 @@ function createFindingsCommand() {
|
|
|
39540
39549
|
findings.addCommand(createFindingsListCommand());
|
|
39541
39550
|
return findings;
|
|
39542
39551
|
}
|
|
39552
|
+
var PRIORITY_SET = new Set(CustomerPriorityCategoryValues);
|
|
39553
|
+
var PRIORITY_LIST = CustomerPriorityCategoryValues.join(", ");
|
|
39554
|
+
function isPriorityCategory(value) {
|
|
39555
|
+
return PRIORITY_SET.has(value);
|
|
39556
|
+
}
|
|
39557
|
+
function buildUpdatePentestBody(input) {
|
|
39558
|
+
return {
|
|
39559
|
+
...input.title !== void 0 ? { title: input.title } : {},
|
|
39560
|
+
...input.description !== void 0 ? { description: input.description } : {},
|
|
39561
|
+
...input.customerConcernsContext !== void 0 ? { customerConcernsContext: input.customerConcernsContext } : {},
|
|
39562
|
+
...input.customerPriorityCategories !== void 0 ? { customerPriorityCategories: input.customerPriorityCategories } : {}
|
|
39563
|
+
};
|
|
39564
|
+
}
|
|
39565
|
+
function isEmptyPentestUpdate(body) {
|
|
39566
|
+
return Object.keys(body).length === 0;
|
|
39567
|
+
}
|
|
39568
|
+
function collectPriority(value, previous) {
|
|
39569
|
+
return [...previous ?? [], value];
|
|
39570
|
+
}
|
|
39571
|
+
function parsePriorityCategories(values) {
|
|
39572
|
+
if (values.length > 20) {
|
|
39573
|
+
return { error: "Pass at most 20 --priority values." };
|
|
39574
|
+
}
|
|
39575
|
+
const categories = [];
|
|
39576
|
+
const seen = /* @__PURE__ */ new Set();
|
|
39577
|
+
for (const raw of values) {
|
|
39578
|
+
const normalized = raw.trim().toUpperCase();
|
|
39579
|
+
if (!isPriorityCategory(normalized)) {
|
|
39580
|
+
return {
|
|
39581
|
+
error: `Invalid priority '${raw}'. Expected one of: ${PRIORITY_LIST}.`
|
|
39582
|
+
};
|
|
39583
|
+
}
|
|
39584
|
+
if (seen.has(normalized)) {
|
|
39585
|
+
continue;
|
|
39586
|
+
}
|
|
39587
|
+
seen.add(normalized);
|
|
39588
|
+
categories.push(normalized);
|
|
39589
|
+
}
|
|
39590
|
+
return { categories };
|
|
39591
|
+
}
|
|
39592
|
+
function resolvePrioritiesOption(priorities, clear) {
|
|
39593
|
+
if (priorities !== void 0 && clear) {
|
|
39594
|
+
return { error: "Pass only one of --priority / --clear-priorities." };
|
|
39595
|
+
}
|
|
39596
|
+
if (clear) {
|
|
39597
|
+
return { value: [] };
|
|
39598
|
+
}
|
|
39599
|
+
if (priorities === void 0) {
|
|
39600
|
+
return { value: void 0 };
|
|
39601
|
+
}
|
|
39602
|
+
const parsed = parsePriorityCategories(priorities);
|
|
39603
|
+
if ("error" in parsed) {
|
|
39604
|
+
return parsed;
|
|
39605
|
+
}
|
|
39606
|
+
return { value: parsed.categories };
|
|
39607
|
+
}
|
|
39608
|
+
function failPentestUpdate(status, body, notFoundLabel) {
|
|
39609
|
+
const { message, errorCode } = readErrorFields(body);
|
|
39610
|
+
if (status === UNAUTHORIZED_STATUS2) {
|
|
39611
|
+
exitAuthFailed();
|
|
39612
|
+
}
|
|
39613
|
+
if (status === 403) {
|
|
39614
|
+
console.error(
|
|
39615
|
+
colorError(
|
|
39616
|
+
"Only Trace staff can update pentests. Sign in with a staff account via `tracecli auth login`."
|
|
39617
|
+
)
|
|
39618
|
+
);
|
|
39619
|
+
process.exit(1);
|
|
39620
|
+
}
|
|
39621
|
+
if (status === 404) {
|
|
39622
|
+
console.error(
|
|
39623
|
+
colorError(notFoundLabel ? `Pentest not found: ${notFoundLabel}.` : message ?? "Not found.")
|
|
39624
|
+
);
|
|
39625
|
+
process.exit(1);
|
|
39626
|
+
}
|
|
39627
|
+
if (status === 400) {
|
|
39628
|
+
console.error(colorError(message ?? "Invalid request (status 400)."));
|
|
39629
|
+
process.exit(1);
|
|
39630
|
+
}
|
|
39631
|
+
if (status === 409) {
|
|
39632
|
+
if (errorCode === "PENTEST_CLOSED") {
|
|
39633
|
+
console.error(colorError(message ?? "Closed pentests must be reopened before editing."));
|
|
39634
|
+
} else {
|
|
39635
|
+
console.error(colorError(message ?? "Conflict (status 409)."));
|
|
39636
|
+
}
|
|
39637
|
+
process.exit(1);
|
|
39638
|
+
}
|
|
39639
|
+
console.error(
|
|
39640
|
+
colorError(`Failed to update pentest (status ${status})${message ? `: ${message}` : "."}`)
|
|
39641
|
+
);
|
|
39642
|
+
process.exit(1);
|
|
39643
|
+
}
|
|
39644
|
+
function formatPriorities(categories) {
|
|
39645
|
+
return categories.length > 0 ? categories.join(", ") : "\u2014";
|
|
39646
|
+
}
|
|
39647
|
+
function truncateForDisplay(value, max = 120) {
|
|
39648
|
+
if (value.length <= max) {
|
|
39649
|
+
return value;
|
|
39650
|
+
}
|
|
39651
|
+
return `${value.slice(0, max - 1)}\u2026`;
|
|
39652
|
+
}
|
|
39653
|
+
function printPentestUpdateDetail(pt2) {
|
|
39654
|
+
const pad = 14;
|
|
39655
|
+
console.log(
|
|
39656
|
+
[
|
|
39657
|
+
`${"ID:".padEnd(pad)} PT-${pt2.displayId}`,
|
|
39658
|
+
`${"Title:".padEnd(pad)} ${sanitizeDisplay(pt2.title)}`,
|
|
39659
|
+
`${"Status:".padEnd(pad)} ${sanitizeDisplay(pt2.status)}`,
|
|
39660
|
+
`${"Stage:".padEnd(pad)} ${sanitizeDisplay(pt2.currentStage)}`,
|
|
39661
|
+
`${"Description:".padEnd(pad)} ${pt2.description ? sanitizeDisplay(truncateForDisplay(pt2.description)) : "\u2014"}`,
|
|
39662
|
+
`${"Priorities:".padEnd(pad)} ${sanitizeDisplay(formatPriorities(pt2.customerPriorityCategories))}`,
|
|
39663
|
+
`${"Concerns:".padEnd(pad)} ${pt2.customerConcernsContext ? sanitizeDisplay(truncateForDisplay(pt2.customerConcernsContext)) : "\u2014"}`,
|
|
39664
|
+
`${"Updated:".padEnd(pad)} ${toIso(pt2.updatedAt)}`
|
|
39665
|
+
].join("\n")
|
|
39666
|
+
);
|
|
39667
|
+
}
|
|
39668
|
+
function printPentestUpdateTsv(pt2) {
|
|
39669
|
+
console.log("displayId title status stage description priorities concerns updatedAt");
|
|
39670
|
+
console.log(
|
|
39671
|
+
[
|
|
39672
|
+
`PT-${pt2.displayId}`,
|
|
39673
|
+
tsvSafe2(sanitizeDisplay(pt2.title)),
|
|
39674
|
+
tsvSafe2(sanitizeDisplay(pt2.status)),
|
|
39675
|
+
tsvSafe2(sanitizeDisplay(pt2.currentStage)),
|
|
39676
|
+
tsvSafe2(sanitizeDisplay(pt2.description ?? "")),
|
|
39677
|
+
tsvSafe2(sanitizeDisplay(pt2.customerPriorityCategories.join(","))),
|
|
39678
|
+
tsvSafe2(sanitizeDisplay(pt2.customerConcernsContext ?? "")),
|
|
39679
|
+
toIso(pt2.updatedAt)
|
|
39680
|
+
].join(" ")
|
|
39681
|
+
);
|
|
39682
|
+
}
|
|
39683
|
+
function outputPentestUpdate(pt2, output) {
|
|
39684
|
+
if (output === "json") {
|
|
39685
|
+
console.log(JSON.stringify(pt2, null, 2));
|
|
39686
|
+
return;
|
|
39687
|
+
}
|
|
39688
|
+
if (output === "tsv") {
|
|
39689
|
+
printPentestUpdateTsv(pt2);
|
|
39690
|
+
return;
|
|
39691
|
+
}
|
|
39692
|
+
printPentestUpdateDetail(pt2);
|
|
39693
|
+
}
|
|
39694
|
+
async function runUpdate4(pentestIdentifier, options) {
|
|
39695
|
+
const output = parseOutput(options.output);
|
|
39696
|
+
const description = resolveClearableOption(
|
|
39697
|
+
options.description,
|
|
39698
|
+
options.clearDescription,
|
|
39699
|
+
"description"
|
|
39700
|
+
);
|
|
39701
|
+
if ("error" in description) {
|
|
39702
|
+
console.error(colorError(description.error));
|
|
39703
|
+
process.exit(1);
|
|
39704
|
+
}
|
|
39705
|
+
const concerns = resolveClearableOption(options.concerns, options.clearConcerns, "concerns");
|
|
39706
|
+
if ("error" in concerns) {
|
|
39707
|
+
console.error(colorError(concerns.error));
|
|
39708
|
+
process.exit(1);
|
|
39709
|
+
}
|
|
39710
|
+
const priorities = resolvePrioritiesOption(options.priority, options.clearPriorities);
|
|
39711
|
+
if ("error" in priorities) {
|
|
39712
|
+
console.error(colorError(priorities.error));
|
|
39713
|
+
process.exit(1);
|
|
39714
|
+
}
|
|
39715
|
+
const body = buildUpdatePentestBody({
|
|
39716
|
+
title: options.title,
|
|
39717
|
+
description: description.value,
|
|
39718
|
+
customerConcernsContext: concerns.value,
|
|
39719
|
+
customerPriorityCategories: priorities.value
|
|
39720
|
+
});
|
|
39721
|
+
if (isEmptyPentestUpdate(body)) {
|
|
39722
|
+
console.error(
|
|
39723
|
+
colorError(
|
|
39724
|
+
"Nothing to update. Pass at least one of --title, --description, --clear-description, --concerns, --clear-concerns, --priority, --clear-priorities."
|
|
39725
|
+
)
|
|
39726
|
+
);
|
|
39727
|
+
process.exit(1);
|
|
39728
|
+
}
|
|
39729
|
+
const organizationId = await resolveOrganizationId();
|
|
39730
|
+
const pentestId = await resolvePentestId(organizationId, pentestIdentifier);
|
|
39731
|
+
const result = await authenticatedRequest(
|
|
39732
|
+
(client) => client.pentest.updatePentest({ params: { organizationId, pentestId }, body })
|
|
39733
|
+
);
|
|
39734
|
+
if (result.status !== SUCCESS_STATUS) {
|
|
39735
|
+
failPentestUpdate(result.status, result.body, pentestIdentifier);
|
|
39736
|
+
}
|
|
39737
|
+
if (output === "table") {
|
|
39738
|
+
console.log(colorSuccess(`Updated pentest PT-${result.body.displayId}`));
|
|
39739
|
+
}
|
|
39740
|
+
outputPentestUpdate(result.body, output);
|
|
39741
|
+
}
|
|
39742
|
+
function createUpdateCommand4() {
|
|
39743
|
+
return new Command("update").description(
|
|
39744
|
+
"Update pentest details and agent brief (staff-only: title, description, priorities, concerns)"
|
|
39745
|
+
).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(
|
|
39746
|
+
"--priority <category>",
|
|
39747
|
+
`Agent-brief priority category (repeatable). One of: ${PRIORITY_LIST}`,
|
|
39748
|
+
collectPriority
|
|
39749
|
+
).option("--clear-priorities", "Clear all agent-brief priority categories").option("-o, --output <format>", "Output format: table, json, tsv (default: table)", "table").addHelpText(
|
|
39750
|
+
"after",
|
|
39751
|
+
`
|
|
39752
|
+
Staff-only. Requires a browser session from \`tracecli auth login\` with a Trace
|
|
39753
|
+
staff account \u2014 API keys are rejected. Edits to the agent brief apply to sessions
|
|
39754
|
+
launched after this change (already-running sessions keep their snapshot).
|
|
39755
|
+
|
|
39756
|
+
Examples:
|
|
39757
|
+
tracecli pentest update PT-3 --title "Acme Q3 engagement"
|
|
39758
|
+
tracecli pentest update PT-3 --description "Focus on the billing API"
|
|
39759
|
+
tracecli pentest update PT-3 --priority AUTHN_BYPASS --priority AUTHZ_IDOR
|
|
39760
|
+
tracecli pentest update PT-3 --concerns "Worried about IDOR on invoice IDs"
|
|
39761
|
+
tracecli pentest update PT-3 --clear-concerns --clear-priorities
|
|
39762
|
+
tracecli pentest update PT-3 --title "Acme Q3" -o json`
|
|
39763
|
+
).action(async (pentestId, options) => {
|
|
39764
|
+
await runUpdate4(pentestId, options);
|
|
39765
|
+
});
|
|
39766
|
+
}
|
|
39543
39767
|
function createPentestCommand() {
|
|
39544
39768
|
const pentest = new Command("pentest").description("Manage pentests");
|
|
39545
39769
|
pentest.addCommand(createListCommand5());
|
|
39770
|
+
pentest.addCommand(createUpdateCommand4());
|
|
39546
39771
|
pentest.addCommand(createFindingsCommand());
|
|
39547
39772
|
return pentest;
|
|
39548
39773
|
}
|