@kinosec/cli 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -6389,6 +6389,9 @@ var ScansResource = class {
6389
6389
  async cancel(id) {
6390
6390
  return this.api.post(`v1/scans/${id}/cancel`, { json: {} }).json();
6391
6391
  }
6392
+ async scoutStatus(profileId) {
6393
+ return this.api.get(`v1/scans/${profileId}/scout-status`).json();
6394
+ }
6392
6395
  };
6393
6396
  var SchedulesResource = class {
6394
6397
  constructor(api) {
@@ -7772,7 +7775,7 @@ Examples:
7772
7775
  }
7773
7776
 
7774
7777
  // src/commands/pentest.ts
7775
- var import_picocolors6 = __toESM(require_picocolors(), 1);
7778
+ var import_picocolors7 = __toESM(require_picocolors(), 1);
7776
7779
 
7777
7780
  // src/lib/confirm.ts
7778
7781
  var import_picocolors3 = __toESM(require_picocolors(), 1);
@@ -11527,6 +11530,122 @@ async function printFindingsSummary(client, profileId, tty3) {
11527
11530
  }
11528
11531
  }
11529
11532
 
11533
+ // src/lib/scout.ts
11534
+ var import_picocolors6 = __toESM(require_picocolors(), 1);
11535
+ var UNICODE2 = isUnicodeSupported();
11536
+ var sym2 = {
11537
+ success: UNICODE2 ? "\u2713" : "v",
11538
+ fail: UNICODE2 ? "\u2717" : "x",
11539
+ warn: UNICODE2 ? "\u26A0" : "!"
11540
+ };
11541
+ function printOnPremInstructions(result) {
11542
+ const tty3 = isTTY();
11543
+ const header = tty3 ? import_picocolors6.default.bold("On-Prem Scout Setup") : "On-Prem Scout Setup";
11544
+ console.log(`
11545
+ ${header}
11546
+ `);
11547
+ console.log(` Session ID: ${result.session_id}`);
11548
+ console.log(` Profile ID: ${result.scan_profile_id}`);
11549
+ const curlCmd = `curl -fsSL https://api.kinosec.ai/functions/v1/scout-bootstrap/launch/${result.bootstrap_token} | bash`;
11550
+ if (tty3) {
11551
+ console.log(`
11552
+ Run on target host:
11553
+ `);
11554
+ console.log(` ${import_picocolors6.default.cyan(curlCmd)}
11555
+ `);
11556
+ console.log(` ${import_picocolors6.default.yellow(sym2.warn)} Token expires in ${import_picocolors6.default.bold("15 minutes")}.`);
11557
+ } else {
11558
+ console.log(`
11559
+ Run on target host:
11560
+ `);
11561
+ console.log(` ${curlCmd}
11562
+ `);
11563
+ console.log(` ${sym2.warn} Token expires in 15 minutes.`);
11564
+ }
11565
+ }
11566
+ async function waitForScoutConnection(client, profileId, options) {
11567
+ const { timeoutMs, intervalMs, useJson } = options;
11568
+ const tty3 = isTTY();
11569
+ const loopStart = Date.now();
11570
+ let spinner = null;
11571
+ if (!useJson && tty3) {
11572
+ spinner = ora({
11573
+ text: "Waiting for Scout connection...",
11574
+ color: "green",
11575
+ isEnabled: true
11576
+ }).start();
11577
+ }
11578
+ const cleanup = () => {
11579
+ if (spinner) spinner.stop();
11580
+ };
11581
+ const onSigint = () => {
11582
+ cleanup();
11583
+ console.log("\nWatch cancelled. Pentest is waiting for Scout connection.");
11584
+ process.exit(0);
11585
+ };
11586
+ process.once("SIGINT", onSigint);
11587
+ while (true) {
11588
+ const status = await client.scans.scoutStatus(profileId);
11589
+ if (useJson) {
11590
+ const elapsed = Math.round((Date.now() - loopStart) / 1e3);
11591
+ process.stdout.write(
11592
+ JSON.stringify({ ...status, elapsed_seconds: elapsed }) + "\n"
11593
+ );
11594
+ }
11595
+ switch (status.status) {
11596
+ case "pending":
11597
+ if (spinner) spinner.text = import_picocolors6.default.green("Waiting for Scout bootstrap...");
11598
+ break;
11599
+ case "bootstrapped":
11600
+ if (spinner) spinner.text = import_picocolors6.default.green("Scout bootstrapped, establishing connection...");
11601
+ break;
11602
+ case "connected": {
11603
+ cleanup();
11604
+ process.removeListener("SIGINT", onSigint);
11605
+ if (!useJson) {
11606
+ const icon = tty3 ? import_picocolors6.default.green(sym2.success) : sym2.success;
11607
+ console.log(`${icon} Scout connected`);
11608
+ if (status.hostname) {
11609
+ console.log(` Hostname: ${status.hostname}`);
11610
+ }
11611
+ if (status.tools.length > 0) {
11612
+ console.log(` Tools: ${status.tools.join(", ")}`);
11613
+ }
11614
+ }
11615
+ return;
11616
+ }
11617
+ case "expired": {
11618
+ cleanup();
11619
+ process.removeListener("SIGINT", onSigint);
11620
+ const icon = tty3 ? import_picocolors6.default.red(sym2.fail) : sym2.fail;
11621
+ console.error(`
11622
+ ${icon} Scout bootstrap token expired.`);
11623
+ console.error("Start a new pentest to get a fresh token.");
11624
+ process.exit(1);
11625
+ }
11626
+ case "disconnected": {
11627
+ cleanup();
11628
+ process.removeListener("SIGINT", onSigint);
11629
+ const icon = tty3 ? import_picocolors6.default.red(sym2.fail) : sym2.fail;
11630
+ console.error(`
11631
+ ${icon} Scout disconnected unexpectedly.`);
11632
+ console.error(`Check status: kinosec pentest get ${profileId}`);
11633
+ process.exit(1);
11634
+ }
11635
+ }
11636
+ if (Date.now() - loopStart > timeoutMs) {
11637
+ cleanup();
11638
+ process.removeListener("SIGINT", onSigint);
11639
+ const secs = Math.round(timeoutMs / 1e3);
11640
+ console.error(`
11641
+ Timeout: Scout did not connect within ${secs}s.`);
11642
+ console.error("The bootstrap token may have expired. Start a new pentest.");
11643
+ process.exit(3);
11644
+ }
11645
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
11646
+ }
11647
+ }
11648
+
11530
11649
  // src/commands/pentest.ts
