securewithtrace 0.49.0 → 0.51.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 +1659 -248
- 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,7 +23189,8 @@ 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);
|
|
23178
23196
|
var AgentSessionStatusValues = [
|
|
@@ -23227,6 +23245,7 @@ var MAX_AGENT_SESSION_MAX_USD = 1500;
|
|
|
23227
23245
|
var MAX_AGENT_SESSION_MAX_WALL_CLOCK_MIN = 24 * 60;
|
|
23228
23246
|
var MAX_AGENT_SESSION_FOCUS_CHARS = 8e3;
|
|
23229
23247
|
var MAX_AGENT_SESSION_FOCUS_BRIEF_CHARS = MAX_AGENT_SESSION_FOCUS_CHARS * 3;
|
|
23248
|
+
var MAX_SETUP_NOTES_CHARS = 8e3;
|
|
23230
23249
|
var OperatorSessionPurpose = external_exports.enum(OPERATOR_PURPOSES);
|
|
23231
23250
|
var CreateAgentSessionCommonSchema = external_exports.object({
|
|
23232
23251
|
provider: AgentSessionProvider.optional(),
|
|
@@ -23337,7 +23356,16 @@ var CreateAgentSessionRequestSchema = external_exports.discriminatedUnion("type"
|
|
|
23337
23356
|
CreateAgentSessionCommonSchema.extend({
|
|
23338
23357
|
type: external_exports.literal("API_COVERAGE"),
|
|
23339
23358
|
focus: external_exports.string().min(1).max(MAX_AGENT_SESSION_FOCUS_CHARS).optional()
|
|
23340
|
-
})
|
|
23359
|
+
}),
|
|
23360
|
+
// SETUP: sets up a PoC engagement's application, environment, exclusions and tenant
|
|
23361
|
+
// accounts from the setup notes. Strict, so a client still sending the removed `focus`,
|
|
23362
|
+
// `tenants` or `shareLink` gets a 400 instead of a launch that silently lost its input.
|
|
23363
|
+
CreateAgentSessionCommonSchema.extend({
|
|
23364
|
+
type: external_exports.literal("SETUP"),
|
|
23365
|
+
/** What the customer sent and what staff want: environments, rules, logins, share links.
|
|
23366
|
+
* Stored encrypted and never returned by any read route. */
|
|
23367
|
+
notes: external_exports.string().trim().min(1).max(MAX_SETUP_NOTES_CHARS).optional()
|
|
23368
|
+
}).strict()
|
|
23341
23369
|
]);
|
|
23342
23370
|
var SendAgentSessionMessageRequestSchema = external_exports.object({
|
|
23343
23371
|
clientRequestId: external_exports.uuid().optional(),
|
|
@@ -23516,6 +23544,9 @@ var OperatorPentestContextSchema = external_exports.object({
|
|
|
23516
23544
|
customerPriorityCategories: external_exports.array(external_exports.string()),
|
|
23517
23545
|
/** Staff scope notes; defaulted so an older API's envelope still parses. */
|
|
23518
23546
|
scopeNotes: external_exports.string().nullable().default(null),
|
|
23547
|
+
/** The rendered staff-notes block (heading and never-quote line included), or null when the
|
|
23548
|
+
* engagement has none or this session's type never receives them. Defaulted like `scopeNotes`. */
|
|
23549
|
+
staffNotes: external_exports.string().nullable().default(null),
|
|
23519
23550
|
/** The customer's intake answers, rendered as child briefs carry them. */
|
|
23520
23551
|
customerIntake: external_exports.string().nullable().default(null),
|
|
23521
23552
|
status: PentestStatus,
|
|
@@ -27462,7 +27493,17 @@ var CustomerPentestFindingRepositorySchema = PentestFindingRepositorySchema.omit
|
|
|
27462
27493
|
repositoryId: true
|
|
27463
27494
|
});
|
|
27464
27495
|
var CustomerFindingPendingRetestSchema = external_exports.discriminatedUnion("state", [
|
|
27465
|
-
external_exports.object({
|
|
27496
|
+
external_exports.object({
|
|
27497
|
+
state: external_exports.literal("QUEUED"),
|
|
27498
|
+
since: external_exports.coerce.date(),
|
|
27499
|
+
/**
|
|
27500
|
+
* Present only while the retest waits on a completed ticket's delay: when
|
|
27501
|
+
* it becomes due. A field on QUEUED rather than its own state because prod
|
|
27502
|
+
* promotes web after the API and web parses these responses, so a new
|
|
27503
|
+
* variant would fail the parse in the web still serving.
|
|
27504
|
+
*/
|
|
27505
|
+
startsAt: external_exports.coerce.date().optional()
|
|
27506
|
+
}),
|
|
27466
27507
|
external_exports.object({ state: external_exports.literal("RUNNING"), since: external_exports.coerce.date() }),
|
|
27467
27508
|
/**
|
|
27468
27509
|
* A mobile-only engagement whose finding has no usable build newer than its
|
|
@@ -28442,6 +28483,9 @@ var PentestResponseSchema = external_exports.object({
|
|
|
28442
28483
|
/** Customer threat-model context — free text. Used by hypothesis-gen prompt. */
|
|
28443
28484
|
customerConcernsContext: external_exports.string().nullable(),
|
|
28444
28485
|
customerPriorityCategories: external_exports.array(CustomerPriorityCategory),
|
|
28486
|
+
/** Staff-only session guidance. Staff response only. `.optional()` for the same
|
|
28487
|
+
* rolling-deploy reason as `purchaseCapUsd`: the panel row reads Unavailable without it. */
|
|
28488
|
+
staffNotes: external_exports.string().nullable().optional(),
|
|
28445
28489
|
hypothesisBudgetCapUsd: external_exports.number(),
|
|
28446
28490
|
validationBudgetCapUsd: external_exports.number(),
|
|
28447
28491
|
perHypothesisBudgetCapUsd: external_exports.number(),
|
|
@@ -28667,6 +28711,8 @@ var CreatePentestRequestSchema = external_exports.object({
|
|
|
28667
28711
|
perHypothesisBudgetCapUsd: external_exports.number().min(0).max(1e5).optional(),
|
|
28668
28712
|
purchaseCapUsd: external_exports.number().min(0).max(1e5).optional()
|
|
28669
28713
|
});
|
|
28714
|
+
var STAFF_NOTES_MAX_CHARS = 1e4;
|
|
28715
|
+
var STAFF_NOTES_BRIEF_MAX_CHARS = STAFF_NOTES_MAX_CHARS * 3;
|
|
28670
28716
|
var UpdatePentestRequestSchema = external_exports.object({
|
|
28671
28717
|
title: external_exports.string().min(1).max(500).optional(),
|
|
28672
28718
|
description: external_exports.string().max(1e4).nullable().optional(),
|
|
@@ -28688,6 +28734,8 @@ var UpdatePentestRequestSchema = external_exports.object({
|
|
|
28688
28734
|
testType: PentestTestType.optional(),
|
|
28689
28735
|
customerConcernsContext: external_exports.string().max(2e4).nullable().optional(),
|
|
28690
28736
|
customerPriorityCategories: external_exports.array(CustomerPriorityCategory).max(20).optional(),
|
|
28737
|
+
/** Staff-only guidance for this engagement's agent sessions; `null` clears. */
|
|
28738
|
+
staffNotes: external_exports.string().max(STAFF_NOTES_MAX_CHARS).nullable().optional(),
|
|
28691
28739
|
/** Report-cover engagement overrides. `null` clears (reverts to auto-derivation),
|
|
28692
28740
|
* `undefined` leaves unchanged. Empty SUT is rejected — use `null` to clear. */
|
|
28693
28741
|
systemUnderTest: external_exports.string().trim().min(1).max(200).nullable().optional(),
|
|
@@ -28816,13 +28864,13 @@ var StorageStateSchema = external_exports.object({
|
|
|
28816
28864
|
} catch {
|
|
28817
28865
|
return;
|
|
28818
28866
|
}
|
|
28819
|
-
s.cookies.forEach((
|
|
28820
|
-
const d =
|
|
28867
|
+
s.cookies.forEach((c84, i2) => {
|
|
28868
|
+
const d = c84.domain.replace(/^\./, "").toLowerCase();
|
|
28821
28869
|
if (d !== host && !host.endsWith(`.${d}`)) {
|
|
28822
28870
|
ctx.addIssue({
|
|
28823
28871
|
code: "custom",
|
|
28824
28872
|
path: ["cookies", i2, "domain"],
|
|
28825
|
-
message: `cookie domain "${
|
|
28873
|
+
message: `cookie domain "${c84.domain}" is not the capture origin host "${host}" or a parent domain of it`
|
|
28826
28874
|
});
|
|
28827
28875
|
}
|
|
28828
28876
|
});
|
|
@@ -30156,7 +30204,27 @@ var AgentSessionCreateTestEmailResponseSchema = external_exports.object({
|
|
|
30156
30204
|
*/
|
|
30157
30205
|
reused: external_exports.boolean()
|
|
30158
30206
|
});
|
|
30159
|
-
var
|
|
30207
|
+
var ProvidedCredentialType = external_exports.enum(CredentialTypeValues).extract(["USERNAME_PASSWORD", "API_KEY", "BEARER_TOKEN"]);
|
|
30208
|
+
var AgentSessionSaveProvidedAccountRequestSchema = external_exports.object({
|
|
30209
|
+
runVersion: external_exports.number().int().nonnegative(),
|
|
30210
|
+
provided: external_exports.literal(true),
|
|
30211
|
+
/**
|
|
30212
|
+
* `customer`: the customer handed it over (a share link). `created`: the agent made it, such as an
|
|
30213
|
+
* API key from an admin account it signed up; it is labelled like any account the agent mints, so
|
|
30214
|
+
* a black-box engagement does not read it as customer-supplied.
|
|
30215
|
+
*/
|
|
30216
|
+
source: external_exports.enum(["customer", "created"]),
|
|
30217
|
+
label: external_exports.string().min(1).max(200),
|
|
30218
|
+
credentialType: ProvidedCredentialType,
|
|
30219
|
+
/** USERNAME_PASSWORD only. */
|
|
30220
|
+
username: external_exports.string().min(1).max(320).optional(),
|
|
30221
|
+
/** The password, key or token. */
|
|
30222
|
+
secret: external_exports.string().min(1).max(4096),
|
|
30223
|
+
/** API_KEY only: the header that carries the key. */
|
|
30224
|
+
headerName: external_exports.string().min(1).max(100).optional(),
|
|
30225
|
+
targetUrl: external_exports.string().min(1).max(2048).optional()
|
|
30226
|
+
});
|
|
30227
|
+
var AgentSessionSaveMintedAccountRequestSchema = external_exports.strictObject({
|
|
30160
30228
|
runVersion: external_exports.number().int().nonnegative(),
|
|
30161
30229
|
/** The `label` returned by `create_test_email`. */
|
|
30162
30230
|
label: external_exports.string().min(1).max(200),
|
|
@@ -30184,11 +30252,18 @@ var AgentSessionSaveTestAccountRequestSchema = external_exports.object({
|
|
|
30184
30252
|
*/
|
|
30185
30253
|
targetUrl: external_exports.string().min(1).max(2048).optional()
|
|
30186
30254
|
});
|
|
30255
|
+
var AgentSessionSaveTestAccountRequestSchema = external_exports.union([
|
|
30256
|
+
AgentSessionSaveProvidedAccountRequestSchema,
|
|
30257
|
+
AgentSessionSaveMintedAccountRequestSchema
|
|
30258
|
+
]);
|
|
30187
30259
|
var AgentSessionSaveTestAccountResponseSchema = external_exports.object({
|
|
30188
30260
|
/** Echoed so the agent names the account the same way afterwards. */
|
|
30189
30261
|
label: external_exports.string().max(200),
|
|
30190
|
-
/**
|
|
30191
|
-
|
|
30262
|
+
/**
|
|
30263
|
+
* Minted accounts: `USERNAME_PASSWORD` when a password was supplied, else `MAGIC_AUTH`.
|
|
30264
|
+
* Provided logins: the type the agent named.
|
|
30265
|
+
*/
|
|
30266
|
+
credentialType: external_exports.enum(["USERNAME_PASSWORD", "MAGIC_AUTH", "API_KEY", "BEARER_TOKEN"]),
|
|
30192
30267
|
/**
|
|
30193
30268
|
* The environment the credential landed on. Authoritative, not an echo: it is
|
|
30194
30269
|
* how the agent learns that its `targetUrl` matched nothing and the server
|
|
@@ -30322,6 +30397,46 @@ var RenewCloudCredentialsResponseSchema = external_exports.object({
|
|
|
30322
30397
|
/** The most recently verified ACTIVE resource, whether or not its mint succeeded. Null when none. */
|
|
30323
30398
|
gcpPrimaryResourceId: GcpPrefixedResourceIdSchema.nullable().optional()
|
|
30324
30399
|
});
|
|
30400
|
+
var GetSessionCredentialsRequestSchema = external_exports.object({
|
|
30401
|
+
runVersion: external_exports.number().int().nonnegative()
|
|
30402
|
+
});
|
|
30403
|
+
var ParsedWebCredentialSchema = external_exports.union([
|
|
30404
|
+
external_exports.strictObject({
|
|
30405
|
+
reference: external_exports.literal("SESSION_STATE"),
|
|
30406
|
+
credentialId: external_exports.uuid(),
|
|
30407
|
+
environmentId: external_exports.uuid(),
|
|
30408
|
+
origin: external_exports.string()
|
|
30409
|
+
}),
|
|
30410
|
+
external_exports.strictObject({
|
|
30411
|
+
reference: external_exports.literal("SSH_PRIVATE_KEY"),
|
|
30412
|
+
credentialId: external_exports.uuid(),
|
|
30413
|
+
environmentId: external_exports.uuid(),
|
|
30414
|
+
host: external_exports.string(),
|
|
30415
|
+
port: external_exports.number().int(),
|
|
30416
|
+
username: external_exports.string()
|
|
30417
|
+
}),
|
|
30418
|
+
external_exports.strictObject({ username: external_exports.string(), password: external_exports.string() }),
|
|
30419
|
+
external_exports.strictObject({ secretValue: external_exports.string() }),
|
|
30420
|
+
external_exports.strictObject({ loginEmail: external_exports.string() })
|
|
30421
|
+
]);
|
|
30422
|
+
var WebCredentialEntrySchema = external_exports.object({
|
|
30423
|
+
source: external_exports.literal("customer"),
|
|
30424
|
+
label: external_exports.string(),
|
|
30425
|
+
credentialType: external_exports.enum(CredentialTypeValues),
|
|
30426
|
+
parsed: ParsedWebCredentialSchema,
|
|
30427
|
+
authConfig: external_exports.record(external_exports.string(), external_exports.unknown()).nullable(),
|
|
30428
|
+
mfa: external_exports.object({ kind: external_exports.string() }).optional(),
|
|
30429
|
+
passkeys: external_exports.number().int().positive().optional()
|
|
30430
|
+
});
|
|
30431
|
+
var SessionCredentialsResponseSchema = external_exports.object({
|
|
30432
|
+
/** Empty when the type gets no web credentials or none resolved. */
|
|
30433
|
+
webCredentials: external_exports.array(WebCredentialEntrySchema),
|
|
30434
|
+
vercel: external_exports.object({ token: external_exports.string(), teamId: external_exports.string().nullable() }).nullable(),
|
|
30435
|
+
browserAllowedBaseUrls: external_exports.array(external_exports.string()),
|
|
30436
|
+
browserExclusions: external_exports.array(external_exports.string()),
|
|
30437
|
+
/** API_COVERAGE only; null otherwise. Range checks stay with the host's rate parser. */
|
|
30438
|
+
requestRate: external_exports.object({ perSec: external_exports.number(), burst: external_exports.number() }).nullable()
|
|
30439
|
+
});
|
|
30325
30440
|
var ConvexReconRequestSchema = external_exports.object({
|
|
30326
30441
|
runVersion: external_exports.number().int().nonnegative()
|
|
30327
30442
|
});
|
|
@@ -30579,7 +30694,7 @@ var ParkAgentSessionRequestSchema = external_exports.object({
|
|
|
30579
30694
|
* idle-lingered out (the host's only natural-completion signal); INTERRUPTED =
|
|
30580
30695
|
* SIGTERM / stream-close / api-outage give-up / wall-clock. Meaningful only when
|
|
30581
30696
|
* outcome === 'IDLE'. OPTIONAL for rolling-deploy tolerance: an old host omits
|
|
30582
|
-
* it, and the api treats absent as non-natural (
|
|
30697
|
+
* it, and the api treats absent as non-natural (the validation run stays RUNNING).
|
|
30583
30698
|
*/
|
|
30584
30699
|
idleReason: external_exports.enum(["COMPLETED", "INTERRUPTED"]).optional(),
|
|
30585
30700
|
waitingReason: AgentSessionWaitingReason.optional(),
|
|
@@ -31460,7 +31575,7 @@ var agentSessionInternalContract = c4.router({
|
|
|
31460
31575
|
201: RecordValidationResultResponseSchema,
|
|
31461
31576
|
400: ErrorResponseSchema,
|
|
31462
31577
|
401: ErrorResponseSchema,
|
|
31463
|
-
// runVersion/status guard, or
|
|
31578
|
+
// runVersion/status guard, an ended engagement, or no validation run for the session.
|
|
31464
31579
|
409: ErrorResponseSchema,
|
|
31465
31580
|
500: ErrorResponseSchema
|
|
31466
31581
|
}
|
|
@@ -31912,6 +32027,21 @@ var agentSessionInternalContract = c4.router({
|
|
|
31912
32027
|
500: ErrorResponseSchema
|
|
31913
32028
|
}
|
|
31914
32029
|
},
|
|
32030
|
+
// The session's credentials, fetched by the host at boot (AgentSessionService.getSessionCredentials).
|
|
32031
|
+
getSessionCredentials: {
|
|
32032
|
+
method: "POST",
|
|
32033
|
+
path: "/internal/v1/agent-sessions/:agentSessionId/session-credentials",
|
|
32034
|
+
pathParams: AgentSessionInternalParamsSchema,
|
|
32035
|
+
body: GetSessionCredentialsRequestSchema,
|
|
32036
|
+
responses: {
|
|
32037
|
+
200: SessionCredentialsResponseSchema,
|
|
32038
|
+
400: ErrorResponseSchema,
|
|
32039
|
+
401: ErrorResponseSchema,
|
|
32040
|
+
403: ErrorResponseSchema,
|
|
32041
|
+
409: ErrorResponseSchema,
|
|
32042
|
+
500: ErrorResponseSchema
|
|
32043
|
+
}
|
|
32044
|
+
},
|
|
31915
32045
|
// get_convex_deployments (Convex integration PR 3): the read-only broker. Every
|
|
31916
32046
|
// connected Convex deployment for the run's OWN org, each with its function
|
|
31917
32047
|
// inventory. No request identifier at all — see the schema block above. No 404:
|
|
@@ -36719,6 +36849,7 @@ var LinearIntegrationStatusValues = [
|
|
|
36719
36849
|
"DISCONNECTED"
|
|
36720
36850
|
];
|
|
36721
36851
|
var linearEntityId2 = external_exports.string().min(1).max(64);
|
|
36852
|
+
var LINEAR_RETEST_DELAY_MINUTES_OPTIONS = [0, 60, 180, 720, 1440, 4320];
|
|
36722
36853
|
var LinearIntegrationSummarySchema = external_exports.object({
|
|
36723
36854
|
linearIntegrationId: external_exports.uuid(),
|
|
36724
36855
|
status: external_exports.enum(LinearIntegrationStatusValues),
|
|
@@ -36728,6 +36859,12 @@ var LinearIntegrationSummarySchema = external_exports.object({
|
|
|
36728
36859
|
defaultTeamId: external_exports.string().nullable(),
|
|
36729
36860
|
defaultProjectId: external_exports.string().nullable(),
|
|
36730
36861
|
defaultLabelIds: external_exports.array(external_exports.string()),
|
|
36862
|
+
/**
|
|
36863
|
+
* Optional on the wire only: prod promotes web after the API, so a web bundle
|
|
36864
|
+
* can read an API that predates the field. Consumers default it. Not limited
|
|
36865
|
+
* to the options so retiring one never makes a stored value unparseable.
|
|
36866
|
+
*/
|
|
36867
|
+
retestDelayMinutes: external_exports.number().int().nonnegative().optional(),
|
|
36731
36868
|
connectedAt: external_exports.iso.datetime(),
|
|
36732
36869
|
connectedByName: external_exports.string().nullable(),
|
|
36733
36870
|
/** Non-null → disconnect committed but Linear-side revocation didn't confirm; UI shows the confirm-removal banner. */
|
|
@@ -36759,6 +36896,9 @@ var UpdateLinearDefaultsRequestSchema = external_exports.object({
|
|
|
36759
36896
|
defaultProjectId: linearEntityId2.nullable().optional(),
|
|
36760
36897
|
defaultLabelIds: external_exports.array(linearEntityId2).max(10).optional()
|
|
36761
36898
|
});
|
|
36899
|
+
var UpdateLinearRetestDelayRequestSchema = external_exports.object({
|
|
36900
|
+
retestDelayMinutes: external_exports.literal(LINEAR_RETEST_DELAY_MINUTES_OPTIONS)
|
|
36901
|
+
});
|
|
36762
36902
|
var LinearTeamSchema = external_exports.object({
|
|
36763
36903
|
id: external_exports.string(),
|
|
36764
36904
|
name: external_exports.string(),
|
|
@@ -36845,6 +36985,27 @@ var linearIntegrationContract = c26.router({
|
|
|
36845
36985
|
503: ErrorResponseSchema
|
|
36846
36986
|
}
|
|
36847
36987
|
},
|
|
36988
|
+
/**
|
|
36989
|
+
* Separate from the defaults PATCH, which revalidates the saved team against
|
|
36990
|
+
* Linear on every save: this setting is ours alone and must save while Linear
|
|
36991
|
+
* is unreachable or the team was archived.
|
|
36992
|
+
*/
|
|
36993
|
+
updateLinearRetestDelay: {
|
|
36994
|
+
method: "PATCH",
|
|
36995
|
+
path: "/organizations/:organizationId/integrations/linear/retest-delay",
|
|
36996
|
+
pathParams: OrgIdParamsSchema,
|
|
36997
|
+
body: UpdateLinearRetestDelayRequestSchema,
|
|
36998
|
+
responses: {
|
|
36999
|
+
200: LinearIntegrationResponseSchema,
|
|
37000
|
+
400: ErrorResponseSchema,
|
|
37001
|
+
401: ErrorResponseSchema,
|
|
37002
|
+
403: ErrorResponseSchema,
|
|
37003
|
+
404: ErrorResponseSchema,
|
|
37004
|
+
409: ErrorResponseSchema,
|
|
37005
|
+
500: ErrorResponseSchema,
|
|
37006
|
+
503: ErrorResponseSchema
|
|
37007
|
+
}
|
|
37008
|
+
},
|
|
36848
37009
|
getLinearMetadata: {
|
|
36849
37010
|
method: "GET",
|
|
36850
37011
|
path: "/organizations/:organizationId/integrations/linear/metadata",
|
|
@@ -41541,6 +41702,23 @@ var pentestFindingContract = c45.router({
|
|
|
41541
41702
|
500: ErrorResponseSchema
|
|
41542
41703
|
}
|
|
41543
41704
|
},
|
|
41705
|
+
// Customer says the fix is already live for a finding whose retest waits on its
|
|
41706
|
+
// ticket's delay: the retest becomes due now. No status change.
|
|
41707
|
+
startScheduledRetest: {
|
|
41708
|
+
method: "POST",
|
|
41709
|
+
path: "/organizations/:organizationId/customer-pentests/:pentestId/findings/:pentestFindingId/start-retest",
|
|
41710
|
+
pathParams: CustomerPentestFindingIdParamsSchema,
|
|
41711
|
+
body: c45.noBody(),
|
|
41712
|
+
responses: {
|
|
41713
|
+
200: CustomerPentestFindingResponseSchema,
|
|
41714
|
+
401: ErrorResponseSchema,
|
|
41715
|
+
403: ErrorResponseSchema,
|
|
41716
|
+
404: ErrorResponseSchema,
|
|
41717
|
+
// RETEST_NOT_SCHEDULED: not REMEDIATED with a retest still waiting.
|
|
41718
|
+
409: ErrorResponseSchema,
|
|
41719
|
+
500: ErrorResponseSchema
|
|
41720
|
+
}
|
|
41721
|
+
},
|
|
41544
41722
|
// Customer accepts the finding → ACCEPTED_WITH_CONTROLS without a dispute or a
|
|
41545
41723
|
// staff decision. Human principals here; an API key sends the same transition
|
|
41546
41724
|
// through `automationStatusUpdate` with status ACCEPTED_WITH_CONTROLS.
|
|
@@ -41935,6 +42113,57 @@ var PentestIntakeDocumentTypeValues = [
|
|
|
41935
42113
|
"OTHER"
|
|
41936
42114
|
];
|
|
41937
42115
|
var PentestIntakeDocumentTypeSchema = external_exports.enum(PentestIntakeDocumentTypeValues);
|
|
42116
|
+
var PENTEST_INTAKE_DOCUMENTS_QUESTION = "Do you have any relevant documents to share?";
|
|
42117
|
+
var PENTEST_INTAKE_DOCUMENT_TYPE_LABELS = {
|
|
42118
|
+
PREVIOUS_PENTEST: "Previous pentest report",
|
|
42119
|
+
ARCHITECTURE_DIAGRAM: "Architecture diagram",
|
|
42120
|
+
THREAT_MODEL: "Threat model",
|
|
42121
|
+
OTHER: "Other"
|
|
42122
|
+
};
|
|
42123
|
+
function guessPentestIntakeDocumentType(fileName) {
|
|
42124
|
+
const name = fileName.toLowerCase();
|
|
42125
|
+
if (/threat[\s_-]*model|stride|attack[\s_-]*tree|dfd/.test(name)) return "THREAT_MODEL";
|
|
42126
|
+
if (/pen[\s_-]*test|pentest|penetration|security[\s_-]*assessment|vapt|findings/.test(name)) {
|
|
42127
|
+
return "PREVIOUS_PENTEST";
|
|
42128
|
+
}
|
|
42129
|
+
if (/arch|diagram|topology|infra|system[\s_-]*design|c4|\.drawio/.test(name)) {
|
|
42130
|
+
return "ARCHITECTURE_DIAGRAM";
|
|
42131
|
+
}
|
|
42132
|
+
return "OTHER";
|
|
42133
|
+
}
|
|
42134
|
+
var INTAKE_DOCUMENT_MIME_BY_EXTENSION = {
|
|
42135
|
+
pdf: "application/pdf",
|
|
42136
|
+
doc: "application/msword",
|
|
42137
|
+
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
42138
|
+
xls: "application/vnd.ms-excel",
|
|
42139
|
+
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
42140
|
+
ppt: "application/vnd.ms-powerpoint",
|
|
42141
|
+
pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
42142
|
+
txt: "text/plain",
|
|
42143
|
+
md: "text/markdown",
|
|
42144
|
+
csv: "text/csv",
|
|
42145
|
+
json: "application/json",
|
|
42146
|
+
xml: "application/xml",
|
|
42147
|
+
// text/yaml rather than application/yaml: readers that only accept text/* still take it.
|
|
42148
|
+
yaml: "text/yaml",
|
|
42149
|
+
yml: "text/yaml",
|
|
42150
|
+
html: "text/html",
|
|
42151
|
+
htm: "text/html",
|
|
42152
|
+
png: "image/png",
|
|
42153
|
+
jpg: "image/jpeg",
|
|
42154
|
+
jpeg: "image/jpeg",
|
|
42155
|
+
gif: "image/gif",
|
|
42156
|
+
svg: "image/svg+xml",
|
|
42157
|
+
webp: "image/webp",
|
|
42158
|
+
zip: "application/zip",
|
|
42159
|
+
drawio: "application/xml",
|
|
42160
|
+
vsdx: "application/vnd.ms-visio.drawing"
|
|
42161
|
+
};
|
|
42162
|
+
function pentestIntakeDocumentMimeType(fileName) {
|
|
42163
|
+
const dot = fileName.lastIndexOf(".");
|
|
42164
|
+
const extension = dot === -1 ? "" : fileName.slice(dot + 1).toLowerCase();
|
|
42165
|
+
return INTAKE_DOCUMENT_MIME_BY_EXTENSION[extension] ?? "application/octet-stream";
|
|
42166
|
+
}
|
|
41938
42167
|
var MAX_PENTEST_INTAKE_DOCUMENT_BYTES = 50 * 1024 * 1024;
|
|
41939
42168
|
var PentestIntakeDocumentIdParamsSchema = CustomerPentestIdParamsSchema.extend({
|
|
41940
42169
|
documentId: external_exports.uuid()
|
|
@@ -46227,16 +46456,16 @@ var COVERAGE_CLASSES = [
|
|
|
46227
46456
|
},
|
|
46228
46457
|
{ taxonomy: "RECON", id: "RECON-15", name: "Deployment-platform config", shortLabel: "Deploy" }
|
|
46229
46458
|
];
|
|
46230
|
-
var BY_ID = new Map(COVERAGE_CLASSES.map((
|
|
46459
|
+
var BY_ID = new Map(COVERAGE_CLASSES.map((c84) => [c84.id, c84]));
|
|
46231
46460
|
function idsWhere(keep) {
|
|
46232
|
-
return [OTHER_CLASS_ID, ...COVERAGE_CLASSES.filter(keep).map((
|
|
46461
|
+
return [OTHER_CLASS_ID, ...COVERAGE_CLASSES.filter(keep).map((c84) => c84.id)];
|
|
46233
46462
|
}
|
|
46234
|
-
var HUNTING_CLASS_IDS = idsWhere((
|
|
46235
|
-
var RECON_CLASS_IDS = idsWhere((
|
|
46463
|
+
var HUNTING_CLASS_IDS = idsWhere((c84) => c84.taxonomy !== "RECON");
|
|
46464
|
+
var RECON_CLASS_IDS = idsWhere((c84) => c84.taxonomy === "RECON");
|
|
46236
46465
|
var HUNTING_ID_SET = new Set(HUNTING_CLASS_IDS);
|
|
46237
46466
|
var RECON_ID_SET = new Set(RECON_CLASS_IDS);
|
|
46238
46467
|
var TAXONOMY_ORDER = new Map(CoverageTaxonomyValues.map((t2, i2) => [t2, i2]));
|
|
46239
|
-
var CLASS_ORDER = new Map(COVERAGE_CLASSES.map((
|
|
46468
|
+
var CLASS_ORDER = new Map(COVERAGE_CLASSES.map((c84, i2) => [c84.id, i2]));
|
|
46240
46469
|
|
|
46241
46470
|
// ../../packages/api-contracts/src/contracts/v1/api-coverage-internal.ts
|
|
46242
46471
|
var c80 = initContract();
|
|
@@ -46309,6 +46538,135 @@ var apiCoverageInternalContract = c80.router({
|
|
|
46309
46538
|
}
|
|
46310
46539
|
});
|
|
46311
46540
|
|
|
46541
|
+
// ../../packages/api-contracts/src/schemas/api/v1/setup-session-internal.api.ts
|
|
46542
|
+
var RunVersionSchema2 = external_exports.number().int().nonnegative();
|
|
46543
|
+
var SetupTargetType = external_exports.enum(ApplicationTypeValues).extract(["WEB", "API_SERVICE"]);
|
|
46544
|
+
var ReasonSchema = external_exports.string().min(1).max(500);
|
|
46545
|
+
var SetupVerificationStatusValues = [
|
|
46546
|
+
"UNVERIFIED",
|
|
46547
|
+
"PENDING",
|
|
46548
|
+
"VERIFIED",
|
|
46549
|
+
"FAILED",
|
|
46550
|
+
"INCONCLUSIVE"
|
|
46551
|
+
];
|
|
46552
|
+
var SetupVerificationStatus = external_exports.enum(SetupVerificationStatusValues);
|
|
46553
|
+
var SetupAddTargetRequestSchema = external_exports.object({
|
|
46554
|
+
runVersion: RunVersionSchema2,
|
|
46555
|
+
reason: ReasonSchema,
|
|
46556
|
+
/** The application's name in Trace. An existing org application with this name is reused. */
|
|
46557
|
+
name: external_exports.string().min(1).max(200),
|
|
46558
|
+
type: SetupTargetType,
|
|
46559
|
+
/** The environment's login or base URL. */
|
|
46560
|
+
url: external_exports.url({ protocol: /^https?$/ }).max(2048),
|
|
46561
|
+
environmentName: external_exports.string().min(1).max(100),
|
|
46562
|
+
isProduction: external_exports.boolean()
|
|
46563
|
+
});
|
|
46564
|
+
var SetupAddTargetResponseSchema = external_exports.object({
|
|
46565
|
+
applicationName: external_exports.string(),
|
|
46566
|
+
environmentName: external_exports.string(),
|
|
46567
|
+
created: external_exports.object({
|
|
46568
|
+
application: external_exports.boolean(),
|
|
46569
|
+
environment: external_exports.boolean(),
|
|
46570
|
+
binding: external_exports.boolean(),
|
|
46571
|
+
selection: external_exports.boolean()
|
|
46572
|
+
}),
|
|
46573
|
+
/** The URL's registrable domain differs from the org website's: list it in the summary. */
|
|
46574
|
+
offHomepageDomain: external_exports.boolean()
|
|
46575
|
+
});
|
|
46576
|
+
var SetupAddExclusionRequestSchema = external_exports.object({
|
|
46577
|
+
runVersion: RunVersionSchema2,
|
|
46578
|
+
reason: ReasonSchema,
|
|
46579
|
+
expression: external_exports.string().min(1).max(500)
|
|
46580
|
+
});
|
|
46581
|
+
var SetupAddExclusionResponseSchema = external_exports.object({
|
|
46582
|
+
/** false when the same exclusion was already on the engagement. */
|
|
46583
|
+
created: external_exports.boolean()
|
|
46584
|
+
});
|
|
46585
|
+
var SetupUpdateEngagementRequestSchema = external_exports.object({
|
|
46586
|
+
runVersion: RunVersionSchema2,
|
|
46587
|
+
reason: ReasonSchema,
|
|
46588
|
+
testType: PentestTestType.optional(),
|
|
46589
|
+
// The same ranges the staff engagement form enforces: a rate outside them turns API coverage's
|
|
46590
|
+
// sender off at launch. There is no description field: it is shown to the customer. Not
|
|
46591
|
+
// nullable: setup only adds, so it can set a limit but never clear one.
|
|
46592
|
+
requestRatePerSec: external_exports.number().min(REQUEST_RATE_PER_SEC_RANGE.min).max(REQUEST_RATE_PER_SEC_RANGE.max).optional(),
|
|
46593
|
+
requestBurst: external_exports.number().int().min(REQUEST_BURST_RANGE.min).max(REQUEST_BURST_RANGE.max).optional()
|
|
46594
|
+
});
|
|
46595
|
+
var SetupVerifyRequestSchema = external_exports.object({
|
|
46596
|
+
runVersion: RunVersionSchema2,
|
|
46597
|
+
action: external_exports.enum(["start", "status"])
|
|
46598
|
+
});
|
|
46599
|
+
var SetupVerifyResponseSchema = external_exports.object({
|
|
46600
|
+
applications: external_exports.array(
|
|
46601
|
+
external_exports.object({
|
|
46602
|
+
applicationName: external_exports.string(),
|
|
46603
|
+
/** What `start` did for this application; null on `status`. */
|
|
46604
|
+
launch: external_exports.enum(["STARTED", "IN_FLIGHT", "NOTHING_TO_CHECK"]).nullable(),
|
|
46605
|
+
environments: external_exports.array(external_exports.object({ name: external_exports.string(), status: SetupVerificationStatus })),
|
|
46606
|
+
credentials: external_exports.array(
|
|
46607
|
+
external_exports.object({
|
|
46608
|
+
label: external_exports.string(),
|
|
46609
|
+
environmentName: external_exports.string(),
|
|
46610
|
+
status: SetupVerificationStatus
|
|
46611
|
+
})
|
|
46612
|
+
)
|
|
46613
|
+
})
|
|
46614
|
+
)
|
|
46615
|
+
});
|
|
46616
|
+
var SetupNotesRequestSchema = external_exports.object({ runVersion: RunVersionSchema2 });
|
|
46617
|
+
var SetupNotesResponseSchema = external_exports.object({ notes: external_exports.string().nullable() });
|
|
46618
|
+
|
|
46619
|
+
// ../../packages/api-contracts/src/contracts/v1/setup-session-internal.ts
|
|
46620
|
+
var c81 = initContract();
|
|
46621
|
+
var pathParams2 = external_exports.strictObject({ agentSessionId: external_exports.uuid() });
|
|
46622
|
+
var errors6 = {
|
|
46623
|
+
400: ErrorResponseSchema,
|
|
46624
|
+
401: ErrorResponseSchema,
|
|
46625
|
+
403: ErrorResponseSchema,
|
|
46626
|
+
404: ErrorResponseSchema,
|
|
46627
|
+
409: ErrorResponseSchema,
|
|
46628
|
+
500: ErrorResponseSchema,
|
|
46629
|
+
503: ErrorResponseSchema
|
|
46630
|
+
};
|
|
46631
|
+
var base2 = "/internal/v1/agent-sessions/:agentSessionId/setup";
|
|
46632
|
+
var setupSessionInternalContract = c81.router({
|
|
46633
|
+
addSetupTarget: {
|
|
46634
|
+
method: "POST",
|
|
46635
|
+
path: `${base2}/target`,
|
|
46636
|
+
pathParams: pathParams2,
|
|
46637
|
+
body: SetupAddTargetRequestSchema,
|
|
46638
|
+
responses: { 200: SetupAddTargetResponseSchema, ...errors6 }
|
|
46639
|
+
},
|
|
46640
|
+
addSetupExclusion: {
|
|
46641
|
+
method: "POST",
|
|
46642
|
+
path: `${base2}/exclusion`,
|
|
46643
|
+
pathParams: pathParams2,
|
|
46644
|
+
body: SetupAddExclusionRequestSchema,
|
|
46645
|
+
responses: { 200: SetupAddExclusionResponseSchema, ...errors6 }
|
|
46646
|
+
},
|
|
46647
|
+
updateSetupEngagement: {
|
|
46648
|
+
method: "POST",
|
|
46649
|
+
path: `${base2}/engagement`,
|
|
46650
|
+
pathParams: pathParams2,
|
|
46651
|
+
body: SetupUpdateEngagementRequestSchema,
|
|
46652
|
+
responses: { 200: OperatorPentestContextSchema, ...errors6 }
|
|
46653
|
+
},
|
|
46654
|
+
verifySetup: {
|
|
46655
|
+
method: "POST",
|
|
46656
|
+
path: `${base2}/verify`,
|
|
46657
|
+
pathParams: pathParams2,
|
|
46658
|
+
body: SetupVerifyRequestSchema,
|
|
46659
|
+
responses: { 200: SetupVerifyResponseSchema, ...errors6 }
|
|
46660
|
+
},
|
|
46661
|
+
getSetupNotes: {
|
|
46662
|
+
method: "POST",
|
|
46663
|
+
path: `${base2}/notes`,
|
|
46664
|
+
pathParams: pathParams2,
|
|
46665
|
+
body: SetupNotesRequestSchema,
|
|
46666
|
+
responses: { 200: SetupNotesResponseSchema, ...errors6 }
|
|
46667
|
+
}
|
|
46668
|
+
});
|
|
46669
|
+
|
|
46312
46670
|
// ../../packages/api-contracts/src/schemas/api/v1/premise-check.api.ts
|
|
46313
46671
|
var PREMISE_VERDICTS = ["supported", "contradicted", "cant_tell"];
|
|
46314
46672
|
var EXTRACTED_PREMISE_KINDS = [
|
|
@@ -46398,8 +46756,8 @@ var ListReviewPremiseChecksResponseSchema = external_exports.object({
|
|
|
46398
46756
|
});
|
|
46399
46757
|
|
|
46400
46758
|
// ../../packages/api-contracts/src/contracts/v1/premise-check-internal.ts
|
|
46401
|
-
var
|
|
46402
|
-
var premiseCheckInternalContract =
|
|
46759
|
+
var c82 = initContract();
|
|
46760
|
+
var premiseCheckInternalContract = c82.router({
|
|
46403
46761
|
listReviewPremiseChecks: {
|
|
46404
46762
|
method: "POST",
|
|
46405
46763
|
path: "/internal/v1/agent-sessions/:agentSessionId/premise-checks/list",
|
|
@@ -48261,11 +48619,11 @@ function buildCloneTokenFailureWarning(cloneUrl, failure) {
|
|
|
48261
48619
|
return `Trace returned an unexpected response while generating clone credentials for ${cloneUrl}${quotedMessage ? ` ${quotedMessage}` : ""}. ${fallbackSuffix}`;
|
|
48262
48620
|
}
|
|
48263
48621
|
}
|
|
48264
|
-
function withOptionalMessage(
|
|
48622
|
+
function withOptionalMessage(base3, message) {
|
|
48265
48623
|
if (typeof message === "string") {
|
|
48266
|
-
return { ...
|
|
48624
|
+
return { ...base3, message };
|
|
48267
48625
|
}
|
|
48268
|
-
return
|
|
48626
|
+
return base3;
|
|
48269
48627
|
}
|
|
48270
48628
|
async function listOrganizationsSafe() {
|
|
48271
48629
|
const result = await authenticatedRequestSafe(
|
|
@@ -49083,6 +49441,13 @@ function parseOutput(value) {
|
|
|
49083
49441
|
console.error(colorError(`Invalid --output '${value}'. Use table, json, or tsv.`));
|
|
49084
49442
|
process.exit(1);
|
|
49085
49443
|
}
|
|
49444
|
+
function parseTextOutput(value) {
|
|
49445
|
+
if (value === "text" || value === "json") {
|
|
49446
|
+
return value;
|
|
49447
|
+
}
|
|
49448
|
+
console.error(colorError(`Invalid --output '${sanitizeDisplay(value)}'. Use text or json.`));
|
|
49449
|
+
process.exit(1);
|
|
49450
|
+
}
|
|
49086
49451
|
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
49452
|
function isUuid(value) {
|
|
49088
49453
|
return UUID_PATTERN.test(value);
|
|
@@ -49316,12 +49681,12 @@ function buildApplicationsTable(apps) {
|
|
|
49316
49681
|
repos: String(a2.repositories.length),
|
|
49317
49682
|
url: a2.applicationUrl ? sanitizeDisplay(a2.applicationUrl) : "\u2014"
|
|
49318
49683
|
}));
|
|
49319
|
-
const idWidth = Math.max(2, ...cells.map((
|
|
49320
|
-
const nameWidth = Math.max(4, ...cells.map((
|
|
49321
|
-
const typeWidth = Math.max(4, ...cells.map((
|
|
49684
|
+
const idWidth = Math.max(2, ...cells.map((c84) => c84.id.length));
|
|
49685
|
+
const nameWidth = Math.max(4, ...cells.map((c84) => c84.name.length));
|
|
49686
|
+
const typeWidth = Math.max(4, ...cells.map((c84) => c84.type.length));
|
|
49322
49687
|
const header = `${"ID".padEnd(idWidth)} ${"NAME".padEnd(nameWidth)} ${"TYPE".padEnd(typeWidth)} REPOS URL`;
|
|
49323
49688
|
const rows = cells.map(
|
|
49324
|
-
(
|
|
49689
|
+
(c84) => `${c84.id.padEnd(idWidth)} ${c84.name.padEnd(nameWidth)} ${c84.type.padEnd(typeWidth)} ${c84.repos.padStart(5)} ${c84.url}`
|
|
49325
49690
|
);
|
|
49326
49691
|
return { header, rows };
|
|
49327
49692
|
}
|
|
@@ -49341,12 +49706,12 @@ function buildApplicationRepositoriesTable(links) {
|
|
|
49341
49706
|
path: l2.repositoryPath ? sanitizeDisplay(l2.repositoryPath) : "\u2014",
|
|
49342
49707
|
status: formatStatus(l2.repositoryStatus)
|
|
49343
49708
|
}));
|
|
49344
|
-
const nameWidth = Math.max(10, ...cells.map((
|
|
49345
|
-
const roleWidth = Math.max(4, ...cells.map((
|
|
49346
|
-
const pathWidth = Math.max(4, ...cells.map((
|
|
49709
|
+
const nameWidth = Math.max(10, ...cells.map((c84) => c84.name.length));
|
|
49710
|
+
const roleWidth = Math.max(4, ...cells.map((c84) => c84.role.length));
|
|
49711
|
+
const pathWidth = Math.max(4, ...cells.map((c84) => c84.path.length));
|
|
49347
49712
|
const header = `${"REPOSITORY".padEnd(nameWidth)} SCAN ${"ROLE".padEnd(roleWidth)} ${"PATH".padEnd(pathWidth)} STATUS`;
|
|
49348
49713
|
const rows = cells.map(
|
|
49349
|
-
(
|
|
49714
|
+
(c84) => `${c84.name.padEnd(nameWidth)} ${c84.scan.padEnd(4)} ${c84.role.padEnd(roleWidth)} ${c84.path.padEnd(pathWidth)} ${c84.status}`
|
|
49350
49715
|
);
|
|
49351
49716
|
return { header, rows };
|
|
49352
49717
|
}
|
|
@@ -50131,8 +50496,8 @@ function wrapTextWithPrefix(e, r2, o2, t2 = o2, s = o2, n3) {
|
|
|
50131
50496
|
hard: true,
|
|
50132
50497
|
trim: false
|
|
50133
50498
|
}).split(`
|
|
50134
|
-
`).map((
|
|
50135
|
-
const d = n3 ? n3(
|
|
50499
|
+
`).map((c84, i2, m2) => {
|
|
50500
|
+
const d = n3 ? n3(c84, i2) : c84;
|
|
50136
50501
|
return i2 === 0 ? `${t2}${d}` : i2 === m2.length - 1 ? `${s}${d}` : `${o2}${d}`;
|
|
50137
50502
|
}).join(`
|
|
50138
50503
|
`);
|
|
@@ -50630,14 +50995,14 @@ var limitOptions = ({
|
|
|
50630
50995
|
Math.min(l2 - a2 + 3, e.length - a2),
|
|
50631
50996
|
0
|
|
50632
50997
|
));
|
|
50633
|
-
let d = a2 < e.length && f > 0,
|
|
50998
|
+
let d = a2 < e.length && f > 0, c84 = a2 < e.length && f + a2 < e.length;
|
|
50634
50999
|
const W = Math.min(
|
|
50635
51000
|
f + a2,
|
|
50636
51001
|
e.length
|
|
50637
51002
|
), s = [];
|
|
50638
51003
|
let g = 0;
|
|
50639
|
-
d && g++,
|
|
50640
|
-
const T = f + (d ? 1 : 0), y = W - (
|
|
51004
|
+
d && g++, c84 && g++;
|
|
51005
|
+
const T = f + (d ? 1 : 0), y = W - (c84 ? 1 : 0);
|
|
50641
51006
|
for (let t2 = T; t2 < y; t2++) {
|
|
50642
51007
|
const n3 = e[t2], o2 = n3 ? w(n3, t2 === l2) : "", h2 = wrapAnsi(o2, i2, {
|
|
50643
51008
|
hard: true,
|
|
@@ -50658,14 +51023,14 @@ var limitOptions = ({
|
|
|
50658
51023
|
u4,
|
|
50659
51024
|
true
|
|
50660
51025
|
);
|
|
50661
|
-
d ? ({ lineCount: o2, removals: t2 } = L(), o2 > u4 && (
|
|
51026
|
+
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
51027
|
}
|
|
50663
51028
|
const x = [];
|
|
50664
51029
|
d && x.push(M);
|
|
50665
51030
|
for (const t2 of s)
|
|
50666
51031
|
for (const n3 of t2)
|
|
50667
51032
|
x.push(n3);
|
|
50668
|
-
return
|
|
51033
|
+
return c84 && x.push(M), x;
|
|
50669
51034
|
};
|
|
50670
51035
|
var confirm = (i2) => {
|
|
50671
51036
|
const a2 = i2.active ?? "Yes", s = i2.inactive ?? "No";
|
|
@@ -50684,15 +51049,15 @@ var confirm = (i2) => {
|
|
|
50684
51049
|
u4
|
|
50685
51050
|
), o2 = `${e ? `${styleText2("gray", S_BAR)}
|
|
50686
51051
|
` : ""}${f}
|
|
50687
|
-
`,
|
|
51052
|
+
`, c84 = this.value ? a2 : s;
|
|
50688
51053
|
switch (this.state) {
|
|
50689
51054
|
case "submit": {
|
|
50690
51055
|
const r2 = e ? `${styleText2("gray", S_BAR)} ` : "";
|
|
50691
|
-
return `${o2}${r2}${styleText2("dim",
|
|
51056
|
+
return `${o2}${r2}${styleText2("dim", c84)}`;
|
|
50692
51057
|
}
|
|
50693
51058
|
case "cancel": {
|
|
50694
51059
|
const r2 = e ? `${styleText2("gray", S_BAR)} ` : "";
|
|
50695
|
-
return `${o2}${r2}${styleText2(["strikethrough", "dim"],
|
|
51060
|
+
return `${o2}${r2}${styleText2(["strikethrough", "dim"], c84)}${e ? `
|
|
50696
51061
|
${styleText2("gray", S_BAR)}` : ""}`;
|
|
50697
51062
|
}
|
|
50698
51063
|
default: {
|
|
@@ -50840,13 +51205,13 @@ var multiline = (e) => new h({
|
|
|
50840
51205
|
`, m2 = e.placeholder && e.placeholder.length > 0 ? (
|
|
50841
51206
|
// biome-ignore lint/style/noNonNullAssertion: guarded by placeholder.length > 0
|
|
50842
51207
|
styleText2("inverse", e.placeholder[0]) + styleText2("dim", e.placeholder.slice(1))
|
|
50843
|
-
) : styleText2(["inverse", "hidden"], "_"), a2 = this.userInput ? this.userInputWithCursor : m2, l2 = this.value ?? "",
|
|
51208
|
+
) : styleText2(["inverse", "hidden"], "_"), a2 = this.userInput ? this.userInputWithCursor : m2, l2 = this.value ?? "", c84 = e.showSubmit ? `
|
|
50844
51209
|
${styleText2(this.focused === "submit" ? "cyan" : "dim", "[ submit ]")}` : "";
|
|
50845
51210
|
switch (this.state) {
|
|
50846
51211
|
case "error": {
|
|
50847
51212
|
const n3 = `${styleText2("yellow", S_BAR)} `, r2 = i2 ? wrapTextWithPrefix(e.output, a2, n3, void 0) : a2, u4 = styleText2("yellow", S_BAR_END);
|
|
50848
51213
|
return `${o2}${r2}
|
|
50849
|
-
${u4} ${styleText2("yellow", this.error)}${
|
|
51214
|
+
${u4} ${styleText2("yellow", this.error)}${c84}
|
|
50850
51215
|
`;
|
|
50851
51216
|
}
|
|
50852
51217
|
case "submit": {
|
|
@@ -50874,7 +51239,7 @@ ${u4} ${styleText2("yellow", this.error)}${c83}
|
|
|
50874
51239
|
default: {
|
|
50875
51240
|
const n3 = i2 ? `${styleText2("cyan", S_BAR)} ` : "", r2 = i2 ? styleText2("cyan", S_BAR_END) : "", u4 = i2 ? wrapTextWithPrefix(e.output, a2, n3) : a2;
|
|
50876
51241
|
return `${o2}${u4}
|
|
50877
|
-
${r2}${
|
|
51242
|
+
${r2}${c84}
|
|
50878
51243
|
`;
|
|
50879
51244
|
}
|
|
50880
51245
|
}
|
|
@@ -50889,7 +51254,7 @@ var password = (r2) => new u$1({
|
|
|
50889
51254
|
render() {
|
|
50890
51255
|
const e = r2.withGuide ?? settings.withGuide, o2 = `${e ? `${styleText2("gray", S_BAR)}
|
|
50891
51256
|
` : ""}${symbol2(this.state)} ${r2.message}
|
|
50892
|
-
`,
|
|
51257
|
+
`, c84 = this.userInputWithCursor, i2 = this.masked;
|
|
50893
51258
|
switch (this.state) {
|
|
50894
51259
|
case "error": {
|
|
50895
51260
|
const s = e ? `${styleText2("yellow", S_BAR)} ` : "", n3 = e ? `${styleText2("yellow", S_BAR_END)} ` : "", l2 = i2 ?? "";
|
|
@@ -50909,7 +51274,7 @@ ${styleText2("gray", S_BAR)}` : ""}`;
|
|
|
50909
51274
|
}
|
|
50910
51275
|
default: {
|
|
50911
51276
|
const s = e ? `${styleText2("cyan", S_BAR)} ` : "", n3 = e ? styleText2("cyan", S_BAR_END) : "";
|
|
50912
|
-
return `${o2}${s}${
|
|
51277
|
+
return `${o2}${s}${c84}
|
|
50913
51278
|
${n3}
|
|
50914
51279
|
`;
|
|
50915
51280
|
}
|
|
@@ -50925,7 +51290,7 @@ var SELECT_INSTRUCTIONS = [
|
|
|
50925
51290
|
`${styleText2("dim", "\u2191/\u2193")} to navigate`,
|
|
50926
51291
|
`${styleText2("dim", "Enter:")} confirm`
|
|
50927
51292
|
];
|
|
50928
|
-
var
|
|
51293
|
+
var c83 = (t2, o2) => t2.includes(`
|
|
50929
51294
|
`) ? t2.split(`
|
|
50930
51295
|
`).map((d) => o2(d)).join(`
|
|
50931
51296
|
`) : o2(t2);
|
|
@@ -50936,15 +51301,15 @@ var select = (t2) => {
|
|
|
50936
51301
|
const s = n3.label ?? String(n3.value);
|
|
50937
51302
|
switch (m2) {
|
|
50938
51303
|
case "disabled":
|
|
50939
|
-
return `${styleText2("gray", S_RADIO_INACTIVE)} ${
|
|
51304
|
+
return `${styleText2("gray", S_RADIO_INACTIVE)} ${c83(s, (i2) => styleText2("gray", i2))}${n3.hint ? ` ${styleText2("dim", `(${n3.hint ?? "disabled"})`)}` : ""}`;
|
|
50940
51305
|
case "selected":
|
|
50941
|
-
return `${
|
|
51306
|
+
return `${c83(s, (i2) => styleText2("dim", i2))}`;
|
|
50942
51307
|
case "active":
|
|
50943
51308
|
return `${styleText2("green", S_RADIO_ACTIVE)} ${s}${n3.hint ? ` ${styleText2("dim", `(${n3.hint})`)}` : ""}`;
|
|
50944
51309
|
case "cancelled":
|
|
50945
|
-
return `${
|
|
51310
|
+
return `${c83(s, (i2) => styleText2(["strikethrough", "dim"], i2))}`;
|
|
50946
51311
|
default:
|
|
50947
|
-
return `${styleText2("dim", S_RADIO_INACTIVE)} ${
|
|
51312
|
+
return `${styleText2("dim", S_RADIO_INACTIVE)} ${c83(s, (i2) => styleText2("dim", i2))}`;
|
|
50948
51313
|
}
|
|
50949
51314
|
}, d = t2.showInstructions ?? true;
|
|
50950
51315
|
return new n$1({
|
|
@@ -51817,7 +52182,7 @@ function buildAuthStatusInfo(credential, refreshToken, selectedOrganizationId) {
|
|
|
51817
52182
|
};
|
|
51818
52183
|
}
|
|
51819
52184
|
const token = credential.token;
|
|
51820
|
-
const
|
|
52185
|
+
const base3 = {
|
|
51821
52186
|
loggedIn: true,
|
|
51822
52187
|
authMethod: "user",
|
|
51823
52188
|
expired: false,
|
|
@@ -51828,20 +52193,20 @@ function buildAuthStatusInfo(credential, refreshToken, selectedOrganizationId) {
|
|
|
51828
52193
|
const parts = token.split(".");
|
|
51829
52194
|
const payload = parts[1];
|
|
51830
52195
|
if (!payload) {
|
|
51831
|
-
return
|
|
52196
|
+
return base3;
|
|
51832
52197
|
}
|
|
51833
52198
|
const decoded = JSON.parse(Buffer.from(payload, "base64url").toString("utf-8"));
|
|
51834
52199
|
const expiry = decoded.exp ? new Date(decoded.exp * 1e3) : null;
|
|
51835
52200
|
const isExpired = expiry ? expiry < /* @__PURE__ */ new Date() : false;
|
|
51836
52201
|
return {
|
|
51837
|
-
...
|
|
52202
|
+
...base3,
|
|
51838
52203
|
...decoded.sub ? { userId: decoded.sub } : {},
|
|
51839
52204
|
...decoded.org_id ? { orgId: decoded.org_id } : {},
|
|
51840
52205
|
...expiry ? { expires: expiry.toISOString() } : {},
|
|
51841
52206
|
expired: isExpired
|
|
51842
52207
|
};
|
|
51843
52208
|
} catch {
|
|
51844
|
-
return
|
|
52209
|
+
return base3;
|
|
51845
52210
|
}
|
|
51846
52211
|
}
|
|
51847
52212
|
function createStatusCommand() {
|
|
@@ -52441,7 +52806,7 @@ function extractCommandTree(cmd) {
|
|
|
52441
52806
|
description: o2.description,
|
|
52442
52807
|
takesValue: o2.required || o2.optional
|
|
52443
52808
|
}));
|
|
52444
|
-
const subcommands = cmd.commands.filter((
|
|
52809
|
+
const subcommands = cmd.commands.filter((c84) => !c84._hidden).map((c84) => extractCommandTree(c84));
|
|
52445
52810
|
return {
|
|
52446
52811
|
name: cmd.name(),
|
|
52447
52812
|
description: cmd.description(),
|
|
@@ -52507,7 +52872,7 @@ function generateBashCompletion(tree, binName) {
|
|
|
52507
52872
|
}
|
|
52508
52873
|
function collectBashCases(tree, prefix) {
|
|
52509
52874
|
const cases = /* @__PURE__ */ new Map();
|
|
52510
|
-
const subcommandNames = tree.subcommands.map((
|
|
52875
|
+
const subcommandNames = tree.subcommands.map((c84) => c84.name);
|
|
52511
52876
|
const optionNames = tree.options.flatMap((o2) => o2.short ? [o2.long, o2.short] : [o2.long]);
|
|
52512
52877
|
const extraOptions = prefix === "" ? ["--help", "--version"] : ["--help"];
|
|
52513
52878
|
const allOptions = [.../* @__PURE__ */ new Set([...optionNames, ...extraOptions])];
|
|
@@ -52922,9 +53287,9 @@ function buildInboxesTable(inboxes) {
|
|
|
52922
53287
|
address: sanitizeDisplay(inbox.address),
|
|
52923
53288
|
label: sanitizeDisplay(inbox.label)
|
|
52924
53289
|
}));
|
|
52925
|
-
const addressWidth = Math.max(7, ...cells.map((
|
|
53290
|
+
const addressWidth = Math.max(7, ...cells.map((c84) => c84.address.length));
|
|
52926
53291
|
const header = `${"ADDRESS".padEnd(addressWidth)} LABEL`;
|
|
52927
|
-
const rows = cells.map((
|
|
53292
|
+
const rows = cells.map((c84) => `${c84.address.padEnd(addressWidth)} ${c84.label}`);
|
|
52928
53293
|
return { header, rows };
|
|
52929
53294
|
}
|
|
52930
53295
|
function formatInboxQuota(quota) {
|
|
@@ -52948,14 +53313,14 @@ function buildInboxEmailsTable(messages) {
|
|
|
52948
53313
|
subject: sanitizeDisplay(message.subject ?? ""),
|
|
52949
53314
|
unread: message.unread ? "yes" : "no"
|
|
52950
53315
|
}));
|
|
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((
|
|
53316
|
+
const idWidth = Math.max(2, ...cells.map((c84) => c84.id.length));
|
|
53317
|
+
const receivedWidth = Math.max(8, ...cells.map((c84) => c84.received.length));
|
|
53318
|
+
const fromWidth = Math.max(4, ...cells.map((c84) => c84.from.length));
|
|
53319
|
+
const toWidth = Math.max(2, ...cells.map((c84) => c84.to.length));
|
|
53320
|
+
const subjectWidth = Math.max(7, ...cells.map((c84) => c84.subject.length));
|
|
52956
53321
|
const header = `${"ID".padEnd(idWidth)} ${"RECEIVED".padEnd(receivedWidth)} ${"FROM".padEnd(fromWidth)} ${"TO".padEnd(toWidth)} ${"SUBJECT".padEnd(subjectWidth)} UNREAD`;
|
|
52957
53322
|
const rows = cells.map(
|
|
52958
|
-
(
|
|
53323
|
+
(c84) => `${c84.id.padEnd(idWidth)} ${c84.received.padEnd(receivedWidth)} ${c84.from.padEnd(fromWidth)} ${c84.to.padEnd(toWidth)} ${c84.subject.padEnd(subjectWidth)} ${c84.unread}`
|
|
52959
53324
|
);
|
|
52960
53325
|
return { header, rows };
|
|
52961
53326
|
}
|
|
@@ -53698,6 +54063,13 @@ function mfaDisplay(mfa) {
|
|
|
53698
54063
|
if (mfa.algorithm !== "SHA1") parts.push(mfa.algorithm);
|
|
53699
54064
|
return parts.join(" \xB7 ");
|
|
53700
54065
|
}
|
|
54066
|
+
function sourceDisplay(place) {
|
|
54067
|
+
return `${sanitizeDisplay(place.applicationName)} / ${sanitizeDisplay(place.environmentName)}`;
|
|
54068
|
+
}
|
|
54069
|
+
function linkedIntoDisplay(linkedInto) {
|
|
54070
|
+
if (!linkedInto || linkedInto.length === 0) return "(none)";
|
|
54071
|
+
return linkedInto.map(sourceDisplay).join(", ");
|
|
54072
|
+
}
|
|
53701
54073
|
function renderCredentialDetail(cred) {
|
|
53702
54074
|
const pad = 13;
|
|
53703
54075
|
return [
|
|
@@ -53707,6 +54079,7 @@ function renderCredentialDetail(cred) {
|
|
|
53707
54079
|
`${"Environment:".padEnd(pad)} ${cred.environmentId}`,
|
|
53708
54080
|
`${"Auth config:".padEnd(pad)} ${authConfigDisplay(cred.authConfig)}`,
|
|
53709
54081
|
`${"MFA:".padEnd(pad)} ${mfaDisplay(cred.mfa)}`,
|
|
54082
|
+
`${"Linked into:".padEnd(pad)} ${linkedIntoDisplay(cred.linkedInto)}`,
|
|
53710
54083
|
`${"Created:".padEnd(pad)} ${formatDate(cred.createdAt)}`
|
|
53711
54084
|
].join("\n");
|
|
53712
54085
|
}
|
|
@@ -53730,16 +54103,16 @@ function printCredentialsTsv(creds) {
|
|
|
53730
54103
|
}
|
|
53731
54104
|
}
|
|
53732
54105
|
function buildCredentialsTable(creds) {
|
|
53733
|
-
const cells = creds.map((
|
|
53734
|
-
id:
|
|
53735
|
-
label: sanitizeDisplay(
|
|
53736
|
-
type:
|
|
54106
|
+
const cells = creds.map((c84) => ({
|
|
54107
|
+
id: c84.credentialId,
|
|
54108
|
+
label: sanitizeDisplay(c84.label),
|
|
54109
|
+
type: c84.credentialType
|
|
53737
54110
|
}));
|
|
53738
|
-
const idWidth = Math.max(2, ...cells.map((
|
|
53739
|
-
const labelWidth = Math.max(5, ...cells.map((
|
|
54111
|
+
const idWidth = Math.max(2, ...cells.map((c84) => c84.id.length));
|
|
54112
|
+
const labelWidth = Math.max(5, ...cells.map((c84) => c84.label.length));
|
|
53740
54113
|
const header = `${"ID".padEnd(idWidth)} ${"LABEL".padEnd(labelWidth)} TYPE`;
|
|
53741
54114
|
const rows = cells.map(
|
|
53742
|
-
(
|
|
54115
|
+
(c84) => `${c84.id.padEnd(idWidth)} ${c84.label.padEnd(labelWidth)} ${c84.type}`
|
|
53743
54116
|
);
|
|
53744
54117
|
return { header, rows };
|
|
53745
54118
|
}
|
|
@@ -54174,6 +54547,512 @@ async function runDelete3(credentialId, options) {
|
|
|
54174
54547
|
}
|
|
54175
54548
|
console.log(colorSuccess(`Deleted credential ${credentialId}`));
|
|
54176
54549
|
}
|
|
54550
|
+
function failLinkOp(op, status, body, notFoundLabel) {
|
|
54551
|
+
const { message, errorCode } = readErrorFields(body);
|
|
54552
|
+
if (status === UNAUTHORIZED_STATUS2) {
|
|
54553
|
+
exitAuthFailed();
|
|
54554
|
+
}
|
|
54555
|
+
if (status === 403) {
|
|
54556
|
+
const verb = op.startsWith("un") ? "unlink" : "link";
|
|
54557
|
+
console.error(
|
|
54558
|
+
colorError(
|
|
54559
|
+
`Only an organization admin or Trace staff can ${verb} credentials, and API keys can't.`
|
|
54560
|
+
)
|
|
54561
|
+
);
|
|
54562
|
+
if (message !== void 0) console.error(message);
|
|
54563
|
+
process.exit(1);
|
|
54564
|
+
}
|
|
54565
|
+
if (status === 404) {
|
|
54566
|
+
console.error(colorError(message ?? `Not found: ${notFoundLabel}.`));
|
|
54567
|
+
process.exit(1);
|
|
54568
|
+
}
|
|
54569
|
+
if (status === 400) {
|
|
54570
|
+
console.error(colorError(message ?? "Invalid request (status 400)."));
|
|
54571
|
+
process.exit(1);
|
|
54572
|
+
}
|
|
54573
|
+
if (status === 409) {
|
|
54574
|
+
if (errorCode === "CREDENTIAL_LINK_SELF") {
|
|
54575
|
+
console.error(
|
|
54576
|
+
colorError(message ?? "That already belongs to this environment; nothing to link.")
|
|
54577
|
+
);
|
|
54578
|
+
} else if (errorCode === "CREDENTIAL_ALREADY_LINKED") {
|
|
54579
|
+
console.error(colorError(message ?? "That is already linked into this environment."));
|
|
54580
|
+
} else if (errorCode === "CREDENTIAL_LABEL_TAKEN") {
|
|
54581
|
+
console.error(
|
|
54582
|
+
colorError(
|
|
54583
|
+
message ?? "This environment already shows a credential with that label. Rename one of them first."
|
|
54584
|
+
)
|
|
54585
|
+
);
|
|
54586
|
+
} else if (errorCode === "CREDENTIAL_IN_USE") {
|
|
54587
|
+
console.error(
|
|
54588
|
+
colorError(message ?? "You can't unlink while a pentest using this environment is running.")
|
|
54589
|
+
);
|
|
54590
|
+
} else {
|
|
54591
|
+
console.error(colorError(message ?? "Conflict (status 409)."));
|
|
54592
|
+
}
|
|
54593
|
+
process.exit(1);
|
|
54594
|
+
}
|
|
54595
|
+
console.error(colorError(`Failed to ${op} (status ${status})${message ? `: ${message}` : "."}`));
|
|
54596
|
+
process.exit(1);
|
|
54597
|
+
}
|
|
54598
|
+
function requireLinkId(credentialLinkId) {
|
|
54599
|
+
if (!isUuid(credentialLinkId)) {
|
|
54600
|
+
console.error(colorError(`Invalid credential link id '${credentialLinkId}'. Expected a UUID.`));
|
|
54601
|
+
process.exit(1);
|
|
54602
|
+
}
|
|
54603
|
+
}
|
|
54604
|
+
function renderCredentialLinkDetail(link) {
|
|
54605
|
+
const pad = 13;
|
|
54606
|
+
return [
|
|
54607
|
+
`${"Link ID:".padEnd(pad)} ${link.credentialLinkId}`,
|
|
54608
|
+
`${"Credential:".padEnd(pad)} ${link.credential.credentialId}`,
|
|
54609
|
+
`${"Label:".padEnd(pad)} ${sanitizeDisplay(link.credential.label)}`,
|
|
54610
|
+
`${"Type:".padEnd(pad)} ${link.credential.credentialType}`,
|
|
54611
|
+
`${"Source:".padEnd(pad)} ${sourceDisplay(link.source)} (${link.source.environmentId})`,
|
|
54612
|
+
`${"Linked into:".padEnd(pad)} ${link.environmentId}`,
|
|
54613
|
+
`${"MFA:".padEnd(pad)} ${mfaDisplay(link.credential.mfa)}`,
|
|
54614
|
+
`${"Linked:".padEnd(pad)} ${formatDate(link.createdAt)}`
|
|
54615
|
+
].join("\n");
|
|
54616
|
+
}
|
|
54617
|
+
function printCredentialLinksTsv(links) {
|
|
54618
|
+
console.log(
|
|
54619
|
+
[
|
|
54620
|
+
"credentialLinkId",
|
|
54621
|
+
"credentialId",
|
|
54622
|
+
"label",
|
|
54623
|
+
"credentialType",
|
|
54624
|
+
"sourceApplication",
|
|
54625
|
+
"sourceEnvironment",
|
|
54626
|
+
"sourceEnvironmentId",
|
|
54627
|
+
"environmentCredentialSourceId",
|
|
54628
|
+
"mfa",
|
|
54629
|
+
"createdAt"
|
|
54630
|
+
].join(" ")
|
|
54631
|
+
);
|
|
54632
|
+
for (const link of links) {
|
|
54633
|
+
console.log(
|
|
54634
|
+
[
|
|
54635
|
+
tsvSafe(link.credentialLinkId),
|
|
54636
|
+
tsvSafe(link.credential.credentialId),
|
|
54637
|
+
tsvSafe(sanitizeDisplay(link.credential.label)),
|
|
54638
|
+
tsvSafe(link.credential.credentialType),
|
|
54639
|
+
tsvSafe(sanitizeDisplay(link.source.applicationName)),
|
|
54640
|
+
tsvSafe(sanitizeDisplay(link.source.environmentName)),
|
|
54641
|
+
tsvSafe(link.source.environmentId),
|
|
54642
|
+
tsvSafe(link.environmentCredentialSourceId ?? ""),
|
|
54643
|
+
tsvSafe(link.credential.mfa ? link.credential.mfa.kind : ""),
|
|
54644
|
+
tsvSafe(toIso(link.createdAt))
|
|
54645
|
+
].join(" ")
|
|
54646
|
+
);
|
|
54647
|
+
}
|
|
54648
|
+
}
|
|
54649
|
+
function buildCredentialLinksTable(links) {
|
|
54650
|
+
const cells = links.map((link) => ({
|
|
54651
|
+
id: link.credentialLinkId,
|
|
54652
|
+
label: sanitizeDisplay(link.credential.label),
|
|
54653
|
+
type: link.credential.credentialType,
|
|
54654
|
+
source: sourceDisplay(link.source),
|
|
54655
|
+
via: link.environmentCredentialSourceId ? "link-all" : ""
|
|
54656
|
+
}));
|
|
54657
|
+
const idWidth = Math.max("LINK ID".length, ...cells.map((c84) => c84.id.length));
|
|
54658
|
+
const labelWidth = Math.max("LABEL".length, ...cells.map((c84) => c84.label.length));
|
|
54659
|
+
const typeWidth = Math.max("TYPE".length, ...cells.map((c84) => c84.type.length));
|
|
54660
|
+
const sourceWidth = Math.max("SOURCE".length, ...cells.map((c84) => c84.source.length));
|
|
54661
|
+
const header = `${"LINK ID".padEnd(idWidth)} ${"LABEL".padEnd(labelWidth)} ${"TYPE".padEnd(typeWidth)} ${"SOURCE".padEnd(sourceWidth)} VIA`;
|
|
54662
|
+
const rows = cells.map(
|
|
54663
|
+
(c84) => `${c84.id.padEnd(idWidth)} ${c84.label.padEnd(labelWidth)} ${c84.type.padEnd(typeWidth)} ${c84.source.padEnd(sourceWidth)} ${c84.via}`.trimEnd()
|
|
54664
|
+
);
|
|
54665
|
+
return { header, rows };
|
|
54666
|
+
}
|
|
54667
|
+
function outputCredentialLink(link, output) {
|
|
54668
|
+
if (output === "json") {
|
|
54669
|
+
printJson(link);
|
|
54670
|
+
return;
|
|
54671
|
+
}
|
|
54672
|
+
if (output === "tsv") {
|
|
54673
|
+
printCredentialLinksTsv([link]);
|
|
54674
|
+
return;
|
|
54675
|
+
}
|
|
54676
|
+
console.log(renderCredentialLinkDetail(link));
|
|
54677
|
+
}
|
|
54678
|
+
function printLinkableCredentialsTsv(creds) {
|
|
54679
|
+
console.log(
|
|
54680
|
+
[
|
|
54681
|
+
"credentialId",
|
|
54682
|
+
"label",
|
|
54683
|
+
"credentialType",
|
|
54684
|
+
"application",
|
|
54685
|
+
"environment",
|
|
54686
|
+
"environmentId",
|
|
54687
|
+
"hasMfa",
|
|
54688
|
+
"alreadyLinked"
|
|
54689
|
+
].join(" ")
|
|
54690
|
+
);
|
|
54691
|
+
for (const cred of creds) {
|
|
54692
|
+
console.log(
|
|
54693
|
+
[
|
|
54694
|
+
tsvSafe(cred.credentialId),
|
|
54695
|
+
tsvSafe(sanitizeDisplay(cred.label)),
|
|
54696
|
+
tsvSafe(cred.credentialType),
|
|
54697
|
+
tsvSafe(sanitizeDisplay(cred.application.name)),
|
|
54698
|
+
tsvSafe(sanitizeDisplay(cred.environment.name)),
|
|
54699
|
+
tsvSafe(cred.environment.environmentId),
|
|
54700
|
+
tsvSafe(String(cred.hasMfa)),
|
|
54701
|
+
tsvSafe(String(cred.alreadyLinked))
|
|
54702
|
+
].join(" ")
|
|
54703
|
+
);
|
|
54704
|
+
}
|
|
54705
|
+
}
|
|
54706
|
+
function buildLinkableCredentialsTable(creds) {
|
|
54707
|
+
const cells = creds.map((cred) => ({
|
|
54708
|
+
id: cred.credentialId,
|
|
54709
|
+
label: sanitizeDisplay(cred.label),
|
|
54710
|
+
type: cred.credentialType,
|
|
54711
|
+
source: sourceDisplay({
|
|
54712
|
+
applicationName: cred.application.name,
|
|
54713
|
+
environmentName: cred.environment.name
|
|
54714
|
+
}),
|
|
54715
|
+
linked: cred.alreadyLinked ? "linked" : ""
|
|
54716
|
+
}));
|
|
54717
|
+
const idWidth = Math.max("ID".length, ...cells.map((c84) => c84.id.length));
|
|
54718
|
+
const labelWidth = Math.max("LABEL".length, ...cells.map((c84) => c84.label.length));
|
|
54719
|
+
const typeWidth = Math.max("TYPE".length, ...cells.map((c84) => c84.type.length));
|
|
54720
|
+
const sourceWidth = Math.max("SOURCE".length, ...cells.map((c84) => c84.source.length));
|
|
54721
|
+
const header = `${"ID".padEnd(idWidth)} ${"LABEL".padEnd(labelWidth)} ${"TYPE".padEnd(typeWidth)} ${"SOURCE".padEnd(sourceWidth)} STATUS`;
|
|
54722
|
+
const rows = cells.map(
|
|
54723
|
+
(c84) => `${c84.id.padEnd(idWidth)} ${c84.label.padEnd(labelWidth)} ${c84.type.padEnd(typeWidth)} ${c84.source.padEnd(sourceWidth)} ${c84.linked}`.trimEnd()
|
|
54724
|
+
);
|
|
54725
|
+
return { header, rows };
|
|
54726
|
+
}
|
|
54727
|
+
async function runLink(credentialId, options) {
|
|
54728
|
+
const output = parseOutput(options.output);
|
|
54729
|
+
requireEnvId(options.env);
|
|
54730
|
+
requireCredId(credentialId);
|
|
54731
|
+
const organizationId = await resolveOrganizationId();
|
|
54732
|
+
const result = await authenticatedRequest(
|
|
54733
|
+
(client) => client.credentialLink.createCredentialLink({
|
|
54734
|
+
params: { organizationId, environmentId: options.env },
|
|
54735
|
+
body: { credentialId }
|
|
54736
|
+
})
|
|
54737
|
+
);
|
|
54738
|
+
if (result.status !== CREATED_STATUS) {
|
|
54739
|
+
failLinkOp("link", result.status, result.body, credentialId);
|
|
54740
|
+
}
|
|
54741
|
+
if (output === "table") {
|
|
54742
|
+
console.log(
|
|
54743
|
+
colorSuccess(
|
|
54744
|
+
`Linked credential ${sanitizeDisplay(result.body.credential.label)} from ${sourceDisplay(result.body.source)}`
|
|
54745
|
+
)
|
|
54746
|
+
);
|
|
54747
|
+
}
|
|
54748
|
+
outputCredentialLink(result.body, output);
|
|
54749
|
+
}
|
|
54750
|
+
async function runUnlink(credentialLinkId, options) {
|
|
54751
|
+
requireEnvId(options.env);
|
|
54752
|
+
requireLinkId(credentialLinkId);
|
|
54753
|
+
const confirmed = await confirmAction(
|
|
54754
|
+
`Unlink credential link ${credentialLinkId} (the source credential is kept)?`,
|
|
54755
|
+
{ skip: options.yes, verb: "unlink" }
|
|
54756
|
+
);
|
|
54757
|
+
if (!confirmed) {
|
|
54758
|
+
console.log("Aborted.");
|
|
54759
|
+
return;
|
|
54760
|
+
}
|
|
54761
|
+
const organizationId = await resolveOrganizationId();
|
|
54762
|
+
const result = await authenticatedRequest(
|
|
54763
|
+
(client) => client.credentialLink.deleteCredentialLink({
|
|
54764
|
+
params: { organizationId, environmentId: options.env, credentialLinkId }
|
|
54765
|
+
})
|
|
54766
|
+
);
|
|
54767
|
+
if (result.status !== NO_CONTENT_STATUS) {
|
|
54768
|
+
if (result.status === 409 && readErrorFields(result.body).errorCode === "CREDENTIAL_LINKED_BY_RULE") {
|
|
54769
|
+
await failLinkedByRule(organizationId, options.env, result.body);
|
|
54770
|
+
}
|
|
54771
|
+
failLinkOp("unlink", result.status, result.body, credentialLinkId);
|
|
54772
|
+
}
|
|
54773
|
+
console.log(colorSuccess(`Unlinked credential link ${credentialLinkId}`));
|
|
54774
|
+
}
|
|
54775
|
+
function coveringRuleId(body) {
|
|
54776
|
+
if (typeof body !== "object" || body === null || !("details" in body)) return null;
|
|
54777
|
+
const { details } = body;
|
|
54778
|
+
if (typeof details !== "object" || details === null) return null;
|
|
54779
|
+
if (!("environmentCredentialSourceId" in details)) return null;
|
|
54780
|
+
const id = details.environmentCredentialSourceId;
|
|
54781
|
+
return typeof id === "string" && isUuid(id) ? id : null;
|
|
54782
|
+
}
|
|
54783
|
+
async function failLinkedByRule(organizationId, environmentId, body) {
|
|
54784
|
+
const { message } = readErrorFields(body);
|
|
54785
|
+
console.error(
|
|
54786
|
+
colorError(
|
|
54787
|
+
message ?? "All of the environment this credential comes from is linked here, so it stays."
|
|
54788
|
+
)
|
|
54789
|
+
);
|
|
54790
|
+
const ruleId = coveringRuleId(body);
|
|
54791
|
+
const rules = ruleId === null ? null : await fetchCredentialSources(organizationId, environmentId);
|
|
54792
|
+
const rule = rules?.find((r2) => r2.environmentCredentialSourceId === ruleId);
|
|
54793
|
+
console.error(
|
|
54794
|
+
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}`
|
|
54795
|
+
);
|
|
54796
|
+
process.exit(1);
|
|
54797
|
+
}
|
|
54798
|
+
async function fetchCredentialSources(organizationId, environmentId) {
|
|
54799
|
+
const result = await authenticatedRequestSafe(
|
|
54800
|
+
(client) => client.credentialLink.listCredentialSources({
|
|
54801
|
+
params: { organizationId, environmentId }
|
|
54802
|
+
})
|
|
54803
|
+
);
|
|
54804
|
+
if (result === null) return null;
|
|
54805
|
+
if (result.status === SUCCESS_STATUS) return result.body;
|
|
54806
|
+
if (result.status === 404) return [];
|
|
54807
|
+
return null;
|
|
54808
|
+
}
|
|
54809
|
+
function credentialSourceLines(rules) {
|
|
54810
|
+
return rules.map(
|
|
54811
|
+
(rule) => `All of ${sourceDisplay(rule.source)} is linked here, including credentials added there later.`
|
|
54812
|
+
);
|
|
54813
|
+
}
|
|
54814
|
+
async function runLinks(options) {
|
|
54815
|
+
const output = parseOutput(options.output);
|
|
54816
|
+
requireEnvId(options.env);
|
|
54817
|
+
const organizationId = await resolveOrganizationId();
|
|
54818
|
+
const result = await authenticatedRequest(
|
|
54819
|
+
(client) => client.credentialLink.listCredentialLinks({
|
|
54820
|
+
params: { organizationId, environmentId: options.env }
|
|
54821
|
+
})
|
|
54822
|
+
);
|
|
54823
|
+
if (result.status !== SUCCESS_STATUS) {
|
|
54824
|
+
failReadOp3("list credential links", result.status, result.body, options.env);
|
|
54825
|
+
}
|
|
54826
|
+
const links = result.body;
|
|
54827
|
+
if (output === "json") {
|
|
54828
|
+
printJson(links);
|
|
54829
|
+
return;
|
|
54830
|
+
}
|
|
54831
|
+
if (output === "tsv") {
|
|
54832
|
+
printCredentialLinksTsv(links);
|
|
54833
|
+
return;
|
|
54834
|
+
}
|
|
54835
|
+
const rules = await fetchCredentialSources(organizationId, options.env);
|
|
54836
|
+
if (rules === null) {
|
|
54837
|
+
console.error(
|
|
54838
|
+
colorError("Could not read which environments are linked whole; showing links only.")
|
|
54839
|
+
);
|
|
54840
|
+
}
|
|
54841
|
+
const ruleLines = credentialSourceLines(rules ?? []);
|
|
54842
|
+
if (links.length === 0) {
|
|
54843
|
+
if (ruleLines.length > 0) {
|
|
54844
|
+
for (const line of ruleLines) console.log(line);
|
|
54845
|
+
console.log("No credentials from there yet.");
|
|
54846
|
+
return;
|
|
54847
|
+
}
|
|
54848
|
+
console.log(
|
|
54849
|
+
`No linked credentials. See what you can link with: tracecli credential linkable --env ${options.env}`
|
|
54850
|
+
);
|
|
54851
|
+
return;
|
|
54852
|
+
}
|
|
54853
|
+
for (const line of ruleLines) console.log(line);
|
|
54854
|
+
if (ruleLines.length > 0) console.log("");
|
|
54855
|
+
const { header, rows } = buildCredentialLinksTable(links);
|
|
54856
|
+
await runInteractivePager({
|
|
54857
|
+
items: links,
|
|
54858
|
+
header,
|
|
54859
|
+
renderItem: (_link, i2) => [rows[i2] ?? ""],
|
|
54860
|
+
disabled: options.pager === false
|
|
54861
|
+
});
|
|
54862
|
+
}
|
|
54863
|
+
async function runLinkable(options) {
|
|
54864
|
+
const output = parseOutput(options.output);
|
|
54865
|
+
requireEnvId(options.env);
|
|
54866
|
+
const organizationId = await resolveOrganizationId();
|
|
54867
|
+
const result = await authenticatedRequest(
|
|
54868
|
+
(client) => client.credentialLink.listLinkableCredentials({
|
|
54869
|
+
params: { organizationId, environmentId: options.env }
|
|
54870
|
+
})
|
|
54871
|
+
);
|
|
54872
|
+
if (result.status !== SUCCESS_STATUS) {
|
|
54873
|
+
failReadOp3("list linkable credentials", result.status, result.body, options.env);
|
|
54874
|
+
}
|
|
54875
|
+
const creds = result.body;
|
|
54876
|
+
if (output === "json") {
|
|
54877
|
+
printJson(creds);
|
|
54878
|
+
return;
|
|
54879
|
+
}
|
|
54880
|
+
if (output === "tsv") {
|
|
54881
|
+
printLinkableCredentialsTsv(creds);
|
|
54882
|
+
return;
|
|
54883
|
+
}
|
|
54884
|
+
if (creds.length === 0) {
|
|
54885
|
+
console.log("No credentials elsewhere in this organization to link.");
|
|
54886
|
+
return;
|
|
54887
|
+
}
|
|
54888
|
+
const { header, rows } = buildLinkableCredentialsTable(creds);
|
|
54889
|
+
await runInteractivePager({
|
|
54890
|
+
items: creds,
|
|
54891
|
+
header,
|
|
54892
|
+
renderItem: (_cred, i2) => [rows[i2] ?? ""],
|
|
54893
|
+
disabled: options.pager === false
|
|
54894
|
+
});
|
|
54895
|
+
}
|
|
54896
|
+
function printLinkableEnvironmentsTsv(envs) {
|
|
54897
|
+
console.log(
|
|
54898
|
+
["environmentId", "application", "environment", "credentialCount", "alreadyLinked"].join(" ")
|
|
54899
|
+
);
|
|
54900
|
+
for (const env2 of envs) {
|
|
54901
|
+
console.log(
|
|
54902
|
+
[
|
|
54903
|
+
tsvSafe(env2.environmentId),
|
|
54904
|
+
tsvSafe(sanitizeDisplay(env2.application.name)),
|
|
54905
|
+
tsvSafe(sanitizeDisplay(env2.name)),
|
|
54906
|
+
tsvSafe(String(env2.credentialCount)),
|
|
54907
|
+
tsvSafe(String(env2.alreadyLinked))
|
|
54908
|
+
].join(" ")
|
|
54909
|
+
);
|
|
54910
|
+
}
|
|
54911
|
+
}
|
|
54912
|
+
function buildLinkableEnvironmentsTable(envs) {
|
|
54913
|
+
const cells = envs.map((env2) => ({
|
|
54914
|
+
id: env2.environmentId,
|
|
54915
|
+
name: sourceDisplay({ applicationName: env2.application.name, environmentName: env2.name }),
|
|
54916
|
+
count: String(env2.credentialCount),
|
|
54917
|
+
linked: env2.alreadyLinked ? "linked" : ""
|
|
54918
|
+
}));
|
|
54919
|
+
const idWidth = Math.max("ENVIRONMENT ID".length, ...cells.map((c84) => c84.id.length));
|
|
54920
|
+
const nameWidth = Math.max("ENVIRONMENT".length, ...cells.map((c84) => c84.name.length));
|
|
54921
|
+
const countWidth = "CREDENTIALS".length;
|
|
54922
|
+
const header = `${"ENVIRONMENT ID".padEnd(idWidth)} ${"ENVIRONMENT".padEnd(nameWidth)} ${"CREDENTIALS".padEnd(countWidth)} STATUS`;
|
|
54923
|
+
const rows = cells.map(
|
|
54924
|
+
(c84) => `${c84.id.padEnd(idWidth)} ${c84.name.padEnd(nameWidth)} ${c84.count.padEnd(countWidth)} ${c84.linked}`.trimEnd()
|
|
54925
|
+
);
|
|
54926
|
+
return { header, rows };
|
|
54927
|
+
}
|
|
54928
|
+
async function runLinkableEnvironments(options) {
|
|
54929
|
+
const output = parseOutput(options.output);
|
|
54930
|
+
requireEnvId(options.env);
|
|
54931
|
+
const organizationId = await resolveOrganizationId();
|
|
54932
|
+
const result = await authenticatedRequest(
|
|
54933
|
+
(client) => client.credentialLink.listLinkableEnvironments({
|
|
54934
|
+
params: { organizationId, environmentId: options.env }
|
|
54935
|
+
})
|
|
54936
|
+
);
|
|
54937
|
+
if (result.status !== SUCCESS_STATUS) {
|
|
54938
|
+
failReadOp3("list linkable environments", result.status, result.body, options.env);
|
|
54939
|
+
}
|
|
54940
|
+
const envs = result.body;
|
|
54941
|
+
if (output === "json") {
|
|
54942
|
+
printJson(envs);
|
|
54943
|
+
return;
|
|
54944
|
+
}
|
|
54945
|
+
if (output === "tsv") {
|
|
54946
|
+
printLinkableEnvironmentsTsv(envs);
|
|
54947
|
+
return;
|
|
54948
|
+
}
|
|
54949
|
+
if (envs.length === 0) {
|
|
54950
|
+
console.log("No other environments in this organization to link.");
|
|
54951
|
+
return;
|
|
54952
|
+
}
|
|
54953
|
+
const { header, rows } = buildLinkableEnvironmentsTable(envs);
|
|
54954
|
+
await runInteractivePager({
|
|
54955
|
+
items: envs,
|
|
54956
|
+
header,
|
|
54957
|
+
renderItem: (_env, i2) => [rows[i2] ?? ""],
|
|
54958
|
+
disabled: options.pager === false
|
|
54959
|
+
});
|
|
54960
|
+
}
|
|
54961
|
+
function requireSourceEnvId(sourceEnvironmentId) {
|
|
54962
|
+
if (!isUuid(sourceEnvironmentId)) {
|
|
54963
|
+
console.error(
|
|
54964
|
+
colorError(
|
|
54965
|
+
`Invalid source environment id '${sourceEnvironmentId}'. Expected an environment UUID.`
|
|
54966
|
+
)
|
|
54967
|
+
);
|
|
54968
|
+
process.exit(1);
|
|
54969
|
+
}
|
|
54970
|
+
}
|
|
54971
|
+
async function runLinkAll(sourceEnvironmentId, options) {
|
|
54972
|
+
const output = parseOutput(options.output);
|
|
54973
|
+
requireEnvId(options.env);
|
|
54974
|
+
requireSourceEnvId(sourceEnvironmentId);
|
|
54975
|
+
const organizationId = await resolveOrganizationId();
|
|
54976
|
+
const result = await authenticatedRequest(
|
|
54977
|
+
(client) => client.credentialLink.createCredentialSource({
|
|
54978
|
+
params: { organizationId, environmentId: options.env },
|
|
54979
|
+
body: { sourceEnvironmentId }
|
|
54980
|
+
})
|
|
54981
|
+
);
|
|
54982
|
+
if (result.status !== CREATED_STATUS) {
|
|
54983
|
+
failLinkOp("link all", result.status, result.body, sourceEnvironmentId);
|
|
54984
|
+
}
|
|
54985
|
+
const rule = result.body;
|
|
54986
|
+
if (output === "json") {
|
|
54987
|
+
printJson(rule);
|
|
54988
|
+
return;
|
|
54989
|
+
}
|
|
54990
|
+
if (output === "tsv") {
|
|
54991
|
+
printCredentialLinksTsv(rule.links);
|
|
54992
|
+
return;
|
|
54993
|
+
}
|
|
54994
|
+
const pad = 13;
|
|
54995
|
+
console.log(
|
|
54996
|
+
colorSuccess(
|
|
54997
|
+
`Linked all of ${sourceDisplay(rule.source)}. Credentials added there later show up here too.`
|
|
54998
|
+
)
|
|
54999
|
+
);
|
|
55000
|
+
console.log(
|
|
55001
|
+
[
|
|
55002
|
+
`${"Source:".padEnd(pad)} ${sourceDisplay(rule.source)} (${rule.source.environmentId})`,
|
|
55003
|
+
`${"Linked into:".padEnd(pad)} ${rule.environmentId}`,
|
|
55004
|
+
`${"New links:".padEnd(pad)} ${rule.links.length}`
|
|
55005
|
+
].join("\n")
|
|
55006
|
+
);
|
|
55007
|
+
}
|
|
55008
|
+
async function runUnlinkAll(sourceEnvironmentId, options) {
|
|
55009
|
+
requireEnvId(options.env);
|
|
55010
|
+
requireSourceEnvId(sourceEnvironmentId);
|
|
55011
|
+
const organizationId = await resolveOrganizationId();
|
|
55012
|
+
const listed = await authenticatedRequest(
|
|
55013
|
+
(client) => client.credentialLink.listCredentialSources({
|
|
55014
|
+
params: { organizationId, environmentId: options.env }
|
|
55015
|
+
})
|
|
55016
|
+
);
|
|
55017
|
+
if (listed.status !== SUCCESS_STATUS) {
|
|
55018
|
+
failReadOp3("look up the link-all", listed.status, listed.body, options.env);
|
|
55019
|
+
}
|
|
55020
|
+
const wanted = sourceEnvironmentId.toLowerCase();
|
|
55021
|
+
const rule = listed.body.find((r2) => r2.source.environmentId.toLowerCase() === wanted);
|
|
55022
|
+
if (!rule) {
|
|
55023
|
+
console.error(
|
|
55024
|
+
colorError(
|
|
55025
|
+
`Nothing from ${sourceEnvironmentId} is linked whole into this environment. See what is with: tracecli credential linkable --env ${options.env} --environments`
|
|
55026
|
+
)
|
|
55027
|
+
);
|
|
55028
|
+
process.exit(1);
|
|
55029
|
+
}
|
|
55030
|
+
const confirmed = await confirmAction(
|
|
55031
|
+
`Unlink all of ${sourceDisplay(rule.source)} from this environment (credentials linked on their own stay)?`,
|
|
55032
|
+
{ skip: options.yes, verb: "unlink" }
|
|
55033
|
+
);
|
|
55034
|
+
if (!confirmed) {
|
|
55035
|
+
console.log("Aborted.");
|
|
55036
|
+
return;
|
|
55037
|
+
}
|
|
55038
|
+
const result = await authenticatedRequest(
|
|
55039
|
+
(client) => client.credentialLink.deleteCredentialSource({
|
|
55040
|
+
params: {
|
|
55041
|
+
organizationId,
|
|
55042
|
+
environmentId: options.env,
|
|
55043
|
+
environmentCredentialSourceId: rule.environmentCredentialSourceId
|
|
55044
|
+
}
|
|
55045
|
+
})
|
|
55046
|
+
);
|
|
55047
|
+
if (result.status !== NO_CONTENT_STATUS) {
|
|
55048
|
+
failLinkOp("unlink all", result.status, result.body, rule.environmentCredentialSourceId);
|
|
55049
|
+
}
|
|
55050
|
+
console.log(
|
|
55051
|
+
colorSuccess(
|
|
55052
|
+
`Unlinked all of ${sourceDisplay(rule.source)}. Credentials linked on their own stay.`
|
|
55053
|
+
)
|
|
55054
|
+
);
|
|
55055
|
+
}
|
|
54177
55056
|
function addSecretInputOptions(command) {
|
|
54178
55057
|
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
55058
|
}
|
|
@@ -54284,6 +55163,89 @@ left \u2014 if the current window is nearly over, the API waits for the next one
|
|
|
54284
55163
|
await runMfaCode(credentialId, options);
|
|
54285
55164
|
});
|
|
54286
55165
|
}
|
|
55166
|
+
function createLinkCommand() {
|
|
55167
|
+
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(
|
|
55168
|
+
"after",
|
|
55169
|
+
`
|
|
55170
|
+
A link holds no secret: the linked environment sees the source credential as it
|
|
55171
|
+
is now, so a rotation, MFA change, or relabel on the source shows up here too.
|
|
55172
|
+
Edit the credential where it lives (its own --env). The label must be free in
|
|
55173
|
+
the target environment, and the credential must not already belong to it.
|
|
55174
|
+
|
|
55175
|
+
Find candidates with: tracecli credential linkable --env <envId>
|
|
55176
|
+
To link every credential of an environment, now and as they are added, use
|
|
55177
|
+
link-all instead.
|
|
55178
|
+
|
|
55179
|
+
Examples:
|
|
55180
|
+
tracecli credential linkable --env <envId>
|
|
55181
|
+
tracecli credential link <credentialId> --env <envId>`
|
|
55182
|
+
).action(async (credentialId, options) => {
|
|
55183
|
+
await runLink(credentialId, options);
|
|
55184
|
+
});
|
|
55185
|
+
}
|
|
55186
|
+
function createUnlinkCommand() {
|
|
55187
|
+
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(
|
|
55188
|
+
"after",
|
|
55189
|
+
`
|
|
55190
|
+
Blocked while a pentest using this environment is running, like deleting one of
|
|
55191
|
+
the environment's own credentials. A credential whose whole environment is
|
|
55192
|
+
linked here (VIA link-all in \`credential links\`) stays until that is removed
|
|
55193
|
+
with \`credential unlink-all\`.`
|
|
55194
|
+
).action(async (credentialLinkId, options) => {
|
|
55195
|
+
await runUnlink(credentialLinkId, options);
|
|
55196
|
+
});
|
|
55197
|
+
}
|
|
55198
|
+
function createLinksCommand() {
|
|
55199
|
+
return new Command("links").description(
|
|
55200
|
+
"List the credentials linked into an environment from elsewhere (secrets are never shown)"
|
|
55201
|
+
).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) => {
|
|
55202
|
+
await runLinks(options);
|
|
55203
|
+
});
|
|
55204
|
+
}
|
|
55205
|
+
function createLinkableCommand() {
|
|
55206
|
+
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(
|
|
55207
|
+
"after",
|
|
55208
|
+
`
|
|
55209
|
+
Rows already linked into this environment are marked "linked" (table) or
|
|
55210
|
+
alreadyLinked=true (tsv). With --environments, "linked" means all of that
|
|
55211
|
+
environment is linked here already.`
|
|
55212
|
+
).action(async (options) => {
|
|
55213
|
+
if (options.environments) {
|
|
55214
|
+
await runLinkableEnvironments(options);
|
|
55215
|
+
return;
|
|
55216
|
+
}
|
|
55217
|
+
await runLinkable(options);
|
|
55218
|
+
});
|
|
55219
|
+
}
|
|
55220
|
+
function createLinkAllCommand() {
|
|
55221
|
+
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(
|
|
55222
|
+
"after",
|
|
55223
|
+
`
|
|
55224
|
+
Each credential shows here as a link, like \`credential link\`, and a credential
|
|
55225
|
+
added to the source later shows up here too. Edit them where they live. Every
|
|
55226
|
+
label must be free in this environment. Credentials you already linked on their
|
|
55227
|
+
own stay as they are.
|
|
55228
|
+
|
|
55229
|
+
Find environments with: tracecli credential linkable --env <envId> --environments
|
|
55230
|
+
|
|
55231
|
+
Examples:
|
|
55232
|
+
tracecli credential link-all <sourceEnvId> --env <envId>
|
|
55233
|
+
tracecli credential unlink-all <sourceEnvId> --env <envId>`
|
|
55234
|
+
).action(async (sourceEnvironmentId, options) => {
|
|
55235
|
+
await runLinkAll(sourceEnvironmentId, options);
|
|
55236
|
+
});
|
|
55237
|
+
}
|
|
55238
|
+
function createUnlinkAllCommand() {
|
|
55239
|
+
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(
|
|
55240
|
+
"after",
|
|
55241
|
+
`
|
|
55242
|
+
Removes the links link-all made; the credentials stay in their own environment.
|
|
55243
|
+
Credentials you linked on their own stay linked. Blocked while a pentest using
|
|
55244
|
+
this environment is running.`
|
|
55245
|
+
).action(async (sourceEnvironmentId, options) => {
|
|
55246
|
+
await runUnlinkAll(sourceEnvironmentId, options);
|
|
55247
|
+
});
|
|
55248
|
+
}
|
|
54287
55249
|
function createCredentialCommand() {
|
|
54288
55250
|
const credential = new Command("credential").alias("cred").description("Manage environment credentials");
|
|
54289
55251
|
credential.addCommand(createCreateCommand3());
|
|
@@ -54294,6 +55256,12 @@ function createCredentialCommand() {
|
|
|
54294
55256
|
credential.addCommand(createSetMfaCommand());
|
|
54295
55257
|
credential.addCommand(createClearMfaCommand());
|
|
54296
55258
|
credential.addCommand(createMfaCodeCommand());
|
|
55259
|
+
credential.addCommand(createLinkCommand());
|
|
55260
|
+
credential.addCommand(createUnlinkCommand());
|
|
55261
|
+
credential.addCommand(createLinkAllCommand());
|
|
55262
|
+
credential.addCommand(createUnlinkAllCommand());
|
|
55263
|
+
credential.addCommand(createLinksCommand());
|
|
55264
|
+
credential.addCommand(createLinkableCommand());
|
|
54297
55265
|
credential.addCommand(createInboxCommand(), { hidden: true });
|
|
54298
55266
|
return credential;
|
|
54299
55267
|
}
|
|
@@ -54509,14 +55477,14 @@ function buildEnvironmentTable(envs, leading) {
|
|
|
54509
55477
|
url: e.primaryUrl != null ? sanitizeDisplay(e.primaryUrl) : "\u2014"
|
|
54510
55478
|
}));
|
|
54511
55479
|
const leadingWidths = leading.map(
|
|
54512
|
-
(column, i2) => Math.max(column.header.length, ...cells.map((
|
|
55480
|
+
(column, i2) => Math.max(column.header.length, ...cells.map((c84) => c84.leading[i2]?.length ?? 0))
|
|
54513
55481
|
);
|
|
54514
|
-
const nameWidth = Math.max(4, ...cells.map((
|
|
55482
|
+
const nameWidth = Math.max(4, ...cells.map((c84) => c84.name.length));
|
|
54515
55483
|
const padLeading = (values) => values.map((v, i2) => v.padEnd(leadingWidths[i2] ?? 0)).join(" ");
|
|
54516
55484
|
return {
|
|
54517
|
-
header: `${padLeading(leading.map((
|
|
55485
|
+
header: `${padLeading(leading.map((c84) => c84.header))} ${"NAME".padEnd(nameWidth)} PROD URL`,
|
|
54518
55486
|
rows: cells.map(
|
|
54519
|
-
(
|
|
55487
|
+
(c84) => `${padLeading(c84.leading)} ${c84.name.padEnd(nameWidth)} ${c84.prod.padEnd(4)} ${c84.url}`
|
|
54520
55488
|
)
|
|
54521
55489
|
};
|
|
54522
55490
|
}
|
|
@@ -54898,7 +55866,6 @@ var CLI_VERSION = packageMetadata.version;
|
|
|
54898
55866
|
import { readFileSync as readFileSync7 } from "fs";
|
|
54899
55867
|
|
|
54900
55868
|
// src/pentest/evidence.ts
|
|
54901
|
-
import { readFileSync as readFileSync5, statSync as statSync2 } from "fs";
|
|
54902
55869
|
import { basename as basename2 } from "path";
|
|
54903
55870
|
|
|
54904
55871
|
// src/pentest/resolve.ts
|
|
@@ -54973,6 +55940,67 @@ Run \`tracecli pentest list\` to see available pentests.`
|
|
|
54973
55940
|
return pentest.pentestId;
|
|
54974
55941
|
}
|
|
54975
55942
|
|
|
55943
|
+
// src/pentest/upload-file.ts
|
|
55944
|
+
import { readFileSync as readFileSync5, statSync as statSync2 } from "fs";
|
|
55945
|
+
function formatBytes2(bytes) {
|
|
55946
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
55947
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)} KB`;
|
|
55948
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
55949
|
+
}
|
|
55950
|
+
function statUploadFile(path3, limit) {
|
|
55951
|
+
let sizeBytes;
|
|
55952
|
+
try {
|
|
55953
|
+
const stats = statSync2(path3);
|
|
55954
|
+
if (!stats.isFile()) {
|
|
55955
|
+
console.error(colorError(`--file '${sanitizeDisplay(path3)}' is not a regular file.`));
|
|
55956
|
+
process.exit(1);
|
|
55957
|
+
}
|
|
55958
|
+
sizeBytes = stats.size;
|
|
55959
|
+
} catch {
|
|
55960
|
+
console.error(colorError(`Cannot read --file '${sanitizeDisplay(path3)}'.`));
|
|
55961
|
+
process.exit(1);
|
|
55962
|
+
}
|
|
55963
|
+
if (sizeBytes === 0) {
|
|
55964
|
+
console.error(colorError(`--file '${sanitizeDisplay(path3)}' is empty.`));
|
|
55965
|
+
process.exit(1);
|
|
55966
|
+
}
|
|
55967
|
+
if (sizeBytes > limit.bytes) {
|
|
55968
|
+
console.error(
|
|
55969
|
+
colorError(
|
|
55970
|
+
`File is ${formatBytes2(sizeBytes)}; the limit for one ${limit.noun} is ${formatBytes2(limit.bytes)}.`
|
|
55971
|
+
)
|
|
55972
|
+
);
|
|
55973
|
+
process.exit(1);
|
|
55974
|
+
}
|
|
55975
|
+
return sizeBytes;
|
|
55976
|
+
}
|
|
55977
|
+
async function putFileToPresignedUrl(uploadUrl, path3, options = {}) {
|
|
55978
|
+
let put;
|
|
55979
|
+
try {
|
|
55980
|
+
put = await fetch(uploadUrl, {
|
|
55981
|
+
method: "PUT",
|
|
55982
|
+
body: new Uint8Array(readFileSync5(path3)),
|
|
55983
|
+
...options.contentType === void 0 ? {} : { headers: { "Content-Type": options.contentType } }
|
|
55984
|
+
});
|
|
55985
|
+
} catch (error48) {
|
|
55986
|
+
const reason = error48 instanceof Error ? error48.message : "unknown error";
|
|
55987
|
+
console.error(
|
|
55988
|
+
colorError(
|
|
55989
|
+
`Upload to storage failed: could not reach storage (${sanitizeDisplay(reason)}). Check the connection and re-run the command.`
|
|
55990
|
+
)
|
|
55991
|
+
);
|
|
55992
|
+
process.exit(1);
|
|
55993
|
+
}
|
|
55994
|
+
if (!put.ok) {
|
|
55995
|
+
console.error(
|
|
55996
|
+
colorError(
|
|
55997
|
+
`Upload to storage failed (status ${put.status}). The upload link is short-lived; re-run the command.`
|
|
55998
|
+
)
|
|
55999
|
+
);
|
|
56000
|
+
process.exit(1);
|
|
56001
|
+
}
|
|
56002
|
+
}
|
|
56003
|
+
|
|
54976
56004
|
// src/pentest/evidence.ts
|
|
54977
56005
|
var SECTION_LIST = ValidationEvidenceSectionValues.join(", ");
|
|
54978
56006
|
var KIND_LIST = PentestExploitArtifactKindValues.join(", ");
|
|
@@ -54982,11 +56010,6 @@ function isSection(value) {
|
|
|
54982
56010
|
function isKind(value) {
|
|
54983
56011
|
return PentestExploitArtifactKindValues.includes(value);
|
|
54984
56012
|
}
|
|
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
56013
|
async function resolveFindingId(organizationId, pentestId, identifier) {
|
|
54991
56014
|
if (isUuid(identifier)) {
|
|
54992
56015
|
return identifier;
|
|
@@ -55033,30 +56056,10 @@ async function runAttachEvidence(options) {
|
|
|
55033
56056
|
console.error(`Valid kinds: ${KIND_LIST}`);
|
|
55034
56057
|
process.exit(1);
|
|
55035
56058
|
}
|
|
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
|
-
}
|
|
56059
|
+
const sizeBytes = statUploadFile(options.file, {
|
|
56060
|
+
bytes: MAX_STAFF_EVIDENCE_UPLOAD_BYTES,
|
|
56061
|
+
noun: "exhibit"
|
|
56062
|
+
});
|
|
55060
56063
|
const organizationId = await resolveOrganizationId();
|
|
55061
56064
|
const pentestId = await resolvePentestId(organizationId, options.pentest);
|
|
55062
56065
|
const pentestFindingId = await resolveFindingId(organizationId, pentestId, options.finding);
|
|
@@ -55078,16 +56081,7 @@ async function runAttachEvidence(options) {
|
|
|
55078
56081
|
);
|
|
55079
56082
|
process.exit(1);
|
|
55080
56083
|
}
|
|
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
|
-
}
|
|
56084
|
+
await putFileToPresignedUrl(slot.body.uploadUrl, options.file);
|
|
55091
56085
|
const attached = await authenticatedRequest(
|
|
55092
56086
|
(client) => client.pentestFinding.attachFindingEvidence({
|
|
55093
56087
|
params: params3,
|
|
@@ -55896,8 +56890,8 @@ function requiredExtensionForKind(kind) {
|
|
|
55896
56890
|
return ".zip";
|
|
55897
56891
|
}
|
|
55898
56892
|
function defaultDisplayName(originalFilename) {
|
|
55899
|
-
const
|
|
55900
|
-
return
|
|
56893
|
+
const base3 = originalFilename.replace(/\.(zip|apk|ipa)$/i, "").trim();
|
|
56894
|
+
return base3.slice(0, 200) || "source";
|
|
55901
56895
|
}
|
|
55902
56896
|
function formatArchiveBytes(bytes) {
|
|
55903
56897
|
if (bytes < 1024) return `${bytes} B`;
|
|
@@ -56812,6 +57806,258 @@ function createAttestationCommand() {
|
|
|
56812
57806
|
return attestation;
|
|
56813
57807
|
}
|
|
56814
57808
|
|
|
57809
|
+
// src/pentest/documents.ts
|
|
57810
|
+
import { basename as basename5 } from "path";
|
|
57811
|
+
|
|
57812
|
+
// src/pentest/intake-errors.ts
|
|
57813
|
+
var WRITE_FORBIDDEN_MESSAGE = {
|
|
57814
|
+
questionnaire: "Questionnaire writes require an organization admin (or Trace staff) signed in with a browser session. API keys are rejected.",
|
|
57815
|
+
"questionnaire documents": "Questionnaire document writes require an organization admin (or Trace staff) signed in with a browser session. API keys are rejected."
|
|
57816
|
+
};
|
|
57817
|
+
function failIntakeRequest(status, body, options) {
|
|
57818
|
+
const { message } = readErrorFields(body);
|
|
57819
|
+
if (status === UNAUTHORIZED_STATUS2) {
|
|
57820
|
+
exitAuthFailed();
|
|
57821
|
+
}
|
|
57822
|
+
if (status === 403) {
|
|
57823
|
+
console.error(
|
|
57824
|
+
colorError(
|
|
57825
|
+
options.action === "write" ? WRITE_FORBIDDEN_MESSAGE[options.subject] : message ?? `You do not have permission to view this ${options.subject}.`
|
|
57826
|
+
)
|
|
57827
|
+
);
|
|
57828
|
+
process.exit(1);
|
|
57829
|
+
}
|
|
57830
|
+
if (status === 404) {
|
|
57831
|
+
console.error(
|
|
57832
|
+
colorError(
|
|
57833
|
+
options.notFound === void 0 ? message ?? "Not found." : `${options.notFound.what} not found: ${sanitizeDisplay(options.notFound.label)}.`
|
|
57834
|
+
)
|
|
57835
|
+
);
|
|
57836
|
+
process.exit(1);
|
|
57837
|
+
}
|
|
57838
|
+
if (status === 400) {
|
|
57839
|
+
console.error(colorError(message ?? `Invalid ${options.subject} request (status 400).`));
|
|
57840
|
+
process.exit(1);
|
|
57841
|
+
}
|
|
57842
|
+
if (status === 409) {
|
|
57843
|
+
console.error(colorError(message ?? QUESTIONNAIRE_LOCKED_MESSAGE));
|
|
57844
|
+
process.exit(1);
|
|
57845
|
+
}
|
|
57846
|
+
console.error(
|
|
57847
|
+
colorError(
|
|
57848
|
+
`Failed to ${options.action === "write" ? "save" : "load"} ${options.subject} (status ${status})${message ? `: ${message}` : "."}`
|
|
57849
|
+
)
|
|
57850
|
+
);
|
|
57851
|
+
process.exit(1);
|
|
57852
|
+
}
|
|
57853
|
+
|
|
57854
|
+
// src/pentest/documents.ts
|
|
57855
|
+
var TYPE_LIST = PentestIntakeDocumentTypeValues.join(", ");
|
|
57856
|
+
function isDocumentType(value) {
|
|
57857
|
+
return PentestIntakeDocumentTypeValues.includes(value);
|
|
57858
|
+
}
|
|
57859
|
+
function renderDocumentLines(doc) {
|
|
57860
|
+
const uploader = doc.uploadedByName ? ` by ${sanitizeDisplay(doc.uploadedByName)}` : "";
|
|
57861
|
+
return [
|
|
57862
|
+
` ${sanitizeDisplay(doc.fileName)}`,
|
|
57863
|
+
` ${PENTEST_INTAKE_DOCUMENT_TYPE_LABELS[doc.documentType]} | ${formatBytes2(doc.fileSizeBytes)} | uploaded${uploader} on ${formatDate(doc.createdAt)}`,
|
|
57864
|
+
` id ${doc.documentId}`
|
|
57865
|
+
];
|
|
57866
|
+
}
|
|
57867
|
+
var TSV_HEADER = "documentId documentType fileName fileType fileSizeBytes uploadedByName createdAt";
|
|
57868
|
+
async function runList5(options) {
|
|
57869
|
+
const output = parseOutput(options.output);
|
|
57870
|
+
const organizationId = await resolveOrganizationId();
|
|
57871
|
+
const pentestId = await resolvePentestId(organizationId, options.pentest);
|
|
57872
|
+
const result = await authenticatedRequest(
|
|
57873
|
+
(client) => client.pentestIntakeDocument.listDocuments({ params: { organizationId, pentestId } })
|
|
57874
|
+
);
|
|
57875
|
+
if (result.status !== SUCCESS_STATUS) {
|
|
57876
|
+
failIntakeRequest(result.status, result.body, {
|
|
57877
|
+
subject: "questionnaire documents",
|
|
57878
|
+
action: "read",
|
|
57879
|
+
notFound: { what: "Pentest", label: options.pentest }
|
|
57880
|
+
});
|
|
57881
|
+
}
|
|
57882
|
+
const { editable, documents } = result.body;
|
|
57883
|
+
if (output === "json") {
|
|
57884
|
+
printJson({ editable, documents });
|
|
57885
|
+
return;
|
|
57886
|
+
}
|
|
57887
|
+
if (output === "tsv") {
|
|
57888
|
+
console.log(TSV_HEADER);
|
|
57889
|
+
for (const doc of documents) {
|
|
57890
|
+
console.log(
|
|
57891
|
+
[
|
|
57892
|
+
doc.documentId,
|
|
57893
|
+
doc.documentType,
|
|
57894
|
+
tsvSafe(sanitizeDisplay(doc.fileName)),
|
|
57895
|
+
tsvSafe(sanitizeDisplay(doc.fileType)),
|
|
57896
|
+
String(doc.fileSizeBytes),
|
|
57897
|
+
tsvSafe(doc.uploadedByName === null ? null : sanitizeDisplay(doc.uploadedByName)),
|
|
57898
|
+
toIso(doc.createdAt)
|
|
57899
|
+
].join(" ")
|
|
57900
|
+
);
|
|
57901
|
+
}
|
|
57902
|
+
return;
|
|
57903
|
+
}
|
|
57904
|
+
console.log(`Editable: ${editable ? "yes" : "no"}`);
|
|
57905
|
+
if (documents.length === 0) {
|
|
57906
|
+
console.log("No documents attached.");
|
|
57907
|
+
return;
|
|
57908
|
+
}
|
|
57909
|
+
console.log(`${documents.length} document${documents.length === 1 ? "" : "s"}:`);
|
|
57910
|
+
for (const doc of documents) {
|
|
57911
|
+
for (const line of renderDocumentLines(doc)) {
|
|
57912
|
+
console.log(line);
|
|
57913
|
+
}
|
|
57914
|
+
}
|
|
57915
|
+
}
|
|
57916
|
+
async function runAttach(options) {
|
|
57917
|
+
const output = parseTextOutput(options.output);
|
|
57918
|
+
if (options.type !== void 0 && !isDocumentType(options.type)) {
|
|
57919
|
+
console.error(colorError(`Invalid --type '${sanitizeDisplay(options.type)}'.`));
|
|
57920
|
+
console.error(`Valid types: ${TYPE_LIST}`);
|
|
57921
|
+
process.exit(1);
|
|
57922
|
+
}
|
|
57923
|
+
const sizeBytes = statUploadFile(options.file, {
|
|
57924
|
+
bytes: MAX_PENTEST_INTAKE_DOCUMENT_BYTES,
|
|
57925
|
+
noun: "document"
|
|
57926
|
+
});
|
|
57927
|
+
const fileName = basename5(options.file);
|
|
57928
|
+
const documentType = options.type !== void 0 && isDocumentType(options.type) ? options.type : guessPentestIntakeDocumentType(fileName);
|
|
57929
|
+
const organizationId = await resolveOrganizationId();
|
|
57930
|
+
const pentestId = await resolvePentestId(organizationId, options.pentest);
|
|
57931
|
+
const params3 = { organizationId, pentestId };
|
|
57932
|
+
const slot = await authenticatedRequest(
|
|
57933
|
+
(client) => client.pentestIntakeDocument.createUpload({
|
|
57934
|
+
params: params3,
|
|
57935
|
+
body: { fileName, fileSizeBytes: sizeBytes }
|
|
57936
|
+
})
|
|
57937
|
+
);
|
|
57938
|
+
if (slot.status !== CREATED_STATUS) {
|
|
57939
|
+
failIntakeRequest(slot.status, slot.body, {
|
|
57940
|
+
subject: "questionnaire documents",
|
|
57941
|
+
action: "write",
|
|
57942
|
+
notFound: { what: "Pentest", label: options.pentest }
|
|
57943
|
+
});
|
|
57944
|
+
}
|
|
57945
|
+
const fileType = pentestIntakeDocumentMimeType(fileName);
|
|
57946
|
+
await putFileToPresignedUrl(slot.body.uploadUrl, options.file, { contentType: fileType });
|
|
57947
|
+
const attached = await authenticatedRequest(
|
|
57948
|
+
(client) => client.pentestIntakeDocument.attachDocument({
|
|
57949
|
+
params: params3,
|
|
57950
|
+
body: {
|
|
57951
|
+
uploadKey: slot.body.uploadKey,
|
|
57952
|
+
documentType,
|
|
57953
|
+
fileName,
|
|
57954
|
+
fileType
|
|
57955
|
+
}
|
|
57956
|
+
})
|
|
57957
|
+
);
|
|
57958
|
+
if (attached.status !== CREATED_STATUS) {
|
|
57959
|
+
failIntakeRequest(attached.status, attached.body, {
|
|
57960
|
+
subject: "questionnaire documents",
|
|
57961
|
+
action: "write",
|
|
57962
|
+
notFound: { what: "Pentest", label: options.pentest }
|
|
57963
|
+
});
|
|
57964
|
+
}
|
|
57965
|
+
if (output === "json") {
|
|
57966
|
+
printJson(attached.body);
|
|
57967
|
+
return;
|
|
57968
|
+
}
|
|
57969
|
+
console.log(
|
|
57970
|
+
colorSuccess(
|
|
57971
|
+
`Attached ${sanitizeDisplay(attached.body.fileName)} (${PENTEST_INTAKE_DOCUMENT_TYPE_LABELS[attached.body.documentType]}, ${formatBytes2(attached.body.fileSizeBytes)})`
|
|
57972
|
+
)
|
|
57973
|
+
);
|
|
57974
|
+
console.log(` id ${attached.body.documentId}`);
|
|
57975
|
+
}
|
|
57976
|
+
async function runRemove(documentId, options) {
|
|
57977
|
+
if (!isUuid(documentId)) {
|
|
57978
|
+
console.error(
|
|
57979
|
+
colorError(
|
|
57980
|
+
`Invalid document ID '${sanitizeDisplay(documentId)}'. Pass the UUID from \`tracecli pentest documents list\`.`
|
|
57981
|
+
)
|
|
57982
|
+
);
|
|
57983
|
+
process.exit(1);
|
|
57984
|
+
}
|
|
57985
|
+
const organizationId = await resolveOrganizationId();
|
|
57986
|
+
const pentestId = await resolvePentestId(organizationId, options.pentest);
|
|
57987
|
+
const confirmed = await confirmDeletion(`document ${documentId}`, options.yes === true);
|
|
57988
|
+
if (!confirmed) {
|
|
57989
|
+
console.log("Cancelled.");
|
|
57990
|
+
return;
|
|
57991
|
+
}
|
|
57992
|
+
const result = await authenticatedRequest(
|
|
57993
|
+
(client) => client.pentestIntakeDocument.deleteDocument({
|
|
57994
|
+
params: { organizationId, pentestId, documentId }
|
|
57995
|
+
})
|
|
57996
|
+
);
|
|
57997
|
+
if (result.status !== NO_CONTENT_STATUS) {
|
|
57998
|
+
failIntakeRequest(result.status, result.body, {
|
|
57999
|
+
subject: "questionnaire documents",
|
|
58000
|
+
action: "write",
|
|
58001
|
+
notFound: { what: "Document", label: documentId }
|
|
58002
|
+
});
|
|
58003
|
+
}
|
|
58004
|
+
console.log(colorSuccess(`Removed document ${documentId}.`));
|
|
58005
|
+
}
|
|
58006
|
+
function createListCommand6() {
|
|
58007
|
+
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(
|
|
58008
|
+
"after",
|
|
58009
|
+
`
|
|
58010
|
+
Examples:
|
|
58011
|
+
tracecli pentest documents list --pentest PT-1
|
|
58012
|
+
tracecli pentest documents list --pentest PT-1 --output json`
|
|
58013
|
+
).action(async (options) => {
|
|
58014
|
+
await runList5(options);
|
|
58015
|
+
});
|
|
58016
|
+
}
|
|
58017
|
+
function createAttachCommand() {
|
|
58018
|
+
return new Command("attach").description(
|
|
58019
|
+
"Attach a document (prior report, architecture diagram, threat model) to the questionnaire"
|
|
58020
|
+
).requiredOption("--pentest <id>", "Pentest display ID (e.g. PT-3) or UUID").requiredOption("--file <path>", "Path to the document").option(
|
|
58021
|
+
"--type <type>",
|
|
58022
|
+
`Document type (${TYPE_LIST}); guessed from the file name when omitted`
|
|
58023
|
+
).option("-o, --output <format>", "Output format: text, json (default: text)", "text").addHelpText(
|
|
58024
|
+
"after",
|
|
58025
|
+
`
|
|
58026
|
+
Answers the questionnaire's "documents to share" question. Same edit window as
|
|
58027
|
+
the answers: allowed until testing starts. Every documents command needs a
|
|
58028
|
+
browser session from \`tracecli auth login\`; API keys are rejected. Attach and
|
|
58029
|
+
remove also need an organization admin (or Trace staff).
|
|
58030
|
+
Up to ${formatBytes2(MAX_PENTEST_INTAKE_DOCUMENT_BYTES)} per file.
|
|
58031
|
+
|
|
58032
|
+
Examples:
|
|
58033
|
+
tracecli pentest documents attach --pentest PT-1 --file ./threat-model.pdf
|
|
58034
|
+
tracecli pentest documents attach --pentest PT-1 --file ./q3-report.pdf --type PREVIOUS_PENTEST
|
|
58035
|
+
tracecli pentest documents attach --pentest PT-1 --file ./arch.png --output json`
|
|
58036
|
+
).action(async (options) => {
|
|
58037
|
+
await runAttach(options);
|
|
58038
|
+
});
|
|
58039
|
+
}
|
|
58040
|
+
function createRemoveCommand() {
|
|
58041
|
+
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(
|
|
58042
|
+
"after",
|
|
58043
|
+
`
|
|
58044
|
+
Examples:
|
|
58045
|
+
tracecli pentest documents remove 3f1c... --pentest PT-1
|
|
58046
|
+
tracecli pentest documents remove 3f1c... --pentest PT-1 --yes`
|
|
58047
|
+
).action(async (documentId, options) => {
|
|
58048
|
+
await runRemove(documentId, options);
|
|
58049
|
+
});
|
|
58050
|
+
}
|
|
58051
|
+
function createDocumentsCommand() {
|
|
58052
|
+
const documents = new Command("documents").description(
|
|
58053
|
+
"Documents attached to the pentest intake questionnaire"
|
|
58054
|
+
);
|
|
58055
|
+
documents.addCommand(createListCommand6());
|
|
58056
|
+
documents.addCommand(createAttachCommand());
|
|
58057
|
+
documents.addCommand(createRemoveCommand());
|
|
58058
|
+
return documents;
|
|
58059
|
+
}
|
|
58060
|
+
|
|
56815
58061
|
// src/pentest/get-finding.ts
|
|
56816
58062
|
var OUTPUT_FORMATS2 = ["text", "json"];
|
|
56817
58063
|
function isOutputFormat2(value) {
|
|
@@ -57261,7 +58507,10 @@ function parseQuestionnaireAnswersFile(raw, sourceLabel = "file") {
|
|
|
57261
58507
|
error: `${sourceLabel} must contain the answers object directly, not an { answers } wrapper. The CLI wraps it for the API.`
|
|
57262
58508
|
};
|
|
57263
58509
|
}
|
|
57264
|
-
const
|
|
58510
|
+
const withoutNulls = Object.fromEntries(
|
|
58511
|
+
Object.entries(parsed).filter(([, value]) => value !== null)
|
|
58512
|
+
);
|
|
58513
|
+
const result = PentestCustomerIntakeAnswersSchema.safeParse(withoutNulls);
|
|
57265
58514
|
if (!result.success) {
|
|
57266
58515
|
const issue2 = result.error.issues[0];
|
|
57267
58516
|
const path3 = issue2?.path.length ? issue2.path.join(".") : "answers";
|
|
@@ -57286,6 +58535,12 @@ function applyMultiSelectIntakeAnswer(current, key, selected) {
|
|
|
57286
58535
|
[key]: Array.from(new Set(selected)).sort()
|
|
57287
58536
|
};
|
|
57288
58537
|
}
|
|
58538
|
+
function mergeIntakeAnswers(base3, patch) {
|
|
58539
|
+
return normalizePentestCustomerIntakeAnswers({
|
|
58540
|
+
...base3 ?? {},
|
|
58541
|
+
...patch
|
|
58542
|
+
});
|
|
58543
|
+
}
|
|
57289
58544
|
function formatIssueClasses(values) {
|
|
57290
58545
|
return values.map((v) => INTAKE_ISSUE_CLASS_LABELS[v]).join(", ");
|
|
57291
58546
|
}
|
|
@@ -57317,22 +58572,113 @@ function formatAnswerValue(key, answers) {
|
|
|
57317
58572
|
}
|
|
57318
58573
|
}
|
|
57319
58574
|
}
|
|
58575
|
+
var NOT_ANSWERED = "(not answered)";
|
|
57320
58576
|
function formatQuestionnaireShow(input) {
|
|
57321
58577
|
const submitted = input.submitted === true;
|
|
57322
58578
|
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
|
-
|
|
58579
|
+
const answers = input.answers ?? {};
|
|
58580
|
+
const lines = [
|
|
58581
|
+
`Editable: ${editableLine}`,
|
|
58582
|
+
`Submitted: ${submitted ? "yes" : "no"}`,
|
|
58583
|
+
`Answers: ${countAnsweredIntakeQuestions(answers)} of ${PENTEST_CUSTOMER_INTAKE_QUESTION_COUNT} questions filled`
|
|
58584
|
+
];
|
|
57329
58585
|
for (const key of PENTEST_CUSTOMER_INTAKE_ANSWER_KEYS) {
|
|
57330
|
-
const value = formatAnswerValue(key,
|
|
57331
|
-
|
|
57332
|
-
|
|
58586
|
+
const value = formatAnswerValue(key, answers) ?? NOT_ANSWERED;
|
|
58587
|
+
lines.push(`${PENTEST_CUSTOMER_INTAKE_LABELS[key]} (${key}): ${value}`);
|
|
58588
|
+
}
|
|
58589
|
+
return lines.join("\n");
|
|
58590
|
+
}
|
|
58591
|
+
function buildQuestionnaireExport(answers) {
|
|
58592
|
+
const n3 = normalizePentestCustomerIntakeAnswers(answers ?? {});
|
|
58593
|
+
const text = (value) => value === void 0 || value.trim() === "" ? null : value;
|
|
58594
|
+
const list = (value) => value === void 0 || value.length === 0 ? null : [...value];
|
|
58595
|
+
return {
|
|
58596
|
+
issueClasses: list(n3.issueClasses),
|
|
58597
|
+
issueClassesNotes: text(n3.issueClassesNotes),
|
|
58598
|
+
focusAreas: text(n3.focusAreas),
|
|
58599
|
+
rolesAndPermissions: text(n3.rolesAndPermissions),
|
|
58600
|
+
authenticationSetup: text(n3.authenticationSetup),
|
|
58601
|
+
tenantIsolationExpectation: text(n3.tenantIsolationExpectation),
|
|
58602
|
+
surfaces: list(n3.surfaces),
|
|
58603
|
+
surfacesNotes: text(n3.surfacesNotes),
|
|
58604
|
+
thirdParties: text(n3.thirdParties),
|
|
58605
|
+
sensitiveData: text(n3.sensitiveData),
|
|
58606
|
+
neverTouch: text(n3.neverTouch),
|
|
58607
|
+
knownRisks: text(n3.knownRisks),
|
|
58608
|
+
tribalKnowledge: text(n3.tribalKnowledge)
|
|
58609
|
+
};
|
|
58610
|
+
}
|
|
58611
|
+
function buildQuestionnaireRecordJson(input) {
|
|
58612
|
+
const toIsoString = (value) => typeof value === "string" ? value : value.toISOString();
|
|
58613
|
+
return {
|
|
58614
|
+
pentestId: input.pentestId,
|
|
58615
|
+
editable: input.editable,
|
|
58616
|
+
lockedReason: input.lockedReason ?? null,
|
|
58617
|
+
submitted: input.submittedAt != null,
|
|
58618
|
+
submittedAt: input.submittedAt == null ? null : toIsoString(input.submittedAt),
|
|
58619
|
+
updatedAt: toIsoString(input.updatedAt),
|
|
58620
|
+
answers: buildQuestionnaireExport(input.answers)
|
|
58621
|
+
};
|
|
58622
|
+
}
|
|
58623
|
+
var DOCUMENTS_ATTACH_COMMAND = "tracecli pentest documents attach --pentest <pentestId> --file <path>";
|
|
58624
|
+
function buildQuestionnaireCatalog() {
|
|
58625
|
+
const questions = PENTEST_CUSTOMER_INTAKE_QUESTIONS.map(
|
|
58626
|
+
(entry, index) => {
|
|
58627
|
+
if (entry.kind === "text") {
|
|
58628
|
+
return {
|
|
58629
|
+
number: index + 1,
|
|
58630
|
+
key: entry.key,
|
|
58631
|
+
kind: "text",
|
|
58632
|
+
question: entry.question,
|
|
58633
|
+
helper: entry.helper ?? null,
|
|
58634
|
+
options: null,
|
|
58635
|
+
notes: null
|
|
58636
|
+
};
|
|
58637
|
+
}
|
|
58638
|
+
return {
|
|
58639
|
+
number: index + 1,
|
|
58640
|
+
key: entry.key,
|
|
58641
|
+
kind: "multiSelect",
|
|
58642
|
+
question: entry.question,
|
|
58643
|
+
helper: entry.helper ?? null,
|
|
58644
|
+
options: entry.key === "issueClasses" ? issueClassSelectOptions() : surfaceSelectOptions(),
|
|
58645
|
+
notes: { key: entry.notesKey, question: entry.notesQuestion }
|
|
58646
|
+
};
|
|
58647
|
+
}
|
|
58648
|
+
);
|
|
58649
|
+
return {
|
|
58650
|
+
questions,
|
|
58651
|
+
documents: {
|
|
58652
|
+
question: PENTEST_INTAKE_DOCUMENTS_QUESTION,
|
|
58653
|
+
command: DOCUMENTS_ATTACH_COMMAND,
|
|
58654
|
+
types: PentestIntakeDocumentTypeValues.map((value) => ({
|
|
58655
|
+
value,
|
|
58656
|
+
label: PENTEST_INTAKE_DOCUMENT_TYPE_LABELS[value]
|
|
58657
|
+
}))
|
|
58658
|
+
}
|
|
58659
|
+
};
|
|
58660
|
+
}
|
|
58661
|
+
function formatQuestionnaireCatalog(catalog) {
|
|
58662
|
+
const lines = [];
|
|
58663
|
+
for (const entry of catalog.questions) {
|
|
58664
|
+
lines.push(`${entry.number}. ${entry.question}`);
|
|
58665
|
+
if (entry.helper !== null) {
|
|
58666
|
+
lines.push(` ${entry.helper}`);
|
|
58667
|
+
}
|
|
58668
|
+
lines.push(` key: ${entry.key} (${entry.kind === "text" ? "text" : "multi-select"})`);
|
|
58669
|
+
if (entry.options !== null) {
|
|
58670
|
+
lines.push(` options: ${entry.options.map((o2) => `${o2.value} (${o2.label})`).join(", ")}`);
|
|
58671
|
+
}
|
|
58672
|
+
if (entry.notes !== null) {
|
|
58673
|
+
lines.push(` notes key: ${entry.notes.key} (text): ${entry.notes.question}`);
|
|
57333
58674
|
}
|
|
57334
|
-
lines.push(
|
|
58675
|
+
lines.push("");
|
|
57335
58676
|
}
|
|
58677
|
+
lines.push(`${catalog.questions.length + 1}. ${catalog.documents.question}`);
|
|
58678
|
+
lines.push(` Files, not an answers key. Attach with: ${catalog.documents.command}`);
|
|
58679
|
+
lines.push(
|
|
58680
|
+
` types: ${catalog.documents.types.map((t2) => `${t2.value} (${t2.label})`).join(", ")}`
|
|
58681
|
+
);
|
|
57336
58682
|
return lines.join("\n");
|
|
57337
58683
|
}
|
|
57338
58684
|
function issueClassSelectOptions() {
|
|
@@ -57431,7 +58777,7 @@ function formatFindingSeverity(severity) {
|
|
|
57431
58777
|
return severity;
|
|
57432
58778
|
}
|
|
57433
58779
|
}
|
|
57434
|
-
function
|
|
58780
|
+
function createListCommand7() {
|
|
57435
58781
|
return new Command("list").description("List pentests for your organization").option(
|
|
57436
58782
|
"--status <status>",
|
|
57437
58783
|
"Filter by status (SCOPING, IN_PROGRESS, REMEDIATION, CLOSED)",
|
|
@@ -57789,7 +59135,8 @@ function buildUpdatePentestBody(input) {
|
|
|
57789
59135
|
...input.title !== void 0 ? { title: input.title } : {},
|
|
57790
59136
|
...input.description !== void 0 ? { description: input.description } : {},
|
|
57791
59137
|
...input.customerConcernsContext !== void 0 ? { customerConcernsContext: input.customerConcernsContext } : {},
|
|
57792
|
-
...input.customerPriorityCategories !== void 0 ? { customerPriorityCategories: input.customerPriorityCategories } : {}
|
|
59138
|
+
...input.customerPriorityCategories !== void 0 ? { customerPriorityCategories: input.customerPriorityCategories } : {},
|
|
59139
|
+
...input.staffNotes !== void 0 ? { staffNotes: input.staffNotes } : {}
|
|
57793
59140
|
};
|
|
57794
59141
|
}
|
|
57795
59142
|
function isEmptyPentestUpdate(body) {
|
|
@@ -57891,12 +59238,15 @@ function printPentestUpdateDetail(pt) {
|
|
|
57891
59238
|
`${"Description:".padEnd(pad)} ${pt.description ? sanitizeDisplay(truncateForDisplay(pt.description)) : "\u2014"}`,
|
|
57892
59239
|
`${"Priorities:".padEnd(pad)} ${sanitizeDisplay(formatPriorities(pt.customerPriorityCategories))}`,
|
|
57893
59240
|
`${"Concerns:".padEnd(pad)} ${pt.customerConcernsContext ? sanitizeDisplay(truncateForDisplay(pt.customerConcernsContext)) : "\u2014"}`,
|
|
59241
|
+
`${"Staff notes:".padEnd(pad)} ${pt.staffNotes ? sanitizeDisplay(truncateForDisplay(pt.staffNotes)) : "\u2014"}`,
|
|
57894
59242
|
`${"Updated:".padEnd(pad)} ${toIso(pt.updatedAt)}`
|
|
57895
59243
|
].join("\n")
|
|
57896
59244
|
);
|
|
57897
59245
|
}
|
|
57898
59246
|
function printPentestUpdateTsv(pt) {
|
|
57899
|
-
console.log(
|
|
59247
|
+
console.log(
|
|
59248
|
+
"displayId title status phase description priorities concerns staffNotes updatedAt"
|
|
59249
|
+
);
|
|
57900
59250
|
console.log(
|
|
57901
59251
|
[
|
|
57902
59252
|
`PT-${pt.displayId}`,
|
|
@@ -57906,6 +59256,7 @@ function printPentestUpdateTsv(pt) {
|
|
|
57906
59256
|
tsvSafe2(sanitizeDisplay(pt.description ?? "")),
|
|
57907
59257
|
tsvSafe2(sanitizeDisplay(pt.customerPriorityCategories.join(","))),
|
|
57908
59258
|
tsvSafe2(sanitizeDisplay(pt.customerConcernsContext ?? "")),
|
|
59259
|
+
tsvSafe2(sanitizeDisplay(pt.staffNotes ?? "")),
|
|
57909
59260
|
toIso(pt.updatedAt)
|
|
57910
59261
|
].join(" ")
|
|
57911
59262
|
);
|
|
@@ -57942,16 +59293,26 @@ async function runUpdate5(pentestIdentifier, options) {
|
|
|
57942
59293
|
console.error(colorError(priorities.error));
|
|
57943
59294
|
process.exit(1);
|
|
57944
59295
|
}
|
|
59296
|
+
const staffNotes = resolveClearableOption(
|
|
59297
|
+
options.staffNotes,
|
|
59298
|
+
options.clearStaffNotes,
|
|
59299
|
+
"staff-notes"
|
|
59300
|
+
);
|
|
59301
|
+
if ("error" in staffNotes) {
|
|
59302
|
+
console.error(colorError(staffNotes.error));
|
|
59303
|
+
process.exit(1);
|
|
59304
|
+
}
|
|
57945
59305
|
const body = buildUpdatePentestBody({
|
|
57946
59306
|
title: options.title,
|
|
57947
59307
|
description: description.value,
|
|
57948
59308
|
customerConcernsContext: concerns.value,
|
|
57949
|
-
customerPriorityCategories: priorities.value
|
|
59309
|
+
customerPriorityCategories: priorities.value,
|
|
59310
|
+
staffNotes: staffNotes.value
|
|
57950
59311
|
});
|
|
57951
59312
|
if (isEmptyPentestUpdate(body)) {
|
|
57952
59313
|
console.error(
|
|
57953
59314
|
colorError(
|
|
57954
|
-
"Nothing to update. Pass at least one of --title, --description, --clear-description, --concerns, --clear-concerns, --priority, --clear-priorities."
|
|
59315
|
+
"Nothing to update. Pass at least one of --title, --description, --clear-description, --concerns, --clear-concerns, --priority, --clear-priorities, --staff-notes, --clear-staff-notes."
|
|
57955
59316
|
)
|
|
57956
59317
|
);
|
|
57957
59318
|
process.exit(1);
|
|
@@ -57964,19 +59325,30 @@ async function runUpdate5(pentestIdentifier, options) {
|
|
|
57964
59325
|
if (result.status !== SUCCESS_STATUS) {
|
|
57965
59326
|
failPentestUpdate(result.status, result.body, pentestIdentifier);
|
|
57966
59327
|
}
|
|
59328
|
+
if (staffNotesIgnoredByApi(body, result.body)) {
|
|
59329
|
+
console.error(
|
|
59330
|
+
colorError(
|
|
59331
|
+
"This API does not support staff notes yet, so they were not saved. Any other fields you passed were saved."
|
|
59332
|
+
)
|
|
59333
|
+
);
|
|
59334
|
+
process.exit(1);
|
|
59335
|
+
}
|
|
57967
59336
|
if (output === "table") {
|
|
57968
59337
|
console.log(colorSuccess(`Updated pentest PT-${result.body.displayId}`));
|
|
57969
59338
|
}
|
|
57970
59339
|
outputPentestUpdate(result.body, output);
|
|
57971
59340
|
}
|
|
59341
|
+
function staffNotesIgnoredByApi(body, response) {
|
|
59342
|
+
return body.staffNotes !== void 0 && !("staffNotes" in response);
|
|
59343
|
+
}
|
|
57972
59344
|
function createUpdateCommand4() {
|
|
57973
59345
|
return new Command("update").description(
|
|
57974
|
-
"Update pentest details and agent brief (staff-only: title, description, priorities, concerns)"
|
|
59346
|
+
"Update pentest details and agent brief (staff-only: title, description, priorities, concerns, staff notes)"
|
|
57975
59347
|
).argument("<pentestId>", "Pentest display ID (e.g. PT-3) or UUID").option("--title <title>", "Engagement title").option("--description <text>", "Engagement description").option("--clear-description", "Clear the engagement description").option("--concerns <text>", "Agent-brief concerns (threat-model context)").option("--clear-concerns", "Clear agent-brief concerns").option(
|
|
57976
59348
|
"--priority <category>",
|
|
57977
59349
|
`Agent-brief priority category (repeatable). One of: ${PRIORITY_LIST}`,
|
|
57978
59350
|
collectPriority
|
|
57979
|
-
).option("--clear-priorities", "Clear all agent-brief priority categories").option("-o, --output <format>", "Output format: table, json, tsv (default: table)", "table").addHelpText(
|
|
59351
|
+
).option("--clear-priorities", "Clear all agent-brief priority categories").option("--staff-notes <text>", "Guidance for the agent sessions on this pentest").option("--clear-staff-notes", "Clear the staff notes").option("-o, --output <format>", "Output format: table, json, tsv (default: table)", "table").addHelpText(
|
|
57980
59352
|
"after",
|
|
57981
59353
|
`
|
|
57982
59354
|
Staff-only. Requires a browser session from \`tracecli auth login\` with a Trace
|
|
@@ -57989,51 +59361,12 @@ Examples:
|
|
|
57989
59361
|
tracecli pentest update PT-3 --priority AUTHN_BYPASS --priority AUTHZ_IDOR
|
|
57990
59362
|
tracecli pentest update PT-3 --concerns "Worried about IDOR on invoice IDs"
|
|
57991
59363
|
tracecli pentest update PT-3 --clear-concerns --clear-priorities
|
|
59364
|
+
tracecli pentest update PT-3 --staff-notes "Staging is behind a Cloudflare challenge; use headed agent-browser"
|
|
57992
59365
|
tracecli pentest update PT-3 --title "Acme Q3" -o json`
|
|
57993
59366
|
).action(async (pentestId, options) => {
|
|
57994
59367
|
await runUpdate5(pentestId, options);
|
|
57995
59368
|
});
|
|
57996
59369
|
}
|
|
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
59370
|
async function fetchQuestionnaire(organizationId, pentestId, notFoundLabel) {
|
|
58038
59371
|
const result = await authenticatedRequest(
|
|
58039
59372
|
(client) => client.pentest.getCustomerPentestQuestionnaire({
|
|
@@ -58041,7 +59374,11 @@ async function fetchQuestionnaire(organizationId, pentestId, notFoundLabel) {
|
|
|
58041
59374
|
})
|
|
58042
59375
|
);
|
|
58043
59376
|
if (result.status !== SUCCESS_STATUS) {
|
|
58044
|
-
|
|
59377
|
+
failIntakeRequest(result.status, result.body, {
|
|
59378
|
+
subject: "questionnaire",
|
|
59379
|
+
action: "read",
|
|
59380
|
+
notFound: { what: "Pentest", label: notFoundLabel }
|
|
59381
|
+
});
|
|
58045
59382
|
}
|
|
58046
59383
|
return toQuestionnaireGetBody(result.body);
|
|
58047
59384
|
}
|
|
@@ -58054,7 +59391,11 @@ async function putQuestionnaire(organizationId, pentestId, answers, notFoundLabe
|
|
|
58054
59391
|
})
|
|
58055
59392
|
);
|
|
58056
59393
|
if (result.status !== SUCCESS_STATUS) {
|
|
58057
|
-
|
|
59394
|
+
failIntakeRequest(result.status, result.body, {
|
|
59395
|
+
subject: "questionnaire",
|
|
59396
|
+
action: "write",
|
|
59397
|
+
notFound: { what: "Pentest", label: notFoundLabel }
|
|
59398
|
+
});
|
|
58058
59399
|
}
|
|
58059
59400
|
return toQuestionnaireGetBody(result.body);
|
|
58060
59401
|
}
|
|
@@ -58066,7 +59407,11 @@ async function submitQuestionnaire(organizationId, pentestId, answers, notFoundL
|
|
|
58066
59407
|
})
|
|
58067
59408
|
);
|
|
58068
59409
|
if (result.status !== SUCCESS_STATUS) {
|
|
58069
|
-
|
|
59410
|
+
failIntakeRequest(result.status, result.body, {
|
|
59411
|
+
subject: "questionnaire",
|
|
59412
|
+
action: "write",
|
|
59413
|
+
notFound: { what: "Pentest", label: notFoundLabel }
|
|
59414
|
+
});
|
|
58070
59415
|
}
|
|
58071
59416
|
return toQuestionnaireGetBody(result.body);
|
|
58072
59417
|
}
|
|
@@ -58149,35 +59494,85 @@ async function promptQuestionnaireAnswers(existing) {
|
|
|
58149
59494
|
}
|
|
58150
59495
|
return working;
|
|
58151
59496
|
}
|
|
58152
|
-
|
|
58153
|
-
const
|
|
58154
|
-
|
|
58155
|
-
|
|
58156
|
-
|
|
58157
|
-
|
|
59497
|
+
function resolveQuestionnaireMode(options) {
|
|
59498
|
+
const exclusive = [
|
|
59499
|
+
{ flag: "--show", set: options.show === true },
|
|
59500
|
+
{ flag: "--questions", set: options.questions === true },
|
|
59501
|
+
{ flag: "--export", set: options.export === true },
|
|
59502
|
+
{ flag: "--file", set: options.file !== void 0 }
|
|
59503
|
+
];
|
|
59504
|
+
const chosen = exclusive.filter((entry) => entry.set);
|
|
59505
|
+
if (chosen.length > 1) {
|
|
59506
|
+
console.error(colorError(`Pass only one of ${chosen.map((c84) => c84.flag).join(" / ")}.`));
|
|
59507
|
+
process.exit(1);
|
|
59508
|
+
}
|
|
59509
|
+
const only = chosen[0];
|
|
59510
|
+
if (only !== void 0 && only.flag !== "--file" && options.submit === true) {
|
|
59511
|
+
console.error(colorError(`Pass only one of ${only.flag} / --submit.`));
|
|
58158
59512
|
process.exit(1);
|
|
58159
59513
|
}
|
|
58160
|
-
if (
|
|
58161
|
-
console.error(colorError("
|
|
59514
|
+
if (options.merge === true && options.file === void 0) {
|
|
59515
|
+
console.error(colorError("--merge needs --file <path>."));
|
|
58162
59516
|
process.exit(1);
|
|
58163
59517
|
}
|
|
59518
|
+
if (options.show) return { kind: "show" };
|
|
59519
|
+
if (options.questions) return { kind: "questions" };
|
|
59520
|
+
if (options.export) return { kind: "export" };
|
|
59521
|
+
if (options.file !== void 0) {
|
|
59522
|
+
return { kind: "file", path: options.file, merge: options.merge === true };
|
|
59523
|
+
}
|
|
59524
|
+
if (options.submit) return { kind: "submit" };
|
|
59525
|
+
return { kind: "interactive" };
|
|
59526
|
+
}
|
|
59527
|
+
function printQuestionnaireRecord(body, output, successLine) {
|
|
59528
|
+
if (output === "json") {
|
|
59529
|
+
printJson(buildQuestionnaireRecordJson(body));
|
|
59530
|
+
return;
|
|
59531
|
+
}
|
|
59532
|
+
if (successLine !== void 0) {
|
|
59533
|
+
console.log(colorSuccess(successLine));
|
|
59534
|
+
}
|
|
59535
|
+
console.log(formatQuestionnaireShow(questionnaireShowInput(body)));
|
|
59536
|
+
}
|
|
59537
|
+
function submittedLine(record2) {
|
|
59538
|
+
return record2.editable ? "Submitted questionnaire. You can edit until testing starts." : "Submitted questionnaire.";
|
|
59539
|
+
}
|
|
59540
|
+
async function runQuestionnaire(pentestIdentifier, options) {
|
|
59541
|
+
const mode = resolveQuestionnaireMode(options);
|
|
59542
|
+
const output = parseTextOutput(options.output);
|
|
59543
|
+
const submit = options.submit === true;
|
|
59544
|
+
if (mode.kind === "questions") {
|
|
59545
|
+
const catalog = buildQuestionnaireCatalog();
|
|
59546
|
+
if (output === "json") {
|
|
59547
|
+
printJson(catalog);
|
|
59548
|
+
} else {
|
|
59549
|
+
console.log(formatQuestionnaireCatalog(catalog));
|
|
59550
|
+
}
|
|
59551
|
+
return;
|
|
59552
|
+
}
|
|
58164
59553
|
const isInteractiveTty = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
58165
|
-
if (
|
|
59554
|
+
if (mode.kind === "interactive" && !isInteractiveTty) {
|
|
58166
59555
|
console.error(
|
|
58167
59556
|
colorError(
|
|
58168
|
-
"Interactive questionnaire requires a TTY.
|
|
59557
|
+
"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
59558
|
)
|
|
58170
59559
|
);
|
|
58171
59560
|
process.exit(2);
|
|
58172
59561
|
}
|
|
58173
59562
|
const organizationId = await resolveOrganizationId();
|
|
58174
59563
|
const pentestId = await resolvePentestId(organizationId, pentestIdentifier);
|
|
58175
|
-
if (show) {
|
|
59564
|
+
if (mode.kind === "show") {
|
|
58176
59565
|
const current2 = await fetchQuestionnaire(organizationId, pentestId, pentestIdentifier);
|
|
58177
|
-
|
|
59566
|
+
printQuestionnaireRecord(current2, output);
|
|
58178
59567
|
return;
|
|
58179
59568
|
}
|
|
58180
|
-
if (
|
|
59569
|
+
if (mode.kind === "export") {
|
|
59570
|
+
const current2 = await fetchQuestionnaire(organizationId, pentestId, pentestIdentifier);
|
|
59571
|
+
printJson(buildQuestionnaireExport(current2.answers));
|
|
59572
|
+
return;
|
|
59573
|
+
}
|
|
59574
|
+
if (mode.kind === "file") {
|
|
59575
|
+
const filePath = mode.path;
|
|
58181
59576
|
let raw;
|
|
58182
59577
|
try {
|
|
58183
59578
|
raw = readFileSync7(filePath, "utf8");
|
|
@@ -58190,28 +59585,27 @@ async function runQuestionnaire(pentestIdentifier, options) {
|
|
|
58190
59585
|
console.error(colorError(parsed.error));
|
|
58191
59586
|
process.exit(1);
|
|
58192
59587
|
}
|
|
58193
|
-
|
|
58194
|
-
|
|
58195
|
-
|
|
58196
|
-
|
|
58197
|
-
|
|
59588
|
+
let answers = parsed.answers;
|
|
59589
|
+
if (mode.merge) {
|
|
59590
|
+
const current2 = await fetchQuestionnaire(organizationId, pentestId, pentestIdentifier);
|
|
59591
|
+
answers = mergeIntakeAnswers(current2.answers, parsed.answers);
|
|
59592
|
+
}
|
|
59593
|
+
const saved2 = submit ? await submitQuestionnaire(organizationId, pentestId, answers, pentestIdentifier) : await putQuestionnaire(organizationId, pentestId, answers, pentestIdentifier);
|
|
59594
|
+
printQuestionnaireRecord(
|
|
59595
|
+
saved2,
|
|
59596
|
+
output,
|
|
59597
|
+
submit ? submittedLine(saved2) : "Saved questionnaire answers."
|
|
58198
59598
|
);
|
|
58199
|
-
console.log(formatQuestionnaireShow(questionnaireShowInput(saved2)));
|
|
58200
59599
|
return;
|
|
58201
59600
|
}
|
|
58202
|
-
if (submit) {
|
|
59601
|
+
if (mode.kind === "submit") {
|
|
58203
59602
|
const submitted = await submitQuestionnaire(
|
|
58204
59603
|
organizationId,
|
|
58205
59604
|
pentestId,
|
|
58206
59605
|
void 0,
|
|
58207
59606
|
pentestIdentifier
|
|
58208
59607
|
);
|
|
58209
|
-
|
|
58210
|
-
colorSuccess(
|
|
58211
|
-
submitted.editable ? "Submitted questionnaire. You can edit until testing starts." : "Submitted questionnaire."
|
|
58212
|
-
)
|
|
58213
|
-
);
|
|
58214
|
-
console.log(formatQuestionnaireShow(questionnaireShowInput(submitted)));
|
|
59608
|
+
printQuestionnaireRecord(submitted, output, submittedLine(submitted));
|
|
58215
59609
|
return;
|
|
58216
59610
|
}
|
|
58217
59611
|
const current = await fetchQuestionnaire(organizationId, pentestId, pentestIdentifier);
|
|
@@ -58225,32 +59619,48 @@ async function runQuestionnaire(pentestIdentifier, options) {
|
|
|
58225
59619
|
}
|
|
58226
59620
|
const merged = await promptQuestionnaireAnswers(current.answers);
|
|
58227
59621
|
const saved = await putQuestionnaire(organizationId, pentestId, merged, pentestIdentifier);
|
|
58228
|
-
|
|
58229
|
-
console.log(formatQuestionnaireShow(questionnaireShowInput(saved)));
|
|
59622
|
+
printQuestionnaireRecord(saved, output, "Saved questionnaire answers.");
|
|
58230
59623
|
}
|
|
58231
59624
|
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
|
|
59625
|
+
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(
|
|
59626
|
+
"--questions",
|
|
59627
|
+
"Print the question catalog: keys, kinds, option values (no auth needed)"
|
|
59628
|
+
).option("--export", "Print the current answers as JSON, every key present, blanks as null").option(
|
|
58233
59629
|
"--file <path>",
|
|
58234
|
-
"Upload answers from a JSON file (answers object
|
|
58235
|
-
).option(
|
|
59630
|
+
"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"
|
|
59631
|
+
).option(
|
|
59632
|
+
"--merge",
|
|
59633
|
+
"With --file: change only the keys the file sets; keep every other saved answer"
|
|
59634
|
+
).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
59635
|
"after",
|
|
58237
59636
|
`
|
|
58238
|
-
Optional scoping questionnaire
|
|
58239
|
-
browser session
|
|
58240
|
-
|
|
58241
|
-
|
|
59637
|
+
Optional scoping questionnaire, editable until testing starts.
|
|
59638
|
+
Every mode but --questions needs a browser session from \`tracecli auth login\`;
|
|
59639
|
+
API keys are rejected. Writes (--file, --submit, the TTY fill) also need an
|
|
59640
|
+
organization admin (or Trace staff).
|
|
58242
59641
|
|
|
58243
59642
|
Modes:
|
|
58244
|
-
--
|
|
58245
|
-
--
|
|
59643
|
+
--questions The question catalog with answer keys and option values
|
|
59644
|
+
--show GET and print every question with its key and current answer
|
|
59645
|
+
--export GET and print the answers object as JSON (all keys, null when blank)
|
|
59646
|
+
--file <path> PUT the file's answers object, replacing every saved answer;
|
|
59647
|
+
add --merge to change only the keys the file sets
|
|
58246
59648
|
--submit Mark as done (combine with --file or TTY fill, or alone)
|
|
58247
59649
|
(TTY default) load-merge-write: GET, prompt each question, confirm, PUT
|
|
58248
59650
|
|
|
58249
|
-
Non-interactive shells must pass
|
|
59651
|
+
Every mode takes -o json. Non-interactive shells must pass one of the flags
|
|
59652
|
+
above (exit 2 otherwise).
|
|
59653
|
+
|
|
59654
|
+
Scripted fill (what a coding agent runs):
|
|
59655
|
+
tracecli pentest questionnaire PT-1 --questions
|
|
59656
|
+
tracecli pentest questionnaire PT-1 --export > answers.json
|
|
59657
|
+
# fill in the null values the code can answer, then
|
|
59658
|
+
tracecli pentest questionnaire PT-1 --file answers.json --merge
|
|
59659
|
+
tracecli pentest documents attach --pentest PT-1 --file ./threat-model.pdf
|
|
58250
59660
|
|
|
58251
59661
|
Examples:
|
|
58252
59662
|
tracecli pentest questionnaire PT-1 --show
|
|
58253
|
-
tracecli pentest questionnaire PT-1 --
|
|
59663
|
+
tracecli pentest questionnaire PT-1 --show -o json
|
|
58254
59664
|
tracecli pentest questionnaire PT-1 --file answers.json --submit
|
|
58255
59665
|
tracecli pentest questionnaire PT-1 --submit
|
|
58256
59666
|
tracecli pentest questionnaire PT-1`
|
|
@@ -58260,11 +59670,12 @@ Examples:
|
|
|
58260
59670
|
}
|
|
58261
59671
|
function createPentestCommand() {
|
|
58262
59672
|
const pentest = new Command("pentest").description("Manage pentests");
|
|
58263
|
-
pentest.addCommand(
|
|
59673
|
+
pentest.addCommand(createListCommand7());
|
|
58264
59674
|
pentest.addCommand(createUpdateCommand4());
|
|
58265
59675
|
pentest.addCommand(createCreatePentestCommand(), { hidden: true });
|
|
58266
59676
|
pentest.addCommand(createVisibilityCommand(), { hidden: true });
|
|
58267
59677
|
pentest.addCommand(createQuestionnaireCommand());
|
|
59678
|
+
pentest.addCommand(createDocumentsCommand());
|
|
58268
59679
|
pentest.addCommand(createFindingsCommand());
|
|
58269
59680
|
pentest.addCommand(createHypothesesCommand(), { hidden: true });
|
|
58270
59681
|
pentest.addCommand(createReportCommand());
|
|
@@ -58901,7 +60312,7 @@ Examples:
|
|
|
58901
60312
|
// src/upload/download.ts
|
|
58902
60313
|
import { createHash as createHash2 } from "crypto";
|
|
58903
60314
|
import { open as open3, rename as rename2, rm as rm2, stat as stat2 } from "fs/promises";
|
|
58904
|
-
import { basename as
|
|
60315
|
+
import { basename as basename6, join as join8, resolve as resolve2 } from "path";
|
|
58905
60316
|
var CodeUploadDownloadError = class extends Error {
|
|
58906
60317
|
constructor(message) {
|
|
58907
60318
|
super(message);
|
|
@@ -58911,7 +60322,7 @@ var CodeUploadDownloadError = class extends Error {
|
|
|
58911
60322
|
function safeDownloadFilename(upload) {
|
|
58912
60323
|
const extension = requiredExtensionForKind(upload.artifactKind);
|
|
58913
60324
|
const fallback = `app${extension}`;
|
|
58914
|
-
const stripped = Array.from(
|
|
60325
|
+
const stripped = Array.from(basename6(upload.originalFilename.replace(/\\/g, "/")), (char) => {
|
|
58915
60326
|
const code = char.codePointAt(0) ?? 0;
|
|
58916
60327
|
return code <= 31 || code === 127 ? "_" : char;
|
|
58917
60328
|
}).join("").trim();
|
|
@@ -59326,13 +60737,13 @@ function buildUploadsTable(uploads) {
|
|
|
59326
60737
|
size: formatArchiveBytes(upload.sizeBytes),
|
|
59327
60738
|
id: upload.codeUploadId
|
|
59328
60739
|
}));
|
|
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((
|
|
60740
|
+
const nameWidth = Math.max(4, ...cells.map((c84) => c84.name.length));
|
|
60741
|
+
const kindWidth = Math.max(4, ...cells.map((c84) => c84.kind.length));
|
|
60742
|
+
const statusWidth = Math.max(6, ...cells.map((c84) => c84.status.length));
|
|
60743
|
+
const sizeWidth = Math.max(4, ...cells.map((c84) => c84.size.length));
|
|
59333
60744
|
const header = `${"NAME".padEnd(nameWidth)} ${"KIND".padEnd(kindWidth)} ${"STATUS".padEnd(statusWidth)} ${"SIZE".padEnd(sizeWidth)} ID`;
|
|
59334
60745
|
const rows = cells.map(
|
|
59335
|
-
(
|
|
60746
|
+
(c84) => `${c84.name.padEnd(nameWidth)} ${c84.kind.padEnd(kindWidth)} ${c84.status.padEnd(statusWidth)} ${c84.size.padEnd(sizeWidth)} ${c84.id}`
|
|
59336
60747
|
);
|
|
59337
60748
|
return { header, rows };
|
|
59338
60749
|
}
|
|
@@ -59686,7 +61097,7 @@ async function runResume(codeUploadIdArg, filePath, options, runtime) {
|
|
|
59686
61097
|
}
|
|
59687
61098
|
outputUpload(upload, output);
|
|
59688
61099
|
}
|
|
59689
|
-
async function
|
|
61100
|
+
async function runList6(options) {
|
|
59690
61101
|
const output = parseOutput(options.output);
|
|
59691
61102
|
if (options.web) {
|
|
59692
61103
|
const slug = await resolveOrganizationSlug();
|
|
@@ -59819,7 +61230,7 @@ async function runDelete5(codeUploadIdArg, options) {
|
|
|
59819
61230
|
}
|
|
59820
61231
|
console.log(colorSuccess(`Deleted upload ${codeUploadId}`));
|
|
59821
61232
|
}
|
|
59822
|
-
async function
|
|
61233
|
+
async function runLink2(options) {
|
|
59823
61234
|
const output = parseOutput(options.output);
|
|
59824
61235
|
const applicationId = requireUuid(options.app, "--app");
|
|
59825
61236
|
const codeUploadId = requireUuid(options.upload, "--upload");
|
|
@@ -59901,9 +61312,9 @@ Examples:
|
|
|
59901
61312
|
await runBuild(file2, options, runtime);
|
|
59902
61313
|
});
|
|
59903
61314
|
}
|
|
59904
|
-
function
|
|
61315
|
+
function createListCommand8() {
|
|
59905
61316
|
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
|
|
61317
|
+
await runList6(options);
|
|
59907
61318
|
});
|
|
59908
61319
|
}
|
|
59909
61320
|
function createGetCommand4() {
|
|
@@ -59951,7 +61362,7 @@ function createDeleteCommand5() {
|
|
|
59951
61362
|
await runDelete5(codeUploadId, options);
|
|
59952
61363
|
});
|
|
59953
61364
|
}
|
|
59954
|
-
function
|
|
61365
|
+
function createLinkCommand2() {
|
|
59955
61366
|
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
61367
|
"after",
|
|
59957
61368
|
`
|
|
@@ -59961,7 +61372,7 @@ APK and IPA builds attach to an application with \`upload build --app\`.
|
|
|
59961
61372
|
Examples:
|
|
59962
61373
|
tracecli upload link --app <applicationId> --upload <codeUploadId>`
|
|
59963
61374
|
).action(async (options) => {
|
|
59964
|
-
await
|
|
61375
|
+
await runLink2(options);
|
|
59965
61376
|
});
|
|
59966
61377
|
}
|
|
59967
61378
|
function createUploadCommand(runtime = {}) {
|
|
@@ -59969,13 +61380,13 @@ function createUploadCommand(runtime = {}) {
|
|
|
59969
61380
|
const upload = new Command("upload").alias("code-upload").description("Upload source archives and mobile builds for pentest; download builds back");
|
|
59970
61381
|
upload.addCommand(createCreateCommand5(resolved));
|
|
59971
61382
|
upload.addCommand(createBuildCommand(resolved));
|
|
59972
|
-
upload.addCommand(
|
|
61383
|
+
upload.addCommand(createListCommand8());
|
|
59973
61384
|
upload.addCommand(createGetCommand4());
|
|
59974
61385
|
upload.addCommand(createDownloadCommand(resolved));
|
|
59975
61386
|
upload.addCommand(createResumeCommand(resolved));
|
|
59976
61387
|
upload.addCommand(createWaitCommand(resolved));
|
|
59977
61388
|
upload.addCommand(createDeleteCommand5());
|
|
59978
|
-
upload.addCommand(
|
|
61389
|
+
upload.addCommand(createLinkCommand2());
|
|
59979
61390
|
return upload;
|
|
59980
61391
|
}
|
|
59981
61392
|
|
|
@@ -60214,7 +61625,7 @@ Run \`tracecli vulnerabilities list\` to see available vulnerabilities.`
|
|
|
60214
61625
|
printVulnerabilityDetail(result.body);
|
|
60215
61626
|
});
|
|
60216
61627
|
}
|
|
60217
|
-
function
|
|
61628
|
+
function createListCommand9() {
|
|
60218
61629
|
return new Command("list").description("List vulnerabilities for your organization").option("--repo <repo>", "Filter by repository name or owner/repo").option(
|
|
60219
61630
|
"--severity <severity>",
|
|
60220
61631
|
"Filter by severity (CRITICAL, HIGH, MEDIUM, LOW)",
|
|
@@ -60348,7 +61759,7 @@ Examples:
|
|
|
60348
61759
|
function createVulnerabilitiesCommand() {
|
|
60349
61760
|
const vulnerabilities = new Command("vulnerabilities").description("Manage vulnerabilities");
|
|
60350
61761
|
vulnerabilities.addCommand(createGetCommand5());
|
|
60351
|
-
vulnerabilities.addCommand(
|
|
61762
|
+
vulnerabilities.addCommand(createListCommand9());
|
|
60352
61763
|
return vulnerabilities;
|
|
60353
61764
|
}
|
|
60354
61765
|
|