Skip to content
Notifications
Clear all

Step-by-step: Integrating Black Duck scans into our GitHub Actions CI.

3 Posts
3 Users
0 Reactions
1 Views
(@gregr)
Estimable Member
Joined: 2 weeks ago
Posts: 103
Topic starter   [#22105]

After evaluating several software composition analysis tools for our polyglot microservices repository, my team settled on Black Duck primarily due to its nuanced policy engine and the depth of its vulnerability intelligence. However, the initial integration felt somewhat opaque compared to more developer-centric, CLI-first alternatives. I've spent the last few weeks refining a GitHub Actions workflow that is both efficient and provides the actionable feedback our engineers need without slowing down the merge process. The goal was to move beyond a simple pass/fail gate and create a reporting pipeline that integrates with our existing monitoring and ticketing systems.

The core challenge was balancing comprehensive scanning—which can be time-consuming—with the rapid feedback required in CI. We achieved this through a two-stage scanning strategy: a rapid scan on PRs targeting only newly introduced dependencies, and a full forensic scan on the main branch nightly. Below is the foundational workflow for the PR scan, which hinges on Black Duck's `detect` script and careful artifact management.

```yaml
name: SCA PR Scan
on: [pull_request]

jobs:
black-duck-rapid-scan:
runs-on: ubuntu-latest
permissions:
security-events: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Set up JDK
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'

- name: Download Synopsys Detect
run: |
curl -sSL https://detect.synopsys.com/detect.sh -o detect.sh
chmod +x detect.sh

- name: Run Targeted Black Duck Scan
env:
BD_HUB_TOKEN: ${{ secrets.BLACKDUCK_TOKEN }}
BD_URL: ${{ vars.BLACKDUCK_URL }}
run: |
./detect.sh
--blackduck.url="${BD_URL}"
--blackduck.api.token="${BD_HUB_TOKEN}"
--detect.project.name="${GITHUB_REPOSITORY}"
--detect.project.version.name="${GITHUB_HEAD_REF_SLUG}-${GITHUB_SHA}"
--detect.policy.check.fail.on.severities="BLOCKER,CRITICAL"
--detect.source.path="."
--detect.risk.report.pdf=true
--detect.tools.excluded="SIGNATURE_SCAN"
--detect.detector.search.depth=1
```

Key configuration decisions we made include:
* Using `--detect.detector.search.depth=1` to limit the scan primarily to manifest files (e.g., `package.json`, `pom.xml`, `requirements.txt`), significantly speeding up the process.
* Explicitly excluding `SIGNATURE_SCAN` for PR checks, as the binary/bytecode scanning is deferred to the nightly full scan.
* Generating a PDF risk report for each run as an artifact, which has proven invaluable for security reviews on contentious updates.
* Setting policy failures only for `BLOCKER` and `CRITICAL` severities at the PR stage; `MAJOR` and lower are flagged but don't block the merge, creating a triage workflow.

The output from this scan is then funneled into two locations: the SARIF format is uploaded to GitHub's code scanning alerts for visibility within the repository's security tab, and a summary of findings is posted as a comment on the PR using a custom script. The nightly full scan uses a similar configuration but removes the depth limit, includes signature scanning, and runs a broader set of detectors, with its results pushing data to our internal dashboard powered by Grafana.

One significant pitfall we encountered was the default memory settings for the Detect script on the GitHub-provided runners; we had to explicitly set `--detect.java.opts` to `-Xmx4g -XX:MaxRAMPercentage=80.0` to prevent out-of-memory kills during larger monorepo scans. Furthermore, while the integration is robust, the feedback loop is not as instantaneous as some pure SaaS scanners—this is the trade-off for the deeper analysis. The next phase of our tinkering involves hooking the policy violation events into a dedicated Slack channel via webhooks and correlating Black Duck data with runtime vulnerability data from our container orchestration layer.

testing all the things


throughput first


   
Quote
(@harperk)
Reputable Member
Joined: 2 weeks ago
Posts: 167
 

The two-stage strategy makes sense. I've found the "new dependencies only" scan is fast, but if you're not careful, it can miss policy violations triggered by transitive updates or license changes in existing packages after a patch version bump. How are you handling that, just relying on the nightly forensic run to catch it? That creates a lag.

Also, using the `detect` script directly, you're at the mercy of their sync detection for granularity. We ended up piping the dependency manifest from the previous main commit into the scan parameters to get a more accurate diff, otherwise it was noisy.


Data over dogma.


   
ReplyQuote
(@cloud_cost_nerd)
Estimable Member
Joined: 3 months ago
Posts: 116
 

Interesting approach with the two-stage scan. We went down a similar path but found the "new dependencies only" scan created blind spots around transitive dependency upgrades. A patch version bump for `lodash` in your `webpack` dependency tree might not appear as a "new" dependency, but it can still introduce a policy violation if that new version has a newly discovered CVE.

We ended up modifying the PR scan to use a manifest diff. It adds a couple of steps to fetch the `package-lock.json` or `pom.xml` from the base commit, but the comparison is more accurate. This catches license changes and new vulnerabilities in existing packages that a simple "new component" scan would miss until the nightly run.

What's your timeout for the rapid scan? We had to bump our runner to a 4-core instance and set a hard 8-minute limit to keep it from derailing PR velocity.


Right-size or die


   
ReplyQuote