BytesafeDependency Firewall

GitHub Actions

Give GitHub Actions workflows firewall access without stored secrets. Register a Trusted Builder and exchange the workflow's OpenID Connect identity for a short-lived token.

A workflow that installs packages through the firewall needs a token. Storing a long-lived Service Access Token as a repository secret works, but it is a stored credential: it can leak, and someone has to rotate it. GitHub Actions can do better, because every workflow run can prove its identity with an OpenID Connect (OIDC) ID token signed by GitHub.

This tutorial registers your repository as a Trusted Builder and sets up a workflow that exchanges its OIDC identity for a firewall token at run time. The exchanged token is a Service Access Token valid for 15 minutes; there is nothing to store and nothing to rotate.

Workflow
GitHub OIDC
Firewall
OIDC token exchange
Request ID token for audience bytesafe:trusted-builder:{id}
Signed OIDC ID token
POST /v1/<namespace-id>/oidc/exchange { idToken }
verify signature, audience, repository claims
Service Access Token (15 min)
Package access
Install package with the token
Workflow
GitHub OIDC
Firewall
request response

The examples use <namespace-id> and <firewall-id> placeholders. Use IDs, not namespace or firewall names.

Before you start

  • You can create Service Access Tokens (the ns:create:service-token permission, held by admins by default). A Trusted Builder creates a Service Access Token on every exchange, so registering one requires this permission.
  • Your repository already has the firewall configured as its registry (see your ecosystem page), with the token read from BYTESAFE_TOKEN.

Step 1: register the Trusted Builder

In the dashboard, open the user menu in the lower-left corner and select Access Tokens (not in the main sidebar), switch to the Trusted Builders tab and select Add Builder:

  • Name: something that identifies the workflow, for example acme/webshop CI.
  • Repository owner and Repository: your GitHub organization and repository, for example acme and acme/webshop. The exchange verifies these against the token's claims; leave a field empty to not constrain it.
  • Repository ID: GitHub's numeric repository ID. Optional but recommended: names can be reassigned after a repository is deleted or renamed, the ID cannot.
  • Permissions: what the exchanged token may do. For installs, fw:access is enough.
  • Restrict to firewalls: limit the token to the firewalls this workflow needs. Empty allows all firewalls.

Save it and copy the audience value. It has the form:

bytesafe:trusted-builder:6f1e9c2a-...

A Trusted Builder cannot grant more than you hold yourself; permissions are capped at creation just like manual Service Access Tokens.

Step 2: add the exchange to the workflow

The job needs id-token: write permission, which is what lets it request an OIDC token from GitHub. Then bsfw oidc exchange fetches the ID token for your audience and swaps it for a firewall token:

.github/workflows/build.yml
jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    env:
      BYTESAFE_ENDPOINT: https://eu-sov-1.bytesafecloud.eu
      BYTESAFE_NAMESPACE: <namespace-id>
      BYTESAFE_TRUSTED_BUILDER_AUDIENCE: bytesafe:trusted-builder:6f1e9c2a-...
    steps:
      - uses: actions/checkout@v4

      - name: Get firewall token
        run: echo "BYTESAFE_TOKEN=$(bsfw oidc exchange)" >> "$GITHUB_ENV"

      - name: Install dependencies
        run: npm ci

The exchange endpoint is unauthenticated, so no BYTESAFE_TOKEN is needed before this step. The exchanged token lands in BYTESAFE_TOKEN, where your committed registry config already expects it.

The step above assumes the bsfw binary is available on the runner. If it is not, use the curl version below, which needs nothing but curl and jq.

The exchanged token is valid for 15 minutes from the exchange, not from the start of the job. In a long job, run the exchange again in the step that needs it rather than once at the top.

To correlate all requests from one run in the logs, append the run ID: echo "BYTESAFE_TOKEN=$(bsfw oidc exchange)::${GITHUB_RUN_ID}" >> "$GITHUB_ENV".

Without the CLI

The exchange is two HTTP calls, so a workflow can do it with curl:

      - name: Get firewall token
        run: |
          ID_TOKEN=$(curl -sSf -H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
            "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=$BYTESAFE_TRUSTED_BUILDER_AUDIENCE" | jq -r .value)
          ACCESS_TOKEN=$(curl -sSf -X POST \
            "$BYTESAFE_ENDPOINT/v1/$BYTESAFE_NAMESPACE/oidc/exchange" \
            -H "Content-Type: application/json" \
            -d "{\"idToken\": \"$ID_TOKEN\"}" | jq -r .accessToken)
          echo "BYTESAFE_TOKEN=$ACCESS_TOKEN" >> "$GITHUB_ENV"

ACTIONS_ID_TOKEN_REQUEST_URL and ACTIONS_ID_TOKEN_REQUEST_TOKEN are injected by GitHub into any job with id-token: write.

Step 3: verify

Run the workflow, then check the firewall logs:

bsfw logs --firewall <firewall-id> --all-users

Requests from the run are attributed to a token named github:acme/webshop:<run-id>. If you appended the run ID as an interaction ID, it appears on the entries too.

When the exchange fails

The exchange returns 401 without detail, so start with the two values the workflow controls: the job needs id-token: write, and BYTESAFE_TRUSTED_BUILDER_AUDIENCE has to match the audience shown on the Trusted Builder registration exactly.

If the token identifies a configured Trusted Builder, the audit log records the oidc.exchange result and reasons such as repository mismatch or verification failure. Malformed tokens and unknown audiences cannot be assigned to a tenant, so they are not shown in its audit log. An exchange that fails without leaving an audit entry points at the audience or the ID token itself.

Next steps

  • Restrict what the workflow can do: the Trusted Builder's permissions and firewall list are the place, see Manage tokens.
  • Correlate runs in logs: interaction IDs.
  • Reference: Tokens, CLI.

On this page