WASViking Docs
⌘K
Getting Started

CI/CD SCA, SBOM & Secrets with Bitbucket Pipelines

Run the WASViking Sentinel inside Bitbucket Pipelines to build a CycloneDX SBOM, check dependencies against OSV and CISA KEV, scan the working tree and git history for hard-coded credentials, and stop a vulnerable release before it reaches your main branch.

This guide runs Software Composition Analysis (SCA) and a secrets scan inside a Bitbucket Pipelines build. The WASViking® Sentinel reads your dependency manifests, builds a CycloneDX SBOM, enriches it with OSV and CISA KEV, walks the working tree and the git history for hard-coded credentials, and fails the build before a vulnerable dependency or a leaked key reaches your main branch.

Both gates are the same binary and the same API used in the GitHub Actions guide. Neither one is tied to a CI vendor: the agent runs as a one-shot command, authenticates with an organization API Key, and submits over HTTPS REST. What changes between vendors is the YAML around it, plus two Bitbucket details that are easy to miss and that this page calls out explicitly (the clone depth and secured variables).

There is no mTLS tunnel in this flow. No source code leaves the runner, only the dependency graph (package names and versions) and redacted secret matches.

The setup has two halves, and the order matters: configure the WASViking portal first (one API Key scoped to ci:scan, sca:submit, and secrets:submit), then wire it into Bitbucket.

What this integration does

  • Generates a CycloneDX 1.5 SBOM from your manifests (npm, pip, go, composer, Maven, gem) on pull requests and on pushes to main.
  • Enriches components with OSV and CISA KEV (known exploited vulnerabilities).
  • Scans the working tree and, with --git, the repository history for hard-coded secrets, submitting only redacted matches.
  • Optionally verifies a match against the provider's identity endpoint with --verify, so a rotated key does not read like a live incident.
  • Fails the build by severity with --fail-on, which blocks the merge.
  • Writes SARIF 2.1.0 and the raw CycloneDX JSON as build artifacts.
  • Builds a consolidated software inventory per organization and detects drift between consecutive submissions.
  • Leaves nothing behind on the runner. The agent is installed per build and dies with the container.

How it works

  1. The build container downloads and installs the Sentinel agent using the API Key.
  2. The agent runs a license preflight against the WASViking API. An approval is cached for 30 minutes under ~/.wasviking/, which in a Bitbucket build means it is fetched fresh on every run.
  3. It walks the project manifests and builds a CycloneDX SBOM.
  4. It enriches components against OSV and applies CISA KEV validation.
  5. It scans the tree, and the git history when --git is set, for hard-coded credentials.
  6. It submits the SBOM and the redacted matches over HTTPS REST. The API validates quota, registers the snapshot, detects component drift, and promotes findings.
  7. The agent writes the JSON and SARIF outputs into --out, and Bitbucket collects that directory as a build artifact.

Access posture

  • Only manifests and the dependency graph are read. No source code, no repository variables beyond the key you pass, and nothing outside .wasviking/ is collected.
  • Raw secret values never leave the runner. Submissions carry a hash and a masked preview.
  • Submission is HTTPS REST with an API Key header. There is no mTLS tunnel and no inbound connection to the runner.
  • --verify adds read-only calls to the provider identity endpoints of the matched credentials (for example the GitHub or AWS identity API). Drop the flag if outbound calls to third parties are not acceptable in your build network.
  • --air-gapped on the sbom gate guarantees zero external egress: no OSV lookup and no submission.
  • Revoke access at any time by revoking the API Key in the portal.

Pre-requisites

Requirement Detail
WASViking plan SBOM and secrets submissions enabled (Pro or higher).
Portal role Admin or Manager, to issue API Keys.
Bitbucket Pipelines enabled on the repository, under Repository settings → Pipelines → Settings.
Bitbucket permission Admin on the repository, to create secured repository variables and commit bitbucket-pipelines.yml.
Build image Any Linux x86_64 image with curl, sha256sum, and dpkg-deb (or ar, from binutils). atlassian/default-image:5 has all of them.
Clone depth clone: depth: full when you scan git history with --git. Bitbucket clones shallow by default.
Project manifests At least one supported lockfile: package-lock.json, requirements.txt, Pipfile.lock, go.sum, composer.lock, pom.xml, Gemfile.lock.
Network egress HTTPS to api.wasviking.com and api.osv.dev on 443. No inbound is needed.

