BytesafeDependency Firewall

CLI

Install and configure bsfw. Commands, profiles, authentication, flags, environment variables, output formats, and exit codes.

bsfw is the Bytesafe Dependency Firewall CLI. It is distributed from bytesafedev/bsfw and uses the same management API as the dashboard, so anything it reads or changes is the same data the dashboard shows.

To do thisUse
See which firewalls exist and how one is set upbsfw firewall list, bsfw firewall show
Find out why an install was blockedbsfw logs
Check what the firewall knows about one package versionbsfw package
Keep policy in git and apply itbsfw firewall export, bsfw firewall import
See who changed a firewall, a token, or a teambsfw audit
List the packages your builds actually pulledbsfw observations
Authenticate a CI job without storing a tokenbsfw oidc exchange

Install

Homebrew

Homebrew supports macOS and Linux, including Linux running under Windows Subsystem for Linux (WSL) 2. The bsfw cask installs the binary, shell completions, and the bsfw man page.

brew install bytesafedev/tap/bsfw

Install script

The install script supports macOS and Linux on amd64 and arm64. It downloads the latest release archive, verifies it against the release's checksums.txt, and installs bsfw to /usr/local/bin when writable or ~/.local/bin otherwise.

curl -fsSL https://raw.githubusercontent.com/bytesafedev/bsfw/main/install.sh | sh
VariableDescription
BSFW_VERSIONInstall a specific release, for example v0.2.0, instead of latest
BSFW_INSTALL_DIRInstall the binary in this directory
BSFW_BASE_URLDownload release files from an internal mirror. Requires BSFW_VERSION

Native Windows and manual installation

On native Windows, download the Windows archive from GitHub Releases, verify it against checksums.txt, and put bsfw on PATH. WSL 2 users can use the Homebrew command above. Releases provide macOS, Linux, and Windows archives for amd64 and arm64. Each archive also contains shell completions and man pages.

Verify any installation:

bsfw version

Initial setup

Create a Personal Access Token (PAT) in the dashboard, then store the endpoint and namespace on the active profile and log in:

bsfw profile set endpoint https://eu-sov-1.bytesafecloud.eu
bsfw profile set namespace <namespace-id>
bsfw auth login
bsfw whoami

auth login prompts for the token, verifies it against the configured endpoint and namespace, then stores it for the active profile. Tokens are not written to the profile config. The CLI uses the operating system keyring and falls back to ~/.config/bytesafe/credentials.json with file mode 0600 when no keyring is available.

In CI, set BYTESAFE_TOKEN instead of storing a token. For GitHub Actions without a long-lived token, use bsfw oidc exchange.

Configuration resolution

The CLI needs an endpoint, a namespace, an access token, and for many commands a firewall. Each of those can be set in more than one place: a flag on the command, an environment variable, the active profile, or a built-in default. Resolution is how the CLI picks one when several are set. It takes the first that has a value:

  1. Command flag
  2. Environment variable
  3. Active profile
  4. Built-in default, where one exists

Nothing warns you when two sources disagree, and the winner is not the one set most recently: an exported BYTESAFE_NAMESPACE overrides the namespace on your profile every time. Keep one source per setting where you can: profiles for local work, environment variables in CI, flags for one-off overrides.

The access token follows the same order with one difference. Below the environment variable it comes from the token stored by bsfw auth login, not from the profile, because tokens are never written to the profile config.

bsfw auth status prints the resolved values and the source each came from, which is the fastest way to settle a "but I set that" question. Add --no-verify to inspect resolution without contacting the service.

Profiles

A profile stores an endpoint, namespace, and optional default firewall. Profiles live in ~/.config/bytesafe/config.toml; XDG_CONFIG_HOME changes the base directory and BYTESAFE_CONFIG replaces the full path.

CommandDescription
bsfw profile listList profiles; * marks the active profile
bsfw profile showShow the active profile's resolved settings
bsfw profile use <profile>Make a profile active
bsfw profile set <key> <value>Set endpoint, namespace, or firewall
bsfw profile remove <profile>Delete a profile; add --with-token to remove its stored token too

Use --profile <profile> or BYTESAFE_PROFILE for one command without changing the active profile.

bsfw profile set --profile production endpoint https://eu-sov-1.bytesafecloud.eu
bsfw profile set --profile production namespace <namespace-id>
bsfw profile set --profile production firewall <firewall-id>
bsfw logs --profile production --all-users

