Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
752a51d
fix(security): keep accounts off the engine's container names (GW-2)
SauravYadav12 Nov 10, 2026
62cf649
fix(gateway): cap what a client can make us allocate before auth (PER…
SauravYadav12 Nov 10, 2026
2e789c8
fix(security): check the release signature in fox setup too
SauravYadav12 Nov 10, 2026
cc6e724
feat(install): find the distro image in its own checksum listing
SauravYadav12 Nov 10, 2026
e8e152e
fix(security): never sign checksums read back from a release
SauravYadav12 Nov 10, 2026
86f8b5c
fix(security): a pipeline's claim ends with its branch (API-2)
SauravYadav12 Nov 10, 2026
c993325
fix(security): check pipeline targets where the name comes in (GW-2)
SauravYadav12 Nov 10, 2026
a8903c2
perf(branch): one short inspect per branch, remembered for two seconds
SauravYadav12 Nov 10, 2026
5753c07
perf(gateway): recover from a stale backend address
SauravYadav12 Nov 10, 2026
303867d
fix(promote): refuse a request whose statements leave its transaction
SauravYadav12 Nov 10, 2026
4ca6d3c
fix(security): keep the Blackbox's chain head in a row taken under th…
SauravYadav12 Nov 10, 2026
f452613
fix(perf): verify the security log's checkpoint from its last anchor
SauravYadav12 Nov 10, 2026
f76b2b7
fix(perf): throttle and coalesce the Gateway's refusals per address
SauravYadav12 Nov 10, 2026
eaaae5e
fix(promote): apply a request as db_client over pgx, in one transaction
SauravYadav12 Nov 10, 2026
94ff1e3
perf(console): serve status, branches and replication from one snapshot
SauravYadav12 Nov 10, 2026
68e4f1d
perf(web): poll after the last poll, every 10s, only when visible
SauravYadav12 Nov 10, 2026
8eba7bc
fix(security): put timeouts on the connection that records a refusal
SauravYadav12 Nov 10, 2026
d2eaa03
fix(perf): read the audit log and its evidence a day at a time
SauravYadav12 Nov 10, 2026
a30afb0
style: gofmt the evidence test
SauravYadav12 Nov 10, 2026
b38f2fb
fix(perf): advance a quiet realtime slot from the server's position
SauravYadav12 Nov 10, 2026
6a6cf4a
fix(security): take EXECUTE on the Blackbox's helpers away from clients
SauravYadav12 Nov 10, 2026
a7f45c2
fix(security): refuse a replaced Blackbox trigger
SauravYadav12 Nov 10, 2026
b0ef926
Merge worktree-agent-ab5ab26bf7fd3db24
SauravYadav12 Nov 10, 2026
c75c313
Merge worktree-agent-a74ba681a6c1ca3a4
SauravYadav12 Nov 10, 2026
7d9e8a5
Merge worktree-agent-a6ff3de8d8fb27d36
SauravYadav12 Nov 10, 2026
efbca8b
Merge worktree-agent-a46f55431ad1199d8
SauravYadav12 Nov 10, 2026
7ed1149
Merge worktree-agent-a33af7ec723e37863
SauravYadav12 Nov 10, 2026
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
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ jobs:
# occasional impossible value rather than a failing test.
- name: Test (race detector)
run: go test -race ./...
# The release workflows share one SHA256SUMS; this checks the script that
# merges into it never drops another job's entries.
- name: Release checksum merging
# Each release workflow signs only its own build's checksums; this checks
# the script never signs anything read back from a release.
- name: Release checksum signing
run: bash scripts/test_release_checksums.sh
# The release job's check that every file `fox update` needs was published.
- name: Release assets for fox update
Expand Down
11 changes: 5 additions & 6 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -285,11 +285,10 @@ jobs:
# only integrity guarantee is TLS, and these assets are executed as root
# inside the user's distro.
#
# SHA256SUMS is shared with wsl-distro.yml, which runs on the same tag and
# can finish before or after this job. The script merges this job's entries
# into the published file by exact name, keeps the other job's, and checks
# again after uploading — so neither job can drop the other's checksums
# (v0.8.1 lost the distro image's entry that way).
# SHA256SUMS belongs to this job alone and lists only what it built here;
# wsl-distro.yml, on its own schedule, signs SHA256SUMS.distro for the
# distro image. Merging them meant signing lines read back from the
# release, so anyone able to upload an asset could get a hash signed.
- name: Publish checksums
env:
GH_TOKEN: ${{ github.token }}
Expand All @@ -298,7 +297,7 @@ jobs:
FOX_RELEASE_SIGNING_KEY: ${{ secrets.FOX_RELEASE_SIGNING_KEY }}
run: |
cd dist
bash ../scripts/release-checksums.sh "$TAG" fox-* foxbyte-docker-context.tar.gz foxbyte-sbom.spdx.json
bash ../scripts/release-checksums.sh "$TAG" SHA256SUMS fox-* foxbyte-docker-context.tar.gz foxbyte-sbom.spdx.json

- name: Attach binaries
env:
Expand Down
19 changes: 9 additions & 10 deletions .github/workflows/wsl-distro.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,22 +73,21 @@ jobs:
gh release view "$TAG" >/dev/null 2>&1 ||
gh release create "$TAG" --prerelease --generate-notes --title "$TAG"

# SHA256SUMS is signed with the release key (audit v2 G22): fox update and
# install.sh refuse a release whose signature does not check. A missing
# secret fails the release here, rather than publishing one nobody can
# install. (cmd/releasesign also refuses a key fox would not accept.)
# This job's checksums are signed with the release key (audit v2 G22). A
# missing secret fails the job here, rather than publishing an image with
# no signature. (cmd/releasesign also refuses a key fox would not accept.)
- name: Build the release signer
env:
FOX_RELEASE_SIGNING_KEY: ${{ secrets.FOX_RELEASE_SIGNING_KEY }}
run: |
test -n "$FOX_RELEASE_SIGNING_KEY" || { echo "::error::the FOX_RELEASE_SIGNING_KEY secret is not set (see cmd/releasesign)"; exit 1; }
go build -o "$RUNNER_TEMP/releasesign" ./cmd/releasesign

# Merged, not replaced: release.yml publishes sums for the binaries into
# the same SHA256SUMS. Every asset the installer or setup downloads must be
# listed, or verification silently skips it. The shared script matches
# entries by exact name and re-checks after uploading, so whichever job
# finishes last keeps the other's entries.
# A signed file of this job's own, because this workflow can run at any
# time for an existing tag and release.yml owns SHA256SUMS. Only the hash
# of the image built above is signed; nothing is read back from the
# release. install.ps1 looks the image up here, then in SHA256SUMS for
# releases made before this file existed.
- name: Publish checksums
env:
GH_TOKEN: ${{ github.token }}
Expand All @@ -97,7 +96,7 @@ jobs:
FOX_RELEASE_SIGNING_KEY: ${{ secrets.FOX_RELEASE_SIGNING_KEY }}
run: |
cd dist
bash ../scripts/release-checksums.sh "$TAG" foxbyte-distro.tar.zst
bash ../scripts/release-checksums.sh "$TAG" SHA256SUMS.distro foxbyte-distro.tar.zst

- name: Attach the distro image
env:
Expand Down
15 changes: 7 additions & 8 deletions .github/workflows/wsl-zfs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,11 @@ jobs:
set -eux
gh release upload "$TAG" dist/foxbyte-zfs-modules-*.tar.gz --clobber

# Keep SHA256SUMS complete, or setup skips verifying this bundle.
# A checksum beside each bundle, not in SHA256SUMS: that file is signed
# and belongs to release.yml, and rewriting it here without the key
# broke its signature, so fox update then refused the whole release.
cd dist
gh release download "$TAG" -p SHA256SUMS -O existing.txt 2>/dev/null || : > existing.txt
sha256sum foxbyte-zfs-modules-*.tar.gz > mine.txt
awk '{print $2}' mine.txt > names.txt
grep -vFf names.txt existing.txt > SHA256SUMS || : > SHA256SUMS
cat mine.txt >> SHA256SUMS
sort -k2 -o SHA256SUMS SHA256SUMS
gh release upload "$TAG" SHA256SUMS --clobber
for f in foxbyte-zfs-modules-*.tar.gz; do
sha256sum "$f" > "$f.sha256"
gh release upload "$TAG" "$f.sha256" --clobber
done
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,9 @@ Every release's `SHA256SUMS` is signed with the FoxByte release key
it carries — which comes from this repository, not from the release — wherever
`openssl` can do Ed25519 (OpenSSL 3; macOS's LibreSSL cannot, and the installer
says so; `FOX_REQUIRE_SIGNATURE=1` refuses instead). `fox update` checks it on
every update and never installs a release that fails. Each file also has a
every update, `fox setup` on the engine it downloads, and neither installs a
release that fails. The Windows distro image, built by its own workflow, has
its own signed `SHA256SUMS.distro`. Each file also has a
GitHub build attestation and the release an SBOM (`foxbyte-sbom.spdx.json`):

```bash
Expand Down
19 changes: 19 additions & 0 deletions cmd/fox/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -696,6 +696,12 @@ func pipelineCmd(args []string) {
if target == "" {
target = "pl-" + strings.TrimSuffix(filepath.Base(specPath), filepath.Ext(specPath))
}
must(branch.CheckNewName(target))
// A run deletes and re-creates its target. A branch an account owns is not
// this command's to replace; deleting it first is a decision made out loud.
if branch.Exists(target) && accountOwnsBranch(target) {
must(fmt.Errorf("the branch %q belongs to an account: delete it first, or choose another name with --as", target))
}
res, err := branch.RunPipeline(&branch.Progress{Log: os.Stdout}, spec, target)
must(err)
if res.Failed {
Expand Down Expand Up @@ -784,6 +790,7 @@ func branchCmd(args []string) {
fmt.Println("usage: fox branch create <name> [--from <branch>] [--masked]")
os.Exit(2)
}
must(branch.CheckNewName(name))
started := time.Now()
if hasFlag(args[1:], "--masked") {
maskedBranchCreate(name, optValue(args[1:], "--from"), started)
Expand Down Expand Up @@ -1482,6 +1489,18 @@ func forgetOwner(name string) {
}
}

// accountOwnsBranch reports whether an account is recorded as a branch's owner.
// Best-effort, like forgetOwner: with no store there are no accounts.
func accountOwnsBranch(name string) bool {
s, err := auth.OpenFromEnv()
if err != nil {
return false
}
defer s.Close()
_, owned := s.BranchOwner(name)
return owned
}

// userCmd is `fox user`: accounts, for whoever runs the engine (audit v2 G16).
func userCmd(args []string) {
usage := func() {
Expand Down
20 changes: 20 additions & 0 deletions deploy/install.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ Describe 'FoxByte release verification' {
BeforeEach {
$env:FOX_NO_VERIFY = $null
Set-Variable -Name FoxSums -Scope Script -Value $script:listing
Set-Variable -Name FoxDistroSums -Scope Script -Value ''
}
AfterAll { $env:FOX_NO_VERIFY = $null }

Expand All @@ -97,6 +98,25 @@ Describe 'FoxByte release verification' {
Get-FoxChecksum 'foxbyte-distro.tar.gz' | Should -Be ('b' * 64)
}

It 'reads the distro image from its own listing' {
Set-Variable -Name FoxDistroSums -Scope Script -Value (('c' * 64) + " foxbyte-distro.tar.zst`n")
Get-FoxChecksum 'foxbyte-distro.tar.zst' | Should -Be ('c' * 64)
}

