@kinosec/cli 0.2.1 → 0.3.1
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/README.md +17 -17
- package/dist/cli.js +227 -137
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -7851,6 +7851,9 @@ var AssetsResource = class {
|
|
|
7851
7851
|
async verify(id) {
|
|
7852
7852
|
return this.api.post(`v1/assets/${id}/verify`, { json: {} }).json();
|
|
7853
7853
|
}
|
|
7854
|
+
async lookup(domain) {
|
|
7855
|
+
return this.api.get("v1/assets/lookup", { searchParams: { domain } }).json();
|
|
7856
|
+
}
|
|
7854
7857
|
};
|
|
7855
7858
|
var ScansResource = class {
|
|
7856
7859
|
constructor(api) {
|
|
@@ -8199,9 +8202,6 @@ Update: npm update -g @kinosec/cli`
|
|
|
8199
8202
|
}
|
|
8200
8203
|
}
|
|
8201
8204
|
|
|
8202
|
-
// src/commands/auth.ts
|
|
8203
|
-
import { createInterface } from "readline";
|
|
8204
|
-
|
|
8205
8205
|
// src/lib/auth.ts
|
|
8206
8206
|
var DEFAULT_API_URL = "https://api.kinosec.ai/v1";
|
|
8207
8207
|
function resolveToken(options) {
|
|
@@ -8225,12 +8225,40 @@ function maskToken(token) {
|
|
|
8225
8225
|
return token.slice(0, 16) + "\u2026" + token.slice(-4);
|
|
8226
8226
|
}
|
|
8227
8227
|
async function promptForToken() {
|
|
8228
|
-
|
|
8229
|
-
return new Promise((resolve) => {
|
|
8230
|
-
|
|
8231
|
-
|
|
8232
|
-
|
|
8233
|
-
|
|
8228
|
+
process.stderr.write("API token: ");
|
|
8229
|
+
return new Promise((resolve, reject) => {
|
|
8230
|
+
const chars = [];
|
|
8231
|
+
process.stdin.setRawMode(true);
|
|
8232
|
+
process.stdin.resume();
|
|
8233
|
+
process.stdin.setEncoding("utf-8");
|
|
8234
|
+
const onData = (chunk) => {
|
|
8235
|
+
for (const ch of chunk) {
|
|
8236
|
+
if (ch === "\r" || ch === "\n") {
|
|
8237
|
+
process.stdin.setRawMode(false);
|
|
8238
|
+
process.stdin.pause();
|
|
8239
|
+
process.stdin.removeListener("data", onData);
|
|
8240
|
+
process.stderr.write("\n");
|
|
8241
|
+
resolve(chars.join(""));
|
|
8242
|
+
return;
|
|
8243
|
+
} else if (ch === "") {
|
|
8244
|
+
process.stdin.setRawMode(false);
|
|
8245
|
+
process.stdin.pause();
|
|
8246
|
+
process.stdin.removeListener("data", onData);
|
|
8247
|
+
process.stderr.write("\n");
|
|
8248
|
+
reject(new Error("Cancelled"));
|
|
8249
|
+
return;
|
|
8250
|
+
} else if (ch === "\x7F" || ch === "\b") {
|
|
8251
|
+
if (chars.length > 0) {
|
|
8252
|
+
chars.pop();
|
|
8253
|
+
process.stderr.write("\b \b");
|
|
8254
|
+
}
|
|
8255
|
+
} else if (ch >= " ") {
|
|
8256
|
+
chars.push(ch);
|
|
8257
|
+
process.stderr.write("*");
|
|
8258
|
+
}
|
|
8259
|
+
}
|
|
8260
|
+
};
|
|
8261
|
+
process.stdin.on("data", onData);
|
|
8234
8262
|
});
|
|
8235
8263
|
}
|
|
8236
8264
|
function registerAuthCommand(program3) {
|
|
@@ -8248,7 +8276,7 @@ Examples:
|
|
|
8248
8276
|
);
|
|
8249
8277
|
}
|
|
8250
8278
|
if (!token) {
|
|
8251
|
-
if (isTTY()) {
|
|
8279
|
+
if (isTTY() && process.stdin.isTTY) {
|
|
8252
8280
|
token = await promptForToken();
|
|
8253
8281
|
} else {
|
|
8254
8282
|
const chunks = [];
|
|
@@ -9817,21 +9845,19 @@ function ora(options) {
|
|
|
9817
9845
|
return new Ora(options);
|
|
9818
9846
|
}
|
|
9819
9847
|
|
|
9820
|
-
// src/commands/
|
|
9821
|
-
var
|
|
9822
|
-
|
|
9823
|
-
// src/commands/schedule.ts
|
|
9824
|
-
var import_picocolors3 = __toESM(require_picocolors(), 1);
|
|
9848
|
+
// src/commands/pentest.ts
|
|
9849
|
+
var import_picocolors5 = __toESM(require_picocolors(), 1);
|
|
9825
9850
|
|
|
9826
9851
|
// src/lib/confirm.ts
|
|
9827
|
-
|
|
9852
|
+
var import_picocolors3 = __toESM(require_picocolors(), 1);
|
|
9853
|
+
import { createInterface } from "readline";
|
|
9828
9854
|
async function confirmDestructive(message, yes) {
|
|
9829
9855
|
if (yes) return;
|
|
9830
9856
|
if (!isTTY()) {
|
|
9831
9857
|
console.error("Use --yes to confirm destructive operations in non-interactive mode.");
|
|
9832
9858
|
process.exit(2);
|
|
9833
9859
|
}
|
|
9834
|
-
const rl =
|
|
9860
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
9835
9861
|
const answer = await new Promise((resolve) => {
|
|
9836
9862
|
rl.question(`${message} (y/N) `, resolve);
|
|
9837
9863
|
});
|
|
@@ -9841,8 +9867,45 @@ async function confirmDestructive(message, yes) {
|
|
|
9841
9867
|
process.exit(0);
|
|
9842
9868
|
}
|
|
9843
9869
|
}
|
|
9870
|
+
async function confirmAuthorization(target, yes) {
|
|
9871
|
+
if (yes) return;
|
|
9872
|
+
if (!isTTY()) {
|
|
9873
|
+
console.error("Use --yes to accept authorization terms in non-interactive mode.");
|
|
9874
|
+
process.exit(2);
|
|
9875
|
+
}
|
|
9876
|
+
const warning = import_picocolors3.default.yellow("\u26A0");
|
|
9877
|
+
console.error(`
|
|
9878
|
+
${warning} ${import_picocolors3.default.bold("Authorization Required")}
|
|
9879
|
+
|
|
9880
|
+
Penetration testing without proper authorization is illegal
|
|
9881
|
+
and may result in criminal prosecution.
|
|
9882
|
+
|
|
9883
|
+
${import_picocolors3.default.bold("Target:")} ${target}
|
|
9884
|
+
|
|
9885
|
+
By proceeding, you confirm that:
|
|
9886
|
+
${import_picocolors3.default.dim("\u2022")} You own or have explicit written authorization to test this target
|
|
9887
|
+
${import_picocolors3.default.dim("\u2022")} You will comply with all applicable laws and regulations
|
|
9888
|
+
${import_picocolors3.default.dim("\u2022")} You will not use the results for illegal or malicious purposes
|
|
9889
|
+
${import_picocolors3.default.dim("\u2022")} You accept full legal responsibility for this pentest
|
|
9890
|
+
|
|
9891
|
+
${import_picocolors3.default.dim("Unauthorized access to computer systems is a criminal offense under")}
|
|
9892
|
+
${import_picocolors3.default.dim("laws such as the CFAA (US), Computer Misuse Act (UK), and similar")}
|
|
9893
|
+
${import_picocolors3.default.dim("legislation worldwide. KinoSec bears no liability for unauthorized")}
|
|
9894
|
+
${import_picocolors3.default.dim("use of its services.")}
|
|
9895
|
+
`);
|
|
9896
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
9897
|
+
const answer = await new Promise((resolve) => {
|
|
9898
|
+
rl.question("Agree and continue? (y/N) ", resolve);
|
|
9899
|
+
});
|
|
9900
|
+
rl.close();
|
|
9901
|
+
if (answer.toLowerCase() !== "y") {
|
|
9902
|
+
console.log("Cancelled.");
|
|
9903
|
+
process.exit(0);
|
|
9904
|
+
}
|
|
9905
|
+
}
|
|
9844
9906
|
|
|
9845
9907
|
// src/commands/schedule.ts
|
|
9908
|
+
var import_picocolors4 = __toESM(require_picocolors(), 1);
|
|
9846
9909
|
var VALID_PATTERNS = ["once", "daily", "weekly", "biweekly", "monthly"];
|
|
9847
9910
|
var VALID_MODES = ["quick", "full"];
|
|
9848
9911
|
var DAY_NAMES = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
|
|
@@ -9868,12 +9931,12 @@ function formatPattern(s) {
|
|
|
9868
9931
|
return s.recurrence_pattern;
|
|
9869
9932
|
}
|
|
9870
9933
|
}
|
|
9871
|
-
function registerScheduleCommand(
|
|
9872
|
-
const schedule =
|
|
9873
|
-
schedule.command("list").description("List scheduled
|
|
9934
|
+
function registerScheduleCommand(pentestCmd) {
|
|
9935
|
+
const schedule = pentestCmd.command("schedule").description("Manage scheduled pentests");
|
|
9936
|
+
schedule.command("list").description("List scheduled pentests").option("--limit <n>", "Max results per page", "20").option("--cursor <cursor>", "Pagination cursor").option("--status <status>", "Filter: active, paused, or all", "all").addHelpText("after", `
|
|
9874
9937
|
Examples:
|
|
9875
|
-
$ kinosec
|
|
9876
|
-
$ kinosec
|
|
9938
|
+
$ kinosec pentest schedule list
|
|
9939
|
+
$ kinosec pentest schedule list --status active --json`).action(async (options) => {
|
|
9877
9940
|
try {
|
|
9878
9941
|
const opts = schedule.parent.parent.opts();
|
|
9879
9942
|
const { token, apiUrl } = resolveToken(opts);
|
|
@@ -9888,7 +9951,7 @@ Examples:
|
|
|
9888
9951
|
return;
|
|
9889
9952
|
}
|
|
9890
9953
|
if (result.items.length === 0) {
|
|
9891
|
-
console.log("No scheduled
|
|
9954
|
+
console.log("No scheduled pentests found.");
|
|
9892
9955
|
return;
|
|
9893
9956
|
}
|
|
9894
9957
|
printTable(
|
|
@@ -9898,12 +9961,12 @@ Examples:
|
|
|
9898
9961
|
truncate(s.domain_or_ip, 25),
|
|
9899
9962
|
s.scan_mode,
|
|
9900
9963
|
formatPattern(s),
|
|
9901
|
-
s.is_active ?
|
|
9964
|
+
s.is_active ? import_picocolors4.default.green("active") : import_picocolors4.default.yellow("paused"),
|
|
9902
9965
|
s.next_scheduled_run ? new Date(s.next_scheduled_run).toLocaleString() : "\u2014"
|
|
9903
9966
|
])
|
|
9904
9967
|
);
|
|
9905
9968
|
if (result.next_cursor) {
|
|
9906
|
-
console.log(
|
|
9969
|
+
console.log(import_picocolors4.default.dim(`
|
|
9907
9970
|
More results available. Use --cursor ${result.next_cursor}`));
|
|
9908
9971
|
}
|
|
9909
9972
|
} catch (error) {
|
|
@@ -9912,8 +9975,8 @@ More results available. Use --cursor ${result.next_cursor}`));
|
|
|
9912
9975
|
});
|
|
9913
9976
|
schedule.command("get").description("Get schedule details").argument("<id>", "Schedule ID").addHelpText("after", `
|
|
9914
9977
|
Examples:
|
|
9915
|
-
$ kinosec
|
|
9916
|
-
$ kinosec
|
|
9978
|
+
$ kinosec pentest schedule get abc123
|
|
9979
|
+
$ kinosec pentest schedule get abc123 --json`).action(async (id) => {
|
|
9917
9980
|
try {
|
|
9918
9981
|
const opts = schedule.parent.parent.opts();
|
|
9919
9982
|
const { token, apiUrl } = resolveToken(opts);
|
|
@@ -9942,11 +10005,11 @@ Examples:
|
|
|
9942
10005
|
handleError(error);
|
|
9943
10006
|
}
|
|
9944
10007
|
});
|
|
9945
|
-
schedule.command("create").description("Create a scheduled
|
|
10008
|
+
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", `
|
|
9946
10009
|
Examples:
|
|
9947
|
-
$ kinosec
|
|
9948
|
-
$ kinosec
|
|
9949
|
-
$ kinosec
|
|
10010
|
+
$ kinosec pentest schedule create --asset abc123 --pattern weekly --day-of-week 1 --time 09:00
|
|
10011
|
+
$ kinosec pentest schedule create --asset abc123 --pattern daily --mode quick --time 14:00
|
|
10012
|
+
$ kinosec pentest schedule create --asset abc123 --pattern monthly --day-of-month 15 --time 03:00 --timezone UTC`).action(async (options) => {
|
|
9950
10013
|
if (!VALID_PATTERNS.includes(options.pattern)) {
|
|
9951
10014
|
console.error(`Invalid pattern "${options.pattern}". Must be: ${VALID_PATTERNS.join(", ")}`);
|
|
9952
10015
|
process.exit(2);
|
|
@@ -9969,7 +10032,7 @@ Examples:
|
|
|
9969
10032
|
const client = new KinoSecClient({ apiUrl, token });
|
|
9970
10033
|
const params = {
|
|
9971
10034
|
asset_id: options.domain_or_ip,
|
|
9972
|
-
name: options.name ?? `${options.pattern} ${options.mode}
|
|
10035
|
+
name: options.name ?? `${options.pattern} ${options.mode} pentest`,
|
|
9973
10036
|
scan_mode: options.mode,
|
|
9974
10037
|
recurrence_pattern: options.pattern,
|
|
9975
10038
|
time_of_day: options.time,
|
|
@@ -9987,15 +10050,15 @@ Examples:
|
|
|
9987
10050
|
console.log(` Schedule: ${formatPattern(s)}`);
|
|
9988
10051
|
console.log(` Next run: ${s.next_scheduled_run ?? "pending"}`);
|
|
9989
10052
|
console.log(`
|
|
9990
|
-
Manage: kinosec
|
|
10053
|
+
Manage: kinosec pentest schedule get ${s.id}`);
|
|
9991
10054
|
} catch (error) {
|
|
9992
10055
|
handleError(error);
|
|
9993
10056
|
}
|
|
9994
10057
|
});
|
|
9995
10058
|
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", `
|
|
9996
10059
|
Examples:
|
|
9997
|
-
$ kinosec
|
|
9998
|
-
$ kinosec
|
|
10060
|
+
$ kinosec pentest schedule update abc123 --pattern daily --time 14:00
|
|
10061
|
+
$ kinosec pentest schedule update abc123 --mode quick`).action(async (id, options) => {
|
|
9999
10062
|
const params = {};
|
|
10000
10063
|
if (options.pattern) params.recurrence_pattern = options.pattern;
|
|
10001
10064
|
if (options.name) params.name = options.name;
|
|
@@ -10026,8 +10089,8 @@ Examples:
|
|
|
10026
10089
|
});
|
|
10027
10090
|
schedule.command("delete").description("Delete a schedule").argument("<id>", "Schedule ID").option("-y, --yes", "Skip confirmation prompt").addHelpText("after", `
|
|
10028
10091
|
Examples:
|
|
10029
|
-
$ kinosec
|
|
10030
|
-
$ kinosec
|
|
10092
|
+
$ kinosec pentest schedule delete abc123
|
|
10093
|
+
$ kinosec pentest schedule delete abc123 --yes`).action(async (id, options) => {
|
|
10031
10094
|
try {
|
|
10032
10095
|
await confirmDestructive(`Delete schedule ${id}?`, Boolean(options.yes));
|
|
10033
10096
|
const opts = schedule.parent.parent.opts();
|
|
@@ -10043,9 +10106,9 @@ Examples:
|
|
|
10043
10106
|
handleError(error);
|
|
10044
10107
|
}
|
|
10045
10108
|
});
|
|
10046
|
-
schedule.command("pause").description("Pause a scheduled
|
|
10109
|
+
schedule.command("pause").description("Pause a scheduled pentest").argument("<id>", "Schedule ID").addHelpText("after", `
|
|
10047
10110
|
Examples:
|
|
10048
|
-
$ kinosec
|
|
10111
|
+
$ kinosec pentest schedule pause abc123`).action(async (id) => {
|
|
10049
10112
|
try {
|
|
10050
10113
|
const opts = schedule.parent.parent.opts();
|
|
10051
10114
|
const { token, apiUrl } = resolveToken(opts);
|
|
@@ -10062,7 +10125,7 @@ Examples:
|
|
|
10062
10125
|
});
|
|
10063
10126
|
schedule.command("resume").description("Resume a paused schedule").argument("<id>", "Schedule ID").addHelpText("after", `
|
|
10064
10127
|
Examples:
|
|
10065
|
-
$ kinosec
|
|
10128
|
+
$ kinosec pentest schedule resume abc123`).action(async (id) => {
|
|
10066
10129
|
try {
|
|
10067
10130
|
const opts = schedule.parent.parent.opts();
|
|
10068
10131
|
const { token, apiUrl } = resolveToken(opts);
|
|
@@ -10082,14 +10145,79 @@ Examples:
|
|
|
10082
10145
|
});
|
|
10083
10146
|
}
|
|
10084
10147
|
|
|
10085
|
-
// src/commands/
|
|
10086
|
-
function
|
|
10087
|
-
const
|
|
10088
|
-
|
|
10089
|
-
|
|
10148
|
+
// src/commands/pentest.ts
|
|
10149
|
+
function stripProtocol(input) {
|
|
10150
|
+
const match = input.match(/^https?:\/\/(.+)/i);
|
|
10151
|
+
if (match) return { domain: match[1].replace(/\/+$/, ""), warned: true };
|
|
10152
|
+
return { domain: input, warned: false };
|
|
10153
|
+
}
|
|
10154
|
+
function isValidDomain(value) {
|
|
10155
|
+
return value.includes(".") && !/\s/.test(value);
|
|
10156
|
+
}
|
|
10157
|
+
function registerPentestCommand(program3) {
|
|
10158
|
+
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").addHelpText("after", `
|
|
10090
10159
|
Examples:
|
|
10091
|
-
$ kinosec
|
|
10092
|
-
$ kinosec
|
|
10160
|
+
$ kinosec pentest -t delikado.org
|
|
10161
|
+
$ kinosec pentest --target delikado.org --mode quick
|
|
10162
|
+
$ kinosec pentest --profile abc123`).action(async (options) => {
|
|
10163
|
+
if (!options.target && !options.profile) {
|
|
10164
|
+
console.error("Provide --target <domain> or --profile <id>.");
|
|
10165
|
+
process.exit(2);
|
|
10166
|
+
}
|
|
10167
|
+
try {
|
|
10168
|
+
const opts = program3.opts();
|
|
10169
|
+
const { token, apiUrl } = resolveToken(opts);
|
|
10170
|
+
const client = new KinoSecClient({ apiUrl, token });
|
|
10171
|
+
let targetDisplay;
|
|
10172
|
+
let params;
|
|
10173
|
+
if (options.profile) {
|
|
10174
|
+
const detail = await client.scans.get(options.profile);
|
|
10175
|
+
targetDisplay = detail.domain_or_ip;
|
|
10176
|
+
params = { scan_profile_id: options.profile };
|
|
10177
|
+
} else {
|
|
10178
|
+
const { domain, warned } = stripProtocol(options.target);
|
|
10179
|
+
if (warned) {
|
|
10180
|
+
console.error(`Warning: protocol stripped. Using "${domain}".`);
|
|
10181
|
+
}
|
|
10182
|
+
if (!isValidDomain(domain)) {
|
|
10183
|
+
console.error(`Invalid domain format: "${domain}"`);
|
|
10184
|
+
console.error("Provide a domain like delikado.org or juice.delikado.org.");
|
|
10185
|
+
process.exit(2);
|
|
10186
|
+
}
|
|
10187
|
+
let asset;
|
|
10188
|
+
try {
|
|
10189
|
+
asset = await client.assets.lookup(domain);
|
|
10190
|
+
} catch (err) {
|
|
10191
|
+
if (err instanceof ApiError && err.status === 404) {
|
|
10192
|
+
console.error(`No asset found for '${domain}'. Run kinosec asset list to see your assets.`);
|
|
10193
|
+
process.exit(1);
|
|
10194
|
+
}
|
|
10195
|
+
throw err;
|
|
10196
|
+
}
|
|
10197
|
+
targetDisplay = domain;
|
|
10198
|
+
params = { asset_id: asset.id, scan_mode: options.mode };
|
|
10199
|
+
}
|
|
10200
|
+
await confirmAuthorization(targetDisplay, Boolean(options.yes));
|
|
10201
|
+
const result = await client.scans.create(params);
|
|
10202
|
+
if (opts.json || opts.output === "json") {
|
|
10203
|
+
printJson(result);
|
|
10204
|
+
return;
|
|
10205
|
+
}
|
|
10206
|
+
printSuccess(`Pentest started: ${result.domain_or_ip}`);
|
|
10207
|
+
console.log(` Profile ID: ${result.scan_profile_id}`);
|
|
10208
|
+
console.log(` Mode: ${result.scan_mode}`);
|
|
10209
|
+
console.log(` Status: ${result.scan_status}`);
|
|
10210
|
+
console.log(`
|
|
10211
|
+
Watch progress: kinosec pentest watch ${result.scan_profile_id}`);
|
|
10212
|
+
} catch (error) {
|
|
10213
|
+
handleError(error);
|
|
10214
|
+
}
|
|
10215
|
+
});
|
|
10216
|
+
registerScheduleCommand(pentest);
|
|
10217
|
+
pentest.command("list").description("List pentests").option("--limit <n>", "Max results per page", "20").option("--cursor <cursor>", "Pagination cursor").addHelpText("after", `
|
|
10218
|
+
Examples:
|
|
10219
|
+
$ kinosec pentest list
|
|
10220
|
+
$ kinosec pentest list --limit 5 --json`).action(async (options) => {
|
|
10093
10221
|
try {
|
|
10094
10222
|
const opts = program3.opts();
|
|
10095
10223
|
const { token, apiUrl } = resolveToken(opts);
|
|
@@ -10103,7 +10231,7 @@ Examples:
|
|
|
10103
10231
|
return;
|
|
10104
10232
|
}
|
|
10105
10233
|
if (result.items.length === 0) {
|
|
10106
|
-
console.log("No
|
|
10234
|
+
console.log("No pentests found.");
|
|
10107
10235
|
return;
|
|
10108
10236
|
}
|
|
10109
10237
|
printTable(
|
|
@@ -10117,17 +10245,17 @@ Examples:
|
|
|
10117
10245
|
])
|
|
10118
10246
|
);
|
|
10119
10247
|
if (result.next_cursor) {
|
|
10120
|
-
console.log(
|
|
10248
|
+
console.log(import_picocolors5.default.dim(`
|
|
10121
10249
|
More results available. Use --cursor ${result.next_cursor}`));
|
|
10122
10250
|
}
|
|
10123
10251
|
} catch (error) {
|
|
10124
10252
|
handleError(error);
|
|
10125
10253
|
}
|
|
10126
10254
|
});
|
|
10127
|
-
|
|
10255
|
+
pentest.command("get").description("Get pentest details").argument("<profile-id>", "Pentest profile ID").showHelpAfterError().addHelpText("after", `
|
|
10128
10256
|
Examples:
|
|
10129
|
-
$ kinosec
|
|
10130
|
-
$ kinosec
|
|
10257
|
+
$ kinosec pentest get abc123
|
|
10258
|
+
$ kinosec pentest get abc123 --json`).action(async (id) => {
|
|
10131
10259
|
try {
|
|
10132
10260
|
const opts = program3.opts();
|
|
10133
10261
|
const { token, apiUrl } = resolveToken(opts);
|
|
@@ -10154,45 +10282,9 @@ Examples:
|
|
|
10154
10282
|
handleError(error);
|
|
10155
10283
|
}
|
|
10156
10284
|
});
|
|
10157
|
-
|
|
10285
|
+
pentest.command("cancel").description("Cancel a running pentest").argument("<profile-id>", "Pentest profile ID").showHelpAfterError().addHelpText("after", `
|
|
10158
10286
|
Examples:
|
|
10159
|
-
$ kinosec
|
|
10160
|
-
$ kinosec scan create --profile def456`).action(async (options) => {
|
|
10161
|
-
if (!options.asset && !options.profile) {
|
|
10162
|
-
console.error("Provide --asset <id> or --profile <id>.");
|
|
10163
|
-
process.exit(2);
|
|
10164
|
-
}
|
|
10165
|
-
const uuidRe = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
10166
|
-
const idValue = options.asset ?? options.profile;
|
|
10167
|
-
if (!uuidRe.test(idValue)) {
|
|
10168
|
-
console.error(`Invalid ID format: "${idValue}"`);
|
|
10169
|
-
console.error("IDs are UUIDs (e.g. 550e8400-e29b-41d4-a716-446655440000).");
|
|
10170
|
-
console.error("Run kinosec asset list to find your asset ID.");
|
|
10171
|
-
process.exit(2);
|
|
10172
|
-
}
|
|
10173
|
-
try {
|
|
10174
|
-
const opts = program3.opts();
|
|
10175
|
-
const { token, apiUrl } = resolveToken(opts);
|
|
10176
|
-
const client = new KinoSecClient({ apiUrl, token });
|
|
10177
|
-
const params = options.profile ? { scan_profile_id: options.profile } : { asset_id: options.asset, scan_mode: options.mode };
|
|
10178
|
-
const result = await client.scans.create(params);
|
|
10179
|
-
if (opts.json || opts.output === "json") {
|
|
10180
|
-
printJson(result);
|
|
10181
|
-
return;
|
|
10182
|
-
}
|
|
10183
|
-
printSuccess(`Scan started: ${result.domain_or_ip}`);
|
|
10184
|
-
console.log(` Profile ID: ${result.scan_profile_id}`);
|
|
10185
|
-
console.log(` Mode: ${result.scan_mode}`);
|
|
10186
|
-
console.log(` Status: ${result.scan_status}`);
|
|
10187
|
-
console.log(`
|
|
10188
|
-
Watch progress: kinosec scan watch ${result.scan_profile_id}`);
|
|
10189
|
-
} catch (error) {
|
|
10190
|
-
handleError(error);
|
|
10191
|
-
}
|
|
10192
|
-
});
|
|
10193
|
-
scan.command("cancel").description("Cancel a running scan").argument("<id>", "Scan profile ID").addHelpText("after", `
|
|
10194
|
-
Examples:
|
|
10195
|
-
$ kinosec scan cancel abc123`).action(async (id) => {
|
|
10287
|
+
$ kinosec pentest cancel abc123`).action(async (id) => {
|
|
10196
10288
|
try {
|
|
10197
10289
|
const opts = program3.opts();
|
|
10198
10290
|
const { token, apiUrl } = resolveToken(opts);
|
|
@@ -10202,16 +10294,16 @@ Examples:
|
|
|
10202
10294
|
printJson(result);
|
|
10203
10295
|
return;
|
|
10204
10296
|
}
|
|
10205
|
-
printSuccess(`
|
|
10297
|
+
printSuccess(`Pentest cancel requested: ${result.scan_status}`);
|
|
10206
10298
|
} catch (error) {
|
|
10207
10299
|
handleError(error);
|
|
10208
10300
|
}
|
|
10209
10301
|
});
|
|
10210
|
-
|
|
10302
|
+
pentest.command("watch").description("Watch pentest progress in real-time").argument("<profile-id>", "Pentest profile ID").showHelpAfterError().option("--timeout <seconds>", "Max time to watch", "1800").option("--interval <seconds>", "Poll interval", "5").addHelpText("after", `
|
|
10211
10303
|
Examples:
|
|
10212
|
-
$ kinosec
|
|
10213
|
-
$ kinosec
|
|
10214
|
-
$ kinosec
|
|
10304
|
+
$ kinosec pentest watch abc123
|
|
10305
|
+
$ kinosec pentest watch abc123 --timeout 600
|
|
10306
|
+
$ kinosec pentest watch abc123 --json`).action(async (id, options) => {
|
|
10215
10307
|
try {
|
|
10216
10308
|
const opts = program3.opts();
|
|
10217
10309
|
const { token, apiUrl } = resolveToken(opts);
|
|
@@ -10223,14 +10315,14 @@ Examples:
|
|
|
10223
10315
|
let spinner = null;
|
|
10224
10316
|
let lastActivity = "";
|
|
10225
10317
|
if (!useJson && isTTY()) {
|
|
10226
|
-
spinner = ora("Fetching
|
|
10318
|
+
spinner = ora("Fetching pentest status...").start();
|
|
10227
10319
|
}
|
|
10228
10320
|
const cleanup = () => {
|
|
10229
10321
|
if (spinner) spinner.stop();
|
|
10230
10322
|
};
|
|
10231
10323
|
process.once("SIGINT", () => {
|
|
10232
10324
|
cleanup();
|
|
10233
|
-
console.log("\nWatch cancelled.
|
|
10325
|
+
console.log("\nWatch cancelled. Pentest is still running.");
|
|
10234
10326
|
process.exit(0);
|
|
10235
10327
|
});
|
|
10236
10328
|
while (true) {
|
|
@@ -10241,7 +10333,7 @@ Examples:
|
|
|
10241
10333
|
process.stdout.write(JSON.stringify({ ...scanDetail, elapsed_seconds: elapsed }) + "\n");
|
|
10242
10334
|
} else if (spinner) {
|
|
10243
10335
|
if (activity && activity !== lastActivity && lastActivity) {
|
|
10244
|
-
spinner.stopAndPersist({ symbol:
|
|
10336
|
+
spinner.stopAndPersist({ symbol: import_picocolors5.default.green("\u2713"), text: lastActivity });
|
|
10245
10337
|
spinner = ora().start();
|
|
10246
10338
|
}
|
|
10247
10339
|
lastActivity = activity || lastActivity;
|
|
@@ -10255,24 +10347,24 @@ Examples:
|
|
|
10255
10347
|
break;
|
|
10256
10348
|
}
|
|
10257
10349
|
if (scanDetail.scan_status.includes("Failed")) {
|
|
10258
|
-
console.log(
|
|
10259
|
-
|
|
10350
|
+
console.log(import_picocolors5.default.red(`
|
|
10351
|
+
Pentest failed: ${scanDetail.failure_reason || "unknown reason"}`));
|
|
10260
10352
|
process.exit(1);
|
|
10261
10353
|
}
|
|
10262
10354
|
if (scanDetail.scan_status.includes("Stopped")) {
|
|
10263
|
-
console.log(
|
|
10355
|
+
console.log(import_picocolors5.default.yellow("\nPentest was cancelled."));
|
|
10264
10356
|
break;
|
|
10265
10357
|
}
|
|
10266
|
-
console.log(
|
|
10267
|
-
|
|
10268
|
-
console.log(` View findings: kinosec finding list
|
|
10358
|
+
console.log(import_picocolors5.default.green(`
|
|
10359
|
+
Pentest completed: ${scanDetail.domain_or_ip}`));
|
|
10360
|
+
console.log(` View findings: kinosec finding list ${id}`);
|
|
10269
10361
|
break;
|
|
10270
10362
|
}
|
|
10271
10363
|
if (Date.now() - startTime > timeoutMs) {
|
|
10272
10364
|
cleanup();
|
|
10273
10365
|
console.error(`
|
|
10274
|
-
Timeout:
|
|
10275
|
-
console.error(`Check status: kinosec
|
|
10366
|
+
Timeout: pentest still running after ${options.timeout}s.`);
|
|
10367
|
+
console.error(`Check status: kinosec pentest get ${id}`);
|
|
10276
10368
|
process.exit(3);
|
|
10277
10369
|
}
|
|
10278
10370
|
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
@@ -10284,7 +10376,7 @@ Timeout: scan still running after ${options.timeout}s.`);
|
|
|
10284
10376
|
}
|
|
10285
10377
|
|
|
10286
10378
|
// src/commands/asset.ts
|
|
10287
|
-
var
|
|
10379
|
+
var import_picocolors6 = __toESM(require_picocolors(), 1);
|
|
10288
10380
|
function registerAssetCommand(program3) {
|
|
10289
10381
|
const asset = program3.command("asset").description("Manage assets");
|
|
10290
10382
|
asset.command("list").description("List assets").option("--limit <n>", "Max results per page", "20").option("--cursor <cursor>", "Pagination cursor").option("--search <term>", "Filter by asset value (substring match)").option("--status <status>", "Filter by status (verified, unverified)").option("--type <type>", "Filter by type (domain, subdomain, ip)").addHelpText("after", `
|
|
@@ -10323,7 +10415,7 @@ Examples:
|
|
|
10323
10415
|
])
|
|
10324
10416
|
);
|
|
10325
10417
|
if (result.next_cursor) {
|
|
10326
|
-
console.log(
|
|
10418
|
+
console.log(import_picocolors6.default.dim(`
|
|
10327
10419
|
More results available. Use --cursor ${result.next_cursor}`));
|
|
10328
10420
|
}
|
|
10329
10421
|
} catch (error) {
|
|
@@ -10434,31 +10526,31 @@ Examples:
|
|
|
10434
10526
|
}
|
|
10435
10527
|
|
|
10436
10528
|
// src/commands/finding.ts
|
|
10437
|
-
var
|
|
10529
|
+
var import_picocolors7 = __toESM(require_picocolors(), 1);
|
|
10438
10530
|
function severityColor(level) {
|
|
10439
10531
|
if (!isTTY()) return level;
|
|
10440
10532
|
switch (level.toLowerCase()) {
|
|
10441
10533
|
case "critical":
|
|
10442
|
-
return
|
|
10534
|
+
return import_picocolors7.default.red(import_picocolors7.default.bold(level));
|
|
10443
10535
|
case "high":
|
|
10444
|
-
return
|
|
10536
|
+
return import_picocolors7.default.red(level);
|
|
10445
10537
|
case "medium":
|
|
10446
|
-
return
|
|
10538
|
+
return import_picocolors7.default.yellow(level);
|
|
10447
10539
|
case "low":
|
|
10448
|
-
return
|
|
10540
|
+
return import_picocolors7.default.blue(level);
|
|
10449
10541
|
case "info":
|
|
10450
|
-
return
|
|
10542
|
+
return import_picocolors7.default.dim(level);
|
|
10451
10543
|
default:
|
|
10452
10544
|
return level;
|
|
10453
10545
|
}
|
|
10454
10546
|
}
|
|
10455
|
-
var VALID_STATUSES = ["open", "fixed", "false_positive", "
|
|
10547
|
+
var VALID_STATUSES = ["open", "fixed", "false_positive", "accepted_risk", "resolved"];
|
|
10456
10548
|
function registerFindingCommand(program3) {
|
|
10457
10549
|
const finding = program3.command("finding").description("Manage findings");
|
|
10458
|
-
finding.command("list").description("List findings").
|
|
10550
|
+
finding.command("list").description("List findings").argument("[profile-id]", "Pentest profile ID").option("--limit <n>", "Max results per page", "20").option("--cursor <cursor>", "Pagination cursor").addHelpText("after", `
|
|
10459
10551
|
Examples:
|
|
10460
|
-
$ kinosec finding list
|
|
10461
|
-
$ kinosec finding list --limit 50 --json`).action(async (options) => {
|
|
10552
|
+
$ kinosec finding list abc123
|
|
10553
|
+
$ kinosec finding list --limit 50 --json`).action(async (profileId, options) => {
|
|
10462
10554
|
try {
|
|
10463
10555
|
const opts = program3.opts();
|
|
10464
10556
|
const { token, apiUrl } = resolveToken(opts);
|
|
@@ -10466,7 +10558,7 @@ Examples:
|
|
|
10466
10558
|
const result = await client.findings.list({
|
|
10467
10559
|
limit: parseInt(options.limit, 10),
|
|
10468
10560
|
cursor: options.cursor,
|
|
10469
|
-
scan:
|
|
10561
|
+
scan: profileId
|
|
10470
10562
|
});
|
|
10471
10563
|
if (opts.json || opts.output === "json") {
|
|
10472
10564
|
printJson(result);
|
|
@@ -10477,17 +10569,16 @@ Examples:
|
|
|
10477
10569
|
return;
|
|
10478
10570
|
}
|
|
10479
10571
|
printTable(
|
|
10480
|
-
["ID", "Name", "Severity", "Status"
|
|
10572
|
+
["ID", "Name", "Severity", "Status"],
|
|
10481
10573
|
result.items.map((f) => [
|
|
10482
10574
|
f.id,
|
|
10483
10575
|
truncate(f.finding_name, 40),
|
|
10484
10576
|
severityColor(f.impact_level),
|
|
10485
|
-
f.status
|
|
10486
|
-
f.scan_phase
|
|
10577
|
+
f.status
|
|
10487
10578
|
])
|
|
10488
10579
|
);
|
|
10489
10580
|
if (result.next_cursor) {
|
|
10490
|
-
console.log(
|
|
10581
|
+
console.log(import_picocolors7.default.dim(`
|
|
10491
10582
|
More results available. Use --cursor ${result.next_cursor}`));
|
|
10492
10583
|
}
|
|
10493
10584
|
} catch (error) {
|
|
@@ -10515,7 +10606,6 @@ Examples:
|
|
|
10515
10606
|
["Severity", severityColor(detail.impact_level)],
|
|
10516
10607
|
["Status", detail.status],
|
|
10517
10608
|
["Verification", detail.verification_status],
|
|
10518
|
-
["Phase", detail.scan_phase],
|
|
10519
10609
|
["Description", truncate(detail.finding_description, 60)],
|
|
10520
10610
|
["Solution", truncate(detail.finding_solution, 60)],
|
|
10521
10611
|
["Created", detail.created_at]
|
|
@@ -10525,7 +10615,7 @@ Examples:
|
|
|
10525
10615
|
handleError(error);
|
|
10526
10616
|
}
|
|
10527
10617
|
});
|
|
10528
|
-
finding.command("update").description("Update finding status").argument("<id>", "Finding ID").requiredOption("--status <status>", "New status: open, fixed, false_positive,
|
|
10618
|
+
finding.command("update").description("Update finding status").argument("<id>", "Finding ID").requiredOption("--status <status>", "New status: open, fixed, false_positive, accepted_risk, resolved").addHelpText("after", `
|
|
10529
10619
|
Examples:
|
|
10530
10620
|
$ kinosec finding update abc123 --status false_positive
|
|
10531
10621
|
$ kinosec finding update abc123 --status fixed`).action(async (id, options) => {
|
|
@@ -10572,7 +10662,7 @@ Examples:
|
|
|
10572
10662
|
}
|
|
10573
10663
|
|
|
10574
10664
|
// src/commands/report.ts
|
|
10575
|
-
var
|
|
10665
|
+
var import_picocolors8 = __toESM(require_picocolors(), 1);
|
|
10576
10666
|
function renderMarkdown(data) {
|
|
10577
10667
|
const { scan, findings } = data;
|
|
10578
10668
|
const lines = [];
|
|
@@ -10651,7 +10741,7 @@ Examples:
|
|
|
10651
10741
|
])
|
|
10652
10742
|
);
|
|
10653
10743
|
if (result.next_cursor) {
|
|
10654
|
-
console.log(
|
|
10744
|
+
console.log(import_picocolors8.default.dim(`
|
|
10655
10745
|
More results available. Use --cursor ${result.next_cursor}`));
|
|
10656
10746
|
}
|
|
10657
10747
|
} catch (error) {
|
|
@@ -10693,10 +10783,10 @@ Examples:
|
|
|
10693
10783
|
handleError(error);
|
|
10694
10784
|
}
|
|
10695
10785
|
});
|
|
10696
|
-
report.command("export").description("Export a
|
|
10786
|
+
report.command("export").description("Export a report").argument("<id>", "Report ID").option("--format <format>", "Export format: json or markdown", "json").addHelpText("after", `
|
|
10697
10787
|
Examples:
|
|
10698
|
-
$ kinosec report export
|
|
10699
|
-
$ kinosec report export
|
|
10788
|
+
$ kinosec report export abc123 --format json > report.json
|
|
10789
|
+
$ kinosec report export abc123 --format markdown > report.md`).action(async (id, options) => {
|
|
10700
10790
|
const validFormats = ["json", "markdown"];
|
|
10701
10791
|
if (!validFormats.includes(options.format)) {
|
|
10702
10792
|
console.error(`Invalid format "${options.format}". Valid: ${validFormats.join(", ")}`);
|
|
@@ -10707,7 +10797,7 @@ Examples:
|
|
|
10707
10797
|
const { token, apiUrl } = resolveToken(opts);
|
|
10708
10798
|
const client = new KinoSecClient({ apiUrl, token });
|
|
10709
10799
|
const result = await client.reports.export({
|
|
10710
|
-
scan_report_id:
|
|
10800
|
+
scan_report_id: id,
|
|
10711
10801
|
format: "json"
|
|
10712
10802
|
});
|
|
10713
10803
|
if (options.format === "markdown" && !(opts.json || opts.output === "json")) {
|
|
@@ -10722,7 +10812,7 @@ Examples:
|
|
|
10722
10812
|
}
|
|
10723
10813
|
|
|
10724
10814
|
// src/commands/token.ts
|
|
10725
|
-
var
|
|
10815
|
+
var import_picocolors9 = __toESM(require_picocolors(), 1);
|
|
10726
10816
|
function registerTokenCommand(program3) {
|
|
10727
10817
|
const token = program3.command("token").description("Manage API tokens");
|
|
10728
10818
|
token.command("list").description("List API tokens").option("--limit <n>", "Max results per page", "20").option("--cursor <cursor>", "Pagination cursor").addHelpText("after", `
|
|
@@ -10757,7 +10847,7 @@ Examples:
|
|
|
10757
10847
|
])
|
|
10758
10848
|
);
|
|
10759
10849
|
if (result.next_cursor) {
|
|
10760
|
-
console.log(
|
|
10850
|
+
console.log(import_picocolors9.default.dim(`
|
|
10761
10851
|
More results available. Use --cursor ${result.next_cursor}`));
|
|
10762
10852
|
}
|
|
10763
10853
|
} catch (error) {
|
|
@@ -10784,7 +10874,7 @@ Examples:
|
|
|
10784
10874
|
}
|
|
10785
10875
|
printSuccess(`Token created: ${result.name}`);
|
|
10786
10876
|
console.log("");
|
|
10787
|
-
console.log(
|
|
10877
|
+
console.log(import_picocolors9.default.bold(" Token: ") + result.token);
|
|
10788
10878
|
console.log("");
|
|
10789
10879
|
printWarning("Save this token \u2014 it will not be shown again.");
|
|
10790
10880
|
console.log(` ID: ${result.id}`);
|
|
@@ -10817,16 +10907,16 @@ Examples:
|
|
|
10817
10907
|
|
|
10818
10908
|
// src/cli.ts
|
|
10819
10909
|
var program2 = new Command();
|
|
10820
|
-
program2.name("kinosec").description("KinoSec CLI \u2014 run pentests from your terminal").version("0.
|
|
10910
|
+
program2.name("kinosec").description("KinoSec CLI \u2014 run pentests from your terminal").version("0.3.1", "-v, --version").option("--token <pat>", "API token (overrides KINOSEC_TOKEN and config)").option("--api-url <url>", "API base URL (overrides config)").option("--output <format>", "Output format: text or json", "text").option("--json", "Shorthand for --output json").option("--no-color", "Disable colors");
|
|
10821
10911
|
registerAuthCommand(program2);
|
|
10822
10912
|
registerConfigCommand(program2);
|
|
10823
10913
|
registerMeCommand(program2);
|
|
10824
|
-
|
|
10914
|
+
registerPentestCommand(program2);
|
|
10825
10915
|
registerAssetCommand(program2);
|
|
10826
10916
|
registerFindingCommand(program2);
|
|
10827
10917
|
registerReportCommand(program2);
|
|
10828
10918
|
registerTokenCommand(program2);
|
|
10829
|
-
program2.parseAsync(process.argv).then(() => checkForUpdate("0.
|
|
10919
|
+
program2.parseAsync(process.argv).then(() => checkForUpdate("0.3.1")).catch(handleError);
|
|
10830
10920
|
/*! Bundled license information:
|
|
10831
10921
|
|
|
10832
10922
|
ky/distribution/index.js:
|