@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 +365 -111
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -7775,7 +7775,7 @@ Examples:
|
|
|
7775
7775
|
}
|
|
7776
7776
|
|
|
7777
7777
|
// src/commands/pentest.ts
|
|
7778
|
-
var
|
|
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:
|
|
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 --
|
|
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
|
-
|
|
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
|
|
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 --
|
|
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)
|
|
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
|
|
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
|
|
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 =
|
|
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() ?
|
|
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(
|
|
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
|
|
12157
|
+
var import_picocolors10 = __toESM(require_picocolors(), 1);
|
|
11900
12158
|
|
|
11901
12159
|
// src/commands/asset-auth.ts
|
|
11902
|
-
var
|
|
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: ${
|
|
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: ${
|
|
12360
|
+
console.log(`Target Account: ${import_picocolors9.default.green("configured")} (${detail})`);
|
|
12181
12361
|
} else {
|
|
12182
|
-
console.log(`Target Account: ${
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
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.
|
|
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.
|
|
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:
|