@kinosec/cli 0.8.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 +541 -112
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
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
|
|
7778
|
+
var import_picocolors8 = __toESM(require_picocolors(), 1);
|
|
7776
7779
|
|
|
7777
7780
|
// src/lib/confirm.ts
|
|
7778
7781
|
var import_picocolors3 = __toESM(require_picocolors(), 1);
|
|
@@ -7832,8 +7835,21 @@ ${import_picocolors3.default.dim("use of its services.")}
|
|
|
7832
7835
|
|
|
7833
7836
|
// src/commands/schedule.ts
|
|
7834
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
|
|
7835
7852
|
var VALID_PATTERNS = ["once", "daily", "weekly", "biweekly", "monthly"];
|
|
7836
|
-
var VALID_MODES = ["quick", "full"];
|
|
7837
7853
|
var DAY_NAMES = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
|
|
7838
7854
|
function extractTime(scheduledAt) {
|
|
7839
7855
|
if (!scheduledAt) return "\u2014";
|
|
@@ -7931,19 +7947,16 @@ Examples:
|
|
|
7931
7947
|
handleError(error2);
|
|
7932
7948
|
}
|
|
7933
7949
|
});
|
|
7934
|
-
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", `
|
|
7935
7951
|
Examples:
|
|
7936
7952
|
$ kinosec pentest schedule create --asset abc123 --pattern weekly --day-of-week 1 --time 09:00
|
|
7937
|
-
$ kinosec pentest schedule create --asset abc123 --pattern daily --
|
|
7953
|
+
$ kinosec pentest schedule create --asset abc123 --pattern daily --time 14:00
|
|
7938
7954
|
$ kinosec pentest schedule create --asset abc123 --pattern monthly --day-of-month 15 --time 03:00 --timezone UTC`).action(async (options) => {
|
|
7939
7955
|
if (!VALID_PATTERNS.includes(options.pattern)) {
|
|
7940
7956
|
console.error(`Invalid pattern "${options.pattern}". Must be: ${VALID_PATTERNS.join(", ")}`);
|
|
7941
7957
|
process.exit(2);
|
|
7942
7958
|
}
|
|
7943
|
-
|
|
7944
|
-
console.error(`Invalid mode "${options.mode}". Must be: ${VALID_MODES.join(", ")}`);
|
|
7945
|
-
process.exit(2);
|
|
7946
|
-
}
|
|
7959
|
+
validateScanMode(options.mode);
|
|
7947
7960
|
if ((options.pattern === "weekly" || options.pattern === "biweekly") && options.dayOfWeek === void 0) {
|
|
7948
7961
|
console.error(`--day-of-week is required for ${options.pattern} schedules.`);
|
|
7949
7962
|
process.exit(2);
|
|
@@ -7981,14 +7994,17 @@ Manage: kinosec pentest schedule get ${s.id}`);
|
|
|
7981
7994
|
handleError(error2);
|
|
7982
7995
|
}
|
|
7983
7996
|
});
|
|
7984
|
-
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", `
|
|
7985
7998
|
Examples:
|
|
7986
7999
|
$ kinosec pentest schedule update abc123 --pattern daily --time 14:00
|
|
7987
|
-
$ kinosec pentest schedule update abc123 --
|
|
8000
|
+
$ kinosec pentest schedule update abc123 --time 14:00`).action(async (id, options) => {
|
|
7988
8001
|
const params = {};
|
|
7989
8002
|
if (options.pattern) params.recurrence_pattern = options.pattern;
|
|
7990
8003
|
if (options.name) params.name = options.name;
|
|
7991
|
-
if (options.mode)
|
|
8004
|
+
if (options.mode) {
|
|
8005
|
+
validateScanMode(options.mode);
|
|
8006
|
+
params.scan_mode = options.mode;
|
|
8007
|
+
}
|
|
7992
8008
|
if (options.time) params.time_of_day = options.time;
|
|
7993
8009
|
if (options.dayOfWeek !== void 0) params.day_of_week = parseInt(options.dayOfWeek, 10);
|
|
7994
8010
|
if (options.dayOfMonth !== void 0) params.day_of_month = parseInt(options.dayOfMonth, 10);
|
|
@@ -11527,6 +11543,320 @@ async function printFindingsSummary(client, profileId, tty3) {
|
|
|
11527
11543
|
}
|
|
11528
11544
|
}
|
|
11529
11545
|
|
|
11546
|
+
// src/lib/scout.ts
|
|
11547
|
+
var import_picocolors6 = __toESM(require_picocolors(), 1);
|
|
11548
|
+
var UNICODE2 = isUnicodeSupported();
|
|
11549
|
+
var sym2 = {
|
|
11550
|
+
success: UNICODE2 ? "\u2713" : "v",
|
|
11551
|
+
fail: UNICODE2 ? "\u2717" : "x",
|
|
11552
|
+
warn: UNICODE2 ? "\u26A0" : "!"
|
|
11553
|
+
};
|
|
11554
|
+
function printOnPremInstructions(result) {
|
|
11555
|
+
const tty3 = isTTY();
|
|
11556
|
+
const header = tty3 ? import_picocolors6.default.bold("On-Prem Scout Setup") : "On-Prem Scout Setup";
|
|
11557
|
+
console.log(`
|
|
11558
|
+
${header}
|
|
11559
|
+
`);
|
|
11560
|
+
console.log(` Session ID: ${result.session_id}`);
|
|
11561
|
+
console.log(` Profile ID: ${result.scan_profile_id}`);
|
|
11562
|
+
const curlCmd = `curl -fsSL https://api.kinosec.ai/functions/v1/scout-bootstrap/launch/${result.bootstrap_token} | bash`;
|
|
11563
|
+
if (tty3) {
|
|
11564
|
+
console.log(`
|
|
11565
|
+
Run on target host:
|
|
11566
|
+
`);
|
|
11567
|
+
console.log(` ${import_picocolors6.default.cyan(curlCmd)}
|
|
11568
|
+
`);
|
|
11569
|
+
console.log(` ${import_picocolors6.default.yellow(sym2.warn)} Token expires in ${import_picocolors6.default.bold("15 minutes")}.`);
|
|
11570
|
+
} else {
|
|
11571
|
+
console.log(`
|
|
11572
|
+
Run on target host:
|
|
11573
|
+
`);
|
|
11574
|
+
console.log(` ${curlCmd}
|
|
11575
|
+
`);
|
|
11576
|
+
console.log(` ${sym2.warn} Token expires in 15 minutes.`);
|
|
11577
|
+
}
|
|
11578
|
+
}
|
|
11579
|
+
async function waitForScoutConnection(client, profileId, options) {
|
|
11580
|
+
const { timeoutMs, intervalMs, useJson } = options;
|
|
11581
|
+
const tty3 = isTTY();
|
|
11582
|
+
const loopStart = Date.now();
|
|
11583
|
+
let spinner = null;
|
|
11584
|
+
if (!useJson && tty3) {
|
|
11585
|
+
spinner = ora({
|
|
11586
|
+
text: "Waiting for Scout connection...",
|
|
11587
|
+
color: "green",
|
|
11588
|
+
isEnabled: true
|
|
11589
|
+
}).start();
|
|
11590
|
+
}
|
|
11591
|
+
const cleanup = () => {
|
|
11592
|
+
if (spinner) spinner.stop();
|
|
11593
|
+
};
|
|
11594
|
+
const onSigint = () => {
|
|
11595
|
+
cleanup();
|
|
11596
|
+
console.log("\nWatch cancelled. Pentest is waiting for Scout connection.");
|
|
11597
|
+
process.exit(0);
|
|
11598
|
+
};
|
|
11599
|
+
process.once("SIGINT", onSigint);
|
|
11600
|
+
while (true) {
|
|
11601
|
+
const status = await client.scans.scoutStatus(profileId);
|
|
11602
|
+
if (useJson) {
|
|
11603
|
+
const elapsed = Math.round((Date.now() - loopStart) / 1e3);
|
|
11604
|
+
process.stdout.write(
|
|
11605
|
+
JSON.stringify({ ...status, elapsed_seconds: elapsed }) + "\n"
|
|
11606
|
+
);
|
|
11607
|
+
}
|
|
11608
|
+
switch (status.status) {
|
|
11609
|
+
case "pending":
|
|
11610
|
+
if (spinner) spinner.text = import_picocolors6.default.green("Waiting for Scout bootstrap...");
|
|
11611
|
+
break;
|
|
11612
|
+
case "bootstrapped":
|
|
11613
|
+
if (spinner) spinner.text = import_picocolors6.default.green("Scout bootstrapped, establishing connection...");
|
|
11614
|
+
break;
|
|
11615
|
+
case "connected": {
|
|
11616
|
+
cleanup();
|
|
11617
|
+
process.removeListener("SIGINT", onSigint);
|
|
11618
|
+
if (!useJson) {
|
|
11619
|
+
const icon = tty3 ? import_picocolors6.default.green(sym2.success) : sym2.success;
|
|
11620
|
+
console.log(`${icon} Scout connected`);
|
|
11621
|
+
if (status.hostname) {
|
|
11622
|
+
console.log(` Hostname: ${status.hostname}`);
|
|
11623
|
+
}
|
|
11624
|
+
if (status.tools.length > 0) {
|
|
11625
|
+
console.log(` Tools: ${status.tools.join(", ")}`);
|
|
11626
|
+
}
|
|
11627
|
+
}
|
|
11628
|
+
return;
|
|
11629
|
+
}
|
|
11630
|
+
case "expired": {
|
|
11631
|
+
cleanup();
|
|
11632
|
+
process.removeListener("SIGINT", onSigint);
|
|
11633
|
+
const icon = tty3 ? import_picocolors6.default.red(sym2.fail) : sym2.fail;
|
|
11634
|
+
console.error(`
|
|
11635
|
+
${icon} Scout bootstrap token expired.`);
|
|
11636
|
+
console.error("Start a new pentest to get a fresh token.");
|
|
11637
|
+
process.exit(1);
|
|
11638
|
+
}
|
|
11639
|
+
case "disconnected": {
|
|
11640
|
+
cleanup();
|
|
11641
|
+
process.removeListener("SIGINT", onSigint);
|
|
11642
|
+
const icon = tty3 ? import_picocolors6.default.red(sym2.fail) : sym2.fail;
|
|
11643
|
+
console.error(`
|
|
11644
|
+
${icon} Scout disconnected unexpectedly.`);
|
|
11645
|
+
console.error(`Check status: kinosec pentest get ${profileId}`);
|
|
11646
|
+
process.exit(1);
|
|
11647
|
+
}
|
|
11648
|
+
}
|
|
11649
|
+
if (Date.now() - loopStart > timeoutMs) {
|
|
11650
|
+
cleanup();
|
|
11651
|
+
process.removeListener("SIGINT", onSigint);
|
|
11652
|
+
const secs = Math.round(timeoutMs / 1e3);
|
|
11653
|
+
console.error(`
|
|
11654
|
+
Timeout: Scout did not connect within ${secs}s.`);
|
|
11655
|
+
console.error("The bootstrap token may have expired. Start a new pentest.");
|
|
11656
|
+
process.exit(3);
|
|
11657
|
+
}
|
|
11658
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
11659
|
+
}
|
|
11660
|
+
}
|
|
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
|
+
|
|
11530
11860
|
// src/commands/pentest.ts
|
|
11531
11861
|
function stripProtocol(input) {
|
|
11532
11862
|
const match = input.match(/^https?:\/\/(.+)/i);
|
|
@@ -11537,15 +11867,40 @@ function isValidDomain(value) {
|
|
|
11537
11867
|
return value.includes(".") && !/\s/.test(value);
|
|
11538
11868
|
}
|
|
11539
11869
|
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
|
|
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", `
|
|
11541
11871
|
Examples:
|
|
11542
11872
|
$ kinosec pentest -t delikado.org
|
|
11543
|
-
$ kinosec pentest --
|
|
11544
|
-
$ kinosec pentest
|
|
11873
|
+
$ kinosec pentest --profile abc123
|
|
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) => {
|
|
11545
11878
|
if (!options.target && !options.profile) {
|
|
11546
11879
|
console.error("Provide --target <domain> or --profile <id>.");
|
|
11547
11880
|
process.exit(2);
|
|
11548
11881
|
}
|
|
11882
|
+
if (options.deployment) {
|
|
11883
|
+
options.deployment = options.deployment.replace(/-/g, "_");
|
|
11884
|
+
}
|
|
11885
|
+
if (options.deployment !== "cloud" && options.deployment !== "on_prem") {
|
|
11886
|
+
console.error("Invalid deployment type. Must be: cloud or on-prem.");
|
|
11887
|
+
process.exit(2);
|
|
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
|
+
}
|
|
11549
11904
|
try {
|
|
11550
11905
|
const opts = program3.opts();
|
|
11551
11906
|
const { token, apiUrl } = resolveToken(opts);
|
|
@@ -11582,10 +11937,48 @@ Examples:
|
|
|
11582
11937
|
}
|
|
11583
11938
|
}
|
|
11584
11939
|
targetDisplay = domain;
|
|
11585
|
-
|
|
11940
|
+
const assetParams = {
|
|
11941
|
+
asset_id: asset.id,
|
|
11942
|
+
scan_mode: options.mode
|
|
11943
|
+
};
|
|
11944
|
+
if (options.deployment !== "cloud") {
|
|
11945
|
+
assetParams.deployment_type = options.deployment;
|
|
11946
|
+
}
|
|
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);
|
|
11586
11978
|
}
|
|
11587
11979
|
await confirmAuthorization(targetDisplay, Boolean(options.yes));
|
|
11588
11980
|
const result = await client.scans.create(params);
|
|
11981
|
+
const isOnPrem = result.deployment_type === "on_prem";
|
|
11589
11982
|
if (opts.json || opts.output === "json") {
|
|
11590
11983
|
printJson(result);
|
|
11591
11984
|
return;
|
|
@@ -11593,13 +11986,37 @@ Examples:
|
|
|
11593
11986
|
printSuccess(`Pentest started: ${result.domain_or_ip}`);
|
|
11594
11987
|
console.log(` Profile ID: ${result.scan_profile_id}`);
|
|
11595
11988
|
console.log(` Mode: ${result.scan_mode}`);
|
|
11989
|
+
if (isOnPrem) {
|
|
11990
|
+
printOnPremInstructions(result);
|
|
11991
|
+
if (options.watch && isTTY()) {
|
|
11992
|
+
const divider2 = import_picocolors8.default.dim("\u2500".repeat(37));
|
|
11993
|
+
console.log(`
|
|
11994
|
+
${divider2}
|
|
11995
|
+
`);
|
|
11996
|
+
await waitForScoutConnection(client, result.scan_profile_id, {
|
|
11997
|
+
timeoutMs: 15 * 60 * 1e3,
|
|
11998
|
+
intervalMs: 5 * 1e3,
|
|
11999
|
+
useJson: false
|
|
12000
|
+
});
|
|
12001
|
+
await watchPentest(client, result.scan_profile_id, {
|
|
12002
|
+
timeoutMs: 1800 * 1e3,
|
|
12003
|
+
intervalMs: 5 * 1e3,
|
|
12004
|
+
useJson: false,
|
|
12005
|
+
showFindingsSummary: true
|
|
12006
|
+
});
|
|
12007
|
+
} else {
|
|
12008
|
+
console.log(`
|
|
12009
|
+
Watch connection: kinosec pentest watch ${result.scan_profile_id}`);
|
|
12010
|
+
}
|
|
12011
|
+
return;
|
|
12012
|
+
}
|
|
11596
12013
|
if (!options.watch || !isTTY()) {
|
|
11597
12014
|
console.log(` Status: ${result.scan_status}`);
|
|
11598
12015
|
console.log(`
|
|
11599
12016
|
Watch progress: kinosec pentest watch ${result.scan_profile_id}`);
|
|
11600
12017
|
return;
|
|
11601
12018
|
}
|
|
11602
|
-
const divider = isTTY() ?
|
|
12019
|
+
const divider = isTTY() ? import_picocolors8.default.dim("\u2500".repeat(37)) : "-".repeat(37);
|
|
11603
12020
|
console.log(`
|
|
11604
12021
|
${divider}
|
|
11605
12022
|
`);
|
|
@@ -11645,7 +12062,7 @@ Examples:
|
|
|
11645
12062
|
])
|
|
11646
12063
|
);
|
|
11647
12064
|
if (result.next_cursor) {
|
|
11648
|
-
console.log(
|
|
12065
|
+
console.log(import_picocolors8.default.dim(`
|
|
11649
12066
|
More results available. Use --cursor ${result.next_cursor}`));
|
|
11650
12067
|
}
|
|
11651
12068
|
} catch (error2) {
|
|
@@ -11672,6 +12089,7 @@ Examples:
|
|
|
11672
12089
|
["Target", detail.domain_or_ip],
|
|
11673
12090
|
["Mode", detail.scan_mode],
|
|
11674
12091
|
["Type", detail.scan_type],
|
|
12092
|
+
...detail.deployment_type ? [["Deployment", detail.deployment_type === "on_prem" ? "On-Prem" : "Cloud"]] : [],
|
|
11675
12093
|
["Status", detail.scan_status],
|
|
11676
12094
|
["Activity", detail.current_activity || "(none)"],
|
|
11677
12095
|
["Created", detail.created_at],
|
|
@@ -11708,10 +12126,25 @@ Examples:
|
|
|
11708
12126
|
const opts = program3.opts();
|
|
11709
12127
|
const { token, apiUrl } = resolveToken(opts);
|
|
11710
12128
|
const client = new KinoSecClient({ apiUrl, token });
|
|
12129
|
+
const useJson = Boolean(opts.json || opts.output === "json");
|
|
12130
|
+
const detail = await client.scans.get(id);
|
|
12131
|
+
if (detail.deployment_type === "on_prem") {
|
|
12132
|
+
try {
|
|
12133
|
+
const scout = await client.scans.scoutStatus(id);
|
|
12134
|
+
if (scout.status !== "connected") {
|
|
12135
|
+
await waitForScoutConnection(client, id, {
|
|
12136
|
+
timeoutMs: 15 * 60 * 1e3,
|
|
12137
|
+
intervalMs: parseInt(options.interval, 10) * 1e3,
|
|
12138
|
+
useJson
|
|
12139
|
+
});
|
|
12140
|
+
}
|
|
12141
|
+
} catch {
|
|
12142
|
+
}
|
|
12143
|
+
}
|
|
11711
12144
|
await watchPentest(client, id, {
|
|
11712
12145
|
timeoutMs: parseInt(options.timeout, 10) * 1e3,
|
|
11713
12146
|
intervalMs: parseInt(options.interval, 10) * 1e3,
|
|
11714
|
-
useJson
|
|
12147
|
+
useJson,
|
|
11715
12148
|
showFindingsSummary: true
|
|
11716
12149
|
});
|
|
11717
12150
|
} catch (error2) {
|
|
@@ -11721,10 +12154,10 @@ Examples:
|
|
|
11721
12154
|
}
|
|
11722
12155
|
|
|
11723
12156
|
// src/commands/asset.ts
|
|
11724
|
-
var
|
|
12157
|
+
var import_picocolors10 = __toESM(require_picocolors(), 1);
|
|
11725
12158
|
|
|
11726
12159
|
// src/commands/asset-auth.ts
|
|
11727
|
-
var
|
|
12160
|
+
var import_picocolors9 = __toESM(require_picocolors(), 1);
|
|
11728
12161
|
|
|
11729
12162
|
// src/lib/resolve-asset.ts
|
|
11730
12163
|
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-/i;
|
|
@@ -11750,84 +12183,6 @@ or use pentest which auto-creates the asset:
|
|
|
11750
12183
|
}
|
|
11751
12184
|
}
|
|
11752
12185
|
|
|
11753
|
-
// src/lib/prompt.ts
|
|
11754
|
-
import { createInterface as createInterface2 } from "readline";
|
|
11755
|
-
async function promptMasked(label) {
|
|
11756
|
-
if (!isTTY() || !process.stdin.isTTY) {
|
|
11757
|
-
console.error(`Cannot prompt for ${label} in non-interactive mode. Use flags instead.`);
|
|
11758
|
-
process.exit(2);
|
|
11759
|
-
}
|
|
11760
|
-
process.stderr.write(`${label}: `);
|
|
11761
|
-
return new Promise((resolve, reject) => {
|
|
11762
|
-
const chars = [];
|
|
11763
|
-
process.stdin.setRawMode(true);
|
|
11764
|
-
process.stdin.resume();
|
|
11765
|
-
process.stdin.setEncoding("utf-8");
|
|
11766
|
-
const onData = (chunk) => {
|
|
11767
|
-
for (const ch of chunk) {
|
|
11768
|
-
if (ch === "\r" || ch === "\n") {
|
|
11769
|
-
process.stdin.setRawMode(false);
|
|
11770
|
-
process.stdin.pause();
|
|
11771
|
-
process.stdin.removeListener("data", onData);
|
|
11772
|
-
process.stderr.write("\n");
|
|
11773
|
-
resolve(chars.join(""));
|
|
11774
|
-
return;
|
|
11775
|
-
} else if (ch === "") {
|
|
11776
|
-
process.stdin.setRawMode(false);
|
|
11777
|
-
process.stdin.pause();
|
|
11778
|
-
process.stdin.removeListener("data", onData);
|
|
11779
|
-
process.stderr.write("\n");
|
|
11780
|
-
reject(new Error("Cancelled"));
|
|
11781
|
-
return;
|
|
11782
|
-
} else if (ch === "\x7F" || ch === "\b") {
|
|
11783
|
-
if (chars.length > 0) {
|
|
11784
|
-
chars.pop();
|
|
11785
|
-
process.stderr.write("\b \b");
|
|
11786
|
-
}
|
|
11787
|
-
} else if (ch >= " ") {
|
|
11788
|
-
chars.push(ch);
|
|
11789
|
-
process.stderr.write("*");
|
|
11790
|
-
}
|
|
11791
|
-
}
|
|
11792
|
-
};
|
|
11793
|
-
process.stdin.on("data", onData);
|
|
11794
|
-
});
|
|
11795
|
-
}
|
|
11796
|
-
async function promptText(label) {
|
|
11797
|
-
if (!isTTY() || !process.stdin.isTTY) {
|
|
11798
|
-
console.error(`Cannot prompt for ${label} in non-interactive mode. Use flags instead.`);
|
|
11799
|
-
process.exit(2);
|
|
11800
|
-
}
|
|
11801
|
-
const rl = createInterface2({ input: process.stdin, output: process.stderr });
|
|
11802
|
-
const answer = await new Promise((resolve) => {
|
|
11803
|
-
rl.question(`${label}: `, resolve);
|
|
11804
|
-
});
|
|
11805
|
-
rl.close();
|
|
11806
|
-
return answer;
|
|
11807
|
-
}
|
|
11808
|
-
async function promptSelect(message, options) {
|
|
11809
|
-
if (!isTTY() || !process.stdin.isTTY) {
|
|
11810
|
-
console.error("Cannot show interactive menu in non-interactive mode. Use flags instead.");
|
|
11811
|
-
process.exit(2);
|
|
11812
|
-
}
|
|
11813
|
-
console.error(`
|
|
11814
|
-
${message}`);
|
|
11815
|
-
for (let i = 0; i < options.length; i++) {
|
|
11816
|
-
console.error(` ${i + 1}. ${options[i].label} \u2014 ${options[i].description}`);
|
|
11817
|
-
}
|
|
11818
|
-
const rl = createInterface2({ input: process.stdin, output: process.stderr });
|
|
11819
|
-
const answer = await new Promise((resolve) => {
|
|
11820
|
-
rl.question("> ", resolve);
|
|
11821
|
-
});
|
|
11822
|
-
rl.close();
|
|
11823
|
-
const index = parseInt(answer, 10) - 1;
|
|
11824
|
-
if (isNaN(index) || index < 0 || index >= options.length) {
|
|
11825
|
-
console.error("Invalid selection.");
|
|
11826
|
-
process.exit(2);
|
|
11827
|
-
}
|
|
11828
|
-
return options[index].value;
|
|
11829
|
-
}
|
|
11830
|
-
|
|
11831
12186
|
// src/commands/asset-auth.ts
|
|
11832
12187
|
var VALID_AUTH_TYPES = ["bearer", "basic", "apikey", "session", "cookie"];
|
|
11833
12188
|
var AUTH_TYPE_OPTIONS = [
|
|
@@ -11981,7 +12336,7 @@ Examples:
|
|
|
11981
12336
|
}
|
|
11982
12337
|
if (!creds.has_credentials) {
|
|
11983
12338
|
console.log(`Asset: ${asset.asset_value}`);
|
|
11984
|
-
console.log(`Auth Type: ${
|
|
12339
|
+
console.log(`Auth Type: ${import_picocolors9.default.dim("(none)")}`);
|
|
11985
12340
|
console.log(`Has Credentials: no`);
|
|
11986
12341
|
return;
|
|
11987
12342
|
}
|
|
@@ -12002,9 +12357,9 @@ Examples:
|
|
|
12002
12357
|
const hasAny = ta.has_username || ta.has_cookie || ta.has_bearer_token || ta.has_apikey;
|
|
12003
12358
|
if (hasAny) {
|
|
12004
12359
|
const detail = ta.has_username ? "username" : ta.has_bearer_token ? "bearer token" : ta.has_cookie ? "cookie" : "API key";
|
|
12005
|
-
console.log(`Target Account: ${
|
|
12360
|
+
console.log(`Target Account: ${import_picocolors9.default.green("configured")} (${detail})`);
|
|
12006
12361
|
} else {
|
|
12007
|
-
console.log(`Target Account: ${
|
|
12362
|
+
console.log(`Target Account: ${import_picocolors9.default.dim("not configured")}`);
|
|
12008
12363
|
}
|
|
12009
12364
|
}
|
|
12010
12365
|
} catch (error2) {
|
|
@@ -12145,17 +12500,18 @@ Examples:
|
|
|
12145
12500
|
return;
|
|
12146
12501
|
}
|
|
12147
12502
|
printTable(
|
|
12148
|
-
["ID", "Value", "Type", "Status", "Created"],
|
|
12503
|
+
["ID", "Value", "Type", "Status", "Scope", "Created"],
|
|
12149
12504
|
result.items.map((a) => [
|
|
12150
12505
|
a.id,
|
|
12151
12506
|
truncate(a.asset_value, 40),
|
|
12152
12507
|
a.asset_type,
|
|
12153
12508
|
a.verification_status,
|
|
12509
|
+
a.scope_filter ?? "all",
|
|
12154
12510
|
new Date(a.created_at).toLocaleDateString()
|
|
12155
12511
|
])
|
|
12156
12512
|
);
|
|
12157
12513
|
if (result.next_cursor) {
|
|
12158
|
-
console.log(
|
|
12514
|
+
console.log(import_picocolors10.default.dim(`
|
|
12159
12515
|
More results available. Use --cursor ${result.next_cursor}`));
|
|
12160
12516
|
}
|
|
12161
12517
|
} catch (error2) {
|
|
@@ -12263,11 +12619,84 @@ Examples:
|
|
|
12263
12619
|
handleError(error2);
|
|
12264
12620
|
}
|
|
12265
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
|
+
});
|
|
12266
12695
|
registerAssetAuthCommand(asset);
|
|
12267
12696
|
}
|
|
12268
12697
|
|
|
12269
12698
|
// src/commands/finding.ts
|
|
12270
|
-
var
|
|
12699
|
+
var import_picocolors11 = __toESM(require_picocolors(), 1);
|
|
12271
12700
|
function formatPoc(poc) {
|
|
12272
12701
|
if (poc == null) return null;
|
|
12273
12702
|
if (typeof poc === "string") return poc.trim() || null;
|
|
@@ -12308,7 +12737,7 @@ Examples:
|
|
|
12308
12737
|
])
|
|
12309
12738
|
);
|
|
12310
12739
|
if (result.next_cursor) {
|
|
12311
|
-
console.log(
|
|
12740
|
+
console.log(import_picocolors11.default.dim(`
|
|
12312
12741
|
More results available. Use --cursor ${result.next_cursor}`));
|
|
12313
12742
|
}
|
|
12314
12743
|
} catch (error2) {
|
|
@@ -12396,7 +12825,7 @@ Examples:
|
|
|
12396
12825
|
}
|
|
12397
12826
|
|
|
12398
12827
|
// src/commands/report.ts
|
|
12399
|
-
var
|
|
12828
|
+
var import_picocolors12 = __toESM(require_picocolors(), 1);
|
|
12400
12829
|
function renderMarkdown(data) {
|
|
12401
12830
|
const { scan, findings } = data;
|
|
12402
12831
|
const lines = [];
|
|
@@ -12473,7 +12902,7 @@ Examples:
|
|
|
12473
12902
|
])
|
|
12474
12903
|
);
|
|
12475
12904
|
if (result.next_cursor) {
|
|
12476
|
-
console.log(
|
|
12905
|
+
console.log(import_picocolors12.default.dim(`
|
|
12477
12906
|
More results available. Use --cursor ${result.next_cursor}`));
|
|
12478
12907
|
}
|
|
12479
12908
|
} catch (error2) {
|
|
@@ -12544,7 +12973,7 @@ Examples:
|
|
|
12544
12973
|
}
|
|
12545
12974
|
|
|
12546
12975
|
// src/commands/token.ts
|
|
12547
|
-
var
|
|
12976
|
+
var import_picocolors13 = __toESM(require_picocolors(), 1);
|
|
12548
12977
|
function registerTokenCommand(program3) {
|
|
12549
12978
|
const token = program3.command("token").description("Manage API tokens");
|
|
12550
12979
|
token.command("list").description("List API tokens").option("--limit <n>", "Max results per page", "20").option("--cursor <cursor>", "Pagination cursor").addHelpText("after", `
|
|
@@ -12579,7 +13008,7 @@ Examples:
|
|
|
12579
13008
|
])
|
|
12580
13009
|
);
|
|
12581
13010
|
if (result.next_cursor) {
|
|
12582
|
-
console.log(
|
|
13011
|
+
console.log(import_picocolors13.default.dim(`
|
|
12583
13012
|
More results available. Use --cursor ${result.next_cursor}`));
|
|
12584
13013
|
}
|
|
12585
13014
|
} catch (error2) {
|
|
@@ -12606,7 +13035,7 @@ Examples:
|
|
|
12606
13035
|
}
|
|
12607
13036
|
printSuccess(`Token created: ${result.name}`);
|
|
12608
13037
|
console.log("");
|
|
12609
|
-
console.log(
|
|
13038
|
+
console.log(import_picocolors13.default.bold(" Token: ") + result.token);
|
|
12610
13039
|
console.log("");
|
|
12611
13040
|
printWarning("Save this token \u2014 it will not be shown again.");
|
|
12612
13041
|
console.log(` ID: ${result.id}`);
|
|
@@ -12639,7 +13068,7 @@ Examples:
|
|
|
12639
13068
|
|
|
12640
13069
|
// src/cli.ts
|
|
12641
13070
|
var program2 = new Command();
|
|
12642
|
-
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");
|
|
12643
13072
|
registerAuthCommand(program2);
|
|
12644
13073
|
registerMeCommand(program2);
|
|
12645
13074
|
registerCreditsCommand(program2);
|
|
@@ -12648,7 +13077,7 @@ registerAssetCommand(program2);
|
|
|
12648
13077
|
registerFindingCommand(program2);
|
|
12649
13078
|
registerReportCommand(program2);
|
|
12650
13079
|
registerTokenCommand(program2);
|
|
12651
|
-
program2.parseAsync(process.argv).then(() => checkForUpdate("0.
|
|
13080
|
+
program2.parseAsync(process.argv).then(() => checkForUpdate("0.10.0")).catch(handleError);
|
|
12652
13081
|
/*! Bundled license information:
|
|
12653
13082
|
|
|
12654
13083
|
ky/distribution/index.js:
|