Environment variables

VariableDescription
BYTESAFE_ENDPOINTManagement API base URL
BYTESAFE_NAMESPACENamespace ID
BYTESAFE_TOKENAccess token
BYTESAFE_FIREWALLDefault firewall ID
BYTESAFE_PROFILEProfile to use
BYTESAFE_CONFIGFull path to the profile config file
BYTESAFE_CREDENTIAL_STOREStored-token backend: auto, keyring, or file
BYTESAFE_INTERACTIONInteraction ID appended to the token for firewall and audit-log correlation
BYTESAFE_TRUSTED_BUILDER_AUDIENCEDefault audience for bsfw oidc exchange
NO_COLORDisable colored output when set

Namespace and firewall values are IDs, not display names. Use bsfw namespaces and bsfw firewall list to find them.

Global flags

FlagDescription
--endpoint <url>Management API base URL
--namespace <id>Namespace ID
--token <token>Access token. A command-specific --token on firewall credentials means the upstream token instead
--firewall <id>Default firewall ID for the command
--profile <name>Profile to use
--credential-store auto|keyring|fileStored-token backend
--interaction <id>Interaction ID for log correlation
-o, --output table|jsonOutput format. logs also supports ndjson and logfmt
--timeout <seconds>Request timeout; default 30
--no-colorDisable colored output

Dates and durations

Every flag that takes a point in time (logs --since, audit --from, audit --to) accepts the same four forms.

FormExampleMeans
Date2026-08-01Midnight local time on that date
Date and time"2026-08-01 14:30" or "2026-08-01 14:30:00"That local time. Quote it, or the shell splits it into two arguments
RFC 3339 timestamp2026-08-01T14:30:00ZExactly that instant, in the time zone the value carries
Duration back from now30m, 24h--since only

The date order is fixed: YYYY-MM-DD, the same order the CLI prints. Other orders such as 08/01/2026 are rejected with a usage error. A value without a time zone is read in the machine's local time zone, so the same command run in two time zones covers two different windows. Pass an RFC 3339 timestamp when the window has to be identical everywhere, which is the usual choice in CI.

Durations use hours, minutes, and seconds (h, m, s). There is no day unit: for a week use 168h or a date, not 7d. The fixed windows 7d, 30d, and 90d belong to observations --period only.

Timestamps in table output are printed in local time as YYYY-MM-DD HH:MM:SS. JSON output keeps the server's RFC 3339 UTC timestamps unchanged.

Commands

bsfw auth

CommandDescription
bsfw auth loginPrompt for, verify, and store an access token. --token-stdin reads it from stdin
bsfw auth statusShow resolved configuration and verify the token. --no-verify skips the request
bsfw auth logoutRemove the stored token for the selected profile

bsfw whoami

Show the organization, namespace, user, and exact permissions granted by the token. Use this first when a command returns 403.

bsfw whoami
bsfw whoami -o json

bsfw namespaces

List namespaces available to the current credentials. Personal and Service Access Tokens are pinned to one namespace, so they normally return one entry.

bsfw namespaces

bsfw firewall list

Every firewall in the namespace, with the IDs the other commands take. Firewalls created in the dashboard get a generated ID that differs from the display name, and commands, profiles, and API paths all want the ID.

bsfw firewall list

The four commands below take the firewall ID as an argument. Leave it out to use the firewall set on the profile or in BYTESAFE_FIREWALL; without either, the command exits with a usage error.

bsfw firewall show

Summarize one firewall: ecosystem, which firewall it inherits from, its upstreams, and how many rules and exceptions it has. For the raw configuration, use export.

bsfw firewall show <firewall-id>

bsfw firewall rules

The firewall's rules, one per row. --verbose adds the selector columns, and -o json returns the complete rule objects. Inherited rules are not included; they stay on the parent firewall.

bsfw firewall rules <firewall-id> --verbose

bsfw firewall exceptions

The firewall's exceptions, one per row. --verbose adds columns; -o json returns the complete objects, including reason, expiry, and notes, which is what a review of open exceptions needs.

bsfw firewall exceptions <firewall-id> -o json

Creating and changing policy

The CLI deliberately changes firewall policy as one configuration document. Export, edit, and import it. Granular rule and exception editing lives in the dashboard, and the CLI has no delete command for a firewall.

bsfw firewall export

