@kinosec/cli 0.9.0 → 0.10.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
@@ -7775,7 +7775,7 @@ Examples:
7775
7775
  }
7776
7776
 
7777
7777
  // src/commands/pentest.ts
7778
- var import_picocolors7 = __toESM(require_picocolors(), 1);
7778
+ var import_picocolors8 = __toESM(require_picocolors(), 1);
7779
7779
 
7780
7780
  // src/lib/confirm.ts
7781
7781
  var import_picocolors3 = __toESM(require_picocolors(), 1);
@@ -7835,8 +7835,21 @@ ${import_picocolors3.default.dim("use of its services.")}
7835
7835
 
7836
7836
  // src/commands/schedule.ts
7837
7837
  var import_picocolors4 = __toESM(require_picocolors(), 1);
7838
+
7839
+ // src/lib/validate-mode.ts
7840
+ function validateScanMode(mode) {
7841
+ if (mode === "quick") {
7842
+ console.error("Quick mode is temporarily unavailable. Please use --mode full (the default).");
7843
+ process.exit(2);
7844
+ }
7845
+ if (mode !== "full") {
7846
+ console.error(`Invalid mode "${mode}". Must be: full`);
7847
+ process.exit(2);
7848
+ }
7849
+ }
7850
+
7851
+ // src/commands/schedule.ts
7838
7852
  var VALID_PATTERNS = ["once", "daily", "weekly", "biweekly", "monthly"];
7839
- var VALID_MODES = ["quick", "full"];
7840
7853
  var DAY_NAMES = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
7841
7854
  function extractTime(scheduledAt) {
7842
7855
  if (!scheduledAt) return "\u2014";
@@ -7934,19 +7947,16 @@ Examples:
7934
7947
  handleError(error2);
7935
7948
  }
7936
7949
  });
7937
- schedule.command("create").description("Create a scheduled pentest").requiredOption("--asset <id>", "Asset ID to scan").requiredOption("--pattern <pattern>", "Recurrence: once, daily, weekly, biweekly, monthly").option("--name <name>", "Schedule name").option("--mode <mode>", "Scan mode: quick or full", "full").option("--time <HH:MM>", "Time of day (24h format)", "09:00").option("--day-of-week <0-6>", "Day of week (0=Sun..6=Sat) for weekly/biweekly").option("--day-of-month <1-31>", "Day of month for monthly").option("--timezone <tz>", "Timezone (e.g. America/New_York)", Intl.DateTimeFormat().resolvedOptions().timeZone).addHelpText("after", `
7950
+ schedule.command("create").description("Create a scheduled pentest").requiredOption("--asset <id>", "Asset ID to scan").requiredOption("--pattern <pattern>", "Recurrence: once, daily, weekly, biweekly, monthly").option("--name <name>", "Schedule name").option("--mode <mode>", "Scan mode (default: full)", "full").option("--time <HH:MM>", "Time of day (24h format)", "09:00").option("--day-of-week <0-6>", "Day of week (0=Sun..6=Sat) for weekly/biweekly").option("--day-of-month <1-31>", "Day of month for monthly").option("--timezone <tz>", "Timezone (e.g. America/New_York)", Intl.DateTimeFormat().resolvedOptions().timeZone).addHelpText("after", `
7938
7951
  Examples:
7939
7952
  $ kinosec pentest schedule create --asset abc123 --pattern weekly --day-of-week 1 --time 09:00
7940
- $ kinosec pentest schedule create --asset abc123 --pattern daily --mode quick --time 14:00
7953
+ $ kinosec pentest schedule create --asset abc123 --pattern daily --time 14:00
7941
7954
  $ kinosec pentest schedule create --asset abc123 --pattern monthly --day-of-month 15 --time 03:00 --timezone UTC`).action(async (options) => {
7942
7955
  if (!VALID_PATTERNS.includes(options.pattern)) {
7943
7956
  console.error(`Invalid pattern "${options.pattern}". Must be: ${VALID_PATTERNS.join(", ")}`);
7944
7957
  process.exit(2);
7945
7958
  }
7946
- if (!VALID_MODES.includes(options.mode)) {
7947
- console.error(`Invalid mode "${options.mode}". Must be: ${VALID_MODES.join(", ")}`);
7948
- process.exit(2);
7949
- }
7959
+ validateScanMode(options.mode);
7950
7960
  if ((options.pattern === "weekly" || options.pattern === "biweekly") && options.dayOfWeek === void 0) {
7951
7961
  console.error(`--day-of-week is required for ${options.pattern} schedules.`);
7952
7962
  process.exit(2);
@@ -7984,14 +7994,17 @@ Manage: kinosec pentest schedule get ${s.id}`);
7984
7994
  handleError(error2);