It 'prefers the distro listing over an older entry in SHA256SUMS' {
Set-Variable -Name FoxDistroSums -Scope Script -Value (('c' * 64) + " foxbyte-distro.tar.gz`n")
Get-FoxChecksum 'foxbyte-distro.tar.gz' | Should -Be ('c' * 64)
}

It 'never lets the distro listing answer for another file' {
Set-Variable -Name FoxDistroSums -Scope Script -Value (('c' * 64) + " fox-windows-amd64.exe`n")
Get-FoxChecksum 'fox-windows-amd64.exe' | Should -Be ('a' * 64)
}

It 'refuses a distro image neither listing names' {
{ Get-FoxChecksum 'foxbyte-distro.tar.zst' } | Should -Throw '*not listed in SHA256SUMS*'
}

It 'refuses an asset the listing does not name' {
{ Get-FoxChecksum 'fox-linux-amd64' } | Should -Throw '*not listed in SHA256SUMS*'
}
Expand Down
42 changes: 37 additions & 5 deletions deploy/install.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,12 @@ function Assert-UpstreamChecksum([string]$Path, [string]$SumsUrl, [string]$Name)
# download), not authorship -- signatures would be needed for that. The files
# below are executed as root inside the distro, so an unverifiable one is not
# installed. FOX_NO_VERIFY=1 skips the check deliberately.
#
# The distro image is built by its own workflow, on its own schedule, so its
# checksum is in SHA256SUMS.distro. Releases made before that file existed list
# the image in SHA256SUMS, which is why both are looked at for it, and only for it.
$script:FoxSums = $null
$script:FoxDistroSums = $null

