Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
# Set HOST_REPO_PATH in docker-compose.yml to enable the GUI upgrade button.
# Without this, use SSH + ./upgrade.sh for server upgrades.
#
# HOST_REPO_PATH=/home/jarrodl/bnk-forge-v2
# HOST_REPO_PATH=/path/to/bnk-forge

# ============================================================================
# ENVIRONMENT (development/staging/production)
Expand Down
17 changes: 11 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -668,12 +668,17 @@ jobs:
# dependencies. No `|| true` here: if this cannot run, the job has
# nothing to say and must fail loudly rather than silently continue.
#
# Known fragility, accepted deliberately: the floor's models are
# imported under CURRENT pins, so the gap widens every time a
# dependency moves. v3.0.1 pins cryptography 44 and staging is on 50 —
# six majors — and it holds only because the floor tree touches just
# Fernet, hazmat.primitives.serialization and Ed25519PrivateKey, all
# unchanged across that range. When it does bite, it bites as a
# Known limitation on this repo: f5devcentral/bnk-forge is a squashed

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the part that matters, more than the MIN_UPGRADE_FROM revert itself -- the inherited "v3.0.1 pins cryptography 44 and staging is on 50" paragraph was a specific, confident claim about a tree this repo can't reach, and it's what sent this PR at a tag that doesn't exist. Recording the squashed-mirror constraint means the next person reads why the window is one release wide instead of trying to widen it the same way.

CI agrees: P2 · Migration Upgrade From Released Version (Postgres) is back to COMPLETED/SUCCESS.

# public mirror and carries exactly ONE final tag, v3.1.6, so the floor
# is currently that tag and the upgrade window is one release wide —
# the degenerate case this check otherwise warns against. It can't be
# widened by naming an older tag (v3.0.1 etc. from the upstream history
# aren't reachable here); it widens only as more finals are cut on this
# repo. Accepted deliberately.
#
# The floor's models are imported under CURRENT pins, so a dependency
# gap can still bite once the window does widen. When it does, it bites
# as a
# MANDATORY gate failing hard on a commit that changed nothing
# relevant. The fix then is to raise MIN_UPGRADE_FROM to a release
# whose models import cleanly, not to add `|| true` here: a floor that
Expand Down
33 changes: 20 additions & 13 deletions .trivyignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,28 @@
# projects rebuild with a patched Go version.
#
# Review this file periodically and remove entries when upstream fixes are available.
# Each entry carries an `exp:` review-by date — Trivy drops the suppression after it,
# forcing a re-check. Extend an entry only after re-confirming no upstream fix exists.

# CVE-2025-68121: Go stdlib crypto/tls - Unexpected session resumption
# Fixed in Go >= 1.24.13 / 1.25.7 / 1.26.0-rc.3
# Affects: helm (Go 1.25.0), kubectl, tofu (Go 1.25.6), infracost (Go 1.25.4)
# All current latest releases use Go < 1.25.7 — no upstream fix available yet
# Added: 2026-02-23
CVE-2025-68121
CVE-2025-68121 exp:2026-11-30

# CVE-2024-45337: golang.org/x/crypto/ssh - Misuse of ServerConfig.PublicKeyCallback
# Present in infracost binary's bundled dependencies
# Not exploitable in our context (we don't run an SSH server via infracost)
# Added: 2026-02-23
CVE-2024-45337
CVE-2024-45337 exp:2026-11-30

# CVE-2026-33186: gRPC authorization bypass (google.golang.org/grpc < 1.79.3)
# Affects: helm and tofu binaries in Docker image (grpc v1.76.0)
# Status: Waiting for upstream helm/tofu releases with fixed grpc
# Tracked: GitHub issue #50
# Added: 2026-04-15
CVE-2026-33186
CVE-2026-33186 exp:2026-11-30