7985
7995
  }
7986
7996
  });
7987
- schedule.command("update").description("Update a schedule").argument("<id>", "Schedule ID").option("--pattern <pattern>", "Recurrence: once, daily, weekly, biweekly, monthly").option("--name <name>", "Schedule name").option("--mode <mode>", "Scan mode: quick or full").option("--time <HH:MM>", "Time of day (24h format)").option("--day-of-week <0-6>", "Day of week (0=Sun..6=Sat)").option("--day-of-month <1-31>", "Day of month").option("--timezone <tz>", "Timezone").addHelpText("after", `
7997
+ schedule.command("update").description("Update a schedule").argument("<id>", "Schedule ID").option("--pattern <pattern>", "Recurrence: once, daily, weekly, biweekly, monthly").option("--name <name>", "Schedule name").option("--mode <mode>", "Scan mode").option("--time <HH:MM>", "Time of day (24h format)").option("--day-of-week <0-6>", "Day of week (0=Sun..6=Sat)").option("--day-of-month <1-31>", "Day of month").option("--timezone <tz>", "Timezone").addHelpText("after", `
7988
7998
  Examples:
7989
7999
  $ kinosec pentest schedule update abc123 --pattern daily --time 14:00
7990
- $ kinosec pentest schedule update abc123 --mode quick`).action(async (id, options) => {
8000
+ $ kinosec pentest schedule update abc123 --time 14:00`).action(async (id, options) => {
7991
8001
  const params = {};
7992
8002
  if (options.pattern) params.recurrence_pattern = options.pattern;
7993
8003
  if (options.name) params.name = options.name;
7994
- if (options.mode) params.scan_mode = options.mode;
8004
+ if (options.mode) {
8005
+ validateScanMode(options.mode);
8006
+ params.scan_mode = options.mode;
8007
+ }
7995
8008
  if (options.time) params.time_of_day = options.time;
7996
8009
  if (options.dayOfWeek !== void 0) params.day_of_week = parseInt(options.dayOfWeek, 10);
7997
8010
  if (options.dayOfMonth !== void 0) params.day_of_month = parseInt(options.dayOfMonth, 10);
@@ -11646,6 +11659,204 @@ Timeout: Scout did not connect within ${secs}s.`);
11646
11659
  }
11647
11660
  }
11648
11661
 
11662
+ // src/lib/scope.ts
11663
+ var import_picocolors7 = __toESM(require_picocolors(), 1);
11664
+
11665
+ // src/lib/prompt.ts
11666
+ import { createInterface as createInterface2 } from "readline";
11667
+ async function promptMasked(label) {
11668
+ if (!isTTY() || !process.stdin.isTTY) {
11669
+ console.error(`Cannot prompt for ${label} in non-interactive mode. Use flags instead.`);
11670
+ process.exit(2);
11671
+ }
11672
+ process.stderr.write(`${label}: `);
11673
+ return new Promise((resolve, reject) => {
11674
+ const chars = [];
11675
+ process.stdin.setRawMode(true);
11676
+ process.stdin.resume();
11677
+ process.stdin.setEncoding("utf-8");
11678
+ const onData = (chunk) => {
11679
+ for (const ch of chunk) {
11680
+ if (ch === "\r" || ch === "\n") {
11681
+ process.stdin.setRawMode(false);
11682
+ process.stdin.pause();
11683
+ process.stdin.removeListener("data", onData);
11684
+ process.stderr.write("\n");
11685
+ resolve(chars.join(""));
11686
+ return;
11687
+ } else if (ch === "") {
11688
+ process.stdin.setRawMode(false);
11689
+ process.stdin.pause();
11690
+ process.stdin.removeListener("data", onData);
11691
+ process.stderr.write("\n");
11692
+ reject(new Error("Cancelled"));
11693
+ return;
11694
+ } else if (ch === "\x7F" || ch === "\b") {
11695
+ if (chars.length > 0) {
11696
+ chars.pop();
11697
+ process.stderr.write("\b \b");
11698
+ }
11699
+ } else if (ch >= " ") {
11700
+ chars.push(ch);
11701
+ process.stderr.write("*");
11702
+ }
11703
+ }
11704
+ };
11705
+ process.stdin.on("data", onData);
11706
+ });
11707
+ }
11708
+ async function promptText(label) {
11709
+ if (!isTTY() || !process.stdin.isTTY) {
11710
+ console.error(`Cannot prompt for ${label} in non-interactive mode. Use flags instead.`);
11711
+ process.exit(2);
11712
+ }
11713
+ const rl = createInterface2({ input: process.stdin, output: process.stderr });
11714
+ const answer = await new Promise((resolve) => {
11715
+ rl.question(`${label}: `, resolve);
11716
+ });
11717
+ rl.close();
11718
+ return answer;
11719
+ }
11720
+ async function promptSelect(message, options) {
11721
+ if (!isTTY() || !process.stdin.isTTY) {
11722
+ console.error("Cannot show interactive menu in non-interactive mode. Use flags instead.");
11723
+ process.exit(2);
11724
+ }
11725
+ console.error(`
11726
+ ${message}`);
11727
+ for (let i = 0; i < options.length; i++) {
11728
+ console.error(` ${i + 1}. ${options[i].label} \u2014 ${options[i].description}`);
11729
+ }
11730
+ const rl = createInterface2({ input: process.stdin, output: process.stderr });
11731
+ const answer = await new Promise((resolve) => {
11732
+ rl.question("> ", resolve);
11733
+ });
11734
+ rl.close();
11735
+ const index = parseInt(answer, 10) - 1;
11736
+ if (isNaN(index) || index < 0 || index >= options.length) {
11737
+ console.error("Invalid selection.");
11738
+ process.exit(2);
11739
+ }
11740
+ return options[index].value;
11741
+ }
11742
+
11743
+ // src/lib/scope.ts
11744
+ var HOSTNAME_RE = /^([a-z0-9]([a-z0-9-]*[a-z0-9])?\.)+[a-z]{2,}$/i;
11745
+ var MAX_SCOPE_HOSTS = 50;
11746
+ function parseScopeHosts(input) {
11747
+ return input.split(",").map((h) => h.trim()).filter(Boolean);
11748
+ }
11749
+ function validateScopeHosts(hosts) {
11750
+ for (const host of hosts) {
11751
+ if (!HOSTNAME_RE.test(host)) {
11752
+ console.error(`Error: Invalid hostname "${host}". Must be a valid domain (e.g., api.example.com).`);
11753
+ process.exit(2);
11754
+ }
11755
+ }
11756
+ const seen = /* @__PURE__ */ new Set();
11757
+ for (const host of hosts) {
11758
+ const lower = host.toLowerCase();
11759
+ if (seen.has(lower)) {
11760
+ console.error(`Error: Duplicate hostname "${host}" in list.`);
11761
+ process.exit(2);
11762
+ }
11763
+ seen.add(lower);
11764
+ }
11765
+ }
11766
+ function validateScopeFlags(options) {
11767
+ if (options.include && options.exclude) {
11768
+ console.error("Error: --include and --exclude cannot be used together. Use one or the other.");
11769
+ process.exit(2);
11770
+ }
11771
+ if (options.scopeClear && (options.include || options.exclude)) {
11772
+ console.error("Error: --clear cannot be combined with --include or --exclude.");
11773
+ process.exit(2);
11774
+ }
11775
+ }
11776
+ function validateTotalHostCount(existingCount, newCount) {
11777
+ const total = existingCount + newCount;
11778
+ if (total > MAX_SCOPE_HOSTS) {
11779
+ console.error(`Error: Maximum ${MAX_SCOPE_HOSTS} hosts allowed. You provided ${total}.`);
11780
+ process.exit(2);
11781
+ }
11782
+ }
11783
+ function displayScope(asset) {
11784
+ if (!asset.scope_filter || !asset.scope_hosts?.length) {
11785
+ console.error(`Scope: ${isTTY() ? import_picocolors7.default.dim("all subdomains") : "all subdomains"}`);
11786
+ return;
11787
+ }
11788
+ const label = asset.scope_filter === "include" ? "include only" : "exclude";
11789
+ const hosts = asset.scope_hosts.join(", ");
11790
+ console.error(`Scope: ${label} \u2014 ${hosts}`);
11791
+ }
11792
+ async function resolveScopeUpdate(asset, mode, newHosts) {
11793
+ const existingMode = asset.scope_filter;
11794
+ const existingHosts = asset.scope_hosts ?? [];
11795
+ if (!existingMode || existingHosts.length === 0) {
11796
+ validateScopeHosts(newHosts);
11797
+ if (newHosts.length > MAX_SCOPE_HOSTS) {
11798
+ console.error(`Error: Maximum ${MAX_SCOPE_HOSTS} hosts allowed. You provided ${newHosts.length}.`);
11799
+ process.exit(2);
11800
+ }
11801
+ return { scope_filter: mode, scope_hosts: newHosts };
11802
+ }
11803
+ if (existingMode !== mode) {
11804
+ console.error(`
11805
+ Existing ${existingMode} scope:`);
11806
+ for (const host of existingHosts) {
11807
+ console.error(` - ${host}`);
11808
+ }
11809
+ const answer2 = await promptSelect(
11810
+ `You have an existing ${existingMode} scope. Using --${mode} will replace it. Continue?`,
11811
+ [
11812
+ { value: "switch", label: `Yes, switch to ${mode}`, description: `Replace ${existingMode} list with new ${mode} list` },
11813
+ { value: "cancel", label: "Cancel", description: "Keep existing scope" }
11814
+ ]
11815
+ );
11816
+ if (answer2 === "cancel") {
11817
+ console.error("Cancelled.");
11818
+ return null;
11819
+ }
11820
+ validateScopeHosts(newHosts);
11821
+ if (newHosts.length > MAX_SCOPE_HOSTS) {
11822
+ console.error(`Error: Maximum ${MAX_SCOPE_HOSTS} hosts allowed. You provided ${newHosts.length}.`);
11823
+ process.exit(2);
11824
+ }
11825
+ return { scope_filter: mode, scope_hosts: newHosts };
11826
+ }
11827
+ console.error(`
11828
+ Existing ${existingMode} scope:`);
11829
+ for (const host of existingHosts) {
11830
+ console.error(` - ${host}`);
11831
+ }
11832
+ const answer = await promptSelect(
11833
+ `You have existing subdomains in your ${mode} list. What would you like to do?`,
11834
+ [
11835
+ { value: "add", label: "Add to existing list", description: "Append new hosts to the current list" },
11836
+ { value: "replace", label: "Replace with new list", description: "Remove existing hosts and use only the new ones" }
11837
+ ]
11838
+ );
11839
+ if (answer === "add") {
11840
+ validateScopeHosts(newHosts);
11841
+ const existingLower = new Set(existingHosts.map((h) => h.toLowerCase()));
11842
+ const deduped = newHosts.filter((h) => !existingLower.has(h.toLowerCase()));
11843
+ if (deduped.length === 0) {
11844
+ console.error("All provided hosts are already in the list.");
11845
+ return { scope_filter: mode, scope_hosts: existingHosts };
11846
+ }
11847
+ validateScopeHosts(deduped);
11848
+ validateTotalHostCount(existingHosts.length, deduped.length);
11849
+ const merged = [...existingHosts, ...deduped];
11850
+ return { scope_filter: mode, scope_hosts: merged };
11851
+ }
11852
+ validateScopeHosts(newHosts);
11853
+ if (newHosts.length > MAX_SCOPE_HOSTS) {
11854
+ console.error(`Error: Maximum ${MAX_SCOPE_HOSTS} hosts allowed. You provided ${newHosts.length}.`);
11855
+ process.exit(2);
11856
+ }
11857
+ return { scope_filter: mode, scope_hosts: newHosts };
11858
+ }
11859
+
11649
11860
  // src/commands/pentest.ts
11650
11861
  function stripProtocol(input) {
11651
11862
  const match = input.match(/^https?:\/\/(.+)/i);
@@ -11656,12 +11867,14 @@ function isValidDomain(value) {
11656
11867
  return value.includes(".") && !/\s/.test(value);
11657
11868
  }
11658
11869
  function registerPentestCommand(program3) {
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", `
11870
+ 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", "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").option("--include <hosts>", "Include only these subdomains (comma-separated)").option("--exclude <hosts>", "Exclude these subdomains (comma-separated)").option("--scope <action>", "Scope action: clear").addHelpText("after", `
11660
11871
  Examples:
11661
11872
  $ kinosec pentest -t delikado.org
11662
- $ kinosec pentest --target delikado.org --mode quick
11663
11873
  $ kinosec pentest --profile abc123
11664
- $ kinosec pentest -t internal.corp --deployment on-prem`).action(async (options) => {
11874
+ $ kinosec pentest -t internal.corp --deployment on-prem
11875
+ $ kinosec pentest -t example.com --include api.example.com,app.example.com
11876
+ $ kinosec pentest -t example.com --exclude staging.example.com
11877
+ $ kinosec pentest -t example.com --scope clear`).action(async (options) => {
11665
11878
  if (!options.target && !options.profile) {
11666
11879
  console.error("Provide --target <domain> or --profile <id>.");
11667
11880
  process.exit(2);
@@ -11673,6 +11886,21 @@ Examples:
11673
11886
  console.error("Invalid deployment type. Must be: cloud or on-prem.");
11674
11887
  process.exit(2);
11675
11888
  }
11889
+ validateScanMode(options.mode);
11890
+ const scopeClear = options.scope === "clear";
11891
+ if (options.scope && options.scope !== "clear") {
11892
+ console.error("Invalid --scope value. Must be: clear");
11893
+ process.exit(2);
11894
+ }
11895
+ validateScopeFlags({
11896
+ include: options.include,
11897
+ exclude: options.exclude,
11898
+ scopeClear
11899
+ });
11900
+ if (options.profile && (options.include || options.exclude || scopeClear)) {
11901
+ console.error("Error: Scope flags are only supported with --target, not --profile.");
11902
+ process.exit(2);
11903
+ }
11676
11904
  try {
11677
11905
  const opts = program3.opts();
11678
11906
  const { token, apiUrl } = resolveToken(opts);
@@ -11717,6 +11945,36 @@ Examples:
11717
11945
  assetParams.deployment_type = options.deployment;
11718
11946
  }
11719
11947
  params = assetParams;
11948
+ if (scopeClear) {
11949
+ if (asset.asset_type !== "domain") {
11950
+ console.error("Error: Scope filtering is only available for domain assets.");
11951
+ process.exit(2);
11952
+ }
11953
+ await client.assets.update(asset.id, {
11954
+ scope_filter: null,
11955
+ scope_hosts: null
11956
+ });
11957
+ console.error("Scope settings cleared. Scans will now cover all subdomains by default.");
11958
+ asset = await client.assets.get(asset.id);
11959
+ } else if (options.include || options.exclude) {
11960
+ if (asset.asset_type !== "domain") {
11961
+ console.error("Error: Scope filtering is only available for domain assets.");
11962
+ process.exit(2);
11963
+ }
11964
+ const mode = options.include ? "include" : "exclude";
11965
+ const rawHosts = parseScopeHosts(options.include ?? options.exclude);
11966
+ validateScopeHosts(rawHosts);
11967
+ const scopeResult = await resolveScopeUpdate(asset, mode, rawHosts);
11968
+ if (!scopeResult) {
11969
+ process.exit(0);
11970
+ }
11971
+ await client.assets.update(asset.id, {
11972
+ scope_filter: scopeResult.scope_filter,
11973
+ scope_hosts: scopeResult.scope_hosts
11974
+ });
11975
+ asset = await client.assets.get(asset.id);
11976
+ }
11977
+ displayScope(asset);
11720
11978
  }
11721
11979
  await confirmAuthorization(targetDisplay, Boolean(options.yes));
11722
11980
  const result = await client.scans.create(params);
@@ -11731,7 +11989,7 @@ Examples:
11731
11989
  if (isOnPrem) {
11732
11990
  printOnPremInstructions(result);
11733
11991
  if (options.watch && isTTY()) {
11734
- const divider2 = import_picocolors7.default.dim("\u2500".repeat(37));
11992
+ const divider2 = import_picocolors8.default.dim("\u2500".repeat(37));
11735
11993
  console.log(`
11736
11994
  ${divider2}
11737
11995
  `);
@@ -11758,7 +12016,7 @@ Watch connection: kinosec pentest watch ${result.scan_profile_id}`);
11758
12016
  Watch progress: kinosec pentest watch ${result.scan_profile_id}`);
11759
12017
  return;
11760
12018
  }
11761
- const divider = isTTY() ? import_picocolors7.default.dim("\u2500".repeat(37)) : "-".repeat(37);
12019
+ const divider = isTTY() ? import_picocolors8.default.dim("\u2500".repeat(37)) : "-".repeat(37);
11762
12020
  console.log(`
11763
12021
  ${divider}
11764
12022
  `);
@@ -11804,7 +12062,7 @@ Examples:
11804
12062
  ])