11531
11650
  function stripProtocol(input) {
11532
11651
  const match = input.match(/^https?:\/\/(.+)/i);
@@ -11537,15 +11656,23 @@ function isValidDomain(value) {
11537
11656
  return value.includes(".") && !/\s/.test(value);
11538
11657
  }
11539
11658
  function registerPentestCommand(program3) {
11540
- const pentest = program3.command("pentest").description("Run and manage pentests").option("-t, --target <domain>", "Target domain to pentest").option("--profile <id>", "Pentest profile ID (alternative to --target)").option("--mode <mode>", "Pentest mode: quick or full", "full").option("-y, --yes", "Accept authorization terms without prompting").option("--no-watch", "Skip auto-watch after starting the pentest").addHelpText("after", `
11659
+ const pentest = program3.command("pentest").description("Run and manage pentests").option("-t, --target <domain>", "Target domain to pentest").option("--profile <id>", "Pentest profile ID (alternative to --target)").option("--mode <mode>", "Pentest mode: quick or full", "full").option("-y, --yes", "Accept authorization terms without prompting").option("--no-watch", "Skip auto-watch after starting the pentest").option("--deployment <type>", "Deployment type: cloud or on-prem", "cloud").addHelpText("after", `
11541
11660
  Examples:
11542
11661
  $ kinosec pentest -t delikado.org
11543
11662
  $ kinosec pentest --target delikado.org --mode quick
11544
- $ kinosec pentest --profile abc123`).action(async (options) => {
11663
+ $ kinosec pentest --profile abc123
11664
+ $ kinosec pentest -t internal.corp --deployment on-prem`).action(async (options) => {
11545
11665
  if (!options.target && !options.profile) {
11546
11666
  console.error("Provide --target <domain> or --profile <id>.");
11547
11667
  process.exit(2);
11548
11668
  }
11669
+ if (options.deployment) {
11670
+ options.deployment = options.deployment.replace(/-/g, "_");
11671
+ }
11672
+ if (options.deployment !== "cloud" && options.deployment !== "on_prem") {
11673
+ console.error("Invalid deployment type. Must be: cloud or on-prem.");
11674
+ process.exit(2);
11675
+ }
11549
11676
  try {
11550
11677
  const opts = program3.opts();
11551
11678
  const { token, apiUrl } = resolveToken(opts);
@@ -11582,10 +11709,18 @@ Examples:
11582
11709
  }
11583
11710
  }
11584
11711
  targetDisplay = domain;
11585
- params = { asset_id: asset.id, scan_mode: options.mode };
11712
+ const assetParams = {
11713
+ asset_id: asset.id,
11714
+ scan_mode: options.mode
11715
+ };
11716
+ if (options.deployment !== "cloud") {
11717
+ assetParams.deployment_type = options.deployment;
11718
+ }
11719
+ params = assetParams;
11586
11720
  }
11587
11721
  await confirmAuthorization(targetDisplay, Boolean(options.yes));
11588
11722
  const result = await client.scans.create(params);
11723
+ const isOnPrem = result.deployment_type === "on_prem";
11589
11724
  if (opts.json || opts.output === "json") {
11590
11725
  printJson(result);
11591
11726
  return;
@@ -11593,13 +11728,37 @@ Examples:
11593
11728
  printSuccess(`Pentest started: ${result.domain_or_ip}`);
11594
11729
  console.log(` Profile ID: ${result.scan_profile_id}`);
11595
11730
  console.log(` Mode: ${result.scan_mode}`);
11731
+ if (isOnPrem) {
11732
+ printOnPremInstructions(result);
11733
+ if (options.watch && isTTY()) {
11734
+ const divider2 = import_picocolors7.default.dim("\u2500".repeat(37));
11735
+ console.log(`
11736
+ ${divider2}
11737
+ `);
11738
+ await waitForScoutConnection(client, result.scan_profile_id, {
11739
+ timeoutMs: 15 * 60 * 1e3,
11740
+ intervalMs: 5 * 1e3,
11741
+ useJson: false
11742
+ });
11743
+ await watchPentest(client, result.scan_profile_id, {
11744
+ timeoutMs: 1800 * 1e3,
11745
+ intervalMs: 5 * 1e3,
11746
+ useJson: false,
11747
+ showFindingsSummary: true
11748
+ });
11749
+ } else {
11750
+ console.log(`
11751
+ Watch connection: kinosec pentest watch ${result.scan_profile_id}`);
11752
+ }
11753
+ return;
11754
+ }
11596
11755
  if (!options.watch || !isTTY()) {
11597
11756
  console.log(` Status: ${result.scan_status}`);
11598
11757
  console.log(`
11599
11758
  Watch progress: kinosec pentest watch ${result.scan_profile_id}`);
11600
11759
  return;
11601
11760
  }
11602
- const divider = isTTY() ? import_picocolors6.default.dim("\u2500".repeat(37)) : "-".repeat(37);
11761
+ const divider = isTTY() ? import_picocolors7.default.dim("\u2500".repeat(37)) : "-".repeat(37);
11603
11762
  console.log(`
11604
11763
  ${divider}
11605
11764
  `);
@@ -11645,7 +11804,7 @@ Examples:
11645
11804
  ])
11646
11805
  );
