Skip to content
Merged
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
4 changes: 2 additions & 2 deletions .clusterfuzzlite/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
# `website/`, `node_modules/` and `.git/` out of the context.
#
# base-builder-go currently ships Go 1.25.0 while go.mod asks for 1.26.0 with
# `toolchain go1.27.1`. GOTOOLCHAIN is `auto` in this image, so the go command
# downloads go1.27.1 from the module proxy on first use. That is why
# `toolchain go1.27.2`. GOTOOLCHAIN is `auto` in this image, so the go command
# downloads go1.27.2 from the module proxy on first use. That is why
# proxy.golang.org and sum.golang.org are in the egress allow-lists of both
# ClusterFuzzLite workflows — without them the toolchain switch fails before a
# single fuzzer is built.
Expand Down
4 changes: 4 additions & 0 deletions .coderabbit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ reviews:
- "!website/public/**"
- "!docs/public/**"
- "!CHANGELOG.md"
# Docs archives are frozen copies of older releases, held to their release
# tag by scripts/docs-archive-config-test.sh. Review the provenance file
# (docs/content/archive-provenance.json) and that contract, not the copy.
- "!docs/content/docs/v*/**"

path_instructions:
- path: "internal/auth/**"
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/ci-verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,12 @@ jobs:
- name: Verify the package release contract
run: bash scripts/package-release-config-test.sh

# Archives under docs/content/docs/vX.Y/ must still be byte copies of the
# docs at their recorded release tag. Needs the full-depth checkout above
# for the tags; with no archives it only checks the rules that need none.
- name: Verify the docs archive contract
run: bash scripts/docs-archive-config-test.sh

# Drives the Compose containerd check against a fake `go` on PATH and
# canned build info, so it needs no toolchain and no network.
- name: Verify the Compose containerd check
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/quality-fuzz-cflite-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ run-name: >-
# StepSecurity insights link lists what was actually attempted; add the host
# there rather than guessing. storage.googleapis.com is for the gcr.io blob
# layers, and proxy.golang.org/sum.golang.org carry both the modules and the
# go1.27.1 toolchain that base-builder-go's Go 1.25.0 downloads on first use.
# go1.27.2 toolchain that base-builder-go's Go 1.25.0 downloads on first use.
#
# The three artifact hosts (*.actions.githubusercontent.com,
# results-receiver.actions.githubusercontent.com, *.blob.core.windows.net) are
Expand Down Expand Up @@ -106,7 +106,7 @@ jobs:
code-change-fuzz:
name: "ClusterFuzzLite (code change)"
runs-on: ubuntu-24.04
# Pulling base-builder-go, downloading the go1.27.1 toolchain, building ten
# Pulling base-builder-go, downloading the go1.27.2 toolchain, building ten
# libFuzzer targets, and 300 seconds of fuzzing. 30 minutes is roughly
# double the expected wall time.
timeout-minutes: 30
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/quality-integration-engines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ run-name: >-
# that way, so the two lanes can never drift into testing different things.
#
# Why these pins. internal/docker/client.go falls back to API v1.44 when
# version negotiation fails, and docs/content/docs/api-reference.mdx and
# version negotiation fails, and docs/content/docs/current/api-reference.mdx and
# standalone-mode.mdx both publish v1.44 as the floor. Docker Engine 25.0 is
# the oldest release that serves API v1.44 (24.0 serves v1.43), so 25.0 is the
# oldest Engine the documented contract covers and it anchors the bottom of
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/release-cut.yml
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,11 @@ jobs:
- name: Verify the package release contract against the tag
run: bash scripts/package-release-config-test.sh

# A cut that moves current/ to a new line must already carry the archive
# of the line it leaves, built from that line's last tag.
- name: Verify the docs archive contract against the tag
run: bash scripts/docs-archive-config-test.sh

- name: Create and push release tag
env:
RELEASE_TAG: ${{ steps.next.outputs.release_tag }}
Expand Down
2 changes: 1 addition & 1 deletion BENCHMARKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ the comment block at the top of
- **Runner:** GitHub-hosted `ubuntu-24.04`, `cpu: Intel(R) Xeon(R) 6973P-C`
(`goos: linux`, `goarch: amd64`).
- **Go:** `go1.26.6` — the toolchain `go.mod` pinned on `main` at that commit.
`dev/v0.9` has since moved to `toolchain go1.27.1`; the next monthly run
`dev/v0.9` has since moved to `toolchain go1.27.2`; the next monthly run
will record numbers on that toolchain, and `benchstat`'s hardware-match
walk-back means a run on a new toolchain still compares fine as long as the
CPU model matches.
Expand Down
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [v0.9.25] - 2026-10-09