11805
12063
  );
11806
12064
  if (result.next_cursor) {
11807
- console.log(import_picocolors7.default.dim(`
12065
+ console.log(import_picocolors8.default.dim(`
11808
12066
  More results available. Use --cursor ${result.next_cursor}`));
11809
12067
  }
11810
12068
  } catch (error2) {
@@ -11896,10 +12154,10 @@ Examples:
11896
12154
  }
11897
12155
 
11898
12156
  // src/commands/asset.ts
11899
- var import_picocolors9 = __toESM(require_picocolors(), 1);
12157
+ var import_picocolors10 = __toESM(require_picocolors(), 1);
11900
12158
 
11901
12159
  // src/commands/asset-auth.ts
11902
- var import_picocolors8 = __toESM(require_picocolors(), 1);
12160
+ var import_picocolors9 = __toESM(require_picocolors(), 1);
11903
12161
 
11904
12162
  // src/lib/resolve-asset.ts
11905
12163
  var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-/i;
@@ -11925,84 +12183,6 @@ or use pentest which auto-creates the asset:
11925
12183
  }
11926
12184
  }
11927
12185
 
11928
- // src/lib/prompt.ts
11929
- import { createInterface as createInterface2 } from "readline";
11930
- async function promptMasked(label) {
11931
- if (!isTTY() || !process.stdin.isTTY) {
11932
- console.error(`Cannot prompt for ${label} in non-interactive mode. Use flags instead.`);
11933
- process.exit(2);
11934
- }
11935
- process.stderr.write(`${label}: `);
11936
- return new Promise((resolve, reject) => {
11937
- const chars = [];
11938
- process.stdin.setRawMode(true);
11939
- process.stdin.resume();
11940
- process.stdin.setEncoding("utf-8");
11941
- const onData = (chunk) => {
11942
- for (const ch of chunk) {
11943
- if (ch === "\r" || ch === "\n") {
11944
- process.stdin.setRawMode(false);
11945
- process.stdin.pause();
11946
- process.stdin.removeListener("data", onData);
11947
- process.stderr.write("\n");
11948
- resolve(chars.join(""));
11949
- return;
11950
- } else if (ch === "") {
11951
- process.stdin.setRawMode(false);
11952
- process.stdin.pause();
11953
- process.stdin.removeListener("data", onData);
11954
- process.stderr.write("\n");
11955
- reject(new Error("Cancelled"));
11956
- return;
11957
- } else if (ch === "\x7F" || ch === "\b") {
11958
- if (chars.length > 0) {
11959
- chars.pop();
11960
- process.stderr.write("\b \b");
11961
- }
11962
- } else if (ch >= " ") {
11963
- chars.push(ch);
11964
- process.stderr.write("*");
11965
- }
11966
- }
11967
- };
11968
- process.stdin.on("data", onData);
11969
- });
11970
- }
11971
- async function promptText(label) {
11972
- if (!isTTY() || !process.stdin.isTTY) {
11973
- console.error(`Cannot prompt for ${label} in non-interactive mode. Use flags instead.`);
11974
- process.exit(2);
11975
- }
11976
- const rl = createInterface2({ input: process.stdin, output: process.stderr });
11977
- const answer = await new Promise((resolve) => {
11978
- rl.question(`${label}: `, resolve);
11979
- });
11980
- rl.close();
11981
- return answer;
11982
- }
11983
- async function promptSelect(message, options) {
11984
- if (!isTTY() || !process.stdin.isTTY) {
11985
- console.error("Cannot show interactive menu in non-interactive mode. Use flags instead.");
11986
- process.exit(2);
11987
- }
11988
- console.error(`
11989
- ${message}`);
11990
- for (let i = 0; i < options.length; i++) {
11991
- console.error(` ${i + 1}. ${options[i].label} \u2014 ${options[i].description}`);
11992
- }
11993
- const rl = createInterface2({ input: process.stdin, output: process.stderr });
11994
- const answer = await new Promise((resolve) => {
11995
- rl.question("> ", resolve);
11996
- });
11997
- rl.close();
11998
- const index = parseInt(answer, 10) - 1;
11999
- if (isNaN(index) || index < 0 || index >= options.length) {
12000
- console.error("Invalid selection.");
12001
- process.exit(2);
12002
- }
12003
- return options[index].value;
12004
- }
12005
-
12006
12186
  // src/commands/asset-auth.ts
12007
12187
  var VALID_AUTH_TYPES = ["bearer", "basic", "apikey", "session", "cookie"];
12008
12188
  var AUTH_TYPE_OPTIONS = [
@@ -12156,7 +12336,7 @@ Examples:
12156
12336
  }
12157
12337
  if (!creds.has_credentials) {
12158
12338
  console.log(`Asset: ${asset.asset_value}`);
12159
- console.log(`Auth Type: ${import_picocolors8.default.dim("(none)")}`);
12339
+ console.log(`Auth Type: ${import_picocolors9.default.dim("(none)")}`);
12160
12340
  console.log(`Has Credentials: no`);
12161
12341
  return;
12162
12342
  }
@@ -12177,9 +12357,9 @@ Examples:
12177
12357
  const hasAny = ta.has_username || ta.has_cookie || ta.has_bearer_token || ta.has_apikey;
12178
12358
  if (hasAny) {
12179
12359
  const detail = ta.has_username ? "username" : ta.has_bearer_token ? "bearer token" : ta.has_cookie ? "cookie" : "API key";
12180
- console.log(`Target Account: ${import_picocolors8.default.green("configured")} (${detail})`);
12360
+ console.log(`Target Account: ${import_picocolors9.default.green("configured")} (${detail})`);
12181
12361
  } else {
12182
- console.log(`Target Account: ${import_picocolors8.default.dim("not configured")}`);
12362
+ console.log(`Target Account: ${import_picocolors9.default.dim("not configured")}`);
12183
12363
  }
12184
12364
  }
12185
12365
  } catch (error2) {
@@ -12320,17 +12500,18 @@ Examples:
12320
12500
  return;
12321
12501
  }
12322
12502
  printTable(
12323
- ["ID", "Value", "Type", "Status", "Created"],
12503
+ ["ID", "Value", "Type", "Status", "Scope", "Created"],
12324
12504
  result.items.map((a) => [
12325
12505
  a.id,
12326
12506
  truncate(a.asset_value, 40),
12327
12507
  a.asset_type,
12328
12508
  a.verification_status,
12509
+ a.scope_filter ?? "all",
12329
12510
  new Date(a.created_at).toLocaleDateString()
12330
12511
  ])
12331
12512
  );
12332
12513
  if (result.next_cursor) {
12333
- console.log(import_picocolors9.default.dim(`
12514
+ console.log(import_picocolors10.default.dim(`
12334
12515
  More results available. Use --cursor ${result.next_cursor}`));
12335
12516
  }
12336
12517
  } catch (error2) {
@@ -12438,11 +12619,84 @@ Examples:
12438
12619
  handleError(error2);
12439
12620
  }
12440
12621
  });