11647
11806
  if (result.next_cursor) {
11648
- console.log(import_picocolors6.default.dim(`
11807
+ console.log(import_picocolors7.default.dim(`
11649
11808
  More results available. Use --cursor ${result.next_cursor}`));
11650
11809
  }
11651
11810
  } catch (error2) {
@@ -11672,6 +11831,7 @@ Examples:
11672
11831
  ["Target", detail.domain_or_ip],
11673
11832
  ["Mode", detail.scan_mode],
11674
11833
  ["Type", detail.scan_type],
11834
+ ...detail.deployment_type ? [["Deployment", detail.deployment_type === "on_prem" ? "On-Prem" : "Cloud"]] : [],
11675
11835
  ["Status", detail.scan_status],
11676
11836
  ["Activity", detail.current_activity || "(none)"],
11677
11837
  ["Created", detail.created_at],
@@ -11708,10 +11868,25 @@ Examples:
11708
11868
  const opts = program3.opts();
11709
11869
  const { token, apiUrl } = resolveToken(opts);
11710
11870
  const client = new KinoSecClient({ apiUrl, token });
11871
+ const useJson = Boolean(opts.json || opts.output === "json");
11872
+ const detail = await client.scans.get(id);
11873
+ if (detail.deployment_type === "on_prem") {
11874
+ try {
11875
+ const scout = await client.scans.scoutStatus(id);
11876
+ if (scout.status !== "connected") {
11877
+ await waitForScoutConnection(client, id, {
11878
+ timeoutMs: 15 * 60 * 1e3,
11879
+ intervalMs: parseInt(options.interval, 10) * 1e3,
11880
+ useJson
11881
+ });
11882
+ }
11883
+ } catch {
11884
+ }
11885
+ }
11711
11886
  await watchPentest(client, id, {
11712
11887
  timeoutMs: parseInt(options.timeout, 10) * 1e3,
11713
11888
  intervalMs: parseInt(options.interval, 10) * 1e3,
11714
- useJson: Boolean(opts.json || opts.output === "json"),
11889
+ useJson,
11715
11890
  showFindingsSummary: true
11716
11891
  });
11717
11892
  } catch (error2) {
@@ -11721,10 +11896,10 @@ Examples:
11721
11896
  }
11722
11897
 
11723
11898
  // src/commands/asset.ts
11724
- var import_picocolors8 = __toESM(require_picocolors(), 1);
11899
+ var import_picocolors9 = __toESM(require_picocolors(), 1);
11725
11900
 
11726
11901
  // src/commands/asset-auth.ts
11727
- var import_picocolors7 = __toESM(require_picocolors(), 1);
11902
+ var import_picocolors8 = __toESM(require_picocolors(), 1);
11728
11903
 
11729
11904
  // src/lib/resolve-asset.ts
11730
11905
  var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-/i;
@@ -11981,7 +12156,7 @@ Examples:
11981
12156
  }
11982
12157
  if (!creds.has_credentials) {
11983
12158
  console.log(`Asset: ${asset.asset_value}`);
11984
- console.log(`Auth Type: ${import_picocolors7.default.dim("(none)")}`);
12159
+ console.log(`Auth Type: ${import_picocolors8.default.dim("(none)")}`);
11985
12160
  console.log(`Has Credentials: no`);
11986
12161
  return;
11987
12162
  }
@@ -12002,9 +12177,9 @@ Examples:
12002
12177
  const hasAny = ta.has_username || ta.has_cookie || ta.has_bearer_token || ta.has_apikey;
12003
12178
  if (hasAny) {
12004
12179
  const detail = ta.has_username ? "username" : ta.has_bearer_token ? "bearer token" : ta.has_cookie ? "cookie" : "API key";
12005
- console.log(`Target Account: ${import_picocolors7.default.green("configured")} (${detail})`);
12180
+ console.log(`Target Account: ${import_picocolors8.default.green("configured")} (${detail})`);
12006
12181
  } else {
12007
- console.log(`Target Account: ${import_picocolors7.default.dim("not configured")}`);
12182
+ console.log(`Target Account: ${import_picocolors8.default.dim("not configured")}`);
12008
12183
  }
12009
12184
  }
12010
12185
  } catch (error2) {
@@ -12155,7 +12330,7 @@ Examples:
12155
12330
  ])
12156
12331
  );
12157
12332
  if (result.next_cursor) {
12158
- console.log(import_picocolors8.default.dim(`
12333
+ console.log(import_picocolors9.default.dim(`
12159
12334
  More results available. Use --cursor ${result.next_cursor}`));
12160
12335
  }
12161
12336
  } catch (error2) {
@@ -12267,7 +12442,7 @@ Examples:
12267
12442
  }
12268
12443
 
12269
12444
  // src/commands/finding.ts
12270
- var import_picocolors9 = __toESM(require_picocolors(), 1);
12445
+ var import_picocolors10 = __toESM(require_picocolors(), 1);
12271
12446
  function formatPoc(poc) {
12272
12447
  if (poc == null) return null;
12273
12448
  if (typeof poc === "string") return poc.trim() || null;
@@ -12308,7 +12483,7 @@ Examples:
12308
12483
  ])