### Security

- Update the Go toolchain and builder images to 1.27.2, which fixes 13
standard-library advisories: GO-2026-6599, GO-2026-6600, GO-2026-6603,
GO-2026-6604, GO-2026-6605, GO-2026-6607, GO-2026-6608, GO-2026-6609,
GO-2026-6610, GO-2026-6611, GO-2026-6612, GO-2026-6613, and GO-2026-6617.
`govulncheck` reported ten of them as reachable from Portwing's code on Go
1.27.1, which v0.9.24 was built with.
The lint script moves to golangci-lint v2.14.0, because v2.13.2 cannot read
the export data Go 1.27.2 writes.

### Changed

- The documentation source moved to `docs/content/docs/current/`. Every
published `/docs/...` URL is unchanged. This prepares for archiving each
release line's docs at `/docs/vX.Y/` from v1.0.

## [v0.9.24] - 2026-10-08

### Fixed
Expand Down
4 changes: 2 additions & 2 deletions COMPATIBILITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ DrydockCompat bumps.

| Portwing version | Drydock wire connection | Full feature compatibility | sockguard preset | Wire compat (`DrydockCompat` / `serverCompatLevel`) |
|---|---|---|---|---|
| v0.9.24 (latest release) / `main` | 1.6.x; 1.5.x with `DD_EXPERIMENTAL_PORTWING=true` | `v1.6.0-rc.11+` | `portwing.yaml`, `portwing-with-exec.yaml`, `portwing-with-compose.yaml` | `1.4.0` |
| v0.9.25 (latest release) / `main` | 1.6.x; 1.5.x with `DD_EXPERIMENTAL_PORTWING=true` | `v1.6.0-rc.11+` | `portwing.yaml`, `portwing-with-exec.yaml`, `portwing-with-compose.yaml` | `1.4.0` |

## Wire compatibility versus feature compatibility

Expand Down Expand Up @@ -92,6 +92,6 @@ both together when either changes.
- Portwing: [`STABILITY.md`](STABILITY.md) — semantic-versioning, deprecation,
and change-control guarantees for the HTTP, environment, MCP, and wire
surfaces.
- Portwing: [`docs/drydock-integration.md`](docs/drydock-integration.md) / [`docs/content/docs/drydock-integration.mdx`](docs/content/docs/drydock-integration.mdx) — full wire-protocol and REST/SSE contract detail.
- Portwing: [`docs/drydock-integration.md`](docs/drydock-integration.md) / [`docs/content/docs/current/drydock-integration.mdx`](docs/content/docs/current/drydock-integration.mdx) — full wire-protocol and REST/SSE contract detail.
- Drydock: see its README's ecosystem section.
- sockguard: see its README's ecosystem section.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# Dockerfile.release; this file is the standalone equivalent.)

# Stage 1: Build the binary from source.
FROM golang:1.27.1-alpine@sha256:cf6fca6641884b8433441b2b0652976f975e1d0fdd26d177eaaf8596087f3125 AS builder
FROM golang:1.27.2-alpine@sha256:85dc1069ac644ea3c527b177303a406eb3358192816cd7f9e5848eb658851673 AS builder
RUN apk add --no-cache git
WORKDIR /src
COPY go.mod go.sum ./
Expand Down
4 changes: 2 additions & 2 deletions Dockerfile.armv7
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# (Release images are built by GoReleaser via the unified Dockerfile.release.)

# Stage 1: Build the binary from source.
FROM --platform=$BUILDPLATFORM golang:1.27.1-alpine@sha256:cf6fca6641884b8433441b2b0652976f975e1d0fdd26d177eaaf8596087f3125 AS builder
FROM --platform=$BUILDPLATFORM golang:1.27.2-alpine@sha256:85dc1069ac644ea3c527b177303a406eb3358192816cd7f9e5848eb658851673 AS builder
RUN apk add --no-cache git
WORKDIR /src
COPY go.mod go.sum ./
Expand All @@ -13,7 +13,7 @@ COPY . .
RUN CGO_ENABLED=0 GOARCH=arm GOARM=7 go build -trimpath -ldflags="-s -w" -o /portwing ./cmd/portwing