# CVE-2026-7598: libssh2 — integer overflow via large username/password
# Affects: libssh2-1t64 1.11.1-1 in Debian trixie base image
Expand All @@ -32,11 +34,16 @@ CVE-2026-33186
# The vulnerable code path requires libssh2 to negotiate auth with a
# malicious remote SSH server, which our HTTP backend never does.
# Upstream: no Debian backport yet (Trivy reports empty fix column).
# REVISIT: monthly via https://security-tracker.debian.org/tracker/CVE-2026-7598
# escalate to pin-from-sid if no fix by 2026-08-12
# REVISIT: monthly. Do not extend this entry on the assertion that no fix exists —
# confirm it: re-run `trivy image` (or check the tracker) and only keep
# the ignore while the fix column is still empty for our base image's
# libssh2. https://security-tracker.debian.org/tracker/CVE-2026-7598
# The 2026-08-12 deadline lapsed without that re-check; next check by
# 2026-09-12, and escalate to pin-from-sid if a fixed version is then
# available and we're still ignoring it.
# Tracked: memory/followup_trivyignore_cve_2026_7598_revisit.md
# Added: 2026-05-12
CVE-2026-7598
CVE-2026-7598 exp:2026-09-12

# CVE-2026-42010: GnuTLS Authentication Bypass via NUL Character in DN parsing
# Affects: libgnutls30t64 in our Debian Trixie base image (3.8.9-3+deb13u2)
Expand All @@ -49,7 +56,7 @@ CVE-2026-7598
# REVISIT: monthly via https://security-tracker.debian.org/tracker/CVE-2026-42010
# Pattern mirror of CVE-2026-33845 / CVE-2026-7598 suppressions.
# Added: 2026-05-14
CVE-2026-42010
CVE-2026-42010 exp:2026-11-30

# CVE-2026-42496: perl — Archive::Tar < 3.08 extracts symlinks unsafely
# CVE-2026-8376: perl — heap buffer overflow in the interpreter (<= 5.43.10)
Expand All @@ -69,8 +76,8 @@ CVE-2026-42010
# https://security-tracker.debian.org/tracker/CVE-2026-8376
# Drop once Debian ships a trixie point-release with patched perl.
# Added: 2026-06-02
CVE-2026-42496
CVE-2026-8376
CVE-2026-42496 exp:2026-11-30
CVE-2026-8376 exp:2026-11-30

# CVE-2026-13221: libperl5.40 — silently incorrect results in Perl <= 5.43.9
# Affects: libperl5.40 5.40.1-6 in the python:3.11-slim (Debian trixie) base image
Expand All @@ -80,7 +87,7 @@ CVE-2026-8376
# REVISIT: monthly via https://security-tracker.debian.org/tracker/CVE-2026-13221
# drop once Debian ships a trixie update with a patched libperl5.40.
# Added: 2026-07-15
CVE-2026-13221
CVE-2026-13221 exp:2026-11-30

# CVE-2026-60002: openssh-client — memory corruption in SSH client
# Affects: openssh-client in the python:3.11-slim (Debian trixie) base image
Expand All @@ -91,7 +98,7 @@ CVE-2026-13221
# REVISIT: monthly via https://security-tracker.debian.org/tracker/CVE-2026-60002
# drop once Debian ships a trixie update with a patched openssh-client.
# Added: 2026-07-15
CVE-2026-60002
CVE-2026-60002 exp:2026-11-30

# CVE-2026-33845: GnuTLS DTLS — reachable-assert / auth bypass in DN parsing
# Affects: libgnutls30t64 in our Debian Trixie base image (3.8.9-3+deb13u2)
Expand All @@ -105,7 +112,7 @@ CVE-2026-60002
# Check: https://security-tracker.debian.org/tracker/CVE-2026-33845
# Tracked: GitHub issue #103
# Added: 2026-05-06
CVE-2026-33845
CVE-2026-33845 exp:2026-11-30

# CVE-2026-57433: perl Storable signed-integer flaw (Storable < 3.41)
# Affects: libperl5.40, perl-base (5.40.1-6) in our Debian Trixie base image.
Expand All @@ -117,4 +124,4 @@ CVE-2026-33845
# trixie-security. Check: https://security-tracker.debian.org/tracker/CVE-2026-57433
# Tracked: GitHub issue #492
# Added: 2026-07-22
CVE-2026-57433
CVE-2026-57433 exp:2026-11-30
47 changes: 46 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,54 @@
# Changelog

All notable changes to BNK-Forge v2.
All notable changes to BNK-Forge.

---

## v3.1.6 (2026-08-10) — 3.1.x line

