securewithtrace 0.33.0 → 0.34.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 +1 -0
- package/dist/index.js +996 -176
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -23871,6 +23871,48 @@ var OrganizationCodeSizeResponseSchema = external_exports.object({
|
|
|
23871
23871
|
languageCount: external_exports.number().int().nonnegative()
|
|
23872
23872
|
})
|
|
23873
23873
|
});
|
|
23874
|
+
var StaffCodeSizeRepositorySchema = external_exports.object({
|
|
23875
|
+
repositoryId: external_exports.uuid(),
|
|
23876
|
+
repositoryUrl: external_exports.url(),
|
|
23877
|
+
platform: external_exports.enum(CodeSizePlatformValues),
|
|
23878
|
+
/** Resolved branch the measurement is keyed on (scanningBranch ?? default). */
|
|
23879
|
+
branch: external_exports.string(),
|
|
23880
|
+
/** null = no successful measurement for the current resolved branch. */
|
|
23881
|
+
billableSloc: external_exports.number().int().nonnegative().nullable(),
|
|
23882
|
+
/** null exactly when `billableSloc` is null. */
|
|
23883
|
+
measuredAt: external_exports.iso.datetime().nullable()
|
|
23884
|
+
});
|
|
23885
|
+
var StaffOrganizationCodeSizeResponseSchema = external_exports.object({
|
|
23886
|
+
/** Same server-side sort as the customer wire: measured first by
|
|
23887
|
+
* billableSloc desc (ties repositoryUrl asc), then unmeasured by
|
|
23888
|
+
* repositoryUrl asc. */
|
|
23889
|
+
repositories: external_exports.array(StaffCodeSizeRepositorySchema),
|
|
23890
|
+
// No language breakdown here on purpose: this panel answers "is every
|
|
23891
|
+
// repository measured, and how fresh", and the language view already exists
|
|
23892
|
+
// on the customer panel staff can impersonate into.
|
|
23893
|
+
totals: external_exports.object({
|
|
23894
|
+
billableSloc: external_exports.number().int().nonnegative(),
|
|
23895
|
+
rawLines: external_exports.number().int().nonnegative(),
|
|
23896
|
+
/** Connected GitHub/Bitbucket repos — the re-measure `all` scope size. */
|
|
23897
|
+
repositoryCount: external_exports.number().int().nonnegative(),
|
|
23898
|
+
/** Repos with a measurement for their current resolved branch. */
|
|
23899
|
+
measuredCount: external_exports.number().int().nonnegative(),
|
|
23900
|
+
/** Newest measuredAt across the org; null when nothing is measured. */
|
|
23901
|
+
lastMeasuredAt: external_exports.iso.datetime().nullable()
|
|
23902
|
+
})
|
|
23903
|
+
});
|
|
23904
|
+
var StaffCodeSizeRemeasureScopeValues = ["all", "unmeasured"];
|
|
23905
|
+
var StaffCodeSizeRemeasureRequestSchema = external_exports.object({
|
|
23906
|
+
scope: external_exports.enum(StaffCodeSizeRemeasureScopeValues)
|
|
23907
|
+
});
|
|
23908
|
+
var StaffCodeSizeRemeasureResponseSchema = external_exports.object({
|
|
23909
|
+
scope: external_exports.enum(StaffCodeSizeRemeasureScopeValues),
|
|
23910
|
+
/** Repos the scope selected. */
|
|
23911
|
+
requested: external_exports.number().int().nonnegative(),
|
|
23912
|
+
/** Messages SQS accepted. Lower than `requested` means a partial send —
|
|
23913
|
+
* reported rather than masked (the panel states this number). */
|
|
23914
|
+
enqueued: external_exports.number().int().nonnegative()
|
|
23915
|
+
});
|
|
23874
23916
|
|
|
23875
23917
|
// ../../packages/api-contracts/src/contracts/v1/code-size.ts
|
|
23876
23918
|
var c8 = initContract();
|
|
@@ -23886,6 +23928,36 @@ var codeSizeContract = c8.router({
|
|
|
23886
23928
|
404: ErrorResponseSchema,
|
|
23887
23929
|
500: ErrorResponseSchema
|
|
23888
23930
|
}
|
|
23931
|
+
},
|
|
23932
|
+
// Staff-only: per-repo measurement freshness for the admin Controls panel.
|
|
23933
|
+
getStaffOrganizationCodeSize: {
|
|
23934
|
+
method: "GET",
|
|
23935
|
+
path: "/staff/organizations/:organizationId/code-size",
|
|
23936
|
+
pathParams: OrgIdParamsSchema,
|
|
23937
|
+
responses: {
|
|
23938
|
+
200: StaffOrganizationCodeSizeResponseSchema,
|
|
23939
|
+
401: ErrorResponseSchema,
|
|
23940
|
+
403: ErrorResponseSchema,
|
|
23941
|
+
404: ErrorResponseSchema,
|
|
23942
|
+
500: ErrorResponseSchema
|
|
23943
|
+
}
|
|
23944
|
+
},
|
|
23945
|
+
// Staff-only: enqueue measurements for the scoped repos. Enqueue-only — the
|
|
23946
|
+
// worker measures asynchronously, so a 200 means "requested", not "measured".
|
|
23947
|
+
remeasureStaffOrganizationCodeSize: {
|
|
23948
|
+
method: "POST",
|
|
23949
|
+
path: "/staff/organizations/:organizationId/code-size/re-measure",
|
|
23950
|
+
pathParams: OrgIdParamsSchema,
|
|
23951
|
+
body: StaffCodeSizeRemeasureRequestSchema,
|
|
23952
|
+
responses: {
|
|
23953
|
+
200: StaffCodeSizeRemeasureResponseSchema,
|
|
23954
|
+
401: ErrorResponseSchema,
|
|
23955
|
+
403: ErrorResponseSchema,
|
|
23956
|
+
404: ErrorResponseSchema,
|
|
23957
|
+
500: ErrorResponseSchema,
|
|
23958
|
+
// Measurement enqueueing is unconfigured (REPO_METADATA_QUEUE_URL unset).
|
|
23959
|
+
503: ErrorResponseSchema
|
|
23960
|
+
}
|
|
23889
23961
|
}
|
|
23890
23962
|
});
|
|
23891
23963
|
|
|
@@ -27287,11 +27359,19 @@ var CustomerPentestDetailResponseSchema = CustomerPentestResponseSchema.extend({
|
|
|
27287
27359
|
* GET …/questionnaire. `.optional()` for rolling-deploy safety.
|
|
27288
27360
|
*/
|
|
27289
27361
|
questionnaireEditable: external_exports.boolean().optional(),
|
|
27290
|
-
hasQuestionnaireAnswers: external_exports.boolean().optional()
|
|
27362
|
+
hasQuestionnaireAnswers: external_exports.boolean().optional(),
|
|
27363
|
+
/**
|
|
27364
|
+
* True once the customer has submitted the questionnaire (frozen). Optional
|
|
27365
|
+
* for rolling-deploy safety against an older API that does not send it.
|
|
27366
|
+
*/
|
|
27367
|
+
questionnaireSubmitted: external_exports.boolean().optional()
|
|
27291
27368
|
});
|
|
27292
27369
|
var CustomerPentestQuestionnaireResponseSchema = external_exports.object({
|
|
27293
27370
|
pentestId: external_exports.uuid(),
|
|
27294
|
-
/**
|
|
27371
|
+
/**
|
|
27372
|
+
* True iff the engagement is still in a scoping stage AND the questionnaire
|
|
27373
|
+
* has not been submitted. A submitted intake is frozen even during scoping.
|
|
27374
|
+
*/
|
|
27295
27375
|
editable: external_exports.boolean(),
|
|
27296
27376
|
/**
|
|
27297
27377
|
* Lenient on unknown keys: during a rolling deploy the dashboard runs against
|
|
@@ -27300,7 +27380,13 @@ var CustomerPentestQuestionnaireResponseSchema = external_exports.object({
|
|
|
27300
27380
|
* strict — see the upsert request below.
|
|
27301
27381
|
*/
|
|
27302
27382
|
answers: PentestCustomerIntakeStoredAnswersSchema.nullable(),
|
|
27303
|
-
updatedAt: external_exports.coerce.date()
|
|
27383
|
+
updatedAt: external_exports.coerce.date(),
|
|
27384
|
+
/**
|
|
27385
|
+
* When the customer submitted (froze) the questionnaire. Null = still a
|
|
27386
|
+
* draft. Optional so an older API response still parses during a rolling
|
|
27387
|
+
* deploy; absence means "not submitted" to new clients.
|
|
27388
|
+
*/
|
|
27389
|
+
submittedAt: external_exports.coerce.date().nullable().optional()
|
|
27304
27390
|
});
|
|
27305
27391
|
var UpsertCustomerPentestQuestionnaireRequestSchema = external_exports.object({
|
|
27306
27392
|
/**
|
|
@@ -27310,6 +27396,14 @@ var UpsertCustomerPentestQuestionnaireRequestSchema = external_exports.object({
|
|
|
27310
27396
|
*/
|
|
27311
27397
|
answers: PentestCustomerIntakeWriteAnswersSchema.nullable()
|
|
27312
27398
|
}).strict();
|
|
27399
|
+
var SubmitCustomerPentestQuestionnaireRequestSchema = external_exports.object({
|
|
27400
|
+
/**
|
|
27401
|
+
* Optional last draft to persist atomically with the freeze. Omitted keeps
|
|
27402
|
+
* whatever is already stored. Retired keys are accepted and dropped, same
|
|
27403
|
+
* as the upsert path.
|
|
27404
|
+
*/
|
|
27405
|
+
answers: PentestCustomerIntakeWriteAnswersSchema.optional()
|
|
27406
|
+
}).strict();
|
|
27313
27407
|
var ReadPentestResponseSchema = external_exports.union([
|
|
27314
27408
|
PentestDetailResponseSchema,
|
|
27315
27409
|
CustomerPentestShellResponseSchema
|
|
@@ -27525,6 +27619,21 @@ var pentestContract = c22.router({
|
|
|
27525
27619
|
500: ErrorResponseSchema
|
|
27526
27620
|
}
|
|
27527
27621
|
},
|
|
27622
|
+
submitCustomerPentestQuestionnaire: {
|
|
27623
|
+
method: "POST",
|
|
27624
|
+
path: "/organizations/:organizationId/customer-pentests/:pentestId/questionnaire/submit",
|
|
27625
|
+
pathParams: CustomerPentestIdParamsSchema,
|
|
27626
|
+
body: SubmitCustomerPentestQuestionnaireRequestSchema,
|
|
27627
|
+
responses: {
|
|
27628
|
+
200: CustomerPentestQuestionnaireResponseSchema,
|
|
27629
|
+
400: ErrorResponseSchema,
|
|
27630
|
+
401: ErrorResponseSchema,
|
|
27631
|
+
403: ErrorResponseSchema,
|
|
27632
|
+
404: ErrorResponseSchema,
|
|
27633
|
+
409: ErrorResponseSchema,
|
|
27634
|
+
500: ErrorResponseSchema
|
|
27635
|
+
}
|
|
27636
|
+
},
|
|
27528
27637
|
createPentest: {
|
|
27529
27638
|
method: "POST",
|
|
27530
27639
|
path: "/organizations/:organizationId/pentests",
|
|
@@ -29074,6 +29183,25 @@ var CurateFindingEvidenceRequestSchema = external_exports.object({
|
|
|
29074
29183
|
}).strict().refine((v2) => Object.keys(v2).length > 0, {
|
|
29075
29184
|
message: "At least one curation field must be provided"
|
|
29076
29185
|
});
|
|
29186
|
+
var MAX_STAFF_EVIDENCE_UPLOAD_BYTES = 256 * 1024 * 1024;
|
|
29187
|
+
var CreateFindingEvidenceUploadRequestSchema = external_exports.object({
|
|
29188
|
+
filename: external_exports.string().min(1).max(255),
|
|
29189
|
+
sizeBytes: external_exports.number().int().positive().max(MAX_STAFF_EVIDENCE_UPLOAD_BYTES)
|
|
29190
|
+
}).strict();
|
|
29191
|
+
var CreateFindingEvidenceUploadResponseSchema = external_exports.object({
|
|
29192
|
+
key: external_exports.string(),
|
|
29193
|
+
uploadUrl: external_exports.url(),
|
|
29194
|
+
expiresAt: external_exports.coerce.date(),
|
|
29195
|
+
kind: PentestExploitArtifactKind,
|
|
29196
|
+
mimeType: external_exports.string()
|
|
29197
|
+
});
|
|
29198
|
+
var AttachFindingEvidenceRequestSchema = external_exports.object({
|
|
29199
|
+
key: external_exports.string().min(1).max(1024),
|
|
29200
|
+
kind: PentestExploitArtifactKind.optional(),
|
|
29201
|
+
caption: external_exports.string().min(1).max(500).optional(),
|
|
29202
|
+
section: ValidationEvidenceSection.optional(),
|
|
29203
|
+
customerFacing: external_exports.boolean().optional()
|
|
29204
|
+
}).strict();
|
|
29077
29205
|
var PentestFindingRepositorySchema = external_exports.object({
|
|
29078
29206
|
repositoryId: external_exports.uuid(),
|
|
29079
29207
|
repositoryName: external_exports.string(),
|
|
@@ -29680,6 +29808,43 @@ var pentestFindingContract = c28.router({
|
|
|
29680
29808
|
500: ErrorResponseSchema
|
|
29681
29809
|
}
|
|
29682
29810
|
},
|
|
29811
|
+
// ─── Evidence (staff manual attach) ───
|
|
29812
|
+
//
|
|
29813
|
+
// Two calls, because the bytes never pass through the api: mint a presigned
|
|
29814
|
+
// PUT, upload straight to S3, then register the object as an exhibit. That
|
|
29815
|
+
// keeps a screen-recorded PoC off the request path entirely (no body limit, no
|
|
29816
|
+
// request timeout, no api memory) and mirrors how the operator agent session
|
|
29817
|
+
// already attaches evidence.
|
|
29818
|
+
createFindingEvidenceUpload: {
|
|
29819
|
+
method: "POST",
|
|
29820
|
+
path: "/organizations/:organizationId/pentests/:pentestId/findings/:pentestFindingId/evidence/uploads",
|
|
29821
|
+
pathParams: PentestFindingIdParamsSchema,
|
|
29822
|
+
body: CreateFindingEvidenceUploadRequestSchema,
|
|
29823
|
+
responses: {
|
|
29824
|
+
201: CreateFindingEvidenceUploadResponseSchema,
|
|
29825
|
+
400: ErrorResponseSchema,
|
|
29826
|
+
401: ErrorResponseSchema,
|
|
29827
|
+
403: ErrorResponseSchema,
|
|
29828
|
+
404: ErrorResponseSchema,
|
|
29829
|
+
409: ErrorResponseSchema,
|
|
29830
|
+
500: ErrorResponseSchema
|
|
29831
|
+
}
|
|
29832
|
+
},
|
|
29833
|
+
attachFindingEvidence: {
|
|
29834
|
+
method: "POST",
|
|
29835
|
+
path: "/organizations/:organizationId/pentests/:pentestId/findings/:pentestFindingId/evidence",
|
|
29836
|
+
pathParams: PentestFindingIdParamsSchema,
|
|
29837
|
+
body: AttachFindingEvidenceRequestSchema,
|
|
29838
|
+
responses: {
|
|
29839
|
+
201: PentestExploitArtifactMetadataResponseSchema,
|
|
29840
|
+
400: ErrorResponseSchema,
|
|
29841
|
+
401: ErrorResponseSchema,
|
|
29842
|
+
403: ErrorResponseSchema,
|
|
29843
|
+
404: ErrorResponseSchema,
|
|
29844
|
+
409: ErrorResponseSchema,
|
|
29845
|
+
500: ErrorResponseSchema
|
|
29846
|
+
}
|
|
29847
|
+
},
|
|
29683
29848
|
curateFindingEvidence: {
|
|
29684
29849
|
method: "PATCH",
|
|
29685
29850
|
path: "/organizations/:organizationId/pentests/:pentestId/findings/:pentestFindingId/evidence/:artifactId",
|
|
@@ -30060,6 +30225,12 @@ var PentestHypothesisAttemptSummarySchema = external_exports.object({
|
|
|
30060
30225
|
status: PentestHypothesisAttemptStatus,
|
|
30061
30226
|
attemptOrdinal: external_exports.number().int().positive(),
|
|
30062
30227
|
conclusion: external_exports.string().nullable(),
|
|
30228
|
+
// Optional for rolling-deploy safety: web (Vercel) can deploy this schema before the
|
|
30229
|
+
// API sends the field, and web strictly `.parse()`s the response. Absent → undefined,
|
|
30230
|
+
// which the web serializer coalesces to null. Carries the Blocked/Failed reason so the
|
|
30231
|
+
// hypothesis drawer's verdict panel can show it when the deciding attempt is only in
|
|
30232
|
+
// hand as this summary (a verdict from a prior run).
|
|
30233
|
+
blockerReason: external_exports.string().nullable().optional(),
|
|
30063
30234
|
startedAt: external_exports.coerce.date().nullable(),
|
|
30064
30235
|
completedAt: external_exports.coerce.date().nullable(),
|
|
30065
30236
|
durationMs: external_exports.number().int().nonnegative().nullable(),
|
|
@@ -38860,6 +39031,194 @@ function showBanner() {
|
|
|
38860
39031
|
`);
|
|
38861
39032
|
}
|
|
38862
39033
|
|
|
39034
|
+
// src/code-size/format.ts
|
|
39035
|
+
function formatCount(value) {
|
|
39036
|
+
return value.toLocaleString("en-US");
|
|
39037
|
+
}
|
|
39038
|
+
function repoDisplayName(repositoryUrl) {
|
|
39039
|
+
try {
|
|
39040
|
+
const path = new URL(repositoryUrl).pathname.replace(/^\//, "").replace(/\.git$/, "");
|
|
39041
|
+
return path || repositoryUrl;
|
|
39042
|
+
} catch {
|
|
39043
|
+
return repositoryUrl;
|
|
39044
|
+
}
|
|
39045
|
+
}
|
|
39046
|
+
function countedRows(totals) {
|
|
39047
|
+
const b2 = totals.buckets;
|
|
39048
|
+
return [
|
|
39049
|
+
{ label: "Billable code", value: b2.code.code },
|
|
39050
|
+
{ label: "Blank & comment lines in billable files", value: b2.code.lines - b2.code.code },
|
|
39051
|
+
{ label: "Tests", value: b2.tests.lines },
|
|
39052
|
+
{ label: "Documentation", value: b2.docs.lines },
|
|
39053
|
+
{ label: "Configuration & data", value: b2.config_data.lines },
|
|
39054
|
+
{ label: "Vendored & generated", value: b2.vendored_generated.lines }
|
|
39055
|
+
];
|
|
39056
|
+
}
|
|
39057
|
+
function countedTotal(totals) {
|
|
39058
|
+
return countedRows(totals).reduce((sum, row) => sum + row.value, 0);
|
|
39059
|
+
}
|
|
39060
|
+
function padValues(rows) {
|
|
39061
|
+
const formatted = rows.map((row) => formatCount(row.value));
|
|
39062
|
+
const labelWidth = Math.max(...rows.map((row) => row.label.length));
|
|
39063
|
+
const valueWidth = Math.max(...formatted.map((value) => value.length));
|
|
39064
|
+
return rows.map(
|
|
39065
|
+
(row, i) => ` ${row.label.padEnd(labelWidth)} ${(formatted[i] ?? "").padStart(valueWidth)}`
|
|
39066
|
+
);
|
|
39067
|
+
}
|
|
39068
|
+
function renderCodeSizeTable(codeSize, options) {
|
|
39069
|
+
const { repositories, totals } = codeSize;
|
|
39070
|
+
const measured = repositories.filter((repo) => repo.billableSloc !== null);
|
|
39071
|
+
const lines = [`Code size for ${options.orgSlug}`, ""];
|
|
39072
|
+
if (measured.length === 0) {
|
|
39073
|
+
lines.push(
|
|
39074
|
+
" No measurements yet.",
|
|
39075
|
+
` ${formatCount(repositories.length)} connected ${plural(repositories.length, "repository has", "repositories have")} not been measured.`
|
|
39076
|
+
);
|
|
39077
|
+
return lines;
|
|
39078
|
+
}
|
|
39079
|
+
lines.push(
|
|
39080
|
+
` ${formatCount(totals.billableSloc)} billable SLOC`,
|
|
39081
|
+
` of ${formatCount(totals.rawLines)} total lines across ${formatCount(measured.length)} measured ${plural(measured.length, "repository", "repositories")}`
|
|
39082
|
+
);
|
|
39083
|
+
const unmeasured = repositories.length - measured.length;
|
|
39084
|
+
if (unmeasured > 0) {
|
|
39085
|
+
lines.push(
|
|
39086
|
+
` ${formatCount(unmeasured)} connected ${plural(unmeasured, "repository is", "repositories are")} not measured yet and not included`
|
|
39087
|
+
);
|
|
39088
|
+
}
|
|
39089
|
+
lines.push("", "What's counted", ...padValues(countedRows(totals)));
|
|
39090
|
+
const counted = countedTotal(totals);
|
|
39091
|
+
if (counted !== totals.rawLines) {
|
|
39092
|
+
lines.push(
|
|
39093
|
+
` These sum to ${formatCount(counted)}, not the ${formatCount(totals.rawLines)} above.`,
|
|
39094
|
+
" Some measurement predates the current counting rules; re-measure to reconcile."
|
|
39095
|
+
);
|
|
39096
|
+
}
|
|
39097
|
+
lines.push("", "By repository");
|
|
39098
|
+
const shown = measured.slice(0, options.repoLimit);
|
|
39099
|
+
lines.push(
|
|
39100
|
+
...padValues(
|
|
39101
|
+
shown.map((repo) => ({
|
|
39102
|
+
label: repoDisplayName(repo.repositoryUrl),
|
|
39103
|
+
value: repo.billableSloc ?? 0
|
|
39104
|
+
}))
|
|
39105
|
+
)
|
|
39106
|
+
);
|
|
39107
|
+
const hidden = measured.length - shown.length;
|
|
39108
|
+
if (hidden > 0) {
|
|
39109
|
+
lines.push(` ... ${formatCount(hidden)} more`);
|
|
39110
|
+
}
|
|
39111
|
+
return lines;
|
|
39112
|
+
}
|
|
39113
|
+
function renderCodeSizeTsv(codeSize) {
|
|
39114
|
+
return [
|
|
39115
|
+
"repository billableSloc",
|
|
39116
|
+
...codeSize.repositories.map(
|
|
39117
|
+
(repo) => `${repoDisplayName(repo.repositoryUrl)} ${repo.billableSloc === null ? "" : repo.billableSloc}`
|
|
39118
|
+
)
|
|
39119
|
+
];
|
|
39120
|
+
}
|
|
39121
|
+
function plural(count, one, many) {
|
|
39122
|
+
return count === 1 ? one : many;
|
|
39123
|
+
}
|
|
39124
|
+
|
|
39125
|
+
// src/code-size/commands.ts
|
|
39126
|
+
var DEFAULT_REPO_LIMIT = 20;
|
|
39127
|
+
var OUTPUT_FORMATS = ["table", "json", "tsv"];
|
|
39128
|
+
function isOutputFormat(value) {
|
|
39129
|
+
return OUTPUT_FORMATS.includes(value);
|
|
39130
|
+
}
|
|
39131
|
+
function parseRepoLimit(value) {
|
|
39132
|
+
if (!/^\d+$/.test(value.trim())) {
|
|
39133
|
+
console.error("--repos must be a non-negative whole number (0 shows every repository).");
|
|
39134
|
+
process.exit(1);
|
|
39135
|
+
}
|
|
39136
|
+
return Number.parseInt(value, 10);
|
|
39137
|
+
}
|
|
39138
|
+
function createCodeSizeCommand() {
|
|
39139
|
+
return new Command("code-size").description("Show the organization's billable lines of code").option("-o, --output <format>", "Output format: table, json, tsv (default: table)", "table").option(
|
|
39140
|
+
"-n, --repos <count>",
|
|
39141
|
+
`Per-repository rows to show in table output (default: ${DEFAULT_REPO_LIMIT}, 0 for all)`,
|
|
39142
|
+
parseRepoLimit
|
|
39143
|
+
).option("-w, --web", "Open the code size panel in a browser").addHelpText(
|
|
39144
|
+
"after",
|
|
39145
|
+
`
|
|
39146
|
+
Billable SLOC is counted from each connected repository's current scanning
|
|
39147
|
+
branch. Blanks, comments, tests, docs, config and vendored code are excluded;
|
|
39148
|
+
"What's counted" reconciles the billable number to what plain scc reports.
|
|
39149
|
+
See ${getAppBaseUrl()}/docs/integrations/code-size
|
|
39150
|
+
|
|
39151
|
+
Examples:
|
|
39152
|
+
tracecli code-size
|
|
39153
|
+
tracecli code-size --repos 0
|
|
39154
|
+
tracecli code-size --output json | jq '.totals.billableSloc'
|
|
39155
|
+
tracecli code-size --output tsv | cut -f1,2
|
|
39156
|
+
tracecli code-size --web`
|
|
39157
|
+
).action(async (options) => {
|
|
39158
|
+
if (!isOutputFormat(options.output)) {
|
|
39159
|
+
console.error(
|
|
39160
|
+
`Unsupported --output '${options.output}'. Expected one of: ${OUTPUT_FORMATS.join(", ")}.`
|
|
39161
|
+
);
|
|
39162
|
+
process.exit(1);
|
|
39163
|
+
}
|
|
39164
|
+
const listed = await authenticatedRequest(
|
|
39165
|
+
(client) => client.organization.listMyOrganizations()
|
|
39166
|
+
);
|
|
39167
|
+
if (listed.status === 401) {
|
|
39168
|
+
console.error("Authentication failed. Run `tracecli auth login` to re-authenticate.");
|
|
39169
|
+
process.exit(1);
|
|
39170
|
+
}
|
|
39171
|
+
if (listed.status !== 200) {
|
|
39172
|
+
console.error(`Failed to resolve organization (status ${listed.status}).`);
|
|
39173
|
+
process.exit(1);
|
|
39174
|
+
}
|
|
39175
|
+
const resolution = determineActiveOrganization(
|
|
39176
|
+
listed.body,
|
|
39177
|
+
readSelectedOrganizationId(),
|
|
39178
|
+
extractOrganizationIdFromToken(readToken())
|
|
39179
|
+
);
|
|
39180
|
+
if (!resolution) {
|
|
39181
|
+
console.error("No organization found for your account.");
|
|
39182
|
+
process.exit(1);
|
|
39183
|
+
}
|
|
39184
|
+
const { organizationId, slug } = resolution.organization;
|
|
39185
|
+
if (options.web) {
|
|
39186
|
+
const url2 = `${getAppBaseUrl()}/dashboard/${slug}/settings/integrations/github`;
|
|
39187
|
+
const opened = await openBrowser(url2);
|
|
39188
|
+
if (!opened) {
|
|
39189
|
+
console.log(`Open in browser: ${url2}`);
|
|
39190
|
+
}
|
|
39191
|
+
return;
|
|
39192
|
+
}
|
|
39193
|
+
const result = await authenticatedRequest(
|
|
39194
|
+
(client) => client.codeSize.getOrganizationCodeSize({ params: { organizationId } })
|
|
39195
|
+
);
|
|
39196
|
+
if (result.status === 401) {
|
|
39197
|
+
console.error("Authentication failed. Run `tracecli auth login` to re-authenticate.");
|
|
39198
|
+
process.exit(1);
|
|
39199
|
+
}
|
|
39200
|
+
if (result.status !== 200) {
|
|
39201
|
+
console.error(`Failed to read code size (status ${result.status}).`);
|
|
39202
|
+
process.exit(1);
|
|
39203
|
+
}
|
|
39204
|
+
const codeSize = result.body;
|
|
39205
|
+
if (options.output === "json") {
|
|
39206
|
+
console.log(JSON.stringify(codeSize, null, 2));
|
|
39207
|
+
return;
|
|
39208
|
+
}
|
|
39209
|
+
if (options.output === "tsv") {
|
|
39210
|
+
for (const line of renderCodeSizeTsv(codeSize)) {
|
|
39211
|
+
console.log(line);
|
|
39212
|
+
}
|
|
39213
|
+
return;
|
|
39214
|
+
}
|
|
39215
|
+
const repoLimit = options.repos === void 0 ? DEFAULT_REPO_LIMIT : options.repos === 0 ? Number.POSITIVE_INFINITY : options.repos;
|
|
39216
|
+
for (const line of renderCodeSizeTable(codeSize, { orgSlug: slug, repoLimit })) {
|
|
39217
|
+
console.log(line);
|
|
39218
|
+
}
|
|
39219
|
+
});
|
|
39220
|
+
}
|
|
39221
|
+
|
|
38863
39222
|
// src/completion/index.ts
|
|
38864
39223
|
function extractCommandTree(cmd) {
|
|
38865
39224
|
const BUILTIN_FLAGS = /* @__PURE__ */ new Set(["--help", "-h", "--version", "-V"]);
|
|
@@ -39231,73 +39590,318 @@ Examples:
|
|
|
39231
39590
|
});
|
|
39232
39591
|
}
|
|
39233
39592
|
|
|
39234
|
-
// src/
|
|
39235
|
-
|
|
39236
|
-
|
|
39237
|
-
|
|
39238
|
-
|
|
39239
|
-
|
|
39240
|
-
|
|
39241
|
-
function usesStdin(opts) {
|
|
39242
|
-
return opts.stdin === true;
|
|
39593
|
+
// src/inbox/commands.ts
|
|
39594
|
+
function validateInboxLabel(label) {
|
|
39595
|
+
const parsed = InboxLabelSchema.safeParse(label);
|
|
39596
|
+
if (parsed.success) {
|
|
39597
|
+
return null;
|
|
39598
|
+
}
|
|
39599
|
+
return "Inbox label must be lowercase letters, digits, or dashes, starting with a letter (max 40 characters).";
|
|
39243
39600
|
}
|
|
39244
|
-
function
|
|
39245
|
-
return
|
|
39601
|
+
function buildCreateInboxBody(label) {
|
|
39602
|
+
return { label };
|
|
39246
39603
|
}
|
|
39247
|
-
|
|
39248
|
-
const
|
|
39249
|
-
|
|
39250
|
-
|
|
39604
|
+
function failWriteOp2(action, status, body, notFoundLabel) {
|
|
39605
|
+
const { message, errorCode } = readErrorFields(body);
|
|
39606
|
+
if (status === UNAUTHORIZED_STATUS2) {
|
|
39607
|
+
exitAuthFailed();
|
|
39251
39608
|
}
|
|
39252
|
-
|
|
39253
|
-
|
|
39254
|
-
|
|
39255
|
-
|
|
39256
|
-
|
|
39257
|
-
`Provide only one source for ${label} (--${flagBase}-stdin / --${flagBase}-file / --${flagBase}-env).`
|
|
39609
|
+
if (status === 403) {
|
|
39610
|
+
console.error(
|
|
39611
|
+
colorError(
|
|
39612
|
+
`Only an organization admin (or Trace staff) on a pentest-enabled organization can ${action} inboxes.`
|
|
39613
|
+
)
|
|
39258
39614
|
);
|
|
39615
|
+
process.exit(1);
|
|
39259
39616
|
}
|
|
39260
|
-
if (
|
|
39261
|
-
|
|
39262
|
-
|
|
39263
|
-
|
|
39264
|
-
|
|
39265
|
-
return value;
|
|
39617
|
+
if (status === 404) {
|
|
39618
|
+
console.error(
|
|
39619
|
+
colorError(message ?? (notFoundLabel ? `No inbox labeled '${notFoundLabel}'.` : "Not found."))
|
|
39620
|
+
);
|
|
39621
|
+
process.exit(1);
|
|
39266
39622
|
}
|
|
39267
|
-
if (
|
|
39268
|
-
|
|
39269
|
-
|
|
39270
|
-
contents = readFileSync2(opts.file, "utf8");
|
|
39271
|
-
} catch {
|
|
39272
|
-
throw new SecretInputError(`Could not read ${label} file: ${opts.file}`);
|
|
39273
|
-
}
|
|
39274
|
-
const value = stripTrailingNewline(contents);
|
|
39275
|
-
if (value.length === 0) {
|
|
39276
|
-
throw new SecretInputError(`${label} file is empty: ${opts.file}`);
|
|
39277
|
-
}
|
|
39278
|
-
return value;
|
|
39623
|
+
if (status === 400) {
|
|
39624
|
+
console.error(colorError(message ?? "Invalid request (status 400)."));
|
|
39625
|
+
process.exit(1);
|
|
39279
39626
|
}
|
|
39280
|
-
if (
|
|
39281
|
-
|
|
39282
|
-
|
|
39283
|
-
|
|
39284
|
-
|
|
39627
|
+
if (status === 409) {
|
|
39628
|
+
if (errorCode === "INBOX_ALREADY_EXISTS") {
|
|
39629
|
+
console.error(
|
|
39630
|
+
colorError(message ?? "An inbox with this label already exists in this organization.")
|
|
39631
|
+
);
|
|
39632
|
+
} else if (errorCode === "INBOX_BEING_DELETED") {
|
|
39633
|
+
console.error(
|
|
39634
|
+
colorError(
|
|
39635
|
+
message ?? "That name was recently removed and is still being cleaned up. Try again shortly, or choose another."
|
|
39636
|
+
)
|
|
39637
|
+
);
|
|
39638
|
+
} else if (errorCode === "INBOX_QUOTA_EXCEEDED") {
|
|
39639
|
+
console.error(
|
|
39640
|
+
colorError(message ?? "Inbox limit reached. Delete an unused inbox to create a new one.")
|
|
39285
39641
|
);
|
|
39642
|
+
} else {
|
|
39643
|
+
console.error(colorError(message ?? "Conflict (status 409)."));
|
|
39286
39644
|
}
|
|
39287
|
-
|
|
39645
|
+
process.exit(1);
|
|
39288
39646
|
}
|
|
39289
|
-
if (
|
|
39290
|
-
|
|
39291
|
-
|
|
39647
|
+
if (status === 503) {
|
|
39648
|
+
console.error(
|
|
39649
|
+
colorError(
|
|
39650
|
+
message ?? "Managed inboxes are not enabled on this server: no mail provider is configured."
|
|
39651
|
+
)
|
|
39292
39652
|
);
|
|
39653
|
+
process.exit(1);
|
|
39293
39654
|
}
|
|
39294
|
-
|
|
39295
|
-
|
|
39296
|
-
|
|
39297
|
-
|
|
39298
|
-
return entered;
|
|
39655
|
+
console.error(
|
|
39656
|
+
colorError(`Failed to ${action} inbox (status ${status})${message ? `: ${message}` : "."}`)
|
|
39657
|
+
);
|
|
39658
|
+
process.exit(1);
|
|
39299
39659
|
}
|
|
39300
|
-
|
|
39660
|
+
function failReadOp2(status, body) {
|
|
39661
|
+
const { message } = readErrorFields(body);
|
|
39662
|
+
if (status === UNAUTHORIZED_STATUS2) {
|
|
39663
|
+
exitAuthFailed();
|
|
39664
|
+
}
|
|
39665
|
+
if (status === 403) {
|
|
39666
|
+
console.error(colorError("You don't have access to view inboxes in this organization."));
|
|
39667
|
+
process.exit(1);
|
|
39668
|
+
}
|
|
39669
|
+
if (status === 503) {
|
|
39670
|
+
console.error(
|
|
39671
|
+
colorError(
|
|
39672
|
+
message ?? "Managed inboxes are not enabled on this server: no mail provider is configured."
|
|
39673
|
+
)
|
|
39674
|
+
);
|
|
39675
|
+
process.exit(1);
|
|
39676
|
+
}
|
|
39677
|
+
console.error(
|
|
39678
|
+
colorError(`Failed to list inboxes (status ${status})${message ? `: ${message}` : "."}`)
|
|
39679
|
+
);
|
|
39680
|
+
process.exit(1);
|
|
39681
|
+
}
|
|
39682
|
+
function renderInboxDetail(inbox) {
|
|
39683
|
+
const pad = 10;
|
|
39684
|
+
return [
|
|
39685
|
+
`${"Address:".padEnd(pad)} ${sanitizeDisplay(inbox.address)}`,
|
|
39686
|
+
`${"Label:".padEnd(pad)} ${sanitizeDisplay(inbox.label)}`,
|
|
39687
|
+
`${"Created:".padEnd(pad)} ${formatDate(inbox.createdAt)}`
|
|
39688
|
+
].join("\n");
|
|
39689
|
+
}
|
|
39690
|
+
function printInboxesTsv(inboxes) {
|
|
39691
|
+
console.log(["address", "label", "createdAt"].join(" "));
|
|
39692
|
+
for (const inbox of inboxes) {
|
|
39693
|
+
console.log(
|
|
39694
|
+
[
|
|
39695
|
+
tsvSafe(sanitizeDisplay(inbox.address)),
|
|
39696
|
+
tsvSafe(sanitizeDisplay(inbox.label)),
|
|
39697
|
+
tsvSafe(toIso(inbox.createdAt))
|
|
39698
|
+
].join(" ")
|
|
39699
|
+
);
|
|
39700
|
+
}
|
|
39701
|
+
}
|
|
39702
|
+
function buildInboxesTable(inboxes) {
|
|
39703
|
+
const cells = inboxes.map((inbox) => ({
|
|
39704
|
+
address: sanitizeDisplay(inbox.address),
|
|
39705
|
+
label: sanitizeDisplay(inbox.label)
|
|
39706
|
+
}));
|
|
39707
|
+
const addressWidth = Math.max(7, ...cells.map((c51) => c51.address.length));
|
|
39708
|
+
const header = `${"ADDRESS".padEnd(addressWidth)} LABEL`;
|
|
39709
|
+
const rows = cells.map((c51) => `${c51.address.padEnd(addressWidth)} ${c51.label}`);
|
|
39710
|
+
return { header, rows };
|
|
39711
|
+
}
|
|
39712
|
+
function formatInboxQuota(quota) {
|
|
39713
|
+
return `${quota.used} of ${quota.limit} inboxes`;
|
|
39714
|
+
}
|
|
39715
|
+
function outputInbox(inbox, output) {
|
|
39716
|
+
if (output === "json") {
|
|
39717
|
+
console.log(JSON.stringify(inbox, null, 2));
|
|
39718
|
+
return;
|
|
39719
|
+
}
|
|
39720
|
+
if (output === "tsv") {
|
|
39721
|
+
printInboxesTsv([inbox]);
|
|
39722
|
+
return;
|
|
39723
|
+
}
|
|
39724
|
+
console.log(renderInboxDetail(inbox));
|
|
39725
|
+
}
|
|
39726
|
+
function requireInboxLabel(label) {
|
|
39727
|
+
const error48 = validateInboxLabel(label);
|
|
39728
|
+
if (error48) {
|
|
39729
|
+
console.error(colorError(error48));
|
|
39730
|
+
process.exit(1);
|
|
39731
|
+
}
|
|
39732
|
+
}
|
|
39733
|
+
async function runCreate2(label, options) {
|
|
39734
|
+
const output = parseOutput(options.output);
|
|
39735
|
+
requireInboxLabel(label);
|
|
39736
|
+
const organizationId = await resolveOrganizationId();
|
|
39737
|
+
const result = await authenticatedRequest(
|
|
39738
|
+
(client) => client.inbox.createInbox({
|
|
39739
|
+
params: { organizationId },
|
|
39740
|
+
body: buildCreateInboxBody(label)
|
|
39741
|
+
})
|
|
39742
|
+
);
|
|
39743
|
+
if (result.status !== CREATED_STATUS) {
|
|
39744
|
+
failWriteOp2("create", result.status, result.body);
|
|
39745
|
+
}
|
|
39746
|
+
if (output === "table") {
|
|
39747
|
+
console.log(colorSuccess(`Created inbox ${sanitizeDisplay(result.body.address)}`));
|
|
39748
|
+
}
|
|
39749
|
+
outputInbox(result.body, output);
|
|
39750
|
+
}
|
|
39751
|
+
async function runList2(options) {
|
|
39752
|
+
const output = parseOutput(options.output);
|
|
39753
|
+
const organizationId = await resolveOrganizationId();
|
|
39754
|
+
const result = await authenticatedRequest(
|
|
39755
|
+
(client) => client.inbox.listInboxes({ params: { organizationId } })
|
|
39756
|
+
);
|
|
39757
|
+
if (result.status !== SUCCESS_STATUS) {
|
|
39758
|
+
failReadOp2(result.status, result.body);
|
|
39759
|
+
}
|
|
39760
|
+
const { inboxes, quota } = result.body;
|
|
39761
|
+
if (output === "json") {
|
|
39762
|
+
console.log(JSON.stringify({ inboxes, quota }, null, 2));
|
|
39763
|
+
return;
|
|
39764
|
+
}
|
|
39765
|
+
if (output === "tsv") {
|
|
39766
|
+
printInboxesTsv(inboxes);
|
|
39767
|
+
return;
|
|
39768
|
+
}
|
|
39769
|
+
if (inboxes.length === 0) {
|
|
39770
|
+
console.log(`No inboxes found. ${formatInboxQuota(quota)}.`);
|
|
39771
|
+
return;
|
|
39772
|
+
}
|
|
39773
|
+
const { header, rows } = buildInboxesTable(inboxes);
|
|
39774
|
+
await runInteractivePager({
|
|
39775
|
+
items: inboxes,
|
|
39776
|
+
header,
|
|
39777
|
+
renderItem: (_inbox, i) => [rows[i] ?? ""],
|
|
39778
|
+
disabled: options.pager === false
|
|
39779
|
+
});
|
|
39780
|
+
console.log(formatInboxQuota(quota));
|
|
39781
|
+
}
|
|
39782
|
+
async function runDelete2(label, options) {
|
|
39783
|
+
requireInboxLabel(label);
|
|
39784
|
+
const confirmed = await confirmDeletion(`inbox ${label}`, options.yes);
|
|
39785
|
+
if (!confirmed) {
|
|
39786
|
+
console.log("Aborted.");
|
|
39787
|
+
return;
|
|
39788
|
+
}
|
|
39789
|
+
const organizationId = await resolveOrganizationId();
|
|
39790
|
+
const result = await authenticatedRequest(
|
|
39791
|
+
(client) => client.inbox.deleteInbox({
|
|
39792
|
+
params: { organizationId, label }
|
|
39793
|
+
})
|
|
39794
|
+
);
|
|
39795
|
+
if (result.status !== NO_CONTENT_STATUS) {
|
|
39796
|
+
failWriteOp2("delete", result.status, result.body, label);
|
|
39797
|
+
}
|
|
39798
|
+
console.log(colorSuccess(`Deleted inbox ${sanitizeDisplay(label)}`));
|
|
39799
|
+
}
|
|
39800
|
+
function createCreateCommand2() {
|
|
39801
|
+
return new Command("create").description("Provision a Trace-hosted email inbox").argument("<label>", "Inbox name (lowercase letters/digits/dashes, max 40)").option("-o, --output <format>", "Output format: table, json, tsv (default: table)", "table").addHelpText(
|
|
39802
|
+
"after",
|
|
39803
|
+
`
|
|
39804
|
+
Trace hosts this inbox; the pentest agent reads its emails automatically.
|
|
39805
|
+
The minted address is {org-slug}-{label}@pentest.securewithtrace.com.
|
|
39806
|
+
|
|
39807
|
+
Use the printed address as the test-account email when creating a magic-auth
|
|
39808
|
+
or username-password credential.
|
|
39809
|
+
|
|
39810
|
+
Examples:
|
|
39811
|
+
tracecli credential inbox create portal
|
|
39812
|
+
ADDRESS=$(tracecli credential inbox create qa-admin -o json | jq -r .address)
|
|
39813
|
+
echo "$ADDRESS" | tracecli credential create --env <envId> --type magic-auth --secret-stdin`
|
|
39814
|
+
).action(async (label, options) => {
|
|
39815
|
+
await runCreate2(label, options);
|
|
39816
|
+
});
|
|
39817
|
+
}
|
|
39818
|
+
function createListCommand3() {
|
|
39819
|
+
return new Command("list").description("List Trace-hosted email inboxes in the active organization").option("-o, --output <format>", "Output format: table, json, tsv (default: table)", "table").option("--no-pager", "Disable the interactive pager for table output").action(async (options) => {
|
|
39820
|
+
await runList2(options);
|
|
39821
|
+
});
|
|
39822
|
+
}
|
|
39823
|
+
function createDeleteCommand2() {
|
|
39824
|
+
return new Command("delete").description("Delete a Trace-hosted email inbox").argument("<label>", "Inbox label").option("-y, --yes", "Skip the confirmation prompt (required in non-interactive shells)").action(async (label, options) => {
|
|
39825
|
+
await runDelete2(label, options);
|
|
39826
|
+
});
|
|
39827
|
+
}
|
|
39828
|
+
function createInboxCommand() {
|
|
39829
|
+
const inbox = new Command("inbox").description(
|
|
39830
|
+
"Provision Trace-hosted email inboxes for test-account credentials"
|
|
39831
|
+
);
|
|
39832
|
+
inbox.addCommand(createCreateCommand2());
|
|
39833
|
+
inbox.addCommand(createListCommand3());
|
|
39834
|
+
inbox.addCommand(createDeleteCommand2());
|
|
39835
|
+
return inbox;
|
|
39836
|
+
}
|
|
39837
|
+
|
|
39838
|
+
// src/secret-input.ts
|
|
39839
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
39840
|
+
var SecretInputError = class extends Error {
|
|
39841
|
+
};
|
|
39842
|
+
function secretSourceCount(opts) {
|
|
39843
|
+
return [opts.stdin === true, opts.file !== void 0, opts.env !== void 0].filter(Boolean).length;
|
|
39844
|
+
}
|
|
39845
|
+
function usesStdin(opts) {
|
|
39846
|
+
return opts.stdin === true;
|
|
39847
|
+
}
|
|
39848
|
+
function stripTrailingNewline(value) {
|
|
39849
|
+
return value.replace(/\r?\n$/, "");
|
|
39850
|
+
}
|
|
39851
|
+
async function defaultReadStdin() {
|
|
39852
|
+
const chunks = [];
|
|
39853
|
+
for await (const chunk of process.stdin) {
|
|
39854
|
+
chunks.push(Buffer.from(chunk));
|
|
39855
|
+
}
|
|
39856
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
39857
|
+
}
|
|
39858
|
+
async function resolveSecret(label, flagBase, opts, readStdin = defaultReadStdin) {
|
|
39859
|
+
if (secretSourceCount(opts) > 1) {
|
|
39860
|
+
throw new SecretInputError(
|
|
39861
|
+
`Provide only one source for ${label} (--${flagBase}-stdin / --${flagBase}-file / --${flagBase}-env).`
|
|
39862
|
+
);
|
|
39863
|
+
}
|
|
39864
|
+
if (opts.stdin === true) {
|
|
39865
|
+
const value = stripTrailingNewline(await readStdin());
|
|
39866
|
+
if (value.length === 0) {
|
|
39867
|
+
throw new SecretInputError(`No ${label} received on stdin.`);
|
|
39868
|
+
}
|
|
39869
|
+
return value;
|
|
39870
|
+
}
|
|
39871
|
+
if (opts.file !== void 0) {
|
|
39872
|
+
let contents;
|
|
39873
|
+
try {
|
|
39874
|
+
contents = readFileSync2(opts.file, "utf8");
|
|
39875
|
+
} catch {
|
|
39876
|
+
throw new SecretInputError(`Could not read ${label} file: ${opts.file}`);
|
|
39877
|
+
}
|
|
39878
|
+
const value = stripTrailingNewline(contents);
|
|
39879
|
+
if (value.length === 0) {
|
|
39880
|
+
throw new SecretInputError(`${label} file is empty: ${opts.file}`);
|
|
39881
|
+
}
|
|
39882
|
+
return value;
|
|
39883
|
+
}
|
|
39884
|
+
if (opts.env !== void 0) {
|
|
39885
|
+
const value = process.env[opts.env];
|
|
39886
|
+
if (value === void 0 || value.length === 0) {
|
|
39887
|
+
throw new SecretInputError(
|
|
39888
|
+
`Environment variable ${opts.env} for ${label} is unset or empty.`
|
|
39889
|
+
);
|
|
39890
|
+
}
|
|
39891
|
+
return value;
|
|
39892
|
+
}
|
|
39893
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
39894
|
+
throw new SecretInputError(
|
|
39895
|
+
`No source for ${label}. Use --${flagBase}-stdin, --${flagBase}-file, or --${flagBase}-env (or run interactively to be prompted).`
|
|
39896
|
+
);
|
|
39897
|
+
}
|
|
39898
|
+
const entered = await Ce({ message: `Enter ${label}:` });
|
|
39899
|
+
if (q(entered) || typeof entered !== "string" || entered.length === 0) {
|
|
39900
|
+
throw new SecretInputError(`${label} entry was cancelled or empty.`);
|
|
39901
|
+
}
|
|
39902
|
+
return entered;
|
|
39903
|
+
}
|
|
39904
|
+
|
|
39301
39905
|
// src/credential/commands.ts
|
|
39302
39906
|
var CREDENTIAL_TYPE_BY_INPUT = {
|
|
39303
39907
|
"bearer-token": "BEARER_TOKEN",
|
|
@@ -39523,7 +40127,7 @@ function usernameOpts(opts) {
|
|
|
39523
40127
|
function passwordOpts(opts) {
|
|
39524
40128
|
return { stdin: opts.passwordStdin, file: opts.passwordFile, env: opts.passwordEnv };
|
|
39525
40129
|
}
|
|
39526
|
-
function
|
|
40130
|
+
function failWriteOp3(action, status, body, notFoundLabel) {
|
|
39527
40131
|
const { message, errorCode } = readErrorFields(body);
|
|
39528
40132
|
if (status === UNAUTHORIZED_STATUS2) {
|
|
39529
40133
|
exitAuthFailed();
|
|
@@ -39567,7 +40171,7 @@ function failWriteOp2(action, status, body, notFoundLabel) {
|
|
|
39567
40171
|
);
|
|
39568
40172
|
process.exit(1);
|
|
39569
40173
|
}
|
|
39570
|
-
function
|
|
40174
|
+
function failReadOp3(action, status, body, notFoundLabel) {
|
|
39571
40175
|
const { message } = readErrorFields(body);
|
|
39572
40176
|
if (status === UNAUTHORIZED_STATUS2) {
|
|
39573
40177
|
exitAuthFailed();
|
|
@@ -39659,7 +40263,7 @@ function failAuthBuild(result) {
|
|
|
39659
40263
|
console.error(colorError(result.error));
|
|
39660
40264
|
process.exit(1);
|
|
39661
40265
|
}
|
|
39662
|
-
async function
|
|
40266
|
+
async function runCreate3(options) {
|
|
39663
40267
|
const output = parseOutput(options.output);
|
|
39664
40268
|
requireEnvId(options.env);
|
|
39665
40269
|
const type = normalizeCredentialType(options.type);
|
|
@@ -39699,7 +40303,7 @@ async function runCreate2(options) {
|
|
|
39699
40303
|
(client) => client.credential.createCredential({ params, body })
|
|
39700
40304
|
);
|
|
39701
40305
|
if (result.status !== CREATED_STATUS) {
|
|
39702
|
-
|
|
40306
|
+
failWriteOp3("create", result.status, result.body);
|
|
39703
40307
|
}
|
|
39704
40308
|
if (output === "table") {
|
|
39705
40309
|
console.log(colorSuccess(`Created credential ${sanitizeDisplay(result.body.label)}`));
|
|
@@ -39791,7 +40395,7 @@ async function buildCreateBody(type, label, options) {
|
|
|
39791
40395
|
}
|
|
39792
40396
|
}
|
|
39793
40397
|
}
|
|
39794
|
-
async function
|
|
40398
|
+
async function runList3(options) {
|
|
39795
40399
|
const output = parseOutput(options.output);
|
|
39796
40400
|
requireEnvId(options.env);
|
|
39797
40401
|
const organizationId = await resolveOrganizationId();
|
|
@@ -39799,7 +40403,7 @@ async function runList2(options) {
|
|
|
39799
40403
|
(client) => client.credential.listCredentials({ params: { organizationId, environmentId: options.env } })
|
|
39800
40404
|
);
|
|
39801
40405
|
if (result.status !== SUCCESS_STATUS) {
|
|
39802
|
-
|
|
40406
|
+
failReadOp3("list credentials", result.status, result.body, options.env);
|
|
39803
40407
|
}
|
|
39804
40408
|
const creds = result.body;
|
|
39805
40409
|
if (output === "json") {
|
|
@@ -39833,7 +40437,7 @@ async function runGet2(credentialId, options) {
|
|
|
39833
40437
|
})
|
|
39834
40438
|
);
|
|
39835
40439
|
if (result.status !== SUCCESS_STATUS) {
|
|
39836
|
-
|
|
40440
|
+
failReadOp3("get credential", result.status, result.body, credentialId);
|
|
39837
40441
|
}
|
|
39838
40442
|
outputCredential(result.body, output);
|
|
39839
40443
|
}
|
|
@@ -39867,7 +40471,7 @@ async function runUpdate2(credentialId, options) {
|
|
|
39867
40471
|
(client) => client.credential.getCredential({ params })
|
|
39868
40472
|
);
|
|
39869
40473
|
if (current.status !== SUCCESS_STATUS) {
|
|
39870
|
-
|
|
40474
|
+
failReadOp3("get credential", current.status, current.body, credentialId);
|
|
39871
40475
|
}
|
|
39872
40476
|
const type = current.body.credentialType;
|
|
39873
40477
|
const foreign = rejectForeignAuthFlags(type, options);
|
|
@@ -39900,14 +40504,14 @@ async function runUpdate2(credentialId, options) {
|
|
|
39900
40504
|
(client) => client.credential.updateCredential({ params, body })
|
|
39901
40505
|
);
|
|
39902
40506
|
if (result.status !== SUCCESS_STATUS) {
|
|
39903
|
-
|
|
40507
|
+
failWriteOp3("update", result.status, result.body, credentialId);
|
|
39904
40508
|
}
|
|
39905
40509
|
if (output === "table") {
|
|
39906
40510
|
console.log(colorSuccess(`Updated credential ${sanitizeDisplay(result.body.label)}`));
|
|
39907
40511
|
}
|
|
39908
40512
|
outputCredential(result.body, output);
|
|
39909
40513
|
}
|
|
39910
|
-
async function
|
|
40514
|
+
async function runDelete3(credentialId, options) {
|
|
39911
40515
|
requireEnvId(options.env);
|
|
39912
40516
|
requireCredId(credentialId);
|
|
39913
40517
|
const confirmed = await confirmDeletion(`credential ${credentialId}`, options.yes);
|
|
@@ -39922,7 +40526,7 @@ async function runDelete2(credentialId, options) {
|
|
|
39922
40526
|
})
|
|
39923
40527
|
);
|
|
39924
40528
|
if (result.status !== NO_CONTENT_STATUS) {
|
|
39925
|
-
|
|
40529
|
+
failWriteOp3("delete", result.status, result.body, credentialId);
|
|
39926
40530
|
}
|
|
39927
40531
|
console.log(colorSuccess(`Deleted credential ${credentialId}`));
|
|
39928
40532
|
}
|
|
@@ -39932,7 +40536,7 @@ function addSecretInputOptions(command) {
|
|
|
39932
40536
|
function addAuthConfigOptions(command) {
|
|
39933
40537
|
return command.option("--header-name <name>", "authConfig: header name (bearer-token/jwt-token)").option("--token-prefix <prefix>", "authConfig: token prefix (bearer-token/jwt-token)").option("--placement <header|query>", "authConfig: API key placement (api-key)").option("--key-name <name>", "authConfig: API key name (api-key)").option("--cookie-name <name>", "authConfig: cookie name (cookie)").option("--cookie-domain <domain>", "authConfig: cookie domain (cookie)").option("--cookie-path <path>", "authConfig: cookie path (cookie)").option("--method <form|basic|json>", "authConfig: login method (username-password)").option("--login-url <url>", "authConfig: login URL (username-password)").option("--username-field <name>", "authConfig: username field (username-password)").option("--password-field <name>", "authConfig: password field (username-password)").option("--session-cookie-name <name>", "authConfig: session cookie (username-password)").option("--csrf-token-selector <sel>", "authConfig: CSRF token selector (username-password)").option("--csrf-field-name <name>", "authConfig: CSRF field name (username-password)");
|
|
39934
40538
|
}
|
|
39935
|
-
function
|
|
40539
|
+
function createCreateCommand3() {
|
|
39936
40540
|
const command = new Command("create").description("Create a credential in an environment").requiredOption("--env <environmentId>", "Parent environment UUID").requiredOption(
|
|
39937
40541
|
"--type <type>",
|
|
39938
40542
|
"bearer-token, jwt-token, api-key, passphrase, username-password, cookie, magic-auth"
|
|
@@ -39950,15 +40554,17 @@ Secrets are read from stdin/file/env (or a no-echo prompt) \u2014 never an inlin
|
|
|
39950
40554
|
Examples:
|
|
39951
40555
|
vault read -field=token secret/x | tracecli credential create --env <envId> --type bearer-token --secret-stdin
|
|
39952
40556
|
tracecli credential create --env <envId> --type api-key --label "prod key" --secret-env API_KEY --placement header --key-name X-API-Key
|
|
39953
|
-
tracecli credential create --env <envId> --type username-password --username-env U --password-env P --method form --login-url https://acme.com/login
|
|
40557
|
+
tracecli credential create --env <envId> --type username-password --username-env U --password-env P --method form --login-url https://acme.com/login
|
|
40558
|
+
ADDRESS=$(tracecli credential inbox create portal -o json | jq -r .address)
|
|
40559
|
+
echo "$ADDRESS" | tracecli credential create --env <envId> --type magic-auth --secret-stdin`
|
|
39954
40560
|
);
|
|
39955
40561
|
return command.action(async (options) => {
|
|
39956
|
-
await
|
|
40562
|
+
await runCreate3(options);
|
|
39957
40563
|
});
|
|
39958
40564
|
}
|
|
39959
|
-
function
|
|
40565
|
+
function createListCommand4() {
|
|
39960
40566
|
return new Command("list").description("List an environment's credentials (secrets are never shown)").requiredOption("--env <environmentId>", "Parent environment UUID").option("-o, --output <format>", "Output format: table, json, tsv (default: table)", "table").option("--no-pager", "Disable the interactive pager for table output").action(async (options) => {
|
|
39961
|
-
await
|
|
40567
|
+
await runList3(options);
|
|
39962
40568
|
});
|
|
39963
40569
|
}
|
|
39964
40570
|
function createGetCommand2() {
|
|
@@ -39984,18 +40590,19 @@ Examples:
|
|
|
39984
40590
|
await runUpdate2(credentialId, options);
|
|
39985
40591
|
});
|
|
39986
40592
|
}
|
|
39987
|
-
function
|
|
40593
|
+
function createDeleteCommand3() {
|
|
39988
40594
|
return new Command("delete").description("Delete a credential").argument("<credentialId>", "Credential UUID").requiredOption("--env <environmentId>", "Parent environment UUID").option("-y, --yes", "Skip the confirmation prompt (required in non-interactive shells)").action(async (credentialId, options) => {
|
|
39989
|
-
await
|
|
40595
|
+
await runDelete3(credentialId, options);
|
|
39990
40596
|
});
|
|
39991
40597
|
}
|
|
39992
40598
|
function createCredentialCommand() {
|
|
39993
40599
|
const credential = new Command("credential").alias("cred").description("Manage environment credentials");
|
|
39994
|
-
credential.addCommand(
|
|
39995
|
-
credential.addCommand(
|
|
40600
|
+
credential.addCommand(createCreateCommand3());
|
|
40601
|
+
credential.addCommand(createListCommand4());
|
|
39996
40602
|
credential.addCommand(createGetCommand2());
|
|
39997
40603
|
credential.addCommand(createUpdateCommand2());
|
|
39998
|
-
credential.addCommand(
|
|
40604
|
+
credential.addCommand(createDeleteCommand3());
|
|
40605
|
+
credential.addCommand(createInboxCommand());
|
|
39999
40606
|
return credential;
|
|
40000
40607
|
}
|
|
40001
40608
|
|
|
@@ -40038,7 +40645,7 @@ function resolveProductionFlag(input) {
|
|
|
40038
40645
|
}
|
|
40039
40646
|
return { value: void 0 };
|
|
40040
40647
|
}
|
|
40041
|
-
function
|
|
40648
|
+
function failWriteOp4(action, status, body, notFoundLabel) {
|
|
40042
40649
|
const { message, errorCode } = readErrorFields(body);
|
|
40043
40650
|
if (status === UNAUTHORIZED_STATUS2) {
|
|
40044
40651
|
exitAuthFailed();
|
|
@@ -40086,7 +40693,7 @@ function failWriteOp3(action, status, body, notFoundLabel) {
|
|
|
40086
40693
|
);
|
|
40087
40694
|
process.exit(1);
|
|
40088
40695
|
}
|
|
40089
|
-
function
|
|
40696
|
+
function failReadOp4(action, status, body, notFoundLabel) {
|
|
40090
40697
|
const { message } = readErrorFields(body);
|
|
40091
40698
|
if (status === UNAUTHORIZED_STATUS2) {
|
|
40092
40699
|
exitAuthFailed();
|
|
@@ -40227,7 +40834,7 @@ function requireEnvId2(environmentId) {
|
|
|
40227
40834
|
process.exit(1);
|
|
40228
40835
|
}
|
|
40229
40836
|
}
|
|
40230
|
-
async function
|
|
40837
|
+
async function runCreate4(name, options) {
|
|
40231
40838
|
const output = parseOutput(options.output);
|
|
40232
40839
|
requireAppId(options.app);
|
|
40233
40840
|
const nameError = validateEnvName(name);
|
|
@@ -40249,7 +40856,7 @@ async function runCreate3(name, options) {
|
|
|
40249
40856
|
})
|
|
40250
40857
|
);
|
|
40251
40858
|
if (result.status !== CREATED_STATUS) {
|
|
40252
|
-
|
|
40859
|
+
failWriteOp4("create", result.status, result.body);
|
|
40253
40860
|
}
|
|
40254
40861
|
if (output === "table") {
|
|
40255
40862
|
console.log(colorSuccess(`Created environment ${sanitizeDisplay(result.body.name)}`));
|
|
@@ -40276,7 +40883,7 @@ function createOrgEnvironmentRequests(organizationId) {
|
|
|
40276
40883
|
async function collectOrgEnvironments(requests) {
|
|
40277
40884
|
const appsResult = await requests.listApplications();
|
|
40278
40885
|
if (!appsResult.ok) {
|
|
40279
|
-
|
|
40886
|
+
failReadOp4("list applications", appsResult.status, appsResult.body);
|
|
40280
40887
|
}
|
|
40281
40888
|
const apps = appsResult.data;
|
|
40282
40889
|
const environments = [];
|
|
@@ -40348,7 +40955,7 @@ async function runListAcrossOrganization(output, pager) {
|
|
|
40348
40955
|
disabled: pager === false
|
|
40349
40956
|
});
|
|
40350
40957
|
}
|
|
40351
|
-
async function
|
|
40958
|
+
async function runList4(options) {
|
|
40352
40959
|
const output = parseOutput(options.output);
|
|
40353
40960
|
if (options.app === void 0) {
|
|
40354
40961
|
await runListAcrossOrganization(output, options.pager);
|
|
@@ -40363,7 +40970,7 @@ async function runList3(options) {
|
|
|
40363
40970
|
})
|
|
40364
40971
|
);
|
|
40365
40972
|
if (result.status !== SUCCESS_STATUS) {
|
|
40366
|
-
|
|
40973
|
+
failReadOp4("list environments", result.status, result.body, applicationId);
|
|
40367
40974
|
}
|
|
40368
40975
|
const envs = result.body;
|
|
40369
40976
|
if (output === "json") {
|
|
@@ -40397,7 +41004,7 @@ async function runGet3(environmentId, options) {
|
|
|
40397
41004
|
})
|
|
40398
41005
|
);
|
|
40399
41006
|
if (result.status !== SUCCESS_STATUS) {
|
|
40400
|
-
|
|
41007
|
+
failReadOp4("get environment", result.status, result.body, environmentId);
|
|
40401
41008
|
}
|
|
40402
41009
|
outputEnvironment(result.body, output);
|
|
40403
41010
|
}
|
|
@@ -40439,14 +41046,14 @@ async function runUpdate3(environmentId, options) {
|
|
|
40439
41046
|
})
|
|
40440
41047
|
);
|
|
40441
41048
|
if (result.status !== SUCCESS_STATUS) {
|
|
40442
|
-
|
|
41049
|
+
failWriteOp4("update", result.status, result.body, environmentId);
|
|
40443
41050
|
}
|
|
40444
41051
|
if (output === "table") {
|
|
40445
41052
|
console.log(colorSuccess(`Updated environment ${sanitizeDisplay(result.body.name)}`));
|
|
40446
41053
|
}
|
|
40447
41054
|
outputEnvironment(result.body, output);
|
|
40448
41055
|
}
|
|
40449
|
-
async function
|
|
41056
|
+
async function runDelete4(environmentId, options) {
|
|
40450
41057
|
requireAppId(options.app);
|
|
40451
41058
|
requireEnvId2(environmentId);
|
|
40452
41059
|
const confirmed = await confirmDeletion(`environment ${environmentId}`, options.yes);
|
|
@@ -40461,11 +41068,11 @@ async function runDelete3(environmentId, options) {
|
|
|
40461
41068
|
})
|
|
40462
41069
|
);
|
|
40463
41070
|
if (result.status !== NO_CONTENT_STATUS) {
|
|
40464
|
-
|
|
41071
|
+
failWriteOp4("delete", result.status, result.body, environmentId);
|
|
40465
41072
|
}
|
|
40466
41073
|
console.log(colorSuccess(`Deleted environment ${environmentId}`));
|
|
40467
41074
|
}
|
|
40468
|
-
function
|
|
41075
|
+
function createCreateCommand4() {
|
|
40469
41076
|
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").requiredOption("--url <url>", "Primary URL for the environment").option("--description <text>", "Description").option("--production", "Mark as the production environment").option("-o, --output <format>", "Output format: table, json, tsv (default: table)", "table").addHelpText(
|
|
40470
41077
|
"after",
|
|
40471
41078
|
`
|
|
@@ -40473,10 +41080,10 @@ Examples:
|
|
|
40473
41080
|
tracecli environment create staging --app <appId> --url https://staging.acme.com
|
|
40474
41081
|
tracecli environment create production --app <appId> --url https://acme.com --production`
|
|
40475
41082
|
).action(async (name, options) => {
|
|
40476
|
-
await
|
|
41083
|
+
await runCreate4(name, options);
|
|
40477
41084
|
});
|
|
40478
41085
|
}
|
|
40479
|
-
function
|
|
41086
|
+
function createListCommand5() {
|
|
40480
41087
|
return new Command("list").description("List environments \u2014 an application's, or every one in the organization").option("--app <applicationId>", "Parent application UUID (omit to list the whole org)").option("-o, --output <format>", "Output format: table, json, tsv (default: table)", "table").option("--no-pager", "Disable the interactive pager for table output").addHelpText(
|
|
40481
41088
|
"after",
|
|
40482
41089
|
`
|
|
@@ -40488,7 +41095,7 @@ Examples:
|
|
|
40488
41095
|
tracecli environment list --app <appId>
|
|
40489
41096
|
tracecli environment list --output tsv | cut -f2,3,4`
|
|
40490
41097
|
).action(async (options) => {
|
|
40491
|
-
await
|
|
41098
|
+
await runList4(options);
|
|
40492
41099
|
});
|
|
40493
41100
|
}
|
|
40494
41101
|
function createGetCommand3() {
|
|
@@ -40507,18 +41114,18 @@ Examples:
|
|
|
40507
41114
|
await runUpdate3(environmentId, options);
|
|
40508
41115
|
});
|
|
40509
41116
|
}
|
|
40510
|
-
function
|
|
41117
|
+
function createDeleteCommand4() {
|
|
40511
41118
|
return new Command("delete").description("Delete an environment").argument("<environmentId>", "Environment UUID").requiredOption("--app <applicationId>", "Parent application UUID").option("-y, --yes", "Skip the confirmation prompt (required in non-interactive shells)").action(async (environmentId, options) => {
|
|
40512
|
-
await
|
|
41119
|
+
await runDelete4(environmentId, options);
|
|
40513
41120
|
});
|
|
40514
41121
|
}
|
|
40515
41122
|
function createEnvironmentCommand() {
|
|
40516
41123
|
const environment = new Command("environment").alias("env").description("Manage application environments");
|
|
40517
|
-
environment.addCommand(
|
|
40518
|
-
environment.addCommand(
|
|
41124
|
+
environment.addCommand(createCreateCommand4());
|
|
41125
|
+
environment.addCommand(createListCommand5());
|
|
40519
41126
|
environment.addCommand(createGetCommand3());
|
|
40520
41127
|
environment.addCommand(createUpdateCommand3());
|
|
40521
|
-
environment.addCommand(
|
|
41128
|
+
environment.addCommand(createDeleteCommand4());
|
|
40522
41129
|
return environment;
|
|
40523
41130
|
}
|
|
40524
41131
|
|
|
@@ -40550,7 +41157,223 @@ var CLI_PACKAGE_NAME = packageMetadata.name;
|
|
|
40550
41157
|
var CLI_VERSION = packageMetadata.version;
|
|
40551
41158
|
|
|
40552
41159
|
// src/pentest/commands.ts
|
|
40553
|
-
import { readFileSync as
|
|
41160
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
41161
|
+
|
|
41162
|
+
// src/pentest/evidence.ts
|
|
41163
|
+
import { readFileSync as readFileSync4, statSync } from "fs";
|
|
41164
|
+
import { basename } from "path";
|
|
41165
|
+
|
|
41166
|
+
// src/pentest/resolve.ts
|
|
41167
|
+
var UUID_PATTERN2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
41168
|
+
async function resolvePentestId(organizationId, identifier) {
|
|
41169
|
+
if (UUID_PATTERN2.test(identifier)) {
|
|
41170
|
+
return identifier;
|
|
41171
|
+
}
|
|
41172
|
+
const numericMatch = identifier.match(/^(?:PT-)?(\d+)$/i);
|
|
41173
|
+
if (!numericMatch?.[1]) {
|
|
41174
|
+
console.error(
|
|
41175
|
+
`Invalid pentest identifier '${identifier}'. Use a display ID (e.g. PT-3) or UUID.`
|
|
41176
|
+
);
|
|
41177
|
+
process.exit(1);
|
|
41178
|
+
}
|
|
41179
|
+
const displayId = Number.parseInt(numericMatch[1], 10);
|
|
41180
|
+
const result = await authenticatedRequest(
|
|
41181
|
+
(client) => client.pentest.listPentests({
|
|
41182
|
+
params: { organizationId }
|
|
41183
|
+
})
|
|
41184
|
+
);
|
|
41185
|
+
if (result.status === 401) {
|
|
41186
|
+
console.error("Authentication failed. Run `tracecli auth login` to re-authenticate.");
|
|
41187
|
+
process.exit(1);
|
|
41188
|
+
}
|
|
41189
|
+
if (result.status !== 200) {
|
|
41190
|
+
console.error(`Failed to list pentests (status ${result.status}).`);
|
|
41191
|
+
process.exit(1);
|
|
41192
|
+
}
|
|
41193
|
+
const pentest = result.body.find((p2) => p2.displayId === displayId);
|
|
41194
|
+
if (!pentest) {
|
|
41195
|
+
console.error(
|
|
41196
|
+
`Pentest with display ID ${displayId} not found.
|
|
41197
|
+
Run \`tracecli pentest list\` to see available pentests.`
|
|
41198
|
+
);
|
|
41199
|
+
process.exit(1);
|
|
41200
|
+
}
|
|
41201
|
+
return pentest.pentestId;
|
|
41202
|
+
}
|
|
41203
|
+
|
|
41204
|
+
// src/pentest/evidence.ts
|
|
41205
|
+
var SECTION_LIST = ValidationEvidenceSectionValues.join(", ");
|
|
41206
|
+
var KIND_LIST = PentestExploitArtifactKindValues.join(", ");
|
|
41207
|
+
function isSection(value) {
|
|
41208
|
+
return ValidationEvidenceSectionValues.includes(value);
|
|
41209
|
+
}
|
|
41210
|
+
function isKind(value) {
|
|
41211
|
+
return PentestExploitArtifactKindValues.includes(value);
|
|
41212
|
+
}
|
|
41213
|
+
function formatBytes(bytes) {
|
|
41214
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
41215
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)} KB`;
|
|
41216
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
41217
|
+
}
|
|
41218
|
+
async function resolveFindingId(organizationId, pentestId, identifier) {
|
|
41219
|
+
if (isUuid(identifier)) {
|
|
41220
|
+
return identifier;
|
|
41221
|
+
}
|
|
41222
|
+
const numericMatch = identifier.match(/^(?:PTF-)?(\d+)$/i);
|
|
41223
|
+
if (!numericMatch?.[1]) {
|
|
41224
|
+
console.error(
|
|
41225
|
+
colorError(
|
|
41226
|
+
`Invalid finding identifier '${sanitizeDisplay(identifier)}'. Use a finding number (e.g. PTF-7) or UUID.`
|
|
41227
|
+
)
|
|
41228
|
+
);
|
|
41229
|
+
process.exit(1);
|
|
41230
|
+
}
|
|
41231
|
+
const displayId = Number.parseInt(numericMatch[1], 10);
|
|
41232
|
+
const result = await authenticatedRequest(
|
|
41233
|
+
(client) => client.pentestFinding.listFindings({ params: { organizationId, pentestId } })
|
|
41234
|
+
);
|
|
41235
|
+
if (result.status === UNAUTHORIZED_STATUS2) {
|
|
41236
|
+
exitAuthFailed();
|
|
41237
|
+
}
|
|
41238
|
+
if (result.status !== 200) {
|
|
41239
|
+
console.error(colorError(`Failed to list findings (status ${result.status}).`));
|
|
41240
|
+
process.exit(1);
|
|
41241
|
+
}
|
|
41242
|
+
const match = result.body.find((f) => f.displayId === displayId);
|
|
41243
|
+
if (!match || !("pentestFindingId" in match)) {
|
|
41244
|
+
console.error(
|
|
41245
|
+
colorError(
|
|
41246
|
+
`No finding PTF-${displayId} in this pentest. Unpublished findings have no number yet \u2014 pass the finding UUID instead (\`tracecli pentest findings list --pentest <id> --output json\`).`
|
|
41247
|
+
)
|
|
41248
|
+
);
|
|
41249
|
+
process.exit(1);
|
|
41250
|
+
}
|
|
41251
|
+
return match.pentestFindingId;
|
|
41252
|
+
}
|
|
41253
|
+
async function runAttachEvidence(options) {
|
|
41254
|
+
if (options.section !== void 0 && !isSection(options.section)) {
|
|
41255
|
+
console.error(colorError(`Invalid --section '${sanitizeDisplay(options.section)}'.`));
|
|
41256
|
+
console.error(`Valid sections: ${SECTION_LIST}`);
|
|
41257
|
+
process.exit(1);
|
|
41258
|
+
}
|
|
41259
|
+
if (options.kind !== void 0 && !isKind(options.kind)) {
|
|
41260
|
+
console.error(colorError(`Invalid --kind '${sanitizeDisplay(options.kind)}'.`));
|
|
41261
|
+
console.error(`Valid kinds: ${KIND_LIST}`);
|
|
41262
|
+
process.exit(1);
|
|
41263
|
+
}
|
|
41264
|
+
let sizeBytes;
|
|
41265
|
+
try {
|
|
41266
|
+
const stats = statSync(options.file);
|
|
41267
|
+
if (!stats.isFile()) {
|
|
41268
|
+
console.error(colorError(`--file '${sanitizeDisplay(options.file)}' is not a regular file.`));
|
|
41269
|
+
process.exit(1);
|
|
41270
|
+
}
|
|
41271
|
+
sizeBytes = stats.size;
|
|
41272
|
+
} catch {
|
|
41273
|
+
console.error(colorError(`Cannot read --file '${sanitizeDisplay(options.file)}'.`));
|
|
41274
|
+
process.exit(1);
|
|
41275
|
+
}
|
|
41276
|
+
if (sizeBytes === 0) {
|
|
41277
|
+
console.error(colorError(`--file '${sanitizeDisplay(options.file)}' is empty.`));
|
|
41278
|
+
process.exit(1);
|
|
41279
|
+
}
|
|
41280
|
+
if (sizeBytes > MAX_STAFF_EVIDENCE_UPLOAD_BYTES) {
|
|
41281
|
+
console.error(
|
|
41282
|
+
colorError(
|
|
41283
|
+
`File is ${formatBytes(sizeBytes)}; the limit for one exhibit is ${formatBytes(MAX_STAFF_EVIDENCE_UPLOAD_BYTES)}.`
|
|
41284
|
+
)
|
|
41285
|
+
);
|
|
41286
|
+
process.exit(1);
|
|
41287
|
+
}
|
|
41288
|
+
const organizationId = await resolveOrganizationId();
|
|
41289
|
+
const pentestId = await resolvePentestId(organizationId, options.pentest);
|
|
41290
|
+
const pentestFindingId = await resolveFindingId(organizationId, pentestId, options.finding);
|
|
41291
|
+
const params = { organizationId, pentestId, pentestFindingId };
|
|
41292
|
+
const filename = basename(options.file);
|
|
41293
|
+
const slot = await authenticatedRequest(
|
|
41294
|
+
(client) => client.pentestFinding.createFindingEvidenceUpload({
|
|
41295
|
+
params,
|
|
41296
|
+
body: { filename, sizeBytes }
|
|
41297
|
+
})
|
|
41298
|
+
);
|
|
41299
|
+
if (slot.status === UNAUTHORIZED_STATUS2) {
|
|
41300
|
+
exitAuthFailed();
|
|
41301
|
+
}
|
|
41302
|
+
if (slot.status !== 201) {
|
|
41303
|
+
const { message } = readErrorFields(slot.body);
|
|
41304
|
+
console.error(
|
|
41305
|
+
colorError(message ?? `Failed to start the evidence upload (status ${slot.status}).`)
|
|
41306
|
+
);
|
|
41307
|
+
process.exit(1);
|
|
41308
|
+
}
|
|
41309
|
+
const body = readFileSync4(options.file);
|
|
41310
|
+
const put = await fetch(slot.body.uploadUrl, { method: "PUT", body: new Uint8Array(body) });
|
|
41311
|
+
if (!put.ok) {
|
|
41312
|
+
console.error(
|
|
41313
|
+
colorError(
|
|
41314
|
+
`Upload to storage failed (status ${put.status}). The upload link is short-lived \u2014 re-run the command.`
|
|
41315
|
+
)
|
|
41316
|
+
);
|
|
41317
|
+
process.exit(1);
|
|
41318
|
+
}
|
|
41319
|
+
const attached = await authenticatedRequest(
|
|
41320
|
+
(client) => client.pentestFinding.attachFindingEvidence({
|
|
41321
|
+
params,
|
|
41322
|
+
body: {
|
|
41323
|
+
key: slot.body.key,
|
|
41324
|
+
...options.caption !== void 0 ? { caption: options.caption } : {},
|
|
41325
|
+
...options.section !== void 0 && isSection(options.section) ? { section: options.section } : {},
|
|
41326
|
+
...options.kind !== void 0 && isKind(options.kind) ? { kind: options.kind } : {},
|
|
41327
|
+
...options.customerFacing ? { customerFacing: true } : {}
|
|
41328
|
+
}
|
|
41329
|
+
})
|
|
41330
|
+
);
|
|
41331
|
+
if (attached.status === UNAUTHORIZED_STATUS2) {
|
|
41332
|
+
exitAuthFailed();
|
|
41333
|
+
}
|
|
41334
|
+
if (attached.status !== 201) {
|
|
41335
|
+
const { message } = readErrorFields(attached.body);
|
|
41336
|
+
console.error(
|
|
41337
|
+
colorError(message ?? `Failed to attach the evidence (status ${attached.status}).`)
|
|
41338
|
+
);
|
|
41339
|
+
process.exit(1);
|
|
41340
|
+
}
|
|
41341
|
+
if (options.output === "json") {
|
|
41342
|
+
console.log(JSON.stringify(attached.body, null, 2));
|
|
41343
|
+
return;
|
|
41344
|
+
}
|
|
41345
|
+
const visibility = attached.body.customerFacing ? "customer-facing" : "staff-internal";
|
|
41346
|
+
const placement = attached.body.section ? ` \xB7 ${attached.body.section}` : "";
|
|
41347
|
+
console.log(
|
|
41348
|
+
colorSuccess(
|
|
41349
|
+
`Attached ${attached.body.kind} \xB7 ${formatBytes(attached.body.sizeBytes)} \xB7 ${visibility}${placement}`
|
|
41350
|
+
)
|
|
41351
|
+
);
|
|
41352
|
+
console.log(` artifact ${attached.body.artifactId}`);
|
|
41353
|
+
if (attached.body.kind === "HAR") {
|
|
41354
|
+
console.log(" HAR captures stay internal \u2014 they can carry a live session token.");
|
|
41355
|
+
} else if (!attached.body.customerFacing) {
|
|
41356
|
+
console.log(" Publish it from the finding\u2019s evidence panel in the staff console.");
|
|
41357
|
+
}
|
|
41358
|
+
}
|
|
41359
|
+
function createAttachEvidenceCommand() {
|
|
41360
|
+
return new Command("attach-evidence").description("Attach a screenshot, recording, or capture to a pentest finding (staff)").requiredOption("--pentest <id>", "Pentest display ID (e.g. PT-3) or UUID").requiredOption("--finding <id>", "Finding number (e.g. PTF-7) or UUID").requiredOption("--file <path>", "Path to the evidence file").option("--caption <text>", "What the exhibit shows; rendered as its label").option("--section <section>", `Report section it backs (${SECTION_LIST})`).option("--kind <kind>", `Override the derived exhibit kind (${KIND_LIST})`).option("--customer-facing", "Publish to the customer (not allowed for HAR or video)").option("-o, --output <format>", "Output format: text, json (default: text)", "text").addHelpText(
|
|
41361
|
+
"after",
|
|
41362
|
+
`
|
|
41363
|
+
The file uploads straight to storage, so recordings of any reasonable size work.
|
|
41364
|
+
Videos and HAR captures always land staff-internal \u2014 publish a video with the
|
|
41365
|
+
toggle in the staff console's evidence panel after reviewing it for redaction.
|
|
41366
|
+
|
|
41367
|
+
Examples:
|
|
41368
|
+
tracecli pentest findings attach-evidence --pentest PT-3 --finding 7 --file ./poc.png
|
|
41369
|
+
tracecli pentest findings attach-evidence --pentest PT-3 --finding PTF-7 --file ./poc.mov \\
|
|
41370
|
+
--caption "Cookie survives logout, replayed 40s later" --section proofOfConcept
|
|
41371
|
+
tracecli pentest findings attach-evidence --pentest PT-3 --finding 7 --file ./roster.json \\
|
|
41372
|
+
--caption "Full member roster returned to a non-member" --customer-facing --output json`
|
|
41373
|
+
).action(async (options) => {
|
|
41374
|
+
await runAttachEvidence(options);
|
|
41375
|
+
});
|
|
41376
|
+
}
|
|
40554
41377
|
|
|
40555
41378
|
// src/pentest/questionnaire.ts
|
|
40556
41379
|
function buildUpsertQuestionnaireBody(answers) {
|
|
@@ -40633,9 +41456,9 @@ function formatAnswerValue(key, answers) {
|
|
|
40633
41456
|
}
|
|
40634
41457
|
}
|
|
40635
41458
|
function formatQuestionnaireShow(input) {
|
|
40636
|
-
const
|
|
40637
|
-
|
|
40638
|
-
];
|
|
41459
|
+
const submitted = input.submitted === true;
|
|
41460
|
+
const editableLine = input.editable ? "yes (scoping window open)" : submitted ? "no (submitted)" : "no (editing closed after scoping)";
|
|
41461
|
+
const lines = [`Editable: ${editableLine}`, `Submitted: ${submitted ? "yes" : "no"}`];
|
|
40639
41462
|
if (input.answers === null || Object.keys(input.answers).length === 0) {
|
|
40640
41463
|
lines.push("Answers: (none)");
|
|
40641
41464
|
return lines.join("\n");
|
|
@@ -40695,43 +41518,7 @@ function formatFindingSeverity(severity) {
|
|
|
40695
41518
|
return severity;
|
|
40696
41519
|
}
|
|
40697
41520
|
}
|
|
40698
|
-
|
|
40699
|
-
async function resolvePentestId(organizationId, identifier) {
|
|
40700
|
-
if (UUID_PATTERN2.test(identifier)) {
|
|
40701
|
-
return identifier;
|
|
40702
|
-
}
|
|
40703
|
-
const numericMatch = identifier.match(/^(?:PT-)?(\d+)$/i);
|
|
40704
|
-
if (!numericMatch?.[1]) {
|
|
40705
|
-
console.error(
|
|
40706
|
-
`Invalid pentest identifier '${identifier}'. Use a display ID (e.g. PT-3) or UUID.`
|
|
40707
|
-
);
|
|
40708
|
-
process.exit(1);
|
|
40709
|
-
}
|
|
40710
|
-
const displayId = Number.parseInt(numericMatch[1], 10);
|
|
40711
|
-
const result = await authenticatedRequest(
|
|
40712
|
-
(client) => client.pentest.listPentests({
|
|
40713
|
-
params: { organizationId }
|
|
40714
|
-
})
|
|
40715
|
-
);
|
|
40716
|
-
if (result.status === 401) {
|
|
40717
|
-
console.error("Authentication failed. Run `tracecli auth login` to re-authenticate.");
|
|
40718
|
-
process.exit(1);
|
|
40719
|
-
}
|
|
40720
|
-
if (result.status !== 200) {
|
|
40721
|
-
console.error(`Failed to list pentests (status ${result.status}).`);
|
|
40722
|
-
process.exit(1);
|
|
40723
|
-
}
|
|
40724
|
-
const pentest = result.body.find((p2) => p2.displayId === displayId);
|
|
40725
|
-
if (!pentest) {
|
|
40726
|
-
console.error(
|
|
40727
|
-
`Pentest with display ID ${displayId} not found.
|
|
40728
|
-
Run \`tracecli pentest list\` to see available pentests.`
|
|
40729
|
-
);
|
|
40730
|
-
process.exit(1);
|
|
40731
|
-
}
|
|
40732
|
-
return pentest.pentestId;
|
|
40733
|
-
}
|
|
40734
|
-
function createListCommand5() {
|
|
41521
|
+
function createListCommand6() {
|
|
40735
41522
|
return new Command("list").description("List pentests for your organization").option(
|
|
40736
41523
|
"--status <status>",
|
|
40737
41524
|
"Filter by status (SCOPING, IN_PROGRESS, REMEDIATION, CLOSED)",
|
|
@@ -40981,6 +41768,7 @@ function renderPentestFindingLines(finding) {
|
|
|
40981
41768
|
function createFindingsCommand() {
|
|
40982
41769
|
const findings = new Command("findings").description("Manage pentest findings");
|
|
40983
41770
|
findings.addCommand(createFindingsListCommand());
|
|
41771
|
+
findings.addCommand(createAttachEvidenceCommand());
|
|
40984
41772
|
return findings;
|
|
40985
41773
|
}
|
|
40986
41774
|
var PRIORITY_SET = new Set(CustomerPriorityCategoryValues);
|
|
@@ -41251,7 +42039,7 @@ async function fetchQuestionnaire(organizationId, pentestId, notFoundLabel) {
|
|
|
41251
42039
|
if (result.status !== SUCCESS_STATUS) {
|
|
41252
42040
|
failQuestionnaire(result.status, result.body, "read", notFoundLabel);
|
|
41253
42041
|
}
|
|
41254
|
-
return result.body;
|
|
42042
|
+
return toQuestionnaireGetBody(result.body);
|
|
41255
42043
|
}
|
|
41256
42044
|
async function putQuestionnaire(organizationId, pentestId, answers, notFoundLabel) {
|
|
41257
42045
|
const body = buildUpsertQuestionnaireBody(answers);
|
|
@@ -41264,7 +42052,35 @@ async function putQuestionnaire(organizationId, pentestId, answers, notFoundLabe
|
|
|
41264
42052
|
if (result.status !== SUCCESS_STATUS) {
|
|
41265
42053
|
failQuestionnaire(result.status, result.body, "write", notFoundLabel);
|
|
41266
42054
|
}
|
|
41267
|
-
return result.body;
|
|
42055
|
+
return toQuestionnaireGetBody(result.body);
|
|
42056
|
+
}
|
|
42057
|
+
async function submitQuestionnaire(organizationId, pentestId, answers, notFoundLabel) {
|
|
42058
|
+
const result = await authenticatedRequest(
|
|
42059
|
+
(client) => client.pentest.submitCustomerPentestQuestionnaire({
|
|
42060
|
+
params: { organizationId, pentestId },
|
|
42061
|
+
body: answers === void 0 ? {} : { answers }
|
|
42062
|
+
})
|
|
42063
|
+
);
|
|
42064
|
+
if (result.status !== SUCCESS_STATUS) {
|
|
42065
|
+
failQuestionnaire(result.status, result.body, "write", notFoundLabel);
|
|
42066
|
+
}
|
|
42067
|
+
return toQuestionnaireGetBody(result.body);
|
|
42068
|
+
}
|
|
42069
|
+
function toQuestionnaireGetBody(body) {
|
|
42070
|
+
return {
|
|
42071
|
+
pentestId: body.pentestId,
|
|
42072
|
+
editable: body.editable,
|
|
42073
|
+
answers: body.answers,
|
|
42074
|
+
updatedAt: body.updatedAt,
|
|
42075
|
+
...body.submittedAt !== void 0 ? { submittedAt: body.submittedAt } : {}
|
|
42076
|
+
};
|
|
42077
|
+
}
|
|
42078
|
+
function questionnaireShowInput(body) {
|
|
42079
|
+
return {
|
|
42080
|
+
answers: body.answers,
|
|
42081
|
+
editable: body.editable,
|
|
42082
|
+
submitted: body.submittedAt != null
|
|
42083
|
+
};
|
|
41268
42084
|
}
|
|
41269
42085
|
function exitCancelled() {
|
|
41270
42086
|
me("Cancelled.");
|
|
@@ -41330,15 +42146,20 @@ async function promptQuestionnaireAnswers(existing) {
|
|
|
41330
42146
|
async function runQuestionnaire(pentestIdentifier, options) {
|
|
41331
42147
|
const show = options.show === true;
|
|
41332
42148
|
const filePath = options.file;
|
|
42149
|
+
const submit = options.submit === true;
|
|
41333
42150
|
if (show && filePath !== void 0) {
|
|
41334
42151
|
console.error(colorError("Pass only one of --show / --file."));
|
|
41335
42152
|
process.exit(1);
|
|
41336
42153
|
}
|
|
42154
|
+
if (show && submit) {
|
|
42155
|
+
console.error(colorError("Pass only one of --show / --submit."));
|
|
42156
|
+
process.exit(1);
|
|
42157
|
+
}
|
|
41337
42158
|
const isInteractiveTty = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
41338
|
-
if (!show && filePath === void 0 && !isInteractiveTty) {
|
|
42159
|
+
if (!show && filePath === void 0 && !submit && !isInteractiveTty) {
|
|
41339
42160
|
console.error(
|
|
41340
42161
|
colorError(
|
|
41341
|
-
"Interactive questionnaire requires a TTY. Use --show to print answers,
|
|
42162
|
+
"Interactive questionnaire requires a TTY. Use --show to print answers, --file <path> to upload an answers JSON object, or --submit to freeze the current answers."
|
|
41342
42163
|
)
|
|
41343
42164
|
);
|
|
41344
42165
|
process.exit(2);
|
|
@@ -41347,18 +42168,13 @@ async function runQuestionnaire(pentestIdentifier, options) {
|
|
|
41347
42168
|
const pentestId = await resolvePentestId(organizationId, pentestIdentifier);
|
|
41348
42169
|
if (show) {
|
|
41349
42170
|
const current2 = await fetchQuestionnaire(organizationId, pentestId, pentestIdentifier);
|
|
41350
|
-
console.log(
|
|
41351
|
-
formatQuestionnaireShow({
|
|
41352
|
-
answers: current2.answers,
|
|
41353
|
-
editable: current2.editable
|
|
41354
|
-
})
|
|
41355
|
-
);
|
|
42171
|
+
console.log(formatQuestionnaireShow(questionnaireShowInput(current2)));
|
|
41356
42172
|
return;
|
|
41357
42173
|
}
|
|
41358
42174
|
if (filePath !== void 0) {
|
|
41359
42175
|
let raw;
|
|
41360
42176
|
try {
|
|
41361
|
-
raw =
|
|
42177
|
+
raw = readFileSync5(filePath, "utf8");
|
|
41362
42178
|
} catch {
|
|
41363
42179
|
console.error(colorError(`Could not read answers file: ${filePath}`));
|
|
41364
42180
|
process.exit(1);
|
|
@@ -41368,26 +42184,31 @@ async function runQuestionnaire(pentestIdentifier, options) {
|
|
|
41368
42184
|
console.error(colorError(parsed.error));
|
|
41369
42185
|
process.exit(1);
|
|
41370
42186
|
}
|
|
41371
|
-
const saved2 = await putQuestionnaire(
|
|
42187
|
+
const saved2 = submit ? await submitQuestionnaire(organizationId, pentestId, parsed.answers, pentestIdentifier) : await putQuestionnaire(organizationId, pentestId, parsed.answers, pentestIdentifier);
|
|
42188
|
+
console.log(
|
|
42189
|
+
colorSuccess(
|
|
42190
|
+
submit ? "Submitted questionnaire. Answers are locked." : "Saved questionnaire answers."
|
|
42191
|
+
)
|
|
42192
|
+
);
|
|
42193
|
+
console.log(formatQuestionnaireShow(questionnaireShowInput(saved2)));
|
|
42194
|
+
return;
|
|
42195
|
+
}
|
|
42196
|
+
if (submit) {
|
|
42197
|
+
const submitted = await submitQuestionnaire(
|
|
41372
42198
|
organizationId,
|
|
41373
42199
|
pentestId,
|
|
41374
|
-
|
|
42200
|
+
void 0,
|
|
41375
42201
|
pentestIdentifier
|
|
41376
42202
|
);
|
|
41377
|
-
console.log(colorSuccess("
|
|
41378
|
-
console.log(
|
|
41379
|
-
formatQuestionnaireShow({
|
|
41380
|
-
answers: saved2.answers,
|
|
41381
|
-
editable: saved2.editable
|
|
41382
|
-
})
|
|
41383
|
-
);
|
|
42203
|
+
console.log(colorSuccess("Submitted questionnaire. Answers are locked."));
|
|
42204
|
+
console.log(formatQuestionnaireShow(questionnaireShowInput(submitted)));
|
|
41384
42205
|
return;
|
|
41385
42206
|
}
|
|
41386
42207
|
const current = await fetchQuestionnaire(organizationId, pentestId, pentestIdentifier);
|
|
41387
42208
|
if (!current.editable) {
|
|
41388
42209
|
console.error(
|
|
41389
42210
|
colorError(
|
|
41390
|
-
"Questionnaire editing is closed after scoping starts. Use --show to view the saved answers."
|
|
42211
|
+
current.submittedAt != null ? "Questionnaire has already been submitted. Use --show to view the saved answers." : "Questionnaire editing is closed after scoping starts. Use --show to view the saved answers."
|
|
41391
42212
|
)
|
|
41392
42213
|
);
|
|
41393
42214
|
process.exit(1);
|
|
@@ -41395,35 +42216,33 @@ async function runQuestionnaire(pentestIdentifier, options) {
|
|
|
41395
42216
|
const merged = await promptQuestionnaireAnswers(current.answers);
|
|
41396
42217
|
const saved = await putQuestionnaire(organizationId, pentestId, merged, pentestIdentifier);
|
|
41397
42218
|
console.log(colorSuccess("Saved questionnaire answers."));
|
|
41398
|
-
console.log(
|
|
41399
|
-
formatQuestionnaireShow({
|
|
41400
|
-
answers: saved.answers,
|
|
41401
|
-
editable: saved.editable
|
|
41402
|
-
})
|
|
41403
|
-
);
|
|
42219
|
+
console.log(formatQuestionnaireShow(questionnaireShowInput(saved)));
|
|
41404
42220
|
}
|
|
41405
42221
|
function createQuestionnaireCommand() {
|
|
41406
42222
|
return new Command("questionnaire").description("View or fill the customer pentest intake questionnaire").argument("<pentestId>", "Pentest display ID (e.g. PT-3) or UUID").option("--show", "Print current answers using shared labels (GET)").option(
|
|
41407
42223
|
"--file <path>",
|
|
41408
42224
|
"Upload answers from a JSON file (answers object only \u2014 not { answers }; whole-object replace)"
|
|
41409
|
-
).addHelpText(
|
|
42225
|
+
).option("--submit", "Freeze the questionnaire after saving (or freeze the current answers)").addHelpText(
|
|
41410
42226
|
"after",
|
|
41411
42227
|
`
|
|
41412
42228
|
Optional scoping questionnaire for a new pentest (editable while stage is DRAFT
|
|
41413
|
-
or READY_TO_START). Reads accept a browser session or API key;
|
|
41414
|
-
organization admin (or Trace staff) signed in via \`tracecli
|
|
41415
|
-
keys are rejected.
|
|
42229
|
+
or READY_TO_START, until submitted). Reads accept a browser session or API key;
|
|
42230
|
+
writes require an organization admin (or Trace staff) signed in via \`tracecli
|
|
42231
|
+
auth login\` \u2014 API keys are rejected.
|
|
41416
42232
|
|
|
41417
42233
|
Modes:
|
|
41418
42234
|
--show GET and print key: value with shared customer-facing labels
|
|
41419
42235
|
--file <path> PUT the file's answers object (CLI wraps { answers } for the API)
|
|
42236
|
+
--submit Freeze answers (combine with --file or TTY fill, or alone to freeze current)
|
|
41420
42237
|
(TTY default) load-merge-write: GET, prompt each question, confirm, PUT
|
|
41421
42238
|
|
|
41422
|
-
Non-interactive shells must pass --show or --
|
|
42239
|
+
Non-interactive shells must pass --show, --file, or --submit (exit 2 otherwise).
|
|
41423
42240
|
|
|
41424
42241
|
Examples:
|
|
41425
42242
|
tracecli pentest questionnaire PT-1 --show
|
|
41426
42243
|
tracecli pentest questionnaire PT-1 --file answers.json
|
|
42244
|
+
tracecli pentest questionnaire PT-1 --file answers.json --submit
|
|
42245
|
+
tracecli pentest questionnaire PT-1 --submit
|
|
41427
42246
|
tracecli pentest questionnaire PT-1`
|
|
41428
42247
|
).action(async (pentestId, options) => {
|
|
41429
42248
|
await runQuestionnaire(pentestId, options);
|
|
@@ -41431,7 +42250,7 @@ Examples:
|
|
|
41431
42250
|
}
|
|
41432
42251
|
function createPentestCommand() {
|
|
41433
42252
|
const pentest = new Command("pentest").description("Manage pentests");
|
|
41434
|
-
pentest.addCommand(
|
|
42253
|
+
pentest.addCommand(createListCommand6());
|
|
41435
42254
|
pentest.addCommand(createUpdateCommand4());
|
|
41436
42255
|
pentest.addCommand(createQuestionnaireCommand());
|
|
41437
42256
|
pentest.addCommand(createFindingsCommand());
|
|
@@ -41439,7 +42258,7 @@ function createPentestCommand() {
|
|
|
41439
42258
|
}
|
|
41440
42259
|
|
|
41441
42260
|
// src/update/commands.ts
|
|
41442
|
-
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as
|
|
42261
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
|
|
41443
42262
|
import { dirname as dirname2, join as join5 } from "path";
|
|
41444
42263
|
import { createInterface as createInterface4 } from "readline/promises";
|
|
41445
42264
|
var UPDATE_CACHE_FILE = "update-check.json";
|
|
@@ -41457,7 +42276,7 @@ function readUpdateCheckCache() {
|
|
|
41457
42276
|
return null;
|
|
41458
42277
|
}
|
|
41459
42278
|
try {
|
|
41460
|
-
const rawCache =
|
|
42279
|
+
const rawCache = readFileSync6(cachePath, "utf-8");
|
|
41461
42280
|
const parsed = JSON.parse(rawCache);
|
|
41462
42281
|
if (typeof parsed !== "object" || parsed === null) {
|
|
41463
42282
|
return null;
|
|
@@ -42056,7 +42875,7 @@ Run \`tracecli vulnerabilities list\` to see available vulnerabilities.`
|
|
|
42056
42875
|
printVulnerabilityDetail(result.body);
|
|
42057
42876
|
});
|
|
42058
42877
|
}
|
|
42059
|
-
function
|
|
42878
|
+
function createListCommand7() {
|
|
42060
42879
|
return new Command("list").description("List vulnerabilities for your organization").option("--repo <repo>", "Filter by repository name or owner/repo").option(
|
|
42061
42880
|
"--severity <severity>",
|
|
42062
42881
|
"Filter by severity (CRITICAL, HIGH, MEDIUM, LOW)",
|
|
@@ -42190,7 +43009,7 @@ Examples:
|
|
|
42190
43009
|
function createVulnerabilitiesCommand() {
|
|
42191
43010
|
const vulnerabilities = new Command("vulnerabilities").description("Manage vulnerabilities");
|
|
42192
43011
|
vulnerabilities.addCommand(createGetCommand4());
|
|
42193
|
-
vulnerabilities.addCommand(
|
|
43012
|
+
vulnerabilities.addCommand(createListCommand7());
|
|
42194
43013
|
return vulnerabilities;
|
|
42195
43014
|
}
|
|
42196
43015
|
|
|
@@ -42211,6 +43030,7 @@ program2.addCommand(createEnvironmentCommand());
|
|
|
42211
43030
|
program2.addCommand(createCredentialCommand());
|
|
42212
43031
|
program2.addCommand(createRepoCommand());
|
|
42213
43032
|
program2.addCommand(createVulnerabilitiesCommand());
|
|
43033
|
+
program2.addCommand(createCodeSizeCommand());
|
|
42214
43034
|
program2.addCommand(createPentestCommand());
|
|
42215
43035
|
program2.addCommand(createCompletionCommand(program2));
|
|
42216
43036
|
program2.addCommand(createUpgradeCommand());
|