From 1dff6086be763831a8845e3bb4ea24c3f6bf9dd5 Mon Sep 17 00:00:00 2001 From: Jeff Williams Date: Mon, 31 Aug 2026 16:28:22 -0400 Subject: [PATCH 1/7] Add GitHub Actions build/release workflows, bump version to 1.0 - build.yml: builds jar with Maven on push/PR to main - release.yml: on v* tag push, builds jar and publishes a GitHub Release with it attached - bump pom.xml version from 1.0-SNAPSHOT to 1.0 --- .github/workflows/build.yml | 29 +++++++++++++++++++++++++++++ .github/workflows/release.yml | 31 +++++++++++++++++++++++++++++++ README.md | 6 +++--- pom.xml | 2 +- 4 files changed, 64 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..a15ed88 --- /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 8 + uses: actions/setup-java@v4 + with: + java-version: '8' + distribution: 'temurin' + cache: maven + + - name: Build with Maven + run: mvn clean package + + - name: Upload jar artifact + uses: actions/upload-artifact@v4 + with: + name: bom-squad-jar + path: target/bom-squad-*.jar diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..adb2a00 --- /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 8 + uses: actions/setup-java@v4 + with: + java-version: '8' + 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/bom-squad-*.jar + generate_release_notes: true diff --git a/README.md b/README.md index 3a92bf0..6f487cb 100644 --- a/README.md +++ b/README.md @@ -52,10 +52,10 @@ contrast.api_key=your-api-key mvn clean package # CBOM -java -jar target/bom-squad-1.0-SNAPSHOT.jar cbom +java -jar target/bom-squad-1.0.jar cbom # AI-BOM -java -jar target/bom-squad-1.0-SNAPSHOT.jar aibom +java -jar target/bom-squad-1.0.jar aibom ``` ## Usage @@ -174,7 +174,7 @@ Two sample AI-BOM files are included to try it with: mvn clean package ``` -Creates `target/bom-squad-1.0-SNAPSHOT.jar` (executable uber-jar; `Main` dispatches to `cbom`/`aibom` based on the first argument). +Creates `target/bom-squad-1.0.jar` (executable uber-jar; `Main` dispatches to `cbom`/`aibom` based on the first argument). ## Configuration diff --git a/pom.xml b/pom.xml index a335bb5..f6b013b 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.contrastsecurity bom-squad - 1.0-SNAPSHOT + 1.0 BOM Squad Generates CycloneDX CBOM/AI-BOM inventories from Contrast Security runtime observability data, with an AI-powered advisor for each. From fbcfec5bed81e2740c1cecf035fed4684db14de1 Mon Sep 17 00:00:00 2001 From: Jeff Williams Date: Mon, 31 Aug 2026 16:36:06 -0400 Subject: [PATCH 2/7] Fix Java 8 build: replace String.repeat() (Java 11+) with literal strings CI builds with JDK 8 per pom.xml's maven.compiler.source/target, but String.repeat() requires Java 11+. Only compiled locally due to a newer JDK. --- src/main/java/com/contrastsecurity/bomsquad/AIAdvisor.java | 2 +- .../java/com/contrastsecurity/bomsquad/AIBOMGenerator.java | 4 ++-- .../java/com/contrastsecurity/bomsquad/CBOMGenerator.java | 4 ++-- src/main/java/com/contrastsecurity/bomsquad/ClaudeClient.java | 4 ++-- .../java/com/contrastsecurity/bomsquad/QuantumAdvisor.java | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/contrastsecurity/bomsquad/AIAdvisor.java b/src/main/java/com/contrastsecurity/bomsquad/AIAdvisor.java index 5348e41..c7389f6 100644 --- a/src/main/java/com/contrastsecurity/bomsquad/AIAdvisor.java +++ b/src/main/java/com/contrastsecurity/bomsquad/AIAdvisor.java @@ -199,7 +199,7 @@ private void run(String aibomPath, boolean verbose, String output, String jsonOu } System.out.println("\nReport written to " + output); } else { - System.out.println("\n" + "=".repeat(60)); + System.out.println("\n" + "============================================================"); System.out.println(report); } diff --git a/src/main/java/com/contrastsecurity/bomsquad/AIBOMGenerator.java b/src/main/java/com/contrastsecurity/bomsquad/AIBOMGenerator.java index df6af73..77d4f6a 100644 --- a/src/main/java/com/contrastsecurity/bomsquad/AIBOMGenerator.java +++ b/src/main/java/com/contrastsecurity/bomsquad/AIBOMGenerator.java @@ -194,9 +194,9 @@ private static void printUsage() { * assessment report, in-process (no Python required). */ private void runAIAdvisor(String aiBomFile) { - System.out.println("\n" + "=".repeat(60)); + System.out.println("\n" + "============================================================"); System.out.println("Running AI Advisor Analysis..."); - System.out.println("=".repeat(60)); + System.out.println("============================================================"); String advisorOutput = aiBomFile.replace(".json", "-advisor.md"); diff --git a/src/main/java/com/contrastsecurity/bomsquad/CBOMGenerator.java b/src/main/java/com/contrastsecurity/bomsquad/CBOMGenerator.java index bdae54d..eae22b5 100644 --- a/src/main/java/com/contrastsecurity/bomsquad/CBOMGenerator.java +++ b/src/main/java/com/contrastsecurity/bomsquad/CBOMGenerator.java @@ -211,9 +211,9 @@ private static void printUsage() { * post-quantum cryptography readiness report, in-process (no Python required). */ private void runQuantumAdvisor(String cbomFile) { - System.out.println("\n" + "=".repeat(60)); + System.out.println("\n" + "============================================================"); System.out.println("Running Quantum Advisor Analysis..."); - System.out.println("=".repeat(60)); + System.out.println("============================================================"); // Determine output filename (replace .json with -advisor.md) String advisorOutput = cbomFile.replace(".json", "-advisor.md"); diff --git a/src/main/java/com/contrastsecurity/bomsquad/ClaudeClient.java b/src/main/java/com/contrastsecurity/bomsquad/ClaudeClient.java index 615713d..a4cb757 100644 --- a/src/main/java/com/contrastsecurity/bomsquad/ClaudeClient.java +++ b/src/main/java/com/contrastsecurity/bomsquad/ClaudeClient.java @@ -161,9 +161,9 @@ public static boolean confirmCost(double estimatedCost, int numItems, boolean no } public void printSummary() { - System.out.println("\n" + "=".repeat(50)); + System.out.println("\n" + "=================================================="); System.out.println("AI Usage Summary"); - System.out.println("=".repeat(50)); + System.out.println("=================================================="); System.out.println(" Calls: " + totalCalls); System.out.println(" Input tokens: " + totalInputTokens); System.out.println(" Output tokens: " + totalOutputTokens); diff --git a/src/main/java/com/contrastsecurity/bomsquad/QuantumAdvisor.java b/src/main/java/com/contrastsecurity/bomsquad/QuantumAdvisor.java index a7bfef6..559c950 100644 --- a/src/main/java/com/contrastsecurity/bomsquad/QuantumAdvisor.java +++ b/src/main/java/com/contrastsecurity/bomsquad/QuantumAdvisor.java @@ -255,7 +255,7 @@ private void run(String cbomPath, boolean verbose, String output, String jsonOut } System.out.println("\nReport written to " + output); } else { - System.out.println("\n" + "=".repeat(60)); + System.out.println("\n" + "============================================================"); System.out.println(report); } From 60c68be783e453eb34ad7efaf27b95d4d0cc6594 Mon Sep 17 00:00:00 2001 From: Jeff Williams Date: Mon, 31 Aug 2026 16:56:42 -0400 Subject: [PATCH 3/7] Target Java 17 instead of Java 8 - pom.xml: source/target bumped from 1.8 to 17 - CI workflows: JDK 17 instead of JDK 8 - restore String.repeat() usage now that Java 11+ APIs are available - docs updated from Java 8+ to Java 17+ --- .github/workflows/build.yml | 4 ++-- .github/workflows/release.yml | 4 ++-- README.md | 2 +- pom.xml | 8 ++++---- .../java/com/contrastsecurity/bomsquad/AIAdvisor.java | 2 +- .../com/contrastsecurity/bomsquad/AIBOMGenerator.java | 4 ++-- .../java/com/contrastsecurity/bomsquad/CBOMGenerator.java | 4 ++-- .../java/com/contrastsecurity/bomsquad/ClaudeClient.java | 4 ++-- .../com/contrastsecurity/bomsquad/QuantumAdvisor.java | 2 +- 9 files changed, 17 insertions(+), 17 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a15ed88..3dc3e9a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,10 +12,10 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Set up JDK 8 + - name: Set up JDK 17 uses: actions/setup-java@v4 with: - java-version: '8' + java-version: '17' distribution: 'temurin' cache: maven diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index adb2a00..fb9f7a4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,10 +14,10 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Set up JDK 8 + - name: Set up JDK 17 uses: actions/setup-java@v4 with: - java-version: '8' + java-version: '17' distribution: 'temurin' cache: maven diff --git a/README.md b/README.md index 6f487cb..325fd0d 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Generate [CycloneDX](https://cyclonedx.org/) Bills of Materials from Contrast Se Both come with an AI-powered advisor report: **Quantum Advisor** (crypto risk) and **AI Advisor** (AI usage risk). -**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. +**Requirements:** Java 17+ 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. ## Why Contrast for This? diff --git a/pom.xml b/pom.xml index f6b013b..47ce81a 100644 --- a/pom.xml +++ b/pom.xml @@ -11,8 +11,8 @@ 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 @@ -50,8 +50,8 @@ maven-compiler-plugin 3.15.0 - 1.8 - 1.8 + 17 + 17 diff --git a/src/main/java/com/contrastsecurity/bomsquad/AIAdvisor.java b/src/main/java/com/contrastsecurity/bomsquad/AIAdvisor.java index c7389f6..5348e41 100644 --- a/src/main/java/com/contrastsecurity/bomsquad/AIAdvisor.java +++ b/src/main/java/com/contrastsecurity/bomsquad/AIAdvisor.java @@ -199,7 +199,7 @@ private void run(String aibomPath, boolean verbose, String output, String jsonOu } System.out.println("\nReport written to " + output); } else { - System.out.println("\n" + "============================================================"); + System.out.println("\n" + "=".repeat(60)); System.out.println(report); } diff --git a/src/main/java/com/contrastsecurity/bomsquad/AIBOMGenerator.java b/src/main/java/com/contrastsecurity/bomsquad/AIBOMGenerator.java index 77d4f6a..df6af73 100644 --- a/src/main/java/com/contrastsecurity/bomsquad/AIBOMGenerator.java +++ b/src/main/java/com/contrastsecurity/bomsquad/AIBOMGenerator.java @@ -194,9 +194,9 @@ private static void printUsage() { * assessment report, in-process (no Python required). */ private void runAIAdvisor(String aiBomFile) { - System.out.println("\n" + "============================================================"); + System.out.println("\n" + "=".repeat(60)); System.out.println("Running AI Advisor Analysis..."); - System.out.println("============================================================"); + System.out.println("=".repeat(60)); String advisorOutput = aiBomFile.replace(".json", "-advisor.md"); diff --git a/src/main/java/com/contrastsecurity/bomsquad/CBOMGenerator.java b/src/main/java/com/contrastsecurity/bomsquad/CBOMGenerator.java index eae22b5..bdae54d 100644 --- a/src/main/java/com/contrastsecurity/bomsquad/CBOMGenerator.java +++ b/src/main/java/com/contrastsecurity/bomsquad/CBOMGenerator.java @@ -211,9 +211,9 @@ private static void printUsage() { * post-quantum cryptography readiness report, in-process (no Python required). */ private void runQuantumAdvisor(String cbomFile) { - System.out.println("\n" + "============================================================"); + System.out.println("\n" + "=".repeat(60)); System.out.println("Running Quantum Advisor Analysis..."); - System.out.println("============================================================"); + System.out.println("=".repeat(60)); // Determine output filename (replace .json with -advisor.md) String advisorOutput = cbomFile.replace(".json", "-advisor.md"); diff --git a/src/main/java/com/contrastsecurity/bomsquad/ClaudeClient.java b/src/main/java/com/contrastsecurity/bomsquad/ClaudeClient.java index a4cb757..615713d 100644 --- a/src/main/java/com/contrastsecurity/bomsquad/ClaudeClient.java +++ b/src/main/java/com/contrastsecurity/bomsquad/ClaudeClient.java @@ -161,9 +161,9 @@ public static boolean confirmCost(double estimatedCost, int numItems, boolean no } public void printSummary() { - System.out.println("\n" + "=================================================="); + System.out.println("\n" + "=".repeat(50)); System.out.println("AI Usage Summary"); - System.out.println("=================================================="); + System.out.println("=".repeat(50)); System.out.println(" Calls: " + totalCalls); System.out.println(" Input tokens: " + totalInputTokens); System.out.println(" Output tokens: " + totalOutputTokens); diff --git a/src/main/java/com/contrastsecurity/bomsquad/QuantumAdvisor.java b/src/main/java/com/contrastsecurity/bomsquad/QuantumAdvisor.java index 559c950..a7bfef6 100644 --- a/src/main/java/com/contrastsecurity/bomsquad/QuantumAdvisor.java +++ b/src/main/java/com/contrastsecurity/bomsquad/QuantumAdvisor.java @@ -255,7 +255,7 @@ private void run(String cbomPath, boolean verbose, String output, String jsonOut } System.out.println("\nReport written to " + output); } else { - System.out.println("\n" + "============================================================"); + System.out.println("\n" + "=".repeat(60)); System.out.println(report); } From 1a12bd0b22a12203d070542bd426d0042857f87a Mon Sep 17 00:00:00 2001 From: Jeff Williams Date: Tue, 1 Sep 2026 11:35:36 -0400 Subject: [PATCH 4/7] Rename project to Runtime Analyst Renames com.contrastsecurity.bomsquad -> com.contrastsecurity.runtimeanalyst, the Maven artifact/jar (bom-squad -> runtime-analyst), and the GitHub repo (Contrast-Security-OSS/bom-squad -> Contrast-Security-OSS/runtime-analyst). Updates CI workflows, README, and CLAUDE.md accordingly. --- .github/workflows/build.yml | 4 +-- .github/workflows/release.yml | 2 +- README.md | 36 +++++++++---------- pom.xml | 8 ++--- .../AIAdvisor.java | 6 ++-- .../AIBOMGenerator.java | 26 +++++++------- .../AIUsageParser.java | 2 +- .../AlgorithmParser.java | 2 +- .../AppGraphInfo.java | 2 +- .../ApplicationGraphFetcher.java | 2 +- .../CBOMGenerator.java | 36 +++++++++---------- .../ClaudeClient.java | 2 +- .../{bomsquad => runtimeanalyst}/Finding.java | 2 +- .../{bomsquad => runtimeanalyst}/Main.java | 20 +++++------ .../QuantumAdvisor.java | 6 ++-- .../QuantumReport.java | 4 +-- 16 files changed, 80 insertions(+), 80 deletions(-) rename src/main/java/com/contrastsecurity/{bomsquad => runtimeanalyst}/AIAdvisor.java (99%) rename src/main/java/com/contrastsecurity/{bomsquad => runtimeanalyst}/AIBOMGenerator.java (96%) rename src/main/java/com/contrastsecurity/{bomsquad => runtimeanalyst}/AIUsageParser.java (98%) rename src/main/java/com/contrastsecurity/{bomsquad => runtimeanalyst}/AlgorithmParser.java (99%) rename src/main/java/com/contrastsecurity/{bomsquad => runtimeanalyst}/AppGraphInfo.java (94%) rename src/main/java/com/contrastsecurity/{bomsquad => runtimeanalyst}/ApplicationGraphFetcher.java (99%) rename src/main/java/com/contrastsecurity/{bomsquad => runtimeanalyst}/CBOMGenerator.java (95%) rename src/main/java/com/contrastsecurity/{bomsquad => runtimeanalyst}/ClaudeClient.java (99%) rename src/main/java/com/contrastsecurity/{bomsquad => runtimeanalyst}/Finding.java (99%) rename src/main/java/com/contrastsecurity/{bomsquad => runtimeanalyst}/Main.java (60%) rename src/main/java/com/contrastsecurity/{bomsquad => runtimeanalyst}/QuantumAdvisor.java (99%) rename src/main/java/com/contrastsecurity/{bomsquad => runtimeanalyst}/QuantumReport.java (97%) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3dc3e9a..e27db24 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,5 +25,5 @@ jobs: - name: Upload jar artifact uses: actions/upload-artifact@v4 with: - name: bom-squad-jar - path: target/bom-squad-*.jar + name: runtime-analyst-jar + path: target/runtime-analyst-*.jar diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fb9f7a4..2c80f5f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,5 +27,5 @@ jobs: - name: Create GitHub Release uses: softprops/action-gh-release@v2 with: - files: target/bom-squad-*.jar + files: target/runtime-analyst-*.jar generate_release_notes: true diff --git a/README.md b/README.md index 325fd0d..8e5274b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# BOM Squad +# Runtime Analyst Generate [CycloneDX](https://cyclonedx.org/) Bills of Materials from Contrast Security runtime observability data: @@ -52,10 +52,10 @@ contrast.api_key=your-api-key mvn clean package # CBOM -java -jar target/bom-squad-1.0.jar cbom +java -jar target/runtime-analyst-1.0.jar cbom # AI-BOM -java -jar target/bom-squad-1.0.jar aibom +java -jar target/runtime-analyst-1.0.jar aibom ``` ## Usage @@ -66,26 +66,26 @@ A single jar with two subcommands, `cbom` and `aibom`, taking the same set of fl ```bash # Generate CBOM for all apps -java -jar bom-squad.jar cbom +java -jar runtime-analyst.jar cbom # List available applications -java -jar bom-squad.jar cbom --list +java -jar runtime-analyst.jar cbom --list # 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 +java -jar runtime-analyst.jar cbom --app "MyApp" +java -jar runtime-analyst.jar cbom --app 7136cb1b-f846-4c1d-bdd3-77b448cbd2fe # Filter by environment -java -jar bom-squad.jar cbom --env PRODUCTION +java -jar runtime-analyst.jar cbom --env PRODUCTION # Combine filters -java -jar bom-squad.jar cbom --app "MyApp" --env PRODUCTION -o myapp-prod.json +java -jar runtime-analyst.jar cbom --app "MyApp" --env PRODUCTION -o myapp-prod.json # Use custom config file -java -jar bom-squad.jar cbom -c /path/to/config.properties +java -jar runtime-analyst.jar cbom -c /path/to/config.properties # Generate CBOM + AI-powered Quantum Advisor risk report -java -jar bom-squad.jar cbom --analyze +java -jar runtime-analyst.jar cbom --analyze ``` ### AI-BOM (AI/LLM usage) @@ -93,10 +93,10 @@ java -jar bom-squad.jar cbom --analyze Same flags, `aibom` subcommand: ```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 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 ``` ### Re-running an advisor against an existing BOM @@ -105,10 +105,10 @@ java -jar bom-squad.jar aibom --analyze ```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 cbom.json -o report.md # 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 aibom.json -o report.md ``` 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. @@ -174,7 +174,7 @@ Two sample AI-BOM files are included to try it with: mvn clean package ``` -Creates `target/bom-squad-1.0.jar` (executable uber-jar; `Main` dispatches to `cbom`/`aibom` based on the first argument). +Creates `target/runtime-analyst-1.0.jar` (executable uber-jar; `Main` dispatches to `cbom`/`aibom` based on the first argument). ## Configuration diff --git a/pom.xml b/pom.xml index 47ce81a..d8152c7 100644 --- a/pom.xml +++ b/pom.xml @@ -5,9 +5,9 @@ 4.0.0 com.contrastsecurity - bom-squad + runtime-analyst 1.0 - BOM Squad + Runtime Analyst Generates CycloneDX CBOM/AI-BOM inventories from Contrast Security runtime observability data, with an AI-powered advisor for each. @@ -55,7 +55,7 @@ - + org.apache.maven.plugins maven-shade-plugin @@ -69,7 +69,7 @@ - com.contrastsecurity.bomsquad.Main + com.contrastsecurity.runtimeanalyst.Main false 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 96% rename from src/main/java/com/contrastsecurity/bomsquad/AIBOMGenerator.java rename to src/main/java/com/contrastsecurity/runtimeanalyst/AIBOMGenerator.java index df6af73..5741c16 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,13 +175,13 @@ 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.org_id=your-org-id"); 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/bomsquad/CBOMGenerator.java b/src/main/java/com/contrastsecurity/runtimeanalyst/CBOMGenerator.java similarity index 95% rename from src/main/java/com/contrastsecurity/bomsquad/CBOMGenerator.java rename to src/main/java/com/contrastsecurity/runtimeanalyst/CBOMGenerator.java index bdae54d..ff6c84d 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,11 +51,11 @@ * 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 @@ -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.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/bomsquad/Main.java b/src/main/java/com/contrastsecurity/runtimeanalyst/Main.java similarity index 60% rename from src/main/java/com/contrastsecurity/bomsquad/Main.java rename to src/main/java/com/contrastsecurity/runtimeanalyst/Main.java index f35f658..9076121 100644 --- a/src/main/java/com/contrastsecurity/bomsquad/Main.java +++ b/src/main/java/com/contrastsecurity/runtimeanalyst/Main.java @@ -1,4 +1,4 @@ -package com.contrastsecurity.bomsquad; +package com.contrastsecurity.runtimeanalyst; import java.util.Arrays; @@ -7,8 +7,8 @@ * 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 + * 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 { @@ -46,16 +46,16 @@ public static void main(String[] args) { } private static void printUsage() { - System.out.println("\nBOM Squad - Contrast Security Bill of Materials generator"); + System.out.println("\nRuntime Analyst - 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(" 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 bom-squad.jar cbom -h"); - System.out.println(" java -jar bom-squad.jar aibom -h"); + 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 ); From 45b9a3c2aef9bc935cad89427c5e23395739a688 Mon Sep 17 00:00:00 2001 From: Jeff Williams Date: Tue, 1 Sep 2026 14:20:48 -0400 Subject: [PATCH 5/7] Replace eval.contrastsecurity.com with generic placeholder in help text Addresses PR #7 review comments from jason-at-contrast. --- .../com/contrastsecurity/runtimeanalyst/AIBOMGenerator.java | 2 +- .../com/contrastsecurity/runtimeanalyst/CBOMGenerator.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/contrastsecurity/runtimeanalyst/AIBOMGenerator.java b/src/main/java/com/contrastsecurity/runtimeanalyst/AIBOMGenerator.java index 5741c16..2dce5bd 100644 --- a/src/main/java/com/contrastsecurity/runtimeanalyst/AIBOMGenerator.java +++ b/src/main/java/com/contrastsecurity/runtimeanalyst/AIBOMGenerator.java @@ -183,7 +183,7 @@ private static void printUsage() { 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/runtimeanalyst/CBOMGenerator.java b/src/main/java/com/contrastsecurity/runtimeanalyst/CBOMGenerator.java index ff6c84d..62b14c6 100644 --- a/src/main/java/com/contrastsecurity/runtimeanalyst/CBOMGenerator.java +++ b/src/main/java/com/contrastsecurity/runtimeanalyst/CBOMGenerator.java @@ -58,7 +58,7 @@ * 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 @@ -194,7 +194,7 @@ private static void printUsage() { 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"); From 7931ae9d3752bddb0cab9e9fceebd4d8d5b7bc81 Mon Sep 17 00:00:00 2001 From: Jeff Williams Date: Tue, 1 Sep 2026 17:04:47 -0400 Subject: [PATCH 6/7] Add auth command to set up contrast.properties via browser login Drives a real, visible Chromium window through the user's own login (including SSO/MFA), then reads their personal API key/service key/org id straight off the User Settings > Your Keys page DOM. The visible browser is used only for login; once the org UUID appears in the post-login URL, its session cookies are exported via storageState() and the visible browser is closed immediately - the account-page navigation and scraping happen in a second, genuinely headless browser reusing those cookies, so nothing past the login screen itself ever renders on screen. Verifies the scraped credentials against a real API call before writing contrast.properties. --- .gitignore | 2 + pom.xml | 5 + .../runtimeanalyst/AuthCommand.java | 207 ++++++++++++++++++ .../contrastsecurity/runtimeanalyst/Main.java | 4 + 4 files changed, 218 insertions(+) create mode 100644 src/main/java/com/contrastsecurity/runtimeanalyst/AuthCommand.java 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/pom.xml b/pom.xml index d8152c7..861aa95 100644 --- a/pom.xml +++ b/pom.xml @@ -41,6 +41,11 @@ cyclonedx-core-java 13.0.0 + + com.microsoft.playwright + playwright + 1.49.0 + 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..91a2268 --- /dev/null +++ b/src/main/java/com/contrastsecurity/runtimeanalyst/AuthCommand.java @@ -0,0 +1,207 @@ +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.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); + } + } + + private static Browser launchChromium(Playwright playwright, boolean headless) { + try { + return playwright.chromium().launch(new BrowserType.LaunchOptions().setHeadless(headless)); + } catch (Exception e) { + System.err.println("Couldn't launch a browser. If this is the first run, install Playwright's browser with:"); + System.err.println(" mvn com.microsoft.playwright:playwright:1.49.0:install"); + throw new IllegalStateException(e.getMessage(), e); + } + } + + /** + * 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/runtimeanalyst/Main.java b/src/main/java/com/contrastsecurity/runtimeanalyst/Main.java index 9076121..d94b52b 100644 --- a/src/main/java/com/contrastsecurity/runtimeanalyst/Main.java +++ b/src/main/java/com/contrastsecurity/runtimeanalyst/Main.java @@ -22,6 +22,9 @@ public static void main(String[] args) { String[] rest = Arrays.copyOfRange(args, 1, args.length); switch (subcommand) { + case "auth": + AuthCommand.main(rest); + break; case "cbom": CBOMGenerator.main(rest); break; @@ -48,6 +51,7 @@ public static void main(String[] args) { 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"); From 43126b62f6a71c0c7bfbbac61eb396b1569a9d3a Mon Sep 17 00:00:00 2001 From: Jeff Williams Date: Wed, 2 Sep 2026 13:13:52 -0400 Subject: [PATCH 7/7] Bump Playwright to 1.62.0, auto-run auth when contrast.properties is missing, revamp README Playwright bump: 1.49.0 -> 1.62.0 (Chromium 131 -> Chrome for Testing 151). The old pinned version was ~13 minor releases and ~20 Chromium majors behind. AuthCommand now auto-installs the browser binary itself (in a separate JVM process, since Playwright's CLI.main() calls System.exit() internally and would otherwise kill this whole run) the first time it's missing, instead of telling the user to run a separate install command - no extra manual step for a first-time user. Main now runs auth automatically the first time cbom/aibom don't find a contrast.properties (respecting -c), then proceeds with the command actually requested - skipped for -h/--help. README: rewritten from scratch (purpose, --help output, authentication/usage, per-subcommand examples, then reference sections), with Why Contrast moved up near the top. --- README.md | 236 +++++++++++++----- pom.xml | 2 +- .../runtimeanalyst/AuthCommand.java | 35 ++- .../contrastsecurity/runtimeanalyst/Main.java | 26 ++ 4 files changed, 233 insertions(+), 66 deletions(-) diff --git a/README.md b/README.md index 8e5274b..94122e1 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,22 @@ # 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 17+ 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: + +**Option 1 - `auth` (recommended):** -- 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 +```bash +java -jar runtime-analyst.jar auth --host https://your-instance.contrastsecurity.com +``` -## Quick Start +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. -1. Create `contrast.properties` in your working directory: +**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 +``` + +A single jar, dispatched by subcommand: -# CBOM -java -jar target/runtime-analyst-1.0.jar cbom +| 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/runtime-analyst-1.0.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 runtime-analyst.jar cbom +# First-time setup against your instance +java -jar runtime-analyst.jar auth --host https://eval.contrastsecurity.com -# List available applications -java -jar runtime-analyst.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 runtime-analyst.jar cbom --app "MyApp" -java -jar runtime-analyst.jar cbom --app 7136cb1b-f846-4c1d-bdd3-77b448cbd2fe +### `cbom` -# Filter by environment -java -jar runtime-analyst.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 +```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 - -# Use custom config file -java -jar runtime-analyst.jar cbom -c /path/to/config.properties - -# Generate CBOM + AI-powered Quantum Advisor risk report -java -jar runtime-analyst.jar cbom --analyze +java -jar runtime-analyst.jar cbom -c prod.properties --list +java -jar runtime-analyst.jar cbom --analyze # + Quantum Advisor risk report ``` -### AI-BOM (AI/LLM usage) +### `aibom` -Same flags, `aibom` subcommand: +``` +$ 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 +``` ```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 +java -jar runtime-analyst.jar aibom --analyze # + AI Advisor governance report ``` -### Re-running an advisor against an existing BOM +### `blueprint` (alpha) -`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: +``` +$ 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 -# Crypto / post-quantum risk report -java -jar runtime-analyst.jar cbom-advisor cbom.json -o report.md +java -jar runtime-analyst.jar blueprint +java -jar runtime-analyst.jar blueprint --app "MyApp" --env PRODUCTION +``` + +### `cbom-advisor` / `aibom-advisor` + +Re-run an advisor against a BOM you already have, without regenerating it: + +``` +$ 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 runtime-analyst.jar aibom-advisor +Usage: java -jar runtime-analyst.jar aibom-advisor [-v] [-o report.md] [--json out.json] [--no-confirm] +``` + +```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) - just make sure `claude` is on your `PATH` and authenticated. +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/runtime-analyst-1.0.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 861aa95..c1b7c8e 100644 --- a/pom.xml +++ b/pom.xml @@ -44,7 +44,7 @@ com.microsoft.playwright playwright - 1.49.0 + 1.62.0 diff --git a/src/main/java/com/contrastsecurity/runtimeanalyst/AuthCommand.java b/src/main/java/com/contrastsecurity/runtimeanalyst/AuthCommand.java index 91a2268..fab808b 100644 --- a/src/main/java/com/contrastsecurity/runtimeanalyst/AuthCommand.java +++ b/src/main/java/com/contrastsecurity/runtimeanalyst/AuthCommand.java @@ -6,6 +6,7 @@ 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; @@ -112,13 +113,37 @@ public static void main(String[] args) { } } - private static Browser launchChromium(Playwright playwright, boolean headless) { + /** + * 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 e) { - System.err.println("Couldn't launch a browser. If this is the first run, install Playwright's browser with:"); - System.err.println(" mvn com.microsoft.playwright:playwright:1.49.0:install"); - throw new IllegalStateException(e.getMessage(), e); + } 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); } } diff --git a/src/main/java/com/contrastsecurity/runtimeanalyst/Main.java b/src/main/java/com/contrastsecurity/runtimeanalyst/Main.java index d94b52b..78ea4c8 100644 --- a/src/main/java/com/contrastsecurity/runtimeanalyst/Main.java +++ b/src/main/java/com/contrastsecurity/runtimeanalyst/Main.java @@ -1,5 +1,6 @@ package com.contrastsecurity.runtimeanalyst; +import java.io.File; import java.util.Arrays; /** @@ -26,9 +27,11 @@ public static void main(String[] args) { AuthCommand.main(rest); break; case "cbom": + ensureAuthenticated(rest); CBOMGenerator.main(rest); break; case "aibom": + ensureAuthenticated(rest); AIBOMGenerator.main(rest); break; case "cbom-advisor": @@ -48,6 +51,29 @@ public static void main(String[] args) { } } + /** + * 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:");