On GitLab CI, Jenkins, CircleCI, or anything else that can execute a Linux binary? The commands below are identical. Only the YAML dialect changes. Start from wasviking-sentinel in CI/CD.


Step 1: Create an API Key for the pipeline (portal)

One API Key covers the whole build: it authorizes the agent installer download and both submissions. No Sentinel agent token is involved here, that belongs to the tunnel flow.

Go to Settings → System Settings → API Keys and click + New Key.

Field Value
Label Something you will recognise in six months, for example Bitbucket SCA, checkout-api. Use one key per repository so revoking it does not take down other pipelines.
Scopes ci:scan (Trigger scans from a CI/CD pipeline, which is also what authorizes the installer download), sca:submit (Send SBOMs from the Sentinel agent. OWASP A06), and secrets:submit (Send hard-coded credential matches from the Sentinel agent. OWASP A07).
Expiration 90 days is a sensible default. Rotate on that cadence.

Save and copy the key once. You will paste it into Bitbucket in Step 2 as WASV_SCA_API_KEY.

WASViking authenticates with the Authorization: ApiKey <key> header, not Bearer. Scopes can be adjusted later with Edit without changing the key value, so the pipeline keeps working.

System Settings, API Keys tab, with the key list Settings → System Settings → API Keys.

API Key scope picker with the pipeline scopes selected Select the three pipeline scopes when creating the key.


Step 2: Store the key as a secured repository variable

Never commit a key to the repository. In Bitbucket, go to Repository settings → Pipelines → Repository variables and add:

Name Value Secured
WASV_SCA_API_KEY The API Key from Step 1. Yes

Tick Secured so the value is masked in the build log and cannot be read back from the UI. If several repositories share one key, a workspace variable works the same way, though a key per repository gives you a cleaner revocation story.

One Bitbucket behaviour to plan for: pull request builds triggered from a forked repository do not receive secured variables. The first line of the step below is a guard that fails the build immediately in that case, instead of letting the scan run unauthenticated and report a false all-clear.


Step 3: Add the pipeline

Create bitbucket-pipelines.yml at the repository root:

image: atlassian/default-image:5

# Full Git history is required when using the secrets --git option.
clone:
  depth: full