Print the firewall's complete configuration as JSON on stdout: upstreams, rules, exceptions, and permissions. Redirect it to a file to keep it in git. Stored upstream credentials come out as placeholders, never as values, so an export is safe to commit. Inherited rules stay on the parent firewall and are not part of the document.

bsfw firewall export <firewall-id> > firewall-config.json

bsfw firewall import

Read a complete firewall configuration from a file or stdin and apply it. The id in the JSON says which firewall it belongs to; there is no --firewall flag here. Import creates that firewall when it does not exist and replaces its entire configuration when it does, so a rule or exception missing from the file is removed from the firewall.

bsfw firewall import --file firewall-config.json
bsfw firewall import < firewall-config.json

Imports reject plaintext credentials. Exported placeholders preserve stored values for the same firewall and upstream. A new firewall or upstream has no stored value to preserve; set it with firewall credentials.

bsfw firewall credentials

Set the credentials a firewall uses against one private upstream. This is the command that fills in what an export leaves as a placeholder, and the one to run on a schedule when an upstream's token expires.

Only the fields you pass change, so rotating a token does not mean re-entering a username and password. With no credential flags and no --stdin, the CLI prompts for the values without echoing them, which keeps secrets out of shell history.

bsfw firewall credentials --firewall <firewall-id> --upstream <upstream-id>
printf '%s' '{"token":"<upstream-token>"}' | \
  bsfw firewall credentials --firewall <firewall-id> --upstream <upstream-id> --stdin
FlagDescription
--firewall <id>Which firewall owns the upstream
--file <path>Take the firewall ID from a config JSON file instead of --firewall
--upstream <id>Required. The upstream's id from the firewall config, not its URL
--user <value>Username. Sent with the password as basic auth
--password <value>Password
--token <value>Token, sent as a bearer header. When both a token and a username and password are stored, the token is used
--stdinRead user, password, and token as one JSON object from stdin, for scripted rotation

bsfw logs

One entry per package request the firewall evaluated, and what it decided. This is where a failed install is explained. Results are newest first.

Without --user or --all-users, the server returns only the calling user's own entries, which is why a first run often looks empty. A Service Access Token belongs to no user, so a CI token sees nothing unless --all-users is given.

bsfw logs --firewall <firewall-id>
bsfw logs --all-users --blocked --since 24h
bsfw logs --all-users --follow -o ndjson
FlagDescription
--firewall <id>Only this firewall. Falls back to the profile default when omitted
--user <id>Only this user's requests, by raw user ID, not a name or email. Mutually exclusive with --all-users
--all-usersEveryone's requests, not just your own
--blockedOnly requests the firewall stopped. Start here when someone reports a failed install
--phase versions|download|uploadOnly one execution phase: versions is the metadata request that lists versions, download is fetching one artifact, upload is a publish
--search <text>Free-text match across user, ecosystem, package name, version, and message. Use the package name reported in the install error
--since <value>Entries newer than a duration or a point in time. See Dates and durations
--limit <n>Entries to return; default 100. Asking for more than one server page pages through automatically
-f, --followKeep the connection open and print entries as they arrive, reconnecting if the stream drops

--follow starts from the server's short backfill and then prints new entries, so --since and --limit do not apply to it. Filters and output format do.

The default table is a compact view. Use -o json for the complete response, -o ndjson for one complete entry per line (the form to pipe into jq while following), or -o logfmt for the legacy single-line form.

bsfw package

Known vulnerability and malware findings for one exact package version: the same data the firewall's rules evaluate. Use it to explain a block, or to check a dependency before adding it.

bsfw package <ecosystem> <name> <version>

Ecosystem is one of npm, pypi, maven, nuget, go, or conda. The name is written the way that ecosystem writes it, and the version must be exact: no ranges, no dist-tags.

bsfw package npm lodash 4.17.21
bsfw package npm @scope/name 1.2.3
bsfw package maven org.apache.commons:commons-lang3 3.14.0 -o json

An absent malware result is reported as unknown, not clean.

bsfw observations

The inventory view: which package versions your builds actually pulled through the firewall in a period, how often each was requested, and how many known vulnerabilities each carries today. Derived from traffic, not from manifests.

