aisbom-cli 0.9.2__tar.gz → 0.10.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.
- {aisbom_cli-0.9.2 → aisbom_cli-0.10.0}/PKG-INFO +1 -1
- {aisbom_cli-0.9.2 → aisbom_cli-0.10.0}/aisbom/cli.py +144 -20
- {aisbom_cli-0.9.2 → aisbom_cli-0.10.0}/pyproject.toml +1 -1
- {aisbom_cli-0.9.2 → aisbom_cli-0.10.0}/LICENSE +0 -0
- {aisbom_cli-0.9.2 → aisbom_cli-0.10.0}/README.md +0 -0
- {aisbom_cli-0.9.2 → aisbom_cli-0.10.0}/aisbom/__init__.py +0 -0
- {aisbom_cli-0.9.2 → aisbom_cli-0.10.0}/aisbom/diff.py +0 -0
- {aisbom_cli-0.9.2 → aisbom_cli-0.10.0}/aisbom/linter.py +0 -0
- {aisbom_cli-0.9.2 → aisbom_cli-0.10.0}/aisbom/mock_generator.py +0 -0
- {aisbom_cli-0.9.2 → aisbom_cli-0.10.0}/aisbom/remote.py +0 -0
- {aisbom_cli-0.9.2 → aisbom_cli-0.10.0}/aisbom/safety.py +0 -0
- {aisbom_cli-0.9.2 → aisbom_cli-0.10.0}/aisbom/scanner.py +0 -0
- {aisbom_cli-0.9.2 → aisbom_cli-0.10.0}/aisbom/spdx_gen.py +0 -0
- {aisbom_cli-0.9.2 → aisbom_cli-0.10.0}/aisbom/telemetry.py +0 -0
- {aisbom_cli-0.9.2 → aisbom_cli-0.10.0}/aisbom/version_check.py +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import typer
|
|
2
2
|
import json
|
|
3
|
+
import os
|
|
3
4
|
import tomllib
|
|
4
5
|
import importlib.metadata
|
|
5
6
|
from enum import Enum
|
|
@@ -29,8 +30,38 @@ console = Console()
|
|
|
29
30
|
|
|
30
31
|
|
|
31
32
|
@app.callback(invoke_without_command=True)
|
|
32
|
-
def main(
|
|
33
|
-
|
|
33
|
+
def main(
|
|
34
|
+
ctx: typer.Context,
|
|
35
|
+
version: bool = typer.Option(
|
|
36
|
+
False,
|
|
37
|
+
"--version",
|
|
38
|
+
"-V",
|
|
39
|
+
help="Print the installed version and exit.",
|
|
40
|
+
is_eager=True,
|
|
41
|
+
),
|
|
42
|
+
):
|
|
43
|
+
"""
|
|
44
|
+
AIsbom — AI Supply Chain Security Scanner.
|
|
45
|
+
|
|
46
|
+
Deep introspection of ML model artifacts (.pt, .safetensors, .gguf) for
|
|
47
|
+
malware, license risk, and silent drift. Run `aisbom` with no args to see
|
|
48
|
+
a working example, or `aisbom <command> --help` for details on each command.
|
|
49
|
+
|
|
50
|
+
Environment variables:
|
|
51
|
+
|
|
52
|
+
AISBOM_NO_TELEMETRY=1 Disable all anonymous usage telemetry. Honored on
|
|
53
|
+
every code path; never overridden.
|
|
54
|
+
"""
|
|
55
|
+
# Order matters: --version wins over the no-args panel so that
|
|
56
|
+
# `aisbom --version` is short and scriptable.
|
|
57
|
+
if version:
|
|
58
|
+
try:
|
|
59
|
+
ver = importlib.metadata.version("aisbom-cli")
|
|
60
|
+
except importlib.metadata.PackageNotFoundError:
|
|
61
|
+
ver = "unknown (dev build)"
|
|
62
|
+
console.print(f"aisbom {ver}")
|
|
63
|
+
raise typer.Exit(code=0)
|
|
64
|
+
|
|
34
65
|
# Owns the no-args codepath. When the user runs `aisbom` with no
|
|
35
66
|
# subcommand, show a one-screen quickstart instead of Typer's auto-help
|
|
36
67
|
# block. `--help` still short-circuits to Typer's full reference, and
|
|
@@ -131,6 +162,69 @@ def _flush_telemetry_threads(threads: list[threading.Thread | None]) -> None:
|
|
|
131
162
|
t.join(timeout=2.0)
|
|
132
163
|
|
|
133
164
|
|
|
165
|
+
def _attribution_ref(base_url: str) -> str:
|
|
166
|
+
"""Append `ref=cli` to a URL so we can attribute return visits to CLI
|
|
167
|
+
users in GA4 Acquisition reports. Strips the tag when the user has opted
|
|
168
|
+
out of telemetry — the URL stays useful, just untracked."""
|
|
169
|
+
if os.getenv("AISBOM_NO_TELEMETRY"):
|
|
170
|
+
return base_url
|
|
171
|
+
sep = "&" if "?" in base_url else "?"
|
|
172
|
+
return f"{base_url}{sep}ref=cli"
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _render_scan_footer(
|
|
176
|
+
*,
|
|
177
|
+
share_url: str | None,
|
|
178
|
+
output_path: str | None,
|
|
179
|
+
output_format: "OutputFormat",
|
|
180
|
+
share_attempted: bool,
|
|
181
|
+
) -> None:
|
|
182
|
+
"""Print the post-scan acquisition footer (Phase 4.3).
|
|
183
|
+
|
|
184
|
+
Drives recurring engagement with aisbom.io properties — the viewer
|
|
185
|
+
(per-scan) and the advisories page (weekly). Three flavors:
|
|
186
|
+
|
|
187
|
+
1. `--share` succeeded → point at the hosted viewer URL.
|
|
188
|
+
2. `--share` not used (or failed) and machine-readable format → drag-and-
|
|
189
|
+
drop hint + a nudge to try `--share` next time.
|
|
190
|
+
3. Markdown format → only the advisories link (no viewer hint, since
|
|
191
|
+
the markdown report isn't a viewer input).
|
|
192
|
+
|
|
193
|
+
URL attribution respects `AISBOM_NO_TELEMETRY` via `_attribution_ref`.
|
|
194
|
+
"""
|
|
195
|
+
lines: list[str] = []
|
|
196
|
+
|
|
197
|
+
if share_url:
|
|
198
|
+
lines.append(
|
|
199
|
+
f"🔗 [bold]View this SBOM online:[/bold] "
|
|
200
|
+
f"[underline cyan]{_attribution_ref(share_url)}[/underline cyan]"
|
|
201
|
+
)
|
|
202
|
+
elif output_format in (OutputFormat.JSON, OutputFormat.SPDX) and output_path:
|
|
203
|
+
lines.append(
|
|
204
|
+
f"📊 [bold]Drag [cyan]{output_path}[/cyan] into the offline viewer:[/bold]\n"
|
|
205
|
+
f" [underline cyan]{_attribution_ref('https://aisbom.io/viewer')}[/underline cyan]"
|
|
206
|
+
)
|
|
207
|
+
if not share_attempted:
|
|
208
|
+
lines.append(
|
|
209
|
+
"💡 [dim]Tip: re-run with [white]--share[/white] to get a hosted viewer link.[/dim]"
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
# Always: advisories. Recurring re-engagement vector independent of format.
|
|
213
|
+
lines.append(
|
|
214
|
+
f"📰 [bold]Latest model advisories:[/bold] "
|
|
215
|
+
f"[underline cyan]{_attribution_ref('https://aisbom.io/advisories')}[/underline cyan]"
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
console.print(
|
|
219
|
+
Panel(
|
|
220
|
+
"\n".join(lines),
|
|
221
|
+
title=" Next steps ",
|
|
222
|
+
border_style="blue",
|
|
223
|
+
expand=False,
|
|
224
|
+
)
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
|
|
134
228
|
class OutputFormat(str, Enum):
|
|
135
229
|
JSON = "json"
|
|
136
230
|
MARKDOWN = "markdown"
|
|
@@ -171,7 +265,13 @@ def _generate_markdown(results: dict) -> str:
|
|
|
171
265
|
|
|
172
266
|
@app.command()
|
|
173
267
|
def scan(
|
|
174
|
-
target: str = typer.Argument(
|
|
268
|
+
target: str = typer.Argument(
|
|
269
|
+
".",
|
|
270
|
+
help=(
|
|
271
|
+
"Local directory, HTTP(S) URL, or Hugging Face slug "
|
|
272
|
+
"(e.g. ./models, https://example.com/model.pt, hf://google-bert/bert-base-uncased)."
|
|
273
|
+
),
|
|
274
|
+
),
|
|
175
275
|
output: str | None = typer.Option(None, help="Output file path"),
|
|
176
276
|
schema_version: str = typer.Option("1.6", help="CycloneDX schema version (default is 1.6)", case_sensitive=False, rich_help_panel="Advanced Options"),
|
|
177
277
|
spdx_version: str = typer.Option("2.3", help="SPDX version (2.3 or 3.0)", case_sensitive=False, rich_help_panel="Advanced Options"),
|
|
@@ -179,8 +279,23 @@ def scan(
|
|
|
179
279
|
strict: bool = typer.Option(False, help="Enable strict allowlisting mode (flags any unknown imports)"),
|
|
180
280
|
lint: bool = typer.Option(False, help="Enable Migration Linter (checks for weights_only=True compatibility)"),
|
|
181
281
|
format: OutputFormat = typer.Option(OutputFormat.JSON, help="Output format (JSON for SBOM, MARKDOWN for Human Report, SPDX for Compliance)"),
|
|
182
|
-
share: bool = typer.Option(
|
|
183
|
-
|
|
282
|
+
share: bool = typer.Option(
|
|
283
|
+
False,
|
|
284
|
+
help=(
|
|
285
|
+
"Upload the generated SBOM to aisbom.io and print a public viewer URL. "
|
|
286
|
+
"Anyone with the link can view the SBOM; data expires after 30 days. "
|
|
287
|
+
"Prompts for confirmation before uploading unless --share-yes is also set."
|
|
288
|
+
),
|
|
289
|
+
),
|
|
290
|
+
share_yes: bool = typer.Option(
|
|
291
|
+
False,
|
|
292
|
+
"--share-yes",
|
|
293
|
+
help=(
|
|
294
|
+
"Skip the --share confirmation prompt. Intended for CI/CD pipelines; "
|
|
295
|
+
"do not pass this flag interactively unless you understand that the "
|
|
296
|
+
"uploaded SBOM becomes publicly viewable for 30 days."
|
|
297
|
+
),
|
|
298
|
+
),
|
|
184
299
|
):
|
|
185
300
|
"""
|
|
186
301
|
Deep Introspection Scan: Analyzes binary headers and dependency manifests.
|
|
@@ -199,6 +314,11 @@ def scan(
|
|
|
199
314
|
_maybe_emit_install_event(),
|
|
200
315
|
]
|
|
201
316
|
|
|
317
|
+
# Phase 4.3 — captured by the --share success path below and read by the
|
|
318
|
+
# acquisition footer at scan end. Function-scope name so the inner JSON
|
|
319
|
+
# branch can write to it and the footer can read it.
|
|
320
|
+
share_url: str | None = None
|
|
321
|
+
|
|
202
322
|
# 1. Run the Logic
|
|
203
323
|
try:
|
|
204
324
|
scanner = DeepScanner(target, strict_mode=strict, lint=lint)
|
|
@@ -425,20 +545,15 @@ def scan(
|
|
|
425
545
|
f.write(markdown)
|
|
426
546
|
console.print(f"\n[bold green]✔ Markdown Report Generated:[/bold green] {output}")
|
|
427
547
|
|
|
428
|
-
#
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
f"Drag and drop [cyan]{output}[/cyan] into our secure offline viewer:\n"
|
|
438
|
-
f"> View detailed artifact visualizer: https://aisbom.io/viewer",
|
|
439
|
-
border_style="blue",
|
|
440
|
-
expand=False
|
|
441
|
-
))
|
|
548
|
+
# Phase 4.3 — acquisition footer (replaces the previous "Visualize this
|
|
549
|
+
# report" panel). Always shown after a successful scan. Drives recurring
|
|
550
|
+
# engagement with aisbom.io's viewer + advisories pages.
|
|
551
|
+
_render_scan_footer(
|
|
552
|
+
share_url=share_url,
|
|
553
|
+
output_path=output,
|
|
554
|
+
output_format=format,
|
|
555
|
+
share_attempted=share,
|
|
556
|
+
)
|
|
442
557
|
|
|
443
558
|
# Signal exit behavior to the user
|
|
444
559
|
if exit_code == 2:
|
|
@@ -466,12 +581,21 @@ def info():
|
|
|
466
581
|
except importlib.metadata.PackageNotFoundError:
|
|
467
582
|
ver = "unknown (dev build)"
|
|
468
583
|
|
|
584
|
+
# Phase 4 help-pass: surface telemetry state in `info` so users have one
|
|
585
|
+
# canonical place to confirm whether events are firing on their machine.
|
|
586
|
+
telemetry_state = (
|
|
587
|
+
"opted out via AISBOM_NO_TELEMETRY"
|
|
588
|
+
if os.getenv("AISBOM_NO_TELEMETRY")
|
|
589
|
+
else "enabled (set AISBOM_NO_TELEMETRY=1 to disable)"
|
|
590
|
+
)
|
|
591
|
+
|
|
469
592
|
console.print(Panel(
|
|
470
593
|
f"[bold cyan]AI SBOM[/bold cyan]: AI Software Bill of Materials - The Supply Chain for Artificial Intelligence\n"
|
|
471
594
|
f"[bold]Version:[/bold] {ver}\n"
|
|
472
595
|
f"[bold]License:[/bold] Apache 2.0\n"
|
|
473
596
|
f"[bold]Website:[/bold] https://www.aisbom.io\n"
|
|
474
|
-
f"[bold]Repository:[/bold] https://github.com/Lab700xOrg/aisbom"
|
|
597
|
+
f"[bold]Repository:[/bold] https://github.com/Lab700xOrg/aisbom\n"
|
|
598
|
+
f"[bold]Telemetry:[/bold] {telemetry_state}",
|
|
475
599
|
title=" System Info ",
|
|
476
600
|
border_style="magenta",
|
|
477
601
|
expand=False
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "aisbom-cli"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.10.0"
|
|
4
4
|
description = "An AI Supply Chain security tool that that detects Pickle bombs and generates CycloneDX SBOMs for Machine Learning models."
|
|
5
5
|
authors = ["Ajoy L <lab700xdev@gmail.com>"]
|
|
6
6
|
readme = "README.md"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|