# Compose v5.5.1 with the containerd security patch, isolated from Portwing modules.
FROM --platform=$BUILDPLATFORM golang:1.27.1-alpine@sha256:cf6fca6641884b8433441b2b0652976f975e1d0fdd26d177eaaf8596087f3125 AS compose-builder
FROM --platform=$BUILDPLATFORM golang:1.27.2-alpine@sha256:85dc1069ac644ea3c527b177303a406eb3358192816cd7f9e5848eb658851673 AS compose-builder
ADD --checksum=sha256:c72877db37172d8ee55f565e4fed20067af89015e986b190768fd4ee621025f2 https://github.com/docker/compose/archive/5f94fb0aa42a2cd1248c6e6c7fafb87546b9c8de.tar.gz /tmp/compose.tar.gz
WORKDIR /compose
RUN tar -xzf /tmp/compose.tar.gz --strip-components=1 \
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile.dev
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM golang:1.27.1-alpine@sha256:cf6fca6641884b8433441b2b0652976f975e1d0fdd26d177eaaf8596087f3125
FROM golang:1.27.2-alpine@sha256:85dc1069ac644ea3c527b177303a406eb3358192816cd7f9e5848eb658851673
# Mirror the release images: dedicated portwing user (UID 65532). Reaching
# the host Docker socket requires --group-add with the socket's GID.
RUN apk add --no-cache git docker-cli docker-compose \
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile.release
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
# linux/arm/v7/portwing).

# Compose v5.5.1 with the containerd security patch, isolated from Portwing modules.
FROM --platform=$BUILDPLATFORM golang:1.27.1-alpine@sha256:cf6fca6641884b8433441b2b0652976f975e1d0fdd26d177eaaf8596087f3125 AS compose-builder
FROM --platform=$BUILDPLATFORM golang:1.27.2-alpine@sha256:85dc1069ac644ea3c527b177303a406eb3358192816cd7f9e5848eb658851673 AS compose-builder
ADD --checksum=sha256:c72877db37172d8ee55f565e4fed20067af89015e986b190768fd4ee621025f2 https://github.com/docker/compose/archive/5f94fb0aa42a2cd1248c6e6c7fafb87546b9c8de.tar.gz /tmp/compose.tar.gz
WORKDIR /compose
RUN tar -xzf /tmp/compose.tar.gz --strip-components=1 \
Expand Down
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@
<hr>

> [!WARNING]
> **Pre-1.0 software — APIs may still change.** Portwing is pre-`v1.0.0` (currently `v0.9.24`). The compatibility guarantees that already apply are published in [STABILITY.md](STABILITY.md); other surfaces may still change between minor releases. Pin to an exact version and review the [CHANGELOG](CHANGELOG.md) before upgrading.
> **Pre-1.0 software — APIs may still change.** Portwing is pre-`v1.0.0` (currently `v0.9.25`). The compatibility guarantees that already apply are published in [STABILITY.md](STABILITY.md); other surfaces may still change between minor releases. Pin to an exact version and review the [CHANGELOG](CHANGELOG.md) before upgrading.

<!-- separate alerts: a blank-line-only gap between blockquotes trips markdownlint MD028 -->

> [!NOTE]
> **v0.9.24 is the current release.** Exec sessions on the edge now end cleanly when one of their goroutines panics: a failed start gets an audit record, the session is closed, and the controller is told once. The comparison data and README were also corrected. Binary request uploads require a controller that implements the negotiated `edge-request-body-stream` capability. Wire compatibility remains `portwing/1.0` and `DrydockCompat` 1.4.0; full watcher/update feature compatibility requires Drydock `v1.6.0-rc.11+`. See [CHANGELOG.md](CHANGELOG.md) for the full itemized history.
> **v0.9.25 is the current release.** It is built with Go 1.27.2, which fixes 13 standard-library advisories; ten of them were reachable from Portwing's code in the previous release, so upgrading is recommended. Binary request uploads require a controller that implements the negotiated `edge-request-body-stream` capability. Wire compatibility remains `portwing/1.0` and `DrydockCompat` 1.4.0; full watcher/update feature compatibility requires Drydock `v1.6.0-rc.11+`. See [CHANGELOG.md](CHANGELOG.md) for the full itemized history.

<h2 align="center">Contents</h2>

Expand Down Expand Up @@ -160,7 +160,7 @@ services:
- SOCKGUARD_LISTEN_SOCKET=/var/run/sockguard/sockguard.sock

