securewithtrace 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/index.js +210 -59
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -101,7 +101,8 @@ Supported filter values:
|
|
|
101
101
|
|
|
102
102
|
## Security Notes
|
|
103
103
|
|
|
104
|
-
-
|
|
104
|
+
- Access tokens are stored at `~/.trace/token`.
|
|
105
|
+
- Refresh tokens are stored at `~/.trace/refresh-token` and used for automatic access-token refresh.
|
|
105
106
|
- `repo clone` uses secure credential flow for GitHub clones when available.
|
|
106
107
|
- Credentials are not persisted in Git remote URLs after clone completes.
|
|
107
108
|
|
package/dist/index.js
CHANGED
|
@@ -3410,37 +3410,110 @@ async function pollForToken(clientId, deviceCode, options = {}) {
|
|
|
3410
3410
|
}
|
|
3411
3411
|
throw new Error("Authorization timed out. Please try again.");
|
|
3412
3412
|
}
|
|
3413
|
+
async function refreshAccessToken(clientId, refreshToken) {
|
|
3414
|
+
const response = await fetch(WORKOS_TOKEN_URL, {
|
|
3415
|
+
method: "POST",
|
|
3416
|
+
headers: {
|
|
3417
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
3418
|
+
},
|
|
3419
|
+
body: new URLSearchParams({
|
|
3420
|
+
grant_type: "refresh_token",
|
|
3421
|
+
client_id: clientId,
|
|
3422
|
+
refresh_token: refreshToken
|
|
3423
|
+
})
|
|
3424
|
+
});
|
|
3425
|
+
if (!response.ok) {
|
|
3426
|
+
const text = await response.text();
|
|
3427
|
+
throw new Error(`Failed to refresh token: ${response.status} ${text}`);
|
|
3428
|
+
}
|
|
3429
|
+
return await response.json();
|
|
3430
|
+
}
|
|
3413
3431
|
|
|
3414
3432
|
// src/auth/token.ts
|
|
3415
3433
|
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
3416
3434
|
import { homedir } from "os";
|
|
3417
3435
|
import { join } from "path";
|
|
3418
|
-
var
|
|
3419
|
-
var
|
|
3436
|
+
var ACCESS_TOKEN_FILENAME = "token";
|
|
3437
|
+
var REFRESH_TOKEN_FILENAME = "refresh-token";
|
|
3438
|
+
function resolveTraceDir() {
|
|
3439
|
+
const override = process.env["TRACE_CLI_AUTH_DIR"];
|
|
3440
|
+
if (override && override.trim().length > 0) {
|
|
3441
|
+
return override;
|
|
3442
|
+
}
|
|
3443
|
+
return join(homedir(), ".trace");
|
|
3444
|
+
}
|
|
3445
|
+
function ensureTraceDir() {
|
|
3446
|
+
const traceDir = resolveTraceDir();
|
|
3447
|
+
if (!existsSync(traceDir)) {
|
|
3448
|
+
mkdirSync(traceDir, { recursive: true, mode: 448 });
|
|
3449
|
+
}
|
|
3450
|
+
}
|
|
3451
|
+
function getTokenPath() {
|
|
3452
|
+
return join(resolveTraceDir(), ACCESS_TOKEN_FILENAME);
|
|
3453
|
+
}
|
|
3454
|
+
function getRefreshTokenPath() {
|
|
3455
|
+
return join(resolveTraceDir(), REFRESH_TOKEN_FILENAME);
|
|
3456
|
+
}
|
|
3420
3457
|
function readToken() {
|
|
3458
|
+
const tokenPath = getTokenPath();
|
|
3459
|
+
try {
|
|
3460
|
+
if (!existsSync(tokenPath)) {
|
|
3461
|
+
return null;
|
|
3462
|
+
}
|
|
3463
|
+
const token = readFileSync(tokenPath, "utf-8").trim();
|
|
3464
|
+
return token || null;
|
|
3465
|
+
} catch {
|
|
3466
|
+
return null;
|
|
3467
|
+
}
|
|
3468
|
+
}
|
|
3469
|
+
function readRefreshToken() {
|
|
3470
|
+
const refreshTokenPath = getRefreshTokenPath();
|
|
3421
3471
|
try {
|
|
3422
|
-
if (!existsSync(
|
|
3472
|
+
if (!existsSync(refreshTokenPath)) {
|
|
3423
3473
|
return null;
|
|
3424
3474
|
}
|
|
3425
|
-
const token = readFileSync(
|
|
3475
|
+
const token = readFileSync(refreshTokenPath, "utf-8").trim();
|
|
3426
3476
|
return token || null;
|
|
3427
3477
|
} catch {
|
|
3428
3478
|
return null;
|
|
3429
3479
|
}
|
|
3430
3480
|
}
|
|
3431
3481
|
function writeToken(token) {
|
|
3432
|
-
|
|
3433
|
-
|
|
3482
|
+
ensureTraceDir();
|
|
3483
|
+
writeFileSync(getTokenPath(), token, { encoding: "utf-8", mode: 384 });
|
|
3484
|
+
}
|
|
3485
|
+
function writeRefreshToken(token) {
|
|
3486
|
+
ensureTraceDir();
|
|
3487
|
+
writeFileSync(getRefreshTokenPath(), token, { encoding: "utf-8", mode: 384 });
|
|
3488
|
+
}
|
|
3489
|
+
function writeTokens(accessToken, refreshToken) {
|
|
3490
|
+
writeToken(accessToken);
|
|
3491
|
+
if (refreshToken && refreshToken.trim().length > 0) {
|
|
3492
|
+
writeRefreshToken(refreshToken);
|
|
3493
|
+
return;
|
|
3434
3494
|
}
|
|
3435
|
-
|
|
3495
|
+
removeRefreshToken();
|
|
3436
3496
|
}
|
|
3437
|
-
function
|
|
3438
|
-
|
|
3497
|
+
function removeRefreshToken() {
|
|
3498
|
+
const refreshTokenPath = getRefreshTokenPath();
|
|
3499
|
+
if (!existsSync(refreshTokenPath)) {
|
|
3439
3500
|
return false;
|
|
3440
3501
|
}
|
|
3441
|
-
rmSync(
|
|
3502
|
+
rmSync(refreshTokenPath);
|
|
3442
3503
|
return true;
|
|
3443
3504
|
}
|
|
3505
|
+
function removeToken() {
|
|
3506
|
+
let removed = false;
|
|
3507
|
+
const tokenPath = getTokenPath();
|
|
3508
|
+
if (existsSync(tokenPath)) {
|
|
3509
|
+
rmSync(tokenPath);
|
|
3510
|
+
removed = true;
|
|
3511
|
+
}
|
|
3512
|
+
if (removeRefreshToken()) {
|
|
3513
|
+
removed = true;
|
|
3514
|
+
}
|
|
3515
|
+
return removed;
|
|
3516
|
+
}
|
|
3444
3517
|
|
|
3445
3518
|
// src/auth/commands.ts
|
|
3446
3519
|
function getClientId() {
|
|
@@ -3501,12 +3574,12 @@ function createLoginCommand() {
|
|
|
3501
3574
|
expiresIn: auth.expires_in,
|
|
3502
3575
|
interval: auth.interval
|
|
3503
3576
|
});
|
|
3504
|
-
|
|
3577
|
+
writeTokens(tokenResponse.access_token, tokenResponse.refresh_token);
|
|
3505
3578
|
const name = [tokenResponse.user.first_name, tokenResponse.user.last_name].filter(Boolean).join(" ");
|
|
3506
3579
|
const displayName = name || tokenResponse.user.email;
|
|
3507
3580
|
console.log(`
|
|
3508
3581
|
Authenticated as ${displayName} (${tokenResponse.user.email})`);
|
|
3509
|
-
console.log("
|
|
3582
|
+
console.log("Tokens saved to ~/.trace");
|
|
3510
3583
|
});
|
|
3511
3584
|
}
|
|
3512
3585
|
function createLogoutCommand() {
|
|
@@ -3522,6 +3595,7 @@ function createLogoutCommand() {
|
|
|
3522
3595
|
function createStatusCommand() {
|
|
3523
3596
|
return new Command("status").description("Show current authentication status").action(() => {
|
|
3524
3597
|
const token = readToken();
|
|
3598
|
+
const refreshToken = readRefreshToken();
|
|
3525
3599
|
if (token) {
|
|
3526
3600
|
try {
|
|
3527
3601
|
const parts = token.split(".");
|
|
@@ -3540,16 +3614,31 @@ function createStatusCommand() {
|
|
|
3540
3614
|
if (expiry) {
|
|
3541
3615
|
console.log(` Expires: ${expiry.toISOString()}${isExpired ? " (EXPIRED)" : ""}`);
|
|
3542
3616
|
}
|
|
3617
|
+
console.log(
|
|
3618
|
+
` Refresh: ${refreshToken ? "Available (auto-refresh enabled)" : "Not available"}`
|
|
3619
|
+
);
|
|
3543
3620
|
if (isExpired) {
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3621
|
+
if (refreshToken) {
|
|
3622
|
+
console.log(
|
|
3623
|
+
"\nYour access token is expired, but a refresh token is available and will be used automatically."
|
|
3624
|
+
);
|
|
3625
|
+
} else {
|
|
3626
|
+
console.log(
|
|
3627
|
+
"\nYour token has expired. Run `trace auth login` after logging out to re-authenticate."
|
|
3628
|
+
);
|
|
3629
|
+
}
|
|
3547
3630
|
}
|
|
3548
3631
|
} else {
|
|
3549
|
-
console.log("Logged in to Trace (token stored at ~/.trace/token)");
|
|
3632
|
+
console.log("Logged in to Trace (access token stored at ~/.trace/token)");
|
|
3633
|
+
console.log(
|
|
3634
|
+
` Refresh: ${refreshToken ? "Available (auto-refresh enabled)" : "Not available"}`
|
|
3635
|
+
);
|
|
3550
3636
|
}
|
|
3551
3637
|
} catch {
|
|
3552
|
-
console.log("Logged in to Trace (token stored at ~/.trace/token)");
|
|
3638
|
+
console.log("Logged in to Trace (access token stored at ~/.trace/token)");
|
|
3639
|
+
console.log(
|
|
3640
|
+
` Refresh: ${refreshToken ? "Available (auto-refresh enabled)" : "Not available"}`
|
|
3641
|
+
);
|
|
3553
3642
|
}
|
|
3554
3643
|
} else {
|
|
3555
3644
|
console.log("Not logged in. Run `trace auth login` to authenticate.");
|
|
@@ -3848,19 +3937,19 @@ function generateCompletion(program3, shell, binName) {
|
|
|
3848
3937
|
import { existsSync as existsSync2, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
3849
3938
|
import { homedir as homedir2 } from "os";
|
|
3850
3939
|
import { join as join2 } from "path";
|
|
3851
|
-
var
|
|
3940
|
+
var TRACE_DIR = ".trace";
|
|
3852
3941
|
function completionFileName(shell) {
|
|
3853
3942
|
switch (shell) {
|
|
3854
3943
|
case "bash":
|
|
3855
3944
|
return "tracecli-completion.bash";
|
|
3856
3945
|
case "zsh":
|
|
3857
|
-
return "
|
|
3946
|
+
return "_tracecli";
|
|
3858
3947
|
case "fish":
|
|
3859
3948
|
return "tracecli-completion.fish";
|
|
3860
3949
|
}
|
|
3861
3950
|
}
|
|
3862
3951
|
function getTraceDir() {
|
|
3863
|
-
return join2(homedir2(),
|
|
3952
|
+
return join2(homedir2(), TRACE_DIR);
|
|
3864
3953
|
}
|
|
3865
3954
|
function getCompletionFilePath(shell) {
|
|
3866
3955
|
return join2(getTraceDir(), completionFileName(shell));
|
|
@@ -3875,7 +3964,7 @@ function writeCompletionFile(shell, script) {
|
|
|
3875
3964
|
return filePath;
|
|
3876
3965
|
}
|
|
3877
3966
|
function getSetupInstructions(shell) {
|
|
3878
|
-
const traceDir = `~/${
|
|
3967
|
+
const traceDir = `~/${TRACE_DIR}`;
|
|
3879
3968
|
switch (shell) {
|
|
3880
3969
|
case "bash":
|
|
3881
3970
|
return [
|
|
@@ -24149,6 +24238,7 @@ var FindingSchema = external_exports.object({
|
|
|
24149
24238
|
// src/api-client.ts
|
|
24150
24239
|
var readTokenSafe = readToken;
|
|
24151
24240
|
var PRODUCTION_API_URL = "https://api.securewithtrace.com";
|
|
24241
|
+
var UNAUTHORIZED_STATUS = 401;
|
|
24152
24242
|
function getApiBaseUrl() {
|
|
24153
24243
|
const raw = process.env["TRACE_API_URL"];
|
|
24154
24244
|
if (!raw) {
|
|
@@ -24174,14 +24264,6 @@ function getApiBaseUrl() {
|
|
|
24174
24264
|
}
|
|
24175
24265
|
return raw.replace(/\/+$/, "");
|
|
24176
24266
|
}
|
|
24177
|
-
function requireAuth() {
|
|
24178
|
-
const token = readToken();
|
|
24179
|
-
if (!token) {
|
|
24180
|
-
console.error("Not logged in. Run `tracecli auth login` first.");
|
|
24181
|
-
process.exit(1);
|
|
24182
|
-
}
|
|
24183
|
-
return { token };
|
|
24184
|
-
}
|
|
24185
24267
|
function createClient(token) {
|
|
24186
24268
|
return initClient(v1Contract, {
|
|
24187
24269
|
baseUrl: getApiBaseUrl(),
|
|
@@ -24190,9 +24272,71 @@ function createClient(token) {
|
|
|
24190
24272
|
}
|
|
24191
24273
|
});
|
|
24192
24274
|
}
|
|
24275
|
+
function getWorkosClientId() {
|
|
24276
|
+
const clientId = "client_01KEZ01W5Q3107GTBY51WQ67HT";
|
|
24277
|
+
if (!clientId || clientId.trim().length === 0) {
|
|
24278
|
+
return null;
|
|
24279
|
+
}
|
|
24280
|
+
return clientId;
|
|
24281
|
+
}
|
|
24282
|
+
async function refreshStoredAccessToken() {
|
|
24283
|
+
const clientId = getWorkosClientId();
|
|
24284
|
+
const refreshToken = readRefreshToken();
|
|
24285
|
+
if (!clientId || !refreshToken) {
|
|
24286
|
+
return null;
|
|
24287
|
+
}
|
|
24288
|
+
try {
|
|
24289
|
+
const tokenResponse = await refreshAccessToken(clientId, refreshToken);
|
|
24290
|
+
const nextRefreshToken = tokenResponse.refresh_token ?? refreshToken;
|
|
24291
|
+
writeTokens(tokenResponse.access_token, nextRefreshToken);
|
|
24292
|
+
return tokenResponse.access_token;
|
|
24293
|
+
} catch (error48) {
|
|
24294
|
+
if (error48 instanceof Error && (error48.message.includes(" 400 ") || error48.message.includes(" 401 "))) {
|
|
24295
|
+
removeToken();
|
|
24296
|
+
}
|
|
24297
|
+
return null;
|
|
24298
|
+
}
|
|
24299
|
+
}
|
|
24300
|
+
async function authenticatedRequest(request) {
|
|
24301
|
+
const token = readToken();
|
|
24302
|
+
if (!token) {
|
|
24303
|
+
console.error("Not logged in. Run `tracecli auth login` first.");
|
|
24304
|
+
process.exit(1);
|
|
24305
|
+
}
|
|
24306
|
+
const firstResult = await request(createClient(token));
|
|
24307
|
+
if (firstResult.status !== UNAUTHORIZED_STATUS) {
|
|
24308
|
+
return firstResult;
|
|
24309
|
+
}
|
|
24310
|
+
const refreshedToken = await refreshStoredAccessToken();
|
|
24311
|
+
if (!refreshedToken) {
|
|
24312
|
+
return firstResult;
|
|
24313
|
+
}
|
|
24314
|
+
return request(createClient(refreshedToken));
|
|
24315
|
+
}
|
|
24316
|
+
async function authenticatedRequestSafe(request) {
|
|
24317
|
+
const token = readTokenSafe();
|
|
24318
|
+
if (!token) {
|
|
24319
|
+
return null;
|
|
24320
|
+
}
|
|
24321
|
+
try {
|
|
24322
|
+
const firstResult = await request(createClient(token));
|
|
24323
|
+
if (firstResult.status !== UNAUTHORIZED_STATUS) {
|
|
24324
|
+
return firstResult;
|
|
24325
|
+
}
|
|
24326
|
+
const refreshedToken = await refreshStoredAccessToken();
|
|
24327
|
+
if (!refreshedToken) {
|
|
24328
|
+
return firstResult;
|
|
24329
|
+
}
|
|
24330
|
+
return await request(createClient(refreshedToken));
|
|
24331
|
+
} catch {
|
|
24332
|
+
return null;
|
|
24333
|
+
}
|
|
24334
|
+
}
|
|
24193
24335
|
var SAFE_ID_PATTERN = /^[a-zA-Z0-9_-]+$/;
|
|
24194
24336
|
async function resolveOrganizationId(client) {
|
|
24195
|
-
const result = await client.organization.listOrganizations()
|
|
24337
|
+
const result = client ? await client.organization.listOrganizations() : await authenticatedRequest(
|
|
24338
|
+
(authenticatedClient) => authenticatedClient.organization.listOrganizations()
|
|
24339
|
+
);
|
|
24196
24340
|
if (result.status !== 200) {
|
|
24197
24341
|
console.error(`Failed to resolve organization (status ${result.status}).`);
|
|
24198
24342
|
process.exit(1);
|
|
@@ -24211,7 +24355,12 @@ async function resolveOrganizationId(client) {
|
|
|
24211
24355
|
}
|
|
24212
24356
|
async function resolveOrganizationIdSafe(client) {
|
|
24213
24357
|
try {
|
|
24214
|
-
const result = await client.organization.listOrganizations()
|
|
24358
|
+
const result = client ? await client.organization.listOrganizations() : await authenticatedRequestSafe(
|
|
24359
|
+
(authenticatedClient) => authenticatedClient.organization.listOrganizations()
|
|
24360
|
+
);
|
|
24361
|
+
if (!result) {
|
|
24362
|
+
return null;
|
|
24363
|
+
}
|
|
24215
24364
|
if (result.status !== 200) {
|
|
24216
24365
|
return null;
|
|
24217
24366
|
}
|
|
@@ -24285,12 +24434,12 @@ function validateBranchName(branch) {
|
|
|
24285
24434
|
}
|
|
24286
24435
|
function createListCommand() {
|
|
24287
24436
|
return new Command("list").description("List repositories in your organization").action(async () => {
|
|
24288
|
-
const
|
|
24289
|
-
const
|
|
24290
|
-
|
|
24291
|
-
|
|
24292
|
-
|
|
24293
|
-
|
|
24437
|
+
const organizationId = await resolveOrganizationId();
|
|
24438
|
+
const result = await authenticatedRequest(
|
|
24439
|
+
(client) => client.repository.listRepositories({
|
|
24440
|
+
params: { organizationId }
|
|
24441
|
+
})
|
|
24442
|
+
);
|
|
24294
24443
|
if (result.status !== 200) {
|
|
24295
24444
|
console.error(`Failed to list repositories (status ${result.status}).`);
|
|
24296
24445
|
process.exit(1);
|
|
@@ -24326,20 +24475,20 @@ echo '${token.replace(/'/g, "'\\''")}'
|
|
|
24326
24475
|
return scriptPath;
|
|
24327
24476
|
}
|
|
24328
24477
|
async function fetchCloneToken(repoUrl) {
|
|
24329
|
-
const token = readTokenSafe();
|
|
24330
|
-
if (!token) {
|
|
24331
|
-
return null;
|
|
24332
|
-
}
|
|
24333
24478
|
try {
|
|
24334
|
-
const
|
|
24335
|
-
const organizationId = await resolveOrganizationIdSafe(client);
|
|
24479
|
+
const organizationId = await resolveOrganizationIdSafe();
|
|
24336
24480
|
if (!organizationId) {
|
|
24337
24481
|
return null;
|
|
24338
24482
|
}
|
|
24339
|
-
const result = await
|
|
24340
|
-
|
|
24341
|
-
|
|
24342
|
-
|
|
24483
|
+
const result = await authenticatedRequestSafe(
|
|
24484
|
+
(client) => client.repository.getCloneToken({
|
|
24485
|
+
params: { organizationId },
|
|
24486
|
+
body: { repositoryUrl: repoUrl }
|
|
24487
|
+
})
|
|
24488
|
+
);
|
|
24489
|
+
if (!result) {
|
|
24490
|
+
return null;
|
|
24491
|
+
}
|
|
24343
24492
|
if (result.status === 200) {
|
|
24344
24493
|
return result.body.token;
|
|
24345
24494
|
}
|
|
@@ -24423,10 +24572,12 @@ var SEVERITY_ORDER = {
|
|
|
24423
24572
|
LOW: 3
|
|
24424
24573
|
};
|
|
24425
24574
|
var DEFAULT_PAGE_SIZE = 25;
|
|
24426
|
-
async function
|
|
24427
|
-
const result = await
|
|
24428
|
-
|
|
24429
|
-
|
|
24575
|
+
async function resolveRepositoryIdAuthenticated(organizationId, repoIdentifier) {
|
|
24576
|
+
const result = await authenticatedRequest(
|
|
24577
|
+
(client) => client.repository.listRepositories({
|
|
24578
|
+
params: { organizationId }
|
|
24579
|
+
})
|
|
24580
|
+
);
|
|
24430
24581
|
if (result.status !== 200) {
|
|
24431
24582
|
console.error(`Failed to list repositories (status ${result.status}).`);
|
|
24432
24583
|
process.exit(1);
|
|
@@ -24527,12 +24678,10 @@ function createListCommand2() {
|
|
|
24527
24678
|
return Math.min(n, 200);
|
|
24528
24679
|
}).option("--json", "Output as JSON").action(
|
|
24529
24680
|
async (options) => {
|
|
24530
|
-
const
|
|
24531
|
-
const client = createClient(token);
|
|
24532
|
-
const organizationId = await resolveOrganizationId(client);
|
|
24681
|
+
const organizationId = await resolveOrganizationId();
|
|
24533
24682
|
const conditions = [];
|
|
24534
24683
|
if (options.repo) {
|
|
24535
|
-
const repositoryId = await
|
|
24684
|
+
const repositoryId = await resolveRepositoryIdAuthenticated(organizationId, options.repo);
|
|
24536
24685
|
conditions.push({ field: "repositoryId", operator: "eq", value: repositoryId });
|
|
24537
24686
|
}
|
|
24538
24687
|
if (options.severity) {
|
|
@@ -24553,10 +24702,12 @@ function createListCommand2() {
|
|
|
24553
24702
|
let total = 0;
|
|
24554
24703
|
const allJsonVulnerabilities = [];
|
|
24555
24704
|
while (true) {
|
|
24556
|
-
const result = await
|
|
24557
|
-
|
|
24558
|
-
|
|
24559
|
-
|
|
24705
|
+
const result = await authenticatedRequest(
|
|
24706
|
+
(client) => client.vulnerability.filterVulnerabilities({
|
|
24707
|
+
params: { organizationId },
|
|
24708
|
+
body: { ...filterBody, page, pageSize }
|
|
24709
|
+
})
|
|
24710
|
+
);
|
|
24560
24711
|
if (result.status !== 200) {
|
|
24561
24712
|
console.error(`Failed to list vulnerabilities (status ${result.status}).`);
|
|
24562
24713
|
process.exit(1);
|