kalibur 0.1.0__tar.gz

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.
kalibur-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,89 @@
1
+ Metadata-Version: 2.4
2
+ Name: kalibur
3
+ Version: 0.1.0
4
+ Summary: AI whitebox pentest assistant
5
+ Author-email: Scapin Ltd <contact@scapin.co.uk>
6
+ License: MIT
7
+ Project-URL: Homepage, https://kalibur.ai
8
+ Requires-Python: >=3.9
9
+ Description-Content-Type: text/markdown
10
+ Requires-Dist: certifi>=2024.0.0
11
+
12
+ # kalibur
13
+
14
+ AI-powered whitebox security scanner. Run `kalibur scan` against any local codebase.
15
+
16
+ ## Requirements
17
+
18
+ - Python 3.9 or later
19
+ - A Kalibur API key (get one at kalibur.ai)
20
+
21
+ ## Installation
22
+
23
+ **macOS**
24
+ ```bash
25
+ brew install kalibur
26
+ ```
27
+
28
+ **Linux / Windows**
29
+ ```bash
30
+ pip install kalibur
31
+ ```
32
+
33
+ ## Usage
34
+
35
+ ```bash
36
+ export KALIBUR_API_KEY=your_api_key
37
+
38
+ # Scan current directory
39
+ kalibur scan
40
+
41
+ # Scan a specific directory
42
+ kalibur scan -t /path/to/project
43
+ ```
44
+
45
+ ## Configuration
46
+
47
+ | Flag | Env var | Description |
48
+ |------|---------|-------------|
49
+ | `-k KEY` | `KALIBUR_API_KEY` | API key (required) |
50
+ | `-t PATH` | | Directory to scan (default: `.`) |
51
+
52
+ ## Output
53
+
54
+ Results are written to a timestamped subfolder inside a `kalibur/` directory in the scanned project:
55
+
56
+ ```
57
+ <target>/kalibur/
58
+ 2026-05-03_14-23-45/
59
+ report.md
60
+ ```
61
+
62
+ Nothing is written if no findings are detected.
63
+
64
+ Add `kalibur/` to your `.gitignore` to keep scan output out of version control.
65
+
66
+ ## Generating a PDF report
67
+
68
+ After reviewing and triaging findings in `report.md`, generate a branded PDF:
69
+
70
+ ```bash
71
+ kalibur report \
72
+ -r ~/project/kalibur/2026-05-03_14-23-45/report.md \
73
+ -f "Your Firm" \
74
+ -c "Client Name" \
75
+ -a "Your Name" \
76
+ -l ./logo.png \
77
+ -v "1.0"
78
+ ```
79
+
80
+ | Flag | Description |
81
+ |------|-------------|
82
+ | `-r FILE` | Path to `report.md` (required) |
83
+ | `-f NAME` | Assessor firm name (required) |
84
+ | `-c NAME` | Client name (required) |
85
+ | `-a NAME` | Assessor name (required) |
86
+ | `-l FILE` | Logo file, `.png` or `.jpg` (required) |
87
+ | `-v VER` | Report version, e.g. `1.0` (required) |
88
+
89
+ The PDF is saved as `report.pdf` alongside `report.md`. Valid triage statuses in `report.md` are `Open` and `Resolved`.
@@ -0,0 +1,78 @@
1
+ # kalibur
2
+
3
+ AI-powered whitebox security scanner. Run `kalibur scan` against any local codebase.
4
+
5
+ ## Requirements
6
+
7
+ - Python 3.9 or later
8
+ - A Kalibur API key (get one at kalibur.ai)
9
+
10
+ ## Installation
11
+
12
+ **macOS**
13
+ ```bash
14
+ brew install kalibur
15
+ ```
16
+
17
+ **Linux / Windows**
18
+ ```bash
19
+ pip install kalibur
20
+ ```
21
+
22
+ ## Usage
23
+
24
+ ```bash
25
+ export KALIBUR_API_KEY=your_api_key
26
+
27
+ # Scan current directory
28
+ kalibur scan
29
+
30
+ # Scan a specific directory
31
+ kalibur scan -t /path/to/project
32
+ ```
33
+
34
+ ## Configuration
35
+
36
+ | Flag | Env var | Description |
37
+ |------|---------|-------------|
38
+ | `-k KEY` | `KALIBUR_API_KEY` | API key (required) |
39
+ | `-t PATH` | | Directory to scan (default: `.`) |
40
+
41
+ ## Output
42
+
43
+ Results are written to a timestamped subfolder inside a `kalibur/` directory in the scanned project:
44
+
45
+ ```
46
+ <target>/kalibur/
47
+ 2026-05-03_14-23-45/
48
+ report.md
49
+ ```
50
+
51
+ Nothing is written if no findings are detected.
52
+
53
+ Add `kalibur/` to your `.gitignore` to keep scan output out of version control.
54
+
55
+ ## Generating a PDF report
56
+
57
+ After reviewing and triaging findings in `report.md`, generate a branded PDF:
58
+
59
+ ```bash
60
+ kalibur report \
61
+ -r ~/project/kalibur/2026-05-03_14-23-45/report.md \
62
+ -f "Your Firm" \
63
+ -c "Client Name" \
64
+ -a "Your Name" \
65
+ -l ./logo.png \
66
+ -v "1.0"
67
+ ```
68
+
69
+ | Flag | Description |
70
+ |------|-------------|
71
+ | `-r FILE` | Path to `report.md` (required) |
72
+ | `-f NAME` | Assessor firm name (required) |
73
+ | `-c NAME` | Client name (required) |
74
+ | `-a NAME` | Assessor name (required) |
75
+ | `-l FILE` | Logo file, `.png` or `.jpg` (required) |
76
+ | `-v VER` | Report version, e.g. `1.0` (required) |
77
+
78
+ The PDF is saved as `report.pdf` alongside `report.md`. Valid triage statuses in `report.md` are `Open` and `Resolved`.
File without changes
@@ -0,0 +1,320 @@
1
+ import argparse
2
+ import getpass
3
+ import os
4
+ import ssl
5
+ import sys
6
+ import urllib.error
7
+ import urllib.request
8
+ from pathlib import Path
9
+
10
+ import certifi
11
+
12
+ from .end import run_end
13
+ from .pull import run_pull
14
+ from .push import run_push, _detect_project
15
+ from .report import run_report
16
+ from .scan import run_scan, print_banner, _USE_COLOR, _DIM, _RESET
17
+
18
+ API_URL = os.environ.get("KALIBUR_API_URL", "https://kalibur.ai")
19
+ _CONFIG_PATH = Path.home() / ".config" / "kalibur" / "key"
20
+
21
+ _RED = "\033[31m" if _USE_COLOR else ""
22
+ _RST = "\033[0m" if _USE_COLOR else ""
23
+
24
+
25
+ def _error(message: str) -> None:
26
+ print(f"{_RED}Error: {message}{_RST}", file=sys.stderr)
27
+ sys.exit(1)
28
+
29
+
30
+ def _load_saved_key() -> str | None:
31
+ try:
32
+ if _CONFIG_PATH.exists():
33
+ key = _CONFIG_PATH.read_text().strip()
34
+ return key if key else None
35
+ except OSError:
36
+ pass
37
+ return None
38
+
39
+
40
+ def _resolve_api_key(flag_value: str | None) -> str | None:
41
+ return flag_value or os.environ.get("KALIBUR_API_KEY") or _load_saved_key()
42
+
43
+
44
+ _KEY_ERROR = "No API key found. Run 'kalibur login' to save your key, or pass it with -k <key>."
45
+
46
+
47
+ def _print_usage(desc: str, usage: str, flags: list[tuple[str, str]]) -> None:
48
+ lines: list[tuple[str, str]] = [("Usage:", usage), ("", "")] + flags
49
+ if _USE_COLOR:
50
+ print(f"{desc}\n")
51
+ for flag, desc_text in lines:
52
+ if flag == "Usage:":
53
+ print(f"{_DIM}{flag}{_RESET} {desc_text}")
54
+ elif flag == "":
55
+ print()
56
+ else:
57
+ print(f" \033[1m{flag}{_RESET} {desc_text}")
58
+ else:
59
+ print(f"{desc}\n")
60
+ for flag, desc_text in lines:
61
+ if flag == "":
62
+ print()
63
+ elif flag == "Usage:":
64
+ print(f"{flag} {desc_text}")
65
+ else:
66
+ print(f" {flag} {desc_text}")
67
+ print()
68
+
69
+
70
+ _KEY_FLAG = ("-k", "Kalibur API key (overrides saved key and KALIBUR_API_KEY)")
71
+ _PROJECT_FLAG = ("-e", "Project name (skip interactive picker)")
72
+
73
+ _COMMAND_HELP: dict[str, tuple[str, str, list[tuple[str, str]]]] = {
74
+ "login": (
75
+ "Save your Kalibur API key locally so you do not need to pass -k or set KALIBUR_API_KEY on every command.",
76
+ "kalibur login",
77
+ [],
78
+ ),
79
+ "scan": (
80
+ "Analyse the codebase in the current directory for security vulnerabilities. Kalibur thinks like an attacker, finds complex exploitable paths most scanners would miss, and generates a full markdown report ready for manual validation.",
81
+ "kalibur scan [-t <path>] [-k <key>]",
82
+ [
83
+ ("-t", "Directory to scan (default: .)"),
84
+ _KEY_FLAG,
85
+ ],
86
+ ),
87
+ "report": (
88
+ "Generate a PDF pentest report from a completed report.md. Kalibur proofreads the findings, generates a relevant executive summary automatically, and produces a client-ready branded PDF.",
89
+ "kalibur report -r <report.md> -f <firm> -a <assessor> -c <client> -l <logo> -v <version>",
90
+ [
91
+ ("-r", "Path to report.md"),
92
+ ("-f", "Assessor firm name"),
93
+ ("-a", "Assessor name"),
94
+ ("-c", "Client name"),
95
+ ("-l", "Logo file (.png or .jpg)"),
96
+ ("-v", "Report version (e.g. 1.0)"),
97
+ _KEY_FLAG,
98
+ ],
99
+ ),
100
+ "push": (
101
+ "Save a snapshot of your local ./kalibur/ folder. Use this to back up your local edits between sessions or share them with teammates.",
102
+ "kalibur push [-k <key>]",
103
+ [_KEY_FLAG],
104
+ ),
105
+ "pull": (
106
+ "Restore a previously saved snapshot to your local ./kalibur/ folder. Useful for resuming work on another machine or recovering a backup.",
107
+ "kalibur pull [-e <project>] [-k <key>]",
108
+ [_PROJECT_FLAG, _KEY_FLAG],
109
+ ),
110
+ "end": (
111
+ "End an engagement and permanently delete all its data: remote scans, findings, snapshots, and the local ./kalibur/ folder. This cannot be undone.",
112
+ "kalibur end [-e <project>] [-k <key>]",
113
+ [_PROJECT_FLAG, _KEY_FLAG],
114
+ ),
115
+ }
116
+
117
+
118
+ def _print_report_usage() -> None:
119
+ desc, usage, flags = _COMMAND_HELP["report"]
120
+ _print_usage(desc, usage, flags)
121
+
122
+
123
+ def _print_help() -> None:
124
+ commands = [
125
+ ("login", "Save your API key locally"),
126
+ ("scan", "Run a whitebox security assessment of the current directory"),
127
+ ("report", "Generate a PDF pentest report from a report.md file"),
128
+ ("push", "Create a snapshot of your local kalibur folder"),
129
+ ("pull", "Restore a snapshot to your local kalibur folder"),
130
+ ("end", "End an engagement and permanently delete all its data"),
131
+ ]
132
+ if _USE_COLOR:
133
+ print(f"{_DIM}Usage:{_RESET} kalibur <command> [options]\n")
134
+ print("Commands:\n")
135
+ for name, desc in commands:
136
+ print(f" \033[1m{name:<8}{_RESET} {desc}")
137
+ print(f"\n{_DIM}Run 'kalibur <command> --help' for options.{_RESET}")
138
+ else:
139
+ print("Usage: kalibur <command> [options]\n")
140
+ print("Commands:\n")
141
+ for name, desc in commands:
142
+ print(f" {name:<8} {desc}")
143
+ print("\nRun 'kalibur <command> --help' for options.")
144
+ print()
145
+
146
+
147
+ def _validate_key(key: str) -> str | None:
148
+ ssl_ctx = ssl.create_default_context(cafile=certifi.where())
149
+ req = urllib.request.Request(
150
+ f"{API_URL}/api/cli/me",
151
+ method="GET",
152
+ headers={"Authorization": f"Bearer {key}", "User-Agent": "kalibur-cli"},
153
+ )
154
+ try:
155
+ with urllib.request.urlopen(req, timeout=10, context=ssl_ctx) as resp:
156
+ import json
157
+ data = json.loads(resp.read())
158
+ return data.get("name")
159
+ except urllib.error.HTTPError as e:
160
+ if e.code == 401:
161
+ return None
162
+ raise
163
+ except Exception:
164
+ raise
165
+
166
+
167
+ def run_login() -> None:
168
+ _GREEN = "\033[32m" if _USE_COLOR else ""
169
+ try:
170
+ key = getpass.getpass("Kalibur API key: ").strip()
171
+ except (EOFError, KeyboardInterrupt):
172
+ print()
173
+ print(f"{_RED}Cancelled.{_RST}", file=sys.stderr)
174
+ sys.exit(130)
175
+ if not key:
176
+ _error("API key cannot be empty")
177
+ print()
178
+ try:
179
+ name = _validate_key(key)
180
+ except Exception as e:
181
+ _error(f"Could not reach Kalibur API: {e}")
182
+ if name is None:
183
+ _error("Invalid API key")
184
+ _CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)
185
+ _CONFIG_PATH.write_text(key)
186
+ _CONFIG_PATH.chmod(0o600)
187
+ print(f"{_GREEN}Logged in. API key saved to {_CONFIG_PATH}{_RST}")
188
+
189
+
190
+ class _Parser(argparse.ArgumentParser):
191
+ def error(self, message: str) -> None:
192
+ sys.stdout.flush()
193
+ self.print_usage(sys.stderr)
194
+ print(f"\n{_RED}Error: {message}{_RST}", file=sys.stderr)
195
+ sys.exit(2)
196
+
197
+
198
+ def main() -> None:
199
+ print_banner(Path(".").resolve())
200
+ sys.stdout.flush()
201
+
202
+ if len(sys.argv) == 1 or (len(sys.argv) == 2 and sys.argv[1] in ("-h", "--help")):
203
+ _print_help()
204
+ sys.exit(0)
205
+
206
+ if (
207
+ len(sys.argv) >= 3
208
+ and sys.argv[1] in _COMMAND_HELP
209
+ and sys.argv[2] in ("-h", "--help")
210
+ ):
211
+ desc, usage, flags = _COMMAND_HELP[sys.argv[1]]
212
+ _print_usage(desc, usage, flags)
213
+ sys.exit(0)
214
+
215
+ if len(sys.argv) == 2 and sys.argv[1] == "login":
216
+ run_login()
217
+ sys.exit(0)
218
+
219
+ parser = _Parser(prog="kalibur", description="AI whitebox pentest assistant")
220
+ sub = parser.add_subparsers(dest="command", required=True)
221
+
222
+ sub.add_parser("login", help="Save your API key locally")
223
+
224
+ scan_cmd = sub.add_parser("scan", help="Run a whitebox assessment of the current directory")
225
+ scan_cmd.add_argument("-t", default=".", metavar="PATH", dest="target", help="Directory to scan (default: .)")
226
+ scan_cmd.add_argument("-k", metavar="KEY", dest="api_key", help="Kalibur API key")
227
+
228
+ report_cmd = sub.add_parser("report", help="Generate a pentest report from a report.md")
229
+ report_cmd.add_argument("-r", metavar="FILE", dest="report", help="Path to report.md")
230
+ report_cmd.add_argument("-f", metavar="NAME", dest="firm", help="Assessor firm name")
231
+ report_cmd.add_argument("-a", metavar="NAME", dest="assessor", help="Assessor name")
232
+ report_cmd.add_argument("-c", metavar="NAME", dest="client", help="Client name")
233
+ report_cmd.add_argument("-l", metavar="FILE", dest="logo", help="Logo file (.png or .jpg)")
234
+ report_cmd.add_argument("-v", metavar="VER", dest="version", help="Report version (e.g. 1.0)")
235
+ report_cmd.add_argument("-k", metavar="KEY", dest="api_key", help="Kalibur API key")
236
+
237
+ push_cmd = sub.add_parser("push", help="Push local kalibur folders")
238
+ push_cmd.add_argument("-k", metavar="KEY", dest="api_key", help="Kalibur API key")
239
+
240
+ pull_cmd = sub.add_parser("pull", help="Pull a saved snapshot")
241
+ pull_cmd.add_argument("-e", metavar="PROJECT", dest="project", help="Project name (skip interactive picker)")
242
+ pull_cmd.add_argument("-k", metavar="KEY", dest="api_key", help="Kalibur API key")
243
+
244
+ end_cmd = sub.add_parser("end", help="End an engagement and permanently delete all its data")
245
+ end_cmd.add_argument("-e", metavar="PROJECT", dest="project", help="Project name (skip interactive picker)")
246
+ end_cmd.add_argument("-k", metavar="KEY", dest="api_key", help="Kalibur API key")
247
+
248
+ args = parser.parse_args()
249
+
250
+ if args.command == "login":
251
+ run_login()
252
+ sys.exit(0)
253
+
254
+ api_key = _resolve_api_key(args.api_key)
255
+
256
+ if args.command == "scan":
257
+ if not api_key:
258
+ _error(_KEY_ERROR)
259
+
260
+ target = Path(args.target).resolve()
261
+ if not target.exists():
262
+ _error(f"target directory does not exist: {target}")
263
+
264
+ output_dir = target / "kalibur"
265
+
266
+ try:
267
+ run_scan(
268
+ target_dir=target,
269
+ api_key=api_key,
270
+ api_url=API_URL,
271
+ output_dir=output_dir,
272
+ project=_detect_project(),
273
+ )
274
+ except KeyboardInterrupt:
275
+ print("\nInterrupted, quitting.", file=sys.stderr)
276
+ sys.exit(130)
277
+
278
+ elif args.command == "report":
279
+ required_flags = [("-r", "report"), ("-f", "firm"), ("-a", "assessor"), ("-c", "client"), ("-l", "logo"), ("-v", "version")]
280
+ missing = [flag for flag, attr in required_flags if not getattr(args, attr, None)]
281
+ if missing:
282
+ _print_report_usage()
283
+ sys.stdout.flush()
284
+ if len(missing) < len(required_flags):
285
+ print(f"\n{_RED}Error: missing required option(s): {', '.join(missing)}{_RST}", file=sys.stderr)
286
+ sys.exit(1)
287
+ sys.exit(0)
288
+ if not api_key:
289
+ _print_report_usage()
290
+ sys.stdout.flush()
291
+ _error(_KEY_ERROR)
292
+ run_report(
293
+ report_path=Path(args.report).resolve(),
294
+ firm=args.firm,
295
+ assessor=args.assessor,
296
+ client=args.client,
297
+ logo_path=Path(args.logo).resolve(),
298
+ version=args.version,
299
+ api_key=api_key,
300
+ api_url=API_URL,
301
+ )
302
+
303
+ elif args.command == "push":
304
+ if not api_key:
305
+ _error(_KEY_ERROR)
306
+ run_push(api_key=api_key, api_url=API_URL)
307
+
308
+ elif args.command == "pull":
309
+ if not api_key:
310
+ _error(_KEY_ERROR)
311
+ run_pull(project_flag=getattr(args, "project", None), api_key=api_key, api_url=API_URL)
312
+
313
+ elif args.command == "end":
314
+ if not api_key:
315
+ _error(_KEY_ERROR)
316
+ run_end(project_flag=getattr(args, "project", None), api_key=api_key, api_url=API_URL)
317
+
318
+
319
+ if __name__ == "__main__":
320
+ main()