12309
12484
  );
12310
12485
  if (result.next_cursor) {
12311
- console.log(import_picocolors9.default.dim(`
12486
+ console.log(import_picocolors10.default.dim(`
12312
12487
  More results available. Use --cursor ${result.next_cursor}`));
12313
12488
  }
12314
12489
  } catch (error2) {
@@ -12396,7 +12571,7 @@ Examples:
12396
12571
  }
12397
12572
 
12398
12573
  // src/commands/report.ts
12399
- var import_picocolors10 = __toESM(require_picocolors(), 1);
12574
+ var import_picocolors11 = __toESM(require_picocolors(), 1);
12400
12575
  function renderMarkdown(data) {
12401
12576
  const { scan, findings } = data;
12402
12577
  const lines = [];
@@ -12473,7 +12648,7 @@ Examples:
12473
12648
  ])
12474
12649
  );
12475
12650
  if (result.next_cursor) {
12476
- console.log(import_picocolors10.default.dim(`
12651
+ console.log(import_picocolors11.default.dim(`
12477
12652
  More results available. Use --cursor ${result.next_cursor}`));
12478
12653
  }
12479
12654
  } catch (error2) {
@@ -12544,7 +12719,7 @@ Examples:
12544
12719
  }
12545
12720
 
12546
12721
  // src/commands/token.ts
12547
- var import_picocolors11 = __toESM(require_picocolors(), 1);
12722
+ var import_picocolors12 = __toESM(require_picocolors(), 1);
12548
12723
  function registerTokenCommand(program3) {
12549
12724
  const token = program3.command("token").description("Manage API tokens");
12550
12725
  token.command("list").description("List API tokens").option("--limit <n>", "Max results per page", "20").option("--cursor <cursor>", "Pagination cursor").addHelpText("after", `
@@ -12579,7 +12754,7 @@ Examples:
12579
12754
  ])
12580
12755
  );
12581
12756
  if (result.next_cursor) {
12582
- console.log(import_picocolors11.default.dim(`
12757
+ console.log(import_picocolors12.default.dim(`
12583
12758
  More results available. Use --cursor ${result.next_cursor}`));
12584
12759
  }
12585
12760
  } catch (error2) {
@@ -12606,7 +12781,7 @@ Examples:
12606
12781
  }
12607
12782
  printSuccess(`Token created: ${result.name}`);
12608
12783
  console.log("");
12609
- console.log(import_picocolors11.default.bold(" Token: ") + result.token);
12784
+ console.log(import_picocolors12.default.bold(" Token: ") + result.token);
12610
12785
  console.log("");
12611
12786
  printWarning("Save this token \u2014 it will not be shown again.");
12612
12787
  console.log(` ID: ${result.id}`);
@@ -12639,7 +12814,7 @@ Examples:
12639
12814
 
12640
12815
  // src/cli.ts
12641
12816
  var program2 = new Command();
12642
- program2.name("kinosec").description("KinoSec CLI \u2014 run pentests from your terminal").version("0.8.0", "-v, --version").option("--token <pat>", "API token (overrides KINOSEC_TOKEN)").option("--output <format>", "Output format: text or json", "text").option("--json", "Shorthand for --output json").option("--no-color", "Disable colors");
12817
+ program2.name("kinosec").description("KinoSec CLI \u2014 run pentests from your terminal").version("0.9.0", "-v, --version").option("--token <pat>", "API token (overrides KINOSEC_TOKEN)").option("--output <format>", "Output format: text or json", "text").option("--json", "Shorthand for --output json").option("--no-color", "Disable colors");
12643
12818
  registerAuthCommand(program2);
12644
12819
  registerMeCommand(program2);
12645
12820
  registerCreditsCommand(program2);
@@ -12648,7 +12823,7 @@ registerAssetCommand(program2);
12648
12823
  registerFindingCommand(program2);
12649
12824
  registerReportCommand(program2);
12650
12825
  registerTokenCommand(program2);
12651
- program2.parseAsync(process.argv).then(() => checkForUpdate("0.8.0")).catch(handleError);
12826
+ program2.parseAsync(process.argv).then(() => checkForUpdate("0.9.0")).catch(handleError);
12652
12827
  /*! Bundled license information:
12653
12828
 
12654
12829
  ky/distribution/index.js: