@kinosec/cli 0.10.2 → 0.11.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/dist/cli.js +286 -12
- package/dist/cli.js.map +1 -1
- package/dist/vendor/README.md +134 -0
- package/dist/vendor/ctxpack.py +701 -0
- package/package.json +14 -13
- package/vendor/README.md +134 -0
- package/vendor/ctxpack.py +701 -0
package/dist/cli.js
CHANGED
|
@@ -6732,10 +6732,11 @@ function parseApiError(body, status) {
|
|
|
6732
6732
|
});
|
|
6733
6733
|
}
|
|
6734
6734
|
const b = body;
|
|
6735
|
+
const detail = typeof b.detail === "string" ? b.detail : typeof b.error === "string" ? b.error : "An unexpected error occurred";
|
|
6735
6736
|
return new ApiError({
|
|
6736
6737
|
status: typeof b.status === "number" ? b.status : status,
|
|
6737
6738
|
code: typeof b.code === "string" ? b.code : "unknown_error",
|
|
6738
|
-
detail
|
|
6739
|
+
detail,
|
|
6739
6740
|
requestId: typeof b.request_id === "string" ? b.request_id : "unknown"
|
|
6740
6741
|
});
|
|
6741
6742
|
}
|
|
@@ -8435,13 +8436,10 @@ ${import_picocolors3.default.dim("use of its services.")}
|
|
|
8435
8436
|
var import_picocolors4 = __toESM(require_picocolors(), 1);
|
|
8436
8437
|
|
|
8437
8438
|
// src/lib/validate-mode.ts
|
|
8439
|
+
var VALID_SCAN_MODES = ["full", "quick", "htb", "defcon", "exploit-only"];
|
|
8438
8440
|
function validateScanMode(mode) {
|
|
8439
|
-
if (mode
|
|
8440
|
-
console.error(
|
|
8441
|
-
process.exit(2);
|
|
8442
|
-
}
|
|
8443
|
-
if (mode !== "full") {
|
|
8444
|
-
console.error(`Invalid mode "${mode}". Must be: full`);
|
|
8441
|
+
if (!VALID_SCAN_MODES.includes(mode)) {
|
|
8442
|
+
console.error(`Invalid mode "${mode}". Must be one of: ${VALID_SCAN_MODES.join(", ")}.`);
|
|
8445
8443
|
process.exit(2);
|
|
8446
8444
|
}
|
|
8447
8445
|
}
|
|
@@ -12564,16 +12562,228 @@ function parseTarget(input) {
|
|
|
12564
12562
|
};
|
|
12565
12563
|
}
|
|
12566
12564
|
|
|
12565
|
+
// src/lib/graybox.ts
|
|
12566
|
+
import { spawnSync } from "child_process";
|
|
12567
|
+
import { existsSync as existsSync3, mkdtempSync, readFileSync as readFileSync3, rmSync, statSync } from "fs";
|
|
12568
|
+
import { tmpdir } from "os";
|
|
12569
|
+
import { dirname as dirname3, join as join3 } from "path";
|
|
12570
|
+
import { fileURLToPath } from "url";
|
|
12571
|
+
var SOURCE_BYTES_WARN = 2e5;
|
|
12572
|
+
function findPython() {
|
|
12573
|
+
const candidates = [
|
|
12574
|
+
{ cmd: "python3", args: [] },
|
|
12575
|
+
{ cmd: "py", args: ["-3"] },
|
|
12576
|
+
{ cmd: "python", args: [] }
|
|
12577
|
+
];
|
|
12578
|
+
for (const interp of candidates) {
|
|
12579
|
+
try {
|
|
12580
|
+
const result = spawnSync(interp.cmd, [...interp.args, "--version"], { stdio: "ignore" });
|
|
12581
|
+
if (!result.error && result.status === 0) return interp;
|
|
12582
|
+
} catch {
|
|
12583
|
+
}
|
|
12584
|
+
}
|
|
12585
|
+
return null;
|
|
12586
|
+
}
|
|
12587
|
+
function resolveCtxpackScript() {
|
|
12588
|
+
const here = dirname3(fileURLToPath(import.meta.url));
|
|
12589
|
+
const candidates = [
|
|
12590
|
+
join3(here, "vendor", "ctxpack.py"),
|
|
12591
|
+
// bundled: dist/cli.js -> dist/vendor/
|
|
12592
|
+
join3(here, "..", "vendor", "ctxpack.py"),
|
|
12593
|
+
// published package root -> vendor/
|
|
12594
|
+
join3(here, "..", "..", "vendor", "ctxpack.py")
|
|
12595
|
+
// dev: src/lib -> packages/cli/vendor/
|
|
12596
|
+
];
|
|
12597
|
+
for (const candidate of candidates) {
|
|
12598
|
+
if (existsSync3(candidate)) return candidate;
|
|
12599
|
+
}
|
|
12600
|
+
throw new Error(
|
|
12601
|
+
`Could not locate the vendored ctxpack packer (looked in: ${candidates.join(", ")}). This is a packaging bug \u2014 reinstall @kinosec/cli.`
|
|
12602
|
+
);
|
|
12603
|
+
}
|
|
12604
|
+
var PYTHON_MISSING_MESSAGE = 'Packing --source requires Python 3, but none of "python3", "py -3", or "python" was found on PATH.\nInstall Python 3 (https://www.python.org/downloads/) and re-run, or pass an already-packed bundle: --source <bundle.txt>.';
|
|
12605
|
+
async function packSource(source, options) {
|
|
12606
|
+
if (!existsSync3(source)) {
|
|
12607
|
+
throw new Error(`--source path not found: ${source}`);
|
|
12608
|
+
}
|
|
12609
|
+
const stats = statSync(source);
|
|
12610
|
+
if (stats.isFile() && source.toLowerCase().endsWith(".txt")) {
|
|
12611
|
+
const bundleText = readFileSync3(source, "utf8");
|
|
12612
|
+
return {
|
|
12613
|
+
bundleText,
|
|
12614
|
+
bytes: Buffer.byteLength(bundleText, "utf8"),
|
|
12615
|
+
mode: null,
|
|
12616
|
+
stack: sniffBundleStack(bundleText),
|
|
12617
|
+
manifest: null,
|
|
12618
|
+
stderr: "",
|
|
12619
|
+
premade: true
|
|
12620
|
+
};
|
|
12621
|
+
}
|
|
12622
|
+
const python = findPython();
|
|
12623
|
+
if (!python) {
|
|
12624
|
+
throw new Error(PYTHON_MISSING_MESSAGE);
|
|
12625
|
+
}
|
|
12626
|
+
const script = resolveCtxpackScript();
|
|
12627
|
+
const outDir = mkdtempSync(join3(tmpdir(), "kinosec-ctxpack-"));
|
|
12628
|
+
try {
|
|
12629
|
+
const args = [...python.args, script, source, "--budget", String(options.budget), "-o", outDir];
|
|
12630
|
+
if (options.zipPassword) {
|
|
12631
|
+
args.push("--zip-password", options.zipPassword);
|
|
12632
|
+
}
|
|
12633
|
+
const proc = spawnSync(python.cmd, args, { encoding: "utf8" });
|
|
12634
|
+
if (proc.error) {
|
|
12635
|
+
throw new Error(`Failed to run ctxpack via ${python.cmd}: ${proc.error.message}`);
|
|
12636
|
+
}
|
|
12637
|
+
if (proc.status !== 0) {
|
|
12638
|
+
const detail = (proc.stderr || "").trim();
|
|
12639
|
+
throw new Error(
|
|
12640
|
+
`ctxpack failed to pack --source (exit ${proc.status ?? "unknown"}).` + (detail ? `
|
|
12641
|
+
${detail}` : "") + (looksLikeAesZip(detail) ? '\nHint: only classic ZipCrypto zips are supported (HTB handouts use "hackthebox"), not WinZip/AES-256. Re-create the archive with classic encryption, or extract it and pass the directory.' : "")
|
|
12642
|
+
);
|
|
12643
|
+
}
|
|
12644
|
+
const bundlePath = join3(outDir, "bundle.txt");
|
|
12645
|
+
if (!existsSync3(bundlePath)) {
|
|
12646
|
+
throw new Error(`ctxpack produced no bundle (${bundlePath} missing).`);
|
|
12647
|
+
}
|
|
12648
|
+
const bundleText = readFileSync3(bundlePath, "utf8");
|
|
12649
|
+
let manifest = null;
|
|
12650
|
+
try {
|
|
12651
|
+
manifest = JSON.parse(readFileSync3(join3(outDir, "manifest.json"), "utf8"));
|
|
12652
|
+
} catch {
|
|
12653
|
+
manifest = null;
|
|
12654
|
+
}
|
|
12655
|
+
const stack = manifest?.signals?.stack ? String(manifest.signals.stack) : null;
|
|
12656
|
+
const mode = manifest?.mode ? String(manifest.mode) : null;
|
|
12657
|
+
return {
|
|
12658
|
+
bundleText,
|
|
12659
|
+
bytes: Buffer.byteLength(bundleText, "utf8"),
|
|
12660
|
+
mode,
|
|
12661
|
+
stack,
|
|
12662
|
+
manifest,
|
|
12663
|
+
stderr: proc.stderr || "",
|
|
12664
|
+
premade: false
|
|
12665
|
+
};
|
|
12666
|
+
} finally {
|
|
12667
|
+
try {
|
|
12668
|
+
rmSync(outDir, { recursive: true, force: true });
|
|
12669
|
+
} catch {
|
|
12670
|
+
}
|
|
12671
|
+
}
|
|
12672
|
+
}
|
|
12673
|
+
function looksLikeAesZip(stderr) {
|
|
12674
|
+
const lc = stderr.toLowerCase();
|
|
12675
|
+
return lc.includes("compression type 99") || lc.includes("aes") || lc.includes("winzip");
|
|
12676
|
+
}
|
|
12677
|
+
function sniffBundleStack(text) {
|
|
12678
|
+
const lc = text.toLowerCase();
|
|
12679
|
+
if (/"next"|next\.config|\/_next\//.test(lc)) return "nextjs";
|
|
12680
|
+
if (/"express"/.test(lc)) return "express";
|
|
12681
|
+
if (/from flask|import flask/.test(lc)) return "flask";
|
|
12682
|
+
if (/<\?php/.test(lc)) return "php";
|
|
12683
|
+
return null;
|
|
12684
|
+
}
|
|
12685
|
+
function matchLiveStack(lowercased) {
|
|
12686
|
+
if (lowercased.includes("x-powered-by: next.js") || lowercased.includes("/_next/") || lowercased.includes("__next_data__")) {
|
|
12687
|
+
return "nextjs";
|
|
12688
|
+
}
|
|
12689
|
+
if (lowercased.includes("x-powered-by: express")) return "express";
|
|
12690
|
+
if (lowercased.includes("x-powered-by: php") || lowercased.includes("phpsessid")) return "php";
|
|
12691
|
+
if (lowercased.includes("server: werkzeug")) return "flask";
|
|
12692
|
+
if (lowercased.includes("csrftoken")) return "django";
|
|
12693
|
+
return null;
|
|
12694
|
+
}
|
|
12695
|
+
var COMPATIBLE_STACKS = /* @__PURE__ */ new Set([
|
|
12696
|
+
"nextjs/react",
|
|
12697
|
+
"react/nextjs",
|
|
12698
|
+
"nextjs/node",
|
|
12699
|
+
"node/nextjs",
|
|
12700
|
+
"express/node",
|
|
12701
|
+
"node/express"
|
|
12702
|
+
]);
|
|
12703
|
+
function stacksConflict(bundleStack, liveStack) {
|
|
12704
|
+
if (!bundleStack || !liveStack) return false;
|
|
12705
|
+
if (bundleStack === liveStack) return false;
|
|
12706
|
+
if (COMPATIBLE_STACKS.has(`${bundleStack}/${liveStack}`)) return false;
|
|
12707
|
+
return true;
|
|
12708
|
+
}
|
|
12709
|
+
async function probeLiveStack(host, timeoutMs = 8e3) {
|
|
12710
|
+
for (const scheme of ["https", "http"]) {
|
|
12711
|
+
let res;
|
|
12712
|
+
const controller = new AbortController();
|
|
12713
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
12714
|
+
try {
|
|
12715
|
+
res = await fetch(`${scheme}://${host}/`, { signal: controller.signal, redirect: "follow" });
|
|
12716
|
+
} catch {
|
|
12717
|
+
continue;
|
|
12718
|
+
} finally {
|
|
12719
|
+
clearTimeout(timer);
|
|
12720
|
+
}
|
|
12721
|
+
const headerBlob = [...res.headers].map(([key, value]) => `${key}: ${value}`).join("\n");
|
|
12722
|
+
let body = "";
|
|
12723
|
+
try {
|
|
12724
|
+
body = (await res.text()).slice(0, 4096);
|
|
12725
|
+
} catch {
|
|
12726
|
+
}
|
|
12727
|
+
return matchLiveStack(`${headerBlob}
|
|
12728
|
+
${body}`.toLowerCase());
|
|
12729
|
+
}
|
|
12730
|
+
return null;
|
|
12731
|
+
}
|
|
12732
|
+
|
|
12567
12733
|
// src/commands/pentest.ts
|
|
12734
|
+
var DEFAULT_SOURCE_BUDGET = 4e4;
|
|
12735
|
+
function reportPackResult(result, budget) {
|
|
12736
|
+
const size = `${result.bytes.toLocaleString()} bytes`;
|
|
12737
|
+
if (result.premade) {
|
|
12738
|
+
console.error(`Graybox: using pre-made source bundle (${size}).`);
|
|
12739
|
+
} else {
|
|
12740
|
+
for (const line of result.stderr.split("\n")) {
|
|
12741
|
+
const trimmed = line.trim();
|
|
12742
|
+
if (trimmed.startsWith("ctxpack: mode=")) {
|
|
12743
|
+
console.error(`Graybox: ${trimmed.slice("ctxpack: ".length)} [${size}]`);
|
|
12744
|
+
}
|
|
12745
|
+
}
|
|
12746
|
+
}
|
|
12747
|
+
if (result.bytes > SOURCE_BYTES_WARN) {
|
|
12748
|
+
printWarning(
|
|
12749
|
+
`Source bundle is ${size} \u2014 that's a lot of prompt context. It will still be delivered (the server ships oversize source out-of-band), but large bundles raise per-scan cost and can dilute the agent's focus. Lower --budget (currently ${budget}) or trim --source to tighten it.`
|
|
12750
|
+
);
|
|
12751
|
+
}
|
|
12752
|
+
}
|
|
12753
|
+
function ensurePasteBundle(result) {
|
|
12754
|
+
if (result.premade || result.mode === "paste") return;
|
|
12755
|
+
if (result.mode === "retrieve") {
|
|
12756
|
+
printWarning(
|
|
12757
|
+
"ctxpack fell back to retrieve mode: the source is too large to paste within --budget, so the bundle is a path outline with no code \u2014 useless as graybox context."
|
|
12758
|
+
);
|
|
12759
|
+
console.error(
|
|
12760
|
+
"Not sending it. Raise --budget to re-pack the source as a full paste bundle, or trim --source to the exploit-relevant files (or run a normal black-box scan without --source)."
|
|
12761
|
+
);
|
|
12762
|
+
process.exit(1);
|
|
12763
|
+
}
|
|
12764
|
+
}
|
|
12765
|
+
function isUserDataTooLarge(err) {
|
|
12766
|
+
return err.status === 413 || /user-?data too large/i.test(err.detail);
|
|
12767
|
+
}
|
|
12568
12768
|
function registerPentestCommand(program3) {
|
|
12569
|
-
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", `
|
|
12769
|
+
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, quick, htb, defcon, or exploit-only", "full").option("--flag <prefix>", "CTF flag prefix (defcon mode only, default HTB)").option("--context <text>", "CTF focus hint (defcon mode only)").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").option("--source <path>", "Graybox source handout: a directory, an archive (.zip/.tar/.tgz), or a pre-made bundle .txt. Its presence enables graybox").option("--budget <tokens>", "Token budget for packing --source (how much source to pack; larger = more context and cost)", String(DEFAULT_SOURCE_BUDGET)).option("--zip-password <pw>", "Password for an encrypted --source .zip (classic ZipCrypto only; HTB handouts use: hackthebox)").option("--force", "Proceed even when the --source stack looks unlike the live target").addHelpText("after", `
|
|
12570
12770
|
Examples:
|
|
12571
12771
|
$ kinosec pentest -t delikado.org
|
|
12572
12772
|
$ kinosec pentest --profile abc123
|
|
12573
12773
|
$ kinosec pentest -t internal.corp --deployment on-prem
|
|
12574
12774
|
$ kinosec pentest -t example.com --include api.example.com,app.example.com
|
|
12575
12775
|
$ kinosec pentest -t example.com --exclude staging.example.com
|
|
12576
|
-
$ kinosec pentest -t example.com --scope clear
|
|
12776
|
+
$ kinosec pentest -t example.com --scope clear
|
|
12777
|
+
$ kinosec pentest -t juice.example.com --mode exploit-only
|
|
12778
|
+
$ kinosec pentest -t target.htb --mode htb --source ./challenge.zip --zip-password hackthebox
|
|
12779
|
+
|
|
12780
|
+
Graybox (--source):
|
|
12781
|
+
Packs the target's source (paste-mode only) and folds it into the agent as authoritative
|
|
12782
|
+
context. Works with --mode full and quick for any user's own asset; htb/defcon additionally
|
|
12783
|
+
require admin access. Large source is delivered out-of-band, so there's no size ceiling to
|
|
12784
|
+
fight \u2014 --budget just controls how much source is packed (bigger bundles add context and cost).
|
|
12785
|
+
The source is sent over HTTPS, used only on the ephemeral scan droplet, and is NOT stored
|
|
12786
|
+
server-side (the training log records just a "[GRAYBOX] N bytes injected" marker, never the source).`).action(async (options) => {
|
|
12577
12787
|
if (!options.target && !options.profile) {
|
|
12578
12788
|
console.error("Provide --target <domain> or --profile <id>.");
|
|
12579
12789
|
process.exit(2);
|
|
@@ -12586,6 +12796,16 @@ Examples:
|
|
|
12586
12796
|
process.exit(2);
|
|
12587
12797
|
}
|
|
12588
12798
|
validateScanMode(options.mode);
|
|
12799
|
+
let sourceBudget = DEFAULT_SOURCE_BUDGET;
|
|
12800
|
+
if (options.source) {
|
|
12801
|
+
sourceBudget = parseInt(options.budget, 10);
|
|
12802
|
+
if (!Number.isInteger(sourceBudget) || sourceBudget <= 0) {
|
|
12803
|
+
console.error("Invalid --budget. Must be a positive integer (tokens).");
|
|
12804
|
+
process.exit(2);
|
|
12805
|
+
}
|
|
12806
|
+
} else if (options.zipPassword || options.force) {
|
|
12807
|
+
console.error("Warning: --zip-password/--force apply only with --source; ignoring.");
|
|
12808
|
+
}
|
|
12589
12809
|
const scopeClear = options.scope === "clear";
|
|
12590
12810
|
if (options.scope && options.scope !== "clear") {
|
|
12591
12811
|
console.error("Invalid --scope value. Must be: clear");
|
|
@@ -12604,6 +12824,15 @@ Examples:
|
|
|
12604
12824
|
const opts = program3.opts();
|
|
12605
12825
|
const { token, apiUrl } = resolveToken(opts);
|
|
12606
12826
|
const client = new KinoSecClient({ apiUrl, token });
|
|
12827
|
+
let packResult = null;
|
|
12828
|
+
if (options.source) {
|
|
12829
|
+
packResult = await packSource(options.source, {
|
|
12830
|
+
budget: sourceBudget,
|
|
12831
|
+
zipPassword: options.zipPassword
|
|
12832
|
+
});
|
|
12833
|
+
reportPackResult(packResult, sourceBudget);
|
|
12834
|
+
ensurePasteBundle(packResult);
|
|
12835
|
+
}
|
|
12607
12836
|
let targetDisplay;
|
|
12608
12837
|
let params;
|
|
12609
12838
|
if (options.profile) {
|
|
@@ -12642,6 +12871,12 @@ Examples:
|
|
|
12642
12871
|
asset_id: asset.id,
|
|
12643
12872
|
scan_mode: options.mode
|
|
12644
12873
|
};
|
|
12874
|
+
if (options.mode === "defcon") {
|
|
12875
|
+
assetParams.flag_format = options.flag ?? "HTB";
|
|
12876
|
+
if (options.context) assetParams.ctf_context = options.context;
|
|
12877
|
+
} else if (options.flag || options.context) {
|
|
12878
|
+
console.error("Warning: --flag/--context apply only to --mode defcon; ignoring.");
|
|
12879
|
+
}
|
|
12645
12880
|
if (options.deployment !== "cloud") {
|
|
12646
12881
|
assetParams.deployment_type = options.deployment;
|
|
12647
12882
|
}
|
|
@@ -12681,8 +12916,47 @@ Examples:
|
|
|
12681
12916
|
}
|
|
12682
12917
|
displayScope(asset);
|
|
12683
12918
|
}
|
|
12919
|
+
if (packResult?.stack && !options.force) {
|
|
12920
|
+
const liveStack = await probeLiveStack(targetDisplay);
|
|
12921
|
+
if (liveStack && stacksConflict(packResult.stack, liveStack)) {
|
|
12922
|
+
printWarning(
|
|
12923
|
+
`Source handout looks like "${packResult.stack}", but ${targetDisplay} responds like "${liveStack}".`
|
|
12924
|
+
);
|
|
12925
|
+
printWarning("The source may belong to a different app; graybox context could mislead the pentest.");
|
|
12926
|
+
if (options.yes) {
|
|
12927
|
+
console.error("Proceeding anyway (--yes).");
|
|
12928
|
+
} else {
|
|
12929
|
+
console.error("Aborting. Re-run with --force to override, or fix --source.");
|
|
12930
|
+
process.exit(2);
|
|
12931
|
+
}
|
|
12932
|
+
}
|
|
12933
|
+
}
|
|
12684
12934
|
await confirmAuthorization(targetDisplay, Boolean(options.yes));
|
|
12685
|
-
|
|
12935
|
+
if (packResult) {
|
|
12936
|
+
params.source_context = packResult.bundleText;
|
|
12937
|
+
}
|
|
12938
|
+
let result;
|
|
12939
|
+
try {
|
|
12940
|
+
result = await client.scans.create(params);
|
|
12941
|
+
} catch (err) {
|
|
12942
|
+
if (err instanceof ApiError) {
|
|
12943
|
+
if (packResult && isUserDataTooLarge(err)) {
|
|
12944
|
+
printWarning(`Server rejected the scan: ${err.detail}`);
|
|
12945
|
+
console.error(
|
|
12946
|
+
`The graybox source made the provisioning payload too large. Re-pack with a smaller --budget (currently ${sourceBudget}) or trim --source, then retry.`
|
|
12947
|
+
);
|
|
12948
|
+
process.exit(1);
|
|
12949
|
+
}
|
|
12950
|
+
if (err.status === 403 && (options.mode === "htb" || options.mode === "defcon")) {
|
|
12951
|
+
printWarning(`The "${options.mode}" scan mode requires admin access (server returned 403).`);
|
|
12952
|
+
console.error(
|
|
12953
|
+
"Run with --mode full or --mode quick (which support --source for your own assets), or ask your KinoSec admin to enable CTF modes for your account."
|
|
12954
|
+
);
|
|
12955
|
+
process.exit(77);
|
|
12956
|
+
}
|
|
12957
|
+
}
|
|
12958
|
+
throw err;
|
|
12959
|
+
}
|
|
12686
12960
|
const isOnPrem = result.deployment_type === "on_prem";
|
|
12687
12961
|
if (opts.json || opts.output === "json") {
|
|
12688
12962
|
printJson(result);
|
|
@@ -13790,7 +14064,7 @@ Examples:
|
|
|
13790
14064
|
|
|
13791
14065
|
// src/cli.ts
|
|
13792
14066
|
var program2 = new Command();
|
|
13793
|
-
program2.name("kinosec").description("KinoSec CLI \u2014 run pentests from your terminal").version("0.
|
|
14067
|
+
program2.name("kinosec").description("KinoSec CLI \u2014 run pentests from your terminal").version("0.11.1", "-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");
|
|
13794
14068
|
registerAuthCommand(program2);
|
|
13795
14069
|
registerMeCommand(program2);
|
|
13796
14070
|
registerCreditsCommand(program2);
|
|
@@ -13799,7 +14073,7 @@ registerAssetCommand(program2);
|
|
|
13799
14073
|
registerFindingCommand(program2);
|
|
13800
14074
|
registerReportCommand(program2);
|
|
13801
14075
|
registerTokenCommand(program2);
|
|
13802
|
-
program2.parseAsync(process.argv).then(() => checkForUpdate("0.
|
|
14076
|
+
program2.parseAsync(process.argv).then(() => checkForUpdate("0.11.1")).catch(handleError);
|
|
13803
14077
|
/*! Bundled license information:
|
|
13804
14078
|
|
|
13805
14079
|
ky/distribution/index.js:
|