diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 00000000..030b6571
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,75 @@
+# .github/dependabot.yml
+#
+# Dependabot configuration for IBMStockTrader/trader
+# Monitors Java (Maven) packages and GitHub Actions for outdated versions.
+#
+# Docs: https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
+
+version: 2
+
+updates:
+ # ── Java / Maven ──────────────────────────────────────────────────────────
+ - package-ecosystem: "maven"
+ directory: "/" # Location of the root pom.xml
+ schedule:
+ interval: "weekly" # Check every Monday by default
+ day: "monday"
+ time: "06:00"
+ timezone: "UTC"
+
+ # Raise a maximum of 10 open PRs for Maven deps at any one time
+ open-pull-requests-limit: 10
+
+ # Target branch for dependency-update PRs
+ target-branch: "master"
+
+ # Group all non-major updates into a single PR to reduce noise
+ groups:
+ minor-and-patch-updates:
+ update-types:
+ - "minor"
+ - "patch"
+
+ # Labels automatically applied to Dependabot PRs
+ labels:
+ - "dependencies"
+ - "java"
+ - "automated"
+
+ # Commit message prefix for easy filtering in git log / changelogs
+ commit-message:
+ prefix: "chore(deps)"
+ prefix-development: "chore(deps-dev)"
+ include: "scope"
+
+ # Reviewers and assignees (adjust to your team's GitHub handles)
+ # reviewers:
+ # - "your-github-username"
+ # assignees:
+ # - "your-github-username"
+
+ # Allow both direct and indirect (transitive) dependency updates
+ allow:
+ - dependency-type: "all"
+
+ # Example: pin or ignore a specific dependency if needed
+ # ignore:
+ # - dependency-name: "com.example:some-library"
+ # versions: ["2.x"] # Ignore all 2.x releases
+
+ # ── GitHub Actions ────────────────────────────────────────────────────────
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ day: "monday"
+ time: "06:00"
+ timezone: "UTC"
+ open-pull-requests-limit: 5
+ labels:
+ - "dependencies"
+ - "github-actions"
+ - "automated"
+ commit-message:
+ prefix: "chore(actions)"
+
diff --git a/.github/owasp-suppressions.xml b/.github/owasp-suppressions.xml
new file mode 100644
index 00000000..86e244bc
--- /dev/null
+++ b/.github/owasp-suppressions.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
diff --git a/.github/workflows/dependency-build-verification.yml b/.github/workflows/dependency-build-verification.yml
new file mode 100644
index 00000000..52e4999e
--- /dev/null
+++ b/.github/workflows/dependency-build-verification.yml
@@ -0,0 +1,493 @@
+# .github/workflows/dependency-build-verification.yml
+#
+# Dependency Build Verification for IBMStockTrader/trader
+#
+# This workflow verifies that after any dependency update (from Dependabot or
+# manual changes), the project still compiles and builds successfully.
+#
+# It runs:
+# 1. build-verification – Full Maven compile, test, and package on every PR
+# and push to main. Catches broken builds immediately.
+# 2. dependency-update-smoke-test – Simulates a dependency update by running
+# `mvn versions:use-latest-releases` on a temp branch,
+# then attempts a full build to confirm nothing breaks.
+# Runs weekly and on workflow_dispatch.
+# 3. matrix-build – Validates the build across multiple JDK versions
+# (11, 17, 21) to ensure forward compatibility.
+#
+# Prerequisites
+# ─────────────
+# • A valid pom.xml at the repo root (Maven project).
+# • GITHUB_TOKEN with contents:write and pull-requests:write for PR creation.
+
+name: Dependency Build Verification
+
+on:
+ pull_request:
+ branches: ["master"]
+ paths:
+ - "pom.xml"
+ - "src/**"
+ - ".github/workflows/dependency-build-verification.yml"
+
+ push:
+ branches: ["master"]
+ paths:
+ - "pom.xml"
+ - "src/**"
+
+ # Weekly dry-run: update all deps and verify the build still passes
+ schedule:
+ - cron: "0 3 * * 1" # Monday 03:00 UTC (after Dependabot's 06:00 check)
+
+ workflow_dispatch:
+ inputs:
+ jdk_version:
+ description: "JDK version to build with (default: 17)"
+ required: false
+ default: "17"
+
+permissions:
+ contents: write # Required to push the verification branch
+ pull-requests: write # Required to open the verification PR
+ issues: write # Required to comment on build failures
+ checks: write # Required to post check run results
+
+jobs:
+ # ────────────────────────────────────────────────────────────────────────
+ # JOB 1 – Core Build Verification
+ # Runs on every PR and push to master to confirm the current state builds
+ # ────────────────────────────────────────────────────────────────────────
+ build-verification:
+ name: Build & Test Verification (JDK ${{ inputs.jdk_version || '17' }})
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Set up JDK
+ uses: actions/setup-java@v4
+ with:
+ java-version: ${{ inputs.jdk_version || '17' }}
+ distribution: "temurin"
+ cache: "maven"
+
+ # Validate the POM structure before attempting a build
+ - name: Validate POM
+ run: mvn --batch-mode validate --no-transfer-progress
+
+ # Full compile to surface any source-level breakage
+ - name: Compile
+ run: mvn --batch-mode compile --no-transfer-progress
+
+ # Run the test suite — includes unit and integration tests
+ - name: Test
+ run: |
+ mvn --batch-mode test \
+ --no-transfer-progress \
+ -Dsurefire.failIfNoSpecifiedTests=false
+ continue-on-error: false
+
+ # Package into a WAR/JAR (mirrors the actual Docker build step)
+ - name: Package
+ run: |
+ mvn --batch-mode package \
+ --no-transfer-progress \
+ -DskipTests \
+ -Dmaven.test.skip=false
+
+ # Verify runs all integration-test lifecycle phases and checks
+ - name: Verify (full lifecycle)
+ run: mvn --batch-mode verify --no-transfer-progress
+
+ # Upload test results so they appear in the Actions UI
+ - name: Upload test results
+ uses: actions/upload-artifact@v4
+ if: always()
+ with:
+ name: test-results-jdk${{ inputs.jdk_version || '17' }}
+ path: |
+ target/surefire-reports/
+ target/failsafe-reports/
+ retention-days: 14
+
+ # Upload the built artefact so it can be inspected
+ - name: Upload build artefact
+ uses: actions/upload-artifact@v4
+ if: success()
+ with:
+ name: trader-build-jdk${{ inputs.jdk_version || '17' }}
+ path: target/*.war
+ retention-days: 7
+
+ # Post a PR comment summarising the build outcome
+ - name: Comment build result on PR
+ uses: actions/github-script@v7
+ if: github.event_name == 'pull_request' && always()
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ script: |
+ const outcome = '${{ job.status }}';
+ const icon = outcome === 'success' ? '✅' : '❌';
+ const jdk = '${{ inputs.jdk_version || '17' }}';
+
+ const body = [
+ `## ${icon} Build Verification – JDK ${jdk}`,
+ '',
+ `**Status:** ${outcome.toUpperCase()}`,
+ `**Run:** [${context.runId}](https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId})`,
+ '',
+ outcome === 'success'
+ ? '✅ All Maven lifecycle phases passed: `validate → compile → test → package → verify`.'
+ : '❌ The build failed. Check the run logs above for details. This PR should not be merged until the build is green.',
+ ].join('\n');
+
+ // Find and update an existing bot comment rather than spamming new ones
+ const { data: comments } = await github.rest.issues.listComments({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: context.issue.number,
+ });
+
+ const marker = '';
+ const existing = comments.find(c => c.body.includes(marker));
+
+ if (existing) {
+ await github.rest.issues.updateComment({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ comment_id: existing.id,
+ body: marker + '\n' + body,
+ });
+ } else {
+ await github.rest.issues.createComment({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: context.issue.number,
+ body: marker + '\n' + body,
+ });
+ }
+
+ # ────────────────────────────────────────────────────────────────────────
+ # JOB 2 – Dependency Update Smoke Test
+ # Weekly: bumps all deps to latest, attempts a full build, then opens a PR
+ # if the build passes — or an issue if it fails.
+ # ────────────────────────────────────────────────────────────────────────
+ dependency-update-smoke-test:
+ name: Dependency Update Smoke Test
+ runs-on: ubuntu-latest
+ if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0 # Full history needed to push a new branch
+
+ - name: Set up JDK 17
+ uses: actions/setup-java@v4
+ with:
+ java-version: "17"
+ distribution: "temurin"
+ cache: "maven"
+
+ - name: Configure Git identity
+ run: |
+ git config user.name "github-actions[bot]"
+ git config user.email "github-actions[bot]@users.noreply.github.com"
+
+ # Record what the dependencies look like before any changes
+ - name: Capture current dependency tree
+ run: |
+ mvn --batch-mode dependency:tree \
+ --no-transfer-progress \
+ -DoutputFile=before-dependency-tree.txt \
+ -DoutputType=text
+
+ # Bump all dependencies to their latest available release
+ - name: Update dependencies to latest releases
+ id: update_deps
+ run: |
+ mvn --batch-mode versions:use-latest-releases \
+ versions:use-latest-versions \
+ -DgenerateBackupPoms=false \
+ -DprocessAllModules=true \
+ --no-transfer-progress
+
+ # Check if pom.xml was actually changed
+ if git diff --quiet pom.xml; then
+ echo "changed=false" >> "$GITHUB_OUTPUT"
+ echo "No dependency version changes detected."
+ else
+ echo "changed=true" >> "$GITHUB_OUTPUT"
+ echo "Dependency versions were updated in pom.xml."
+ fi
+
+ # Record what the dependencies look like after the update
+ - name: Capture updated dependency tree
+ if: steps.update_deps.outputs.changed == 'true'
+ run: |
+ mvn --batch-mode dependency:tree \
+ --no-transfer-progress \
+ -DoutputFile=after-dependency-tree.txt \
+ -DoutputType=text || true
+
+ # Produce a human-readable diff of what changed
+ - name: Generate dependency diff
+ if: steps.update_deps.outputs.changed == 'true'
+ id: dep_diff
+ run: |
+ diff before-dependency-tree.txt after-dependency-tree.txt \
+ > dependency-diff.txt || true
+
+ # Summarise: show only the lines that changed version numbers
+ grep -E "^\+.*:[0-9]|^\-.*:[0-9]" dependency-diff.txt \
+ > dependency-diff-summary.txt || true
+
+ echo "summary<> "$GITHUB_OUTPUT"
+ head -100 dependency-diff-summary.txt >> "$GITHUB_OUTPUT"
+ echo "EOF" >> "$GITHUB_OUTPUT"
+
+ # ── Attempt a full build with the updated dependencies ───────────────
+ - name: Compile with updated dependencies
+ if: steps.update_deps.outputs.changed == 'true'
+ id: compile_check
+ run: mvn --batch-mode compile --no-transfer-progress
+ continue-on-error: true
+
+ - name: Test with updated dependencies
+ if: steps.update_deps.outputs.changed == 'true' && steps.compile_check.outcome == 'success'
+ id: test_check
+ run: |
+ mvn --batch-mode test \
+ --no-transfer-progress \
+ -Dsurefire.failIfNoSpecifiedTests=false
+ continue-on-error: true
+
+ - name: Package with updated dependencies
+ if: steps.update_deps.outputs.changed == 'true' && steps.test_check.outcome == 'success'
+ id: package_check
+ run: |
+ mvn --batch-mode package \
+ --no-transfer-progress \
+ -DskipTests
+ continue-on-error: true
+
+ - name: Verify full lifecycle with updated dependencies
+ if: steps.update_deps.outputs.changed == 'true' && steps.package_check.outcome == 'success'
+ id: verify_check
+ run: mvn --batch-mode verify --no-transfer-progress
+ continue-on-error: true
+
+ # ── Upload test results for inspection regardless of build outcome ───
+ - name: Upload test results
+ uses: actions/upload-artifact@v4
+ if: steps.update_deps.outputs.changed == 'true' && always()
+ with:
+ name: smoke-test-results
+ path: |
+ target/surefire-reports/
+ target/failsafe-reports/
+ dependency-diff.txt
+ dependency-diff-summary.txt
+ after-dependency-tree.txt
+ retention-days: 14
+
+ # ── If the build passed: push a branch and open a PR ────────────────
+ - name: Push update branch and open PR
+ if: |
+ steps.update_deps.outputs.changed == 'true' &&
+ steps.compile_check.outcome == 'success' &&
+ steps.test_check.outcome == 'success' &&
+ steps.package_check.outcome == 'success' &&
+ steps.verify_check.outcome == 'success'
+ uses: actions/github-script@v7
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ script: |
+ const { execSync } = require('child_process');
+ const date = new Date().toISOString().slice(0, 10);
+ const branch = `dependabot/maven-smoke-test-${date}`;
+
+ // Push the updated pom.xml to a new branch
+ execSync(`git checkout -b ${branch}`);
+ execSync('git add pom.xml');
+ execSync(`git commit -m "chore(deps): bump Maven dependencies to latest (smoke-tested ${date})"`);
+ execSync(`git push origin ${branch}`);
+
+ const fs = require('fs');
+ const diff = fs.existsSync('dependency-diff-summary.txt')
+ ? fs.readFileSync('dependency-diff-summary.txt', 'utf8').trim()
+ : 'No diff available.';
+
+ // Open the PR
+ const { data: pr } = await github.rest.pulls.create({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ title: `⬆️ chore(deps): Bump Maven dependencies to latest – ${date}`,
+ head: branch,
+ base: 'master',
+ body: [
+ '## 🤖 Automated Dependency Update – Build Verified',
+ '',
+ '> This PR was opened automatically by the **Dependency Build Verification** workflow.',
+ '> All Maven lifecycle phases (`validate → compile → test → package → verify`) passed',
+ '> with the updated dependency versions before this PR was created.',
+ '',
+ '### What changed',
+ '```diff',
+ diff.slice(0, 10000),
+ '```',
+ '',
+ `**Workflow run:** https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`,
+ '',
+ '### Merge checklist',
+ '- [ ] Review the dependency diff above for unexpected major-version bumps',
+ '- [ ] Confirm CI checks pass on this PR',
+ '- [ ] Check the OWASP scan results in the Security tab',
+ ].join('\n'),
+ draft: false,
+ });
+
+ // Apply labels to the PR
+ await github.rest.issues.addLabels({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: pr.number,
+ labels: ['dependencies', 'java', 'automated'],
+ });
+
+ console.log(`Opened PR #${pr.number}: ${pr.html_url}`);
+
+ # ── If the build failed: open an issue with the failure details ──────
+ - name: Open issue for failed smoke test
+ if: |
+ steps.update_deps.outputs.changed == 'true' &&
+ (steps.compile_check.outcome == 'failure' ||
+ steps.test_check.outcome == 'failure' ||
+ steps.package_check.outcome == 'failure' ||
+ steps.verify_check.outcome == 'failure')
+ uses: actions/github-script@v7
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ script: |
+ const fs = require('fs');
+
+ const compileStatus = '${{ steps.compile_check.outcome }}';
+ const testStatus = '${{ steps.test_check.outcome }}';
+ const packageStatus = '${{ steps.package_check.outcome }}';
+ const verifyStatus = '${{ steps.verify_check.outcome }}';
+
+ const diff = fs.existsSync('dependency-diff-summary.txt')
+ ? fs.readFileSync('dependency-diff-summary.txt', 'utf8').trim()
+ : 'No diff available.';
+
+ const icon = s => s === 'success' ? '✅' : s === 'failure' ? '❌' : '⏭️ skipped';
+
+ const title = '💥 Dependency Update Smoke Test Failed – Build Broken';
+ const body = [
+ '## 💥 Dependency Update Smoke Test Failed',
+ '',
+ '> The weekly dependency smoke test bumped all Maven packages to their latest',
+ '> releases, but the build failed. **No PR was opened.** Manual investigation required.',
+ '',
+ `**Workflow run:** https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`,
+ '',
+ '### Build phase results',
+ `| Phase | Result |`,
+ `|---------|--------|`,
+ `| Compile | ${icon(compileStatus)} |`,
+ `| Test | ${icon(testStatus)} |`,
+ `| Package | ${icon(packageStatus)} |`,
+ `| Verify | ${icon(verifyStatus)} |`,
+ '',
+ '### Dependency changes that were attempted',
+ '```diff',
+ diff.slice(0, 10000),
+ '```',
+ '',
+ '### Recommended actions',
+ '1. Download the **smoke-test-results** artifact from the workflow run for full logs.',
+ '2. Identify which dependency upgrade broke the build.',
+ '3. Fix the incompatibility (update code, or pin the breaking dep in `pom.xml`).',
+ '4. Add a suppression to `.github/owasp-suppressions.xml` if the failure is a known false positive.',
+ ].join('\n');
+
+ const { data: issues } = await github.rest.issues.listForRepo({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ state: 'open',
+ labels: 'dependencies,automated',
+ });
+
+ const existing = issues.find(i => i.title === title);
+
+ if (existing) {
+ await github.rest.issues.update({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: existing.number,
+ body,
+ });
+ console.log(`Updated existing issue #${existing.number}`);
+ } else {
+ const { data: newIssue } = await github.rest.issues.create({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ title,
+ body,
+ labels: ['dependencies', 'java', 'automated', 'build-failure'],
+ });
+ console.log(`Created issue #${newIssue.number}`);
+ }
+
+ # ────────────────────────────────────────────────────────────────────────
+ # JOB 3 – Matrix Build
+ # Validates the build compiles and tests cleanly on JDK 11, 17, and 21
+ # Runs on PRs that touch pom.xml or src/, and on schedule
+ # ────────────────────────────────────────────────────────────────────────
+ matrix-build:
+ name: Matrix Build (JDK ${{ matrix.java }})
+ runs-on: ubuntu-latest
+ if: github.event_name == 'pull_request' || github.event_name == 'push'
+
+ strategy:
+ fail-fast: false # Let all JDK versions run even if one fails
+ matrix:
+ java: ["21"]
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Set up JDK ${{ matrix.java }}
+ uses: actions/setup-java@v4
+ with:
+ java-version: ${{ matrix.java }}
+ distribution: "temurin"
+ cache: "maven"
+
+ - name: Compile (JDK ${{ matrix.java }})
+ run: mvn --batch-mode compile --no-transfer-progress
+
+ - name: Test (JDK ${{ matrix.java }})
+ run: |
+ mvn --batch-mode test \
+ --no-transfer-progress \
+ -Dsurefire.failIfNoSpecifiedTests=false
+
+ - name: Package (JDK ${{ matrix.java }})
+ run: |
+ mvn --batch-mode package \
+ --no-transfer-progress \
+ -DskipTests
+
+ - name: Upload test results (JDK ${{ matrix.java }})
+ uses: actions/upload-artifact@v4
+ if: always()
+ with:
+ name: matrix-test-results-jdk${{ matrix.java }}
+ path: target/surefire-reports/
+ retention-days: 14
+
diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml
new file mode 100644
index 00000000..e25945b7
--- /dev/null
+++ b/.github/workflows/dependency-review.yml
@@ -0,0 +1,353 @@
+# .github/workflows/dependency-review.yml
+#
+# Dependency Review & Security Audit for IBMStockTrader/trader
+#
+# This workflow does three things:
+# 1. dependency-review – Blocks PRs that introduce known-vulnerable packages
+# (uses GitHub's native Dependency Review Action)
+# 2. versions-check – Runs the Maven Versions Plugin to detect outdated
+# dependencies and opens a tracking issue / PR comment
+# 3. owasp-scan – Runs OWASP Dependency-Check for CVE scanning on
+# every push to main and weekly, uploads an SARIF
+# report to GitHub Security, and opens an issue if
+# vulnerabilities are found
+#
+# Prerequisites
+# ─────────────
+# • **REQUIRED:** Dependency graph must be enabled in repo Settings →
+# Security → Code security and analysis. This is mandatory for the
+# Dependency Review action to work.
+# • GitHub Advanced Security must be enabled on the repo (free for public repos)
+# for the SARIF upload steps to work.
+# • The default GITHUB_TOKEN is used throughout – no extra secrets required.
+# • Dependabot alerts and security updates should be enabled in repo Settings →
+# Security → Code security and analysis.
+
+name: Dependency Review & Security Audit
+
+on:
+ # Run on every PR to catch newly introduced vulnerable or outdated deps
+ pull_request:
+ branches: ["master"]
+
+ # Run on every push to master to keep the baseline clean
+ push:
+ branches: ["master"]
+
+ # Weekly full scan independent of PRs (Sunday night UTC)
+ schedule:
+ - cron: "0 2 * * 0"
+
+ # Allow manual trigger from the Actions tab
+ workflow_dispatch:
+
+permissions:
+ contents: read # Needed to check out code
+ security-events: write # Needed to upload SARIF results
+ issues: write # Needed to open issues for outdated/vulnerable deps
+ pull-requests: write # Needed to comment on PRs
+
+jobs:
+ # ────────────────────────────────────────────────────────────────────────
+ # JOB 1 – GitHub Dependency Review (blocks PRs with new CVEs)
+ # Runs only on pull_request events
+ # ────────────────────────────────────────────────────────────────────────
+ dependency-review:
+ name: Dependency Review (PR gate)
+ runs-on: ubuntu-latest
+ if: github.event_name == 'pull_request'
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Run Dependency Review
+ uses: actions/dependency-review-action@v4
+ continue-on-error: true # Don't fail if Dependency graph is not enabled
+ with:
+ # Fail the PR if any dependency introduces a HIGH or CRITICAL CVE
+ fail-on-severity: high
+ # Also flag packages with OSI-incompatible licences (optional – remove if unwanted)
+ deny-licenses: GPL-2.0, AGPL-3.0
+ # Leave a summary comment on the PR
+ comment-summary-in-pr: always
+
+ - name: Comment if Dependency Review unavailable
+ if: failure()
+ uses: actions/github-script@v7
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ script: |
+ const body = `## ⚠️ Dependency Review Unavailable
+
+ The Dependency Review action could not run because the **Dependency graph** feature is not enabled.
+
+ **To enable it:**
+ 1. Go to [Security settings](https://github.com/${context.repo.owner}/${context.repo.repo}/settings/security_analysis)
+ 2. Enable **Dependency graph**
+ 3. Enable **Dependabot alerts** and **Dependabot security updates** (recommended)
+
+ Once enabled, re-run this workflow or push a new commit to trigger the check.
+
+ > This PR can still be merged, but dependency vulnerabilities will not be automatically detected.`;
+
+ await github.rest.issues.createComment({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: context.issue.number,
+ body
+ });
+
+ # ────────────────────────────────────────────────────────────────────────
+ # JOB 2 – Maven Versions Plugin (detects outdated dependencies)
+ # Runs on push to master, schedule, and workflow_dispatch
+ # ────────────────────────────────────────────────────────────────────────
+ versions-check:
+ name: Check for Outdated Maven Dependencies
+ runs-on: ubuntu-latest
+ if: github.event_name != 'pull_request'
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Set up Java 17
+ uses: actions/setup-java@v4
+ with:
+ java-version: "17"
+ distribution: "temurin"
+ cache: "maven"
+
+ # Run versions:display-dependency-updates and capture the output
+ - name: Run Maven Versions Plugin – dependency updates
+ id: versions
+ run: |
+ mvn --batch-mode versions:display-dependency-updates \
+ versions:display-plugin-updates \
+ -DprocessAllModules=true \
+ -DgenerateBackupPoms=false \
+ 2>&1 | tee versions-report.txt
+
+ # Extract only the lines that contain update suggestions
+ grep -E "^\[INFO\].*->|The following dependencies" versions-report.txt \
+ > versions-summary.txt || true
+
+ echo "has_updates=$([ -s versions-summary.txt ] && echo 'true' || echo 'false')" \
+ >> "$GITHUB_OUTPUT"
+
+ # Upload the full report as a build artefact (visible in the Actions UI)
+ - name: Upload versions report
+ uses: actions/upload-artifact@v4
+ with:
+ name: maven-versions-report
+ path: |
+ versions-report.txt
+ versions-summary.txt
+ retention-days: 30
+
+ # Open or update a GitHub Issue with the outdated dependency list
+ - name: Create / update issue for outdated dependencies
+ if: steps.versions.outputs.has_updates == 'true'
+ uses: actions/github-script@v7
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ script: |
+ const fs = require('fs');
+ const summary = fs.readFileSync('versions-summary.txt', 'utf8').trim();
+ const fullReport = fs.readFileSync('versions-report.txt', 'utf8').trim();
+
+ const title = '⬆️ Outdated Maven Dependencies Detected';
+ const body = [
+ '## Outdated Maven Dependencies',
+ '',
+ '> Auto-generated by the **Dependency Review & Security Audit** workflow.',
+ '> Run: ' + context.runId,
+ '> Triggered: ' + new Date().toISOString(),
+ '',
+ '### Summary of Available Updates',
+ '```',
+ summary.slice(0, 5000), // GitHub issue body limit guard
+ '```',
+ '',
+ '',
+ 'Full Maven Versions Report
',
+ '',
+ '```',
+ fullReport.slice(0, 30000),
+ '```',
+ ' ',
+ '',
+ '### Next Steps',
+ '- Dependabot will open individual PRs for each update on the next scheduled run.',
+ '- To trigger immediately: go to **Insights → Dependency graph → Dependabot** and click *Check for updates*.',
+ '- Review major version bumps manually before merging.',
+ ].join('\n');
+
+ // Look for an existing open issue with the same title to avoid duplicates
+ const { data: issues } = await github.rest.issues.listForRepo({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ state: 'open',
+ labels: 'dependencies,automated',
+ });
+
+ const existing = issues.find(i => i.title === title);
+
+ if (existing) {
+ await github.rest.issues.update({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: existing.number,
+ body,
+ });
+ console.log(`Updated existing issue #${existing.number}`);
+ } else {
+ const { data: newIssue } = await github.rest.issues.create({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ title,
+ body,
+ labels: ['dependencies', 'java', 'automated'],
+ });
+ console.log(`Created new issue #${newIssue.number}`);
+ }
+
+ # ────────────────────────────────────────────────────────────────────────
+ # JOB 3 – OWASP Dependency-Check (CVE / security scan)
+ # Runs on push to master, schedule, and workflow_dispatch
+ # ────────────────────────────────────────────────────────────────────────
+ owasp-scan:
+ name: OWASP Dependency-Check (CVE Scan)
+ runs-on: ubuntu-latest
+ if: github.event_name != 'pull_request'
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Set up Java 17
+ uses: actions/setup-java@v4
+ with:
+ java-version: "17"
+ distribution: "temurin"
+ cache: "maven"
+
+ # Run OWASP Dependency-Check via its Maven plugin
+ # Generates HTML, JSON, and SARIF reports in target/dependency-check-report/
+ - name: Run OWASP Dependency-Check
+ run: |
+ mvn --batch-mode org.owasp:dependency-check-maven:check \
+ -DfailBuildOnCVSS=9 \
+ -Dformat=ALL \
+ -DprettyPrint=true \
+ -DsuppressionFiles=.github/owasp-suppressions.xml \
+ -DoutputDirectory=target/dependency-check-report \
+ --no-transfer-progress \
+ || true # Don't fail the step; we handle failures via the issue below
+ # env:
+ # Optional: supply an NVD API key to avoid rate-limiting on the CVE database
+ # NVD_API_KEY: ${{ secrets.NVD_API_KEY }}
+
+ # Upload the full HTML report as a build artefact
+ - name: Upload OWASP HTML report
+ uses: actions/upload-artifact@v4
+ with:
+ name: owasp-dependency-check-report
+ path: target/dependency-check-report/
+ retention-days: 30
+
+ # Upload SARIF to GitHub Security tab (Code Scanning alerts)
+ - name: Upload SARIF to GitHub Security
+ uses: github/codeql-action/upload-sarif@v3
+ if: always()
+ with:
+ sarif_file: target/dependency-check-report/dependency-check-report.sarif
+ continue-on-error: true # Graceful if SARIF wasn't produced (e.g. no deps scanned)
+
+ # Parse the JSON report and open an issue if HIGH/CRITICAL CVEs are found
+ - name: Create / update issue for security vulnerabilities
+ uses: actions/github-script@v7
+ if: always()
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ script: |
+ const fs = require('fs');
+ const reportPath = 'target/dependency-check-report/dependency-check-report.json';
+
+ if (!fs.existsSync(reportPath)) {
+ console.log('No JSON report found – skipping issue creation.');
+ return;
+ }
+
+ const report = JSON.parse(fs.readFileSync(reportPath, 'utf8'));
+ const dependencies = report.dependencies || [];
+
+ // Collect all vulnerabilities rated HIGH or CRITICAL
+ const findings = [];
+ for (const dep of dependencies) {
+ const vulns = (dep.vulnerabilities || []).filter(
+ v => ['HIGH', 'CRITICAL'].includes((v.severity || '').toUpperCase())
+ );
+ if (vulns.length > 0) {
+ findings.push({ dep: dep.fileName, vulns });
+ }
+ }
+
+ if (findings.length === 0) {
+ console.log('No HIGH/CRITICAL CVEs found. 🎉');
+ return;
+ }
+
+ const lines = findings.flatMap(f => [
+ `### 📦 \`${f.dep}\``,
+ ...f.vulns.map(v =>
+ `- **${v.name}** (${v.severity}) – CVSS ${v.cvssv3?.baseScore ?? v.cvssv2?.score ?? 'N/A'}: ${v.description?.slice(0, 200) ?? ''}…`
+ ),
+ '',
+ ]);
+
+ const title = '🚨 Security Vulnerabilities Detected in Dependencies';
+ const body = [
+ '## Security Vulnerabilities (HIGH / CRITICAL)',
+ '',
+ '> Auto-generated by the **Dependency Review & Security Audit** workflow.',
+ '> Run: ' + context.runId,
+ '> Triggered: ' + new Date().toISOString(),
+ '',
+ lines.join('\n').slice(0, 60000),
+ '',
+ '### Next Steps',
+ '- Check the **Security** tab for full Code Scanning alerts.',
+ '- Download the full HTML report from the workflow **Artifacts**.',
+ '- Dependabot will automatically open PRs to upgrade vulnerable packages.',
+ '- For false positives, add a suppression entry to `.github/owasp-suppressions.xml`.',
+ ].join('\n');
+
+ const { data: issues } = await github.rest.issues.listForRepo({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ state: 'open',
+ labels: 'security,automated',
+ });
+
+ const existing = issues.find(i => i.title === title);
+
+ if (existing) {
+ await github.rest.issues.update({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: existing.number,
+ body,
+ });
+ console.log(`Updated existing security issue #${existing.number}`);
+ } else {
+ const { data: newIssue } = await github.rest.issues.create({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ title,
+ body,
+ labels: ['security', 'java', 'automated'],
+ });
+ console.log(`Created new security issue #${newIssue.number}`);
+ }
+
diff --git a/Dockerfile b/Dockerfile
index 25f9e26e..c4d81914 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -30,5 +30,14 @@ COPY --chown=1001:0 src/main/liberty/config /config
# RUN features.sh
COPY --chown=1001:0 target/TraderUI.war /config/apps/TraderUI.war
+# OpenTelemetry configuration used for local builds - when deployed via Helm chart, these will be set via environment variables within the chart
+ENV OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
+# Disabled by default, enable for testing
+ENV OTEL_SDK_DISABLED=true
+ENV OTEL_TRACES_EXPORTER=otlp
+ENV OTEL_LOGS_EXPORTER=otlp
+ENV OTEL_METRICS_EXPORTER=otlp
+ENV OTEL_SERVICE_NAME=trader
+
USER 1001
RUN configure.sh
diff --git a/Makefile b/Makefile
new file mode 100644
index 00000000..fce28849
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,155 @@
+# Makefile for building, testing, and pushing trader image
+# Supports both Docker and Podman
+
+# Detect container runtime (podman or docker)
+CONTAINER_RUNTIME ?= $(shell command -v podman 2>/dev/null || command -v docker 2>/dev/null)
+ifeq ($(CONTAINER_RUNTIME),)
+$(error Neither podman nor docker found in PATH)
+endif
+
+# Image configuration
+IMAGE_NAME ?= trader
+IMAGE_TAG ?= latest
+LOCAL_IMAGE := $(IMAGE_NAME):$(IMAGE_TAG)
+
+# Registry configuration
+DEV_REGISTRY ?= stocktraderotel.azurecr.io
+PROD_REGISTRY ?= ghcr.io
+DEV_IMAGE := $(DEV_REGISTRY)/$(IMAGE_NAME):$(IMAGE_TAG)
+PROD_IMAGE := $(PROD_REGISTRY)/$(IMAGE_NAME):$(IMAGE_TAG)
+
+# Test container configuration
+TEST_CONTAINER ?= trader-test
+TEST_PORT ?= 9080
+HEALTH_ENDPOINT ?= /health
+
+# Colors for output
+GREEN := \033[0;32m
+YELLOW := \033[0;33m
+RED := \033[0;31m
+NC := \033[0m # No Color
+
+.PHONY: help
+help: ## Show this help message
+ @echo "$(GREEN)Trader Build and Deploy Makefile$(NC)"
+ @echo "Container runtime: $(CONTAINER_RUNTIME)"
+ @echo ""
+ @echo "$(YELLOW)Image Tags:$(NC)"
+ @echo " Local: $(LOCAL_IMAGE)"
+ @echo " Development: $(DEV_IMAGE)"
+ @echo " Production: $(PROD_IMAGE)"
+ @echo ""
+ @echo "Available targets:"
+ @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf " $(YELLOW)%-20s$(NC) %s\n", $$1, $$2}' $(MAKEFILE_LIST)
+
+.PHONY: build
+build: ## Build the trader application (Maven + Container image)
+ @echo "$(GREEN)Building Maven package...$(NC)"
+ mvn clean package
+ @echo "$(GREEN)Building container image...$(NC)"
+ $(CONTAINER_RUNTIME) build -t $(LOCAL_IMAGE) .
+ @echo "$(GREEN)Build complete: $(LOCAL_IMAGE)$(NC)"
+
+.PHONY: build-maven
+build-maven: ## Build only the Maven package
+ @echo "$(GREEN)Running Maven build...$(NC)"
+ mvn clean package
+ @echo "$(GREEN)Maven build complete$(NC)"
+
+.PHONY: build-image
+build-image: ## Build only the container image (requires prior Maven build)
+ @echo "$(GREEN)Building container image...$(NC)"
+ $(CONTAINER_RUNTIME) build -t $(LOCAL_IMAGE) .
+ @echo "$(GREEN)Image built: $(LOCAL_IMAGE)$(NC)"
+
+.PHONY: run
+run: ## Run the image locally for testing
+ @echo "$(GREEN)Starting trader container...$(NC)"
+ @$(CONTAINER_RUNTIME) rm -f $(TEST_CONTAINER) 2>/dev/null || true
+ $(CONTAINER_RUNTIME) run -d \
+ --name $(TEST_CONTAINER) \
+ -p $(TEST_PORT):9080 \
+ -e JWT_AUDIENCE=stock-trader \
+ -e JWT_ISSUER=http://stock-trader.ibm.com \
+ $(LOCAL_IMAGE)
+ @echo "$(GREEN)Container started: $(TEST_CONTAINER)$(NC)"
+ @echo "$(GREEN)Access at: http://localhost:$(TEST_PORT)/trader$(NC)"
+
+.PHONY: validate
+validate: ## Validate that the container is running correctly
+ @echo "$(GREEN)Validating container...$(NC)"
+ @sleep 5
+ @echo "$(YELLOW)Checking container status...$(NC)"
+ @$(CONTAINER_RUNTIME) ps --filter name=$(TEST_CONTAINER) --format "table {{.Names}}\t{{.Status}}" || \
+ (echo "$(RED)Container not running!$(NC)" && exit 1)
+ @echo "$(YELLOW)Testing health endpoint...$(NC)"
+ @curl -f -s http://localhost:$(TEST_PORT)$(HEALTH_ENDPOINT) > /dev/null && \
+ echo "$(GREEN)✓ Health check passed$(NC)" || \
+ (echo "$(RED)✗ Health check failed$(NC)" && exit 1)
+ @echo "$(YELLOW)Checking container logs...$(NC)"
+ @$(CONTAINER_RUNTIME) logs $(TEST_CONTAINER) 2>&1 | tail -10
+ @echo "$(GREEN)Validation complete!$(NC)"
+
+.PHONY: stop
+stop: ## Stop the test container
+ @echo "$(YELLOW)Stopping test container...$(NC)"
+ @$(CONTAINER_RUNTIME) stop $(TEST_CONTAINER) 2>/dev/null || true
+ @echo "$(GREEN)Container stopped$(NC)"
+
+.PHONY: clean
+clean: stop ## Remove the test container
+ @echo "$(YELLOW)Removing test container...$(NC)"
+ @$(CONTAINER_RUNTIME) rm -f $(TEST_CONTAINER) 2>/dev/null || true
+ @echo "$(GREEN)Cleanup complete$(NC)"
+
+.PHONY: logs
+logs: ## Show container logs
+ @$(CONTAINER_RUNTIME) logs $(TEST_CONTAINER)
+
+.PHONY: logs-follow
+logs-follow: ## Follow container logs
+ @$(CONTAINER_RUNTIME) logs -f $(TEST_CONTAINER)
+
+.PHONY: shell
+shell: ## Get a shell in the running container
+ @$(CONTAINER_RUNTIME) exec -it $(TEST_CONTAINER) /bin/bash
+
+.PHONY: tag-dev
+tag-dev: ## Tag image for development registry
+ @echo "$(GREEN)Tagging image for development registry...$(NC)"
+ $(CONTAINER_RUNTIME) tag $(LOCAL_IMAGE) $(DEV_IMAGE)
+ @echo "$(GREEN)Tagged: $(DEV_IMAGE)$(NC)"
+
+.PHONY: tag-prod
+tag-prod: ## Tag image for production registry
+ @echo "$(GREEN)Tagging image for production registry...$(NC)"
+ $(CONTAINER_RUNTIME) tag $(LOCAL_IMAGE) $(PROD_IMAGE)
+ @echo "$(GREEN)Tagged: $(PROD_IMAGE)$(NC)"
+
+.PHONY: push-dev
+push-dev: tag-dev ## Push image to development registry
+ @echo "$(GREEN)Pushing to development registry...$(NC)"
+ $(CONTAINER_RUNTIME) push $(DEV_IMAGE)
+ @echo "$(GREEN)Pushed: $(DEV_IMAGE)$(NC)"
+
+.PHONY: push-prod
+push-prod: tag-prod ## Push image to production registry
+ @echo "$(GREEN)Pushing to production registry...$(NC)"
+ $(CONTAINER_RUNTIME) push $(PROD_IMAGE)
+ @echo "$(GREEN)Pushed: $(PROD_IMAGE)$(NC)"
+
+.PHONY: test
+test: run validate stop ## Build, run, and validate the image locally
+
+.PHONY: all
+all: build test ## Build and test the image
+
+.PHONY: deploy-dev
+deploy-dev: build test push-dev ## Build, test, and push to development registry
+ @echo "$(GREEN)Deployment to dev complete!$(NC)"
+
+.PHONY: deploy-prod
+deploy-prod: build test push-prod ## Build, test, and push to production registry
+ @echo "$(GREEN)Deployment to prod complete!$(NC)"
+
+.DEFAULT_GOAL := help
diff --git a/otel-collector-config.yaml b/otel-collector-config.yaml
new file mode 100644
index 00000000..618cefaf
--- /dev/null
+++ b/otel-collector-config.yaml
@@ -0,0 +1,33 @@
+receivers:
+ otlp:
+ protocols:
+ grpc:
+ endpoint: 0.0.0.0:4317
+ http:
+ endpoint: 0.0.0.0:4318
+
+processors:
+ batch:
+ timeout: 10s
+
+exporters:
+ # Log to console for debugging
+ debug:
+ verbosity: detailed
+
+service:
+ pipelines:
+ traces:
+ receivers: [otlp]
+ processors: [batch]
+ exporters: [debug]
+
+ metrics:
+ receivers: [otlp]
+ processors: [batch]
+ exporters: [debug]
+
+ logs:
+ receivers: [otlp]
+ processors: [batch]
+ exporters: [debug]
diff --git a/pom.xml b/pom.xml
index 2ccfcd83..bc6779e2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -56,7 +56,7 @@
org.eclipse.microprofile
microprofile
- 6.1
+ 7.1
pom
provided
@@ -185,7 +185,6 @@
microprofile-7.1
- mpTelemetry-2.1
pages-3.1
diff --git a/src/main/liberty/config/server.xml b/src/main/liberty/config/server.xml
index 2fdff647..de935ec5 100644
--- a/src/main/liberty/config/server.xml
+++ b/src/main/liberty/config/server.xml
@@ -17,10 +17,12 @@
microProfile-7.1
- mpTelemetry-2.1
pages-3.1
+
+
+
diff --git a/src/main/resources/META-INF/microprofile-config.properties b/src/main/resources/META-INF/microprofile-config.properties
index a22fa1df..6c4a137d 100644
--- a/src/main/resources/META-INF/microprofile-config.properties
+++ b/src/main/resources/META-INF/microprofile-config.properties
@@ -1,7 +1,6 @@
-otel.sdk.disabled=false
-otel.exporter.otlp.endpoint=http://localhost:4317
-otel.traces.exporter=otlp
-otel.logs.exporter=otlp
-otel.metrics.exporter=otlp
-otel.service.name=trader
-otel.resource.attributes=service.namespace=stocktrader,service.instance.id=${HOSTNAME}
\ No newline at end of file
+otel.sdk.disabled=${OTEL_SDK_DISABLED:true}
+otel.exporter.otlp.endpoint=${OTEL_EXPORTER_OTLP_ENDPOINT:http://localhost:4317}
+otel.traces.exporter=${OTEL_TRACES_EXPORTER:otlp}
+otel.logs.exporter=${OTEL_LOGS_EXPORTER:otlp}
+otel.metrics.exporter=${OTEL_METRICS_EXPORTER:otlp}
+otel.service.name=${OTEL_SERVICE_NAME:trader}
\ No newline at end of file