securewithtrace 0.39.0 → 0.40.1
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 +387 -70
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -22144,7 +22144,7 @@ var AgentSessionFailureReasonValues = [
|
|
|
22144
22144
|
"INTERNAL"
|
|
22145
22145
|
];
|
|
22146
22146
|
var AgentSessionFailureReason = external_exports.enum(AgentSessionFailureReasonValues);
|
|
22147
|
-
var AgentSessionProviderValues = ["BEDROCK", "ANTHROPIC"];
|
|
22147
|
+
var AgentSessionProviderValues = ["BEDROCK", "ANTHROPIC", "OPENAI"];
|
|
22148
22148
|
var AgentSessionProvider = external_exports.enum(AgentSessionProviderValues);
|
|
22149
22149
|
var AgentSessionEventKindValues = [
|
|
22150
22150
|
"AGENT_TEXT",
|
|
@@ -22283,6 +22283,9 @@ var AgentSessionSummarySchema = external_exports.object({
|
|
|
22283
22283
|
status: AgentSessionStatus,
|
|
22284
22284
|
waitingReason: AgentSessionWaitingReason.nullable(),
|
|
22285
22285
|
failureReason: AgentSessionFailureReason.nullable(),
|
|
22286
|
+
/** Sanitized, shape-only failure description (e.g. a provider refusal message) — never customer
|
|
22287
|
+
* data. In the summary so the staff console can show WHY a listed session failed. */
|
|
22288
|
+
failureDetails: external_exports.string().nullable(),
|
|
22286
22289
|
objective: external_exports.string(),
|
|
22287
22290
|
provider: AgentSessionProvider,
|
|
22288
22291
|
model: external_exports.string(),
|
|
@@ -22305,8 +22308,6 @@ var AgentSessionRepositoryPinSchema = external_exports.object({
|
|
|
22305
22308
|
commitSha: external_exports.string()
|
|
22306
22309
|
});
|
|
22307
22310
|
var AgentSessionDetailSchema = AgentSessionSummarySchema.extend({
|
|
22308
|
-
/** Sanitized, shape-only failure description — never customer data. */
|
|
22309
|
-
failureDetails: external_exports.string().nullable(),
|
|
22310
22311
|
sdkSessionId: external_exports.string().nullable(),
|
|
22311
22312
|
promptVersion: external_exports.string(),
|
|
22312
22313
|
toolsetVersion: external_exports.string(),
|
|
@@ -25006,34 +25007,6 @@ var PentestDetailResponseSchema = PentestResponseSchema.extend({
|
|
|
25006
25007
|
testingWindowStart: external_exports.coerce.date().nullable().optional(),
|
|
25007
25008
|
testingWindowEnd: external_exports.coerce.date().nullable().optional(),
|
|
25008
25009
|
engagementMetadataOverrides: EngagementMetadataOverridesSchema.optional(),
|
|
25009
|
-
/**
|
|
25010
|
-
* True once the engagement's repository scope has been snapshotted, which
|
|
25011
|
-
* PERMANENTLY refuses bind and unbind (`repositoryScopes > 0` in
|
|
25012
|
-
* `requireBindableInLock`).
|
|
25013
|
-
*
|
|
25014
|
-
* This one refusal only — that guard also refuses on `PENTEST_CLOSED` and on a
|
|
25015
|
-
* non-scoping stage, both of which the client can already read off `status` and
|
|
25016
|
-
* `currentStage` on this same response. So `false` means "not frozen by the
|
|
25017
|
-
* snapshot", NOT "a bind will succeed"; a consumer that gates a control must check
|
|
25018
|
-
* the stage and closed state too.
|
|
25019
|
-
*
|
|
25020
|
-
* Needed as its OWN field because nothing else on the wire answers it: the scope
|
|
25021
|
-
* freezes at the first scope-consuming session create, and only VALIDATION moves
|
|
25022
|
-
* the stage, so RECON / CODE_REVIEW / PRODUCT_RECON leave the engagement in
|
|
25023
|
-
* `DRAFT` with permanently frozen bindings. `getRepositoryScope` reports
|
|
25024
|
-
* `source: 'PENTEST_SCOPE'` only PAST the scoping stages, so a frozen `DRAFT`
|
|
25025
|
-
* engagement still returns live application links there.
|
|
25026
|
-
*
|
|
25027
|
-
* Scoped to BINDINGS, not to the whole target: environment add/remove is gated on
|
|
25028
|
-
* stage alone (`PentestEnvironmentService`, `NON_SCOPING_PENTEST_STAGES`), and
|
|
25029
|
-
* `DRAFT` is a scoping stage — so a consumer must not disable environment editing
|
|
25030
|
-
* on this flag. Doing so would remove the one provisioning action a frozen
|
|
25031
|
-
* multi-application engagement still legitimately needs.
|
|
25032
|
-
*
|
|
25033
|
-
* Detail-only (the list schema deliberately carries no per-pentest aggregates) and
|
|
25034
|
-
* `.optional()` for the same rolling-deploy reason as the fields above.
|
|
25035
|
-
*/
|
|
25036
|
-
applicationBindingFrozen: external_exports.boolean().optional(),
|
|
25037
25010
|
/**
|
|
25038
25011
|
* Optional customer intake questionnaire answers. Staff detail only.
|
|
25039
25012
|
* `.optional()` for rolling-deploy safety (Vercel ahead of ECS).
|
|
@@ -25481,7 +25454,18 @@ var ClaimedDesktopActionSchema = external_exports.object({
|
|
|
25481
25454
|
kind: external_exports.enum(DesktopActionKindValues),
|
|
25482
25455
|
request: external_exports.unknown(),
|
|
25483
25456
|
/** Epoch millis; past this the box abandons the work and reports nothing. */
|
|
25484
|
-
expiresAtMs: external_exports.number().int().positive()
|
|
25457
|
+
expiresAtMs: external_exports.number().int().positive(),
|
|
25458
|
+
/**
|
|
25459
|
+
* A presigned PUT for the ONE artifact this kind can produce, issued with the
|
|
25460
|
+
* claim so an action does not spend a second api round trip asking for it.
|
|
25461
|
+
* The key is target-relative and travels back on `complete` unchanged.
|
|
25462
|
+
*
|
|
25463
|
+
* Null when the api has no artifacts bucket configured, and absent from an
|
|
25464
|
+
* api that predates this. The box presigns for itself in both cases, and also
|
|
25465
|
+
* whenever the PUT is refused, which is what keeps the 15-minute URL safe to
|
|
25466
|
+
* issue at claim time for an action that may run longer than that.
|
|
25467
|
+
*/
|
|
25468
|
+
artifactUpload: external_exports.object({ key: external_exports.string().min(1).max(1024), url: external_exports.url() }).nullable().optional()
|
|
25485
25469
|
});
|
|
25486
25470
|
var DesktopTargetClaimResponseSchema = external_exports.object({
|
|
25487
25471
|
action: ClaimedDesktopActionSchema.nullable(),
|
|
@@ -25501,7 +25485,18 @@ var DesktopTargetCompleteRequestSchema = external_exports.object({
|
|
|
25501
25485
|
*/
|
|
25502
25486
|
resultS3Key: external_exports.string().min(1).max(1024).optional(),
|
|
25503
25487
|
/** Shape-only. Never customer data. */
|
|
25504
|
-
failureDetails: external_exports.string().max(2e3).optional()
|
|
25488
|
+
failureDetails: external_exports.string().max(2e3).optional(),
|
|
25489
|
+
/**
|
|
25490
|
+
* What the box spent, as a SIBLING of `result` rather than a member of it.
|
|
25491
|
+
*
|
|
25492
|
+
* Two reasons it cannot live inside `result`: the api parses that against the
|
|
25493
|
+
* kind's own schema, which strips anything the schema does not declare, and
|
|
25494
|
+
* the box sizes its inline output against the 32 KB cap before this is known.
|
|
25495
|
+
*/
|
|
25496
|
+
rigTimings: external_exports.object({
|
|
25497
|
+
executeMs: external_exports.number().int().nonnegative(),
|
|
25498
|
+
uploadMs: external_exports.number().int().nonnegative()
|
|
25499
|
+
}).optional()
|
|
25505
25500
|
});
|
|
25506
25501
|
var DesktopTargetCompleteResponseSchema = external_exports.object({ ok: external_exports.literal(true) });
|
|
25507
25502
|
var DesktopTargetPresignRequestSchema = external_exports.object({
|
|
@@ -32310,12 +32305,15 @@ var StaffOrganizationSummarySchema = external_exports.object({
|
|
|
32310
32305
|
var ListAllOrganizationsResponseSchema = external_exports.array(StaffOrganizationSummarySchema);
|
|
32311
32306
|
var CreateOrganizationRequestSchema = external_exports.object({
|
|
32312
32307
|
name: external_exports.string().trim().min(2, "Organization name must be at least 2 characters").max(100, "Organization name must be 100 characters or fewer"),
|
|
32313
|
-
referrerId: external_exports.uuid().nullable().optional()
|
|
32308
|
+
referrerId: external_exports.uuid().nullable().optional(),
|
|
32309
|
+
homepageUrl: external_exports.url("Homepage must be a full URL, like https://acme.com").max(2048).nullable().optional()
|
|
32314
32310
|
});
|
|
32311
|
+
var LogoFetchOutcomeValues = ["skipped", "stored", "not_found", "failed"];
|
|
32315
32312
|
var CreateOrganizationResponseSchema = external_exports.object({
|
|
32316
32313
|
organizationId: external_exports.uuid(),
|
|
32317
32314
|
slug: external_exports.string(),
|
|
32318
|
-
name: external_exports.string()
|
|
32315
|
+
name: external_exports.string(),
|
|
32316
|
+
logoFetch: external_exports.enum(LogoFetchOutcomeValues)
|
|
32319
32317
|
});
|
|
32320
32318
|
var OnboardingItemValues = [
|
|
32321
32319
|
"INVITE_MEMBERS",
|
|
@@ -33183,10 +33181,9 @@ var pentestContract = c27.router({
|
|
|
33183
33181
|
401: ErrorResponseSchema,
|
|
33184
33182
|
403: ErrorResponseSchema,
|
|
33185
33183
|
404: ErrorResponseSchema,
|
|
33186
|
-
// 409
|
|
33187
|
-
//
|
|
33188
|
-
//
|
|
33189
|
-
// so a new binding could never enter it).
|
|
33184
|
+
// 409: PENTEST_APPLICATION_ALREADY_BOUND (unique violation), PENTEST_CLOSED,
|
|
33185
|
+
// or PENTEST_APPLICATION_WITHOUT_REPOSITORY (the repository scope is already
|
|
33186
|
+
// snapshotted and this application has no scan-enabled repository to add).
|
|
33190
33187
|
409: ErrorResponseSchema,
|
|
33191
33188
|
500: ErrorResponseSchema
|
|
33192
33189
|
}
|
|
@@ -33202,7 +33199,7 @@ var pentestContract = c27.router({
|
|
|
33202
33199
|
403: ErrorResponseSchema,
|
|
33203
33200
|
404: ErrorResponseSchema,
|
|
33204
33201
|
// 409 — PENTEST_APPLICATION_UNBIND_BLOCKED (this application still has
|
|
33205
|
-
// linked environments)
|
|
33202
|
+
// linked environments) or PENTEST_CLOSED.
|
|
33206
33203
|
409: ErrorResponseSchema,
|
|
33207
33204
|
500: ErrorResponseSchema
|
|
33208
33205
|
}
|
|
@@ -41589,6 +41586,323 @@ ${b}
|
|
|
41589
41586
|
};
|
|
41590
41587
|
var i = `${styleText2("gray", S_BAR)} `;
|
|
41591
41588
|
|
|
41589
|
+
// src/org/update.ts
|
|
41590
|
+
import { readFileSync as readFileSync2, statSync } from "fs";
|
|
41591
|
+
import { basename, extname } from "path";
|
|
41592
|
+
var LOGO_CONTENT_TYPE_BY_EXTENSION = {
|
|
41593
|
+
".png": "image/png",
|
|
41594
|
+
".jpg": "image/jpeg",
|
|
41595
|
+
".jpeg": "image/jpeg",
|
|
41596
|
+
".gif": "image/gif",
|
|
41597
|
+
".svg": "image/svg+xml",
|
|
41598
|
+
".webp": "image/webp"
|
|
41599
|
+
};
|
|
41600
|
+
var SUPPORTED_LOGO_EXTENSIONS = Object.keys(
|
|
41601
|
+
LOGO_CONTENT_TYPE_BY_EXTENSION
|
|
41602
|
+
);
|
|
41603
|
+
var MAX_LOGO_UPLOAD_BYTES = 2 * 1024 * 1024;
|
|
41604
|
+
function logoContentTypeForFile(path) {
|
|
41605
|
+
return LOGO_CONTENT_TYPE_BY_EXTENSION[extname(path).toLowerCase()] ?? null;
|
|
41606
|
+
}
|
|
41607
|
+
function isHttpUrl(value) {
|
|
41608
|
+
try {
|
|
41609
|
+
const parsed = new URL(value);
|
|
41610
|
+
return parsed.protocol === "http:" || parsed.protocol === "https:";
|
|
41611
|
+
} catch {
|
|
41612
|
+
return false;
|
|
41613
|
+
}
|
|
41614
|
+
}
|
|
41615
|
+
var URL_SCHEME_PATTERN = /^[a-z][a-z0-9+.-]*:\/\//i;
|
|
41616
|
+
function resolveLogoValue(value, flag) {
|
|
41617
|
+
const trimmed = value.trim();
|
|
41618
|
+
if (trimmed.length === 0) {
|
|
41619
|
+
return { error: `--${flag} cannot be empty. Pass an image file path or an http(s) URL.` };
|
|
41620
|
+
}
|
|
41621
|
+
if (isHttpUrl(trimmed)) {
|
|
41622
|
+
return { change: { action: "url", url: trimmed } };
|
|
41623
|
+
}
|
|
41624
|
+
if (URL_SCHEME_PATTERN.test(trimmed)) {
|
|
41625
|
+
return {
|
|
41626
|
+
error: `--${flag} '${sanitizeDisplay(trimmed)}' must be an http(s) URL or a file path.`
|
|
41627
|
+
};
|
|
41628
|
+
}
|
|
41629
|
+
const contentType = logoContentTypeForFile(trimmed);
|
|
41630
|
+
if (!contentType) {
|
|
41631
|
+
return {
|
|
41632
|
+
error: `--${flag} '${sanitizeDisplay(trimmed)}' has an unsupported extension. Use one of: ${SUPPORTED_LOGO_EXTENSIONS.join(", ")}.`
|
|
41633
|
+
};
|
|
41634
|
+
}
|
|
41635
|
+
let sizeBytes;
|
|
41636
|
+
try {
|
|
41637
|
+
const stats = statSync(trimmed);
|
|
41638
|
+
if (!stats.isFile()) {
|
|
41639
|
+
return { error: `--${flag} '${sanitizeDisplay(trimmed)}' is not a regular file.` };
|
|
41640
|
+
}
|
|
41641
|
+
sizeBytes = stats.size;
|
|
41642
|
+
} catch {
|
|
41643
|
+
return { error: `Cannot read --${flag} '${sanitizeDisplay(trimmed)}'.` };
|
|
41644
|
+
}
|
|
41645
|
+
if (sizeBytes === 0) {
|
|
41646
|
+
return { error: `--${flag} '${sanitizeDisplay(trimmed)}' is empty.` };
|
|
41647
|
+
}
|
|
41648
|
+
if (sizeBytes > MAX_LOGO_UPLOAD_BYTES) {
|
|
41649
|
+
return {
|
|
41650
|
+
error: `--${flag} is ${formatBytes(sizeBytes)}; logo files must be ${formatBytes(MAX_LOGO_UPLOAD_BYTES)} or smaller.`
|
|
41651
|
+
};
|
|
41652
|
+
}
|
|
41653
|
+
return { change: { action: "upload", file: { path: trimmed, contentType, sizeBytes } } };
|
|
41654
|
+
}
|
|
41655
|
+
function formatBytes(bytes) {
|
|
41656
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
41657
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)} KB`;
|
|
41658
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
41659
|
+
}
|
|
41660
|
+
var NOTHING_TO_UPDATE = "Nothing to update. Pass at least one of --homepage, --clear-homepage, --logo, --clear-logo, --dark-logo, --clear-dark-logo, --logos-from-homepage.";
|
|
41661
|
+
function resolveLogoSlot(value, clear, flag) {
|
|
41662
|
+
const resolved = resolveClearableOption(value, clear, flag);
|
|
41663
|
+
if ("error" in resolved) {
|
|
41664
|
+
return resolved;
|
|
41665
|
+
}
|
|
41666
|
+
if (resolved.value === null) {
|
|
41667
|
+
return { change: { action: "clear" } };
|
|
41668
|
+
}
|
|
41669
|
+
if (resolved.value === void 0) {
|
|
41670
|
+
return { change: void 0 };
|
|
41671
|
+
}
|
|
41672
|
+
const logo = resolveLogoValue(resolved.value, flag);
|
|
41673
|
+
return "error" in logo ? logo : { change: logo.change };
|
|
41674
|
+
}
|
|
41675
|
+
function planOrgUpdate(options) {
|
|
41676
|
+
const homepage = resolveClearableOption(options.homepage, options.clearHomepage, "homepage");
|
|
41677
|
+
if ("error" in homepage) {
|
|
41678
|
+
return homepage;
|
|
41679
|
+
}
|
|
41680
|
+
if (typeof homepage.value === "string" && !isHttpUrl(homepage.value.trim())) {
|
|
41681
|
+
return {
|
|
41682
|
+
error: `--homepage '${sanitizeDisplay(homepage.value)}' must be an http(s) URL (e.g. https://acme.com).`
|
|
41683
|
+
};
|
|
41684
|
+
}
|
|
41685
|
+
const logo = resolveLogoSlot(options.logo, options.clearLogo, "logo");
|
|
41686
|
+
if ("error" in logo) {
|
|
41687
|
+
return logo;
|
|
41688
|
+
}
|
|
41689
|
+
const darkLogo = resolveLogoSlot(options.darkLogo, options.clearDarkLogo, "dark-logo");
|
|
41690
|
+
if ("error" in darkLogo) {
|
|
41691
|
+
return darkLogo;
|
|
41692
|
+
}
|
|
41693
|
+
const derive = options.logosFromHomepage === true;
|
|
41694
|
+
if (derive && (logo.change !== void 0 || darkLogo.change !== void 0)) {
|
|
41695
|
+
return {
|
|
41696
|
+
error: "Pass either --logos-from-homepage or explicit logo flags (--logo, --dark-logo, --clear-logo, --clear-dark-logo), not both."
|
|
41697
|
+
};
|
|
41698
|
+
}
|
|
41699
|
+
if (derive && homepage.value === null) {
|
|
41700
|
+
return { error: "--logos-from-homepage needs a homepage; drop --clear-homepage." };
|
|
41701
|
+
}
|
|
41702
|
+
const homepageUrl = typeof homepage.value === "string" ? homepage.value.trim() : homepage.value;
|
|
41703
|
+
if (homepageUrl === void 0 && logo.change === void 0 && darkLogo.change === void 0 && !derive) {
|
|
41704
|
+
return { error: NOTHING_TO_UPDATE };
|
|
41705
|
+
}
|
|
41706
|
+
return {
|
|
41707
|
+
plan: {
|
|
41708
|
+
homepageUrl,
|
|
41709
|
+
logo: logo.change,
|
|
41710
|
+
darkLogo: darkLogo.change,
|
|
41711
|
+
deriveLogosFromHomepage: derive
|
|
41712
|
+
}
|
|
41713
|
+
};
|
|
41714
|
+
}
|
|
41715
|
+
function slotFields(change, uploadedKey, variant) {
|
|
41716
|
+
switch (change.action) {
|
|
41717
|
+
case "clear":
|
|
41718
|
+
return { url: null, s3Key: null };
|
|
41719
|
+
case "url":
|
|
41720
|
+
return { url: change.url, s3Key: null };
|
|
41721
|
+
case "upload":
|
|
41722
|
+
if (uploadedKey === void 0) {
|
|
41723
|
+
throw new Error(
|
|
41724
|
+
`Missing uploaded S3 key for the ${variant} logo; expected the upload step to run before building the update body.`
|
|
41725
|
+
);
|
|
41726
|
+
}
|
|
41727
|
+
return { url: null, s3Key: uploadedKey };
|
|
41728
|
+
}
|
|
41729
|
+
}
|
|
41730
|
+
function buildOrgUpdateBody(plan, uploaded) {
|
|
41731
|
+
const body = {};
|
|
41732
|
+
if (plan.homepageUrl !== void 0) {
|
|
41733
|
+
body.homepageUrl = plan.homepageUrl;
|
|
41734
|
+
}
|
|
41735
|
+
if (plan.logo !== void 0) {
|
|
41736
|
+
const fields = slotFields(plan.logo, uploaded.light, "light");
|
|
41737
|
+
body.logoUrl = fields.url;
|
|
41738
|
+
body.logoS3Key = fields.s3Key;
|
|
41739
|
+
}
|
|
41740
|
+
if (plan.darkLogo !== void 0) {
|
|
41741
|
+
const fields = slotFields(plan.darkLogo, uploaded.dark, "dark");
|
|
41742
|
+
body.darkModeLogoUrl = fields.url;
|
|
41743
|
+
body.darkModeLogoS3Key = fields.s3Key;
|
|
41744
|
+
}
|
|
41745
|
+
return body;
|
|
41746
|
+
}
|
|
41747
|
+
function isEmptyOrgUpdate(body) {
|
|
41748
|
+
return Object.keys(body).length === 0;
|
|
41749
|
+
}
|
|
41750
|
+
var MAX_DISPLAY_URL_LENGTH = 120;
|
|
41751
|
+
function formatUrlForDisplay(url2) {
|
|
41752
|
+
if (url2 === null) {
|
|
41753
|
+
return "\u2014";
|
|
41754
|
+
}
|
|
41755
|
+
const clean = sanitizeDisplay(url2);
|
|
41756
|
+
return clean.length > MAX_DISPLAY_URL_LENGTH ? `${clean.slice(0, MAX_DISPLAY_URL_LENGTH - 1)}\u2026` : clean;
|
|
41757
|
+
}
|
|
41758
|
+
function failOrgOp(action, status, body) {
|
|
41759
|
+
const { message } = readErrorFields(body);
|
|
41760
|
+
if (status === UNAUTHORIZED_STATUS2) {
|
|
41761
|
+
exitAuthFailed();
|
|
41762
|
+
}
|
|
41763
|
+
if (status === 403) {
|
|
41764
|
+
console.error(
|
|
41765
|
+
colorError(
|
|
41766
|
+
"Updating organization settings requires an organization admin signed in with a browser session (API keys are rejected)."
|
|
41767
|
+
)
|
|
41768
|
+
);
|
|
41769
|
+
process.exit(1);
|
|
41770
|
+
}
|
|
41771
|
+
if (status === 404) {
|
|
41772
|
+
console.error(colorError(message ?? "Organization not found."));
|
|
41773
|
+
process.exit(1);
|
|
41774
|
+
}
|
|
41775
|
+
if (status === 400 || status === 422) {
|
|
41776
|
+
console.error(colorError(message ?? `Failed to ${action} (status ${status}).`));
|
|
41777
|
+
process.exit(1);
|
|
41778
|
+
}
|
|
41779
|
+
console.error(
|
|
41780
|
+
colorError(`Failed to ${action} (status ${status})${message ? `: ${message}` : "."}`)
|
|
41781
|
+
);
|
|
41782
|
+
process.exit(1);
|
|
41783
|
+
}
|
|
41784
|
+
async function uploadLogo(organizationId, variant, file2) {
|
|
41785
|
+
const slot = await authenticatedRequest(
|
|
41786
|
+
(client) => client.organization.generateLogoUploadUrl({
|
|
41787
|
+
params: { organizationId },
|
|
41788
|
+
body: { variant, contentType: file2.contentType, fileName: basename(file2.path) }
|
|
41789
|
+
})
|
|
41790
|
+
);
|
|
41791
|
+
if (slot.status !== SUCCESS_STATUS) {
|
|
41792
|
+
failOrgOp(`start the ${variant} logo upload`, slot.status, slot.body);
|
|
41793
|
+
}
|
|
41794
|
+
const bytes = readFileSync2(file2.path);
|
|
41795
|
+
const put = await fetch(slot.body.uploadUrl, {
|
|
41796
|
+
method: "PUT",
|
|
41797
|
+
body: new Uint8Array(bytes),
|
|
41798
|
+
headers: { "Content-Type": file2.contentType }
|
|
41799
|
+
});
|
|
41800
|
+
if (!put.ok) {
|
|
41801
|
+
console.error(
|
|
41802
|
+
colorError(
|
|
41803
|
+
`Upload of the ${variant} logo to storage failed (status ${put.status}). The upload link is short-lived; re-run the command.`
|
|
41804
|
+
)
|
|
41805
|
+
);
|
|
41806
|
+
process.exit(1);
|
|
41807
|
+
}
|
|
41808
|
+
return slot.body.s3Key;
|
|
41809
|
+
}
|
|
41810
|
+
function outputOrganization(org, output, derived) {
|
|
41811
|
+
if (output === "json") {
|
|
41812
|
+
console.log(JSON.stringify(org, null, 2));
|
|
41813
|
+
return;
|
|
41814
|
+
}
|
|
41815
|
+
if (output === "tsv") {
|
|
41816
|
+
console.log("name slug homepageUrl logoUrl darkModeLogoUrl");
|
|
41817
|
+
console.log(
|
|
41818
|
+
[
|
|
41819
|
+
tsvSafe(sanitizeDisplay(org.name)),
|
|
41820
|
+
tsvSafe(sanitizeDisplay(org.slug)),
|
|
41821
|
+
tsvSafe(sanitizeDisplay(org.homepageUrl ?? "")),
|
|
41822
|
+
tsvSafe(sanitizeDisplay(org.logoUrl ?? "")),
|
|
41823
|
+
tsvSafe(sanitizeDisplay(org.darkModeLogoUrl ?? ""))
|
|
41824
|
+
].join(" ")
|
|
41825
|
+
);
|
|
41826
|
+
return;
|
|
41827
|
+
}
|
|
41828
|
+
console.log(
|
|
41829
|
+
colorSuccess(`Updated organization ${sanitizeDisplay(org.name)} (${sanitizeDisplay(org.slug)})`)
|
|
41830
|
+
);
|
|
41831
|
+
if (derived) {
|
|
41832
|
+
console.log(" Logos fetched from the homepage.");
|
|
41833
|
+
}
|
|
41834
|
+
console.log(` Homepage: ${formatUrlForDisplay(org.homepageUrl)}`);
|
|
41835
|
+
console.log(` Light logo: ${formatUrlForDisplay(org.logoUrl)}`);
|
|
41836
|
+
console.log(` Dark logo: ${formatUrlForDisplay(org.darkModeLogoUrl)}`);
|
|
41837
|
+
}
|
|
41838
|
+
async function runUpdate2(options) {
|
|
41839
|
+
const output = parseOutput(options.output);
|
|
41840
|
+
const planned = planOrgUpdate(options);
|
|
41841
|
+
if ("error" in planned) {
|
|
41842
|
+
console.error(colorError(planned.error));
|
|
41843
|
+
process.exit(1);
|
|
41844
|
+
}
|
|
41845
|
+
const { plan } = planned;
|
|
41846
|
+
const organizationId = await resolveOrganizationId();
|
|
41847
|
+
const uploaded = {};
|
|
41848
|
+
if (plan.logo?.action === "upload") {
|
|
41849
|
+
uploaded.light = await uploadLogo(organizationId, "light", plan.logo.file);
|
|
41850
|
+
}
|
|
41851
|
+
if (plan.darkLogo?.action === "upload") {
|
|
41852
|
+
uploaded.dark = await uploadLogo(organizationId, "dark", plan.darkLogo.file);
|
|
41853
|
+
}
|
|
41854
|
+
let org = null;
|
|
41855
|
+
const body = buildOrgUpdateBody(plan, uploaded);
|
|
41856
|
+
if (!isEmptyOrgUpdate(body)) {
|
|
41857
|
+
const result = await authenticatedRequest(
|
|
41858
|
+
(client) => client.organization.updateOrganization({ params: { organizationId }, body })
|
|
41859
|
+
);
|
|
41860
|
+
if (result.status !== SUCCESS_STATUS) {
|
|
41861
|
+
failOrgOp("update organization settings", result.status, result.body);
|
|
41862
|
+
}
|
|
41863
|
+
org = result.body;
|
|
41864
|
+
}
|
|
41865
|
+
if (plan.deriveLogosFromHomepage) {
|
|
41866
|
+
const result = await authenticatedRequest(
|
|
41867
|
+
(client) => client.organization.deriveLogosFromHomepage({ params: { organizationId }, body: {} })
|
|
41868
|
+
);
|
|
41869
|
+
if (result.status !== SUCCESS_STATUS) {
|
|
41870
|
+
failOrgOp("fetch logos from the homepage", result.status, result.body);
|
|
41871
|
+
}
|
|
41872
|
+
org = result.body;
|
|
41873
|
+
}
|
|
41874
|
+
if (org === null) {
|
|
41875
|
+
throw new Error("Organization update produced no response; expected at least one API call.");
|
|
41876
|
+
}
|
|
41877
|
+
outputOrganization(org, output, plan.deriveLogosFromHomepage);
|
|
41878
|
+
}
|
|
41879
|
+
function createOrgUpdateCommand() {
|
|
41880
|
+
return new Command("update").description("Update the active organization's homepage URL and logos").option("--homepage <url>", "Homepage URL (https://...)").option("--clear-homepage", "Remove the homepage URL").option("--logo <file-or-url>", "Light-mode logo: an image file to upload, or an http(s) URL").option("--clear-logo", "Remove the light-mode logo").option(
|
|
41881
|
+
"--dark-logo <file-or-url>",
|
|
41882
|
+
"Dark-mode logo: an image file to upload, or an http(s) URL"
|
|
41883
|
+
).option("--clear-dark-logo", "Remove the dark-mode logo").option(
|
|
41884
|
+
"--logos-from-homepage",
|
|
41885
|
+
"Fetch light and dark logos from the homepage (after saving --homepage, if given)"
|
|
41886
|
+
).option("-o, --output <format>", "Output format: table, json, tsv (default: table)", "table").addHelpText(
|
|
41887
|
+
"after",
|
|
41888
|
+
`
|
|
41889
|
+
Requires an organization admin signed in with \`tracecli auth login\`; API keys
|
|
41890
|
+
are rejected. Logo files upload straight to storage (.png, .jpg, .jpeg, .gif,
|
|
41891
|
+
.svg, .webp; 2 MB max). Saving a file or URL for a slot replaces whatever that
|
|
41892
|
+
slot held before.
|
|
41893
|
+
|
|
41894
|
+
Examples:
|
|
41895
|
+
tracecli org update --homepage https://acme.com
|
|
41896
|
+
tracecli org update --homepage https://acme.com --logos-from-homepage
|
|
41897
|
+
tracecli org update --logo ./logo.svg --dark-logo ./logo-dark.svg
|
|
41898
|
+
tracecli org update --logo https://cdn.acme.com/logo.png
|
|
41899
|
+
tracecli org update --clear-dark-logo
|
|
41900
|
+
tracecli org update --homepage https://acme.com --output json`
|
|
41901
|
+
).action(async (options) => {
|
|
41902
|
+
await runUpdate2(options);
|
|
41903
|
+
});
|
|
41904
|
+
}
|
|
41905
|
+
|
|
41592
41906
|
// src/auth/commands.ts
|
|
41593
41907
|
function getClientId() {
|
|
41594
41908
|
const clientId = "client_01KEZ01W5Q3107GTBY51WQ67HT";
|
|
@@ -42361,10 +42675,13 @@ Examples:
|
|
|
42361
42675
|
});
|
|
42362
42676
|
}
|
|
42363
42677
|
function createOrgCommand() {
|
|
42364
|
-
const org = new Command("org").description(
|
|
42678
|
+
const org = new Command("org").description(
|
|
42679
|
+
"Manage active organization context and organization settings"
|
|
42680
|
+
);
|
|
42365
42681
|
org.addCommand(createOrgListCommand());
|
|
42366
42682
|
org.addCommand(createOrgCurrentCommand());
|
|
42367
42683
|
org.addCommand(createOrgSwitchCommand());
|
|
42684
|
+
org.addCommand(createOrgUpdateCommand());
|
|
42368
42685
|
return org;
|
|
42369
42686
|
}
|
|
42370
42687
|
function createAuthCommand() {
|
|
@@ -43429,7 +43746,7 @@ function createInboxCommand() {
|
|
|
43429
43746
|
}
|
|
43430
43747
|
|
|
43431
43748
|
// src/secret-input.ts
|
|
43432
|
-
import { readFileSync as
|
|
43749
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
43433
43750
|
var SecretInputError = class extends Error {
|
|
43434
43751
|
};
|
|
43435
43752
|
function secretSourceCount(opts) {
|
|
@@ -43464,7 +43781,7 @@ async function resolveSecret(label, flagBase, opts, readStdin = defaultReadStdin
|
|
|
43464
43781
|
if (opts.file !== void 0) {
|
|
43465
43782
|
let contents;
|
|
43466
43783
|
try {
|
|
43467
|
-
contents =
|
|
43784
|
+
contents = readFileSync3(opts.file, "utf8");
|
|
43468
43785
|
} catch {
|
|
43469
43786
|
throw new SecretInputError(`Could not read ${label} file: ${opts.file}`);
|
|
43470
43787
|
}
|
|
@@ -44050,7 +44367,7 @@ async function runGet2(credentialId, options) {
|
|
|
44050
44367
|
}
|
|
44051
44368
|
outputCredential(result.body, output);
|
|
44052
44369
|
}
|
|
44053
|
-
async function
|
|
44370
|
+
async function runUpdate3(credentialId, options) {
|
|
44054
44371
|
const output = parseOutput(options.output);
|
|
44055
44372
|
requireEnvId(options.env);
|
|
44056
44373
|
requireCredId(credentialId);
|
|
@@ -44302,7 +44619,7 @@ Examples:
|
|
|
44302
44619
|
tracecli credential update <id> --env <envId> --clear-auth-config`
|
|
44303
44620
|
);
|
|
44304
44621
|
return command.action(async (credentialId, options) => {
|
|
44305
|
-
await
|
|
44622
|
+
await runUpdate3(credentialId, options);
|
|
44306
44623
|
});
|
|
44307
44624
|
}
|
|
44308
44625
|
function createDeleteCommand3() {
|
|
@@ -44791,7 +45108,7 @@ async function runGet3(environmentId, options) {
|
|
|
44791
45108
|
}
|
|
44792
45109
|
outputEnvironment(result.body, output);
|
|
44793
45110
|
}
|
|
44794
|
-
async function
|
|
45111
|
+
async function runUpdate4(environmentId, options) {
|
|
44795
45112
|
const output = parseOutput(options.output);
|
|
44796
45113
|
requireAppId(options.app);
|
|
44797
45114
|
requireEnvId2(environmentId);
|
|
@@ -44902,7 +45219,7 @@ Examples:
|
|
|
44902
45219
|
tracecli environment update <envId> --app <appId> --additional-url https://api.acme.com
|
|
44903
45220
|
tracecli environment update <envId> --app <appId> --production`
|
|
44904
45221
|
).action(async (environmentId, options) => {
|
|
44905
|
-
await
|
|
45222
|
+
await runUpdate4(environmentId, options);
|
|
44906
45223
|
});
|
|
44907
45224
|
}
|
|
44908
45225
|
function createDeleteCommand4() {
|
|
@@ -44921,7 +45238,7 @@ function createEnvironmentCommand() {
|
|
|
44921
45238
|
}
|
|
44922
45239
|
|
|
44923
45240
|
// src/metadata.ts
|
|
44924
|
-
import { readFileSync as
|
|
45241
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
44925
45242
|
import { dirname as dirname2, join as join5 } from "path";
|
|
44926
45243
|
import { fileURLToPath } from "url";
|
|
44927
45244
|
var DEFAULT_PACKAGE_NAME = "securewithtrace";
|
|
@@ -44930,7 +45247,7 @@ function readPackageMetadata() {
|
|
|
44930
45247
|
const currentFilePath = fileURLToPath(import.meta.url);
|
|
44931
45248
|
const packageJsonPath = join5(dirname2(currentFilePath), "..", "package.json");
|
|
44932
45249
|
try {
|
|
44933
|
-
const rawPackageJson =
|
|
45250
|
+
const rawPackageJson = readFileSync4(packageJsonPath, "utf-8");
|
|
44934
45251
|
const parsed = JSON.parse(rawPackageJson);
|
|
44935
45252
|
if (typeof parsed !== "object" || parsed === null) {
|
|
44936
45253
|
return { name: DEFAULT_PACKAGE_NAME, version: DEFAULT_VERSION };
|
|
@@ -44948,10 +45265,10 @@ var CLI_PACKAGE_NAME = packageMetadata.name;
|
|
|
44948
45265
|
var CLI_VERSION = packageMetadata.version;
|
|
44949
45266
|
|
|
44950
45267
|
// src/pentest/commands.ts
|
|
44951
|
-
import { readFileSync as
|
|
45268
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
44952
45269
|
|
|
44953
45270
|
// src/pentest/create-finding.ts
|
|
44954
|
-
import { readFileSync as
|
|
45271
|
+
import { readFileSync as readFileSync5, statSync as statSync2 } from "fs";
|
|
44955
45272
|
|
|
44956
45273
|
// src/pentest/resolve.ts
|
|
44957
45274
|
var UUID_PATTERN2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
@@ -45030,14 +45347,14 @@ function parseCreateFindingJsonFile(raw, sourceLabel) {
|
|
|
45030
45347
|
}
|
|
45031
45348
|
function readUtf8File(path, flag) {
|
|
45032
45349
|
try {
|
|
45033
|
-
const stats =
|
|
45350
|
+
const stats = statSync2(path);
|
|
45034
45351
|
if (!stats.isFile()) {
|
|
45035
45352
|
return { error: `${flag} '${sanitizeDisplay(path)}' is not a regular file.` };
|
|
45036
45353
|
}
|
|
45037
45354
|
} catch {
|
|
45038
45355
|
return { error: `Cannot read ${flag} '${sanitizeDisplay(path)}'.` };
|
|
45039
45356
|
}
|
|
45040
|
-
const text =
|
|
45357
|
+
const text = readFileSync5(path, "utf8");
|
|
45041
45358
|
if (text.trim().length === 0) {
|
|
45042
45359
|
return { error: `${flag} '${sanitizeDisplay(path)}' is empty.` };
|
|
45043
45360
|
}
|
|
@@ -45263,8 +45580,8 @@ function createCreateFindingCommand() {
|
|
|
45263
45580
|
}
|
|
45264
45581
|
|
|
45265
45582
|
// src/pentest/evidence.ts
|
|
45266
|
-
import { readFileSync as
|
|
45267
|
-
import { basename } from "path";
|
|
45583
|
+
import { readFileSync as readFileSync6, statSync as statSync3 } from "fs";
|
|
45584
|
+
import { basename as basename2 } from "path";
|
|
45268
45585
|
var SECTION_LIST = ValidationEvidenceSectionValues.join(", ");
|
|
45269
45586
|
var KIND_LIST = PentestExploitArtifactKindValues.join(", ");
|
|
45270
45587
|
function isSection(value) {
|
|
@@ -45273,7 +45590,7 @@ function isSection(value) {
|
|
|
45273
45590
|
function isKind(value) {
|
|
45274
45591
|
return PentestExploitArtifactKindValues.includes(value);
|
|
45275
45592
|
}
|
|
45276
|
-
function
|
|
45593
|
+
function formatBytes2(bytes) {
|
|
45277
45594
|
if (bytes < 1024) return `${bytes} B`;
|
|
45278
45595
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)} KB`;
|
|
45279
45596
|
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
@@ -45326,7 +45643,7 @@ async function runAttachEvidence(options) {
|
|
|
45326
45643
|
}
|
|
45327
45644
|
let sizeBytes;
|
|
45328
45645
|
try {
|
|
45329
|
-
const stats =
|
|
45646
|
+
const stats = statSync3(options.file);
|
|
45330
45647
|
if (!stats.isFile()) {
|
|
45331
45648
|
console.error(colorError(`--file '${sanitizeDisplay(options.file)}' is not a regular file.`));
|
|
45332
45649
|
process.exit(1);
|
|
@@ -45343,7 +45660,7 @@ async function runAttachEvidence(options) {
|
|
|
45343
45660
|
if (sizeBytes > MAX_STAFF_EVIDENCE_UPLOAD_BYTES) {
|
|
45344
45661
|
console.error(
|
|
45345
45662
|
colorError(
|
|
45346
|
-
`File is ${
|
|
45663
|
+
`File is ${formatBytes2(sizeBytes)}; the limit for one exhibit is ${formatBytes2(MAX_STAFF_EVIDENCE_UPLOAD_BYTES)}.`
|
|
45347
45664
|
)
|
|
45348
45665
|
);
|
|
45349
45666
|
process.exit(1);
|
|
@@ -45352,7 +45669,7 @@ async function runAttachEvidence(options) {
|
|
|
45352
45669
|
const pentestId = await resolvePentestId(organizationId, options.pentest);
|
|
45353
45670
|
const pentestFindingId = await resolveFindingId(organizationId, pentestId, options.finding);
|
|
45354
45671
|
const params = { organizationId, pentestId, pentestFindingId };
|
|
45355
|
-
const filename =
|
|
45672
|
+
const filename = basename2(options.file);
|
|
45356
45673
|
const slot = await authenticatedRequest(
|
|
45357
45674
|
(client) => client.pentestFinding.createFindingEvidenceUpload({
|
|
45358
45675
|
params,
|
|
@@ -45369,7 +45686,7 @@ async function runAttachEvidence(options) {
|
|
|
45369
45686
|
);
|
|
45370
45687
|
process.exit(1);
|
|
45371
45688
|
}
|
|
45372
|
-
const body =
|
|
45689
|
+
const body = readFileSync6(options.file);
|
|
45373
45690
|
const put = await fetch(slot.body.uploadUrl, { method: "PUT", body: new Uint8Array(body) });
|
|
45374
45691
|
if (!put.ok) {
|
|
45375
45692
|
console.error(
|
|
@@ -45409,7 +45726,7 @@ async function runAttachEvidence(options) {
|
|
|
45409
45726
|
const placement = attached.body.section ? ` \xB7 ${attached.body.section}` : "";
|
|
45410
45727
|
console.log(
|
|
45411
45728
|
colorSuccess(
|
|
45412
|
-
`Attached ${attached.body.kind} \xB7 ${
|
|
45729
|
+
`Attached ${attached.body.kind} \xB7 ${formatBytes2(attached.body.sizeBytes)} \xB7 ${visibility}${placement}`
|
|
45413
45730
|
)
|
|
45414
45731
|
);
|
|
45415
45732
|
console.log(` artifact ${attached.body.artifactId}`);
|
|
@@ -46247,7 +46564,7 @@ function outputPentestUpdate(pt, output) {
|
|
|
46247
46564
|
}
|
|
46248
46565
|
printPentestUpdateDetail(pt);
|
|
46249
46566
|
}
|
|
46250
|
-
async function
|
|
46567
|
+
async function runUpdate5(pentestIdentifier, options) {
|
|
46251
46568
|
const output = parseOutput(options.output);
|
|
46252
46569
|
const description = resolveClearableOption(
|
|
46253
46570
|
options.description,
|
|
@@ -46317,7 +46634,7 @@ Examples:
|
|
|
46317
46634
|
tracecli pentest update PT-3 --clear-concerns --clear-priorities
|
|
46318
46635
|
tracecli pentest update PT-3 --title "Acme Q3" -o json`
|
|
46319
46636
|
).action(async (pentestId, options) => {
|
|
46320
|
-
await
|
|
46637
|
+
await runUpdate5(pentestId, options);
|
|
46321
46638
|
});
|
|
46322
46639
|
}
|
|
46323
46640
|
function failQuestionnaire(status, body, action, notFoundLabel) {
|
|
@@ -46508,7 +46825,7 @@ async function runQuestionnaire(pentestIdentifier, options) {
|
|
|
46508
46825
|
if (filePath !== void 0) {
|
|
46509
46826
|
let raw;
|
|
46510
46827
|
try {
|
|
46511
|
-
raw =
|
|
46828
|
+
raw = readFileSync7(filePath, "utf8");
|
|
46512
46829
|
} catch {
|
|
46513
46830
|
console.error(colorError(`Could not read answers file: ${filePath}`));
|
|
46514
46831
|
process.exit(1);
|
|
@@ -46592,7 +46909,7 @@ function createPentestCommand() {
|
|
|
46592
46909
|
}
|
|
46593
46910
|
|
|
46594
46911
|
// src/update/commands.ts
|
|
46595
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as
|
|
46912
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "fs";
|
|
46596
46913
|
import { dirname as dirname3, join as join6 } from "path";
|
|
46597
46914
|
import { createInterface as createInterface4 } from "readline/promises";
|
|
46598
46915
|
var UPDATE_CACHE_FILE = "update-check.json";
|
|
@@ -46610,7 +46927,7 @@ function readUpdateCheckCache() {
|
|
|
46610
46927
|
return null;
|
|
46611
46928
|
}
|
|
46612
46929
|
try {
|
|
46613
|
-
const rawCache =
|
|
46930
|
+
const rawCache = readFileSync8(cachePath, "utf-8");
|
|
46614
46931
|
const parsed = JSON.parse(rawCache);
|
|
46615
46932
|
if (typeof parsed !== "object" || parsed === null) {
|
|
46616
46933
|
return null;
|
|
@@ -46976,9 +47293,9 @@ Examples:
|
|
|
46976
47293
|
|
|
46977
47294
|
// src/upload/prepare.ts
|
|
46978
47295
|
import { createHash } from "crypto";
|
|
46979
|
-
import { createReadStream, statSync as
|
|
47296
|
+
import { createReadStream, statSync as statSync4 } from "fs";
|
|
46980
47297
|
import { open } from "fs/promises";
|
|
46981
|
-
import { basename as
|
|
47298
|
+
import { basename as basename3 } from "path";
|
|
46982
47299
|
var CodeUploadPrepareError = class extends Error {
|
|
46983
47300
|
constructor(message) {
|
|
46984
47301
|
super(message);
|
|
@@ -47077,7 +47394,7 @@ function assertPreparedMatchesUpload(upload, prepared) {
|
|
|
47077
47394
|
async function prepareUploadFile(filePath, options = {}) {
|
|
47078
47395
|
let sizeBytes;
|
|
47079
47396
|
try {
|
|
47080
|
-
const stats =
|
|
47397
|
+
const stats = statSync4(filePath);
|
|
47081
47398
|
if (!stats.isFile()) {
|
|
47082
47399
|
throw new CodeUploadPrepareError("Path is not a regular file. Pass a .zip, .apk, or .ipa.");
|
|
47083
47400
|
}
|
|
@@ -47092,7 +47409,7 @@ async function prepareUploadFile(filePath, options = {}) {
|
|
|
47092
47409
|
if (sizeBytes > CODE_UPLOAD_MAX_BYTES) {
|
|
47093
47410
|
throw new CodeUploadPrepareError(archiveTooLargeMessage());
|
|
47094
47411
|
}
|
|
47095
|
-
const originalFilename =
|
|
47412
|
+
const originalFilename = basename3(filePath);
|
|
47096
47413
|
const inferred = inferArtifactKindFromFilename(originalFilename);
|
|
47097
47414
|
const artifactKind = options.artifactKind ?? inferred;
|
|
47098
47415
|
if (artifactKind == null) {
|