function Get-FoxSums {
if ($env:FOX_NO_VERIFY -eq '1') { return $null }
Expand All @@ -295,16 +300,43 @@ function Get-FoxSums {
return $script:FoxSums
}

# Get-FoxDistroSums returns the distro image's own listing, or '' for a release
# without one (the distro workflow has not finished, or the release predates it).
function Get-FoxDistroSums {
if ($null -ne $script:FoxDistroSums) { return $script:FoxDistroSums }
try {
$timeout = $script:WebTimeout
$body = (Invoke-WebRequest -UseBasicParsing -Uri (Get-FoxAsset 'SHA256SUMS.distro') @timeout).Content
$script:FoxDistroSums = if ($body -is [byte[]]) { [System.Text.Encoding]::UTF8.GetString($body) } else { [string]$body }
} catch {
$script:FoxDistroSums = ''
}
return $script:FoxDistroSums
}

# Find-FoxChecksum returns the SHA256 a listing gives for $Name, or $null.
function Find-FoxChecksum([string]$Listing, [string]$Name) {
foreach ($line in ($Listing -split "`n")) {
if ($line -match '^([0-9a-fA-F]{64})\s+\*?(.+?)\s*$' -and $Matches[2] -eq $Name) {
return $Matches[1].ToLower()
}
}
return $null
}

# Get-FoxChecksum returns the expected SHA256 for a release asset, or $null when
# verification is switched off.
# verification is switched off. The distro's own listing goes first, so a distro
# rebuilt for an older release is checked against its current hash; it can never
# answer for any other file.
function Get-FoxChecksum([string]$Name) {
$sums = Get-FoxSums
if ($null -eq $sums) { return $null }
foreach ($line in ($sums -split "`n")) {
if ($line -match '^([0-9a-fA-F]{64})\s+\*?(.+?)\s*$' -and $Matches[2] -eq $Name) {
return $Matches[1].ToLower()
}
if ($Name -like 'foxbyte-distro.*') {
$want = Find-FoxChecksum (Get-FoxDistroSums) $Name
if ($want) { return $want }
}
$want = Find-FoxChecksum $sums $Name
if ($want) { return $want }
throw "$Name is not listed in SHA256SUMS for $Version -- nothing was installed.`nSet FOX_NO_VERIFY=1 to install without checking (not recommended)."
}

Expand Down
2 changes: 1 addition & 1 deletion docs/windows-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ costs one ~2 MB bundle rather than another 740 MB image.

**To cover a new kernel on an existing release**, run the **wsl-zfs** workflow
manually with the kernel tag and the release tag. It builds just that bundle
(~1 hour), attaches it, and updates `SHA256SUMS` — no full rebuild. Add the
(~1 hour) and attaches it with a `.sha256` file beside it — no full rebuild. Add the
kernel to the matrix as well, so future releases keep covering it.

## Building the ZFS bundle (maintainers)
Expand Down
20 changes: 4 additions & 16 deletions enterprise/auditevidence/evidence.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ type inputs struct {
keyID string
forward *branch.ForwardStatus
findings []branch.AuditFinding
records []branch.AuditRecord
activity branch.EvidenceActivity
records []branch.AuditRecord // only when they were asked for
anchorSet []ledger.Anchor
publicKey string
}
Expand Down Expand Up @@ -85,7 +86,7 @@ func gather(o branch.EvidenceOptions) (inputs, error) {
}
}
in.findings = branch.AuditFindings(marks)
if in.records, err = branch.AuditRecordsBetween(o.Since, o.Until); err != nil {
if in.activity, in.records, err = branch.AuditActivityBetween(o.Since, o.Until, o.Records); err != nil {
return in, err
}
if o.Records {
Expand All @@ -108,7 +109,7 @@ func assemble(o branch.EvidenceOptions, in inputs) branch.AuditEvidence {
},
Integrity: branch.EvidenceIntegrity{ChainReport: in.chain, AnchorsKept: in.anchors, KeyID: in.keyID},
Delivery: branch.EvidenceDelivery{Forward: in.forward, Findings: in.findings},
Activity: branch.EvidenceActivity{ByKind: map[string]int{}, ByBranch: map[string]int{}, Gaps: []branch.AuditRecord{}},
Activity: in.activity,
Changes: []auth.SecurityEvent{},
Limits: branch.EvidenceLimits,
}
Expand All @@ -121,19 +122,6 @@ func assemble(o branch.EvidenceOptions, in inputs) branch.AuditEvidence {
if ev.Delivery.Findings == nil {
ev.Delivery.Findings = []branch.AuditFinding{}
}
for _, r := range in.records {
ev.Activity.Records++
ev.Activity.ByKind[r.Kind]++
ev.Activity.ByBranch[r.Branch]++
switch r.Kind {
case "outcome":
ev.Activity.Errors++
case "heartbeat":
ev.Activity.Heartbeats++
case "gap":
ev.Activity.Gaps = append(ev.Activity.Gaps, r)
}
}
for _, e := range o.Events {
if !strings.HasPrefix(e.Kind, "audit-log.") {
continue
Expand Down
4 changes: 4 additions & 0 deletions enterprise/auditevidence/evidence_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ func TestAssemble(t *testing.T) {
{Seq: 1, Kind: "statement", Branch: "main"}, {Seq: 2, Kind: "outcome", Branch: "main"},
{Seq: 3, Kind: "heartbeat", Branch: "dev"}, {Seq: 4, Kind: "gap", Branch: "dev", Missing: 2},
}}
in.activity = branch.NewAuditActivity()
for _, r := range in.records {
branch.TallyAuditActivity(&in.activity, r)
}
o := branch.EvidenceOptions{Since: since, Until: until, By: "ada@example.com", Events: []auth.SecurityEvent{
{Kind: auth.EvAuditLogOn, At: "2026-11-02T10:00:00Z", Actor: "ada@example.com", IP: "10.0.0.9"},
{Kind: auth.EvAuditMarked, At: "2026-10-30T10:00:00Z", Subject: "main", Detail: "auditing writes of public.t"}, // before the period
Expand Down
Loading
Loading