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.
Files changed (3) hide show
  1. package/README.md +2 -1
  2. package/dist/index.js +210 -59
  3. 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
- - Auth tokens are stored at `~/.trace/token`.
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 TRACE_DIR = join(homedir(), ".trace");
3419
- var TOKEN_PATH = join(TRACE_DIR, "token");
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(TOKEN_PATH)) {
3472
+ if (!existsSync(refreshTokenPath)) {
3423
3473
  return null;
3424
3474
  }
3425
- const token = readFileSync(TOKEN_PATH, "utf-8").trim();
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
- if (!existsSync(TRACE_DIR)) {
3433
- mkdirSync(TRACE_DIR, { recursive: true, mode: 448 });
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
- writeFileSync(TOKEN_PATH, token, { encoding: "utf-8", mode: 384 });
3495
+ removeRefreshToken();
3436
3496
  }
3437
- function removeToken() {
3438
- if (!existsSync(TOKEN_PATH)) {
3497
+ function removeRefreshToken() {
3498
+ const refreshTokenPath = getRefreshTokenPath();
3499
+ if (!existsSync(refreshTokenPath)) {
3439
3500
  return false;
3440
3501
  }
3441
- rmSync(TOKEN_PATH);
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
- writeToken(tokenResponse.access_token);
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("Token saved to ~/.trace/token");
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
- console.log(
3545
- "\nYour token has expired. Run `trace auth login` after logging out to re-authenticate."
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 TRACE_DIR2 = ".trace";
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 "tracecli-completion.zsh";
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(), TRACE_DIR2);
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 = `~/${TRACE_DIR2}`;
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 { token } = requireAuth();
24289
- const client = createClient(token);
24290
- const organizationId = await resolveOrganizationId(client);
24291
- const result = await client.repository.listRepositories({
24292
- params: { organizationId }
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 client = createClient(token);
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 client.repository.getCloneToken({
24340
- params: { organizationId },
24341
- body: { repositoryUrl: repoUrl }
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 resolveRepositoryId(client, organizationId, repoIdentifier) {
24427
- const result = await client.repository.listRepositories({
24428
- params: { organizationId }
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 { token } = requireAuth();
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 resolveRepositoryId(client, organizationId, options.repo);
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 client.vulnerability.filterVulnerabilities({
24557
- params: { organizationId },
24558
- body: { ...filterBody, page, pageSize }
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securewithtrace",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Official Trace CLI for vulnerability intelligence in your terminal",
5
5
  "type": "module",
6
6
  "bin": {