Milestone `v3.1.6` — the last release before 4.0.0, and the initial public
release tag on `f5devcentral`. This mirror is squashed: the `v3.1.6` tag is a
single `feat: initial public release` commit, so there is no per-change history
behind it to link here. Work that came *after* this tag — including the
container-runner hardening series (#2, #123, #161) and the ADR-424 bare-metal/DPU
work — is part of 4.0.0, not v3.1.6, and is recorded under the 4.0.0 entry when
that release is cut.

> **Heads-up for the 4.0.0 upgrade — two breaking changes:**
>
> 1. **Container runner non-root gate:** it now refuses *named* users — an image
> using the distroless-standard `USER nonroot` is rejected. Switch it to a
> numeric uid. Use **`USER 1000`**: the workspace is mounted from the host
> and chowned `1000:1000`, so uid 1000 is the only value that clears the gate
> *and* can write it. A higher uid such as `65532` passes the non-root gate but
> cannot write the workspace, so the step fails on its first write.
> 2. **`MCP_SERVICE_PASSWORD` becomes required in 4.0.0 (via bonnyr-f5 #188):**
> starting with 4.0.0 the backend refuses to boot in staging/production if it
> is unset or still a shipped default (`changeme` / `mcp-service-changeme`).
> That boot-time check ships in #188 — it is *not* in the 3.1.x line and is
> called out here only so the upgrade step is ready before #188 lands. Every
> existing install still carries one of those defaults, so before upgrading to
> 4.0.0 **set `MCP_SERVICE_PASSWORD` to a real secret** (the same value the MCP
> server receives as `BNK_FORGE_PASSWORD`); once #188 is in the tree, leaving
> it at a default will `SystemExit` the stack at startup.
>
> **Merge ordering (integration dependency).** The dist-bundle wiring these two
> steps assume — the dedicated `mcp` service account for the bundled MCP server,
> and the `MCP_SERVICE_PASSWORD` boot check — arrives in **bonnyr-f5 #186** (service
> account + removal of the shipped `changeme` / `mcp-service-changeme` defaults) and
> **#188** (boot check). This release documents them forward-looking and is therefore
> sequenced to merge **with or after #186 + #188**. Merged ahead of them, the
> `MCP_SERVICE_PASSWORD` guidance is inert for the dist stack (the compose file does
> not pass that variable to the backend) and #186 will conflict in
> `user-pack/install-guide.html` — resolve by taking #186's credential model, not by
> re-adding the `changeme` default this guide describes as a stopgap.

## v3.0.1 — 3.0.x line

The first 3.x release after the 2.x line below (upstream tag dated 2026-04-09).
Bridged entry; this repo is a squashed public mirror, so the `v3.0.1` tag and its
per-change history live upstream, not here.

## v2.10.74 (2026-03-04) — TMM Debug Panel Enhancements: F5 Docs Commands, Netkvest, Bug Fix

### Bug Fixes
Expand Down
7 changes: 5 additions & 2 deletions backend/services/execution/container_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -550,7 +550,9 @@ def is_root_user(image_user: str | None) -> bool:
An image that never declares USER reports an empty string and runs as
root — that is the common case and must be caught.

Closes the numeric bypass only — see the KNOWN GAP note in the body.
Fails closed on anything that is not a bare non-zero decimal uid,
which also subsumes the named-alias case (see the body) — there is no

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checked this rather than taking it at face value, since "there is no remaining KNOWN GAP" is a strong claim to put in a security docstring. It holds:

  • _NUMERIC_UID_RE = re.compile(r"^[0-9]+$") -- anchored both ends, so +0, -0 and ² all fail the match and return True (refused).
  • The polarity is refuse-unless-provable, so an unrecognised USER is rejected rather than classified non-root.
  • return not (0 < value < 2**31) catches the uint32-narrowing case (4294967296 -> uid 0 in the container) that a bare != 0 would let through.

The named-alias case really is subsumed. Good change.

remaining KNOWN GAP.

Only the uid half decides this. Docker's USER is ``<user>[:<group>]``,
so an image declaring ``USER 0:100`` or ``USER root:wheel`` runs as uid 0
Expand Down Expand Up @@ -647,7 +649,8 @@ def _fail(message: str, stdout: str = "") -> StepResult:
f"Artifact image {spec.image_digest} runs as root "
f"(USER={image_user or '<unset>'}). Refusing to start it: the workspace is "
f"mounted from the host, so a root container is a host-root write primitive. "
f"Rebuild the image with a NUMERIC non-root USER (e.g. `USER 65532`). "
f"Rebuild the image with a NUMERIC non-root USER — `USER 1000` matches "
f"the workspace owner (chowned 1000:1000), so the step can write it. "
f"A named user is refused because it cannot be resolved to a uid "
f"without the image's own /etc/passwd — `USER toor` may well be uid 0. "
f"The Kubernetes substrate already enforces this: runAsNonRoot is "
Expand Down
2 changes: 1 addition & 1 deletion bin/roadmap-add.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ def main():
ap = argparse.ArgumentParser(description="Append an item to docs/roadmap.yaml")
ap.add_argument("--section", help="section id (see --list-sections)")
ap.add_argument("--title")
ap.add_argument("--status", help="status key (shipped/in_progress/blocked/deferred/planned)")
ap.add_argument("--status", help="status key (shipped/merged/in_progress/blocked/deferred/planned)")
ap.add_argument("--refs", default="", help='comma-separated, e.g. "#216,PR #188"')
ap.add_argument("--note", default="")
ap.add_argument("--group", default="")
Expand Down
3 changes: 2 additions & 1 deletion bin/roadmap-gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -395,11 +395,12 @@ def main():
print("Wrote %s" % MD_PATH)
print("Wrote %s" % HTML_PATH)
print(
"Stats: in_progress=%d planned=%d shipped=%d blocked=%d deferred=%d"
"Stats: in_progress=%d planned=%d shipped=%d merged=%d blocked=%d deferred=%d"
% (
count_status(data["sections"], "in_progress"),
count_status(data["sections"], "planned"),
count_status(data["sections"], "shipped"),
count_status(data["sections"], "merged"),
count_status(data["sections"], "blocked"),
count_status(data["sections"], "deferred"),
)
Expand Down
4 changes: 2 additions & 2 deletions dist/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ COMPOSE_PROJECT_NAME=bnk-forge

# ── Container Registry ──────────────────────────────────────────────────────
# Where to pull BNK Forge images from (no trailing slash)
BNK_FORGE_REGISTRY=ghcr.io/your-org
BNK_FORGE_VERSION=3.0.1
BNK_FORGE_REGISTRY=ghcr.io/f5devcentral
BNK_FORGE_VERSION=latest

# ── Database ────────────────────────────────────────────────────────────────
POSTGRES_PASSWORD=bnkforge_dev_password
Expand Down
52 changes: 21 additions & 31 deletions dist/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
## Prerequisites

- **Docker Engine 24+** with **Docker Compose v2.24+**
- Access to the BNK Forge container registry (if private)
- Network access to `ghcr.io` (images are public — no registry login required)
- 4 GB RAM minimum (8 GB recommended)
- 10 GB disk space

Expand All @@ -12,8 +12,8 @@
### 1. Download and extract

```bash
tar xzf bnk-forge-3.0.1.tar.gz
cd bnk-forge-3.0.1
tar xzf bnk-forge-3.1.6.tar.gz
cd bnk-forge-3.1.6
```

### 2. Configure
Expand All @@ -27,25 +27,12 @@ nano .env # Set BNK_FORGE_REGISTRY and passwords

| Variable | Description | Example |
|---|---|---|
| `BNK_FORGE_REGISTRY` | Container registry URL (no trailing slash) | `ghcr.io/your-org` |
| `BNK_FORGE_VERSION` | Image version tag | `3.0.1` |
| `BNK_FORGE_REGISTRY` | Container registry URL (no trailing slash) | `ghcr.io/f5devcentral` (public) |
| `BNK_FORGE_VERSION` | Image version tag | `3.1.6` |
| `POSTGRES_PASSWORD` | PostgreSQL password | *(change for production)* |
| `REDIS_PASSWORD` | Redis password | *(change for production)* |

### 3. Authenticate to registry (if private)

```bash
# GitHub Container Registry
echo $GITHUB_TOKEN | docker login ghcr.io -u USERNAME --password-stdin

# Docker Hub
docker login

# AWS ECR
aws ecr get-login-password | docker login --username AWS --password-stdin ACCOUNT.dkr.ecr.REGION.amazonaws.com
```

### 4. Install
### 3. Install

**Linux server** (host networking — production):
```bash
Expand All @@ -59,7 +46,7 @@ chmod +x install.sh
./install.sh --local
```

### 5. Access
### 4. Access

- **Mac/Windows (`--local`):** open **https://localhost**
- **Linux server:** open **https://\<server-ip\>** — the installer prints the exact URL at the end
Expand Down Expand Up @@ -183,7 +170,7 @@ gunzip -c backup_20260417.sql.gz | docker exec -i bnk-forge-postgres psql -U bnk
## File Structure

```
bnk-forge-3.0.1/
bnk-forge-3.1.6/
├── docker-compose.yml # Main compose (Linux server — host networking)
├── docker-compose.local.yml # Overlay for macOS/Windows (bridge networking)
├── .env.example # Configuration template
Expand Down Expand Up @@ -235,17 +222,17 @@ This creates `dist/bnk-forge-VERSION.tar.gz` containing all files needed for ins
echo $GITHUB_TOKEN | docker login ghcr.io -u USERNAME --password-stdin

# Build + push all images for amd64 + arm64 (default)
make push-images BNK_FORGE_REGISTRY=ghcr.io/your-org
make push-images BNK_FORGE_REGISTRY=ghcr.io/f5devcentral

# Or push only amd64 (faster, if you don't need ARM)
make push-images BNK_FORGE_REGISTRY=ghcr.io/your-org PLATFORMS=linux/amd64
make push-images BNK_FORGE_REGISTRY=ghcr.io/f5devcentral PLATFORMS=linux/amd64
```

This uses `docker buildx build --push` to build all 6 images (api, worker, beat, frontend, proxy, mcp) for both architectures and push **multi-arch manifest lists** to the registry. Each tag (e.g., `bnk-forge-api:3.0.1`) is a manifest that Docker automatically resolves to the correct platform on `docker pull`.
This uses `docker buildx build --push` to build all 7 images (api, worker, beat, frontend, proxy, mcp, operator) for both architectures and push **multi-arch manifest lists** to the registry. Each tag (e.g., `bnk-forge-api:3.1.6`) is a manifest that Docker automatically resolves to the correct platform on `docker pull`.

**Verify the manifest:**
```bash
docker manifest inspect ghcr.io/your-org/bnk-forge-api:3.0.1
docker manifest inspect ghcr.io/f5devcentral/bnk-forge-api:3.1.6
```

You should see entries for both `linux/amd64` and `linux/arm64`.
Expand All @@ -266,18 +253,21 @@ gh release create v${VERSION} dist/bnk-forge-${VERSION}.tar.gz \

### What `gh release create` does

1. Creates a Git tag (`v3.0.1`) on the current commit
2. Creates a GitHub Release page at `https://github.com/your-org/bnk-forge/releases/tag/v3.0.1`
1. Creates a Git tag (`v3.1.6`) on the current commit
2. Creates a GitHub Release page at `https://github.com/f5devcentral/bnk-forge/releases/tag/v3.1.6`
3. Uploads the tarball as a downloadable release asset

### End-user download URL

After publishing, users can download and install with:
Once a full (non-prerelease) `vX.Y.Z` release with an attached tarball exists, users
download and install with the URL below — substitute the version you actually published
(the example `3.1.6` is illustrative; no release asset exists until you cut one):

```bash
# Download from GitHub Releases
curl -L https://github.com/your-org/bnk-forge/releases/download/v3.0.1/bnk-forge-3.0.1.tar.gz | tar xz
cd bnk-forge-3.0.1
# Download from GitHub Releases — replace 3.1.6 with your published version
VERSION=3.1.6
curl -L https://github.com/f5devcentral/bnk-forge/releases/download/v${VERSION}/bnk-forge-${VERSION}.tar.gz | tar xz
cd bnk-forge-${VERSION}
./install.sh
```

Expand Down
Loading
Loading