bsfw observations --period 30d --firewall <firewall-id>
bsfw observations --ecosystem npm -o json
FlagDescription
--period today|7d|30d|90dHow far back to look; default today. These four fixed windows are the only accepted values
--firewall <id>Only this firewall. Falls back to the profile default when omitted
--ecosystem <name>Keep only rows from one ecosystem. Applied by the CLI after the server returns a page, so other ecosystems still fill that page and a filtered page can come back empty while more pages remain
--page-size <n>Rows per server page; the server's own default applies when unset
--page-token <token>Continue from the previous page. The CLI prints the token to pass when more rows are available

The vulnerability column is compact: C2 H1 means two critical and one high, and - means none known. Use -o json for the counts as fields. Field-level detail: Observations reference.

bsfw audit

The audit log answers who changed the setup: policy edits, upstream credentials, tokens, teams, and denied attempts. It does not contain package traffic, which is what bsfw logs shows.

Every event records what it acted on as a resource type plus the ID of that one resource, so most reviews start by naming one or both. These are the values --resource-type takes:

Resource typeCovers
firewallConfig imports, rule and exception changes, upstream credentials, firewall deletion
tokenPersonal and Service Access Token creation and deletion
teamTeam creation, updates, and membership changes
namespaceNamespace creation, rename, settings, cloning, deletion
trusted_builderTrusted Builder registration and OIDC exchanges
license_setLicense set changes
webhookWebhook endpoint changes, test deliveries, and secret rotation
sessionSign-ins, including failed ones

--resource-id takes the exact ID the event carries. For a firewall that is the firewall ID from bsfw firewall list, never its display name. For the other types, copy the ID from an event with -o json or from the event detail in the dashboard, then reuse it as a filter.

bsfw audit --resource-type firewall --resource-id <firewall-id>
bsfw audit --outcome failure --from 2026-08-01
bsfw audit --action firewall.config.upsert -o json
FlagDescription
--resource-type <type>What kind of thing was acted on, from the table above
--resource-id <id>Which one, by ID. Combine with --resource-type to scope a review to a single firewall or token
--action <action>One exact action string, for example firewall.rule.upsert or token.delete. Prefixes and wildcards do not match; the full list is the action catalog
--actor <user-id>Who did it, by user ID. bsfw whoami prints your own ID; other users' IDs appear in -o json output and in the dashboard event detail. It is an opaque ID, not an email address
--outcome success|failureWhether the action went through. failure collects permission denials, rejected imports, and failed sign-ins, which is the useful review for spotting misconfigured automation
--from, --toTime bounds, inclusive. See Dates and durations
--limit <n>Events per request, 1 to 1000; default 100
--offset <n>Skip this many events. When more events match than were returned, the table footer prints the --offset value for the next page

The default table shows time, action, outcome, resource, actor, status, and IP. Use -o json for the complete events, including the request path and the action-specific eventData. Field-level detail: Audit log reference.

bsfw oidc exchange

Exchange a GitHub Actions OIDC ID token for a short-lived Service Access Token. With permissions: id-token: write, the CLI fetches the GitHub token automatically. The exchange endpoint does not require an existing access token.

bsfw oidc exchange --audience bytesafe:trusted-builder:<id>
FlagDescription
--audience <value>Trusted Builder audience; defaults to BYTESAFE_TRUSTED_BUILDER_AUDIENCE
--id-token <token>Supply the OIDC token instead of fetching it from GitHub Actions
--format token|jsonPrint only the token or the full response; default token

End-to-end setup: GitHub Actions.

bsfw completion

Generate completion scripts for bash, zsh, fish, or PowerShell. Homebrew installs completions automatically; release archives include generated completion files.

bsfw completion zsh > "${fpath[1]}/_bsfw"
bsfw completion bash > /etc/bash_completion.d/bsfw

bsfw version

Print the installed release, commit, build time, Go version, and platform. Release archives also include generated man pages.

bsfw version -o json

Exit codes

Scripts can branch on these instead of parsing messages.

CodeMeaningUsually means
0Success
1Runtime errorThe server rejected the request, for example an import that failed validation
2Usage errorA misspelled flag or value, a missing argument, or no endpoint, namespace, or firewall configured
3Authentication or authorization failure (401 or 403)Missing, expired, or wrong-namespace token, or a token without the permission the command needs. Run bsfw whoami
4Not found (404)The firewall, namespace, or package version does not exist under that ID. Check with bsfw firewall list
5Network failure or timeoutThe endpoint is unreachable, or the request took longer than --timeout

On this page