definitions:
  steps:
    - step: &wasviking-security-scan
        name: WASViking SCA + SBOM + Secrets
        max-time: 20

        script:
          - test -n "$WASV_SCA_API_KEY"

          # Run from the repository root.
          - cd "$BITBUCKET_CLONE_DIR"

          # WASViking API endpoint.
          - export WASV_API_BASE="https://api.wasviking.com"

          # Install WASViking Sentinel.
          - |
            curl -fsSL \
              -H "Authorization: ApiKey $WASV_SCA_API_KEY" \
              "$WASV_API_BASE/api/v1/sentinel/install.sh" | sh

          - mkdir -p wasviking-reports

          # Generate CycloneDX SBOM, run SCA, and submit results.
          - |
            ./.wasviking/wasviking-sentinel sbom \
              --api "$WASV_API_BASE" \
              --api-key "$WASV_SCA_API_KEY" \
              --path . \
              --app-name "$BITBUCKET_REPO_FULL_NAME" \
              --app-version "$BITBUCKET_COMMIT" \
              --fail-on high \
              --submit \
              --out ./wasviking-reports

          # Scan the working tree and Git history for hard-coded secrets.
          - |
            ./.wasviking/wasviking-sentinel secrets \
              --api "$WASV_API_BASE" \
              --api-key "$WASV_SCA_API_KEY" \
              --path . \
              --git \
              --verify \
              --fail-on high \
              --submit \
              --out ./wasviking-reports

        artifacts:
          - wasviking-reports/**

pipelines:
  pull-requests:
    '**':
      - step: *wasviking-security-scan

  branches:
    main:
      - step: *wasviking-security-scan

  custom:
    wasviking-security-scan:
      - step: *wasviking-security-scan

Four choices in that file are worth understanding before you adapt it:

  • clone: depth: full. The --git flag walks commit history. With Bitbucket's default shallow clone the agent only sees the last commits, so a credential committed months ago and later removed from the working tree stays invisible. Drop both depth: full and --git together if you only want the current tree scanned, which makes builds faster.
  • The YAML anchor (&wasviking-security-scan). The step is declared once under definitions and referenced three times. Pull requests and main get the gate automatically, and the custom entry lets anyone run it on demand from Pipelines → Run pipeline, which is handy for the first rollout.
  • max-time: 20. A ceiling in minutes for the step. Repositories with deep history or many manifests should raise it. The agent has its own timeouts, 5 minutes for the SBOM pipeline and 10 for secrets, both adjustable with --timeout.
  • artifacts. Bitbucket keeps wasviking-reports/** attached to the build, so the SARIF and JSON outputs are downloadable from the Artifacts tab after the run.

Commit the file and the pipeline starts on the next push or pull request.


Step 4: Command flags

sbom (SCA and SBOM)

wasviking-sentinel sbom [flags]
Flag Required Description
--api-key Yes API Key with the sca:submit scope. Also reads WASV_API_KEY.
--api No WASViking API base URL. Default: https://api.wasviking.com.
--path No Directory scanned recursively for manifests. Default: current directory.
--app-name No Project name in the CycloneDX metadata. $BITBUCKET_REPO_FULL_NAME gives you workspace/repo. Pass it: without it the agent falls back to the CI repository name, since the checkout directory on Bitbucket is always called build and would make a poor inventory label.
--app-version No Project version in the metadata. $BITBUCKET_COMMIT ties the SBOM to the exact commit.
--fail-on No Lowest severity that fails the build: critical, high, medium, low, none. Default: high.
--submit No Send the SBOM to WASViking. Omit for a local-only run.
--out No Output directory for the CycloneDX JSON and SARIF. Default: current directory.
--no-osv No Skip OSV enrichment and ship a bare SBOM.
--from-cyclonedx No Ingest an SBOM produced by another tool instead of generating one.
--air-gapped No Fully offline. No OSV lookup and no submission.
--timeout No Wall-clock ceiling for the SBOM pipeline. Default: 5 minutes.

Outputs: wasviking-sbom.cdx.json (CycloneDX 1.5) and wasviking-sbom.sarif (SARIF 2.1.0).

secrets

wasviking-sentinel secrets [flags]
Flag Required Description
--api-key Yes API Key with the secrets:submit scope.
--api No WASViking API base URL.
--path No Directory scanned recursively. Default: current directory.
--git No Also walk the git history. Requires clone: depth: full.
--verify No Confirm matches against the provider identity endpoints, read-only.
--fail-on No Same scale and default (high) as the SBOM gate.
--submit No Send redacted matches to WASViking.
--out No Output directory.
--timeout No Wall-clock ceiling. Default: 10 minutes.

Outputs: wasviking-secrets.json and wasviking-secrets.sarif.

Exit codes

Bitbucket fails the step on any non-zero exit, so these codes are what turn a finding into a blocked merge.

Code Meaning
0 Nothing at or above the --fail-on threshold.
70 SCA gate: a KEV-flagged dependency at or above the threshold. Treat as urgent, it is a known exploited vulnerability.
71 SCA gate: findings at or above the threshold, none of them in KEV.
73 Secrets gate: a credential confirmed live by --verify.
74 Secrets gate: matches at or above the threshold that were not verified.
79 Coverage failure: the scan root could not be traversed, so nothing was scanned. See below.
1 / 2 Operational error: unreadable manifest, rejected API key, network failure on --submit.

Coverage failure (exit 79)

A gate that scans nothing finds nothing, and without a check that reads exactly like a clean run. Exit 79 keeps those apart: the agent measures what it covered against what the scan root actually contained, and stops rather than reporting a pass over an empty traversal.

It is not a finding, and it does not fire on an empty repository or on one with no supported manifest. Those walk correctly and pass, and the log reports how many entries were walked so you can see the difference.

Bitbucket is worth one note here. $BITBUCKET_CLONE_DIR is always /opt/atlassian/pipelines/agent/build, so on this platform the scan root is called build, which is also a name the agent excludes inside projects. Exclusions apply to sub-directories only, so the checkout is scanned in full and the log states that the root name was recognised. A build/ directory inside your repository is still excluded, as it should be. The full reference is Coverage failures.

Fail-on policy

--fail-on sets the lowest severity that fails the build, and everything above it also fails.

Setting Behavior
--fail-on none Never fails. Report only, useful for the first week.
--fail-on critical Fails on critical only.
--fail-on high Fails on high and critical. The default, and a good one for pull requests.
--fail-on medium Fails on medium and above. Stricter, more friction.
--fail-on low Maximum posture. Realistic on new projects with no accumulated debt.

A rollout that tends to survive contact with a real team: start at none on pull requests to see the volume, move to high once the backlog is triaged, and keep main stricter than feature branches.

Reading the results

The portal is the system of record. Dependency data lands under Inventory → SBOM and credential matches under Inventory → Secrets, both with the promoted findings attached to your organization's posture.

The SARIF files are written for tools that consume the format. Bitbucket does not ingest SARIF natively, so on this platform they are build artifacts you can download or hand to another tool, not an annotation layer on the pull request. The build log carries the summary line, and the portal carries the history.

Drift compares the component set of this submission against the previous one carrying the same --app-name, and it ignores --app-version entirely. A commit that changes no dependency reports no drift even though the version string moved.

That makes --app-name the value to keep stable. If it varies between runs, every submission starts its own lineage with nothing to compare against, and drift silently never fires. Deriving it from $BITBUCKET_REPO_FULL_NAME, as the pipeline above does, keeps it constant for the life of the repository.

Air-gapped mode

Build networks with no external egress can still produce an SBOM:

./.wasviking/wasviking-sentinel sbom \
  --path . \
  --air-gapped \
  --out ./wasviking-reports

--air-gapped skips the OSV lookup and the submission, working from the bundled KEV data. Use it on isolated build farms, or to validate locally before shipping an artifact by hand.

What is and isn't collected

WASViking does not collect your repository source code, repository variables beyond the key you pass, files outside .wasviking/, or any production data. The SBOM holds the dependency graph (package names, versions, licenses, manifest hashes). The secrets gate submits a hash and a masked preview, never the credential itself. Data is processed in the US region, encrypted in transit (TLS 1.2+) and at rest (AES-256), with retention set by your plan. A DPA and EU data residency are available on request. See the Trust Center for the full compliance mapping (ISO 27001, SOC 2, LGPD, GDPR, NIST SSDF, OWASP Top 10 A06/A07, OWASP DSOMM, CISA SBOM minimum elements).

Common problems

Problem Likely cause
Step fails on the first line, before any output WASV_SCA_API_KEY is not set. On a pull request from a fork, secured variables are not delivered to the build.
HTTP 401 Unauthorized Key revoked, expired, or missing ci:scan, sca:submit, or secrets:submit. The header is Authorization: ApiKey <key>, not Bearer.
HTTP 402 quota exceeded Monthly submission quota reached. Wait for the cycle, upgrade, or add a pack.
Secrets scan finds nothing in history clone: depth: full is missing, so the shallow clone has no history to walk.
neither dpkg-deb nor ar is available A minimal build image (Alpine and similar). Install binutils, or switch the step to atlassian/default-image:5.
install.sh download error Network policy blocking api.wasviking.com or the release bucket.
Step killed at 20 minutes Deep history plus --verify on a large repository. Raise max-time and the agent --timeout, or drop --verify on pull requests and keep it on main.
Exit 79 The scan root could not be traversed. Check the Root: line against your repository layout, then check that the step can read the checkout.
manifest not detected No supported lockfile under --path. Commit your lockfiles.
OSV timeout api.osv.dev slow or unreachable. Use --no-osv for a bare SBOM, or --air-gapped.
Findings differ between runs A lockfile regenerated during the build. Install with npm ci or the equivalent, and commit the lockfile.
Unexpected exit 70 on a small change A new transitive dependency arrived through the lockfile and matches CISA KEV. Inspect with npm ls <package> or the equivalent.

Where this fits in the platform