12622
+ asset.command("scope").description("View or configure subdomain scope for an asset").argument("<id>", "Asset ID or domain").option("--include <hosts>", "Include only these subdomains (comma-separated)").option("--exclude <hosts>", "Exclude these subdomains (comma-separated)").option("--clear", "Clear scope settings").addHelpText("after", `
12623
+ Examples:
12624
+ $ kinosec asset scope abc123
12625
+ $ kinosec asset scope example.com --include api.example.com,app.example.com
12626
+ $ kinosec asset scope abc123 --exclude staging.example.com
12627
+ $ kinosec asset scope abc123 --clear`).action(async (id, options) => {
12628
+ try {
12629
+ validateScopeFlags({
12630
+ include: options.include,
12631
+ exclude: options.exclude,
12632
+ scopeClear: options.clear
12633
+ });
12634
+ const opts = program3.opts();
12635
+ const { token, apiUrl } = resolveToken(opts);
12636
+ const client = new KinoSecClient({ apiUrl, token });
12637
+ const asset2 = await resolveAsset(client, id);
12638
+ if (asset2.asset_type !== "domain") {
12639
+ console.error("Error: Scope filtering is only available for domain assets.");
12640
+ process.exit(2);
12641
+ }
12642
+ if (!options.include && !options.exclude && !options.clear) {
12643
+ if (opts.json || opts.output === "json") {
12644
+ printJson({
12645
+ scope_filter: asset2.scope_filter,
12646
+ scope_hosts: asset2.scope_hosts
12647
+ });
12648
+ return;
12649
+ }
12650
+ if (!asset2.scope_filter || !asset2.scope_hosts?.length) {
12651
+ console.log("Scope: all subdomains");
12652
+ } else {
12653
+ console.log(`Scope: ${asset2.scope_filter}`);
12654
+ console.log("Hosts:");
12655
+ for (const host of asset2.scope_hosts) {
12656
+ console.log(` - ${host}`);
12657
+ }
12658
+ }
12659
+ return;
12660
+ }
12661
+ if (options.clear) {
12662
+ await client.assets.update(asset2.id, {
12663
+ scope_filter: null,
12664
+ scope_hosts: null
12665
+ });
12666
+ if (opts.json || opts.output === "json") {
12667
+ printJson({ scope_filter: null, scope_hosts: null });
12668
+ return;
12669
+ }
12670
+ printSuccess("Scope settings cleared. Scans will now cover all subdomains by default.");
12671
+ return;
12672
+ }
12673
+ const mode = options.include ? "include" : "exclude";
12674
+ const rawHosts = parseScopeHosts(options.include ?? options.exclude);
12675
+ validateScopeHosts(rawHosts);
12676
+ const result = await resolveScopeUpdate(asset2, mode, rawHosts);
12677
+ if (!result) return;
12678
+ await client.assets.update(asset2.id, {
12679
+ scope_filter: result.scope_filter,
12680
+ scope_hosts: result.scope_hosts
12681
+ });
12682
+ if (opts.json || opts.output === "json") {
12683
+ printJson(result);
12684
+ return;
12685
+ }
12686
+ printSuccess(`Scope updated: ${result.scope_filter}`);
12687
+ console.log("Hosts:");
12688
+ for (const host of result.scope_hosts) {
12689
+ console.log(` - ${host}`);
12690
+ }
12691
+ } catch (error2) {
12692
+ handleError(error2);
12693
+ }
12694
+ });
12441
12695
  registerAssetAuthCommand(asset);