portwing:
image: ghcr.io/codeswhat/portwing:0.9.24
image: ghcr.io/codeswhat/portwing:0.9.25
restart: unless-stopped
depends_on:
- sockguard
Expand Down Expand Up @@ -215,7 +215,7 @@ sudo chown 65532:65532 portwing_ed25519.pem && sudo chmod 0400 portwing_ed25519.
```yaml
services:
portwing:
image: ghcr.io/codeswhat/portwing:0.9.24
image: ghcr.io/codeswhat/portwing:0.9.25
restart: unless-stopped
read_only: true
cap_drop:
Expand Down Expand Up @@ -255,10 +255,10 @@ Stable releases also ship a Homebrew cask plus signed/checksummed `deb` and
brew install --cask codeswhat/tap/portwing

# Debian/Ubuntu (after downloading the matching release asset)
sudo apt install ./portwing_0.9.24_linux_amd64.deb
sudo apt install ./portwing_0.9.25_linux_amd64.deb

# Fedora/RHEL (after downloading the matching release asset)
sudo rpm --install ./portwing_0.9.24_linux_amd64.rpm
sudo rpm --install ./portwing_0.9.25_linux_amd64.rpm
```

Packages install the command and, on Linux, a hardened `portwing.service`; they
Expand All @@ -281,7 +281,7 @@ docker run -d \
-v /var/run/docker.sock:/var/run/docker.sock \
-p 127.0.0.1:3000:3000 \
-e TOKEN=$(openssl rand -hex 24) \
ghcr.io/codeswhat/portwing:0.9.24
ghcr.io/codeswhat/portwing:0.9.25
```

Portwing now fails closed: Standard mode refuses to start without `TOKEN`,
Expand Down
34 changes: 32 additions & 2 deletions RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@

6. **No source version bump needed** — the binary's version is injected at build time via GoReleaser ldflags (`-X github.com/codeswhat/portwing/internal/protocol.AgentVersion={{.Version}}`). `AgentVersion` in `internal/protocol/version.go` must stay a `var`: `-X` silently does nothing to a `const`.

7. **Lefthook pre-push** — runs automatically on `git push`. Sequence: clean-tree → release contract (`scripts/package-release-config-test.sh`) → goreleaser snapshot → lint → Qlty → test (-race) → govulncheck → fuzz smoke → actionlint → zizmor → web (`npm run check:web`). The push is blocked if any step fails.
7. **Lefthook pre-push** — runs automatically on `git push`. Sequence: clean-tree → release contract (`scripts/package-release-config-test.sh`) and docs archive contract (`scripts/docs-archive-config-test.sh`) → goreleaser snapshot → lint → Qlty → test (-race) → govulncheck → fuzz smoke → actionlint → zizmor → web (`npm run check:web`). The push is blocked if any step fails.

---

Expand Down Expand Up @@ -87,6 +87,36 @@ sit at "Expected" forever. So the order is:
Read the effective ruleset back afterward rather than trusting the PATCH's
200. The script prints it.

## Cutting a docs archive

Do this in the prep PR for a new minor or major line (v0.9 to v1.0, say),
never for a patch. The old line's docs get frozen as `docs/content/docs/vX.Y/`
and `current/` moves on to the new line. Layout and rules:
`docs/content/README.md`.

1. Make sure the old line is finished. The archive comes from its **last** tag,
so the final patch has to be tagged and fetched (`git fetch --tags`).
2. Retitle `docs/content/docs/current/meta.json` to the new line. The cut
refuses to archive the line `current/` still names.
3. Cut it from that tag:

```sh
npm run docs:archive -- vX.Y.Z # the old line's last tag
```

This reads `docs/content/docs/current` from the tag with git (never your
working tree), writes `docs/content/docs/vX.Y/`, adds the entry to
`docs/content/archive-provenance.json`, and lists `vX.Y` in
`docs/content/docs/meta.json` after `current`.
4. Run `bash scripts/docs-archive-config-test.sh` after committing the new
directory and the provenance entry together. The same contract runs in
`Release Contract`, `release-cut`, and pre-push; once the changelog's
previous release is on an older line than `current/`, it fails until that
line's archive exists.

Never edit a file under an archived `vX.Y/`. A correction goes in `current/`;
an archive that no longer matches its tag fails the contract.

## Cutting the tag

**Preferred path: use the `release-cut` workflow.**
Expand Down Expand Up @@ -199,7 +229,7 @@ the Git-backed deployment or update its GitHub status.
**ARMv7 runtime.** Wolfi publishes no armv7 repository, so this image retains
Alpine 3.24 release metadata and CA certificates alongside static binaries.
Docker CLI 29.8.0 is an official download pinned by SHA256. Compose is built
from checksum-pinned upstream 5.5.1 source with containerd 2.3.6 and Go 1.27.1,
from checksum-pinned upstream 5.5.1 source with containerd 2.3.6 and Go 1.27.2,
identifies itself as `v5.5.1-portwing.2`, and runs directly or as a CLI plugin.
Both ARM recipes omit BusyBox; image health checks use `portwing healthcheck`
for HTTP/TLS probes, with HTTPS pinned to the configured `TLS_CERT` leaf.
Expand Down
17 changes: 9 additions & 8 deletions ROADMAP.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Portwing Roadmap

> Portwing is pre-`v1.0.0` software (currently `v0.9.24`). This roadmap describes
> Portwing is pre-`v1.0.0` software (currently `v0.9.25`). This roadmap describes
> direction and priorities — not commitments. Items and ordering may change
> between releases. For the authoritative record of what has shipped, see the
> [CHANGELOG](CHANGELOG.md).
Expand Down Expand Up @@ -88,13 +88,14 @@ calendar date:
- **Completed for v0.9 — Edge audit export hardening.** Standard and Edge
modes share one exporter implementation; public docs and Kubernetes examples
explicitly isolate Edge Mode's unauthenticated operations listener.
- **Docs stay current-only until v1.0.** `docs/content/docs/` is a single flat
tree tracking the latest release, with no per-version archive. That's
deliberate through `v0.9.x`. The v1.0.0 release-cut PR is the trigger to
decide whether the semver guarantees in [STABILITY.md](STABILITY.md) need a
versioned docs archive — e.g. drydock's `content/docs/{current,v1.x}` split
plus `archive-provenance.json` and its release-docs-archive tests — before
v1.0 ships.
- **Versioned docs archive at v1.0.** `docs/content/docs/current/` holds the
tree tracking the latest release, served at the unversioned `/docs/...`
URLs. Through `v0.9.x` it is the only tree. At `v1.0.0` the `v0.9` docs are
archived from the final `v0.9.x` tag into a sibling `v0.9/` directory,
served at `/docs/v0.9/...` and verified against that tag's tree, so the
semver guarantees in [STABILITY.md](STABILITY.md) keep the docs they were
written against. Each later minor line archives the one before it the same
way.
- **SLSA Build L2 is the ceiling for now.** Provenance attestation (see
[RELEASING.md](RELEASING.md)) stays at Build L2 because GoReleaser, cosign,
and attest all run inline inside `release.yml`. Build L3 needs an isolated,
Expand Down
2 changes: 1 addition & 1 deletion SECURITY-ASSURANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ Evidence: [`.github/workflows/ci-verify.yml`](.github/workflows/ci-verify.yml),
[`.github/workflows/quality-fuzz-cflite-prune.yml`](.github/workflows/quality-fuzz-cflite-prune.yml),
[`.github/workflows/release.yml`](.github/workflows/release.yml), the public
[coverage report](https://qlty.sh/gh/CodesWhat/projects/portwing), and
[`docs/content/docs/verification.mdx`](docs/content/docs/verification.mdx).
[`docs/content/docs/current/verification.mdx`](docs/content/docs/current/verification.mdx).

The scheduled lanes also record what they measured, not only whether they
passed. The weekly RSS-growth soak and the monthly mutation matrix each append
Expand Down
2 changes: 1 addition & 1 deletion SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -632,7 +632,7 @@ Chainguard Wolfi OS packages assembled into a `FROM scratch` image (Alpine on ar
Packages:

- **Wolfi (amd64/arm64):** `ca-certificates-bundle`, `busybox`, `docker-cli`, `docker-compose`, `wget`
- **Alpine (armv7):** `ca-certificates-bundle` and `alpine-release`, with `alpine-keys` as a dependency. Docker CLI 29.8.0 is an official static ARMv7 download pinned by SHA256. Compose is built from checksum-pinned upstream 5.5.1 source with containerd 2.3.6 and Go 1.27.1, identifies itself as `v5.5.1-portwing.2`, and is available directly and as a Docker CLI plugin. Both ARM image recipes omit BusyBox and use `portwing healthcheck` for HTTP/TLS probes.
- **Alpine (armv7):** `ca-certificates-bundle` and `alpine-release`, with `alpine-keys` as a dependency. Docker CLI 29.8.0 is an official static ARMv7 download pinned by SHA256. Compose is built from checksum-pinned upstream 5.5.1 source with containerd 2.3.6 and Go 1.27.2, identifies itself as `v5.5.1-portwing.2`, and is available directly and as a Docker CLI plugin. Both ARM image recipes omit BusyBox and use `portwing healthcheck` for HTTP/TLS probes.

## 15. Migration Strategy

Expand Down
Loading
Loading