securewithtrace 0.49.0 → 0.50.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 +1567 -241
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6283,7 +6283,7 @@ var $ZodBase64 = /* @__PURE__ */ $constructor("$ZodBase64", (inst, def) => {
|
|
|
6283
6283
|
function isValidBase64URL(data) {
|
|
6284
6284
|
if (!base64url.test(data))
|
|
6285
6285
|
return false;
|
|
6286
|
-
const base644 = data.replace(/[-_]/g, (
|
|
6286
|
+
const base644 = data.replace(/[-_]/g, (c84) => c84 === "-" ? "+" : "/");
|
|
6287
6287
|
const padded = base644.padEnd(Math.ceil(base644.length / 4) * 4, "=");
|
|
6288
6288
|
return isValidBase64(padded);
|
|
6289
6289
|
}
|
|
@@ -16327,9 +16327,9 @@ var ZodDate = /* @__PURE__ */ $constructor("ZodDate", (inst, def) => {
|
|
|
16327
16327
|
inst._zod.processJSONSchema = (ctx, json2, params3) => dateProcessor(inst, ctx, json2, params3);
|
|
16328
16328
|
inst.min = (value, params3) => inst.check(_gte(value, params3));
|
|
16329
16329
|
inst.max = (value, params3) => inst.check(_lte(value, params3));
|
|
16330
|
-
const
|
|
16331
|
-
inst.minDate =
|
|
16332
|
-
inst.maxDate =
|
|
16330
|
+
const c84 = inst._zod.bag;
|
|
16331
|
+
inst.minDate = c84.minimum ? new Date(c84.minimum) : null;
|
|
16332
|
+
inst.maxDate = c84.maximum ? new Date(c84.maximum) : null;
|
|
16333
16333
|
});
|
|
16334
16334
|
function date3(params3) {
|
|
16335
16335
|
return _date(ZodDate, params3);
|
|
@@ -18329,6 +18329,8 @@ var STAFF_NOTIFICATION_TOOLSET_VERSIONS = /* @__PURE__ */ new Set([
|
|
|
18329
18329
|
// API_COVERAGE_TOOLSET_VERSION and its predecessor, written out because they are declared below.
|
|
18330
18330
|
"api-coverage-tools-v1",
|
|
18331
18331
|
"api-coverage-tools-v2",
|
|
18332
|
+
// SETUP_TOOLSET_VERSION, written out for the same reason.
|
|
18333
|
+
"setup-tools-v1",
|
|
18332
18334
|
...STAFF_NOTIFICATION_OOB_PROFILE_SUCCESSIONS.flatMap(({ successor, predecessor }) => [
|
|
18333
18335
|
predecessor.toolsetVersion,
|
|
18334
18336
|
successor.toolsetVersion
|
|
@@ -18654,6 +18656,21 @@ var PENTEST_CUSTOMER_INTAKE_QUESTIONS = [
|
|
|
18654
18656
|
}
|
|
18655
18657
|
];
|
|
18656
18658
|
var PENTEST_CUSTOMER_INTAKE_QUESTION_COUNT = PENTEST_CUSTOMER_INTAKE_QUESTIONS.length;
|
|
18659
|
+
function isIntakeAnswerPresent(value) {
|
|
18660
|
+
if (value === void 0 || value === null) return false;
|
|
18661
|
+
if (typeof value === "string") return value.trim().length > 0;
|
|
18662
|
+
if (Array.isArray(value)) return value.length > 0;
|
|
18663
|
+
return true;
|
|
18664
|
+
}
|
|
18665
|
+
function countAnsweredIntakeQuestions(answers) {
|
|
18666
|
+
if (!answers) return 0;
|
|
18667
|
+
let answered = 0;
|
|
18668
|
+
for (const question of PENTEST_CUSTOMER_INTAKE_QUESTIONS) {
|
|
18669
|
+
const keys = question.kind === "multiSelect" ? [question.key, question.notesKey] : [question.key];
|
|
18670
|
+
if (keys.some((key) => isIntakeAnswerPresent(answers[key]))) answered += 1;
|
|
18671
|
+
}
|
|
18672
|
+
return answered;
|
|
18673
|
+
}
|
|
18657
18674
|
var INTAKE_ISSUE_CLASS_LABELS = {
|
|
18658
18675
|
AUTHN_BYPASS: "Authn bypass",
|
|
18659
18676
|
AUTHZ_IDOR: "Broken access control",
|
|
@@ -21924,23 +21941,23 @@ var ZodEffects = class extends ZodType2 {
|
|
|
21924
21941
|
}
|
|
21925
21942
|
if (effect.type === "transform") {
|
|
21926
21943
|
if (ctx.common.async === false) {
|
|
21927
|
-
const
|
|
21944
|
+
const base3 = this._def.schema._parseSync({
|
|
21928
21945
|
data: ctx.data,
|
|
21929
21946
|
path: ctx.path,
|
|
21930
21947
|
parent: ctx
|
|
21931
21948
|
});
|
|
21932
|
-
if (!isValid(
|
|
21933
|
-
return
|
|
21934
|
-
const result = effect.transform(
|
|
21949
|
+
if (!isValid(base3))
|
|
21950
|
+
return base3;
|
|
21951
|
+
const result = effect.transform(base3.value, checkCtx);
|
|
21935
21952
|
if (result instanceof Promise) {
|
|
21936
21953
|
throw new Error(`Asynchronous transform encountered during synchronous parse operation. Use .parseAsync instead.`);
|
|
21937
21954
|
}
|
|
21938
21955
|
return { status: status.value, value: result };
|
|
21939
21956
|
} else {
|
|
21940
|
-
return this._def.schema._parseAsync({ data: ctx.data, path: ctx.path, parent: ctx }).then((
|
|
21941
|
-
if (!isValid(
|
|
21942
|
-
return
|
|
21943
|
-
return Promise.resolve(effect.transform(
|
|
21957
|
+
return this._def.schema._parseAsync({ data: ctx.data, path: ctx.path, parent: ctx }).then((base3) => {
|
|
21958
|
+
if (!isValid(base3))
|
|
21959
|
+
return base3;
|
|
21960
|
+
return Promise.resolve(effect.transform(base3.value, checkCtx)).then((result) => ({ status: status.value, value: result }));
|
|
21944
21961
|
});
|
|
21945
21962
|
}
|
|
21946
21963
|
}
|
|
@@ -22598,8 +22615,8 @@ var initContract = () => {
|
|
|
22598
22615
|
};
|
|
22599
22616
|
};
|
|
22600
22617
|
var insertParamsIntoPath = ({ path: path3, params: params3 }) => {
|
|
22601
|
-
const
|
|
22602
|
-
return path3.replace(/\/?:([^/?]+)\??/g, (matched, p) =>
|
|
22618
|
+
const pathParams3 = params3;
|
|
22619
|
+
return path3.replace(/\/?:([^/?]+)\??/g, (matched, p) => pathParams3[p] ? `${matched.startsWith("/") ? "/" : ""}${pathParams3[p]}` : "");
|
|
22603
22620
|
};
|
|
22604
22621
|
var convertQueryParamsToUrlString = (query, json2 = false) => {
|
|
22605
22622
|
const queryString = json2 ? encodeQueryParamsJson(query) : encodeQueryParams(query);
|
|
@@ -23172,9 +23189,16 @@ var AgentSessionTypeValues = [
|
|
|
23172
23189
|
"ASSESSMENT",
|
|
23173
23190
|
"COORDINATOR",
|
|
23174
23191
|
"REMEDIATION",
|
|
23175
|
-
"API_COVERAGE"
|
|
23192
|
+
"API_COVERAGE",
|
|
23193
|
+
"SETUP"
|
|
23176
23194
|
];
|
|
23177
23195
|
var AgentSessionType = external_exports.enum(AgentSessionTypeValues);
|
|
23196
|
+
var SetupTenantSchema = external_exports.object({
|
|
23197
|
+
admin: external_exports.boolean(),
|
|
23198
|
+
member: external_exports.boolean(),
|
|
23199
|
+
apiKey: external_exports.boolean()
|
|
23200
|
+
}).strict();
|
|
23201
|
+
var MAX_SETUP_TENANTS = 4;
|
|
23178
23202
|
var AgentSessionStatusValues = [
|
|
23179
23203
|
"LAUNCHING",
|
|
23180
23204
|
"RUNNING",
|
|
@@ -23337,6 +23361,17 @@ var CreateAgentSessionRequestSchema = external_exports.discriminatedUnion("type"
|
|
|
23337
23361
|
CreateAgentSessionCommonSchema.extend({
|
|
23338
23362
|
type: external_exports.literal("API_COVERAGE"),
|
|
23339
23363
|
focus: external_exports.string().min(1).max(MAX_AGENT_SESSION_FOCUS_CHARS).optional()
|
|
23364
|
+
}),
|
|
23365
|
+
// SETUP: sets up a PoC engagement's application, environment, exclusions and tenant
|
|
23366
|
+
// accounts. `focus` carries the staff notes (which environment, rate limits, what is
|
|
23367
|
+
// off-limits); `tenants` the accounts to aim for; `shareLink` the customer's logins.
|
|
23368
|
+
CreateAgentSessionCommonSchema.extend({
|
|
23369
|
+
type: external_exports.literal("SETUP"),
|
|
23370
|
+
focus: external_exports.string().min(1).max(MAX_AGENT_SESSION_FOCUS_CHARS).optional(),
|
|
23371
|
+
/** Absent = the default target, two tenants of admin + member + API key. */
|
|
23372
|
+
tenants: external_exports.array(SetupTenantSchema).min(1).max(MAX_SETUP_TENANTS).optional(),
|
|
23373
|
+
/** A credential share link. Stored encrypted and never returned by any read route. */
|
|
23374
|
+
shareLink: external_exports.url({ protocol: /^https$/ }).max(2048).optional()
|
|
23340
23375
|
})
|
|
23341
23376
|
]);
|
|
23342
23377
|
var SendAgentSessionMessageRequestSchema = external_exports.object({
|
|
@@ -27462,7 +27497,17 @@ var CustomerPentestFindingRepositorySchema = PentestFindingRepositorySchema.omit
|
|
|
27462
27497
|
repositoryId: true
|
|
27463
27498
|
});
|
|
27464
27499
|
var CustomerFindingPendingRetestSchema = external_exports.discriminatedUnion("state", [
|
|
27465
|
-
external_exports.object({
|
|
27500
|
+
external_exports.object({
|
|
27501
|
+
state: external_exports.literal("QUEUED"),
|
|
27502
|
+
since: external_exports.coerce.date(),
|
|
27503
|
+
/**
|
|
27504
|
+
* Present only while the retest waits on a completed ticket's delay: when
|
|
27505
|
+
* it becomes due. A field on QUEUED rather than its own state because prod
|
|
27506
|
+
* promotes web after the API and web parses these responses, so a new
|
|
27507
|
+
* variant would fail the parse in the web still serving.
|
|
27508
|
+
*/
|
|
27509
|
+
startsAt: external_exports.coerce.date().optional()
|
|
27510
|
+
}),
|
|
27466
27511
|
external_exports.object({ state: external_exports.literal("RUNNING"), since: external_exports.coerce.date() }),
|
|
27467
27512
|
/**
|
|
27468
27513
|
* A mobile-only engagement whose finding has no usable build newer than its
|
|
@@ -28816,13 +28861,13 @@ var StorageStateSchema = external_exports.object({
|
|
|
28816
28861
|
} catch {
|
|
28817
28862
|
return;
|
|
28818
28863
|
}
|
|
28819
|
-
s.cookies.forEach((
|
|
28820
|
-
const d =
|
|
28864
|
+
s.cookies.forEach((c84, i2) => {
|
|
28865
|
+
const d = c84.domain.replace(/^\./, "").toLowerCase();
|
|
28821
28866
|
if (d !== host && !host.endsWith(`.${d}`)) {
|
|
28822
28867
|
ctx.addIssue({
|
|
28823
28868
|
code: "custom",
|
|
28824
28869
|
path: ["cookies", i2, "domain"],
|
|
28825
|
-
message: `cookie domain "${
|
|
28870
|
+
message: `cookie domain "${c84.domain}" is not the capture origin host "${host}" or a parent domain of it`
|
|
28826
28871
|
});
|
|
28827
28872
|
}
|
|
28828
28873
|
});
|
|
@@ -30156,7 +30201,27 @@ var AgentSessionCreateTestEmailResponseSchema = external_exports.object({
|
|
|
30156
30201
|
*/
|
|
30157
30202
|
reused: external_exports.boolean()
|
|
30158
30203
|
});
|
|
30159
|
-
var
|
|
30204
|
+
var ProvidedCredentialType = external_exports.enum(CredentialTypeValues).extract(["USERNAME_PASSWORD", "API_KEY", "BEARER_TOKEN"]);
|
|
30205
|
+
var AgentSessionSaveProvidedAccountRequestSchema = external_exports.object({
|
|
30206
|
+
runVersion: external_exports.number().int().nonnegative(),
|
|
30207
|
+
provided: external_exports.literal(true),
|
|
30208
|
+
/**
|
|
30209
|
+
* `customer`: the customer handed it over (a share link). `created`: the agent made it, such as an
|
|
30210
|
+
* API key from an admin account it signed up; it is labelled like any account the agent mints, so
|
|
30211
|
+
* a black-box engagement does not read it as customer-supplied.
|
|
30212
|
+
*/
|
|
30213
|
+
source: external_exports.enum(["customer", "created"]),
|
|
30214
|
+
label: external_exports.string().min(1).max(200),
|
|
30215
|
+
credentialType: ProvidedCredentialType,
|
|
30216
|
+
/** USERNAME_PASSWORD only. */
|
|
30217
|
+
username: external_exports.string().min(1).max(320).optional(),
|
|
30218
|
+
/** The password, key or token. */
|
|
30219
|
+
secret: external_exports.string().min(1).max(4096),
|
|
30220
|
+
/** API_KEY only: the header that carries the key. */
|
|
30221
|
+
headerName: external_exports.string().min(1).max(100).optional(),
|
|
30222
|
+
targetUrl: external_exports.string().min(1).max(2048).optional()
|
|
30223
|
+
});
|
|
30224
|
+
var AgentSessionSaveMintedAccountRequestSchema = external_exports.strictObject({
|
|
30160
30225
|
runVersion: external_exports.number().int().nonnegative(),
|
|
30161
30226
|
/** The `label` returned by `create_test_email`. */
|
|
30162
30227
|
label: external_exports.string().min(1).max(200),
|
|
@@ -30184,11 +30249,18 @@ var AgentSessionSaveTestAccountRequestSchema = external_exports.object({
|
|
|
30184
30249
|
*/
|
|
30185
30250
|
targetUrl: external_exports.string().min(1).max(2048).optional()
|
|
30186
30251
|
});
|
|
30252
|
+
var AgentSessionSaveTestAccountRequestSchema = external_exports.union([
|
|
30253
|
+
AgentSessionSaveProvidedAccountRequestSchema,
|
|
30254
|
+
AgentSessionSaveMintedAccountRequestSchema
|
|
30255
|
+
]);
|
|
30187
30256
|
var AgentSessionSaveTestAccountResponseSchema = external_exports.object({
|
|
30188
30257
|
/** Echoed so the agent names the account the same way afterwards. */
|
|
30189
30258
|
label: external_exports.string().max(200),
|
|
30190
|
-
/**
|
|
30191
|
-
|
|
30259
|
+
/**
|
|
30260
|
+
* Minted accounts: `USERNAME_PASSWORD` when a password was supplied, else `MAGIC_AUTH`.
|
|
30261
|
+
* Provided logins: the type the agent named.
|
|
30262
|
+
*/
|
|
30263
|
+
credentialType: external_exports.enum(["USERNAME_PASSWORD", "MAGIC_AUTH", "API_KEY", "BEARER_TOKEN"]),
|
|
30192
30264
|
/**
|
|
30193
30265
|
* The environment the credential landed on. Authoritative, not an echo: it is
|
|
30194
30266
|
* how the agent learns that its `targetUrl` matched nothing and the server
|
|
@@ -30579,7 +30651,7 @@ var ParkAgentSessionRequestSchema = external_exports.object({
|
|
|
30579
30651
|
* idle-lingered out (the host's only natural-completion signal); INTERRUPTED =
|
|
30580
30652
|
* SIGTERM / stream-close / api-outage give-up / wall-clock. Meaningful only when
|
|
30581
30653
|
* outcome === 'IDLE'. OPTIONAL for rolling-deploy tolerance: an old host omits
|
|
30582
|
-
* it, and the api treats absent as non-natural (
|
|
30654
|
+
* it, and the api treats absent as non-natural (the validation run stays RUNNING).
|
|
30583
30655
|
*/
|
|
30584
30656
|
idleReason: external_exports.enum(["COMPLETED", "INTERRUPTED"]).optional(),
|
|
30585
30657
|
waitingReason: AgentSessionWaitingReason.optional(),
|
|
@@ -31460,7 +31532,7 @@ var agentSessionInternalContract = c4.router({
|
|
|
31460
31532
|
201: RecordValidationResultResponseSchema,
|
|
31461
31533
|
400: ErrorResponseSchema,
|
|
31462
31534
|
401: ErrorResponseSchema,
|
|
31463
|
-
// runVersion/status guard, or
|
|
31535
|
+
// runVersion/status guard, an ended engagement, or no validation run for the session.
|
|
31464
31536
|
409: ErrorResponseSchema,
|
|
31465
31537
|
500: ErrorResponseSchema
|
|
31466
31538
|
}
|
|
@@ -36719,6 +36791,7 @@ var LinearIntegrationStatusValues = [
|
|
|
36719
36791
|
"DISCONNECTED"
|
|
36720
36792
|
];
|
|
36721
36793
|
var linearEntityId2 = external_exports.string().min(1).max(64);
|
|
36794
|
+
var LINEAR_RETEST_DELAY_MINUTES_OPTIONS = [0, 60, 180, 720, 1440, 4320];
|
|
36722
36795
|
var LinearIntegrationSummarySchema = external_exports.object({
|
|
36723
36796
|
linearIntegrationId: external_exports.uuid(),
|
|
36724
36797
|
status: external_exports.enum(LinearIntegrationStatusValues),
|
|
@@ -36728,6 +36801,12 @@ var LinearIntegrationSummarySchema = external_exports.object({
|
|
|
36728
36801
|
defaultTeamId: external_exports.string().nullable(),
|
|
36729
36802
|
defaultProjectId: external_exports.string().nullable(),
|
|
36730
36803
|
defaultLabelIds: external_exports.array(external_exports.string()),
|
|
36804
|
+
/**
|
|
36805
|
+
* Optional on the wire only: prod promotes web after the API, so a web bundle
|
|
36806
|
+
* can read an API that predates the field. Consumers default it. Not limited
|
|
36807
|
+
* to the options so retiring one never makes a stored value unparseable.
|
|
36808
|
+
*/
|
|
36809
|
+
retestDelayMinutes: external_exports.number().int().nonnegative().optional(),
|
|
36731
36810
|
connectedAt: external_exports.iso.datetime(),
|
|
36732
36811
|
connectedByName: external_exports.string().nullable(),
|
|
36733
36812
|
/** Non-null → disconnect committed but Linear-side revocation didn't confirm; UI shows the confirm-removal banner. */
|
|
@@ -36759,6 +36838,9 @@ var UpdateLinearDefaultsRequestSchema = external_exports.object({
|
|
|
36759
36838
|
defaultProjectId: linearEntityId2.nullable().optional(),
|
|
36760
36839
|
defaultLabelIds: external_exports.array(linearEntityId2).max(10).optional()
|
|
36761
36840
|
});
|
|
36841
|
+
var UpdateLinearRetestDelayRequestSchema = external_exports.object({
|
|
36842
|
+
retestDelayMinutes: external_exports.literal(LINEAR_RETEST_DELAY_MINUTES_OPTIONS)
|
|
36843
|
+
});
|
|
36762
36844
|
var LinearTeamSchema = external_exports.object({
|
|
36763
36845
|
id: external_exports.string(),
|
|
36764
36846
|
name: external_exports.string(),
|
|
@@ -36845,6 +36927,27 @@ var linearIntegrationContract = c26.router({
|
|
|
36845
36927
|
503: ErrorResponseSchema
|
|
36846
36928
|
}
|
|
36847
36929
|
},
|
|
36930
|
+
/**
|
|
36931
|
+
* Separate from the defaults PATCH, which revalidates the saved team against
|
|
36932
|
+
* Linear on every save: this setting is ours alone and must save while Linear
|
|
36933
|
+
* is unreachable or the team was archived.
|
|
36934
|
+
*/
|
|
36935
|
+
updateLinearRetestDelay: {
|
|
36936
|
+
method: "PATCH",
|
|
36937
|
+
path: "/organizations/:organizationId/integrations/linear/retest-delay",
|
|
36938
|
+
pathParams: OrgIdParamsSchema,
|
|
36939
|
+
body: UpdateLinearRetestDelayRequestSchema,
|
|
36940
|
+
responses: {
|
|
36941
|
+
200: LinearIntegrationResponseSchema,
|
|
36942
|
+
400: ErrorResponseSchema,
|
|
36943
|
+
401: ErrorResponseSchema,
|
|
36944
|
+
403: ErrorResponseSchema,
|
|
36945
|
+
404: ErrorResponseSchema,
|
|
36946
|
+
409: ErrorResponseSchema,
|
|
36947
|
+
500: ErrorResponseSchema,
|
|
36948
|
+
503: ErrorResponseSchema
|
|
36949
|
+
}
|
|
36950
|
+
},
|
|
36848
36951
|
getLinearMetadata: {
|
|
36849
36952
|
method: "GET",
|
|
36850
36953
|
path: "/organizations/:organizationId/integrations/linear/metadata",
|
|
@@ -41541,6 +41644,23 @@ var pentestFindingContract = c45.router({
|
|
|
41541
41644
|
500: ErrorResponseSchema
|
|
41542
41645
|
}
|
|
41543
41646
|
},
|
|
41647
|
+
// Customer says the fix is already live for a finding whose retest waits on its
|
|
41648
|
+
// ticket's delay: the retest becomes due now. No status change.
|
|
41649
|
+
startScheduledRetest: {
|
|
41650
|
+
method: "POST",
|
|
41651
|
+
path: "/organizations/:organizationId/customer-pentests/:pentestId/findings/:pentestFindingId/start-retest",
|
|
41652
|
+
pathParams: CustomerPentestFindingIdParamsSchema,
|
|
41653
|
+
body: c45.noBody(),
|
|
41654
|
+
responses: {
|
|
41655
|
+
200: CustomerPentestFindingResponseSchema,
|
|
41656
|
+
401: ErrorResponseSchema,
|
|
41657
|
+
403: ErrorResponseSchema,
|
|
41658
|
+
404: ErrorResponseSchema,
|
|
41659
|
+
// RETEST_NOT_SCHEDULED: not REMEDIATED with a retest still waiting.
|
|
41660
|
+
409: ErrorResponseSchema,
|
|
41661
|
+
500: ErrorResponseSchema
|
|
41662
|
+
}
|
|
41663
|
+
},
|
|
41544
41664
|
// Customer accepts the finding → ACCEPTED_WITH_CONTROLS without a dispute or a
|
|
41545
41665
|
// staff decision. Human principals here; an API key sends the same transition
|
|
41546
41666
|
// through `automationStatusUpdate` with status ACCEPTED_WITH_CONTROLS.
|
|
@@ -41935,6 +42055,57 @@ var PentestIntakeDocumentTypeValues = [
|
|
|
41935
42055
|
"OTHER"
|
|
41936
42056
|
];
|
|
41937
42057
|
var PentestIntakeDocumentTypeSchema = external_exports.enum(PentestIntakeDocumentTypeValues);
|
|
42058
|
+
var PENTEST_INTAKE_DOCUMENTS_QUESTION = "Do you have any relevant documents to share?";
|
|
42059
|
+
var PENTEST_INTAKE_DOCUMENT_TYPE_LABELS = {
|
|
42060
|
+
PREVIOUS_PENTEST: "Previous pentest report",
|
|
42061
|
+
ARCHITECTURE_DIAGRAM: "Architecture diagram",
|
|
42062
|
+
THREAT_MODEL: "Threat model",
|
|
42063
|
+
OTHER: "Other"
|
|
42064
|
+
};
|
|
42065
|
+
function guessPentestIntakeDocumentType(fileName) {
|
|
42066
|
+
const name = fileName.toLowerCase();
|
|
42067
|
+
if (/threat[\s_-]*model|stride|attack[\s_-]*tree|dfd/.test(name)) return "THREAT_MODEL";
|
|
42068
|
+
if (/pen[\s_-]*test|pentest|penetration|security[\s_-]*assessment|vapt|findings/.test(name)) {
|
|
42069
|
+
return "PREVIOUS_PENTEST";
|
|
42070
|
+
}
|
|
42071
|
+
if (/arch|diagram|topology|infra|system[\s_-]*design|c4|\.drawio/.test(name)) {
|
|
42072
|
+
return "ARCHITECTURE_DIAGRAM";
|
|
42073
|
+
}
|
|
42074
|
+
return "OTHER";
|
|
42075
|
+
}
|
|
42076
|
+
var INTAKE_DOCUMENT_MIME_BY_EXTENSION = {
|
|
42077
|
+
pdf: "application/pdf",
|
|
42078
|
+
doc: "application/msword",
|
|
42079
|
+
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
42080
|
+
xls: "application/vnd.ms-excel",
|
|
42081
|
+
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
42082
|
+
ppt: "application/vnd.ms-powerpoint",
|
|
42083
|
+
pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
42084
|
+
txt: "text/plain",
|
|
42085
|
+
md: "text/markdown",
|
|
42086
|
+
csv: "text/csv",
|
|
42087
|
+
json: "application/json",
|
|
42088
|
+
xml: "application/xml",
|
|
42089
|
+
// text/yaml rather than application/yaml: readers that only accept text/* still take it.
|
|
42090
|
+
yaml: "text/yaml",
|
|
42091
|
+
yml: "text/yaml",
|
|
42092
|
+
html: "text/html",
|
|
42093
|
+
htm: "text/html",
|
|
42094
|
+
png: "image/png",
|
|
42095
|
+
jpg: "image/jpeg",
|
|
42096
|
+
jpeg: "image/jpeg",
|
|
42097
|
+
gif: "image/gif",
|
|
42098
|
+
svg: "image/svg+xml",
|
|
42099
|
+
webp: "image/webp",
|
|
42100
|
+
zip: "application/zip",
|
|
42101
|
+
drawio: "application/xml",
|
|
42102
|
+
vsdx: "application/vnd.ms-visio.drawing"
|
|
42103
|
+
};
|
|
42104
|
+
function pentestIntakeDocumentMimeType(fileName) {
|
|
42105
|
+
const dot = fileName.lastIndexOf(".");
|
|
42106
|
+
const extension = dot === -1 ? "" : fileName.slice(dot + 1).toLowerCase();
|
|
42107
|
+
return INTAKE_DOCUMENT_MIME_BY_EXTENSION[extension] ?? "application/octet-stream";
|
|
42108
|
+
}
|
|
41938
42109
|
var MAX_PENTEST_INTAKE_DOCUMENT_BYTES = 50 * 1024 * 1024;
|
|
41939
42110
|
var PentestIntakeDocumentIdParamsSchema = CustomerPentestIdParamsSchema.extend({
|
|
41940
42111
|
documentId: external_exports.uuid()
|
|
@@ -46227,16 +46398,16 @@ var COVERAGE_CLASSES = [
|
|
|
46227
46398
|
},
|
|
46228
46399
|
{ taxonomy: "RECON", id: "RECON-15", name: "Deployment-platform config", shortLabel: "Deploy" }
|
|
46229
46400
|
];
|
|
46230
|
-
var BY_ID = new Map(COVERAGE_CLASSES.map((
|
|
46401
|
+
var BY_ID = new Map(COVERAGE_CLASSES.map((c84) => [c84.id, c84]));
|
|
46231
46402
|
function idsWhere(keep) {
|
|
46232
|
-
return [OTHER_CLASS_ID, ...COVERAGE_CLASSES.filter(keep).map((
|
|
46403
|
+
return [OTHER_CLASS_ID, ...COVERAGE_CLASSES.filter(keep).map((c84) => c84.id)];
|
|
46233
46404
|
}
|
|
46234
|
-
var HUNTING_CLASS_IDS = idsWhere((
|
|
46235
|
-
var RECON_CLASS_IDS = idsWhere((
|
|
46405
|
+
var HUNTING_CLASS_IDS = idsWhere((c84) => c84.taxonomy !== "RECON");
|
|
46406
|
+
var RECON_CLASS_IDS = idsWhere((c84) => c84.taxonomy === "RECON");
|
|
46236
46407
|
var HUNTING_ID_SET = new Set(HUNTING_CLASS_IDS);
|
|
46237
46408
|
var RECON_ID_SET = new Set(RECON_CLASS_IDS);
|
|
46238
46409
|
var TAXONOMY_ORDER = new Map(CoverageTaxonomyValues.map((t2, i2) => [t2, i2]));
|
|
46239
|
-
var CLASS_ORDER = new Map(COVERAGE_CLASSES.map((
|
|
46410
|
+
var CLASS_ORDER = new Map(COVERAGE_CLASSES.map((c84, i2) => [c84.id, i2]));
|
|
46240
46411
|
|
|
46241
46412
|
// ../../packages/api-contracts/src/contracts/v1/api-coverage-internal.ts
|
|
46242
46413
|
var c80 = initContract();
|
|
@@ -46309,6 +46480,135 @@ var apiCoverageInternalContract = c80.router({
|
|
|
46309
46480
|
}
|
|
46310
46481
|
});
|
|
46311
46482
|
|
|
46483
|
+
// ../../packages/api-contracts/src/schemas/api/v1/setup-session-internal.api.ts
|
|
46484
|
+
var RunVersionSchema2 = external_exports.number().int().nonnegative();
|
|
46485
|
+
var SetupTargetType = external_exports.enum(ApplicationTypeValues).extract(["WEB", "API_SERVICE"]);
|
|
46486
|
+
var ReasonSchema = external_exports.string().min(1).max(500);
|
|
46487
|
+
var SetupVerificationStatusValues = [
|
|
46488
|
+
"UNVERIFIED",
|
|
46489
|
+
"PENDING",
|
|
46490
|
+
"VERIFIED",
|
|
46491
|
+
"FAILED",
|
|
46492
|
+
"INCONCLUSIVE"
|
|
46493
|
+
];
|
|
46494
|
+
var SetupVerificationStatus = external_exports.enum(SetupVerificationStatusValues);
|
|
46495
|
+
var SetupAddTargetRequestSchema = external_exports.object({
|
|
46496
|
+
runVersion: RunVersionSchema2,
|
|
46497
|
+
reason: ReasonSchema,
|
|
46498
|
+
/** The application's name in Trace. An existing org application with this name is reused. */
|
|
46499
|
+
name: external_exports.string().min(1).max(200),
|
|
46500
|
+
type: SetupTargetType,
|
|
46501
|
+
/** The environment's login or base URL. */
|
|
46502
|
+
url: external_exports.url({ protocol: /^https?$/ }).max(2048),
|
|
46503
|
+
environmentName: external_exports.string().min(1).max(100),
|
|
46504
|
+
isProduction: external_exports.boolean()
|
|
46505
|
+
});
|
|
46506
|
+
var SetupAddTargetResponseSchema = external_exports.object({
|
|
46507
|
+
applicationName: external_exports.string(),
|
|
46508
|
+
environmentName: external_exports.string(),
|
|
46509
|
+
created: external_exports.object({
|
|
46510
|
+
application: external_exports.boolean(),
|
|
46511
|
+
environment: external_exports.boolean(),
|
|
46512
|
+
binding: external_exports.boolean(),
|
|
46513
|
+
selection: external_exports.boolean()
|
|
46514
|
+
}),
|
|
46515
|
+
/** The URL's registrable domain differs from the org website's: list it in the summary. */
|
|
46516
|
+
offHomepageDomain: external_exports.boolean()
|
|
46517
|
+
});
|
|
46518
|
+
var SetupAddExclusionRequestSchema = external_exports.object({
|
|
46519
|
+
runVersion: RunVersionSchema2,
|
|
46520
|
+
reason: ReasonSchema,
|
|
46521
|
+
expression: external_exports.string().min(1).max(500)
|
|
46522
|
+
});
|
|
46523
|
+
var SetupAddExclusionResponseSchema = external_exports.object({
|
|
46524
|
+
/** false when the same exclusion was already on the engagement. */
|
|
46525
|
+
created: external_exports.boolean()
|
|
46526
|
+
});
|
|
46527
|
+
var SetupUpdateEngagementRequestSchema = external_exports.object({
|
|
46528
|
+
runVersion: RunVersionSchema2,
|
|
46529
|
+
reason: ReasonSchema,
|
|
46530
|
+
testType: PentestTestType.optional(),
|
|
46531
|
+
// The same ranges the staff engagement form enforces: a rate outside them turns API coverage's
|
|
46532
|
+
// sender off at launch. There is no description field: it is shown to the customer. Not
|
|
46533
|
+
// nullable: setup only adds, so it can set a limit but never clear one.
|
|
46534
|
+
requestRatePerSec: external_exports.number().min(REQUEST_RATE_PER_SEC_RANGE.min).max(REQUEST_RATE_PER_SEC_RANGE.max).optional(),
|
|
46535
|
+
requestBurst: external_exports.number().int().min(REQUEST_BURST_RANGE.min).max(REQUEST_BURST_RANGE.max).optional()
|
|
46536
|
+
});
|
|
46537
|
+
var SetupVerifyRequestSchema = external_exports.object({
|
|
46538
|
+
runVersion: RunVersionSchema2,
|
|
46539
|
+
action: external_exports.enum(["start", "status"])
|
|
46540
|
+
});
|
|
46541
|
+
var SetupVerifyResponseSchema = external_exports.object({
|
|
46542
|
+
applications: external_exports.array(
|
|
46543
|
+
external_exports.object({
|
|
46544
|
+
applicationName: external_exports.string(),
|
|
46545
|
+
/** What `start` did for this application; null on `status`. */
|
|
46546
|
+
launch: external_exports.enum(["STARTED", "IN_FLIGHT", "NOTHING_TO_CHECK"]).nullable(),
|
|
46547
|
+
environments: external_exports.array(external_exports.object({ name: external_exports.string(), status: SetupVerificationStatus })),
|
|
46548
|
+
credentials: external_exports.array(
|
|
46549
|
+
external_exports.object({
|
|
46550
|
+
label: external_exports.string(),
|
|
46551
|
+
environmentName: external_exports.string(),
|
|
46552
|
+
status: SetupVerificationStatus
|
|
46553
|
+
})
|
|
46554
|
+
)
|
|
46555
|
+
})
|
|
46556
|
+
)
|
|
46557
|
+
});
|
|
46558
|
+
var SetupShareLinkRequestSchema = external_exports.object({ runVersion: RunVersionSchema2 });
|
|
46559
|
+
var SetupShareLinkResponseSchema = external_exports.object({ shareLink: external_exports.string().nullable() });
|
|
46560
|
+
|
|
46561
|
+
// ../../packages/api-contracts/src/contracts/v1/setup-session-internal.ts
|
|
46562
|
+
var c81 = initContract();
|
|
46563
|
+
var pathParams2 = external_exports.strictObject({ agentSessionId: external_exports.uuid() });
|
|
46564
|
+
var errors6 = {
|
|
46565
|
+
400: ErrorResponseSchema,
|
|
46566
|
+
401: ErrorResponseSchema,
|
|
46567
|
+
403: ErrorResponseSchema,
|
|
46568
|
+
404: ErrorResponseSchema,
|
|
46569
|
+
409: ErrorResponseSchema,
|
|
46570
|
+
500: ErrorResponseSchema,
|
|
46571
|
+
503: ErrorResponseSchema
|
|
46572
|
+
};
|
|
46573
|
+
var base2 = "/internal/v1/agent-sessions/:agentSessionId/setup";
|
|
46574
|
+
var setupSessionInternalContract = c81.router({
|
|
46575
|
+
addSetupTarget: {
|
|
46576
|
+
method: "POST",
|
|
46577
|
+
path: `${base2}/target`,
|
|
46578
|
+
pathParams: pathParams2,
|
|
46579
|
+
body: SetupAddTargetRequestSchema,
|
|
46580
|
+
responses: { 200: SetupAddTargetResponseSchema, ...errors6 }
|
|
46581
|
+
},
|
|
46582
|
+
addSetupExclusion: {
|
|
46583
|
+
method: "POST",
|
|
46584
|
+
path: `${base2}/exclusion`,
|
|
46585
|
+
pathParams: pathParams2,
|
|
46586
|
+
body: SetupAddExclusionRequestSchema,
|
|
46587
|
+
responses: { 200: SetupAddExclusionResponseSchema, ...errors6 }
|
|
46588
|
+
},
|
|
46589
|
+
updateSetupEngagement: {
|
|
46590
|
+
method: "POST",
|
|
46591
|
+
path: `${base2}/engagement`,
|
|
46592
|
+
pathParams: pathParams2,
|
|
46593
|
+
body: SetupUpdateEngagementRequestSchema,
|
|
46594
|
+
responses: { 200: OperatorPentestContextSchema, ...errors6 }
|
|
46595
|
+
},
|
|
46596
|
+
verifySetup: {
|
|
46597
|
+
method: "POST",
|
|
46598
|
+
path: `${base2}/verify`,
|
|
46599
|
+
pathParams: pathParams2,
|
|
46600
|
+
body: SetupVerifyRequestSchema,
|
|
46601
|
+
responses: { 200: SetupVerifyResponseSchema, ...errors6 }
|
|
46602
|
+
},
|
|
46603
|
+
getSetupShareLink: {
|
|
46604
|
+
method: "POST",
|
|
46605
|
+
path: `${base2}/share-link`,
|
|
46606
|
+
pathParams: pathParams2,
|
|
46607
|
+
body: SetupShareLinkRequestSchema,
|
|
46608
|
+
responses: { 200: SetupShareLinkResponseSchema, ...errors6 }
|
|
46609
|
+
}
|
|
46610
|
+
});
|
|
46611
|
+
|
|
46312
46612
|
// ../../packages/api-contracts/src/schemas/api/v1/premise-check.api.ts
|
|
46313
46613
|
var PREMISE_VERDICTS = ["supported", "contradicted", "cant_tell"];
|
|
46314
46614
|
var EXTRACTED_PREMISE_KINDS = [
|
|
@@ -46398,8 +46698,8 @@ var ListReviewPremiseChecksResponseSchema = external_exports.object({
|
|
|
46398
46698
|
});
|
|
46399
46699
|
|
|
46400
46700
|
// ../../packages/api-contracts/src/contracts/v1/premise-check-internal.ts
|
|
46401
|
-
var
|
|
46402
|
-
var premiseCheckInternalContract =
|
|
46701
|
+
var c82 = initContract();
|
|
46702
|
+
var premiseCheckInternalContract = c82.router({
|
|
46403
46703
|
listReviewPremiseChecks: {
|
|
46404
46704
|
method: "POST",
|
|
46405
46705
|
path: "/internal/v1/agent-sessions/:agentSessionId/premise-checks/list",
|
|
@@ -48261,11 +48561,11 @@ function buildCloneTokenFailureWarning(cloneUrl, failure) {
|
|
|
48261
48561
|
return `Trace returned an unexpected response while generating clone credentials for ${cloneUrl}${quotedMessage ? ` ${quotedMessage}` : ""}. ${fallbackSuffix}`;
|
|
48262
48562
|
}
|
|
48263
48563
|
}
|
|
48264
|
-
function withOptionalMessage(
|
|
48564
|
+
function withOptionalMessage(base3, message) {
|
|
48265
48565
|
if (typeof message === "string") {
|
|
48266
|
-
return { ...
|
|
48566
|
+
return { ...base3, message };
|
|
48267
48567
|
}
|
|
48268
|
-
return
|
|
48568
|
+
return base3;
|
|
48269
48569
|
}
|
|
48270
48570
|
async function listOrganizationsSafe() {
|
|
48271
48571
|
const result = await authenticatedRequestSafe(
|
|
@@ -49083,6 +49383,13 @@ function parseOutput(value) {
|
|
|
49083
49383
|
console.error(colorError(`Invalid --output '${value}'. Use table, json, or tsv.`));
|
|
49084
49384
|
process.exit(1);
|
|
49085
49385
|
}
|
|
49386
|
+
function parseTextOutput(value) {
|
|
49387
|
+
if (value === "text" || value === "json") {
|
|
49388
|
+
return value;
|
|
49389
|
+
}
|
|
49390
|
+
console.error(colorError(`Invalid --output '${sanitizeDisplay(value)}'. Use text or json.`));
|
|
49391
|
+
process.exit(1);
|
|
49392
|
+
}
|
|
49086
49393
|
var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
49087
49394
|
function isUuid(value) {
|
|
49088
49395
|
return UUID_PATTERN.test(value);
|
|
@@ -49316,12 +49623,12 @@ function buildApplicationsTable(apps) {
|
|
|
49316
49623
|
repos: String(a2.repositories.length),
|
|
49317
49624
|
url: a2.applicationUrl ? sanitizeDisplay(a2.applicationUrl) : "\u2014"
|
|
49318
49625
|
}));
|
|
49319
|
-
const idWidth = Math.max(2, ...cells.map((
|
|
49320
|
-
const nameWidth = Math.max(4, ...cells.map((
|
|
49321
|
-
const typeWidth = Math.max(4, ...cells.map((
|
|
49626
|
+
const idWidth = Math.max(2, ...cells.map((c84) => c84.id.length));
|
|
49627
|
+
const nameWidth = Math.max(4, ...cells.map((c84) => c84.name.length));
|
|
49628
|
+
const typeWidth = Math.max(4, ...cells.map((c84) => c84.type.length));
|
|
49322
49629
|
const header = `${"ID".padEnd(idWidth)} ${"NAME".padEnd(nameWidth)} ${"TYPE".padEnd(typeWidth)} REPOS URL`;
|
|
49323
49630
|
const rows = cells.map(
|
|
49324
|
-
(
|
|
49631
|
+
(c84) => `${c84.id.padEnd(idWidth)} ${c84.name.padEnd(nameWidth)} ${c84.type.padEnd(typeWidth)} ${c84.repos.padStart(5)} ${c84.url}`
|
|
49325
49632
|
);
|
|
49326
49633
|
return { header, rows };
|
|
49327
49634
|
}
|
|
@@ -49341,12 +49648,12 @@ function buildApplicationRepositoriesTable(links) {
|
|
|
49341
49648
|
path: l2.repositoryPath ? sanitizeDisplay(l2.repositoryPath) : "\u2014",
|
|
49342
49649
|
status: formatStatus(l2.repositoryStatus)
|
|
49343
49650
|
}));
|
|
49344
|
-
const nameWidth = Math.max(10, ...cells.map((
|
|
49345
|
-
const roleWidth = Math.max(4, ...cells.map((
|
|
49346
|
-
const pathWidth = Math.max(4, ...cells.map((
|
|
49651
|
+
const nameWidth = Math.max(10, ...cells.map((c84) => c84.name.length));
|
|
49652
|
+
const roleWidth = Math.max(4, ...cells.map((c84) => c84.role.length));
|
|
49653
|
+
const pathWidth = Math.max(4, ...cells.map((c84) => c84.path.length));
|
|
49347
49654
|
const header = `${"REPOSITORY".padEnd(nameWidth)} SCAN ${"ROLE".padEnd(roleWidth)} ${"PATH".padEnd(pathWidth)} STATUS`;
|
|
49348
49655
|
const rows = cells.map(
|
|
49349
|
-
(
|
|
49656
|
+
(c84) => `${c84.name.padEnd(nameWidth)} ${c84.scan.padEnd(4)} ${c84.role.padEnd(roleWidth)} ${c84.path.padEnd(pathWidth)} ${c84.status}`
|
|
49350
49657
|
);
|
|
49351
49658
|
return { header, rows };
|
|
49352
49659
|
}
|
|
@@ -50131,8 +50438,8 @@ function wrapTextWithPrefix(e, r2, o2, t2 = o2, s = o2, n3) {
|
|
|
50131
50438
|
hard: true,
|
|
50132
50439
|
trim: false
|
|
50133
50440
|
}).split(`
|
|
50134
|
-
`).map((
|
|
50135
|
-
const d = n3 ? n3(
|
|
50441
|
+
`).map((c84, i2, m2) => {
|
|
50442
|
+
const d = n3 ? n3(c84, i2) : c84;
|
|
50136
50443
|
return i2 === 0 ? `${t2}${d}` : i2 === m2.length - 1 ? `${s}${d}` : `${o2}${d}`;
|
|
50137
50444
|
}).join(`
|
|
50138
50445
|
`);
|
|
@@ -50630,14 +50937,14 @@ var limitOptions = ({
|
|
|
50630
50937
|
Math.min(l2 - a2 + 3, e.length - a2),
|
|
50631
50938
|
0
|
|
50632
50939
|
));
|
|
50633
|
-
let d = a2 < e.length && f > 0,
|
|
50940
|
+
let d = a2 < e.length && f > 0, c84 = a2 < e.length && f + a2 < e.length;
|
|
50634
50941
|
const W = Math.min(
|
|
50635
50942
|
f + a2,
|
|
50636
50943
|
e.length
|
|
50637
50944
|
), s = [];
|
|
50638
50945
|
let g = 0;
|
|
50639
|
-
d && g++,
|
|
50640
|
-
const T = f + (d ? 1 : 0), y = W - (
|
|
50946
|
+
d && g++, c84 && g++;
|
|
50947
|
+
const T = f + (d ? 1 : 0), y = W - (c84 ? 1 : 0);
|
|
50641
50948
|
for (let t2 = T; t2 < y; t2++) {
|
|
50642
50949
|
const n3 = e[t2], o2 = n3 ? w(n3, t2 === l2) : "", h2 = wrapAnsi(o2, i2, {
|
|
50643
50950
|
hard: true,
|
|
@@ -50658,14 +50965,14 @@ var limitOptions = ({
|
|
|
50658
50965
|
u4,
|
|
50659
50966
|
true
|
|
50660
50967
|
);
|
|
50661
|
-
d ? ({ lineCount: o2, removals: t2 } = L(), o2 > u4 && (
|
|
50968
|
+
d ? ({ lineCount: o2, removals: t2 } = L(), o2 > u4 && (c84 || (u4 -= 1), { lineCount: o2, removals: n3 } = E())) : (c84 || (u4 -= 1), { lineCount: o2, removals: n3 } = E(), o2 > u4 && (u4 -= 1, { lineCount: o2, removals: t2 } = L())), t2 > 0 && (d = true, s.splice(0, t2)), n3 > 0 && (c84 = true, s.splice(s.length - n3, n3));
|
|
50662
50969
|
}
|
|
50663
50970
|
const x = [];
|
|
50664
50971
|
d && x.push(M);
|
|
50665
50972
|
for (const t2 of s)
|
|
50666
50973
|
for (const n3 of t2)
|
|
50667
50974
|
x.push(n3);
|
|
50668
|
-
return
|
|
50975
|
+
return c84 && x.push(M), x;
|
|
50669
50976
|
};
|
|
50670
50977
|
var confirm = (i2) => {
|
|
50671
50978
|
const a2 = i2.active ?? "Yes", s = i2.inactive ?? "No";
|
|
@@ -50684,15 +50991,15 @@ var confirm = (i2) => {
|
|
|
50684
50991
|
u4
|
|
50685
50992
|
), o2 = `${e ? `${styleText2("gray", S_BAR)}
|
|
50686
50993
|
` : ""}${f}
|
|
50687
|
-
`,
|
|
50994
|
+
`, c84 = this.value ? a2 : s;
|
|
50688
50995
|
switch (this.state) {
|
|
50689
50996
|
case "submit": {
|
|
50690
50997
|
const r2 = e ? `${styleText2("gray", S_BAR)} ` : "";
|
|
50691
|
-
return `${o2}${r2}${styleText2("dim",
|
|
50998
|
+
return `${o2}${r2}${styleText2("dim", c84)}`;
|
|
50692
50999
|
}
|
|
50693
51000
|
case "cancel": {
|
|
50694
51001
|
const r2 = e ? `${styleText2("gray", S_BAR)} ` : "";
|
|
50695
|
-
return `${o2}${r2}${styleText2(["strikethrough", "dim"],
|
|
51002
|
+
return `${o2}${r2}${styleText2(["strikethrough", "dim"], c84)}${e ? `
|
|
50696
51003
|
${styleText2("gray", S_BAR)}` : ""}`;
|
|
50697
51004
|
}
|
|
50698
51005
|
default: {
|
|
@@ -50840,13 +51147,13 @@ var multiline = (e) => new h({
|
|
|
50840
51147
|
`, m2 = e.placeholder && e.placeholder.length > 0 ? (
|
|
50841
51148
|
// biome-ignore lint/style/noNonNullAssertion: guarded by placeholder.length > 0
|
|
50842
51149
|
styleText2("inverse", e.placeholder[0]) + styleText2("dim", e.placeholder.slice(1))
|
|
50843
|
-
) : styleText2(["inverse", "hidden"], "_"), a2 = this.userInput ? this.userInputWithCursor : m2, l2 = this.value ?? "",
|
|
51150
|
+
) : styleText2(["inverse", "hidden"], "_"), a2 = this.userInput ? this.userInputWithCursor : m2, l2 = this.value ?? "", c84 = e.showSubmit ? `
|
|
50844
51151
|
${styleText2(this.focused === "submit" ? "cyan" : "dim", "[ submit ]")}` : "";
|
|
50845
51152
|
switch (this.state) {
|
|
50846
51153
|
case "error": {
|
|
50847
51154
|
const n3 = `${styleText2("yellow", S_BAR)} `, r2 = i2 ? wrapTextWithPrefix(e.output, a2, n3, void 0) : a2, u4 = styleText2("yellow", S_BAR_END);
|
|
50848
51155
|
return `${o2}${r2}
|
|
50849
|
-
${u4} ${styleText2("yellow", this.error)}${
|
|
51156
|
+
${u4} ${styleText2("yellow", this.error)}${c84}
|
|
50850
51157
|
`;
|
|
50851
51158
|
}
|
|
50852
51159
|
case "submit": {
|
|
@@ -50874,7 +51181,7 @@ ${u4} ${styleText2("yellow", this.error)}${c83}
|
|
|
50874
51181
|
default: {
|
|
50875
51182
|
const n3 = i2 ? `${styleText2("cyan", S_BAR)} ` : "", r2 = i2 ? styleText2("cyan", S_BAR_END) : "", u4 = i2 ? wrapTextWithPrefix(e.output, a2, n3) : a2;
|
|
50876
51183
|
return `${o2}${u4}
|
|
50877
|
-
${r2}${
|
|
51184
|
+
${r2}${c84}
|
|
50878
51185
|
`;
|
|
50879
51186
|
}
|
|
50880
51187
|
}
|
|
@@ -50889,7 +51196,7 @@ var password = (r2) => new u$1({
|
|
|
50889
51196
|
render() {
|
|
50890
51197
|
const e = r2.withGuide ?? settings.withGuide, o2 = `${e ? `${styleText2("gray", S_BAR)}
|
|
50891
51198
|
` : ""}${symbol2(this.state)} ${r2.message}
|
|
50892
|
-
`,
|
|
51199
|
+
`, c84 = this.userInputWithCursor, i2 = this.masked;
|
|
50893
51200
|
switch (this.state) {
|
|
50894
51201
|
case "error": {
|
|
50895
51202
|
const s = e ? `${styleText2("yellow", S_BAR)} ` : "", n3 = e ? `${styleText2("yellow", S_BAR_END)} ` : "", l2 = i2 ?? "";
|
|
@@ -50909,7 +51216,7 @@ ${styleText2("gray", S_BAR)}` : ""}`;
|
|
|
50909
51216
|
}
|
|
50910
51217
|
default: {
|
|
50911
51218
|
const s = e ? `${styleText2("cyan", S_BAR)} ` : "", n3 = e ? styleText2("cyan", S_BAR_END) : "";
|
|
50912
|
-
return `${o2}${s}${
|
|
51219
|
+
return `${o2}${s}${c84}
|
|
50913
51220
|
${n3}
|
|
50914
51221
|
`;
|
|
50915
51222
|
}
|
|
@@ -50925,7 +51232,7 @@ var SELECT_INSTRUCTIONS = [
|
|
|
50925
51232
|
`${styleText2("dim", "\u2191/\u2193")} to navigate`,
|
|
50926
51233
|
`${styleText2("dim", "Enter:")} confirm`
|
|
50927
51234
|
];
|
|
50928
|
-
var
|
|
51235
|
+
var c83 = (t2, o2) => t2.includes(`
|
|
50929
51236
|
`) ? t2.split(`
|
|
50930
51237
|
`).map((d) => o2(d)).join(`
|
|
50931
51238
|
`) : o2(t2);
|
|
@@ -50936,15 +51243,15 @@ var select = (t2) => {
|
|
|
50936
51243
|
const s = n3.label ?? String(n3.value);
|
|
50937
51244
|
switch (m2) {
|
|
50938
51245
|
case "disabled":
|
|
50939
|
-
return `${styleText2("gray", S_RADIO_INACTIVE)} ${
|
|
51246
|
+
return `${styleText2("gray", S_RADIO_INACTIVE)} ${c83(s, (i2) => styleText2("gray", i2))}${n3.hint ? ` ${styleText2("dim", `(${n3.hint ?? "disabled"})`)}` : ""}`;
|
|
50940
51247
|
case "selected":
|
|
50941
|
-
return `${
|
|
51248
|
+
return `${c83(s, (i2) => styleText2("dim", i2))}`;
|
|
50942
51249
|
case "active":
|
|
50943
51250
|
return `${styleText2("green", S_RADIO_ACTIVE)} ${s}${n3.hint ? ` ${styleText2("dim", `(${n3.hint})`)}` : ""}`;
|
|
50944
51251
|
case "cancelled":
|
|
50945
|
-
return `${
|
|
51252
|
+
return `${c83(s, (i2) => styleText2(["strikethrough", "dim"], i2))}`;
|
|
50946
51253
|
default:
|
|
50947
|
-
return `${styleText2("dim", S_RADIO_INACTIVE)} ${
|
|
51254
|
+
return `${styleText2("dim", S_RADIO_INACTIVE)} ${c83(s, (i2) => styleText2("dim", i2))}`;
|
|
50948
51255
|
}
|
|
50949
51256
|
}, d = t2.showInstructions ?? true;
|
|
50950
51257
|
return new n$1({
|
|
@@ -51817,7 +52124,7 @@ function buildAuthStatusInfo(credential, refreshToken, selectedOrganizationId) {
|
|
|
51817
52124
|
};
|
|
51818
52125
|
}
|
|
51819
52126
|
const token = credential.token;
|
|
51820
|
-
const
|
|
52127
|
+
const base3 = {
|
|
51821
52128
|
loggedIn: true,
|
|
51822
52129
|
authMethod: "user",
|
|
51823
52130
|
expired: false,
|
|
@@ -51828,20 +52135,20 @@ function buildAuthStatusInfo(credential, refreshToken, selectedOrganizationId) {
|
|
|
51828
52135
|
const parts = token.split(".");
|
|
51829
52136
|
const payload = parts[1];
|
|
51830
52137
|
if (!payload) {
|
|
51831
|
-
return
|
|
52138
|
+
return base3;
|
|
51832
52139
|
}
|
|
51833
52140
|
const decoded = JSON.parse(Buffer.from(payload, "base64url").toString("utf-8"));
|
|
51834
52141
|
const expiry = decoded.exp ? new Date(decoded.exp * 1e3) : null;
|
|
51835
52142
|
const isExpired = expiry ? expiry < /* @__PURE__ */ new Date() : false;
|
|
51836
52143
|
return {
|
|
51837
|
-
...
|
|
52144
|
+
...base3,
|
|
51838
52145
|
...decoded.sub ? { userId: decoded.sub } : {},
|
|
51839
52146
|
...decoded.org_id ? { orgId: decoded.org_id } : {},
|
|
51840
52147
|
...expiry ? { expires: expiry.toISOString() } : {},
|
|
51841
52148
|
expired: isExpired
|
|
51842
52149
|
};
|
|
51843
52150
|
} catch {
|
|
51844
|
-
return
|
|
52151
|
+
return base3;
|
|
51845
52152
|
}
|
|
51846
52153
|
}
|
|
51847
52154
|
function createStatusCommand() {
|
|
@@ -52441,7 +52748,7 @@ function extractCommandTree(cmd) {
|
|
|
52441
52748
|
description: o2.description,
|
|
52442
52749
|
takesValue: o2.required || o2.optional
|
|
52443
52750
|
}));
|
|
52444
|
-
const subcommands = cmd.commands.filter((
|
|
52751
|
+
const subcommands = cmd.commands.filter((c84) => !c84._hidden).map((c84) => extractCommandTree(c84));
|
|
52445
52752
|
return {
|
|
52446
52753
|
name: cmd.name(),
|
|
52447
52754
|
description: cmd.description(),
|
|
@@ -52507,7 +52814,7 @@ function generateBashCompletion(tree, binName) {
|
|
|
52507
52814
|
}
|
|
52508
52815
|
function collectBashCases(tree, prefix) {
|
|
52509
52816
|
const cases = /* @__PURE__ */ new Map();
|
|
52510
|
-
const subcommandNames = tree.subcommands.map((
|
|
52817
|
+
const subcommandNames = tree.subcommands.map((c84) => c84.name);
|
|
52511
52818
|
const optionNames = tree.options.flatMap((o2) => o2.short ? [o2.long, o2.short] : [o2.long]);
|
|
52512
52819
|
const extraOptions = prefix === "" ? ["--help", "--version"] : ["--help"];
|
|
52513
52820
|
const allOptions = [.../* @__PURE__ */ new Set([...optionNames, ...extraOptions])];
|
|
@@ -52922,9 +53229,9 @@ function buildInboxesTable(inboxes) {
|
|
|
52922
53229
|
address: sanitizeDisplay(inbox.address),
|
|
52923
53230
|
label: sanitizeDisplay(inbox.label)
|
|
52924
53231
|
}));
|
|
52925
|
-
const addressWidth = Math.max(7, ...cells.map((
|
|
53232
|
+
const addressWidth = Math.max(7, ...cells.map((c84) => c84.address.length));
|
|
52926
53233
|
const header = `${"ADDRESS".padEnd(addressWidth)} LABEL`;
|
|
52927
|
-
const rows = cells.map((
|
|
53234
|
+
const rows = cells.map((c84) => `${c84.address.padEnd(addressWidth)} ${c84.label}`);
|
|
52928
53235
|
return { header, rows };
|
|
52929
53236
|
}
|
|
52930
53237
|
function formatInboxQuota(quota) {
|
|
@@ -52948,14 +53255,14 @@ function buildInboxEmailsTable(messages) {
|
|
|
52948
53255
|
subject: sanitizeDisplay(message.subject ?? ""),
|
|
52949
53256
|
unread: message.unread ? "yes" : "no"
|
|
52950
53257
|
}));
|
|
52951
|
-
const idWidth = Math.max(2, ...cells.map((
|
|
52952
|
-
const receivedWidth = Math.max(8, ...cells.map((
|
|
52953
|
-
const fromWidth = Math.max(4, ...cells.map((
|
|
52954
|
-
const toWidth = Math.max(2, ...cells.map((
|
|
52955
|
-
const subjectWidth = Math.max(7, ...cells.map((
|
|
53258
|
+
const idWidth = Math.max(2, ...cells.map((c84) => c84.id.length));
|
|
53259
|
+
const receivedWidth = Math.max(8, ...cells.map((c84) => c84.received.length));
|
|
53260
|
+
const fromWidth = Math.max(4, ...cells.map((c84) => c84.from.length));
|
|
53261
|
+
const toWidth = Math.max(2, ...cells.map((c84) => c84.to.length));
|
|
53262
|
+
const subjectWidth = Math.max(7, ...cells.map((c84) => c84.subject.length));
|
|
52956
53263
|
const header = `${"ID".padEnd(idWidth)} ${"RECEIVED".padEnd(receivedWidth)} ${"FROM".padEnd(fromWidth)} ${"TO".padEnd(toWidth)} ${"SUBJECT".padEnd(subjectWidth)} UNREAD`;
|
|
52957
53264
|
const rows = cells.map(
|
|
52958
|
-
(
|
|
53265
|
+
(c84) => `${c84.id.padEnd(idWidth)} ${c84.received.padEnd(receivedWidth)} ${c84.from.padEnd(fromWidth)} ${c84.to.padEnd(toWidth)} ${c84.subject.padEnd(subjectWidth)} ${c84.unread}`
|
|
52959
53266
|
);
|
|
52960
53267
|
return { header, rows };
|
|
52961
53268
|
}
|
|
@@ -53698,6 +54005,13 @@ function mfaDisplay(mfa) {
|
|
|
53698
54005
|
if (mfa.algorithm !== "SHA1") parts.push(mfa.algorithm);
|
|
53699
54006
|
return parts.join(" \xB7 ");
|
|
53700
54007
|
}
|
|
54008
|
+
function sourceDisplay(place) {
|
|
54009
|
+
return `${sanitizeDisplay(place.applicationName)} / ${sanitizeDisplay(place.environmentName)}`;
|
|
54010
|
+
}
|
|
54011
|
+
function linkedIntoDisplay(linkedInto) {
|
|
54012
|
+
if (!linkedInto || linkedInto.length === 0) return "(none)";
|
|
54013
|
+
return linkedInto.map(sourceDisplay).join(", ");
|
|
54014
|
+
}
|
|
53701
54015
|
function renderCredentialDetail(cred) {
|
|
53702
54016
|
const pad = 13;
|
|
53703
54017
|
return [
|
|
@@ -53707,6 +54021,7 @@ function renderCredentialDetail(cred) {
|
|
|
53707
54021
|
`${"Environment:".padEnd(pad)} ${cred.environmentId}`,
|
|
53708
54022
|
`${"Auth config:".padEnd(pad)} ${authConfigDisplay(cred.authConfig)}`,
|
|
53709
54023
|
`${"MFA:".padEnd(pad)} ${mfaDisplay(cred.mfa)}`,
|
|
54024
|
+
`${"Linked into:".padEnd(pad)} ${linkedIntoDisplay(cred.linkedInto)}`,
|
|
53710
54025
|
`${"Created:".padEnd(pad)} ${formatDate(cred.createdAt)}`
|
|
53711
54026
|
].join("\n");
|
|
53712
54027
|
}
|
|
@@ -53730,16 +54045,16 @@ function printCredentialsTsv(creds) {
|
|
|
53730
54045
|
}
|
|
53731
54046
|
}
|
|
53732
54047
|
function buildCredentialsTable(creds) {
|
|
53733
|
-
const cells = creds.map((
|
|
53734
|
-
id:
|
|
53735
|
-
label: sanitizeDisplay(
|
|
53736
|
-
type:
|
|
54048
|
+
const cells = creds.map((c84) => ({
|
|
54049
|
+
id: c84.credentialId,
|
|
54050
|
+
label: sanitizeDisplay(c84.label),
|
|
54051
|
+
type: c84.credentialType
|
|
53737
54052
|
}));
|
|
53738
|
-
const idWidth = Math.max(2, ...cells.map((
|
|
53739
|
-
const labelWidth = Math.max(5, ...cells.map((
|
|
54053
|
+
const idWidth = Math.max(2, ...cells.map((c84) => c84.id.length));
|
|
54054
|
+
const labelWidth = Math.max(5, ...cells.map((c84) => c84.label.length));
|
|
53740
54055
|
const header = `${"ID".padEnd(idWidth)} ${"LABEL".padEnd(labelWidth)} TYPE`;
|
|
53741
54056
|
const rows = cells.map(
|
|
53742
|
-
(
|
|
54057
|
+
(c84) => `${c84.id.padEnd(idWidth)} ${c84.label.padEnd(labelWidth)} ${c84.type}`
|
|
53743
54058
|
);
|
|
53744
54059
|
return { header, rows };
|
|
53745
54060
|
}
|
|
@@ -54174,6 +54489,512 @@ async function runDelete3(credentialId, options) {
|
|
|
54174
54489
|
}
|
|
54175
54490
|
console.log(colorSuccess(`Deleted credential ${credentialId}`));
|
|
54176
54491
|
}
|
|
54492
|
+
function failLinkOp(op, status, body, notFoundLabel) {
|
|
54493
|
+
const { message, errorCode } = readErrorFields(body);
|
|
54494
|
+
if (status === UNAUTHORIZED_STATUS2) {
|
|
54495
|
+
exitAuthFailed();
|
|
54496
|
+
}
|
|
54497
|
+
if (status === 403) {
|
|
54498
|
+
const verb = op.startsWith("un") ? "unlink" : "link";
|
|
54499
|
+
console.error(
|
|
54500
|
+
colorError(
|
|
54501
|
+
`Only an organization admin or Trace staff can ${verb} credentials, and API keys can't.`
|
|
54502
|
+
)
|
|
54503
|
+
);
|
|
54504
|
+
if (message !== void 0) console.error(message);
|
|
54505
|
+
process.exit(1);
|
|
54506
|
+
}
|
|
54507
|
+
if (status === 404) {
|
|
54508
|
+
console.error(colorError(message ?? `Not found: ${notFoundLabel}.`));
|
|
54509
|
+
process.exit(1);
|
|
54510
|
+
}
|
|
54511
|
+
if (status === 400) {
|
|
54512
|
+
console.error(colorError(message ?? "Invalid request (status 400)."));
|
|
54513
|
+
process.exit(1);
|
|
54514
|
+
}
|
|
54515
|
+
if (status === 409) {
|
|
54516
|
+
if (errorCode === "CREDENTIAL_LINK_SELF") {
|
|
54517
|
+
console.error(
|
|
54518
|
+
colorError(message ?? "That already belongs to this environment; nothing to link.")
|
|
54519
|
+
);
|
|
54520
|
+
} else if (errorCode === "CREDENTIAL_ALREADY_LINKED") {
|
|
54521
|
+
console.error(colorError(message ?? "That is already linked into this environment."));
|
|
54522
|
+
} else if (errorCode === "CREDENTIAL_LABEL_TAKEN") {
|
|
54523
|
+
console.error(
|
|
54524
|
+
colorError(
|
|
54525
|
+
message ?? "This environment already shows a credential with that label. Rename one of them first."
|
|
54526
|
+
)
|
|
54527
|
+
);
|
|
54528
|
+
} else if (errorCode === "CREDENTIAL_IN_USE") {
|
|
54529
|
+
console.error(
|
|
54530
|
+
colorError(message ?? "You can't unlink while a pentest using this environment is running.")
|
|
54531
|
+
);
|
|
54532
|
+
} else {
|
|
54533
|
+
console.error(colorError(message ?? "Conflict (status 409)."));
|
|
54534
|
+
}
|
|
54535
|
+
process.exit(1);
|
|
54536
|
+
}
|
|
54537
|
+
console.error(colorError(`Failed to ${op} (status ${status})${message ? `: ${message}` : "."}`));
|
|
54538
|
+
process.exit(1);
|
|
54539
|
+
}
|
|
54540
|
+
function requireLinkId(credentialLinkId) {
|
|
54541
|
+
if (!isUuid(credentialLinkId)) {
|
|
54542
|
+
console.error(colorError(`Invalid credential link id '${credentialLinkId}'. Expected a UUID.`));
|
|
54543
|
+
process.exit(1);
|
|
54544
|
+
}
|
|
54545
|
+
}
|
|
54546
|
+
function renderCredentialLinkDetail(link) {
|
|
54547
|
+
const pad = 13;
|
|
54548
|
+
return [
|
|
54549
|
+
`${"Link ID:".padEnd(pad)} ${link.credentialLinkId}`,
|
|
54550
|
+
`${"Credential:".padEnd(pad)} ${link.credential.credentialId}`,
|
|
54551
|
+
`${"Label:".padEnd(pad)} ${sanitizeDisplay(link.credential.label)}`,
|
|
54552
|
+
`${"Type:".padEnd(pad)} ${link.credential.credentialType}`,
|
|
54553
|
+
`${"Source:".padEnd(pad)} ${sourceDisplay(link.source)} (${link.source.environmentId})`,
|
|
54554
|
+
`${"Linked into:".padEnd(pad)} ${link.environmentId}`,
|
|
54555
|
+
`${"MFA:".padEnd(pad)} ${mfaDisplay(link.credential.mfa)}`,
|
|
54556
|
+
`${"Linked:".padEnd(pad)} ${formatDate(link.createdAt)}`
|
|
54557
|
+
].join("\n");
|
|
54558
|
+
}
|
|
54559
|
+
function printCredentialLinksTsv(links) {
|
|
54560
|
+
console.log(
|
|
54561
|
+
[
|
|
54562
|
+
"credentialLinkId",
|
|
54563
|
+
"credentialId",
|
|
54564
|
+
"label",
|
|
54565
|
+
"credentialType",
|
|
54566
|
+
"sourceApplication",
|
|
54567
|
+
"sourceEnvironment",
|
|
54568
|
+
"sourceEnvironmentId",
|
|
54569
|
+
"environmentCredentialSourceId",
|
|
54570
|
+
"mfa",
|
|
54571
|
+
"createdAt"
|
|
54572
|
+
].join(" ")
|
|
54573
|
+
);
|
|
54574
|
+
for (const link of links) {
|
|
54575
|
+
console.log(
|
|
54576
|
+
[
|
|
54577
|
+
tsvSafe(link.credentialLinkId),
|
|
54578
|
+
tsvSafe(link.credential.credentialId),
|
|
54579
|
+
tsvSafe(sanitizeDisplay(link.credential.label)),
|
|
54580
|
+
tsvSafe(link.credential.credentialType),
|
|
54581
|
+
tsvSafe(sanitizeDisplay(link.source.applicationName)),
|
|
54582
|
+
tsvSafe(sanitizeDisplay(link.source.environmentName)),
|
|
54583
|
+
tsvSafe(link.source.environmentId),
|
|
54584
|
+
tsvSafe(link.environmentCredentialSourceId ?? ""),
|
|
54585
|
+
tsvSafe(link.credential.mfa ? link.credential.mfa.kind : ""),
|
|
54586
|
+
tsvSafe(toIso(link.createdAt))
|
|
54587
|
+
].join(" ")
|
|
54588
|
+
);
|
|
54589
|
+
}
|
|
54590
|
+
}
|
|
54591
|
+
function buildCredentialLinksTable(links) {
|
|
54592
|
+
const cells = links.map((link) => ({
|
|
54593
|
+
id: link.credentialLinkId,
|
|
54594
|
+
label: sanitizeDisplay(link.credential.label),
|
|
54595
|
+
type: link.credential.credentialType,
|
|
54596
|
+
source: sourceDisplay(link.source),
|
|
54597
|
+
via: link.environmentCredentialSourceId ? "link-all" : ""
|
|
54598
|
+
}));
|
|
54599
|
+
const idWidth = Math.max("LINK ID".length, ...cells.map((c84) => c84.id.length));
|
|
54600
|
+
const labelWidth = Math.max("LABEL".length, ...cells.map((c84) => c84.label.length));
|
|
54601
|
+
const typeWidth = Math.max("TYPE".length, ...cells.map((c84) => c84.type.length));
|
|
54602
|
+
const sourceWidth = Math.max("SOURCE".length, ...cells.map((c84) => c84.source.length));
|
|
54603
|
+
const header = `${"LINK ID".padEnd(idWidth)} ${"LABEL".padEnd(labelWidth)} ${"TYPE".padEnd(typeWidth)} ${"SOURCE".padEnd(sourceWidth)} VIA`;
|
|
54604
|
+
const rows = cells.map(
|
|
54605
|
+
(c84) => `${c84.id.padEnd(idWidth)} ${c84.label.padEnd(labelWidth)} ${c84.type.padEnd(typeWidth)} ${c84.source.padEnd(sourceWidth)} ${c84.via}`.trimEnd()
|
|
54606
|
+
);
|
|
54607
|
+
return { header, rows };
|
|
54608
|
+
}
|
|
54609
|
+
function outputCredentialLink(link, output) {
|
|
54610
|
+
if (output === "json") {
|
|
54611
|
+
printJson(link);
|
|
54612
|
+
return;
|
|
54613
|
+
}
|
|
54614
|
+
if (output === "tsv") {
|
|
54615
|
+
printCredentialLinksTsv([link]);
|
|
54616
|
+
return;
|
|
54617
|
+
}
|
|
54618
|
+
console.log(renderCredentialLinkDetail(link));
|
|
54619
|
+
}
|
|
54620
|
+
function printLinkableCredentialsTsv(creds) {
|
|
54621
|
+
console.log(
|
|
54622
|
+
[
|
|
54623
|
+
"credentialId",
|
|
54624
|
+
"label",
|
|
54625
|
+
"credentialType",
|
|
54626
|
+
"application",
|
|
54627
|
+
"environment",
|
|
54628
|
+
"environmentId",
|
|
54629
|
+
"hasMfa",
|
|
54630
|
+
"alreadyLinked"
|
|
54631
|
+
].join(" ")
|
|
54632
|
+
);
|
|
54633
|
+
for (const cred of creds) {
|
|
54634
|
+
console.log(
|
|
54635
|
+
[
|
|
54636
|
+
tsvSafe(cred.credentialId),
|
|
54637
|
+
tsvSafe(sanitizeDisplay(cred.label)),
|
|
54638
|
+
tsvSafe(cred.credentialType),
|
|
54639
|
+
tsvSafe(sanitizeDisplay(cred.application.name)),
|
|
54640
|
+
tsvSafe(sanitizeDisplay(cred.environment.name)),
|
|
54641
|
+
tsvSafe(cred.environment.environmentId),
|
|
54642
|
+
tsvSafe(String(cred.hasMfa)),
|
|
54643
|
+
tsvSafe(String(cred.alreadyLinked))
|
|
54644
|
+
].join(" ")
|
|
54645
|
+
);
|
|
54646
|
+
}
|
|
54647
|
+
}
|
|
54648
|
+
function buildLinkableCredentialsTable(creds) {
|
|
54649
|
+
const cells = creds.map((cred) => ({
|
|
54650
|
+
id: cred.credentialId,
|
|
54651
|
+
label: sanitizeDisplay(cred.label),
|
|
54652
|
+
type: cred.credentialType,
|
|
54653
|
+
source: sourceDisplay({
|
|
54654
|
+
applicationName: cred.application.name,
|
|
54655
|
+
environmentName: cred.environment.name
|
|
54656
|
+
}),
|
|
54657
|
+
linked: cred.alreadyLinked ? "linked" : ""
|
|
54658
|
+
}));
|
|
54659
|
+
const idWidth = Math.max("ID".length, ...cells.map((c84) => c84.id.length));
|
|
54660
|
+
const labelWidth = Math.max("LABEL".length, ...cells.map((c84) => c84.label.length));
|
|
54661
|
+
const typeWidth = Math.max("TYPE".length, ...cells.map((c84) => c84.type.length));
|
|
54662
|
+
const sourceWidth = Math.max("SOURCE".length, ...cells.map((c84) => c84.source.length));
|
|
54663
|
+
const header = `${"ID".padEnd(idWidth)} ${"LABEL".padEnd(labelWidth)} ${"TYPE".padEnd(typeWidth)} ${"SOURCE".padEnd(sourceWidth)} STATUS`;
|
|
54664
|
+
const rows = cells.map(
|
|
54665
|
+
(c84) => `${c84.id.padEnd(idWidth)} ${c84.label.padEnd(labelWidth)} ${c84.type.padEnd(typeWidth)} ${c84.source.padEnd(sourceWidth)} ${c84.linked}`.trimEnd()
|
|
54666
|
+
);
|
|
54667
|
+
return { header, rows };
|
|
54668
|
+
}
|
|
54669
|
+
async function runLink(credentialId, options) {
|
|
54670
|
+
const output = parseOutput(options.output);
|
|
54671
|
+
requireEnvId(options.env);
|
|
54672
|
+
requireCredId(credentialId);
|
|
54673
|
+
const organizationId = await resolveOrganizationId();
|
|
54674
|
+
const result = await authenticatedRequest(
|
|
54675
|
+
(client) => client.credentialLink.createCredentialLink({
|
|
54676
|
+
params: { organizationId, environmentId: options.env },
|
|
54677
|
+
body: { credentialId }
|
|
54678
|
+
})
|
|
54679
|
+
);
|
|
54680
|
+
if (result.status !== CREATED_STATUS) {
|
|
54681
|
+
failLinkOp("link", result.status, result.body, credentialId);
|
|
54682
|
+
}
|
|
54683
|
+
if (output === "table") {
|
|
54684
|
+
console.log(
|
|
54685
|
+
colorSuccess(
|
|
54686
|
+
`Linked credential ${sanitizeDisplay(result.body.credential.label)} from ${sourceDisplay(result.body.source)}`
|
|
54687
|
+
)
|
|
54688
|
+
);
|
|
54689
|
+
}
|
|
54690
|
+
outputCredentialLink(result.body, output);
|
|
54691
|
+
}
|
|
54692
|
+
async function runUnlink(credentialLinkId, options) {
|
|
54693
|
+
requireEnvId(options.env);
|
|
54694
|
+
requireLinkId(credentialLinkId);
|
|
54695
|
+
const confirmed = await confirmAction(
|
|
54696
|
+
`Unlink credential link ${credentialLinkId} (the source credential is kept)?`,
|
|
54697
|
+
{ skip: options.yes, verb: "unlink" }
|
|
54698
|
+
);
|
|
54699
|
+
if (!confirmed) {
|
|
54700
|
+
console.log("Aborted.");
|
|
54701
|
+
return;
|
|
54702
|
+
}
|
|
54703
|
+
const organizationId = await resolveOrganizationId();
|
|
54704
|
+
const result = await authenticatedRequest(
|
|
54705
|
+
(client) => client.credentialLink.deleteCredentialLink({
|
|
54706
|
+
params: { organizationId, environmentId: options.env, credentialLinkId }
|
|
54707
|
+
})
|
|
54708
|
+
);
|
|
54709
|
+
if (result.status !== NO_CONTENT_STATUS) {
|
|
54710
|
+
if (result.status === 409 && readErrorFields(result.body).errorCode === "CREDENTIAL_LINKED_BY_RULE") {
|
|
54711
|
+
await failLinkedByRule(organizationId, options.env, result.body);
|
|
54712
|
+
}
|
|
54713
|
+
failLinkOp("unlink", result.status, result.body, credentialLinkId);
|
|
54714
|
+
}
|
|
54715
|
+
console.log(colorSuccess(`Unlinked credential link ${credentialLinkId}`));
|
|
54716
|
+
}
|
|
54717
|
+
function coveringRuleId(body) {
|
|
54718
|
+
if (typeof body !== "object" || body === null || !("details" in body)) return null;
|
|
54719
|
+
const { details } = body;
|
|
54720
|
+
if (typeof details !== "object" || details === null) return null;
|
|
54721
|
+
if (!("environmentCredentialSourceId" in details)) return null;
|
|
54722
|
+
const id = details.environmentCredentialSourceId;
|
|
54723
|
+
return typeof id === "string" && isUuid(id) ? id : null;
|
|
54724
|
+
}
|
|
54725
|
+
async function failLinkedByRule(organizationId, environmentId, body) {
|
|
54726
|
+
const { message } = readErrorFields(body);
|
|
54727
|
+
console.error(
|
|
54728
|
+
colorError(
|
|
54729
|
+
message ?? "All of the environment this credential comes from is linked here, so it stays."
|
|
54730
|
+
)
|
|
54731
|
+
);
|
|
54732
|
+
const ruleId = coveringRuleId(body);
|
|
54733
|
+
const rules = ruleId === null ? null : await fetchCredentialSources(organizationId, environmentId);
|
|
54734
|
+
const rule = rules?.find((r2) => r2.environmentCredentialSourceId === ruleId);
|
|
54735
|
+
console.error(
|
|
54736
|
+
rule ? `Unlink all of ${sourceDisplay(rule.source)} with: tracecli credential unlink-all ${rule.source.environmentId} --env ${environmentId}` : `See which environment with: tracecli credential links --env ${environmentId}`
|
|
54737
|
+
);
|
|
54738
|
+
process.exit(1);
|
|
54739
|
+
}
|
|
54740
|
+
async function fetchCredentialSources(organizationId, environmentId) {
|
|
54741
|
+
const result = await authenticatedRequestSafe(
|
|
54742
|
+
(client) => client.credentialLink.listCredentialSources({
|
|
54743
|
+
params: { organizationId, environmentId }
|
|
54744
|
+
})
|
|
54745
|
+
);
|
|
54746
|
+
if (result === null) return null;
|
|
54747
|
+
if (result.status === SUCCESS_STATUS) return result.body;
|
|
54748
|
+
if (result.status === 404) return [];
|
|
54749
|
+
return null;
|
|
54750
|
+
}
|
|
54751
|
+
function credentialSourceLines(rules) {
|
|
54752
|
+
return rules.map(
|
|
54753
|
+
(rule) => `All of ${sourceDisplay(rule.source)} is linked here, including credentials added there later.`
|
|
54754
|
+
);
|
|
54755
|
+
}
|
|
54756
|
+
async function runLinks(options) {
|
|
54757
|
+
const output = parseOutput(options.output);
|
|
54758
|
+
requireEnvId(options.env);
|
|
54759
|
+
const organizationId = await resolveOrganizationId();
|
|
54760
|
+
const result = await authenticatedRequest(
|
|
54761
|
+
(client) => client.credentialLink.listCredentialLinks({
|
|
54762
|
+
params: { organizationId, environmentId: options.env }
|
|
54763
|
+
})
|
|
54764
|
+
);
|
|
54765
|
+
if (result.status !== SUCCESS_STATUS) {
|
|
54766
|
+
failReadOp3("list credential links", result.status, result.body, options.env);
|
|
54767
|
+
}
|
|
54768
|
+
const links = result.body;
|
|
54769
|
+
if (output === "json") {
|
|
54770
|
+
printJson(links);
|
|
54771
|
+
return;
|
|
54772
|
+
}
|
|
54773
|
+
if (output === "tsv") {
|
|
54774
|
+
printCredentialLinksTsv(links);
|
|
54775
|
+
return;
|
|
54776
|
+
}
|
|
54777
|
+
const rules = await fetchCredentialSources(organizationId, options.env);
|
|
54778
|
+
if (rules === null) {
|
|
54779
|
+
console.error(
|
|
54780
|
+
colorError("Could not read which environments are linked whole; showing links only.")
|
|
54781
|
+
);
|
|
54782
|
+
}
|
|
54783
|
+
const ruleLines = credentialSourceLines(rules ?? []);
|
|
54784
|
+
if (links.length === 0) {
|
|
54785
|
+
if (ruleLines.length > 0) {
|
|
54786
|
+
for (const line of ruleLines) console.log(line);
|
|
54787
|
+
console.log("No credentials from there yet.");
|
|
54788
|
+
return;
|
|
54789
|
+
}
|
|
54790
|
+
console.log(
|
|
54791
|
+
`No linked credentials. See what you can link with: tracecli credential linkable --env ${options.env}`
|
|
54792
|
+
);
|
|
54793
|
+
return;
|
|
54794
|
+
}
|
|
54795
|
+
for (const line of ruleLines) console.log(line);
|
|
54796
|
+
if (ruleLines.length > 0) console.log("");
|
|
54797
|
+
const { header, rows } = buildCredentialLinksTable(links);
|
|
54798
|
+
await runInteractivePager({
|
|
54799
|
+
items: links,
|
|
54800
|
+
header,
|
|
54801
|
+
renderItem: (_link, i2) => [rows[i2] ?? ""],
|
|
54802
|
+
disabled: options.pager === false
|
|
54803
|
+
});
|
|
54804
|
+
}
|
|
54805
|
+
async function runLinkable(options) {
|
|
54806
|
+
const output = parseOutput(options.output);
|
|
54807
|
+
requireEnvId(options.env);
|
|
54808
|
+
const organizationId = await resolveOrganizationId();
|
|
54809
|
+
const result = await authenticatedRequest(
|
|
54810
|
+
(client) => client.credentialLink.listLinkableCredentials({
|
|
54811
|
+
params: { organizationId, environmentId: options.env }
|
|
54812
|
+
})
|
|
54813
|
+
);
|
|
54814
|
+
if (result.status !== SUCCESS_STATUS) {
|
|
54815
|
+
failReadOp3("list linkable credentials", result.status, result.body, options.env);
|
|
54816
|
+
}
|
|
54817
|
+
const creds = result.body;
|
|
54818
|
+
if (output === "json") {
|
|
54819
|
+
printJson(creds);
|
|
54820
|
+
return;
|
|
54821
|
+
}
|
|
54822
|
+
if (output === "tsv") {
|
|
54823
|
+
printLinkableCredentialsTsv(creds);
|
|
54824
|
+
return;
|
|
54825
|
+
}
|
|
54826
|
+
if (creds.length === 0) {
|
|
54827
|
+
console.log("No credentials elsewhere in this organization to link.");
|
|
54828
|
+
return;
|
|
54829
|
+
}
|
|
54830
|
+
const { header, rows } = buildLinkableCredentialsTable(creds);
|
|
54831
|
+
await runInteractivePager({
|
|
54832
|
+
items: creds,
|
|
54833
|
+
header,
|
|
54834
|
+
renderItem: (_cred, i2) => [rows[i2] ?? ""],
|
|
54835
|
+
disabled: options.pager === false
|
|
54836
|
+
});
|
|
54837
|
+
}
|
|
54838
|
+
function printLinkableEnvironmentsTsv(envs) {
|
|
54839
|
+
console.log(
|
|
54840
|
+
["environmentId", "application", "environment", "credentialCount", "alreadyLinked"].join(" ")
|
|
54841
|
+
);
|
|
54842
|
+
for (const env2 of envs) {
|
|
54843
|
+
console.log(
|
|
54844
|
+
[
|
|
54845
|
+
tsvSafe(env2.environmentId),
|
|
54846
|
+
tsvSafe(sanitizeDisplay(env2.application.name)),
|
|
54847
|
+
tsvSafe(sanitizeDisplay(env2.name)),
|
|
54848
|
+
tsvSafe(String(env2.credentialCount)),
|
|
54849
|
+
tsvSafe(String(env2.alreadyLinked))
|
|
54850
|
+
].join(" ")
|
|
54851
|
+
);
|
|
54852
|
+
}
|
|
54853
|
+
}
|
|
54854
|
+
function buildLinkableEnvironmentsTable(envs) {
|
|
54855
|
+
const cells = envs.map((env2) => ({
|
|
54856
|
+
id: env2.environmentId,
|
|
54857
|
+
name: sourceDisplay({ applicationName: env2.application.name, environmentName: env2.name }),
|
|
54858
|
+
count: String(env2.credentialCount),
|
|
54859
|
+
linked: env2.alreadyLinked ? "linked" : ""
|
|
54860
|
+
}));
|
|
54861
|
+
const idWidth = Math.max("ENVIRONMENT ID".length, ...cells.map((c84) => c84.id.length));
|
|
54862
|
+
const nameWidth = Math.max("ENVIRONMENT".length, ...cells.map((c84) => c84.name.length));
|
|
54863
|
+
const countWidth = "CREDENTIALS".length;
|
|
54864
|
+
const header = `${"ENVIRONMENT ID".padEnd(idWidth)} ${"ENVIRONMENT".padEnd(nameWidth)} ${"CREDENTIALS".padEnd(countWidth)} STATUS`;
|
|
54865
|
+
const rows = cells.map(
|
|
54866
|
+
(c84) => `${c84.id.padEnd(idWidth)} ${c84.name.padEnd(nameWidth)} ${c84.count.padEnd(countWidth)} ${c84.linked}`.trimEnd()
|
|
54867
|
+
);
|
|
54868
|
+
return { header, rows };
|
|
54869
|
+
}
|
|
54870
|
+
async function runLinkableEnvironments(options) {
|
|
54871
|
+
const output = parseOutput(options.output);
|
|
54872
|
+
requireEnvId(options.env);
|
|
54873
|
+
const organizationId = await resolveOrganizationId();
|
|
54874
|
+
const result = await authenticatedRequest(
|
|
54875
|
+
(client) => client.credentialLink.listLinkableEnvironments({
|
|
54876
|
+
params: { organizationId, environmentId: options.env }
|
|
54877
|
+
})
|
|
54878
|
+
);
|
|
54879
|
+
if (result.status !== SUCCESS_STATUS) {
|
|
54880
|
+
failReadOp3("list linkable environments", result.status, result.body, options.env);
|
|
54881
|
+
}
|
|
54882
|
+
const envs = result.body;
|
|
54883
|
+
if (output === "json") {
|
|
54884
|
+
printJson(envs);
|
|
54885
|
+
return;
|
|
54886
|
+
}
|
|
54887
|
+
if (output === "tsv") {
|
|
54888
|
+
printLinkableEnvironmentsTsv(envs);
|
|
54889
|
+
return;
|
|
54890
|
+
}
|
|
54891
|
+
if (envs.length === 0) {
|
|
54892
|
+
console.log("No other environments in this organization to link.");
|
|
54893
|
+
return;
|
|
54894
|
+
}
|
|
54895
|
+
const { header, rows } = buildLinkableEnvironmentsTable(envs);
|
|
54896
|
+
await runInteractivePager({
|
|
54897
|
+
items: envs,
|
|
54898
|
+
header,
|
|
54899
|
+
renderItem: (_env, i2) => [rows[i2] ?? ""],
|
|
54900
|
+
disabled: options.pager === false
|
|
54901
|
+
});
|
|
54902
|
+
}
|
|
54903
|
+
function requireSourceEnvId(sourceEnvironmentId) {
|
|
54904
|
+
if (!isUuid(sourceEnvironmentId)) {
|
|
54905
|
+
console.error(
|
|
54906
|
+
colorError(
|
|
54907
|
+
`Invalid source environment id '${sourceEnvironmentId}'. Expected an environment UUID.`
|
|
54908
|
+
)
|
|
54909
|
+
);
|
|
54910
|
+
process.exit(1);
|
|
54911
|
+
}
|
|
54912
|
+
}
|
|
54913
|
+
async function runLinkAll(sourceEnvironmentId, options) {
|
|
54914
|
+
const output = parseOutput(options.output);
|
|
54915
|
+
requireEnvId(options.env);
|
|
54916
|
+
requireSourceEnvId(sourceEnvironmentId);
|
|
54917
|
+
const organizationId = await resolveOrganizationId();
|
|
54918
|
+
const result = await authenticatedRequest(
|
|
54919
|
+
(client) => client.credentialLink.createCredentialSource({
|
|
54920
|
+
params: { organizationId, environmentId: options.env },
|
|
54921
|
+
body: { sourceEnvironmentId }
|
|
54922
|
+
})
|
|
54923
|
+
);
|
|
54924
|
+
if (result.status !== CREATED_STATUS) {
|
|
54925
|
+
failLinkOp("link all", result.status, result.body, sourceEnvironmentId);
|
|
54926
|
+
}
|
|
54927
|
+
const rule = result.body;
|
|
54928
|
+
if (output === "json") {
|
|
54929
|
+
printJson(rule);
|
|
54930
|
+
return;
|
|
54931
|
+
}
|
|
54932
|
+
if (output === "tsv") {
|
|
54933
|
+
printCredentialLinksTsv(rule.links);
|
|
54934
|
+
return;
|
|
54935
|
+
}
|
|
54936
|
+
const pad = 13;
|
|
54937
|
+
console.log(
|
|
54938
|
+
colorSuccess(
|
|
54939
|
+
`Linked all of ${sourceDisplay(rule.source)}. Credentials added there later show up here too.`
|
|
54940
|
+
)
|
|
54941
|
+
);
|
|
54942
|
+
console.log(
|
|
54943
|
+
[
|
|
54944
|
+
`${"Source:".padEnd(pad)} ${sourceDisplay(rule.source)} (${rule.source.environmentId})`,
|
|
54945
|
+
`${"Linked into:".padEnd(pad)} ${rule.environmentId}`,
|
|
54946
|
+
`${"New links:".padEnd(pad)} ${rule.links.length}`
|
|
54947
|
+
].join("\n")
|
|
54948
|
+
);
|
|
54949
|
+
}
|
|
54950
|
+
async function runUnlinkAll(sourceEnvironmentId, options) {
|
|
54951
|
+
requireEnvId(options.env);
|
|
54952
|
+
requireSourceEnvId(sourceEnvironmentId);
|
|
54953
|
+
const organizationId = await resolveOrganizationId();
|
|
54954
|
+
const listed = await authenticatedRequest(
|
|
54955
|
+
(client) => client.credentialLink.listCredentialSources({
|
|
54956
|
+
params: { organizationId, environmentId: options.env }
|
|
54957
|
+
})
|
|
54958
|
+
);
|
|
54959
|
+
if (listed.status !== SUCCESS_STATUS) {
|
|
54960
|
+
failReadOp3("look up the link-all", listed.status, listed.body, options.env);
|
|
54961
|
+
}
|
|
54962
|
+
const wanted = sourceEnvironmentId.toLowerCase();
|
|
54963
|
+
const rule = listed.body.find((r2) => r2.source.environmentId.toLowerCase() === wanted);
|
|
54964
|
+
if (!rule) {
|
|
54965
|
+
console.error(
|
|
54966
|
+
colorError(
|
|
54967
|
+
`Nothing from ${sourceEnvironmentId} is linked whole into this environment. See what is with: tracecli credential linkable --env ${options.env} --environments`
|
|
54968
|
+
)
|
|
54969
|
+
);
|
|
54970
|
+
process.exit(1);
|
|
54971
|
+
}
|
|
54972
|
+
const confirmed = await confirmAction(
|
|
54973
|
+
`Unlink all of ${sourceDisplay(rule.source)} from this environment (credentials linked on their own stay)?`,
|
|
54974
|
+
{ skip: options.yes, verb: "unlink" }
|
|
54975
|
+
);
|
|
54976
|
+
if (!confirmed) {
|
|
54977
|
+
console.log("Aborted.");
|
|
54978
|
+
return;
|
|
54979
|
+
}
|
|
54980
|
+
const result = await authenticatedRequest(
|
|
54981
|
+
(client) => client.credentialLink.deleteCredentialSource({
|
|
54982
|
+
params: {
|
|
54983
|
+
organizationId,
|
|
54984
|
+
environmentId: options.env,
|
|
54985
|
+
environmentCredentialSourceId: rule.environmentCredentialSourceId
|
|
54986
|
+
}
|
|
54987
|
+
})
|
|
54988
|
+
);
|
|
54989
|
+
if (result.status !== NO_CONTENT_STATUS) {
|
|
54990
|
+
failLinkOp("unlink all", result.status, result.body, rule.environmentCredentialSourceId);
|
|
54991
|
+
}
|
|
54992
|
+
console.log(
|
|
54993
|
+
colorSuccess(
|
|
54994
|
+
`Unlinked all of ${sourceDisplay(rule.source)}. Credentials linked on their own stay.`
|
|
54995
|
+
)
|
|
54996
|
+
);
|
|
54997
|
+
}
|
|
54177
54998
|
function addSecretInputOptions(command) {
|
|
54178
54999
|
return command.option("--secret-stdin", "Read the secret value from stdin").option("--secret-file <path>", "Read the secret value from a file").option("--secret-env <name>", "Read the secret value from an env var").option("--username-stdin", "Read the username from stdin").option("--username-file <path>", "Read the username from a file").option("--username-env <name>", "Read the username from an env var").option("--password-stdin", "Read the password from stdin").option("--password-file <path>", "Read the password from a file").option("--password-env <name>", "Read the password from an env var");
|
|
54179
55000
|
}
|
|
@@ -54284,6 +55105,89 @@ left \u2014 if the current window is nearly over, the API waits for the next one
|
|
|
54284
55105
|
await runMfaCode(credentialId, options);
|
|
54285
55106
|
});
|
|
54286
55107
|
}
|
|
55108
|
+
function createLinkCommand() {
|
|
55109
|
+
return new Command("link").description("Link another application's credential into an environment").argument("<credentialId>", "UUID of the credential to link (owned by another environment)").requiredOption("--env <environmentId>", "Environment UUID to link it into").option("-o, --output <format>", "Output format: table, json, tsv (default: table)", "table").addHelpText(
|
|
55110
|
+
"after",
|
|
55111
|
+
`
|
|
55112
|
+
A link holds no secret: the linked environment sees the source credential as it
|
|
55113
|
+
is now, so a rotation, MFA change, or relabel on the source shows up here too.
|
|
55114
|
+
Edit the credential where it lives (its own --env). The label must be free in
|
|
55115
|
+
the target environment, and the credential must not already belong to it.
|
|
55116
|
+
|
|
55117
|
+
Find candidates with: tracecli credential linkable --env <envId>
|
|
55118
|
+
To link every credential of an environment, now and as they are added, use
|
|
55119
|
+
link-all instead.
|
|
55120
|
+
|
|
55121
|
+
Examples:
|
|
55122
|
+
tracecli credential linkable --env <envId>
|
|
55123
|
+
tracecli credential link <credentialId> --env <envId>`
|
|
55124
|
+
).action(async (credentialId, options) => {
|
|
55125
|
+
await runLink(credentialId, options);
|
|
55126
|
+
});
|
|
55127
|
+
}
|
|
55128
|
+
function createUnlinkCommand() {
|
|
55129
|
+
return new Command("unlink").description("Remove a credential link from an environment (the source credential is kept)").argument("<credentialLinkId>", "Credential link UUID (the LINK ID from `credential links`)").requiredOption("--env <environmentId>", "Environment UUID the credential is linked into").option("-y, --yes", "Skip the confirmation prompt (required in non-interactive shells)").addHelpText(
|
|
55130
|
+
"after",
|
|
55131
|
+
`
|
|
55132
|
+
Blocked while a pentest using this environment is running, like deleting one of
|
|
55133
|
+
the environment's own credentials. A credential whose whole environment is
|
|
55134
|
+
linked here (VIA link-all in \`credential links\`) stays until that is removed
|
|
55135
|
+
with \`credential unlink-all\`.`
|
|
55136
|
+
).action(async (credentialLinkId, options) => {
|
|
55137
|
+
await runUnlink(credentialLinkId, options);
|
|
55138
|
+
});
|
|
55139
|
+
}
|
|
55140
|
+
function createLinksCommand() {
|
|
55141
|
+
return new Command("links").description(
|
|
55142
|
+
"List the credentials linked into an environment from elsewhere (secrets are never shown)"
|
|
55143
|
+
).requiredOption("--env <environmentId>", "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) => {
|
|
55144
|
+
await runLinks(options);
|
|
55145
|
+
});
|
|
55146
|
+
}
|
|
55147
|
+
function createLinkableCommand() {
|
|
55148
|
+
return new Command("linkable").description("List credentials elsewhere in the organization that an environment could link").requiredOption("--env <environmentId>", "Environment UUID to link into").option("--environments", "List whole environments to link-all instead of credentials").option("-o, --output <format>", "Output format: table, json, tsv (default: table)", "table").option("--no-pager", "Disable the interactive pager for table output").addHelpText(
|
|
55149
|
+
"after",
|
|
55150
|
+
`
|
|
55151
|
+
Rows already linked into this environment are marked "linked" (table) or
|
|
55152
|
+
alreadyLinked=true (tsv). With --environments, "linked" means all of that
|
|
55153
|
+
environment is linked here already.`
|
|
55154
|
+
).action(async (options) => {
|
|
55155
|
+
if (options.environments) {
|
|
55156
|
+
await runLinkableEnvironments(options);
|
|
55157
|
+
return;
|
|
55158
|
+
}
|
|
55159
|
+
await runLinkable(options);
|
|
55160
|
+
});
|
|
55161
|
+
}
|
|
55162
|
+
function createLinkAllCommand() {
|
|
55163
|
+
return new Command("link-all").description("Link every credential of another environment, including ones added there later").argument("<sourceEnvironmentId>", "UUID of the environment whose credentials to link").requiredOption("--env <environmentId>", "Environment UUID to link them into").option("-o, --output <format>", "Output format: table, json, tsv (default: table)", "table").addHelpText(
|
|
55164
|
+
"after",
|
|
55165
|
+
`
|
|
55166
|
+
Each credential shows here as a link, like \`credential link\`, and a credential
|
|
55167
|
+
added to the source later shows up here too. Edit them where they live. Every
|
|
55168
|
+
label must be free in this environment. Credentials you already linked on their
|
|
55169
|
+
own stay as they are.
|
|
55170
|
+
|
|
55171
|
+
Find environments with: tracecli credential linkable --env <envId> --environments
|
|
55172
|
+
|
|
55173
|
+
Examples:
|
|
55174
|
+
tracecli credential link-all <sourceEnvId> --env <envId>
|
|
55175
|
+
tracecli credential unlink-all <sourceEnvId> --env <envId>`
|
|
55176
|
+
).action(async (sourceEnvironmentId, options) => {
|
|
55177
|
+
await runLinkAll(sourceEnvironmentId, options);
|
|
55178
|
+
});
|
|
55179
|
+
}
|
|
55180
|
+
function createUnlinkAllCommand() {
|
|
55181
|
+
return new Command("unlink-all").description("Stop linking every credential of another environment into this one").argument("<sourceEnvironmentId>", "UUID of the environment passed to link-all").requiredOption("--env <environmentId>", "Environment UUID they are linked into").option("-y, --yes", "Skip the confirmation prompt (required in non-interactive shells)").addHelpText(
|
|
55182
|
+
"after",
|
|
55183
|
+
`
|
|
55184
|
+
Removes the links link-all made; the credentials stay in their own environment.
|
|
55185
|
+
Credentials you linked on their own stay linked. Blocked while a pentest using
|
|
55186
|
+
this environment is running.`
|
|
55187
|
+
).action(async (sourceEnvironmentId, options) => {
|
|
55188
|
+
await runUnlinkAll(sourceEnvironmentId, options);
|
|
55189
|
+
});
|
|
55190
|
+
}
|
|
54287
55191
|
function createCredentialCommand() {
|
|
54288
55192
|
const credential = new Command("credential").alias("cred").description("Manage environment credentials");
|
|
54289
55193
|
credential.addCommand(createCreateCommand3());
|
|
@@ -54294,6 +55198,12 @@ function createCredentialCommand() {
|
|
|
54294
55198
|
credential.addCommand(createSetMfaCommand());
|
|
54295
55199
|
credential.addCommand(createClearMfaCommand());
|
|
54296
55200
|
credential.addCommand(createMfaCodeCommand());
|
|
55201
|
+
credential.addCommand(createLinkCommand());
|
|
55202
|
+
credential.addCommand(createUnlinkCommand());
|
|
55203
|
+
credential.addCommand(createLinkAllCommand());
|
|
55204
|
+
credential.addCommand(createUnlinkAllCommand());
|
|
55205
|
+
credential.addCommand(createLinksCommand());
|
|
55206
|
+
credential.addCommand(createLinkableCommand());
|
|
54297
55207
|
credential.addCommand(createInboxCommand(), { hidden: true });
|
|
54298
55208
|
return credential;
|
|
54299
55209
|
}
|
|
@@ -54509,14 +55419,14 @@ function buildEnvironmentTable(envs, leading) {
|
|
|
54509
55419
|
url: e.primaryUrl != null ? sanitizeDisplay(e.primaryUrl) : "\u2014"
|
|
54510
55420
|
}));
|
|
54511
55421
|
const leadingWidths = leading.map(
|
|
54512
|
-
(column, i2) => Math.max(column.header.length, ...cells.map((
|
|
55422
|
+
(column, i2) => Math.max(column.header.length, ...cells.map((c84) => c84.leading[i2]?.length ?? 0))
|
|
54513
55423
|
);
|
|
54514
|
-
const nameWidth = Math.max(4, ...cells.map((
|
|
55424
|
+
const nameWidth = Math.max(4, ...cells.map((c84) => c84.name.length));
|
|
54515
55425
|
const padLeading = (values) => values.map((v, i2) => v.padEnd(leadingWidths[i2] ?? 0)).join(" ");
|
|
54516
55426
|
return {
|
|
54517
|
-
header: `${padLeading(leading.map((
|
|
55427
|
+
header: `${padLeading(leading.map((c84) => c84.header))} ${"NAME".padEnd(nameWidth)} PROD URL`,
|
|
54518
55428
|
rows: cells.map(
|
|
54519
|
-
(
|
|
55429
|
+
(c84) => `${padLeading(c84.leading)} ${c84.name.padEnd(nameWidth)} ${c84.prod.padEnd(4)} ${c84.url}`
|
|
54520
55430
|
)
|
|
54521
55431
|
};
|
|
54522
55432
|
}
|
|
@@ -54898,7 +55808,6 @@ var CLI_VERSION = packageMetadata.version;
|
|
|
54898
55808
|
import { readFileSync as readFileSync7 } from "fs";
|
|
54899
55809
|
|
|
54900
55810
|
// src/pentest/evidence.ts
|
|
54901
|
-
import { readFileSync as readFileSync5, statSync as statSync2 } from "fs";
|
|
54902
55811
|
import { basename as basename2 } from "path";
|
|
54903
55812
|
|
|
54904
55813
|
// src/pentest/resolve.ts
|
|
@@ -54973,6 +55882,67 @@ Run \`tracecli pentest list\` to see available pentests.`
|
|
|
54973
55882
|
return pentest.pentestId;
|
|
54974
55883
|
}
|
|
54975
55884
|
|
|
55885
|
+
// src/pentest/upload-file.ts
|
|
55886
|
+
import { readFileSync as readFileSync5, statSync as statSync2 } from "fs";
|
|
55887
|
+
function formatBytes2(bytes) {
|
|
55888
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
55889
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)} KB`;
|
|
55890
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
55891
|
+
}
|
|
55892
|
+
function statUploadFile(path3, limit) {
|
|
55893
|
+
let sizeBytes;
|
|
55894
|
+
try {
|
|
55895
|
+
const stats = statSync2(path3);
|
|
55896
|
+
if (!stats.isFile()) {
|
|
55897
|
+
console.error(colorError(`--file '${sanitizeDisplay(path3)}' is not a regular file.`));
|
|
55898
|
+
process.exit(1);
|
|
55899
|
+
}
|
|
55900
|
+
sizeBytes = stats.size;
|
|
55901
|
+
} catch {
|
|
55902
|
+
console.error(colorError(`Cannot read --file '${sanitizeDisplay(path3)}'.`));
|
|
55903
|
+
process.exit(1);
|
|
55904
|
+
}
|
|
55905
|
+
if (sizeBytes === 0) {
|
|
55906
|
+
console.error(colorError(`--file '${sanitizeDisplay(path3)}' is empty.`));
|
|
55907
|
+
process.exit(1);
|
|
55908
|
+
}
|
|
55909
|
+
if (sizeBytes > limit.bytes) {
|
|
55910
|
+
console.error(
|
|
55911
|
+
colorError(
|
|
55912
|
+
`File is ${formatBytes2(sizeBytes)}; the limit for one ${limit.noun} is ${formatBytes2(limit.bytes)}.`
|
|
55913
|
+
)
|
|
55914
|
+
);
|
|
55915
|
+
process.exit(1);
|
|
55916
|
+
}
|
|
55917
|
+
return sizeBytes;
|
|
55918
|
+
}
|
|
55919
|
+
async function putFileToPresignedUrl(uploadUrl, path3, options = {}) {
|
|
55920
|
+
let put;
|
|
55921
|
+
try {
|
|
55922
|
+
put = await fetch(uploadUrl, {
|
|
55923
|
+
method: "PUT",
|
|
55924
|
+
body: new Uint8Array(readFileSync5(path3)),
|
|
55925
|
+
...options.contentType === void 0 ? {} : { headers: { "Content-Type": options.contentType } }
|
|
55926
|
+
});
|
|
55927
|
+
} catch (error48) {
|
|
55928
|
+
const reason = error48 instanceof Error ? error48.message : "unknown error";
|
|
55929
|
+
console.error(
|
|
55930
|
+
colorError(
|
|
55931
|
+
`Upload to storage failed: could not reach storage (${sanitizeDisplay(reason)}). Check the connection and re-run the command.`
|
|
55932
|
+
)
|
|
55933
|
+
);
|
|
55934
|
+
process.exit(1);
|
|
55935
|
+
}
|
|
55936
|
+
if (!put.ok) {
|
|
55937
|
+
console.error(
|
|
55938
|
+
colorError(
|
|
55939
|
+
`Upload to storage failed (status ${put.status}). The upload link is short-lived; re-run the command.`
|
|
55940
|
+
)
|
|
55941
|
+
);
|
|
55942
|
+
process.exit(1);
|
|
55943
|
+
}
|
|
55944
|
+
}
|
|
55945
|
+
|
|
54976
55946
|
// src/pentest/evidence.ts
|
|
54977
55947
|
var SECTION_LIST = ValidationEvidenceSectionValues.join(", ");
|
|
54978
55948
|
var KIND_LIST = PentestExploitArtifactKindValues.join(", ");
|
|
@@ -54982,11 +55952,6 @@ function isSection(value) {
|
|
|
54982
55952
|
function isKind(value) {
|
|
54983
55953
|
return PentestExploitArtifactKindValues.includes(value);
|
|
54984
55954
|
}
|
|
54985
|
-
function formatBytes2(bytes) {
|
|
54986
|
-
if (bytes < 1024) return `${bytes} B`;
|
|
54987
|
-
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)} KB`;
|
|
54988
|
-
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
54989
|
-
}
|
|
54990
55955
|
async function resolveFindingId(organizationId, pentestId, identifier) {
|
|
54991
55956
|
if (isUuid(identifier)) {
|
|
54992
55957
|
return identifier;
|
|
@@ -55033,30 +55998,10 @@ async function runAttachEvidence(options) {
|
|
|
55033
55998
|
console.error(`Valid kinds: ${KIND_LIST}`);
|
|
55034
55999
|
process.exit(1);
|
|
55035
56000
|
}
|
|
55036
|
-
|
|
55037
|
-
|
|
55038
|
-
|
|
55039
|
-
|
|
55040
|
-
console.error(colorError(`--file '${sanitizeDisplay(options.file)}' is not a regular file.`));
|
|
55041
|
-
process.exit(1);
|
|
55042
|
-
}
|
|
55043
|
-
sizeBytes = stats.size;
|
|
55044
|
-
} catch {
|
|
55045
|
-
console.error(colorError(`Cannot read --file '${sanitizeDisplay(options.file)}'.`));
|
|
55046
|
-
process.exit(1);
|
|
55047
|
-
}
|
|
55048
|
-
if (sizeBytes === 0) {
|
|
55049
|
-
console.error(colorError(`--file '${sanitizeDisplay(options.file)}' is empty.`));
|
|
55050
|
-
process.exit(1);
|
|
55051
|
-
}
|
|
55052
|
-
if (sizeBytes > MAX_STAFF_EVIDENCE_UPLOAD_BYTES) {
|
|
55053
|
-
console.error(
|
|
55054
|
-
colorError(
|
|
55055
|
-
`File is ${formatBytes2(sizeBytes)}; the limit for one exhibit is ${formatBytes2(MAX_STAFF_EVIDENCE_UPLOAD_BYTES)}.`
|
|
55056
|
-
)
|
|
55057
|
-
);
|
|
55058
|
-
process.exit(1);
|
|
55059
|
-
}
|
|
56001
|
+
const sizeBytes = statUploadFile(options.file, {
|
|
56002
|
+
bytes: MAX_STAFF_EVIDENCE_UPLOAD_BYTES,
|
|
56003
|
+
noun: "exhibit"
|
|
56004
|
+
});
|
|
55060
56005
|
const organizationId = await resolveOrganizationId();
|
|
55061
56006
|
const pentestId = await resolvePentestId(organizationId, options.pentest);
|
|
55062
56007
|
const pentestFindingId = await resolveFindingId(organizationId, pentestId, options.finding);
|
|
@@ -55078,16 +56023,7 @@ async function runAttachEvidence(options) {
|
|
|
55078
56023
|
);
|
|
55079
56024
|
process.exit(1);
|
|
55080
56025
|
}
|
|
55081
|
-
|
|
55082
|
-
const put = await fetch(slot.body.uploadUrl, { method: "PUT", body: new Uint8Array(body) });
|
|
55083
|
-
if (!put.ok) {
|
|
55084
|
-
console.error(
|
|
55085
|
-
colorError(
|
|
55086
|
-
`Upload to storage failed (status ${put.status}). The upload link is short-lived \u2014 re-run the command.`
|
|
55087
|
-
)
|
|
55088
|
-
);
|
|
55089
|
-
process.exit(1);
|
|
55090
|
-
}
|
|
56026
|
+
await putFileToPresignedUrl(slot.body.uploadUrl, options.file);
|
|
55091
56027
|
const attached = await authenticatedRequest(
|
|
55092
56028
|
(client) => client.pentestFinding.attachFindingEvidence({
|
|
55093
56029
|
params: params3,
|
|
@@ -55896,8 +56832,8 @@ function requiredExtensionForKind(kind) {
|
|
|
55896
56832
|
return ".zip";
|
|
55897
56833
|
}
|
|
55898
56834
|
function defaultDisplayName(originalFilename) {
|
|
55899
|
-
const
|
|
55900
|
-
return
|
|
56835
|
+
const base3 = originalFilename.replace(/\.(zip|apk|ipa)$/i, "").trim();
|
|
56836
|
+
return base3.slice(0, 200) || "source";
|
|
55901
56837
|
}
|
|
55902
56838
|
function formatArchiveBytes(bytes) {
|
|
55903
56839
|
if (bytes < 1024) return `${bytes} B`;
|
|
@@ -56812,6 +57748,258 @@ function createAttestationCommand() {
|
|
|
56812
57748
|
return attestation;
|
|
56813
57749
|
}
|
|
56814
57750
|
|
|
57751
|
+
// src/pentest/documents.ts
|
|
57752
|
+
import { basename as basename5 } from "path";
|
|
57753
|
+
|
|
57754
|
+
// src/pentest/intake-errors.ts
|
|
57755
|
+
var WRITE_FORBIDDEN_MESSAGE = {
|
|
57756
|
+
questionnaire: "Questionnaire writes require an organization admin (or Trace staff) signed in with a browser session. API keys are rejected.",
|
|
57757
|
+
"questionnaire documents": "Questionnaire document writes require an organization admin (or Trace staff) signed in with a browser session. API keys are rejected."
|
|
57758
|
+
};
|
|
57759
|
+
function failIntakeRequest(status, body, options) {
|
|
57760
|
+
const { message } = readErrorFields(body);
|
|
57761
|
+
if (status === UNAUTHORIZED_STATUS2) {
|
|
57762
|
+
exitAuthFailed();
|
|
57763
|
+
}
|
|
57764
|
+
if (status === 403) {
|
|
57765
|
+
console.error(
|
|
57766
|
+
colorError(
|
|
57767
|
+
options.action === "write" ? WRITE_FORBIDDEN_MESSAGE[options.subject] : message ?? `You do not have permission to view this ${options.subject}.`
|
|
57768
|
+
)
|
|
57769
|
+
);
|
|
57770
|
+
process.exit(1);
|
|
57771
|
+
}
|
|
57772
|
+
if (status === 404) {
|
|
57773
|
+
console.error(
|
|
57774
|
+
colorError(
|
|
57775
|
+
options.notFound === void 0 ? message ?? "Not found." : `${options.notFound.what} not found: ${sanitizeDisplay(options.notFound.label)}.`
|
|
57776
|
+
)
|
|
57777
|
+
);
|
|
57778
|
+
process.exit(1);
|
|
57779
|
+
}
|
|
57780
|
+
if (status === 400) {
|
|
57781
|
+
console.error(colorError(message ?? `Invalid ${options.subject} request (status 400).`));
|
|
57782
|
+
process.exit(1);
|
|
57783
|
+
}
|
|
57784
|
+
if (status === 409) {
|
|
57785
|
+
console.error(colorError(message ?? QUESTIONNAIRE_LOCKED_MESSAGE));
|
|
57786
|
+
process.exit(1);
|
|
57787
|
+
}
|
|
57788
|
+
console.error(
|
|
57789
|
+
colorError(
|
|
57790
|
+
`Failed to ${options.action === "write" ? "save" : "load"} ${options.subject} (status ${status})${message ? `: ${message}` : "."}`
|
|
57791
|
+
)
|
|
57792
|
+
);
|
|
57793
|
+
process.exit(1);
|
|
57794
|
+
}
|
|
57795
|
+
|
|
57796
|
+
// src/pentest/documents.ts
|
|
57797
|
+
var TYPE_LIST = PentestIntakeDocumentTypeValues.join(", ");
|
|
57798
|
+
function isDocumentType(value) {
|
|
57799
|
+
return PentestIntakeDocumentTypeValues.includes(value);
|
|
57800
|
+
}
|
|
57801
|
+
function renderDocumentLines(doc) {
|
|
57802
|
+
const uploader = doc.uploadedByName ? ` by ${sanitizeDisplay(doc.uploadedByName)}` : "";
|
|
57803
|
+
return [
|
|
57804
|
+
` ${sanitizeDisplay(doc.fileName)}`,
|
|
57805
|
+
` ${PENTEST_INTAKE_DOCUMENT_TYPE_LABELS[doc.documentType]} | ${formatBytes2(doc.fileSizeBytes)} | uploaded${uploader} on ${formatDate(doc.createdAt)}`,
|
|
57806
|
+
` id ${doc.documentId}`
|
|
57807
|
+
];
|
|
57808
|
+
}
|
|
57809
|
+
var TSV_HEADER = "documentId documentType fileName fileType fileSizeBytes uploadedByName createdAt";
|
|
57810
|
+
async function runList5(options) {
|
|
57811
|
+
const output = parseOutput(options.output);
|
|
57812
|
+
const organizationId = await resolveOrganizationId();
|
|
57813
|
+
const pentestId = await resolvePentestId(organizationId, options.pentest);
|
|
57814
|
+
const result = await authenticatedRequest(
|
|
57815
|
+
(client) => client.pentestIntakeDocument.listDocuments({ params: { organizationId, pentestId } })
|
|
57816
|
+
);
|
|
57817
|
+
if (result.status !== SUCCESS_STATUS) {
|
|
57818
|
+
failIntakeRequest(result.status, result.body, {
|
|
57819
|
+
subject: "questionnaire documents",
|
|
57820
|
+
action: "read",
|
|
57821
|
+
notFound: { what: "Pentest", label: options.pentest }
|
|
57822
|
+
});
|
|
57823
|
+
}
|
|
57824
|
+
const { editable, documents } = result.body;
|
|
57825
|
+
if (output === "json") {
|
|
57826
|
+
printJson({ editable, documents });
|
|
57827
|
+
return;
|
|
57828
|
+
}
|
|
57829
|
+
if (output === "tsv") {
|
|
57830
|
+
console.log(TSV_HEADER);
|
|
57831
|
+
for (const doc of documents) {
|
|
57832
|
+
console.log(
|
|
57833
|
+
[
|
|
57834
|
+
doc.documentId,
|
|
57835
|
+
doc.documentType,
|
|
57836
|
+
tsvSafe(sanitizeDisplay(doc.fileName)),
|
|
57837
|
+
tsvSafe(sanitizeDisplay(doc.fileType)),
|
|
57838
|
+
String(doc.fileSizeBytes),
|
|
57839
|
+
tsvSafe(doc.uploadedByName === null ? null : sanitizeDisplay(doc.uploadedByName)),
|
|
57840
|
+
toIso(doc.createdAt)
|
|
57841
|
+
].join(" ")
|
|
57842
|
+
);
|
|
57843
|
+
}
|
|
57844
|
+
return;
|
|
57845
|
+
}
|
|
57846
|
+
console.log(`Editable: ${editable ? "yes" : "no"}`);
|
|
57847
|
+
if (documents.length === 0) {
|
|
57848
|
+
console.log("No documents attached.");
|
|
57849
|
+
return;
|
|
57850
|
+
}
|
|
57851
|
+
console.log(`${documents.length} document${documents.length === 1 ? "" : "s"}:`);
|
|
57852
|
+
for (const doc of documents) {
|
|
57853
|
+
for (const line of renderDocumentLines(doc)) {
|
|
57854
|
+
console.log(line);
|
|
57855
|
+
}
|
|
57856
|
+
}
|
|
57857
|
+
}
|
|
57858
|
+
async function runAttach(options) {
|
|
57859
|
+
const output = parseTextOutput(options.output);
|
|
57860
|
+
if (options.type !== void 0 && !isDocumentType(options.type)) {
|
|
57861
|
+
console.error(colorError(`Invalid --type '${sanitizeDisplay(options.type)}'.`));
|
|
57862
|
+
console.error(`Valid types: ${TYPE_LIST}`);
|
|
57863
|
+
process.exit(1);
|
|
57864
|
+
}
|
|
57865
|
+
const sizeBytes = statUploadFile(options.file, {
|
|
57866
|
+
bytes: MAX_PENTEST_INTAKE_DOCUMENT_BYTES,
|
|
57867
|
+
noun: "document"
|
|
57868
|
+
});
|
|
57869
|
+
const fileName = basename5(options.file);
|
|
57870
|
+
const documentType = options.type !== void 0 && isDocumentType(options.type) ? options.type : guessPentestIntakeDocumentType(fileName);
|
|
57871
|
+
const organizationId = await resolveOrganizationId();
|
|
57872
|
+
const pentestId = await resolvePentestId(organizationId, options.pentest);
|
|
57873
|
+
const params3 = { organizationId, pentestId };
|
|
57874
|
+
const slot = await authenticatedRequest(
|
|
57875
|
+
(client) => client.pentestIntakeDocument.createUpload({
|
|
57876
|
+
params: params3,
|
|
57877
|
+
body: { fileName, fileSizeBytes: sizeBytes }
|
|
57878
|
+
})
|
|
57879
|
+
);
|
|
57880
|
+
if (slot.status !== CREATED_STATUS) {
|
|
57881
|
+
failIntakeRequest(slot.status, slot.body, {
|
|
57882
|
+
subject: "questionnaire documents",
|
|
57883
|
+
action: "write",
|
|
57884
|
+
notFound: { what: "Pentest", label: options.pentest }
|
|
57885
|
+
});
|
|
57886
|
+
}
|
|
57887
|
+
const fileType = pentestIntakeDocumentMimeType(fileName);
|
|
57888
|
+
await putFileToPresignedUrl(slot.body.uploadUrl, options.file, { contentType: fileType });
|
|
57889
|
+
const attached = await authenticatedRequest(
|
|
57890
|
+
(client) => client.pentestIntakeDocument.attachDocument({
|
|
57891
|
+
params: params3,
|
|
57892
|
+
body: {
|
|
57893
|
+
uploadKey: slot.body.uploadKey,
|
|
57894
|
+
documentType,
|
|
57895
|
+
fileName,
|
|
57896
|
+
fileType
|
|
57897
|
+
}
|
|
57898
|
+
})
|
|
57899
|
+
);
|
|
57900
|
+
if (attached.status !== CREATED_STATUS) {
|
|
57901
|
+
failIntakeRequest(attached.status, attached.body, {
|
|
57902
|
+
subject: "questionnaire documents",
|
|
57903
|
+
action: "write",
|
|
57904
|
+
notFound: { what: "Pentest", label: options.pentest }
|
|
57905
|
+
});
|
|
57906
|
+
}
|
|
57907
|
+
if (output === "json") {
|
|
57908
|
+
printJson(attached.body);
|
|
57909
|
+
return;
|
|
57910
|
+
}
|
|
57911
|
+
console.log(
|
|
57912
|
+
colorSuccess(
|
|
57913
|
+
`Attached ${sanitizeDisplay(attached.body.fileName)} (${PENTEST_INTAKE_DOCUMENT_TYPE_LABELS[attached.body.documentType]}, ${formatBytes2(attached.body.fileSizeBytes)})`
|
|
57914
|
+
)
|
|
57915
|
+
);
|
|
57916
|
+
console.log(` id ${attached.body.documentId}`);
|
|
57917
|
+
}
|
|
57918
|
+
async function runRemove(documentId, options) {
|
|
57919
|
+
if (!isUuid(documentId)) {
|
|
57920
|
+
console.error(
|
|
57921
|
+
colorError(
|
|
57922
|
+
`Invalid document ID '${sanitizeDisplay(documentId)}'. Pass the UUID from \`tracecli pentest documents list\`.`
|
|
57923
|
+
)
|
|
57924
|
+
);
|
|
57925
|
+
process.exit(1);
|
|
57926
|
+
}
|
|
57927
|
+
const organizationId = await resolveOrganizationId();
|
|
57928
|
+
const pentestId = await resolvePentestId(organizationId, options.pentest);
|
|
57929
|
+
const confirmed = await confirmDeletion(`document ${documentId}`, options.yes === true);
|
|
57930
|
+
if (!confirmed) {
|
|
57931
|
+
console.log("Cancelled.");
|
|
57932
|
+
return;
|
|
57933
|
+
}
|
|
57934
|
+
const result = await authenticatedRequest(
|
|
57935
|
+
(client) => client.pentestIntakeDocument.deleteDocument({
|
|
57936
|
+
params: { organizationId, pentestId, documentId }
|
|
57937
|
+
})
|
|
57938
|
+
);
|
|
57939
|
+
if (result.status !== NO_CONTENT_STATUS) {
|
|
57940
|
+
failIntakeRequest(result.status, result.body, {
|
|
57941
|
+
subject: "questionnaire documents",
|
|
57942
|
+
action: "write",
|
|
57943
|
+
notFound: { what: "Document", label: documentId }
|
|
57944
|
+
});
|
|
57945
|
+
}
|
|
57946
|
+
console.log(colorSuccess(`Removed document ${documentId}.`));
|
|
57947
|
+
}
|
|
57948
|
+
function createListCommand6() {
|
|
57949
|
+
return new Command("list").description("List documents attached to the intake questionnaire").requiredOption("--pentest <id>", "Pentest display ID (e.g. PT-3) or UUID").option("-o, --output <format>", "Output format: table, json, tsv (default: table)", "table").addHelpText(
|
|
57950
|
+
"after",
|
|
57951
|
+
`
|
|
57952
|
+
Examples:
|
|
57953
|
+
tracecli pentest documents list --pentest PT-1
|
|
57954
|
+
tracecli pentest documents list --pentest PT-1 --output json`
|
|
57955
|
+
).action(async (options) => {
|
|
57956
|
+
await runList5(options);
|
|
57957
|
+
});
|
|
57958
|
+
}
|
|
57959
|
+
function createAttachCommand() {
|
|
57960
|
+
return new Command("attach").description(
|
|
57961
|
+
"Attach a document (prior report, architecture diagram, threat model) to the questionnaire"
|
|
57962
|
+
).requiredOption("--pentest <id>", "Pentest display ID (e.g. PT-3) or UUID").requiredOption("--file <path>", "Path to the document").option(
|
|
57963
|
+
"--type <type>",
|
|
57964
|
+
`Document type (${TYPE_LIST}); guessed from the file name when omitted`
|
|
57965
|
+
).option("-o, --output <format>", "Output format: text, json (default: text)", "text").addHelpText(
|
|
57966
|
+
"after",
|
|
57967
|
+
`
|
|
57968
|
+
Answers the questionnaire's "documents to share" question. Same edit window as
|
|
57969
|
+
the answers: allowed until testing starts. Every documents command needs a
|
|
57970
|
+
browser session from \`tracecli auth login\`; API keys are rejected. Attach and
|
|
57971
|
+
remove also need an organization admin (or Trace staff).
|
|
57972
|
+
Up to ${formatBytes2(MAX_PENTEST_INTAKE_DOCUMENT_BYTES)} per file.
|
|
57973
|
+
|
|
57974
|
+
Examples:
|
|
57975
|
+
tracecli pentest documents attach --pentest PT-1 --file ./threat-model.pdf
|
|
57976
|
+
tracecli pentest documents attach --pentest PT-1 --file ./q3-report.pdf --type PREVIOUS_PENTEST
|
|
57977
|
+
tracecli pentest documents attach --pentest PT-1 --file ./arch.png --output json`
|
|
57978
|
+
).action(async (options) => {
|
|
57979
|
+
await runAttach(options);
|
|
57980
|
+
});
|
|
57981
|
+
}
|
|
57982
|
+
function createRemoveCommand() {
|
|
57983
|
+
return new Command("remove").description("Remove a document from the intake questionnaire").argument("<documentId>", "Document UUID (from `documents list`)").requiredOption("--pentest <id>", "Pentest display ID (e.g. PT-3) or UUID").option("-y, --yes", "Skip the confirmation prompt").addHelpText(
|
|
57984
|
+
"after",
|
|
57985
|
+
`
|
|
57986
|
+
Examples:
|
|
57987
|
+
tracecli pentest documents remove 3f1c... --pentest PT-1
|
|
57988
|
+
tracecli pentest documents remove 3f1c... --pentest PT-1 --yes`
|
|
57989
|
+
).action(async (documentId, options) => {
|
|
57990
|
+
await runRemove(documentId, options);
|
|
57991
|
+
});
|
|
57992
|
+
}
|
|
57993
|
+
function createDocumentsCommand() {
|
|
57994
|
+
const documents = new Command("documents").description(
|
|
57995
|
+
"Documents attached to the pentest intake questionnaire"
|
|
57996
|
+
);
|
|
57997
|
+
documents.addCommand(createListCommand6());
|
|
57998
|
+
documents.addCommand(createAttachCommand());
|
|
57999
|
+
documents.addCommand(createRemoveCommand());
|
|
58000
|
+
return documents;
|
|
58001
|
+
}
|
|
58002
|
+
|
|
56815
58003
|
// src/pentest/get-finding.ts
|
|
56816
58004
|
var OUTPUT_FORMATS2 = ["text", "json"];
|
|
56817
58005
|
function isOutputFormat2(value) {
|
|
@@ -57261,7 +58449,10 @@ function parseQuestionnaireAnswersFile(raw, sourceLabel = "file") {
|
|
|
57261
58449
|
error: `${sourceLabel} must contain the answers object directly, not an { answers } wrapper. The CLI wraps it for the API.`
|
|
57262
58450
|
};
|
|
57263
58451
|
}
|
|
57264
|
-
const
|
|
58452
|
+
const withoutNulls = Object.fromEntries(
|
|
58453
|
+
Object.entries(parsed).filter(([, value]) => value !== null)
|
|
58454
|
+
);
|
|
58455
|
+
const result = PentestCustomerIntakeAnswersSchema.safeParse(withoutNulls);
|
|
57265
58456
|
if (!result.success) {
|
|
57266
58457
|
const issue2 = result.error.issues[0];
|
|
57267
58458
|
const path3 = issue2?.path.length ? issue2.path.join(".") : "answers";
|
|
@@ -57286,6 +58477,12 @@ function applyMultiSelectIntakeAnswer(current, key, selected) {
|
|
|
57286
58477
|
[key]: Array.from(new Set(selected)).sort()
|
|
57287
58478
|
};
|
|
57288
58479
|
}
|
|
58480
|
+
function mergeIntakeAnswers(base3, patch) {
|
|
58481
|
+
return normalizePentestCustomerIntakeAnswers({
|
|
58482
|
+
...base3 ?? {},
|
|
58483
|
+
...patch
|
|
58484
|
+
});
|
|
58485
|
+
}
|
|
57289
58486
|
function formatIssueClasses(values) {
|
|
57290
58487
|
return values.map((v) => INTAKE_ISSUE_CLASS_LABELS[v]).join(", ");
|
|
57291
58488
|
}
|
|
@@ -57317,22 +58514,113 @@ function formatAnswerValue(key, answers) {
|
|
|
57317
58514
|
}
|
|
57318
58515
|
}
|
|
57319
58516
|
}
|
|
58517
|
+
var NOT_ANSWERED = "(not answered)";
|
|
57320
58518
|
function formatQuestionnaireShow(input) {
|
|
57321
58519
|
const submitted = input.submitted === true;
|
|
57322
58520
|
const editableLine = input.editable ? "yes" : input.lockedReason === "ENGAGEMENT_ENDED" ? "no (engagement has ended)" : input.lockedReason === "TESTING_STARTED" ? "no (testing has started)" : "no";
|
|
57323
|
-
const
|
|
57324
|
-
|
|
57325
|
-
|
|
57326
|
-
|
|
57327
|
-
|
|
57328
|
-
|
|
58521
|
+
const answers = input.answers ?? {};
|
|
58522
|
+
const lines = [
|
|
58523
|
+
`Editable: ${editableLine}`,
|
|
58524
|
+
`Submitted: ${submitted ? "yes" : "no"}`,
|
|
58525
|
+
`Answers: ${countAnsweredIntakeQuestions(answers)} of ${PENTEST_CUSTOMER_INTAKE_QUESTION_COUNT} questions filled`
|
|
58526
|
+
];
|
|
57329
58527
|
for (const key of PENTEST_CUSTOMER_INTAKE_ANSWER_KEYS) {
|
|
57330
|
-
const value = formatAnswerValue(key,
|
|
57331
|
-
|
|
57332
|
-
|
|
58528
|
+
const value = formatAnswerValue(key, answers) ?? NOT_ANSWERED;
|
|
58529
|
+
lines.push(`${PENTEST_CUSTOMER_INTAKE_LABELS[key]} (${key}): ${value}`);
|
|
58530
|
+
}
|
|
58531
|
+
return lines.join("\n");
|
|
58532
|
+
}
|
|
58533
|
+
function buildQuestionnaireExport(answers) {
|
|
58534
|
+
const n3 = normalizePentestCustomerIntakeAnswers(answers ?? {});
|
|
58535
|
+
const text = (value) => value === void 0 || value.trim() === "" ? null : value;
|
|
58536
|
+
const list = (value) => value === void 0 || value.length === 0 ? null : [...value];
|
|
58537
|
+
return {
|
|
58538
|
+
issueClasses: list(n3.issueClasses),
|
|
58539
|
+
issueClassesNotes: text(n3.issueClassesNotes),
|
|
58540
|
+
focusAreas: text(n3.focusAreas),
|
|
58541
|
+
rolesAndPermissions: text(n3.rolesAndPermissions),
|
|
58542
|
+
authenticationSetup: text(n3.authenticationSetup),
|
|
58543
|
+
tenantIsolationExpectation: text(n3.tenantIsolationExpectation),
|
|
58544
|
+
surfaces: list(n3.surfaces),
|
|
58545
|
+
surfacesNotes: text(n3.surfacesNotes),
|
|
58546
|
+
thirdParties: text(n3.thirdParties),
|
|
58547
|
+
sensitiveData: text(n3.sensitiveData),
|
|
58548
|
+
neverTouch: text(n3.neverTouch),
|
|
58549
|
+
knownRisks: text(n3.knownRisks),
|
|
58550
|
+
tribalKnowledge: text(n3.tribalKnowledge)
|
|
58551
|
+
};
|
|
58552
|
+
}
|
|
58553
|
+
function buildQuestionnaireRecordJson(input) {
|
|
58554
|
+
const toIsoString = (value) => typeof value === "string" ? value : value.toISOString();
|
|
58555
|
+
return {
|
|
58556
|
+
pentestId: input.pentestId,
|
|
58557
|
+
editable: input.editable,
|
|
58558
|
+
lockedReason: input.lockedReason ?? null,
|
|
58559
|
+
submitted: input.submittedAt != null,
|
|
58560
|
+
submittedAt: input.submittedAt == null ? null : toIsoString(input.submittedAt),
|
|
58561
|
+
updatedAt: toIsoString(input.updatedAt),
|
|
58562
|
+
answers: buildQuestionnaireExport(input.answers)
|
|
58563
|
+
};
|
|
58564
|
+
}
|
|
58565
|
+
var DOCUMENTS_ATTACH_COMMAND = "tracecli pentest documents attach --pentest <pentestId> --file <path>";
|
|
58566
|
+
function buildQuestionnaireCatalog() {
|
|
58567
|
+
const questions = PENTEST_CUSTOMER_INTAKE_QUESTIONS.map(
|
|
58568
|
+
(entry, index) => {
|
|
58569
|
+
if (entry.kind === "text") {
|
|
58570
|
+
return {
|
|
58571
|
+
number: index + 1,
|
|
58572
|
+
key: entry.key,
|
|
58573
|
+
kind: "text",
|
|
58574
|
+
question: entry.question,
|
|
58575
|
+
helper: entry.helper ?? null,
|
|
58576
|
+
options: null,
|
|
58577
|
+
notes: null
|
|
58578
|
+
};
|
|
58579
|
+
}
|
|
58580
|
+
return {
|
|
58581
|
+
number: index + 1,
|
|
58582
|
+
key: entry.key,
|
|
58583
|
+
kind: "multiSelect",
|
|
58584
|
+
question: entry.question,
|
|
58585
|
+
helper: entry.helper ?? null,
|
|
58586
|
+
options: entry.key === "issueClasses" ? issueClassSelectOptions() : surfaceSelectOptions(),
|
|
58587
|
+
notes: { key: entry.notesKey, question: entry.notesQuestion }
|
|
58588
|
+
};
|
|
58589
|
+
}
|
|
58590
|
+
);
|
|
58591
|
+
return {
|
|
58592
|
+
questions,
|
|
58593
|
+
documents: {
|
|
58594
|
+
question: PENTEST_INTAKE_DOCUMENTS_QUESTION,
|
|
58595
|
+
command: DOCUMENTS_ATTACH_COMMAND,
|
|
58596
|
+
types: PentestIntakeDocumentTypeValues.map((value) => ({
|
|
58597
|
+
value,
|
|
58598
|
+
label: PENTEST_INTAKE_DOCUMENT_TYPE_LABELS[value]
|
|
58599
|
+
}))
|
|
58600
|
+
}
|
|
58601
|
+
};
|
|
58602
|
+
}
|
|
58603
|
+
function formatQuestionnaireCatalog(catalog) {
|
|
58604
|
+
const lines = [];
|
|
58605
|
+
for (const entry of catalog.questions) {
|
|
58606
|
+
lines.push(`${entry.number}. ${entry.question}`);
|
|
58607
|
+
if (entry.helper !== null) {
|
|
58608
|
+
lines.push(` ${entry.helper}`);
|
|
57333
58609
|
}
|
|
57334
|
-
lines.push(
|
|
58610
|
+
lines.push(` key: ${entry.key} (${entry.kind === "text" ? "text" : "multi-select"})`);
|
|
58611
|
+
if (entry.options !== null) {
|
|
58612
|
+
lines.push(` options: ${entry.options.map((o2) => `${o2.value} (${o2.label})`).join(", ")}`);
|
|
58613
|
+
}
|
|
58614
|
+
if (entry.notes !== null) {
|
|
58615
|
+
lines.push(` notes key: ${entry.notes.key} (text): ${entry.notes.question}`);
|
|
58616
|
+
}
|
|
58617
|
+
lines.push("");
|
|
57335
58618
|
}
|
|
58619
|
+
lines.push(`${catalog.questions.length + 1}. ${catalog.documents.question}`);
|
|
58620
|
+
lines.push(` Files, not an answers key. Attach with: ${catalog.documents.command}`);
|
|
58621
|
+
lines.push(
|
|
58622
|
+
` types: ${catalog.documents.types.map((t2) => `${t2.value} (${t2.label})`).join(", ")}`
|
|
58623
|
+
);
|
|
57336
58624
|
return lines.join("\n");
|
|
57337
58625
|
}
|
|
57338
58626
|
function issueClassSelectOptions() {
|
|
@@ -57431,7 +58719,7 @@ function formatFindingSeverity(severity) {
|
|
|
57431
58719
|
return severity;
|
|
57432
58720
|
}
|
|
57433
58721
|
}
|
|
57434
|
-
function
|
|
58722
|
+
function createListCommand7() {
|
|
57435
58723
|
return new Command("list").description("List pentests for your organization").option(
|
|
57436
58724
|
"--status <status>",
|
|
57437
58725
|
"Filter by status (SCOPING, IN_PROGRESS, REMEDIATION, CLOSED)",
|
|
@@ -57994,46 +59282,6 @@ Examples:
|
|
|
57994
59282
|
await runUpdate5(pentestId, options);
|
|
57995
59283
|
});
|
|
57996
59284
|
}
|
|
57997
|
-
function failQuestionnaire(status, body, action, notFoundLabel) {
|
|
57998
|
-
const { message } = readErrorFields(body);
|
|
57999
|
-
if (status === UNAUTHORIZED_STATUS2) {
|
|
58000
|
-
exitAuthFailed();
|
|
58001
|
-
}
|
|
58002
|
-
if (status === 403) {
|
|
58003
|
-
if (action === "write") {
|
|
58004
|
-
console.error(
|
|
58005
|
-
colorError(
|
|
58006
|
-
"Questionnaire writes require an organization admin (or Trace staff) signed in with a browser session. API keys are rejected."
|
|
58007
|
-
)
|
|
58008
|
-
);
|
|
58009
|
-
} else {
|
|
58010
|
-
console.error(
|
|
58011
|
-
colorError(message ?? "You do not have permission to view this questionnaire.")
|
|
58012
|
-
);
|
|
58013
|
-
}
|
|
58014
|
-
process.exit(1);
|
|
58015
|
-
}
|
|
58016
|
-
if (status === 404) {
|
|
58017
|
-
console.error(
|
|
58018
|
-
colorError(notFoundLabel ? `Pentest not found: ${notFoundLabel}.` : message ?? "Not found.")
|
|
58019
|
-
);
|
|
58020
|
-
process.exit(1);
|
|
58021
|
-
}
|
|
58022
|
-
if (status === 400) {
|
|
58023
|
-
console.error(colorError(message ?? "Invalid questionnaire answers (status 400)."));
|
|
58024
|
-
process.exit(1);
|
|
58025
|
-
}
|
|
58026
|
-
if (status === 409) {
|
|
58027
|
-
console.error(colorError(message ?? QUESTIONNAIRE_LOCKED_MESSAGE));
|
|
58028
|
-
process.exit(1);
|
|
58029
|
-
}
|
|
58030
|
-
console.error(
|
|
58031
|
-
colorError(
|
|
58032
|
-
`Failed to ${action === "write" ? "save" : "load"} questionnaire (status ${status})${message ? `: ${message}` : "."}`
|
|
58033
|
-
)
|
|
58034
|
-
);
|
|
58035
|
-
process.exit(1);
|
|
58036
|
-
}
|
|
58037
59285
|
async function fetchQuestionnaire(organizationId, pentestId, notFoundLabel) {
|
|
58038
59286
|
const result = await authenticatedRequest(
|
|
58039
59287
|
(client) => client.pentest.getCustomerPentestQuestionnaire({
|
|
@@ -58041,7 +59289,11 @@ async function fetchQuestionnaire(organizationId, pentestId, notFoundLabel) {
|
|
|
58041
59289
|
})
|
|
58042
59290
|
);
|
|
58043
59291
|
if (result.status !== SUCCESS_STATUS) {
|
|
58044
|
-
|
|
59292
|
+
failIntakeRequest(result.status, result.body, {
|
|
59293
|
+
subject: "questionnaire",
|
|
59294
|
+
action: "read",
|
|
59295
|
+
notFound: { what: "Pentest", label: notFoundLabel }
|
|
59296
|
+
});
|
|
58045
59297
|
}
|
|
58046
59298
|
return toQuestionnaireGetBody(result.body);
|
|
58047
59299
|
}
|
|
@@ -58054,7 +59306,11 @@ async function putQuestionnaire(organizationId, pentestId, answers, notFoundLabe
|
|
|
58054
59306
|
})
|
|
58055
59307
|
);
|
|
58056
59308
|
if (result.status !== SUCCESS_STATUS) {
|
|
58057
|
-
|
|
59309
|
+
failIntakeRequest(result.status, result.body, {
|
|
59310
|
+
subject: "questionnaire",
|
|
59311
|
+
action: "write",
|
|
59312
|
+
notFound: { what: "Pentest", label: notFoundLabel }
|
|
59313
|
+
});
|
|
58058
59314
|
}
|
|
58059
59315
|
return toQuestionnaireGetBody(result.body);
|
|
58060
59316
|
}
|
|
@@ -58066,7 +59322,11 @@ async function submitQuestionnaire(organizationId, pentestId, answers, notFoundL
|
|
|
58066
59322
|
})
|
|
58067
59323
|
);
|
|
58068
59324
|
if (result.status !== SUCCESS_STATUS) {
|
|
58069
|
-
|
|
59325
|
+
failIntakeRequest(result.status, result.body, {
|
|
59326
|
+
subject: "questionnaire",
|
|
59327
|
+
action: "write",
|
|
59328
|
+
notFound: { what: "Pentest", label: notFoundLabel }
|
|
59329
|
+
});
|
|
58070
59330
|
}
|
|
58071
59331
|
return toQuestionnaireGetBody(result.body);
|
|
58072
59332
|
}
|
|
@@ -58149,35 +59409,85 @@ async function promptQuestionnaireAnswers(existing) {
|
|
|
58149
59409
|
}
|
|
58150
59410
|
return working;
|
|
58151
59411
|
}
|
|
58152
|
-
|
|
58153
|
-
const
|
|
58154
|
-
|
|
58155
|
-
|
|
58156
|
-
|
|
58157
|
-
|
|
59412
|
+
function resolveQuestionnaireMode(options) {
|
|
59413
|
+
const exclusive = [
|
|
59414
|
+
{ flag: "--show", set: options.show === true },
|
|
59415
|
+
{ flag: "--questions", set: options.questions === true },
|
|
59416
|
+
{ flag: "--export", set: options.export === true },
|
|
59417
|
+
{ flag: "--file", set: options.file !== void 0 }
|
|
59418
|
+
];
|
|
59419
|
+
const chosen = exclusive.filter((entry) => entry.set);
|
|
59420
|
+
if (chosen.length > 1) {
|
|
59421
|
+
console.error(colorError(`Pass only one of ${chosen.map((c84) => c84.flag).join(" / ")}.`));
|
|
59422
|
+
process.exit(1);
|
|
59423
|
+
}
|
|
59424
|
+
const only = chosen[0];
|
|
59425
|
+
if (only !== void 0 && only.flag !== "--file" && options.submit === true) {
|
|
59426
|
+
console.error(colorError(`Pass only one of ${only.flag} / --submit.`));
|
|
58158
59427
|
process.exit(1);
|
|
58159
59428
|
}
|
|
58160
|
-
if (
|
|
58161
|
-
console.error(colorError("
|
|
59429
|
+
if (options.merge === true && options.file === void 0) {
|
|
59430
|
+
console.error(colorError("--merge needs --file <path>."));
|
|
58162
59431
|
process.exit(1);
|
|
58163
59432
|
}
|
|
59433
|
+
if (options.show) return { kind: "show" };
|
|
59434
|
+
if (options.questions) return { kind: "questions" };
|
|
59435
|
+
if (options.export) return { kind: "export" };
|
|
59436
|
+
if (options.file !== void 0) {
|
|
59437
|
+
return { kind: "file", path: options.file, merge: options.merge === true };
|
|
59438
|
+
}
|
|
59439
|
+
if (options.submit) return { kind: "submit" };
|
|
59440
|
+
return { kind: "interactive" };
|
|
59441
|
+
}
|
|
59442
|
+
function printQuestionnaireRecord(body, output, successLine) {
|
|
59443
|
+
if (output === "json") {
|
|
59444
|
+
printJson(buildQuestionnaireRecordJson(body));
|
|
59445
|
+
return;
|
|
59446
|
+
}
|
|
59447
|
+
if (successLine !== void 0) {
|
|
59448
|
+
console.log(colorSuccess(successLine));
|
|
59449
|
+
}
|
|
59450
|
+
console.log(formatQuestionnaireShow(questionnaireShowInput(body)));
|
|
59451
|
+
}
|
|
59452
|
+
function submittedLine(record2) {
|
|
59453
|
+
return record2.editable ? "Submitted questionnaire. You can edit until testing starts." : "Submitted questionnaire.";
|
|
59454
|
+
}
|
|
59455
|
+
async function runQuestionnaire(pentestIdentifier, options) {
|
|
59456
|
+
const mode = resolveQuestionnaireMode(options);
|
|
59457
|
+
const output = parseTextOutput(options.output);
|
|
59458
|
+
const submit = options.submit === true;
|
|
59459
|
+
if (mode.kind === "questions") {
|
|
59460
|
+
const catalog = buildQuestionnaireCatalog();
|
|
59461
|
+
if (output === "json") {
|
|
59462
|
+
printJson(catalog);
|
|
59463
|
+
} else {
|
|
59464
|
+
console.log(formatQuestionnaireCatalog(catalog));
|
|
59465
|
+
}
|
|
59466
|
+
return;
|
|
59467
|
+
}
|
|
58164
59468
|
const isInteractiveTty = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
58165
|
-
if (
|
|
59469
|
+
if (mode.kind === "interactive" && !isInteractiveTty) {
|
|
58166
59470
|
console.error(
|
|
58167
59471
|
colorError(
|
|
58168
|
-
"Interactive questionnaire requires a TTY.
|
|
59472
|
+
"Interactive questionnaire requires a TTY. Without one: --questions lists every question with its answer key, --export prints the current answers as JSON (blanks as null), --file <path> --merge uploads only the keys the file sets, --show prints the saved state, --submit marks it done. --file without --merge replaces every saved answer, so keys missing or null in the file are cleared."
|
|
58169
59473
|
)
|
|
58170
59474
|
);
|
|
58171
59475
|
process.exit(2);
|
|
58172
59476
|
}
|
|
58173
59477
|
const organizationId = await resolveOrganizationId();
|
|
58174
59478
|
const pentestId = await resolvePentestId(organizationId, pentestIdentifier);
|
|
58175
|
-
if (show) {
|
|
59479
|
+
if (mode.kind === "show") {
|
|
59480
|
+
const current2 = await fetchQuestionnaire(organizationId, pentestId, pentestIdentifier);
|
|
59481
|
+
printQuestionnaireRecord(current2, output);
|
|
59482
|
+
return;
|
|
59483
|
+
}
|
|
59484
|
+
if (mode.kind === "export") {
|
|
58176
59485
|
const current2 = await fetchQuestionnaire(organizationId, pentestId, pentestIdentifier);
|
|
58177
|
-
|
|
59486
|
+
printJson(buildQuestionnaireExport(current2.answers));
|
|
58178
59487
|
return;
|
|
58179
59488
|
}
|
|
58180
|
-
if (
|
|
59489
|
+
if (mode.kind === "file") {
|
|
59490
|
+
const filePath = mode.path;
|
|
58181
59491
|
let raw;
|
|
58182
59492
|
try {
|
|
58183
59493
|
raw = readFileSync7(filePath, "utf8");
|
|
@@ -58190,28 +59500,27 @@ async function runQuestionnaire(pentestIdentifier, options) {
|
|
|
58190
59500
|
console.error(colorError(parsed.error));
|
|
58191
59501
|
process.exit(1);
|
|
58192
59502
|
}
|
|
58193
|
-
|
|
58194
|
-
|
|
58195
|
-
|
|
58196
|
-
|
|
58197
|
-
|
|
59503
|
+
let answers = parsed.answers;
|
|
59504
|
+
if (mode.merge) {
|
|
59505
|
+
const current2 = await fetchQuestionnaire(organizationId, pentestId, pentestIdentifier);
|
|
59506
|
+
answers = mergeIntakeAnswers(current2.answers, parsed.answers);
|
|
59507
|
+
}
|
|
59508
|
+
const saved2 = submit ? await submitQuestionnaire(organizationId, pentestId, answers, pentestIdentifier) : await putQuestionnaire(organizationId, pentestId, answers, pentestIdentifier);
|
|
59509
|
+
printQuestionnaireRecord(
|
|
59510
|
+
saved2,
|
|
59511
|
+
output,
|
|
59512
|
+
submit ? submittedLine(saved2) : "Saved questionnaire answers."
|
|
58198
59513
|
);
|
|
58199
|
-
console.log(formatQuestionnaireShow(questionnaireShowInput(saved2)));
|
|
58200
59514
|
return;
|
|
58201
59515
|
}
|
|
58202
|
-
if (submit) {
|
|
59516
|
+
if (mode.kind === "submit") {
|
|
58203
59517
|
const submitted = await submitQuestionnaire(
|
|
58204
59518
|
organizationId,
|
|
58205
59519
|
pentestId,
|
|
58206
59520
|
void 0,
|
|
58207
59521
|
pentestIdentifier
|
|
58208
59522
|
);
|
|
58209
|
-
|
|
58210
|
-
colorSuccess(
|
|
58211
|
-
submitted.editable ? "Submitted questionnaire. You can edit until testing starts." : "Submitted questionnaire."
|
|
58212
|
-
)
|
|
58213
|
-
);
|
|
58214
|
-
console.log(formatQuestionnaireShow(questionnaireShowInput(submitted)));
|
|
59523
|
+
printQuestionnaireRecord(submitted, output, submittedLine(submitted));
|
|
58215
59524
|
return;
|
|
58216
59525
|
}
|
|
58217
59526
|
const current = await fetchQuestionnaire(organizationId, pentestId, pentestIdentifier);
|
|
@@ -58225,32 +59534,48 @@ async function runQuestionnaire(pentestIdentifier, options) {
|
|
|
58225
59534
|
}
|
|
58226
59535
|
const merged = await promptQuestionnaireAnswers(current.answers);
|
|
58227
59536
|
const saved = await putQuestionnaire(organizationId, pentestId, merged, pentestIdentifier);
|
|
58228
|
-
|
|
58229
|
-
console.log(formatQuestionnaireShow(questionnaireShowInput(saved)));
|
|
59537
|
+
printQuestionnaireRecord(saved, output, "Saved questionnaire answers.");
|
|
58230
59538
|
}
|
|
58231
59539
|
function createQuestionnaireCommand() {
|
|
58232
|
-
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
|
|
59540
|
+
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 every question with its answer key and current answer (GET)").option(
|
|
59541
|
+
"--questions",
|
|
59542
|
+
"Print the question catalog: keys, kinds, option values (no auth needed)"
|
|
59543
|
+
).option("--export", "Print the current answers as JSON, every key present, blanks as null").option(
|
|
58233
59544
|
"--file <path>",
|
|
58234
|
-
"Upload answers from a JSON file (answers object
|
|
58235
|
-
).option(
|
|
59545
|
+
"Upload answers from a JSON file (the answers object itself). Replaces every saved answer: keys missing or null in the file are cleared unless you add --merge"
|
|
59546
|
+
).option(
|
|
59547
|
+
"--merge",
|
|
59548
|
+
"With --file: change only the keys the file sets; keep every other saved answer"
|
|
59549
|
+
).option("--submit", "Mark the questionnaire as done (still editable until testing starts)").option("-o, --output <format>", "Output format: text, json (default: text)", "text").addHelpText(
|
|
58236
59550
|
"after",
|
|
58237
59551
|
`
|
|
58238
|
-
Optional scoping questionnaire
|
|
58239
|
-
browser session
|
|
58240
|
-
|
|
58241
|
-
|
|
59552
|
+
Optional scoping questionnaire, editable until testing starts.
|
|
59553
|
+
Every mode but --questions needs a browser session from \`tracecli auth login\`;
|
|
59554
|
+
API keys are rejected. Writes (--file, --submit, the TTY fill) also need an
|
|
59555
|
+
organization admin (or Trace staff).
|
|
58242
59556
|
|
|
58243
59557
|
Modes:
|
|
58244
|
-
--
|
|
58245
|
-
--
|
|
59558
|
+
--questions The question catalog with answer keys and option values
|
|
59559
|
+
--show GET and print every question with its key and current answer
|
|
59560
|
+
--export GET and print the answers object as JSON (all keys, null when blank)
|
|
59561
|
+
--file <path> PUT the file's answers object, replacing every saved answer;
|
|
59562
|
+
add --merge to change only the keys the file sets
|
|
58246
59563
|
--submit Mark as done (combine with --file or TTY fill, or alone)
|
|
58247
59564
|
(TTY default) load-merge-write: GET, prompt each question, confirm, PUT
|
|
58248
59565
|
|
|
58249
|
-
Non-interactive shells must pass
|
|
59566
|
+
Every mode takes -o json. Non-interactive shells must pass one of the flags
|
|
59567
|
+
above (exit 2 otherwise).
|
|
59568
|
+
|
|
59569
|
+
Scripted fill (what a coding agent runs):
|
|
59570
|
+
tracecli pentest questionnaire PT-1 --questions
|
|
59571
|
+
tracecli pentest questionnaire PT-1 --export > answers.json
|
|
59572
|
+
# fill in the null values the code can answer, then
|
|
59573
|
+
tracecli pentest questionnaire PT-1 --file answers.json --merge
|
|
59574
|
+
tracecli pentest documents attach --pentest PT-1 --file ./threat-model.pdf
|
|
58250
59575
|
|
|
58251
59576
|
Examples:
|
|
58252
59577
|
tracecli pentest questionnaire PT-1 --show
|
|
58253
|
-
tracecli pentest questionnaire PT-1 --
|
|
59578
|
+
tracecli pentest questionnaire PT-1 --show -o json
|
|
58254
59579
|
tracecli pentest questionnaire PT-1 --file answers.json --submit
|
|
58255
59580
|
tracecli pentest questionnaire PT-1 --submit
|
|
58256
59581
|
tracecli pentest questionnaire PT-1`
|
|
@@ -58260,11 +59585,12 @@ Examples:
|
|
|
58260
59585
|
}
|
|
58261
59586
|
function createPentestCommand() {
|
|
58262
59587
|
const pentest = new Command("pentest").description("Manage pentests");
|
|
58263
|
-
pentest.addCommand(
|
|
59588
|
+
pentest.addCommand(createListCommand7());
|
|
58264
59589
|
pentest.addCommand(createUpdateCommand4());
|
|
58265
59590
|
pentest.addCommand(createCreatePentestCommand(), { hidden: true });
|
|
58266
59591
|
pentest.addCommand(createVisibilityCommand(), { hidden: true });
|
|
58267
59592
|
pentest.addCommand(createQuestionnaireCommand());
|
|
59593
|
+
pentest.addCommand(createDocumentsCommand());
|
|
58268
59594
|
pentest.addCommand(createFindingsCommand());
|
|
58269
59595
|
pentest.addCommand(createHypothesesCommand(), { hidden: true });
|
|
58270
59596
|
pentest.addCommand(createReportCommand());
|
|
@@ -58901,7 +60227,7 @@ Examples:
|
|
|
58901
60227
|
// src/upload/download.ts
|
|
58902
60228
|
import { createHash as createHash2 } from "crypto";
|
|
58903
60229
|
import { open as open3, rename as rename2, rm as rm2, stat as stat2 } from "fs/promises";
|
|
58904
|
-
import { basename as
|
|
60230
|
+
import { basename as basename6, join as join8, resolve as resolve2 } from "path";
|
|
58905
60231
|
var CodeUploadDownloadError = class extends Error {
|
|
58906
60232
|
constructor(message) {
|
|
58907
60233
|
super(message);
|
|
@@ -58911,7 +60237,7 @@ var CodeUploadDownloadError = class extends Error {
|
|
|
58911
60237
|
function safeDownloadFilename(upload) {
|
|
58912
60238
|
const extension = requiredExtensionForKind(upload.artifactKind);
|
|
58913
60239
|
const fallback = `app${extension}`;
|
|
58914
|
-
const stripped = Array.from(
|
|
60240
|
+
const stripped = Array.from(basename6(upload.originalFilename.replace(/\\/g, "/")), (char) => {
|
|
58915
60241
|
const code = char.codePointAt(0) ?? 0;
|
|
58916
60242
|
return code <= 31 || code === 127 ? "_" : char;
|
|
58917
60243
|
}).join("").trim();
|
|
@@ -59326,13 +60652,13 @@ function buildUploadsTable(uploads) {
|
|
|
59326
60652
|
size: formatArchiveBytes(upload.sizeBytes),
|
|
59327
60653
|
id: upload.codeUploadId
|
|
59328
60654
|
}));
|
|
59329
|
-
const nameWidth = Math.max(4, ...cells.map((
|
|
59330
|
-
const kindWidth = Math.max(4, ...cells.map((
|
|
59331
|
-
const statusWidth = Math.max(6, ...cells.map((
|
|
59332
|
-
const sizeWidth = Math.max(4, ...cells.map((
|
|
60655
|
+
const nameWidth = Math.max(4, ...cells.map((c84) => c84.name.length));
|
|
60656
|
+
const kindWidth = Math.max(4, ...cells.map((c84) => c84.kind.length));
|
|
60657
|
+
const statusWidth = Math.max(6, ...cells.map((c84) => c84.status.length));
|
|
60658
|
+
const sizeWidth = Math.max(4, ...cells.map((c84) => c84.size.length));
|
|
59333
60659
|
const header = `${"NAME".padEnd(nameWidth)} ${"KIND".padEnd(kindWidth)} ${"STATUS".padEnd(statusWidth)} ${"SIZE".padEnd(sizeWidth)} ID`;
|
|
59334
60660
|
const rows = cells.map(
|
|
59335
|
-
(
|
|
60661
|
+
(c84) => `${c84.name.padEnd(nameWidth)} ${c84.kind.padEnd(kindWidth)} ${c84.status.padEnd(statusWidth)} ${c84.size.padEnd(sizeWidth)} ${c84.id}`
|
|
59336
60662
|
);
|
|
59337
60663
|
return { header, rows };
|
|
59338
60664
|
}
|
|
@@ -59686,7 +61012,7 @@ async function runResume(codeUploadIdArg, filePath, options, runtime) {
|
|
|
59686
61012
|
}
|
|
59687
61013
|
outputUpload(upload, output);
|
|
59688
61014
|
}
|
|
59689
|
-
async function
|
|
61015
|
+
async function runList6(options) {
|
|
59690
61016
|
const output = parseOutput(options.output);
|
|
59691
61017
|
if (options.web) {
|
|
59692
61018
|
const slug = await resolveOrganizationSlug();
|
|
@@ -59819,7 +61145,7 @@ async function runDelete5(codeUploadIdArg, options) {
|
|
|
59819
61145
|
}
|
|
59820
61146
|
console.log(colorSuccess(`Deleted upload ${codeUploadId}`));
|
|
59821
61147
|
}
|
|
59822
|
-
async function
|
|
61148
|
+
async function runLink2(options) {
|
|
59823
61149
|
const output = parseOutput(options.output);
|
|
59824
61150
|
const applicationId = requireUuid(options.app, "--app");
|
|
59825
61151
|
const codeUploadId = requireUuid(options.upload, "--upload");
|
|
@@ -59901,9 +61227,9 @@ Examples:
|
|
|
59901
61227
|
await runBuild(file2, options, runtime);
|
|
59902
61228
|
});
|
|
59903
61229
|
}
|
|
59904
|
-
function
|
|
61230
|
+
function createListCommand8() {
|
|
59905
61231
|
return new Command("list").description("List source and build uploads in the active organization").option("--kind <kind>", "Filter: source (code ZIP), apk, or ipa").option("--app <applicationId>", "Filter build uploads for one application").option("-o, --output <format>", "Output format: table, json, tsv (default: table)", "table").option("--no-pager", "Disable the interactive pager for table output").option("-w, --web", "Open the source uploads page in a browser").action(async (options) => {
|
|
59906
|
-
await
|
|
61232
|
+
await runList6(options);
|
|
59907
61233
|
});
|
|
59908
61234
|
}
|
|
59909
61235
|
function createGetCommand4() {
|
|
@@ -59951,7 +61277,7 @@ function createDeleteCommand5() {
|
|
|
59951
61277
|
await runDelete5(codeUploadId, options);
|
|
59952
61278
|
});
|
|
59953
61279
|
}
|
|
59954
|
-
function
|
|
61280
|
+
function createLinkCommand2() {
|
|
59955
61281
|
return new Command("link").description("Link a ready source upload to an application").requiredOption("--app <applicationId>", "Application UUID").requiredOption("--upload <codeUploadId>", "Upload UUID (must be a ready source ZIP)").option("--path <repositoryPath>", "Path within the archive").option("-o, --output <format>", "Output format: table, json, tsv (default: table)", "table").addHelpText(
|
|
59956
61282
|
"after",
|
|
59957
61283
|
`
|
|
@@ -59961,7 +61287,7 @@ APK and IPA builds attach to an application with \`upload build --app\`.
|
|
|
59961
61287
|
Examples:
|
|
59962
61288
|
tracecli upload link --app <applicationId> --upload <codeUploadId>`
|
|
59963
61289
|
).action(async (options) => {
|
|
59964
|
-
await
|
|
61290
|
+
await runLink2(options);
|
|
59965
61291
|
});
|
|
59966
61292
|
}
|
|
59967
61293
|
function createUploadCommand(runtime = {}) {
|
|
@@ -59969,13 +61295,13 @@ function createUploadCommand(runtime = {}) {
|
|
|
59969
61295
|
const upload = new Command("upload").alias("code-upload").description("Upload source archives and mobile builds for pentest; download builds back");
|
|
59970
61296
|
upload.addCommand(createCreateCommand5(resolved));
|
|
59971
61297
|
upload.addCommand(createBuildCommand(resolved));
|
|
59972
|
-
upload.addCommand(
|
|
61298
|
+
upload.addCommand(createListCommand8());
|
|
59973
61299
|
upload.addCommand(createGetCommand4());
|
|
59974
61300
|
upload.addCommand(createDownloadCommand(resolved));
|
|
59975
61301
|
upload.addCommand(createResumeCommand(resolved));
|
|
59976
61302
|
upload.addCommand(createWaitCommand(resolved));
|
|
59977
61303
|
upload.addCommand(createDeleteCommand5());
|
|
59978
|
-
upload.addCommand(
|
|
61304
|
+
upload.addCommand(createLinkCommand2());
|
|
59979
61305
|
return upload;
|
|
59980
61306
|
}
|
|
59981
61307
|
|
|
@@ -60214,7 +61540,7 @@ Run \`tracecli vulnerabilities list\` to see available vulnerabilities.`
|
|
|
60214
61540
|
printVulnerabilityDetail(result.body);
|
|
60215
61541
|
});
|
|
60216
61542
|
}
|
|
60217
|
-
function
|
|
61543
|
+
function createListCommand9() {
|
|
60218
61544
|
return new Command("list").description("List vulnerabilities for your organization").option("--repo <repo>", "Filter by repository name or owner/repo").option(
|
|
60219
61545
|
"--severity <severity>",
|
|
60220
61546
|
"Filter by severity (CRITICAL, HIGH, MEDIUM, LOW)",
|
|
@@ -60348,7 +61674,7 @@ Examples:
|
|
|
60348
61674
|
function createVulnerabilitiesCommand() {
|
|
60349
61675
|
const vulnerabilities = new Command("vulnerabilities").description("Manage vulnerabilities");
|
|
60350
61676
|
vulnerabilities.addCommand(createGetCommand5());
|
|
60351
|
-
vulnerabilities.addCommand(
|
|
61677
|
+
vulnerabilities.addCommand(createListCommand9());
|
|
60352
61678
|
return vulnerabilities;
|
|
60353
61679
|
}
|
|
60354
61680
|
|