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 this | Use |
|---|---|
| See which firewalls exist and how one is set up | bsfw firewall list, bsfw firewall show |
| Find out why an install was blocked | bsfw logs |
| Check what the firewall knows about one package version | bsfw package |
| Keep policy in git and apply it | bsfw firewall export, bsfw firewall import |
| See who changed a firewall, a token, or a team | bsfw audit |
| List the packages your builds actually pulled | bsfw observations |
| Authenticate a CI job without storing a token | bsfw 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/bsfwInstall 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| Variable | Description |
|---|---|
BSFW_VERSION | Install a specific release, for example v0.2.0, instead of latest |
BSFW_INSTALL_DIR | Install the binary in this directory |
BSFW_BASE_URL | Download 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 versionInitial 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 whoamiauth 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:
- Command flag
- Environment variable
- Active profile
- 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.
| Command | Description |
|---|---|
bsfw profile list | List profiles; * marks the active profile |
bsfw profile show | Show 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-usersEnvironment variables
| Variable | Description |
|---|---|
BYTESAFE_ENDPOINT | Management API base URL |
BYTESAFE_NAMESPACE | Namespace ID |
BYTESAFE_TOKEN | Access token |
BYTESAFE_FIREWALL | Default firewall ID |
BYTESAFE_PROFILE | Profile to use |
BYTESAFE_CONFIG | Full path to the profile config file |
BYTESAFE_CREDENTIAL_STORE | Stored-token backend: auto, keyring, or file |
BYTESAFE_INTERACTION | Interaction ID appended to the token for firewall and audit-log correlation |
BYTESAFE_TRUSTED_BUILDER_AUDIENCE | Default audience for bsfw oidc exchange |
NO_COLOR | Disable 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
| Flag | Description |
|---|---|
--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|file | Stored-token backend |
--interaction <id> | Interaction ID for log correlation |
-o, --output table|json | Output format. logs also supports ndjson and logfmt |
--timeout <seconds> | Request timeout; default 30 |
--no-color | Disable colored output |
Dates and durations
Every flag that takes a point in time (logs --since, audit --from, audit --to) accepts the same four forms.
| Form | Example | Means |
|---|---|---|
| Date | 2026-08-01 | Midnight 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 timestamp | 2026-08-01T14:30:00Z | Exactly that instant, in the time zone the value carries |
| Duration back from now | 30m, 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
| Command | Description |
|---|---|
bsfw auth login | Prompt for, verify, and store an access token. --token-stdin reads it from stdin |
bsfw auth status | Show resolved configuration and verify the token. --no-verify skips the request |
bsfw auth logout | Remove 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 jsonbsfw 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 namespacesbsfw 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 listThe 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> --verbosebsfw 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 jsonCreating 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.jsonbsfw 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.jsonImports 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| Flag | Description |
|---|---|
--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 |
--stdin | Read 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| Flag | Description |
|---|---|
--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-users | Everyone's requests, not just your own |
--blocked | Only requests the firewall stopped. Start here when someone reports a failed install |
--phase versions|download|upload | Only 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, --follow | Keep 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 jsonAn 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| Flag | Description |
|---|---|
--period today|7d|30d|90d | How 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 type | Covers |
|---|---|
firewall | Config imports, rule and exception changes, upstream credentials, firewall deletion |
token | Personal and Service Access Token creation and deletion |
team | Team creation, updates, and membership changes |
namespace | Namespace creation, rename, settings, cloning, deletion |
trusted_builder | Trusted Builder registration and OIDC exchanges |
license_set | License set changes |
webhook | Webhook endpoint changes, test deliveries, and secret rotation |
session | Sign-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| Flag | Description |
|---|---|
--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|failure | Whether the action went through. failure collects permission denials, rejected imports, and failed sign-ins, which is the useful review for spotting misconfigured automation |
--from, --to | Time 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>| Flag | Description |
|---|---|
--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|json | Print 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/bsfwbsfw version
Print the installed release, commit, build time, Go version, and platform. Release archives also include generated man pages.
bsfw version -o jsonExit codes
Scripts can branch on these instead of parsing messages.
| Code | Meaning | Usually means |
|---|---|---|
0 | Success | |
1 | Runtime error | The server rejected the request, for example an import that failed validation |
2 | Usage error | A misspelled flag or value, a missing argument, or no endpoint, namespace, or firewall configured |
3 | Authentication or authorization failure (401 or 403) | Missing, expired, or wrong-namespace token, or a token without the permission the command needs. Run bsfw whoami |
4 | Not found (404) | The firewall, namespace, or package version does not exist under that ID. Check with bsfw firewall list |
5 | Network failure or timeout | The endpoint is unreachable, or the request took longer than --timeout |
Related
- How-to: View logs, Review the audit log, Manage upstream credentials
- Getting started: Access Tokens, GitHub Actions, Config as code
- Reference: API, Observations, Audit log