securewithtrace 0.19.0 → 0.20.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 +573 -413
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -24235,6 +24235,14 @@ var ScanTriggerTypeValues = [
|
|
|
24235
24235
|
var ScanTriggerType = external_exports.enum(ScanTriggerTypeValues);
|
|
24236
24236
|
|
|
24237
24237
|
// ../../packages/api-contracts/src/schemas/api/v1/scan.api.ts
|
|
24238
|
+
var ScanProviderValues = ["bedrock"];
|
|
24239
|
+
var ScanConfigurationSchema = external_exports.object({
|
|
24240
|
+
/**
|
|
24241
|
+
* Override the LLM provider used for this scan.
|
|
24242
|
+
* Staff-only — must be enforced server-side.
|
|
24243
|
+
*/
|
|
24244
|
+
provider: external_exports.enum(ScanProviderValues).optional()
|
|
24245
|
+
}).strict();
|
|
24238
24246
|
var ScanIdParamsSchema = OrgIdParamsSchema.extend({
|
|
24239
24247
|
scanId: external_exports.uuid()
|
|
24240
24248
|
});
|
|
@@ -24244,7 +24252,7 @@ var CreateScanRequestSchema = external_exports.discriminatedUnion("scanMode", [
|
|
|
24244
24252
|
webApplicationId: external_exports.uuid(),
|
|
24245
24253
|
targetUrl: external_exports.url(),
|
|
24246
24254
|
userProvidedContext: external_exports.string().optional(),
|
|
24247
|
-
scanConfiguration:
|
|
24255
|
+
scanConfiguration: ScanConfigurationSchema.optional()
|
|
24248
24256
|
}),
|
|
24249
24257
|
external_exports.object({
|
|
24250
24258
|
scanMode: external_exports.literal("SAST"),
|
|
@@ -24252,7 +24260,7 @@ var CreateScanRequestSchema = external_exports.discriminatedUnion("scanMode", [
|
|
|
24252
24260
|
branch: external_exports.string().optional(),
|
|
24253
24261
|
scanType: external_exports.enum(["FULL", "INCREMENTAL"]).optional(),
|
|
24254
24262
|
userProvidedContext: external_exports.string().optional(),
|
|
24255
|
-
scanConfiguration:
|
|
24263
|
+
scanConfiguration: ScanConfigurationSchema.optional()
|
|
24256
24264
|
}),
|
|
24257
24265
|
external_exports.object({
|
|
24258
24266
|
scanMode: external_exports.literal("SCA"),
|
|
@@ -24260,7 +24268,7 @@ var CreateScanRequestSchema = external_exports.discriminatedUnion("scanMode", [
|
|
|
24260
24268
|
branch: external_exports.string().optional(),
|
|
24261
24269
|
scanType: external_exports.enum(["FULL", "INCREMENTAL"]).optional(),
|
|
24262
24270
|
userProvidedContext: external_exports.string().optional(),
|
|
24263
|
-
scanConfiguration:
|
|
24271
|
+
scanConfiguration: ScanConfigurationSchema.optional()
|
|
24264
24272
|
}),
|
|
24265
24273
|
external_exports.object({
|
|
24266
24274
|
scanMode: external_exports.literal("SECRETS"),
|
|
@@ -24268,7 +24276,7 @@ var CreateScanRequestSchema = external_exports.discriminatedUnion("scanMode", [
|
|
|
24268
24276
|
branch: external_exports.string().optional(),
|
|
24269
24277
|
scanType: external_exports.enum(["FULL", "INCREMENTAL"]).optional(),
|
|
24270
24278
|
userProvidedContext: external_exports.string().optional(),
|
|
24271
|
-
scanConfiguration:
|
|
24279
|
+
scanConfiguration: ScanConfigurationSchema.optional()
|
|
24272
24280
|
})
|
|
24273
24281
|
]);
|
|
24274
24282
|
var ScanResponseSchema = external_exports.object({
|
|
@@ -25624,267 +25632,6 @@ function removeToken() {
|
|
|
25624
25632
|
return removed;
|
|
25625
25633
|
}
|
|
25626
25634
|
|
|
25627
|
-
// src/api-client.ts
|
|
25628
|
-
var readTokenSafe = readToken;
|
|
25629
|
-
var PRODUCTION_API_URL = "https://api.securewithtrace.com";
|
|
25630
|
-
var UNAUTHORIZED_STATUS = 401;
|
|
25631
|
-
function getApiBaseUrl() {
|
|
25632
|
-
const raw = process.env["TRACE_API_URL"];
|
|
25633
|
-
if (!raw) {
|
|
25634
|
-
return PRODUCTION_API_URL;
|
|
25635
|
-
}
|
|
25636
|
-
let parsed;
|
|
25637
|
-
try {
|
|
25638
|
-
parsed = new URL(raw);
|
|
25639
|
-
} catch {
|
|
25640
|
-
console.error(`Invalid TRACE_API_URL: '${raw}' is not a valid URL. Using default.`);
|
|
25641
|
-
return PRODUCTION_API_URL;
|
|
25642
|
-
}
|
|
25643
|
-
if (parsed.protocol !== "https:") {
|
|
25644
|
-
console.error("Invalid TRACE_API_URL: only https:// is allowed. Using default.");
|
|
25645
|
-
return PRODUCTION_API_URL;
|
|
25646
|
-
}
|
|
25647
|
-
const hostname3 = parsed.hostname;
|
|
25648
|
-
if (hostname3 === "localhost" || hostname3 === "127.0.0.1" || hostname3 === "0.0.0.0" || hostname3 === "::1" || hostname3.startsWith("10.") || hostname3.startsWith("192.168.") || /^172\.(1[6-9]|2[0-9]|3[01])\./.test(hostname3)) {
|
|
25649
|
-
console.error(
|
|
25650
|
-
"Invalid TRACE_API_URL: private/reserved hostnames are not allowed. Using default."
|
|
25651
|
-
);
|
|
25652
|
-
return PRODUCTION_API_URL;
|
|
25653
|
-
}
|
|
25654
|
-
return raw.replace(/\/+$/, "");
|
|
25655
|
-
}
|
|
25656
|
-
function createClient(token) {
|
|
25657
|
-
return initClient(v1Contract, {
|
|
25658
|
-
baseUrl: getApiBaseUrl(),
|
|
25659
|
-
baseHeaders: {
|
|
25660
|
-
Authorization: `Bearer ${token}`
|
|
25661
|
-
}
|
|
25662
|
-
});
|
|
25663
|
-
}
|
|
25664
|
-
function getWorkosClientId() {
|
|
25665
|
-
const clientId = "client_01KEZ01W5Q3107GTBY51WQ67HT";
|
|
25666
|
-
if (!clientId || clientId.trim().length === 0) {
|
|
25667
|
-
return null;
|
|
25668
|
-
}
|
|
25669
|
-
return clientId;
|
|
25670
|
-
}
|
|
25671
|
-
async function refreshStoredAccessToken() {
|
|
25672
|
-
const clientId = getWorkosClientId();
|
|
25673
|
-
const refreshToken = readRefreshToken();
|
|
25674
|
-
if (!clientId || !refreshToken) {
|
|
25675
|
-
return null;
|
|
25676
|
-
}
|
|
25677
|
-
try {
|
|
25678
|
-
const tokenResponse = await refreshAccessToken(clientId, refreshToken);
|
|
25679
|
-
const nextRefreshToken = tokenResponse.refresh_token ?? refreshToken;
|
|
25680
|
-
writeTokens(tokenResponse.access_token, nextRefreshToken);
|
|
25681
|
-
return tokenResponse.access_token;
|
|
25682
|
-
} catch (error48) {
|
|
25683
|
-
if (error48 instanceof Error && (error48.message.includes(" 400 ") || error48.message.includes(" 401 "))) {
|
|
25684
|
-
removeToken();
|
|
25685
|
-
}
|
|
25686
|
-
return null;
|
|
25687
|
-
}
|
|
25688
|
-
}
|
|
25689
|
-
async function refreshStoredAccessTokenForOrganization(workosOrganizationId) {
|
|
25690
|
-
const clientId = getWorkosClientId();
|
|
25691
|
-
const refreshToken = readRefreshToken();
|
|
25692
|
-
if (!clientId || !refreshToken) {
|
|
25693
|
-
return false;
|
|
25694
|
-
}
|
|
25695
|
-
try {
|
|
25696
|
-
const tokenResponse = await refreshAccessToken(clientId, refreshToken, workosOrganizationId);
|
|
25697
|
-
const nextRefreshToken = tokenResponse.refresh_token ?? refreshToken;
|
|
25698
|
-
writeTokens(tokenResponse.access_token, nextRefreshToken);
|
|
25699
|
-
return true;
|
|
25700
|
-
} catch (error48) {
|
|
25701
|
-
if (error48 instanceof Error && (error48.message.includes(" 400 ") || error48.message.includes(" 401 "))) {
|
|
25702
|
-
removeToken();
|
|
25703
|
-
}
|
|
25704
|
-
return false;
|
|
25705
|
-
}
|
|
25706
|
-
}
|
|
25707
|
-
async function authenticatedRequest(request) {
|
|
25708
|
-
const token = readToken();
|
|
25709
|
-
if (!token) {
|
|
25710
|
-
console.error("Not logged in. Run `tracecli auth login` first.");
|
|
25711
|
-
process.exit(1);
|
|
25712
|
-
}
|
|
25713
|
-
const firstResult = await request(createClient(token));
|
|
25714
|
-
if (firstResult.status !== UNAUTHORIZED_STATUS) {
|
|
25715
|
-
return firstResult;
|
|
25716
|
-
}
|
|
25717
|
-
const refreshedToken = await refreshStoredAccessToken();
|
|
25718
|
-
if (!refreshedToken) {
|
|
25719
|
-
return firstResult;
|
|
25720
|
-
}
|
|
25721
|
-
return request(createClient(refreshedToken));
|
|
25722
|
-
}
|
|
25723
|
-
async function authenticatedRequestSafe(request) {
|
|
25724
|
-
const token = readTokenSafe();
|
|
25725
|
-
if (!token) {
|
|
25726
|
-
return null;
|
|
25727
|
-
}
|
|
25728
|
-
try {
|
|
25729
|
-
const firstResult = await request(createClient(token));
|
|
25730
|
-
if (firstResult.status !== UNAUTHORIZED_STATUS) {
|
|
25731
|
-
return firstResult;
|
|
25732
|
-
}
|
|
25733
|
-
const refreshedToken = await refreshStoredAccessToken();
|
|
25734
|
-
if (!refreshedToken) {
|
|
25735
|
-
return firstResult;
|
|
25736
|
-
}
|
|
25737
|
-
return await request(createClient(refreshedToken));
|
|
25738
|
-
} catch {
|
|
25739
|
-
return null;
|
|
25740
|
-
}
|
|
25741
|
-
}
|
|
25742
|
-
function extractOrganizationIdFromToken(token) {
|
|
25743
|
-
if (!token || token.trim().length === 0) {
|
|
25744
|
-
return null;
|
|
25745
|
-
}
|
|
25746
|
-
const parts = token.split(".");
|
|
25747
|
-
const payload = parts[1];
|
|
25748
|
-
if (!payload) {
|
|
25749
|
-
return null;
|
|
25750
|
-
}
|
|
25751
|
-
try {
|
|
25752
|
-
const decoded = JSON.parse(Buffer.from(payload, "base64url").toString("utf-8"));
|
|
25753
|
-
return typeof decoded.org_id === "string" && decoded.org_id.trim().length > 0 ? decoded.org_id : null;
|
|
25754
|
-
} catch {
|
|
25755
|
-
return null;
|
|
25756
|
-
}
|
|
25757
|
-
}
|
|
25758
|
-
var SAFE_ID_PATTERN = /^[a-zA-Z0-9_-]+$/;
|
|
25759
|
-
function determineActiveOrganization(organizations, selectedOrganizationId, tokenOrganizationId) {
|
|
25760
|
-
if (organizations.length === 0) {
|
|
25761
|
-
return null;
|
|
25762
|
-
}
|
|
25763
|
-
if (selectedOrganizationId && SAFE_ID_PATTERN.test(selectedOrganizationId)) {
|
|
25764
|
-
const selectedOrganization = organizations.find(
|
|
25765
|
-
(organization) => organization.organizationId === selectedOrganizationId
|
|
25766
|
-
);
|
|
25767
|
-
if (selectedOrganization) {
|
|
25768
|
-
return {
|
|
25769
|
-
organization: selectedOrganization,
|
|
25770
|
-
source: "selected",
|
|
25771
|
-
staleSelectionDetected: false
|
|
25772
|
-
};
|
|
25773
|
-
}
|
|
25774
|
-
}
|
|
25775
|
-
if (tokenOrganizationId && SAFE_ID_PATTERN.test(tokenOrganizationId)) {
|
|
25776
|
-
const tokenOrganization = organizations.find(
|
|
25777
|
-
(organization) => organization.organizationId === tokenOrganizationId
|
|
25778
|
-
);
|
|
25779
|
-
if (tokenOrganization) {
|
|
25780
|
-
return {
|
|
25781
|
-
organization: tokenOrganization,
|
|
25782
|
-
source: "default",
|
|
25783
|
-
staleSelectionDetected: selectedOrganizationId != null
|
|
25784
|
-
};
|
|
25785
|
-
}
|
|
25786
|
-
}
|
|
25787
|
-
const [fallbackOrganization] = organizations;
|
|
25788
|
-
if (!fallbackOrganization) {
|
|
25789
|
-
return null;
|
|
25790
|
-
}
|
|
25791
|
-
return {
|
|
25792
|
-
organization: fallbackOrganization,
|
|
25793
|
-
source: "default",
|
|
25794
|
-
staleSelectionDetected: selectedOrganizationId != null
|
|
25795
|
-
};
|
|
25796
|
-
}
|
|
25797
|
-
async function resolveOrganizationId(client) {
|
|
25798
|
-
const result = client ? await client.organization.listMyOrganizations() : await authenticatedRequest(
|
|
25799
|
-
(authenticatedClient) => authenticatedClient.organization.listMyOrganizations()
|
|
25800
|
-
);
|
|
25801
|
-
if (result.status !== 200) {
|
|
25802
|
-
console.error(`Failed to resolve organization (status ${result.status}).`);
|
|
25803
|
-
process.exit(1);
|
|
25804
|
-
}
|
|
25805
|
-
const organizations = result.body;
|
|
25806
|
-
const selectedOrganizationId = readSelectedOrganizationId();
|
|
25807
|
-
const tokenOrganizationId = extractOrganizationIdFromToken(readToken());
|
|
25808
|
-
const activeOrganization = determineActiveOrganization(
|
|
25809
|
-
organizations,
|
|
25810
|
-
selectedOrganizationId,
|
|
25811
|
-
tokenOrganizationId
|
|
25812
|
-
);
|
|
25813
|
-
if (!activeOrganization) {
|
|
25814
|
-
console.error("No organization found for your account.");
|
|
25815
|
-
process.exit(1);
|
|
25816
|
-
}
|
|
25817
|
-
const id = activeOrganization.organization.organizationId;
|
|
25818
|
-
if (!SAFE_ID_PATTERN.test(id)) {
|
|
25819
|
-
console.error("Invalid organization ID received from API.");
|
|
25820
|
-
process.exit(1);
|
|
25821
|
-
}
|
|
25822
|
-
if (activeOrganization.staleSelectionDetected) {
|
|
25823
|
-
removeSelectedOrganizationId();
|
|
25824
|
-
writeSelectedOrganizationId(id);
|
|
25825
|
-
if (selectedOrganizationId) {
|
|
25826
|
-
console.warn(
|
|
25827
|
-
`Stored organization '${selectedOrganizationId}' is unavailable. Using '${activeOrganization.organization.name}' (${id}) instead.`
|
|
25828
|
-
);
|
|
25829
|
-
}
|
|
25830
|
-
} else if (activeOrganization.source === "default") {
|
|
25831
|
-
writeSelectedOrganizationId(id);
|
|
25832
|
-
}
|
|
25833
|
-
return id;
|
|
25834
|
-
}
|
|
25835
|
-
async function resolveOrganizationIdSafe(client) {
|
|
25836
|
-
try {
|
|
25837
|
-
const result = client ? await client.organization.listMyOrganizations() : await authenticatedRequestSafe(
|
|
25838
|
-
(authenticatedClient) => authenticatedClient.organization.listMyOrganizations()
|
|
25839
|
-
);
|
|
25840
|
-
if (!result) {
|
|
25841
|
-
return null;
|
|
25842
|
-
}
|
|
25843
|
-
if (result.status !== 200) {
|
|
25844
|
-
return null;
|
|
25845
|
-
}
|
|
25846
|
-
const organizations = result.body;
|
|
25847
|
-
const selectedOrganizationId = readSelectedOrganizationId();
|
|
25848
|
-
const tokenOrganizationId = extractOrganizationIdFromToken(readToken());
|
|
25849
|
-
const activeOrganization = determineActiveOrganization(
|
|
25850
|
-
organizations,
|
|
25851
|
-
selectedOrganizationId,
|
|
25852
|
-
tokenOrganizationId
|
|
25853
|
-
);
|
|
25854
|
-
if (!activeOrganization) {
|
|
25855
|
-
return null;
|
|
25856
|
-
}
|
|
25857
|
-
const id = activeOrganization.organization.organizationId;
|
|
25858
|
-
if (!SAFE_ID_PATTERN.test(id)) {
|
|
25859
|
-
return null;
|
|
25860
|
-
}
|
|
25861
|
-
if (activeOrganization.staleSelectionDetected) {
|
|
25862
|
-
removeSelectedOrganizationId();
|
|
25863
|
-
writeSelectedOrganizationId(id);
|
|
25864
|
-
} else if (activeOrganization.source === "default") {
|
|
25865
|
-
writeSelectedOrganizationId(id);
|
|
25866
|
-
}
|
|
25867
|
-
return id;
|
|
25868
|
-
} catch {
|
|
25869
|
-
return null;
|
|
25870
|
-
}
|
|
25871
|
-
}
|
|
25872
|
-
|
|
25873
|
-
// src/format.ts
|
|
25874
|
-
var SEVERITY_ORDER = {
|
|
25875
|
-
CRITICAL: 0,
|
|
25876
|
-
HIGH: 1,
|
|
25877
|
-
MEDIUM: 2,
|
|
25878
|
-
LOW: 3,
|
|
25879
|
-
INFORMATIONAL: 4
|
|
25880
|
-
};
|
|
25881
|
-
function formatStatus(status) {
|
|
25882
|
-
return status.toLowerCase().replace("_", " ");
|
|
25883
|
-
}
|
|
25884
|
-
function tsvSafe(value) {
|
|
25885
|
-
return (value ?? "").replace(/[\t\r\n]/g, " ");
|
|
25886
|
-
}
|
|
25887
|
-
|
|
25888
25635
|
// ../../node_modules/.bun/chalk@5.6.2/node_modules/chalk/source/vendor/ansi-styles/index.js
|
|
25889
25636
|
var ANSI_BACKGROUND_OFFSET = 10;
|
|
25890
25637
|
var wrapAnsi16 = (offset = 0) => (code) => `\x1B[${code + offset}m`;
|
|
@@ -26362,61 +26109,331 @@ var applyStyle = (self, string4) => {
|
|
|
26362
26109
|
if (styler === void 0) {
|
|
26363
26110
|
return string4;
|
|
26364
26111
|
}
|
|
26365
|
-
const { openAll, closeAll } = styler;
|
|
26366
|
-
if (string4.includes("\x1B")) {
|
|
26367
|
-
while (styler !== void 0) {
|
|
26368
|
-
string4 = stringReplaceAll(string4, styler.close, styler.open);
|
|
26369
|
-
styler = styler.parent;
|
|
26370
|
-
}
|
|
26112
|
+
const { openAll, closeAll } = styler;
|
|
26113
|
+
if (string4.includes("\x1B")) {
|
|
26114
|
+
while (styler !== void 0) {
|
|
26115
|
+
string4 = stringReplaceAll(string4, styler.close, styler.open);
|
|
26116
|
+
styler = styler.parent;
|
|
26117
|
+
}
|
|
26118
|
+
}
|
|
26119
|
+
const lfIndex = string4.indexOf("\n");
|
|
26120
|
+
if (lfIndex !== -1) {
|
|
26121
|
+
string4 = stringEncaseCRLFWithFirstIndex(string4, closeAll, openAll, lfIndex);
|
|
26122
|
+
}
|
|
26123
|
+
return openAll + string4 + closeAll;
|
|
26124
|
+
};
|
|
26125
|
+
Object.defineProperties(createChalk.prototype, styles2);
|
|
26126
|
+
var chalk = createChalk();
|
|
26127
|
+
var chalkStderr = createChalk({ level: stderrColor ? stderrColor.level : 0 });
|
|
26128
|
+
var source_default = chalk;
|
|
26129
|
+
|
|
26130
|
+
// src/theme.ts
|
|
26131
|
+
function colorSeverity(severity, text, colorizer = source_default) {
|
|
26132
|
+
switch (severity) {
|
|
26133
|
+
case "CRITICAL":
|
|
26134
|
+
return colorizer.red.bold(text);
|
|
26135
|
+
case "HIGH":
|
|
26136
|
+
return colorizer.hex("#ff6b00").bold(text);
|
|
26137
|
+
case "MEDIUM":
|
|
26138
|
+
return colorizer.yellow(text);
|
|
26139
|
+
case "LOW":
|
|
26140
|
+
return colorizer.cyan(text);
|
|
26141
|
+
case "INFORMATIONAL":
|
|
26142
|
+
return colorizer.gray(text);
|
|
26143
|
+
default:
|
|
26144
|
+
return text;
|
|
26145
|
+
}
|
|
26146
|
+
}
|
|
26147
|
+
function colorBanner(text, colorizer = source_default) {
|
|
26148
|
+
return colorizer.cyan.bold(text);
|
|
26149
|
+
}
|
|
26150
|
+
function colorMuted(text, colorizer = source_default) {
|
|
26151
|
+
return colorizer.dim(text);
|
|
26152
|
+
}
|
|
26153
|
+
function colorSuccess(text, colorizer = source_default) {
|
|
26154
|
+
return colorizer.green(text);
|
|
26155
|
+
}
|
|
26156
|
+
function colorError(text, colorizer = source_default) {
|
|
26157
|
+
return colorizer.red(text);
|
|
26158
|
+
}
|
|
26159
|
+
function colorWarningPrefix(text, colorizer = source_default) {
|
|
26160
|
+
return `${colorizer.yellow.bold("Warning:")} ${text}`;
|
|
26161
|
+
}
|
|
26162
|
+
function colorWarning(text, colorizer = source_default) {
|
|
26163
|
+
return colorizer.yellow(text);
|
|
26164
|
+
}
|
|
26165
|
+
function colorAccentBold(text, colorizer = source_default) {
|
|
26166
|
+
return colorizer.bold.cyan(text);
|
|
26167
|
+
}
|
|
26168
|
+
function colorAccentUnderline(text, colorizer = source_default) {
|
|
26169
|
+
return colorizer.underline.cyan(text);
|
|
26170
|
+
}
|
|
26171
|
+
|
|
26172
|
+
// src/api-client.ts
|
|
26173
|
+
var readTokenSafe = readToken;
|
|
26174
|
+
var PRODUCTION_API_URL = "https://api.securewithtrace.com";
|
|
26175
|
+
var UNAUTHORIZED_STATUS = 401;
|
|
26176
|
+
function getApiBaseUrl() {
|
|
26177
|
+
const raw = process.env["TRACE_API_URL"];
|
|
26178
|
+
if (!raw) {
|
|
26179
|
+
return PRODUCTION_API_URL;
|
|
26180
|
+
}
|
|
26181
|
+
let parsed;
|
|
26182
|
+
try {
|
|
26183
|
+
parsed = new URL(raw);
|
|
26184
|
+
} catch {
|
|
26185
|
+
console.error(
|
|
26186
|
+
colorWarning(`Invalid TRACE_API_URL: '${raw}' is not a valid URL. Using default.`)
|
|
26187
|
+
);
|
|
26188
|
+
return PRODUCTION_API_URL;
|
|
26189
|
+
}
|
|
26190
|
+
if (parsed.protocol !== "https:") {
|
|
26191
|
+
console.error(colorWarning("Invalid TRACE_API_URL: only https:// is allowed. Using default."));
|
|
26192
|
+
return PRODUCTION_API_URL;
|
|
26193
|
+
}
|
|
26194
|
+
const hostname3 = parsed.hostname;
|
|
26195
|
+
if (hostname3 === "localhost" || hostname3 === "127.0.0.1" || hostname3 === "0.0.0.0" || hostname3 === "::1" || hostname3.startsWith("10.") || hostname3.startsWith("192.168.") || /^172\.(1[6-9]|2[0-9]|3[01])\./.test(hostname3)) {
|
|
26196
|
+
console.error(
|
|
26197
|
+
colorWarning(
|
|
26198
|
+
"Invalid TRACE_API_URL: private/reserved hostnames are not allowed. Using default."
|
|
26199
|
+
)
|
|
26200
|
+
);
|
|
26201
|
+
return PRODUCTION_API_URL;
|
|
26202
|
+
}
|
|
26203
|
+
return raw.replace(/\/+$/, "");
|
|
26204
|
+
}
|
|
26205
|
+
function createClient(token) {
|
|
26206
|
+
return initClient(v1Contract, {
|
|
26207
|
+
baseUrl: getApiBaseUrl(),
|
|
26208
|
+
baseHeaders: {
|
|
26209
|
+
Authorization: `Bearer ${token}`
|
|
26210
|
+
}
|
|
26211
|
+
});
|
|
26212
|
+
}
|
|
26213
|
+
function getWorkosClientId() {
|
|
26214
|
+
const clientId = "client_01KEZ01W5Q3107GTBY51WQ67HT";
|
|
26215
|
+
if (!clientId || clientId.trim().length === 0) {
|
|
26216
|
+
return null;
|
|
26217
|
+
}
|
|
26218
|
+
return clientId;
|
|
26219
|
+
}
|
|
26220
|
+
async function refreshStoredAccessToken() {
|
|
26221
|
+
const clientId = getWorkosClientId();
|
|
26222
|
+
const refreshToken = readRefreshToken();
|
|
26223
|
+
if (!clientId || !refreshToken) {
|
|
26224
|
+
return null;
|
|
26225
|
+
}
|
|
26226
|
+
try {
|
|
26227
|
+
const tokenResponse = await refreshAccessToken(clientId, refreshToken);
|
|
26228
|
+
const nextRefreshToken = tokenResponse.refresh_token ?? refreshToken;
|
|
26229
|
+
writeTokens(tokenResponse.access_token, nextRefreshToken);
|
|
26230
|
+
return tokenResponse.access_token;
|
|
26231
|
+
} catch (error48) {
|
|
26232
|
+
if (error48 instanceof Error && (error48.message.includes(" 400 ") || error48.message.includes(" 401 "))) {
|
|
26233
|
+
removeToken();
|
|
26234
|
+
}
|
|
26235
|
+
return null;
|
|
26236
|
+
}
|
|
26237
|
+
}
|
|
26238
|
+
async function refreshStoredAccessTokenForOrganization(workosOrganizationId) {
|
|
26239
|
+
const clientId = getWorkosClientId();
|
|
26240
|
+
const refreshToken = readRefreshToken();
|
|
26241
|
+
if (!clientId || !refreshToken) {
|
|
26242
|
+
return false;
|
|
26243
|
+
}
|
|
26244
|
+
try {
|
|
26245
|
+
const tokenResponse = await refreshAccessToken(clientId, refreshToken, workosOrganizationId);
|
|
26246
|
+
const nextRefreshToken = tokenResponse.refresh_token ?? refreshToken;
|
|
26247
|
+
writeTokens(tokenResponse.access_token, nextRefreshToken);
|
|
26248
|
+
return true;
|
|
26249
|
+
} catch (error48) {
|
|
26250
|
+
if (error48 instanceof Error && (error48.message.includes(" 400 ") || error48.message.includes(" 401 "))) {
|
|
26251
|
+
removeToken();
|
|
26252
|
+
}
|
|
26253
|
+
return false;
|
|
26254
|
+
}
|
|
26255
|
+
}
|
|
26256
|
+
async function authenticatedRequest(request) {
|
|
26257
|
+
const token = readToken();
|
|
26258
|
+
if (!token) {
|
|
26259
|
+
console.error(colorError("Not logged in. Run `tracecli auth login` first."));
|
|
26260
|
+
process.exit(1);
|
|
26261
|
+
}
|
|
26262
|
+
const firstResult = await request(createClient(token));
|
|
26263
|
+
if (firstResult.status !== UNAUTHORIZED_STATUS) {
|
|
26264
|
+
return firstResult;
|
|
26265
|
+
}
|
|
26266
|
+
const refreshedToken = await refreshStoredAccessToken();
|
|
26267
|
+
if (!refreshedToken) {
|
|
26268
|
+
return firstResult;
|
|
26269
|
+
}
|
|
26270
|
+
return request(createClient(refreshedToken));
|
|
26271
|
+
}
|
|
26272
|
+
async function authenticatedRequestSafe(request) {
|
|
26273
|
+
const token = readTokenSafe();
|
|
26274
|
+
if (!token) {
|
|
26275
|
+
return null;
|
|
26276
|
+
}
|
|
26277
|
+
try {
|
|
26278
|
+
const firstResult = await request(createClient(token));
|
|
26279
|
+
if (firstResult.status !== UNAUTHORIZED_STATUS) {
|
|
26280
|
+
return firstResult;
|
|
26281
|
+
}
|
|
26282
|
+
const refreshedToken = await refreshStoredAccessToken();
|
|
26283
|
+
if (!refreshedToken) {
|
|
26284
|
+
return firstResult;
|
|
26285
|
+
}
|
|
26286
|
+
return await request(createClient(refreshedToken));
|
|
26287
|
+
} catch {
|
|
26288
|
+
return null;
|
|
26289
|
+
}
|
|
26290
|
+
}
|
|
26291
|
+
function extractOrganizationIdFromToken(token) {
|
|
26292
|
+
if (!token || token.trim().length === 0) {
|
|
26293
|
+
return null;
|
|
26294
|
+
}
|
|
26295
|
+
const parts = token.split(".");
|
|
26296
|
+
const payload = parts[1];
|
|
26297
|
+
if (!payload) {
|
|
26298
|
+
return null;
|
|
26299
|
+
}
|
|
26300
|
+
try {
|
|
26301
|
+
const decoded = JSON.parse(Buffer.from(payload, "base64url").toString("utf-8"));
|
|
26302
|
+
return typeof decoded.org_id === "string" && decoded.org_id.trim().length > 0 ? decoded.org_id : null;
|
|
26303
|
+
} catch {
|
|
26304
|
+
return null;
|
|
26305
|
+
}
|
|
26306
|
+
}
|
|
26307
|
+
var SAFE_ID_PATTERN = /^[a-zA-Z0-9_-]+$/;
|
|
26308
|
+
function determineActiveOrganization(organizations, selectedOrganizationId, tokenOrganizationId) {
|
|
26309
|
+
if (organizations.length === 0) {
|
|
26310
|
+
return null;
|
|
26311
|
+
}
|
|
26312
|
+
if (selectedOrganizationId && SAFE_ID_PATTERN.test(selectedOrganizationId)) {
|
|
26313
|
+
const selectedOrganization = organizations.find(
|
|
26314
|
+
(organization) => organization.organizationId === selectedOrganizationId
|
|
26315
|
+
);
|
|
26316
|
+
if (selectedOrganization) {
|
|
26317
|
+
return {
|
|
26318
|
+
organization: selectedOrganization,
|
|
26319
|
+
source: "selected",
|
|
26320
|
+
staleSelectionDetected: false
|
|
26321
|
+
};
|
|
26322
|
+
}
|
|
26323
|
+
}
|
|
26324
|
+
if (tokenOrganizationId && SAFE_ID_PATTERN.test(tokenOrganizationId)) {
|
|
26325
|
+
const tokenOrganization = organizations.find(
|
|
26326
|
+
(organization) => organization.organizationId === tokenOrganizationId
|
|
26327
|
+
);
|
|
26328
|
+
if (tokenOrganization) {
|
|
26329
|
+
return {
|
|
26330
|
+
organization: tokenOrganization,
|
|
26331
|
+
source: "default",
|
|
26332
|
+
staleSelectionDetected: selectedOrganizationId != null
|
|
26333
|
+
};
|
|
26334
|
+
}
|
|
26335
|
+
}
|
|
26336
|
+
const [fallbackOrganization] = organizations;
|
|
26337
|
+
if (!fallbackOrganization) {
|
|
26338
|
+
return null;
|
|
26339
|
+
}
|
|
26340
|
+
return {
|
|
26341
|
+
organization: fallbackOrganization,
|
|
26342
|
+
source: "default",
|
|
26343
|
+
staleSelectionDetected: selectedOrganizationId != null
|
|
26344
|
+
};
|
|
26345
|
+
}
|
|
26346
|
+
async function resolveOrganizationId(client) {
|
|
26347
|
+
const result = client ? await client.organization.listMyOrganizations() : await authenticatedRequest(
|
|
26348
|
+
(authenticatedClient) => authenticatedClient.organization.listMyOrganizations()
|
|
26349
|
+
);
|
|
26350
|
+
if (result.status !== 200) {
|
|
26351
|
+
console.error(colorError(`Failed to resolve organization (status ${result.status}).`));
|
|
26352
|
+
process.exit(1);
|
|
26353
|
+
}
|
|
26354
|
+
const organizations = result.body;
|
|
26355
|
+
const selectedOrganizationId = readSelectedOrganizationId();
|
|
26356
|
+
const tokenOrganizationId = extractOrganizationIdFromToken(readToken());
|
|
26357
|
+
const activeOrganization = determineActiveOrganization(
|
|
26358
|
+
organizations,
|
|
26359
|
+
selectedOrganizationId,
|
|
26360
|
+
tokenOrganizationId
|
|
26361
|
+
);
|
|
26362
|
+
if (!activeOrganization) {
|
|
26363
|
+
console.error(colorError("No organization found for your account."));
|
|
26364
|
+
process.exit(1);
|
|
26371
26365
|
}
|
|
26372
|
-
const
|
|
26373
|
-
if (
|
|
26374
|
-
|
|
26366
|
+
const id = activeOrganization.organization.organizationId;
|
|
26367
|
+
if (!SAFE_ID_PATTERN.test(id)) {
|
|
26368
|
+
console.error(colorError("Invalid organization ID received from API."));
|
|
26369
|
+
process.exit(1);
|
|
26375
26370
|
}
|
|
26376
|
-
|
|
26377
|
-
|
|
26378
|
-
|
|
26379
|
-
|
|
26380
|
-
|
|
26381
|
-
|
|
26382
|
-
|
|
26383
|
-
|
|
26384
|
-
|
|
26385
|
-
|
|
26386
|
-
|
|
26387
|
-
|
|
26388
|
-
case "HIGH":
|
|
26389
|
-
return colorizer.hex("#ff6b00").bold(text);
|
|
26390
|
-
case "MEDIUM":
|
|
26391
|
-
return colorizer.yellow(text);
|
|
26392
|
-
case "LOW":
|
|
26393
|
-
return colorizer.cyan(text);
|
|
26394
|
-
case "INFORMATIONAL":
|
|
26395
|
-
return colorizer.gray(text);
|
|
26396
|
-
default:
|
|
26397
|
-
return text;
|
|
26371
|
+
if (activeOrganization.staleSelectionDetected) {
|
|
26372
|
+
removeSelectedOrganizationId();
|
|
26373
|
+
writeSelectedOrganizationId(id);
|
|
26374
|
+
if (selectedOrganizationId) {
|
|
26375
|
+
console.warn(
|
|
26376
|
+
colorWarning(
|
|
26377
|
+
`Stored organization '${selectedOrganizationId}' is unavailable. Using '${activeOrganization.organization.name}' (${id}) instead.`
|
|
26378
|
+
)
|
|
26379
|
+
);
|
|
26380
|
+
}
|
|
26381
|
+
} else if (activeOrganization.source === "default") {
|
|
26382
|
+
writeSelectedOrganizationId(id);
|
|
26398
26383
|
}
|
|
26384
|
+
return id;
|
|
26399
26385
|
}
|
|
26400
|
-
function
|
|
26401
|
-
|
|
26402
|
-
|
|
26403
|
-
|
|
26404
|
-
|
|
26405
|
-
|
|
26406
|
-
|
|
26407
|
-
|
|
26408
|
-
|
|
26409
|
-
|
|
26410
|
-
|
|
26411
|
-
|
|
26412
|
-
|
|
26413
|
-
|
|
26386
|
+
async function resolveOrganizationIdSafe(client) {
|
|
26387
|
+
try {
|
|
26388
|
+
const result = client ? await client.organization.listMyOrganizations() : await authenticatedRequestSafe(
|
|
26389
|
+
(authenticatedClient) => authenticatedClient.organization.listMyOrganizations()
|
|
26390
|
+
);
|
|
26391
|
+
if (!result) {
|
|
26392
|
+
return null;
|
|
26393
|
+
}
|
|
26394
|
+
if (result.status !== 200) {
|
|
26395
|
+
return null;
|
|
26396
|
+
}
|
|
26397
|
+
const organizations = result.body;
|
|
26398
|
+
const selectedOrganizationId = readSelectedOrganizationId();
|
|
26399
|
+
const tokenOrganizationId = extractOrganizationIdFromToken(readToken());
|
|
26400
|
+
const activeOrganization = determineActiveOrganization(
|
|
26401
|
+
organizations,
|
|
26402
|
+
selectedOrganizationId,
|
|
26403
|
+
tokenOrganizationId
|
|
26404
|
+
);
|
|
26405
|
+
if (!activeOrganization) {
|
|
26406
|
+
return null;
|
|
26407
|
+
}
|
|
26408
|
+
const id = activeOrganization.organization.organizationId;
|
|
26409
|
+
if (!SAFE_ID_PATTERN.test(id)) {
|
|
26410
|
+
return null;
|
|
26411
|
+
}
|
|
26412
|
+
if (activeOrganization.staleSelectionDetected) {
|
|
26413
|
+
removeSelectedOrganizationId();
|
|
26414
|
+
writeSelectedOrganizationId(id);
|
|
26415
|
+
} else if (activeOrganization.source === "default") {
|
|
26416
|
+
writeSelectedOrganizationId(id);
|
|
26417
|
+
}
|
|
26418
|
+
return id;
|
|
26419
|
+
} catch {
|
|
26420
|
+
return null;
|
|
26421
|
+
}
|
|
26414
26422
|
}
|
|
26415
|
-
|
|
26416
|
-
|
|
26423
|
+
|
|
26424
|
+
// src/format.ts
|
|
26425
|
+
var SEVERITY_ORDER = {
|
|
26426
|
+
CRITICAL: 0,
|
|
26427
|
+
HIGH: 1,
|
|
26428
|
+
MEDIUM: 2,
|
|
26429
|
+
LOW: 3,
|
|
26430
|
+
INFORMATIONAL: 4
|
|
26431
|
+
};
|
|
26432
|
+
function formatStatus(status) {
|
|
26433
|
+
return status.toLowerCase().replace("_", " ");
|
|
26417
26434
|
}
|
|
26418
|
-
function
|
|
26419
|
-
return
|
|
26435
|
+
function tsvSafe(value) {
|
|
26436
|
+
return (value ?? "").replace(/[\t\r\n]/g, " ");
|
|
26420
26437
|
}
|
|
26421
26438
|
|
|
26422
26439
|
// src/auth/commands.ts
|
|
@@ -26664,7 +26681,7 @@ Examples:
|
|
|
26664
26681
|
const existingToken = readToken();
|
|
26665
26682
|
if (existingToken) {
|
|
26666
26683
|
console.log(
|
|
26667
|
-
`${colorWarning("You are already logged in.")} Run \`
|
|
26684
|
+
`${colorWarning("You are already logged in.")} Run \`tracecli auth logout\` first to re-authenticate.`
|
|
26668
26685
|
);
|
|
26669
26686
|
return;
|
|
26670
26687
|
}
|
|
@@ -26762,6 +26779,7 @@ Examples:
|
|
|
26762
26779
|
return;
|
|
26763
26780
|
}
|
|
26764
26781
|
if (options.output === "tsv") {
|
|
26782
|
+
console.log("loggedIn expired hasRefreshToken userId orgId activeOrg");
|
|
26765
26783
|
console.log(
|
|
26766
26784
|
[
|
|
26767
26785
|
String(info.loggedIn),
|
|
@@ -26831,6 +26849,7 @@ Examples:
|
|
|
26831
26849
|
return;
|
|
26832
26850
|
}
|
|
26833
26851
|
if (options.output === "tsv") {
|
|
26852
|
+
console.log("name slug organizationId active");
|
|
26834
26853
|
for (const item of items) {
|
|
26835
26854
|
console.log(
|
|
26836
26855
|
[
|
|
@@ -26884,6 +26903,7 @@ Examples:
|
|
|
26884
26903
|
return;
|
|
26885
26904
|
}
|
|
26886
26905
|
if (options.output === "tsv") {
|
|
26906
|
+
console.log("name slug organizationId source");
|
|
26887
26907
|
console.log(
|
|
26888
26908
|
[
|
|
26889
26909
|
tsvSafe(active.name),
|
|
@@ -27344,7 +27364,15 @@ function resolveShell(shellOption) {
|
|
|
27344
27364
|
return shell;
|
|
27345
27365
|
}
|
|
27346
27366
|
function createCompletionCommand(program3) {
|
|
27347
|
-
return new Command("completion").description("Generate shell completion script").option("--shell <shell>", `Shell type (${SUPPORTED_SHELLS.join(", ")})`).
|
|
27367
|
+
return new Command("completion").description("Generate shell completion script").option("--shell <shell>", `Shell type (${SUPPORTED_SHELLS.join(", ")})`).addHelpText(
|
|
27368
|
+
"after",
|
|
27369
|
+
`
|
|
27370
|
+
Examples:
|
|
27371
|
+
tracecli completion --shell bash
|
|
27372
|
+
tracecli completion --shell zsh
|
|
27373
|
+
tracecli completion --shell fish
|
|
27374
|
+
tracecli completion # auto-detects shell from $SHELL`
|
|
27375
|
+
).action((options) => {
|
|
27348
27376
|
const shell = resolveShell(options.shell);
|
|
27349
27377
|
const script = generateCompletion(program3, shell);
|
|
27350
27378
|
const writtenPath = writeCompletionFile(shell, script);
|
|
@@ -27448,6 +27476,10 @@ Run \`tracecli pentest list\` to see available pentests.`
|
|
|
27448
27476
|
}
|
|
27449
27477
|
function createListCommand() {
|
|
27450
27478
|
return new Command("list").description("List pentests for your organization").option(
|
|
27479
|
+
"--status <status>",
|
|
27480
|
+
"Filter by status (SCOPING, IN_PROGRESS, REMEDIATION, CLOSED)",
|
|
27481
|
+
(value) => value.toUpperCase()
|
|
27482
|
+
).option(
|
|
27451
27483
|
"-L, --limit <n>",
|
|
27452
27484
|
`Max results to return (default: ${DEFAULT_LIMIT}, max: ${MAX_LIMIT})`,
|
|
27453
27485
|
(v) => {
|
|
@@ -27462,6 +27494,7 @@ function createListCommand() {
|
|
|
27462
27494
|
`
|
|
27463
27495
|
Examples:
|
|
27464
27496
|
tracecli pentest list
|
|
27497
|
+
tracecli pentest list --status IN_PROGRESS
|
|
27465
27498
|
tracecli pentest list --limit 10
|
|
27466
27499
|
tracecli pentest list --output json | jq '.[].title'
|
|
27467
27500
|
tracecli pentest list --output tsv | cut -f1,2`
|
|
@@ -27480,8 +27513,12 @@ Examples:
|
|
|
27480
27513
|
console.error(`Failed to list pentests (status ${result.status}).`);
|
|
27481
27514
|
process.exit(1);
|
|
27482
27515
|
}
|
|
27516
|
+
let filteredPentests = result.body;
|
|
27517
|
+
if (options.status) {
|
|
27518
|
+
filteredPentests = filteredPentests.filter((p2) => p2.status === options.status);
|
|
27519
|
+
}
|
|
27483
27520
|
const limit = options.limit ?? DEFAULT_LIMIT;
|
|
27484
|
-
const pentests =
|
|
27521
|
+
const pentests = filteredPentests.slice(0, limit);
|
|
27485
27522
|
const isJson = options.output === "json";
|
|
27486
27523
|
const isTsv = options.output === "tsv";
|
|
27487
27524
|
if (pentests.length === 0) {
|
|
@@ -27531,89 +27568,119 @@ Examples:
|
|
|
27531
27568
|
});
|
|
27532
27569
|
}
|
|
27533
27570
|
function createFindingsListCommand() {
|
|
27534
|
-
return new Command("list").description("List findings for a pentest").requiredOption("--pentest <id>", "Pentest display ID (e.g. PT-3) or UUID").option(
|
|
27571
|
+
return new Command("list").description("List findings for a pentest").requiredOption("--pentest <id>", "Pentest display ID (e.g. PT-3) or UUID").option(
|
|
27572
|
+
"--severity <severity>",
|
|
27573
|
+
"Filter by severity (CRITICAL, HIGH, MEDIUM, LOW, INFORMATIONAL)",
|
|
27574
|
+
(value) => value.toUpperCase()
|
|
27575
|
+
).option(
|
|
27576
|
+
"--status <status>",
|
|
27577
|
+
"Filter by status (OPEN, IN_PROGRESS, CONTESTED, REMEDIATED, VERIFIED_FIXED, VERIFIED_OPEN, DISMISSED)",
|
|
27578
|
+
(value) => value.toUpperCase()
|
|
27579
|
+
).option(
|
|
27580
|
+
"-L, --limit <n>",
|
|
27581
|
+
`Max results to return (default: ${DEFAULT_LIMIT}, max: ${MAX_LIMIT})`,
|
|
27582
|
+
(v) => {
|
|
27583
|
+
const n = Number.parseInt(v, 10);
|
|
27584
|
+
if (Number.isNaN(n) || n < 1) {
|
|
27585
|
+
throw new Error("--limit must be a positive integer");
|
|
27586
|
+
}
|
|
27587
|
+
return Math.min(n, MAX_LIMIT);
|
|
27588
|
+
}
|
|
27589
|
+
).option("-o, --output <format>", "Output format: table, json, tsv (default: table)", "table").addHelpText(
|
|
27535
27590
|
"after",
|
|
27536
27591
|
`
|
|
27537
27592
|
Examples:
|
|
27538
27593
|
tracecli pentest findings list --pentest PT-3
|
|
27594
|
+
tracecli pentest findings list --pentest PT-3 --severity CRITICAL
|
|
27595
|
+
tracecli pentest findings list --pentest PT-3 --status OPEN --limit 20
|
|
27539
27596
|
tracecli pentest findings list --pentest PT-3 --output json
|
|
27540
27597
|
tracecli pentest findings list --pentest PT-3 --output tsv | cut -f1,4`
|
|
27541
|
-
).action(
|
|
27542
|
-
|
|
27543
|
-
|
|
27544
|
-
|
|
27545
|
-
|
|
27546
|
-
|
|
27547
|
-
|
|
27548
|
-
|
|
27549
|
-
if (result.status === 401) {
|
|
27550
|
-
console.error("Authentication failed. Run `tracecli auth login` to re-authenticate.");
|
|
27551
|
-
process.exit(1);
|
|
27552
|
-
}
|
|
27553
|
-
if (result.status === 404) {
|
|
27554
|
-
console.error(
|
|
27555
|
-
`Pentest '${options.pentest}' not found. Verify the ID with \`tracecli pentest list\`.`
|
|
27598
|
+
).action(
|
|
27599
|
+
async (options) => {
|
|
27600
|
+
const organizationId = await resolveOrganizationId();
|
|
27601
|
+
const pentestId = await resolvePentestId(organizationId, options.pentest);
|
|
27602
|
+
const result = await authenticatedRequest(
|
|
27603
|
+
(client) => client.pentestFinding.listFindings({
|
|
27604
|
+
params: { organizationId, pentestId }
|
|
27605
|
+
})
|
|
27556
27606
|
);
|
|
27557
|
-
|
|
27558
|
-
|
|
27559
|
-
|
|
27560
|
-
|
|
27561
|
-
|
|
27562
|
-
|
|
27563
|
-
|
|
27564
|
-
|
|
27565
|
-
|
|
27566
|
-
|
|
27607
|
+
if (result.status === 401) {
|
|
27608
|
+
console.error("Authentication failed. Run `tracecli auth login` to re-authenticate.");
|
|
27609
|
+
process.exit(1);
|
|
27610
|
+
}
|
|
27611
|
+
if (result.status === 404) {
|
|
27612
|
+
console.error(
|
|
27613
|
+
`Pentest '${options.pentest}' not found. Verify the ID with \`tracecli pentest list\`.`
|
|
27614
|
+
);
|
|
27615
|
+
process.exit(1);
|
|
27616
|
+
}
|
|
27617
|
+
if (result.status !== 200) {
|
|
27618
|
+
console.error(`Failed to list findings (status ${result.status}).`);
|
|
27619
|
+
process.exit(1);
|
|
27620
|
+
}
|
|
27621
|
+
let filteredFindings = result.body;
|
|
27622
|
+
if (options.severity) {
|
|
27623
|
+
filteredFindings = filteredFindings.filter((f) => f.severity === options.severity);
|
|
27624
|
+
}
|
|
27625
|
+
if (options.status) {
|
|
27626
|
+
filteredFindings = filteredFindings.filter((f) => f.status === options.status);
|
|
27627
|
+
}
|
|
27628
|
+
filteredFindings.sort(
|
|
27629
|
+
(a, b) => (SEVERITY_ORDER[a.severity] ?? 99) - (SEVERITY_ORDER[b.severity] ?? 99)
|
|
27630
|
+
);
|
|
27631
|
+
const limit = options.limit ?? DEFAULT_LIMIT;
|
|
27632
|
+
const findings = filteredFindings.slice(0, limit);
|
|
27633
|
+
const isJson = options.output === "json";
|
|
27634
|
+
const isTsv = options.output === "tsv";
|
|
27635
|
+
if (findings.length === 0) {
|
|
27636
|
+
if (isJson) {
|
|
27637
|
+
console.log("[]");
|
|
27638
|
+
} else if (isTsv) {
|
|
27639
|
+
console.log("severity status category title cweIds cvssScore");
|
|
27640
|
+
} else {
|
|
27641
|
+
console.log("No findings found for this pentest.");
|
|
27642
|
+
}
|
|
27643
|
+
return;
|
|
27644
|
+
}
|
|
27567
27645
|
if (isJson) {
|
|
27568
|
-
console.log(
|
|
27569
|
-
|
|
27570
|
-
console.log("severity status category title cweIds cvssScore");
|
|
27571
|
-
} else {
|
|
27572
|
-
console.log("No findings found for this pentest.");
|
|
27646
|
+
console.log(JSON.stringify(findings, null, 2));
|
|
27647
|
+
return;
|
|
27573
27648
|
}
|
|
27574
|
-
|
|
27575
|
-
|
|
27576
|
-
|
|
27577
|
-
|
|
27578
|
-
|
|
27579
|
-
|
|
27580
|
-
|
|
27581
|
-
|
|
27582
|
-
|
|
27583
|
-
|
|
27584
|
-
|
|
27585
|
-
|
|
27586
|
-
|
|
27587
|
-
|
|
27588
|
-
finding.status,
|
|
27589
|
-
tsvSafe2(finding.category),
|
|
27590
|
-
tsvSafe2(finding.title),
|
|
27591
|
-
finding.cweIds.join(";"),
|
|
27592
|
-
finding.cvssScore != null ? String(finding.cvssScore) : ""
|
|
27593
|
-
];
|
|
27594
|
-
console.log(cols.join(" "));
|
|
27649
|
+
if (isTsv) {
|
|
27650
|
+
console.log("severity status category title cweIds cvssScore");
|
|
27651
|
+
for (const finding of findings) {
|
|
27652
|
+
const cols = [
|
|
27653
|
+
finding.severity,
|
|
27654
|
+
finding.status,
|
|
27655
|
+
tsvSafe2(finding.category),
|
|
27656
|
+
tsvSafe2(finding.title),
|
|
27657
|
+
finding.cweIds.join(";"),
|
|
27658
|
+
finding.cvssScore != null ? String(finding.cvssScore) : ""
|
|
27659
|
+
];
|
|
27660
|
+
console.log(cols.join(" "));
|
|
27661
|
+
}
|
|
27662
|
+
return;
|
|
27595
27663
|
}
|
|
27596
|
-
|
|
27597
|
-
}
|
|
27598
|
-
console.log(`Found ${findings.length} finding${findings.length === 1 ? "" : "s"}:
|
|
27664
|
+
console.log(`Found ${findings.length} finding${findings.length === 1 ? "" : "s"}:
|
|
27599
27665
|
`);
|
|
27600
|
-
|
|
27601
|
-
|
|
27602
|
-
|
|
27603
|
-
|
|
27604
|
-
|
|
27605
|
-
|
|
27606
|
-
|
|
27607
|
-
|
|
27608
|
-
|
|
27609
|
-
|
|
27610
|
-
|
|
27611
|
-
|
|
27666
|
+
for (const finding of findings) {
|
|
27667
|
+
const sev = formatFindingSeverity(finding.severity);
|
|
27668
|
+
const severityLabel = colorSeverity(finding.severity, sev);
|
|
27669
|
+
const status = formatStatus(finding.status);
|
|
27670
|
+
const cwes = finding.cweIds.length > 0 ? ` | ${finding.cweIds.join(", ")}` : "";
|
|
27671
|
+
const score = finding.cvssScore != null ? ` | CVSS ${finding.cvssScore.toFixed(1)}` : "";
|
|
27672
|
+
console.log(` [${severityLabel}] ${finding.title}`);
|
|
27673
|
+
console.log(` ${finding.category} \xB7 ${status}${cwes}${score}`);
|
|
27674
|
+
if (finding.repositories.length > 0) {
|
|
27675
|
+
for (const repo of finding.repositories) {
|
|
27676
|
+
const location = repo.filePath ? `${repo.repositoryName} \xB7 ${repo.filePath}${repo.startLine != null ? `:${repo.startLine}` : ""}` : repo.repositoryName;
|
|
27677
|
+
console.log(` ${location}`);
|
|
27678
|
+
}
|
|
27612
27679
|
}
|
|
27680
|
+
console.log("");
|
|
27613
27681
|
}
|
|
27614
|
-
console.log("");
|
|
27615
27682
|
}
|
|
27616
|
-
|
|
27683
|
+
);
|
|
27617
27684
|
}
|
|
27618
27685
|
function createFindingsCommand() {
|
|
27619
27686
|
const findings = new Command("findings").description("Manage pentest findings");
|
|
@@ -27908,6 +27975,22 @@ function printRepositoriesTsv(repositories) {
|
|
|
27908
27975
|
);
|
|
27909
27976
|
}
|
|
27910
27977
|
}
|
|
27978
|
+
function printRepositoriesTable(repositories) {
|
|
27979
|
+
const headers = ["Repository URL", "Platform", "Status"];
|
|
27980
|
+
const rows = repositories.map(
|
|
27981
|
+
(repo) => [
|
|
27982
|
+
stripAnsi(String(repo.repositoryUrl)),
|
|
27983
|
+
stripAnsi(String(repo.platform)).toUpperCase(),
|
|
27984
|
+
repo.status === "CONNECTED" ? "connected" : "disconnected"
|
|
27985
|
+
]
|
|
27986
|
+
);
|
|
27987
|
+
const colWidths = headers.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
27988
|
+
const pad = (s, w3) => s.padEnd(w3);
|
|
27989
|
+
console.log(headers.map((h, i) => pad(h, colWidths[i] ?? h.length)).join(" "));
|
|
27990
|
+
for (const row of rows) {
|
|
27991
|
+
console.log(row.map((cell, i) => pad(cell, colWidths[i] ?? 0)).join(" "));
|
|
27992
|
+
}
|
|
27993
|
+
}
|
|
27911
27994
|
function collectFilterExpression(value, previous) {
|
|
27912
27995
|
previous.push(value);
|
|
27913
27996
|
return previous;
|
|
@@ -28031,7 +28114,7 @@ function addRepositoryFilterOptions(command) {
|
|
|
28031
28114
|
"Advanced filter expression. Repeatable. Examples: platform=GITHUB, repo:trace, status!=DISCONNECTED",
|
|
28032
28115
|
collectFilterExpression,
|
|
28033
28116
|
[]
|
|
28034
|
-
).option("-o, --output <format>", "Output format: table
|
|
28117
|
+
).option("-o, --output <format>", "Output format: table, json, tsv (default: table)", "table").option(
|
|
28035
28118
|
"-L, --limit <n>",
|
|
28036
28119
|
`Maximum repositories to display (default ${DEFAULT_LIMIT2}, max ${MAX_LIMIT2})`,
|
|
28037
28120
|
parseListLimit,
|
|
@@ -28089,12 +28172,7 @@ async function runRepositoryListCommand(options) {
|
|
|
28089
28172
|
printRepositoriesTsv(displayedRepositories);
|
|
28090
28173
|
return;
|
|
28091
28174
|
}
|
|
28092
|
-
|
|
28093
|
-
const status = repo.status === "CONNECTED" ? "connected" : "disconnected";
|
|
28094
|
-
const url2 = stripAnsi(String(repo.repositoryUrl));
|
|
28095
|
-
const platform = stripAnsi(String(repo.platform)).toLowerCase();
|
|
28096
|
-
console.log(`${url2} (${platform}, ${status})`);
|
|
28097
|
-
}
|
|
28175
|
+
printRepositoriesTable(displayedRepositories);
|
|
28098
28176
|
}
|
|
28099
28177
|
async function runDependencyFilter(organizationId, options) {
|
|
28100
28178
|
const dependency = options.dependency;
|
|
@@ -28155,6 +28233,7 @@ async function runDependencyFilter(organizationId, options) {
|
|
|
28155
28233
|
return;
|
|
28156
28234
|
}
|
|
28157
28235
|
if (options.output === "tsv") {
|
|
28236
|
+
console.log("repository versions");
|
|
28158
28237
|
for (const repo of displayedRepos) {
|
|
28159
28238
|
console.log(`${tsvSafe(repo.fullName)} ${tsvSafe(repo.versions.join(","))}`);
|
|
28160
28239
|
}
|
|
@@ -28304,7 +28383,7 @@ Examples:
|
|
|
28304
28383
|
installationToken = cloneTokenResult.token;
|
|
28305
28384
|
cloneTokenFailure = cloneTokenResult.failure;
|
|
28306
28385
|
if (cloneTokenFailure) {
|
|
28307
|
-
console.warn(buildCloneTokenFailureWarning(cloneUrl, cloneTokenFailure));
|
|
28386
|
+
console.warn(colorWarning(buildCloneTokenFailureWarning(cloneUrl, cloneTokenFailure)));
|
|
28308
28387
|
}
|
|
28309
28388
|
}
|
|
28310
28389
|
const effectiveUrl = installationToken ? urlWithUsername(cloneUrl) : cloneUrl;
|
|
@@ -28336,7 +28415,9 @@ Examples:
|
|
|
28336
28415
|
if (exitCode !== 0) {
|
|
28337
28416
|
if (!installationToken && cloneTokenFailure) {
|
|
28338
28417
|
console.error(
|
|
28339
|
-
|
|
28418
|
+
colorError(
|
|
28419
|
+
"Clone failed while Trace clone credentials were unavailable. Resolve the warning above and retry."
|
|
28420
|
+
)
|
|
28340
28421
|
);
|
|
28341
28422
|
}
|
|
28342
28423
|
process.exit(1);
|
|
@@ -28350,11 +28431,13 @@ Examples:
|
|
|
28350
28431
|
const resetExitCode = await resetProc.exited;
|
|
28351
28432
|
if (resetExitCode !== 0) {
|
|
28352
28433
|
console.error(
|
|
28353
|
-
|
|
28434
|
+
colorWarningPrefix(
|
|
28435
|
+
"failed to reset remote URL \u2014 the token username may persist in .git/config"
|
|
28436
|
+
)
|
|
28354
28437
|
);
|
|
28355
28438
|
}
|
|
28356
28439
|
}
|
|
28357
|
-
console.log(`Repository cloned into ${targetDir}`);
|
|
28440
|
+
console.log(colorSuccess(`Repository cloned into ${targetDir}`));
|
|
28358
28441
|
} finally {
|
|
28359
28442
|
if (askPassScript) {
|
|
28360
28443
|
rmSync2(askPassScript.replace(/\/[^/]+$/, ""), { recursive: true, force: true });
|
|
@@ -28504,16 +28587,87 @@ function resolveRegistryUrl() {
|
|
|
28504
28587
|
if (typeof override === "string" && override.trim().length > 0) {
|
|
28505
28588
|
return override;
|
|
28506
28589
|
}
|
|
28507
|
-
return `https://registry.npmjs.org/${encodeURIComponent(CLI_PACKAGE_NAME)}
|
|
28590
|
+
return `https://registry.npmjs.org/${encodeURIComponent(CLI_PACKAGE_NAME)}`;
|
|
28591
|
+
}
|
|
28592
|
+
function isRecord(value) {
|
|
28593
|
+
return typeof value === "object" && value !== null;
|
|
28594
|
+
}
|
|
28595
|
+
function selectNewestVersion(versions) {
|
|
28596
|
+
let newestVersion = null;
|
|
28597
|
+
for (const version2 of versions) {
|
|
28598
|
+
if (newestVersion === null) {
|
|
28599
|
+
newestVersion = version2;
|
|
28600
|
+
continue;
|
|
28601
|
+
}
|
|
28602
|
+
const comparison = compareSemver(version2, newestVersion);
|
|
28603
|
+
if (comparison !== null && comparison > 0) {
|
|
28604
|
+
newestVersion = version2;
|
|
28605
|
+
}
|
|
28606
|
+
}
|
|
28607
|
+
return newestVersion;
|
|
28608
|
+
}
|
|
28609
|
+
function isStableSemver(version2) {
|
|
28610
|
+
const parsed = parseSemver(version2);
|
|
28611
|
+
return parsed !== null && parsed.prerelease.length === 0;
|
|
28612
|
+
}
|
|
28613
|
+
function extractPublishedVersions(payload) {
|
|
28614
|
+
const versionsField = payload["versions"];
|
|
28615
|
+
if (!isRecord(versionsField)) {
|
|
28616
|
+
return [];
|
|
28617
|
+
}
|
|
28618
|
+
const publishedVersions = [];
|
|
28619
|
+
for (const version2 of Object.keys(versionsField)) {
|
|
28620
|
+
if (parseSemver(version2) !== null) {
|
|
28621
|
+
publishedVersions.push(version2);
|
|
28622
|
+
}
|
|
28623
|
+
}
|
|
28624
|
+
return publishedVersions;
|
|
28625
|
+
}
|
|
28626
|
+
function extractDistTagLatestVersion(payload) {
|
|
28627
|
+
const distTags = payload["dist-tags"];
|
|
28628
|
+
if (!isRecord(distTags)) {
|
|
28629
|
+
return null;
|
|
28630
|
+
}
|
|
28631
|
+
if (typeof distTags["latest"] !== "string") {
|
|
28632
|
+
return null;
|
|
28633
|
+
}
|
|
28634
|
+
const candidate = distTags["latest"].trim();
|
|
28635
|
+
if (candidate.length === 0 || parseSemver(candidate) === null) {
|
|
28636
|
+
return null;
|
|
28637
|
+
}
|
|
28638
|
+
return candidate;
|
|
28639
|
+
}
|
|
28640
|
+
function extractLatestEndpointVersion(payload) {
|
|
28641
|
+
if (typeof payload["version"] !== "string") {
|
|
28642
|
+
return null;
|
|
28643
|
+
}
|
|
28644
|
+
const candidate = payload["version"].trim();
|
|
28645
|
+
if (candidate.length === 0 || parseSemver(candidate) === null) {
|
|
28646
|
+
return null;
|
|
28647
|
+
}
|
|
28648
|
+
return candidate;
|
|
28508
28649
|
}
|
|
28509
28650
|
function extractLatestVersionFromRegistryPayload(payload) {
|
|
28510
|
-
if (
|
|
28651
|
+
if (!isRecord(payload)) {
|
|
28511
28652
|
return null;
|
|
28512
28653
|
}
|
|
28513
|
-
|
|
28514
|
-
|
|
28654
|
+
const publishedVersions = extractPublishedVersions(payload);
|
|
28655
|
+
const hasPublishedVersions = publishedVersions.length > 0;
|
|
28656
|
+
const publishedVersionSet = new Set(publishedVersions);
|
|
28657
|
+
const distTagLatestVersion = extractDistTagLatestVersion(payload);
|
|
28658
|
+
if (distTagLatestVersion && (!hasPublishedVersions || publishedVersionSet.has(distTagLatestVersion))) {
|
|
28659
|
+
return distTagLatestVersion;
|
|
28515
28660
|
}
|
|
28516
|
-
|
|
28661
|
+
const latestEndpointVersion = extractLatestEndpointVersion(payload);
|
|
28662
|
+
if (latestEndpointVersion && (!hasPublishedVersions || publishedVersionSet.has(latestEndpointVersion))) {
|
|
28663
|
+
return latestEndpointVersion;
|
|
28664
|
+
}
|
|
28665
|
+
const stablePublishedVersions = publishedVersions.filter((version2) => isStableSemver(version2));
|
|
28666
|
+
const newestStableVersion = selectNewestVersion(stablePublishedVersions);
|
|
28667
|
+
if (newestStableVersion) {
|
|
28668
|
+
return newestStableVersion;
|
|
28669
|
+
}
|
|
28670
|
+
return selectNewestVersion(publishedVersions);
|
|
28517
28671
|
}
|
|
28518
28672
|
async function fetchLatestVersionFromRegistry() {
|
|
28519
28673
|
try {
|
|
@@ -28580,15 +28734,21 @@ async function maybeNotifyUpdateAvailable(commandName) {
|
|
|
28580
28734
|
return;
|
|
28581
28735
|
}
|
|
28582
28736
|
noticePrinted = true;
|
|
28583
|
-
|
|
28584
|
-
|
|
28585
|
-
`
|
|
28586
|
-
|
|
28587
|
-
|
|
28588
|
-
|
|
28589
|
-
}
|
|
28590
|
-
|
|
28591
|
-
const
|
|
28737
|
+
const lines = [
|
|
28738
|
+
`A new tracecli version is available: ${availability.currentVersion} -> ${availability.latestVersion}`,
|
|
28739
|
+
"Run `tracecli upgrade` to install the latest version."
|
|
28740
|
+
];
|
|
28741
|
+
const maxLen = Math.max(...lines.map((l) => l.length));
|
|
28742
|
+
const pad = (s) => s + " ".repeat(maxLen - s.length);
|
|
28743
|
+
const top = `\u256D${"\u2500".repeat(maxLen + 2)}\u256E`;
|
|
28744
|
+
const bottom = `\u2570${"\u2500".repeat(maxLen + 2)}\u256F`;
|
|
28745
|
+
const boxed = [top, ...lines.map((l) => `\u2502 ${pad(l)} \u2502`), bottom].join("\n");
|
|
28746
|
+
console.log("");
|
|
28747
|
+
console.log(colorMuted(boxed));
|
|
28748
|
+
console.log("");
|
|
28749
|
+
}
|
|
28750
|
+
function resolveUpgradePlan(targetVersion) {
|
|
28751
|
+
const packageSpec = targetVersion ? `${CLI_PACKAGE_NAME}@${targetVersion}` : `${CLI_PACKAGE_NAME}@latest`;
|
|
28592
28752
|
if (Bun.which("npm")) {
|
|
28593
28753
|
return { command: "npm", args: ["install", "-g", packageSpec] };
|
|
28594
28754
|
}
|
|
@@ -28652,7 +28812,7 @@ Examples:
|
|
|
28652
28812
|
console.log("Upgrade cancelled.");
|
|
28653
28813
|
return;
|
|
28654
28814
|
}
|
|
28655
|
-
const plan = resolveUpgradePlan();
|
|
28815
|
+
const plan = resolveUpgradePlan(availability.latestVersion);
|
|
28656
28816
|
if (!plan) {
|
|
28657
28817
|
console.error(
|
|
28658
28818
|
`No supported package manager found (npm, bun, pnpm). Install the latest version manually from https://www.npmjs.com/package/${CLI_PACKAGE_NAME}`
|
|
@@ -28886,7 +29046,7 @@ function createVulnerabilitiesCommand() {
|
|
|
28886
29046
|
// src/index.ts
|
|
28887
29047
|
var program2 = new Command();
|
|
28888
29048
|
program2.name("tracecli").description("Trace CLI").version(CLI_VERSION);
|
|
28889
|
-
program2.hook("
|
|
29049
|
+
program2.hook("postAction", async (_thisCommand, actionCommand) => {
|
|
28890
29050
|
await maybeNotifyUpdateAvailable(actionCommand.name());
|
|
28891
29051
|
});
|
|
28892
29052
|
program2.action(() => {
|