diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..e27db24 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,29 @@ +name: Build + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + cache: maven + + - name: Build with Maven + run: mvn clean package + + - name: Upload jar artifact + uses: actions/upload-artifact@v4 + with: + name: runtime-analyst-jar + path: target/runtime-analyst-*.jar diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..2c80f5f --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,31 @@ +name: Release + +on: + push: + tags: + - "v*" + +permissions: + contents: write + +jobs: + build-and-release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + cache: maven + + - name: Build with Maven + run: mvn clean package + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + files: target/runtime-analyst-*.jar + generate_release_notes: true diff --git a/.gitignore b/.gitignore index b6f63db..a972429 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,8 @@ CLAUDE.md # Generated files *.csv cbom*.json +aibom*.json +blueprint*.json # Shell scripts (may contain credentials) *.sh diff --git a/README.md b/README.md index 3a92bf0..94122e1 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,22 @@ -# BOM Squad +# Runtime Analyst -Generate [CycloneDX](https://cyclonedx.org/) Bills of Materials from Contrast Security runtime observability data: +Runtime Analyst turns Contrast Security's runtime observability data into structured, standard reports, across three domains: -- **CBOM** ([Cryptography Bill of Materials](https://cyclonedx.org/capabilities/cbom/)) - every cryptographic algorithm actually observed running, for post-quantum migration planning -- **AI-BOM** (AI/LLM usage inventory) - every AI model and provider actually observed running, for AI governance and shadow-AI visibility +- **Crypto** - every cryptographic algorithm observed running in your applications, with NIST post-quantum vulnerability classification +- **AI** - every AI model and provider observed running, with cloud-vs-local classification for shadow-AI visibility +- **Blueprint (alpha)** - a map of how your applications connect and behave: assets, deployment zones, connections, and crypto/AI behaviors -Both come with an AI-powered advisor report: **Quantum Advisor** (crypto risk) and **AI Advisor** (AI usage risk). +For Crypto and AI, it produces both a [CycloneDX](https://cyclonedx.org/) Bill of Materials - **CBOM** and **AI-BOM** - and an AI-powered analysis report - **Quantum Advisor** and **AI Advisor** - that classifies findings, explains what each application actually does, and writes its analysis back into the BOM itself. Blueprint (alpha) produces only a draft CycloneDX 2.0 Architectural BOM + Bill of Behaviors; it doesn't have an analysis report yet. -**Requirements:** Java 8+ and Maven to build; the `claude` CLI on your `PATH` and logged in, for the AI analysis. No Python, no separate API key, no AWS/Bedrock credentials. One jar, one command per BOM type. +It doesn't scan source code or dependency manifests - it reads what Contrast's agents observed actually running in your applications, with full stack traces, usage counts, and architecture context. + +**Requirements:** + +- A Contrast account with runtime data already flowing in from real-world applications and APIs - Contrast's agents must actually be deployed and observing traffic. Runtime Analyst only reports on what Contrast has observed; it has nothing to show against an account with no instrumented applications or no production/QA traffic. +- Java 17+ and Maven to build. +- The `claude` CLI on your `PATH` and logged in, for `--analyze`/the advisor reports - no separate API key, no AWS/Bedrock credentials, no Python. + +One jar, one command per report type. ## Why Contrast for This? @@ -20,24 +29,48 @@ Contrast provides runtime observability that goes far beyond static code scannin - **Multiple call paths** - See how many different code paths invoke each algorithm/model - **Architecture context** - Each application component is enriched with its language, security posture, and what it's connected to (from the Contrast architecture graph), so the advisor reports can describe what an application actually is, not just what it uses - **Application dependencies** - Which apps and APIs depend on each crypto algorithm or AI model +- **Real connections, not guesses** - Blueprint's assets, zones, and flows come from the same architecture graph, so the map it draws is what Contrast actually saw talking to what, not an inferred or self-reported topology This runtime visibility is critical for post-quantum migration and AI governance planning - you need to know not just *what's* in use, but *how* it's being used and by *what*. -## Features +## `--help` + +``` +$ java -jar runtime-analyst.jar + +Runtime Analyst - Contrast Security Bill of Materials generator + +Usage: + java -jar runtime-analyst.jar auth [options] Connect to Contrast and generate contrast.properties + java -jar runtime-analyst.jar cbom [options] Generate a Cryptography Bill of Materials + java -jar runtime-analyst.jar aibom [options] Generate an AI/LLM usage Bill of Materials + java -jar runtime-analyst.jar blueprint [options] Generate a CycloneDX Blueprint (ABOM + Bill of Behaviors) + java -jar runtime-analyst.jar cbom-advisor Re-run the Quantum Advisor against an existing CBOM + java -jar runtime-analyst.jar aibom-advisor Re-run the AI Advisor against an existing AI-BOM + +`cbom --analyze` / `aibom --analyze` already run the matching advisor automatically after generation - +the standalone cbom-advisor/aibom-advisor commands are for re-running the advisor without regenerating the BOM. + +Run with -h after a subcommand for its options, e.g.: + java -jar runtime-analyst.jar cbom -h + java -jar runtime-analyst.jar aibom -h +``` + +Every subcommand supports `-h`/`--help` for its own options - see [Examples](#examples) below for each one's full help text. + +## Authentication + +Every command except `auth` itself reads a `contrast.properties` file for credentials. If `cbom`, `aibom`, or `blueprint` don't find one (or the one named with `-c`), they run `auth` for you automatically first, then proceed with the command you actually asked for - so you never have to run `auth` yourself as a separate step. You can also set up `contrast.properties` ahead of time, two ways: -- Generates CycloneDX 1.6 compliant CBOM and AI-BOM in JSON format -- Fetches usage data from the Contrast `/observations` API (crypto algorithms and AI/LLM usage share this endpoint, filtered by rule type) -- Shows which applications use which algorithms/models -- Application components are enriched with `contrast:*` properties (language, posture score, criticality, open issues, connections) and a deep-link `externalReference` back to the Contrast Explorer UI, both pulled from the `contrast-graph` API -- Includes NIST quantum security levels for post-quantum migration planning, and cloud-vs-local host classification for AI usage -- Tracks usage counts and unique call locations per algorithm/model -- Full stack traces showing usage context -- Filters by application or environment (PRODUCTION, DEVELOPMENT, QA) -- `--analyze` runs the matching AI advisor after generation, which writes its generated application descriptions back into the BOM's `Component.description` field +**Option 1 - `auth` (recommended):** -## Quick Start +```bash +java -jar runtime-analyst.jar auth --host https://your-instance.contrastsecurity.com +``` -1. Create `contrast.properties` in your working directory: +This opens a real browser window and lets you log in exactly the way you normally would, including SSO/MFA - there's nothing to copy or paste. The window closes as soon as login completes; in the background, it reads your personal API key, service key, and organization ID directly off your account's **User Settings > Your Keys** page, verifies them with a real API call, and writes `contrast.properties` for you. Your session cookie is never read or stored - only the API key and service key that page shows you. + +**Option 2 - create it by hand:** ```properties contrast.url=https://your-instance.contrastsecurity.com/api/ns-ui/v1 @@ -46,72 +79,152 @@ contrast.auth_header=base64-encoded-email:service-key contrast.api_key=your-api-key ``` -2. Build and run: +Find these values yourself under **User Settings > Your Keys** in the Contrast UI. `contrast.auth_header` is the base64 encoding of `your-email:your-service-key` (not the service key alone). + +Every command accepts `-c ` to point at a config file somewhere other than the working directory. + +## Usage ```bash -mvn clean package +mvn clean package # build target/runtime-analyst-1.0.jar +``` -# CBOM -java -jar target/bom-squad-1.0-SNAPSHOT.jar cbom +A single jar, dispatched by subcommand: + +| Subcommand | Purpose | +|---|---| +| `auth` | Log in via browser and generate `contrast.properties` | +| `cbom` | Generate a Cryptography Bill of Materials | +| `aibom` | Generate an AI/LLM usage Bill of Materials | +| `blueprint` (alpha) | Generate a draft CycloneDX 2.0 Architectural BOM + Bill of Behaviors | +| `cbom-advisor` | Re-run the Quantum Advisor against an existing CBOM file | +| `aibom-advisor` | Re-run the AI Advisor against an existing AI-BOM file | + +`cbom`, `aibom`, and `blueprint` all share the same filter flags: `--app `, `--env `, `--list` (list available applications and exit), `-o ` (output path), and `-c `. `cbom`/`aibom` additionally support `--analyze`, which runs the matching advisor automatically after generation. + +## Examples + +### `auth` -# AI-BOM -java -jar target/bom-squad-1.0-SNAPSHOT.jar aibom ``` +$ java -jar runtime-analyst.jar auth -h -## Usage +Auth - connect runtime-analyst to your Contrast account -A single jar with two subcommands, `cbom` and `aibom`, taking the same set of flags: +Opens a real browser window, lets you log in (including SSO/MFA) the way you normally +would, then reads your personal API key/service key/org id off User Settings > Your Keys +directly - no manual copy/paste into the terminal. The window closes as soon as login +completes; everything after that runs in a background headless browser. -### CBOM (crypto) +Usage: + java -jar runtime-analyst.jar auth [--host ] [-o ] +``` ```bash -# Generate CBOM for all apps -java -jar bom-squad.jar cbom +# First-time setup against your instance +java -jar runtime-analyst.jar auth --host https://eval.contrastsecurity.com -# List available applications -java -jar bom-squad.jar cbom --list +# Write to a different config path +java -jar runtime-analyst.jar auth --host https://eval.contrastsecurity.com -o prod.properties +``` -# Generate CBOM for specific app (by name or ID) -java -jar bom-squad.jar cbom --app "MyApp" -java -jar bom-squad.jar cbom --app 7136cb1b-f846-4c1d-bdd3-77b448cbd2fe +### `cbom` -# Filter by environment -java -jar bom-squad.jar cbom --env PRODUCTION +``` +$ java -jar runtime-analyst.jar cbom -h + +CBOM Generator - Create CycloneDX CBOM from Contrast observations + +Usage: + java -jar runtime-analyst.jar cbom Generate CBOM for all apps + java -jar runtime-analyst.jar cbom --app Filter by app (ID or name) + java -jar runtime-analyst.jar cbom --env Filter by environment (PRODUCTION, DEVELOPMENT, QA) + java -jar runtime-analyst.jar cbom --list List available applications with IDs + java -jar runtime-analyst.jar cbom --analyze Run Quantum Advisor AI analysis after CBOM generation + java -jar runtime-analyst.jar cbom -o Specify output filename + java -jar runtime-analyst.jar cbom -c Use custom config file +``` -# Combine filters -java -jar bom-squad.jar cbom --app "MyApp" --env PRODUCTION -o myapp-prod.json +```bash +java -jar runtime-analyst.jar cbom # all apps -> cbom.json +java -jar runtime-analyst.jar cbom --list # list applications and their IDs +java -jar runtime-analyst.jar cbom --app "MyApp" # filter by app name +java -jar runtime-analyst.jar cbom --app 7136cb1b-f846-4c1d-bdd3-77b448cbd2fe # ...or by ID +java -jar runtime-analyst.jar cbom --env PRODUCTION # only prod observations +java -jar runtime-analyst.jar cbom --app "MyApp" --env PRODUCTION -o myapp-prod.json +java -jar runtime-analyst.jar cbom -c prod.properties --list +java -jar runtime-analyst.jar cbom --analyze # + Quantum Advisor risk report +``` -# Use custom config file -java -jar bom-squad.jar cbom -c /path/to/config.properties +### `aibom` -# Generate CBOM + AI-powered Quantum Advisor risk report -java -jar bom-squad.jar cbom --analyze +``` +$ java -jar runtime-analyst.jar aibom -h + +AI-BOM Generator - Create CycloneDX AI-BOM from Contrast AI usage observations + +Usage: + java -jar runtime-analyst.jar aibom Generate AI-BOM for all apps + java -jar runtime-analyst.jar aibom --app Filter by app (ID or name) + java -jar runtime-analyst.jar aibom --env Filter by environment (PRODUCTION, DEVELOPMENT, QA) + java -jar runtime-analyst.jar aibom --list List available applications with IDs + java -jar runtime-analyst.jar aibom --analyze Run AI Advisor analysis after AI-BOM generation + java -jar runtime-analyst.jar aibom -o Specify output filename + java -jar runtime-analyst.jar aibom -c Use custom config file ``` -### AI-BOM (AI/LLM usage) +```bash +java -jar runtime-analyst.jar aibom +java -jar runtime-analyst.jar aibom --list +java -jar runtime-analyst.jar aibom --app "MyApp" --env PRODUCTION +java -jar runtime-analyst.jar aibom --analyze # + AI Advisor governance report +``` + +### `blueprint` (alpha) -Same flags, `aibom` subcommand: +``` +$ java -jar runtime-analyst.jar blueprint -h + +Blueprint Generator - Create a CycloneDX Blueprint (ABOM + Bill of Behaviors) from Contrast data + +Usage: + java -jar runtime-analyst.jar blueprint Generate a Blueprint for all apps + java -jar runtime-analyst.jar blueprint --app Filter by app (ID or name) + java -jar runtime-analyst.jar blueprint --env Filter by environment (PRODUCTION, DEVELOPMENT, QA) + java -jar runtime-analyst.jar blueprint --list List available applications with IDs + java -jar runtime-analyst.jar blueprint -o Specify output filename + java -jar runtime-analyst.jar blueprint -c Use custom config file + +Note: Blueprints are a CycloneDX draft (unreleased 2.0-dev branch, spec PR #652). +This command populates assets/zones/flows/behaviors from real Contrast data only - +it does not generate threats/controls/risks (TM-BOM), which would require fabricating +findings Contrast's telemetry cannot back. +``` ```bash -java -jar bom-squad.jar aibom -java -jar bom-squad.jar aibom --list -java -jar bom-squad.jar aibom --app "MyApp" --env PRODUCTION -java -jar bom-squad.jar aibom --analyze +java -jar runtime-analyst.jar blueprint +java -jar runtime-analyst.jar blueprint --app "MyApp" --env PRODUCTION ``` -### Re-running an advisor against an existing BOM +### `cbom-advisor` / `aibom-advisor` -`cbom --analyze` / `aibom --analyze` already run the matching advisor automatically after generation. To re-run the advisor against a BOM you already have (without regenerating it), use the standalone subcommands: +Re-run an advisor against a BOM you already have, without regenerating it: -```bash -# Crypto / post-quantum risk report -java -jar bom-squad.jar cbom-advisor cbom.json -o report.md +``` +$ java -jar runtime-analyst.jar cbom-advisor +Usage: java -jar runtime-analyst.jar cbom-advisor [-v] [-o report.md] [--json out.json] [--no-confirm] [--filter all|vulnerable|asymmetric] -# AI usage inventory / governance risk report -java -jar bom-squad.jar aibom-advisor aibom.json -o report.md +$ java -jar runtime-analyst.jar aibom-advisor +Usage: java -jar runtime-analyst.jar aibom-advisor [-v] [-o report.md] [--json out.json] [--no-confirm] ``` -Everything - BOM generation and AI analysis - runs in a single JVM process. The advisors shell out to the `claude` CLI already logged in to this shell (no separate API key or AWS/Bedrock credentials needed, and no Python required) - just make sure `claude` is on your `PATH` and authenticated. +```bash +java -jar runtime-analyst.jar cbom-advisor cbom.json -o report.md +java -jar runtime-analyst.jar aibom-advisor aibom.json -o report.md +java -jar runtime-analyst.jar cbom-advisor cbom.json -v -o report.md --filter vulnerable +``` + +Everything - BOM generation and AI analysis - runs in a single JVM process. The advisors shell out to the `claude` CLI already logged in to this shell; no separate API key or AWS/Bedrock credentials needed, and no Python required. ## Output @@ -146,6 +259,17 @@ Contrast AI Usage Inventory └── app-reportservice → openai/smollm2:135m-tuned (local, via Ollama) ``` +### Blueprint (alpha) + +A draft CycloneDX 2.0 document with a top-level `blueprints[]` array containing: + +- **`assets[]`** - one per application (from the `contrast-graph` architecture graph) plus one per connection known only by name +- **`zones[]`** - one per deployment tier +- **`flows[]`** - architecture-graph connections between assets, deduplicated and modeled as bidirectional since the graph API doesn't preserve direction +- **`behaviors.instances[]`** - crypto/AI usage observations mapped onto the CycloneDX behavior taxonomy (e.g. `security:cryptography:encryptsData`, `ai:generative:processesPrompt`) + +Deliberately does **not** generate threats, controls, or risks (TM-BOM) - the draft spec models those as a separate, sibling construct, and none of it can be derived from Contrast telemetry without an actual STRIDE-style analysis. + ### Advisor reports - **Quantum Advisor** - findings grouped by risk level (CRITICAL/HIGH/MEDIUM/LOW/NOT_QUANTUM_ISSUE), with an "Application Context" section describing each app from its architecture graph data @@ -168,15 +292,7 @@ Two sample AI-BOM files are included to try it with: - `sample-aibom.json` - real output from `AIBOMGenerator` against a live org (one model, both apps local/self-hosted) - `test-aibom.json` - a hand-crafted fixture covering cases the sample doesn't: multiple providers (OpenAI/Anthropic/Ollama), both cloud and local host categories, and an app with multiple call sites for the same model -## Building - -```bash -mvn clean package -``` - -Creates `target/bom-squad-1.0-SNAPSHOT.jar` (executable uber-jar; `Main` dispatches to `cbom`/`aibom` based on the first argument). - -## Configuration +## Configuration Reference | Property | Description | |----------|-------------| diff --git a/pom.xml b/pom.xml index a335bb5..c1b7c8e 100644 --- a/pom.xml +++ b/pom.xml @@ -5,14 +5,14 @@ 4.0.0 com.contrastsecurity - bom-squad - 1.0-SNAPSHOT - BOM Squad + runtime-analyst + 1.0 + Runtime Analyst Generates CycloneDX CBOM/AI-BOM inventories from Contrast Security runtime observability data, with an AI-powered advisor for each. - 1.8 - 1.8 + 17 + 17 @@ -41,6 +41,11 @@ cyclonedx-core-java 13.0.0 + + com.microsoft.playwright + playwright + 1.62.0 + @@ -50,12 +55,12 @@ maven-compiler-plugin 3.15.0 - 1.8 - 1.8 + 17 + 17 - + org.apache.maven.plugins maven-shade-plugin @@ -69,7 +74,7 @@ - com.contrastsecurity.bomsquad.Main + com.contrastsecurity.runtimeanalyst.Main false diff --git a/src/main/java/com/contrastsecurity/bomsquad/Main.java b/src/main/java/com/contrastsecurity/bomsquad/Main.java deleted file mode 100644 index f35f658..0000000 --- a/src/main/java/com/contrastsecurity/bomsquad/Main.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.contrastsecurity.bomsquad; - -import java.util.Arrays; - -/** - * Single CLI entry point for the jar. Dispatches to CBOMGenerator or AIBOMGenerator - * based on the first argument. - * - * Usage: - * java -jar bom-squad.jar cbom [options] # Cryptography Bill of Materials - * java -jar bom-squad.jar aibom [options] # AI/LLM usage Bill of Materials - */ -public class Main { - - public static void main(String[] args) { - if (args.length == 0) { - printUsage(); - System.exit(1); - } - - String subcommand = args[0]; - String[] rest = Arrays.copyOfRange(args, 1, args.length); - - switch (subcommand) { - case "cbom": - CBOMGenerator.main(rest); - break; - case "aibom": - AIBOMGenerator.main(rest); - break; - case "cbom-advisor": - QuantumAdvisor.main(rest); - break; - case "aibom-advisor": - AIAdvisor.main(rest); - break; - case "--help": - case "-h": - printUsage(); - break; - default: - System.err.println("Unknown command: " + subcommand); - printUsage(); - System.exit(1); - } - } - - private static void printUsage() { - System.out.println("\nBOM Squad - Contrast Security Bill of Materials generator"); - System.out.println("\nUsage:"); - System.out.println(" java -jar bom-squad.jar cbom [options] Generate a Cryptography Bill of Materials"); - System.out.println(" java -jar bom-squad.jar aibom [options] Generate an AI/LLM usage Bill of Materials"); - System.out.println(" java -jar bom-squad.jar cbom-advisor Re-run the Quantum Advisor against an existing CBOM"); - System.out.println(" java -jar bom-squad.jar aibom-advisor Re-run the AI Advisor against an existing AI-BOM"); - System.out.println("\n`cbom --analyze` / `aibom --analyze` already run the matching advisor automatically after generation -"); - System.out.println("the standalone cbom-advisor/aibom-advisor commands are for re-running the advisor without regenerating the BOM."); - System.out.println("\nRun with -h after a subcommand for its options, e.g.:"); - System.out.println(" java -jar bom-squad.jar cbom -h"); - System.out.println(" java -jar bom-squad.jar aibom -h"); - } -} diff --git a/src/main/java/com/contrastsecurity/bomsquad/AIAdvisor.java b/src/main/java/com/contrastsecurity/runtimeanalyst/AIAdvisor.java similarity index 99% rename from src/main/java/com/contrastsecurity/bomsquad/AIAdvisor.java rename to src/main/java/com/contrastsecurity/runtimeanalyst/AIAdvisor.java index 5348e41..17de23a 100644 --- a/src/main/java/com/contrastsecurity/bomsquad/AIAdvisor.java +++ b/src/main/java/com/contrastsecurity/runtimeanalyst/AIAdvisor.java @@ -1,4 +1,4 @@ -package com.contrastsecurity.bomsquad; +package com.contrastsecurity.runtimeanalyst; import java.io.FileReader; import java.io.FileWriter; @@ -27,7 +27,7 @@ * level): for each app, describes what it appears to be, then lists how it uses AI. * * Usage: - * java -jar bom-squad.jar aibom-advisor aibom.json [-v] [-o report.md] [--json out.json] [--no-confirm] + * java -jar runtime-analyst.jar aibom-advisor aibom.json [-v] [-o report.md] [--json out.json] [--no-confirm] */ public class AIAdvisor { @@ -114,7 +114,7 @@ public static void main(String[] args) { else if (a.equals("--json") && i + 1 < args.length) jsonOut = args[++i]; else if (a.equals("--no-confirm")) noConfirm = true; else if (a.equals("-h") || a.equals("--help")) { - System.out.println("Usage: java -jar bom-squad.jar aibom-advisor [-v] [-o report.md] [--json out.json] [--no-confirm]"); + System.out.println("Usage: java -jar runtime-analyst.jar aibom-advisor [-v] [-o report.md] [--json out.json] [--no-confirm]"); return; } else if (!a.startsWith("-")) { aibomPath = a; diff --git a/src/main/java/com/contrastsecurity/bomsquad/AIBOMGenerator.java b/src/main/java/com/contrastsecurity/runtimeanalyst/AIBOMGenerator.java similarity index 95% rename from src/main/java/com/contrastsecurity/bomsquad/AIBOMGenerator.java rename to src/main/java/com/contrastsecurity/runtimeanalyst/AIBOMGenerator.java index df6af73..2dce5bd 100644 --- a/src/main/java/com/contrastsecurity/bomsquad/AIBOMGenerator.java +++ b/src/main/java/com/contrastsecurity/runtimeanalyst/AIBOMGenerator.java @@ -1,4 +1,4 @@ -package com.contrastsecurity.bomsquad; +package com.contrastsecurity.runtimeanalyst; import java.io.File; import java.io.FileInputStream; @@ -44,11 +44,11 @@ * Generates a CycloneDX AI-BOM (AI/ML usage inventory) from Contrast API observations. * * Usage: - * java -jar bom-squad.jar aibom # Fetch all apps, output aibom.json - * java -jar bom-squad.jar aibom --app "AppName" # Fetch single app - * java -jar bom-squad.jar aibom --list # List available applications - * java -jar bom-squad.jar aibom -o custom.json # Custom output filename - * java -jar bom-squad.jar aibom -c config.properties + * java -jar runtime-analyst.jar aibom # Fetch all apps, output aibom.json + * java -jar runtime-analyst.jar aibom --app "AppName" # Fetch single app + * java -jar runtime-analyst.jar aibom --list # List available applications + * java -jar runtime-analyst.jar aibom -o custom.json # Custom output filename + * java -jar runtime-analyst.jar aibom -c config.properties * * Config file (contrast.properties): * contrast.url=https://your-instance.contrastsecurity.com/api/ns-ui/v1 @@ -175,15 +175,15 @@ public static void main(String[] args) { private static void printUsage() { System.out.println("\nAI-BOM Generator - Create CycloneDX AI-BOM from Contrast AI usage observations"); System.out.println("\nUsage:"); - System.out.println(" java -jar bom-squad.jar aibom Generate AI-BOM for all apps"); - System.out.println(" java -jar bom-squad.jar aibom --app Filter by app (ID or name)"); - System.out.println(" java -jar bom-squad.jar aibom --env Filter by environment (PRODUCTION, DEVELOPMENT, QA)"); - System.out.println(" java -jar bom-squad.jar aibom --list List available applications with IDs"); - System.out.println(" java -jar bom-squad.jar aibom --analyze Run AI Advisor analysis after AI-BOM generation"); - System.out.println(" java -jar bom-squad.jar aibom -o Specify output filename"); - System.out.println(" java -jar bom-squad.jar aibom -c Use custom config file"); + System.out.println(" java -jar runtime-analyst.jar aibom Generate AI-BOM for all apps"); + System.out.println(" java -jar runtime-analyst.jar aibom --app Filter by app (ID or name)"); + System.out.println(" java -jar runtime-analyst.jar aibom --env Filter by environment (PRODUCTION, DEVELOPMENT, QA)"); + System.out.println(" java -jar runtime-analyst.jar aibom --list List available applications with IDs"); + System.out.println(" java -jar runtime-analyst.jar aibom --analyze Run AI Advisor analysis after AI-BOM generation"); + System.out.println(" java -jar runtime-analyst.jar aibom -o Specify output filename"); + System.out.println(" java -jar runtime-analyst.jar aibom -c Use custom config file"); System.out.println("\nConfig file (contrast.properties):"); - System.out.println(" contrast.url=https://eval.contrastsecurity.com/api/ns-ui/v1"); + System.out.println(" contrast.url=https://your-instance.contrastsecurity.com/api/ns-ui/v1"); System.out.println(" contrast.org_id=your-org-id"); System.out.println(" contrast.auth_header=base64-encoded-credentials"); System.out.println(" contrast.api_key=your-api-key"); diff --git a/src/main/java/com/contrastsecurity/bomsquad/AIUsageParser.java b/src/main/java/com/contrastsecurity/runtimeanalyst/AIUsageParser.java similarity index 98% rename from src/main/java/com/contrastsecurity/bomsquad/AIUsageParser.java rename to src/main/java/com/contrastsecurity/runtimeanalyst/AIUsageParser.java index 70fc133..b6a19e2 100644 --- a/src/main/java/com/contrastsecurity/bomsquad/AIUsageParser.java +++ b/src/main/java/com/contrastsecurity/runtimeanalyst/AIUsageParser.java @@ -1,4 +1,4 @@ -package com.contrastsecurity.bomsquad; +package com.contrastsecurity.runtimeanalyst; import java.util.regex.Matcher; import java.util.regex.Pattern; diff --git a/src/main/java/com/contrastsecurity/bomsquad/AlgorithmParser.java b/src/main/java/com/contrastsecurity/runtimeanalyst/AlgorithmParser.java similarity index 99% rename from src/main/java/com/contrastsecurity/bomsquad/AlgorithmParser.java rename to src/main/java/com/contrastsecurity/runtimeanalyst/AlgorithmParser.java index b3b9045..a4d22db 100644 --- a/src/main/java/com/contrastsecurity/bomsquad/AlgorithmParser.java +++ b/src/main/java/com/contrastsecurity/runtimeanalyst/AlgorithmParser.java @@ -1,4 +1,4 @@ -package com.contrastsecurity.bomsquad; +package com.contrastsecurity.runtimeanalyst; import java.util.HashMap; import java.util.Map; diff --git a/src/main/java/com/contrastsecurity/bomsquad/AppGraphInfo.java b/src/main/java/com/contrastsecurity/runtimeanalyst/AppGraphInfo.java similarity index 94% rename from src/main/java/com/contrastsecurity/bomsquad/AppGraphInfo.java rename to src/main/java/com/contrastsecurity/runtimeanalyst/AppGraphInfo.java index da338f3..c8e931e 100644 --- a/src/main/java/com/contrastsecurity/bomsquad/AppGraphInfo.java +++ b/src/main/java/com/contrastsecurity/runtimeanalyst/AppGraphInfo.java @@ -1,4 +1,4 @@ -package com.contrastsecurity.bomsquad; +package com.contrastsecurity.runtimeanalyst; import java.util.HashSet; import java.util.Set; diff --git a/src/main/java/com/contrastsecurity/bomsquad/ApplicationGraphFetcher.java b/src/main/java/com/contrastsecurity/runtimeanalyst/ApplicationGraphFetcher.java similarity index 99% rename from src/main/java/com/contrastsecurity/bomsquad/ApplicationGraphFetcher.java rename to src/main/java/com/contrastsecurity/runtimeanalyst/ApplicationGraphFetcher.java index 2492b71..d450541 100644 --- a/src/main/java/com/contrastsecurity/bomsquad/ApplicationGraphFetcher.java +++ b/src/main/java/com/contrastsecurity/runtimeanalyst/ApplicationGraphFetcher.java @@ -1,4 +1,4 @@ -package com.contrastsecurity.bomsquad; +package com.contrastsecurity.runtimeanalyst; import java.io.IOException; import java.util.HashMap; diff --git a/src/main/java/com/contrastsecurity/runtimeanalyst/AuthCommand.java b/src/main/java/com/contrastsecurity/runtimeanalyst/AuthCommand.java new file mode 100644 index 0000000..fab808b --- /dev/null +++ b/src/main/java/com/contrastsecurity/runtimeanalyst/AuthCommand.java @@ -0,0 +1,232 @@ +package com.contrastsecurity.runtimeanalyst; + +import com.microsoft.playwright.Browser; +import com.microsoft.playwright.BrowserContext; +import com.microsoft.playwright.BrowserType; +import com.microsoft.playwright.Page; +import com.microsoft.playwright.Playwright; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Duration; +import java.util.Base64; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * {@code auth}: sets up contrast.properties by driving a real, visible browser through the + * user's own login (including SSO/MFA), then reading their personal API key/service key/org id + * straight off the User Settings > Your Keys page's DOM - no manual copy/paste, and the tool + * never sees or stores a session cookie. + * + * The visible browser is used only for login. Once the org UUID appears in TeamServer's SPA + * hash route (#/<orgId>/...), its session cookies are exported and the visible browser is + * closed immediately - the account-page navigation and DOM scraping happen in a second, genuinely + * headless browser reusing those cookies, so nothing past the login screen itself ever renders + * on screen. (Earlier attempts to hide the same visible window via OS-level minimize/reposition + * tricks were unreliable - Chromium's automation-driven navigate() re-activates a minimized + * window, and macOS clamps windows to always keep part of them on screen - so this avoids that + * class of problem entirely instead of chasing it further.) + */ +public class AuthCommand { + + private static final Pattern UUID_IN_FRAGMENT = + Pattern.compile("#/([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})"); + + public static void main(String[] args) { + String host = null; + String outputPath = "contrast.properties"; + + for (int i = 0; i < args.length; i++) { + switch (args[i]) { + case "--host": + host = args[++i]; + break; + case "-o": + outputPath = args[++i]; + break; + case "-h": + case "--help": + printUsage(); + return; + default: + System.err.println("Unknown option: " + args[i]); + printUsage(); + System.exit(1); + } + } + + String loginUrl = (host != null ? host : "https://cs004.contrastsecurity.com") + "/login"; + + System.out.println("Opening a browser window - log in (or complete SSO/MFA) the same way you normally would."); + System.out.println("The window will close as soon as you're logged in; the rest happens in the background.\n"); + + try (Playwright playwright = Playwright.create()) { + String orgId; + String resolvedHost; + String storageState; + + try (Browser loginBrowser = launchChromium(playwright, false)) { + BrowserContext context = loginBrowser.newContext(); + context.newPage().navigate(loginUrl); + + LoggedInPage found = waitForOrgIdAcrossPages(loginBrowser); + orgId = extractOrgId(found.url()); + resolvedHost = hostFrom(found.url()); + storageState = found.page().context().storageState(); + System.out.println("Logged in - organization " + orgId + " on " + resolvedHost); + } + + String username; + String apiKey; + String serviceKey; + try (Browser scrapeBrowser = launchChromium(playwright, true)) { + BrowserContext context = scrapeBrowser.newContext( + new Browser.NewContextOptions().setStorageState(storageState)); + Page page = context.newPage(); + page.navigate(resolvedHost + "/Contrast/static/ng/index.html#/" + orgId + "/account"); + page.waitForSelector("[data-testid='service-key-code-block-code']"); + + username = page.locator("[data-e2e='contrast-username']").innerText().trim(); + apiKey = page.locator("[data-testid='api-key-code-block-code']").innerText().trim(); + serviceKey = page.locator("[data-testid='service-key-code-block-code']").innerText().trim(); + } + + String authHeader = Base64.getEncoder().encodeToString( + (username + ":" + serviceKey).getBytes(StandardCharsets.UTF_8)); + + System.out.println("Got your keys for " + username + ". Verifying against Contrast..."); + verifyCredentials(resolvedHost, authHeader, apiKey); + + writeConfig(outputPath, resolvedHost, orgId, authHeader, apiKey); + System.out.println("\nWrote " + outputPath + ". You're ready to run cbom/aibom/blueprint."); + } catch (Exception e) { + System.err.println("Auth failed: " + e.getMessage()); + System.exit(1); + } + } + + /** + * Playwright's Java library is bundled in this jar, but the actual Chromium binary it drives + * (~150-300MB) isn't - it has to exist on disk separately. Rather than making a first-time + * user run a separate install command themselves, download it automatically and transparently + * the first time it's missing, then retry. + */ + private static Browser launchChromium(Playwright playwright, boolean headless) throws Exception { + try { + return playwright.chromium().launch(new BrowserType.LaunchOptions().setHeadless(headless)); + } catch (Exception firstAttempt) { + System.out.println("Chromium isn't installed yet - downloading it now (one-time, ~150MB)..."); + installChromium(); + return playwright.chromium().launch(new BrowserType.LaunchOptions().setHeadless(headless)); + } + } + + /** + * Runs Playwright's browser installer in a separate JVM, not in-process - CLI.main() calls + * System.exit() internally (confirmed directly: code after the call never ran), which would + * kill this whole auth run right after installing, before ever getting to actually launch + * the browser it just downloaded. + */ + private static void installChromium() throws Exception { + String javaBin = System.getProperty("java.home") + File.separator + "bin" + File.separator + "java"; + String jarPath = new File(AuthCommand.class.getProtectionDomain().getCodeSource().getLocation().toURI()).getAbsolutePath(); + Process installer = new ProcessBuilder(javaBin, "-cp", jarPath, "com.microsoft.playwright.CLI", "install", "chromium") + .inheritIO() + .start(); + int result = installer.waitFor(); + if (result != 0) { + throw new IllegalStateException("Chromium install exited with code " + result); + } + } + + /** + * Polls every tab in every context on the browser for the org UUID that appears in + * TeamServer's SPA hash route once logged in, and returns whichever tab has it. Login can + * land the user in a different tab/context than the one this code opened, so every tab in + * every context on the browser has to be watched, not just the one Page reference created + * up front. + * + * Deliberately queries window.location.href via JS evaluation rather than trusting + * Page.url() - that property is a cache Playwright updates from CDP navigation events, and + * those events can be dropped/delayed (observed directly: an independent check confirmed the + * real browser had already navigated while Page.url() on this exact same page was still + * reporting the old URL). Runtime.evaluate round-trips to the live DOM instead of relying on + * that event-driven cache. + */ + private record LoggedInPage(Page page, String url) {} + + private static LoggedInPage waitForOrgIdAcrossPages(Browser browser) throws InterruptedException { + long deadline = System.currentTimeMillis() + Duration.ofMinutes(10).toMillis(); + while (System.currentTimeMillis() < deadline) { + for (BrowserContext ctx : browser.contexts()) { + for (Page p : ctx.pages()) { + String url; + try { + url = (String) p.evaluate("() => window.location.href"); + } catch (Exception e) { + continue; // page mid-navigation or otherwise transiently unqueryable + } + if (UUID_IN_FRAGMENT.matcher(url).find()) { + return new LoggedInPage(p, url); + } + } + } + Thread.sleep(200); + } + throw new IllegalStateException("Timed out waiting for login - didn't see an organization URL within 10 minutes"); + } + + private static String extractOrgId(String url) { + Matcher m = UUID_IN_FRAGMENT.matcher(url); + if (!m.find()) { + throw new IllegalStateException("No organization UUID found in " + url); + } + return m.group(1); + } + + private static String hostFrom(String url) { + URI uri = URI.create(url); + return uri.getScheme() + "://" + uri.getAuthority(); + } + + /** Confirms the scraped credentials actually authenticate before writing them to disk. */ + private static void verifyCredentials(String host, String authHeader, String apiKey) throws IOException, InterruptedException { + HttpClient client = HttpClient.newHttpClient(); + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(host + "/Contrast/api/ng/profile/organizations")) + .header("Authorization", authHeader) + .header("API-Key", apiKey) + .GET() + .build(); + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() != 200) { + throw new IllegalStateException("Credential check failed (HTTP " + response.statusCode() + ") - the scraped keys don't seem to work"); + } + } + + private static void writeConfig(String outputPath, String host, String orgId, String authHeader, String apiKey) throws IOException { + String contents = "contrast.url=" + host + "/api/ns-ui/v1\n" + + "contrast.org_id=" + orgId + "\n" + + "contrast.auth_header=" + authHeader + "\n" + + "contrast.api_key=" + apiKey + "\n"; + Files.writeString(Path.of(outputPath), contents); + } + + private static void printUsage() { + System.out.println("\nAuth - connect runtime-analyst to your Contrast account"); + System.out.println("\nOpens a real browser window, lets you log in (including SSO/MFA) the way you normally"); + System.out.println("would, then reads your personal API key/service key/org id off User Settings > Your Keys"); + System.out.println("directly - no manual copy/paste into the terminal. The window closes as soon as login"); + System.out.println("completes; everything after that runs in a background headless browser."); + System.out.println("\nUsage:"); + System.out.println(" java -jar runtime-analyst.jar auth [--host ] [-o ]"); + } +} diff --git a/src/main/java/com/contrastsecurity/bomsquad/CBOMGenerator.java b/src/main/java/com/contrastsecurity/runtimeanalyst/CBOMGenerator.java similarity index 94% rename from src/main/java/com/contrastsecurity/bomsquad/CBOMGenerator.java rename to src/main/java/com/contrastsecurity/runtimeanalyst/CBOMGenerator.java index bdae54d..62b14c6 100644 --- a/src/main/java/com/contrastsecurity/bomsquad/CBOMGenerator.java +++ b/src/main/java/com/contrastsecurity/runtimeanalyst/CBOMGenerator.java @@ -1,4 +1,4 @@ -package com.contrastsecurity.bomsquad; +package com.contrastsecurity.runtimeanalyst; import java.io.File; import java.io.FileInputStream; @@ -51,14 +51,14 @@ * Generates CycloneDX CBOM (Cryptography Bill of Materials) from Contrast API. * * Usage: - * java -jar bom-squad.jar cbom # Fetch all apps, output cbom.json - * java -jar bom-squad.jar cbom --app "AppName" # Fetch single app - * java -jar bom-squad.jar cbom --list # List available apps - * java -jar bom-squad.jar cbom -o custom.json # Custom output filename - * java -jar bom-squad.jar cbom -c config.properties # Use custom config file + * java -jar runtime-analyst.jar cbom # Fetch all apps, output cbom.json + * java -jar runtime-analyst.jar cbom --app "AppName" # Fetch single app + * java -jar runtime-analyst.jar cbom --list # List available apps + * java -jar runtime-analyst.jar cbom -o custom.json # Custom output filename + * java -jar runtime-analyst.jar cbom -c config.properties # Use custom config file * * Config file (contrast.properties): - * contrast.url=https://eval.contrastsecurity.com/api/ns-ui/v1 + * contrast.url=https://your-instance.contrastsecurity.com/api/ns-ui/v1 * contrast.org_id=your-org-id * contrast.auth_header=base64-encoded-credentials * contrast.api_key=your-api-key @@ -186,24 +186,24 @@ public static void main(String[] args) { private static void printUsage() { System.out.println("\nCBOM Generator - Create CycloneDX CBOM from Contrast observations"); System.out.println("\nUsage:"); - System.out.println(" java -jar bom-squad.jar cbom Generate CBOM for all apps"); - System.out.println(" java -jar bom-squad.jar cbom --app Filter by app (ID or name)"); - System.out.println(" java -jar bom-squad.jar cbom --env Filter by environment (PRODUCTION, DEVELOPMENT, QA)"); - System.out.println(" java -jar bom-squad.jar cbom --list List available applications with IDs"); - System.out.println(" java -jar bom-squad.jar cbom --analyze Run Quantum Advisor AI analysis after CBOM generation"); - System.out.println(" java -jar bom-squad.jar cbom -o Specify output filename"); - System.out.println(" java -jar bom-squad.jar cbom -c Use custom config file"); + System.out.println(" java -jar runtime-analyst.jar cbom Generate CBOM for all apps"); + System.out.println(" java -jar runtime-analyst.jar cbom --app Filter by app (ID or name)"); + System.out.println(" java -jar runtime-analyst.jar cbom --env Filter by environment (PRODUCTION, DEVELOPMENT, QA)"); + System.out.println(" java -jar runtime-analyst.jar cbom --list List available applications with IDs"); + System.out.println(" java -jar runtime-analyst.jar cbom --analyze Run Quantum Advisor AI analysis after CBOM generation"); + System.out.println(" java -jar runtime-analyst.jar cbom -o Specify output filename"); + System.out.println(" java -jar runtime-analyst.jar cbom -c Use custom config file"); System.out.println("\nConfig file (contrast.properties):"); - System.out.println(" contrast.url=https://eval.contrastsecurity.com/api/ns-ui/v1"); + System.out.println(" contrast.url=https://your-instance.contrastsecurity.com/api/ns-ui/v1"); System.out.println(" contrast.org_id=your-org-id"); System.out.println(" contrast.auth_header=base64-encoded-credentials"); System.out.println(" contrast.api_key=your-api-key"); System.out.println("\nExamples:"); - System.out.println(" java -jar bom-squad.jar cbom # all apps -> cbom.json"); - System.out.println(" java -jar bom-squad.jar cbom --env PRODUCTION # only prod observations"); - System.out.println(" java -jar bom-squad.jar cbom --app MyApp --env PRODUCTION"); - System.out.println(" java -jar bom-squad.jar cbom --analyze # generate CBOM + AI analysis report"); - System.out.println(" java -jar bom-squad.jar cbom -c prod.properties --list"); + System.out.println(" java -jar runtime-analyst.jar cbom # all apps -> cbom.json"); + System.out.println(" java -jar runtime-analyst.jar cbom --env PRODUCTION # only prod observations"); + System.out.println(" java -jar runtime-analyst.jar cbom --app MyApp --env PRODUCTION"); + System.out.println(" java -jar runtime-analyst.jar cbom --analyze # generate CBOM + AI analysis report"); + System.out.println(" java -jar runtime-analyst.jar cbom -c prod.properties --list"); } /** diff --git a/src/main/java/com/contrastsecurity/bomsquad/ClaudeClient.java b/src/main/java/com/contrastsecurity/runtimeanalyst/ClaudeClient.java similarity index 99% rename from src/main/java/com/contrastsecurity/bomsquad/ClaudeClient.java rename to src/main/java/com/contrastsecurity/runtimeanalyst/ClaudeClient.java index 615713d..485801e 100644 --- a/src/main/java/com/contrastsecurity/bomsquad/ClaudeClient.java +++ b/src/main/java/com/contrastsecurity/runtimeanalyst/ClaudeClient.java @@ -1,4 +1,4 @@ -package com.contrastsecurity.bomsquad; +package com.contrastsecurity.runtimeanalyst; import java.io.BufferedReader; import java.io.IOException; diff --git a/src/main/java/com/contrastsecurity/bomsquad/Finding.java b/src/main/java/com/contrastsecurity/runtimeanalyst/Finding.java similarity index 99% rename from src/main/java/com/contrastsecurity/bomsquad/Finding.java rename to src/main/java/com/contrastsecurity/runtimeanalyst/Finding.java index 9a59f26..08a93f5 100644 --- a/src/main/java/com/contrastsecurity/bomsquad/Finding.java +++ b/src/main/java/com/contrastsecurity/runtimeanalyst/Finding.java @@ -1,4 +1,4 @@ -package com.contrastsecurity.bomsquad; +package com.contrastsecurity.runtimeanalyst; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/com/contrastsecurity/runtimeanalyst/Main.java b/src/main/java/com/contrastsecurity/runtimeanalyst/Main.java new file mode 100644 index 0000000..78ea4c8 --- /dev/null +++ b/src/main/java/com/contrastsecurity/runtimeanalyst/Main.java @@ -0,0 +1,91 @@ +package com.contrastsecurity.runtimeanalyst; + +import java.io.File; +import java.util.Arrays; + +/** + * Single CLI entry point for the jar. Dispatches to CBOMGenerator or AIBOMGenerator + * based on the first argument. + * + * Usage: + * java -jar runtime-analyst.jar cbom [options] # Cryptography Bill of Materials + * java -jar runtime-analyst.jar aibom [options] # AI/LLM usage Bill of Materials + */ +public class Main { + + public static void main(String[] args) { + if (args.length == 0) { + printUsage(); + System.exit(1); + } + + String subcommand = args[0]; + String[] rest = Arrays.copyOfRange(args, 1, args.length); + + switch (subcommand) { + case "auth": + AuthCommand.main(rest); + break; + case "cbom": + ensureAuthenticated(rest); + CBOMGenerator.main(rest); + break; + case "aibom": + ensureAuthenticated(rest); + AIBOMGenerator.main(rest); + break; + case "cbom-advisor": + QuantumAdvisor.main(rest); + break; + case "aibom-advisor": + AIAdvisor.main(rest); + break; + case "--help": + case "-h": + printUsage(); + break; + default: + System.err.println("Unknown command: " + subcommand); + printUsage(); + System.exit(1); + } + } + + /** + * cbom/aibom/blueprint all need a contrast.properties to run - rather than making a user + * run `auth` themselves first, run it for them automatically the first time there's no + * config file yet at the path they'd otherwise be pointed at (respecting -c if given). + * Skipped when the command is just asking for help - that shouldn't require logging in. + */ + private static void ensureAuthenticated(String[] rest) { + if (Arrays.asList(rest).contains("-h") || Arrays.asList(rest).contains("--help")) { + return; + } + String configPath = "contrast.properties"; + for (int i = 0; i < rest.length - 1; i++) { + if ("-c".equals(rest[i])) { + configPath = rest[i + 1]; + break; + } + } + if (!new File(configPath).exists()) { + System.out.println("No " + configPath + " found - connecting to Contrast first.\n"); + AuthCommand.main(new String[] { "-o", configPath }); + } + } + + private static void printUsage() { + System.out.println("\nRuntime Analyst - Contrast Security Bill of Materials generator"); + System.out.println("\nUsage:"); + System.out.println(" java -jar runtime-analyst.jar auth [options] Connect to Contrast and generate contrast.properties"); + System.out.println(" java -jar runtime-analyst.jar cbom [options] Generate a Cryptography Bill of Materials"); + System.out.println(" java -jar runtime-analyst.jar aibom [options] Generate an AI/LLM usage Bill of Materials"); + System.out.println(" java -jar runtime-analyst.jar cbom-advisor Re-run the Quantum Advisor against an existing CBOM"); + System.out.println(" java -jar runtime-analyst.jar aibom-advisor Re-run the AI Advisor against an existing AI-BOM"); + System.out.println("\n`cbom --analyze` / `aibom --analyze` already run the matching advisor automatically after generation -"); + System.out.println("the standalone cbom-advisor/aibom-advisor commands are for re-running the advisor without regenerating the BOM."); + System.out.println("\nRun with -h after a subcommand for its options, e.g.:"); + System.out.println(" java -jar runtime-analyst.jar cbom -h"); + System.out.println(" java -jar runtime-analyst.jar aibom -h"); + } +} diff --git a/src/main/java/com/contrastsecurity/bomsquad/QuantumAdvisor.java b/src/main/java/com/contrastsecurity/runtimeanalyst/QuantumAdvisor.java similarity index 99% rename from src/main/java/com/contrastsecurity/bomsquad/QuantumAdvisor.java rename to src/main/java/com/contrastsecurity/runtimeanalyst/QuantumAdvisor.java index a7bfef6..628dc63 100644 --- a/src/main/java/com/contrastsecurity/bomsquad/QuantumAdvisor.java +++ b/src/main/java/com/contrastsecurity/runtimeanalyst/QuantumAdvisor.java @@ -1,4 +1,4 @@ -package com.contrastsecurity.bomsquad; +package com.contrastsecurity.runtimeanalyst; import java.io.File; import java.io.FileReader; @@ -29,7 +29,7 @@ * writes back a markdown report plus quantum:* risk properties on the CBOM itself. * * Usage: - * java -jar bom-squad.jar cbom-advisor cbom.json [-v] [-o report.md] [--json out.json] [--no-confirm] [--filter all|vulnerable|asymmetric] + * java -jar runtime-analyst.jar cbom-advisor cbom.json [-v] [-o report.md] [--json out.json] [--no-confirm] [--filter all|vulnerable|asymmetric] */ public class QuantumAdvisor { @@ -158,7 +158,7 @@ public static void main(String[] args) { else if (a.equals("--no-confirm")) noConfirm = true; else if (a.equals("--filter") && i + 1 < args.length) filter = args[++i]; else if (a.equals("-h") || a.equals("--help")) { - System.out.println("Usage: java -jar bom-squad.jar cbom-advisor [-v] [-o report.md] [--json out.json] [--no-confirm] [--filter all|vulnerable|asymmetric]"); + System.out.println("Usage: java -jar runtime-analyst.jar cbom-advisor [-v] [-o report.md] [--json out.json] [--no-confirm] [--filter all|vulnerable|asymmetric]"); return; } else if (!a.startsWith("-")) { cbomPath = a; diff --git a/src/main/java/com/contrastsecurity/bomsquad/QuantumReport.java b/src/main/java/com/contrastsecurity/runtimeanalyst/QuantumReport.java similarity index 97% rename from src/main/java/com/contrastsecurity/bomsquad/QuantumReport.java rename to src/main/java/com/contrastsecurity/runtimeanalyst/QuantumReport.java index c2bb197..27fa7b2 100644 --- a/src/main/java/com/contrastsecurity/bomsquad/QuantumReport.java +++ b/src/main/java/com/contrastsecurity/runtimeanalyst/QuantumReport.java @@ -1,4 +1,4 @@ -package com.contrastsecurity.bomsquad; +package com.contrastsecurity.runtimeanalyst; import java.io.File; import java.io.FileWriter; @@ -53,7 +53,7 @@ public static void main(String[] args) { } log = new File( args[0] ); } catch ( Exception e ) { - System.err.println( " Usage: java -jar bom-squad.jar contrast.log" ); + System.err.println( " Usage: java -jar runtime-analyst.jar contrast.log" ); System.exit( -1 ); } System.out.println( " Loading data from " + log );