BytesafeDependency Firewall

Logs

Field-level reference for firewall log entries and the query and streaming API.

A firewall log entry records one of two things: a rule decision on a package request, or a failed call to an upstream registry. For reading them in practice, see View logs; for how they relate to the other records, see Observability.

Entry shape

{
  "timestamp": "2026-07-03T09:14:02Z",
  "user": "user-7d2f...",
  "request": "8f4c2e...",
  "interaction": "build-4711",
  "userAgent": "npm/10.8.1 node/v22.4.0",
  "remoteAddress": "203.0.113.7",
  "firewall": "3e7f5cb806f26e3beae2",
  "firewallVersion": 12,
  "upstream": "npmjs",
  "rule": "block-critical-vulns",
  "executionPhase": "download",
  "blocked": true,
  "ecosystem": "npm",
  "packageName": "lodash",
  "version": "4.17.20",
  "published": "2020-08-13T16:53:52Z",
  "message": "Block critical vulnerabilities",
  "output": { "vulnerabilities": ["CVE-2020-8203"] }
}

Fields

FieldTypeDescription
timestampstringWhen the decision was made
userstringWho made the request, as an opaque user ID (the token's OIDC subject), not an email or display name. PATs resolve to the user; SATs to the token's identity. The dashboard resolves IDs to names where it can
requeststringRequest ID. Also shown in blocked-install error messages, which is how a developer's error finds this entry
interactionstringInteraction ID from the token suffix, if any
userAgentstringClient user agent
remoteAddressstringClient address
firewallstringFirewall ID the request hit
firewallVersionnumberConfig version the request was evaluated under
sourceFirewallstringFirewall the matched rule came from, when it was inherited. Absent for the firewall's own rules
sourceFirewallVersionnumberConfig version of sourceFirewall
upstreamstringUpstream that served or would have served the package
rulestringRule that matched
exceptionstringException ID, when an exception allowed the request. Message is EXCEPTION
exceptionSourceFirewallstringFirewall the applied exception came from, when it was inherited. Absent for the firewall's own exceptions
exceptionSourceFirewallVersionnumberConfig version of exceptionSourceFirewall
executionPhasestringversions, download, or upload. Reflects the request, not the matched rule's phase: a versions rule that matches during an artifact fetch is recorded as download, so versions entries always come from metadata requests
blockedbooleanWhether the effect blocked the request
ecosystemstringPackage ecosystem
packageNamestringPackage name
versionstringPackage version
publishedstringThe version's publish date, when known
messagestringRule description, or rule-specific message such as Malware detected in npm/example@1.0.0: ...
outputobjectRule-specific detail: vulnerability IDs and scores; malware finding summary, scanner, confidence, and signals; redacted secrets findings
upstreamErrorobjectPresent only on upstream error entries, which record a failed upstream call instead of a rule decision

One request can produce several entries: each matching log-effect rule writes one, and a block ends the sequence.

Upstream error entries

A firewall fetches packages from its upstream registries, often more than one. When a call to one of them fails, the firewall answers with what the other upstreams returned, so the request often succeeds and the client sees nothing unusual. The failed call gets its own log entry, which is what makes a failing upstream visible.

An error entry describes a failed upstream call, not a decision about a package. It carries no rule and no exception, blocked is always false, and packageName and version are empty when the call failed before the firewall knew them.

{
  "timestamp": "2026-08-20T11:02:41Z",
  "user": "user-7d2f...",
  "request": "3b91af...",
  "userAgent": "npm/10.8.1 node/v22.4.0",
  "firewall": "3e7f5cb806f26e3beae2",
  "firewallVersion": 12,
  "upstream": "npmjs",
  "executionPhase": "versions",
  "blocked": false,
  "ecosystem": "npm",
  "packageName": "lodash",
  "version": "",
  "message": "upstream request failed: rate limited by upstream (status 429) [https://registry.npmjs.org]",
  "upstreamError": {
    "endpoint": "https://registry.npmjs.org",
    "url": "https://registry.npmjs.org/lodash",
    "status": 429,
    "reason": "rate limited by upstream"
  }
}
FieldTypeDescription
upstreamstringThe upstream that failed, by its configured ID
upstreamError.endpointstringThat upstream's endpoint
upstreamError.urlstringThe exact URL that failed
upstreamError.statusnumberThe status the upstream returned. Absent when there was no response at all, such as a timeout or a hostname that did not resolve
upstreamError.reasonstringShort description of the failure, such as rate limited by upstream or dns lookup failed
upstreamError.countnumberHow many identical failures this one entry stands for. Absent when it stands for a single failure

What is recorded

  • Rate limiting (429), server errors (5xx), denied access (403), and any other unexpected status.
  • 401 from an upstream that has credentials stored. That is how a wrong or expired credential shows up; see Manage upstream credentials.
  • No response at all: a hostname that did not resolve, a request that timed out, a refused connection, or an address the firewall is not allowed to reach.

These are not recorded:

  • 404 and 410. The upstream is saying it has no such package or version, which is a normal answer: the firewall asks the next upstream and carries on.
  • 401 from an upstream that has no credentials stored. Some registries answer this way instead of 404 for a package the caller cannot see, so it means the same thing here.

If the same upstream fails the same way several times inside one request, the failures are collapsed into one entry with a count. Nothing is collapsed across requests: an upstream that keeps failing writes one entry per affected request, not one entry for the whole outage.

Entries are written for metadata and download requests in every ecosystem, and for NuGet search and autocomplete requests. A few fallback paths inside the firewall handle a failed call without recording it, so an entry is good evidence that an upstream is failing, while an empty result only means nothing was recorded.

Upstream error entries are never delivered to webhooks.

Query API

GET /v1/<namespace-id>/log
ParameterDescription
firewallFilter to one firewall
userFilter to a specific user, by raw user ID, not display name
allUserstrue for entries from everyone. Mutually exclusive with user
searchFree-text search
phaseOnly one execution phase: versions, download, or upload
kindrule for rule decisions, error for upstream error entries. Both kinds when omitted
statusblocked, allowed, or error. Every outcome when omitted. The three are exclusive, and an upstream failure outranks a block: an entry that blocked and also recorded a failed upstream matches error, not blocked
limitEntries per page. Default 100, capped at 1000
beforeOpaque cursor from a previous response's nextCursor, to fetch the next older page

Without user or allUsers, the server returns the caller's own entries; this default is server-side, not a CLI convenience. Reading logs requires only namespace access.

{
  "entries": [],
  "nextCursor": "MTc1MTUzMjA0MjAwMDAwMDAwMDoxNDIz"
}

Entries are newest first. nextCursor is an opaque token: pass it back as before for the next page, and treat an empty value as the end of the results. Do not parse it. An invalid limit, before, phase, kind, or status value is answered with 400.

Streaming

GET /v1/<namespace-id>/log/stream

Server-sent events with the same parameters: 10 entries of backfill, then new entries as they happen, with a heartbeat every 20 seconds. This is what bsfw logs --follow and the dashboard's live view consume.

On this page