Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,27 @@ All notable changes to bootintel-cli are documented here. Format follows [Keep a

## [Unreleased]

### Added
- **`bootintel submit`**: save a boot log to your BootIntel account as a persisted
scan and download the server-side artifacts in one step: the CycloneDX 1.6 SBOM
(`--sbom`), evidence pack (`--evidence`), PDF report (`--pdf`) and JSON report
(`--json-report`). `--json` prints the scan id and written paths for scripting.

Separate from `scan --api` on purpose. That posts to `/analysis/scan`, which the
server documents as stateless and returns a freshly minted uuid naming no row,
so there is nothing to export from; artifacts are built from a real scan, which
only `POST /scans/` creates. A saved scan consumes the monthly quota, so this is
opt-in rather than a flag, and the command says what it is about to spend
immediately before spending it.

Requires **Pro** or higher. The export endpoints are gated at Researcher, but
API-key authentication is itself Pro-gated, so a Researcher account can download
these from the dashboard and not from here.

Reuses an existing device with the same name rather than creating one per run,
because the server accepts duplicates and a nightly CI job would otherwise add
a device a day.

## [0.13.0] — 2026-09-29 — read boot logs from your own assistant

### Added
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ Use `bootintel term` when you want a clean terminal and `bootintel analyze` when
| Assess what the boot chain permits | `bootintel verdict session.log` | Reads a `printenv` dump taken at the U-Boot prompt and says what it permits. Entirely offline. |
| Compare firmware boots | `bootintel diff before.log after.log` | Shows meaningful boot-log changes between two captures. |
| Gate a build artifact | `bootintel scan boot.log --format sarif --gate-critical` | Emits CI-friendly output and exits non-zero for critical findings. |
| Produce an SBOM for a boot log | `bootintel submit boot.log --sbom device.cdx.json` | Saves the scan to your account and downloads a CycloneDX 1.6 SBOM built from the server's CVE and KEV data. Needs a Pro plan (API-key auth is Pro-gated) and uses one scan from the monthly quota. `--evidence`, `--pdf` and `--json-report` fetch the other artifacts in the same run. |
| Request richer analysis | `bootintel scan --api --preview boot.log` | Explicitly sends the log to BootIntel's API using the anonymous preview quota. |

## Using it from an assistant
Expand Down
30 changes: 30 additions & 0 deletions crates/cli/src/api/endpoints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,36 @@ pub fn scan_url(base: &str) -> String {
)
}

/// Saved-scan endpoints.
///
/// Distinct from `scan_url` above, and the difference matters: that one is
/// `/analysis/scan`, documented server-side as "stateless inline log analysis
/// without device_id/database persistence". It returns a freshly minted uuid as
/// its `scan_id` which names no row, so there is nothing to export from. These
/// routes create and read a real ScanSession, which is what the server-side
/// artifacts (SBOM, evidence pack, PDF, JSON report) are built from.
///
/// A saved scan consumes the account's monthly scan quota. The stateless route
/// does not. Callers must not reach for these silently.
pub fn devices_url(base: &str) -> String {
format!("{}{}/devices/", base.trim_end_matches('/'), path_prefix())
}

pub fn scans_url(base: &str) -> String {
format!("{}{}/scans/", base.trim_end_matches('/'), path_prefix())
}

/// `/scans/{id}/{artifact}` where artifact is e.g. `sbom.json`.
pub fn scan_artifact_url(base: &str, scan_id: &str, artifact: &str) -> String {
format!(
"{}{}/scans/{}/{}",
base.trim_end_matches('/'),
path_prefix(),
scan_id,
artifact
)
}

/// Result of `check_plaintext_base`. Callers use this to decide whether
/// to abort (auth path) or emit a warning (preview path).
#[derive(Debug, PartialEq, Eq)]
Expand Down
1 change: 1 addition & 0 deletions crates/cli/src/cmd/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ pub mod replay;
pub mod scan;
pub mod schema;
pub mod share;
pub mod submit;
pub mod term;
pub mod verdict;
pub mod version;
Expand Down
Loading
Loading