12442
12696
  }
12443
12697
 
12444
12698
  // src/commands/finding.ts
12445
- var import_picocolors10 = __toESM(require_picocolors(), 1);
12699
+ var import_picocolors11 = __toESM(require_picocolors(), 1);
12446
12700
  function formatPoc(poc) {
12447
12701
  if (poc == null) return null;
12448
12702
  if (typeof poc === "string") return poc.trim() || null;
@@ -12483,7 +12737,7 @@ Examples:
12483
12737
  ])
12484
12738
  );
12485
12739
  if (result.next_cursor) {
12486
- console.log(import_picocolors10.default.dim(`
12740
+ console.log(import_picocolors11.default.dim(`
12487
12741
  More results available. Use --cursor ${result.next_cursor}`));
12488
12742
  }
12489
12743
  } catch (error2) {
@@ -12571,7 +12825,7 @@ Examples:
12571
12825
  }
12572
12826
 
12573
12827
  // src/commands/report.ts
12574
- var import_picocolors11 = __toESM(require_picocolors(), 1);
12828
+ var import_picocolors12 = __toESM(require_picocolors(), 1);
12575
12829
  function renderMarkdown(data) {
12576
12830
  const { scan, findings } = data;
12577
12831
  const lines = [];
@@ -12648,7 +12902,7 @@ Examples:
12648
12902
  ])
12649
12903
  );
12650
12904
  if (result.next_cursor) {
12651
- console.log(import_picocolors11.default.dim(`
12905
+ console.log(import_picocolors12.default.dim(`
12652
12906
  More results available. Use --cursor ${result.next_cursor}`));
12653
12907
  }
12654
12908
  } catch (error2) {
@@ -12719,7 +12973,7 @@ Examples:
12719
12973
  }
12720
12974
 
12721
12975
  // src/commands/token.ts
12722
- var import_picocolors12 = __toESM(require_picocolors(), 1);
12976
+ var import_picocolors13 = __toESM(require_picocolors(), 1);
12723
12977
  function registerTokenCommand(program3) {
12724
12978
  const token = program3.command("token").description("Manage API tokens");
12725
12979
  token.command("list").description("List API tokens").option("--limit <n>", "Max results per page", "20").option("--cursor <cursor>", "Pagination cursor").addHelpText("after", `
@@ -12754,7 +13008,7 @@ Examples:
12754
13008
  ])
12755
13009
  );
12756
13010
  if (result.next_cursor) {
12757
- console.log(import_picocolors12.default.dim(`
13011
+ console.log(import_picocolors13.default.dim(`
12758
13012
  More results available. Use --cursor ${result.next_cursor}`));
12759
13013
  }
12760
13014
  } catch (error2) {
@@ -12781,7 +13035,7 @@ Examples:
12781
13035
  }
12782
13036
  printSuccess(`Token created: ${result.name}`);
12783
13037
  console.log("");
12784
- console.log(import_picocolors12.default.bold(" Token: ") + result.token);
13038
+ console.log(import_picocolors13.default.bold(" Token: ") + result.token);
12785
13039
  console.log("");
12786
13040
  printWarning("Save this token \u2014 it will not be shown again.");
12787
13041
  console.log(` ID: ${result.id}`);
@@ -12814,7 +13068,7 @@ Examples:
12814
13068
 
12815
13069
  // src/cli.ts
12816
13070
  var program2 = new Command();
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");
13071
+ program2.name("kinosec").description("KinoSec CLI \u2014 run pentests from your terminal").version("0.10.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");
12818
13072
  registerAuthCommand(program2);
12819
13073
  registerMeCommand(program2);
12820
13074
  registerCreditsCommand(program2);
@@ -12823,7 +13077,7 @@ registerAssetCommand(program2);
12823
13077
  registerFindingCommand(program2);
12824
13078
  registerReportCommand(program2);
12825
13079
  registerTokenCommand(program2);
12826
- program2.parseAsync(process.argv).then(() => checkForUpdate("0.9.0")).catch(handleError);
13080
+ program2.parseAsync(process.argv).then(() => checkForUpdate("0.10.0")).catch(handleError);
12827
13081
  /*! Bundled license information:
12828
13082
 
12829
13083
  ky/distribution/index.js: