Author SHA1 Message Date
gamertan a39e8f893c requestlog: publish collector-readable evidence boundary
verify / verify (push) Successful in 3m28s
Publish the reviewed Gamertan Web Foundations v0.1.0-preview.6 snapshot with a narrow mode-0640 collector boundary, private mode-0600 default, explicit setgid ownership guidance, and native macOS-safe release verification.

Exported from reviewed private source 120d660fa432761f85316ca3dde990e2dd142f19 after trusted Gitea CI run 681 and the complete native Mac verification suite.

Material implementation assistance provided by OpenAI Codex; reviewed and verified through the maintainer workflow.

Signed-off-by: Cole Speelman <crspeelman@gmail.com>
2026-08-24 17:06:47 -04:00
gamertan 5563306368 Document Web Foundations Preview 5
verify / verify (push) Successful in 3m55s
2026-08-21 17:45:19 -04:00
13 changed files with 117 additions and 22 deletions
+1 -1
View File
@@ -2,7 +2,7 @@
name: verify name: verify
on: on:
push: push:
branches: [main, 'codex/**'] branches: [main, 'codex/**', 'gamertan/**']
workflow_dispatch: workflow_dispatch:
permissions: permissions:
contents: read contents: read
+18
View File
@@ -4,6 +4,20 @@
## Unreleased ## Unreleased
## v0.1.0-preview.6 — 2026-08-24
- Add an explicit mode-`0640` JSONL option for applications that authorize one
narrowly scoped collector group, while keeping private mode `0600` as the
default and rejecting permissive modes.
- Document the setgid-directory ownership boundary for Observatory-style
collection without granting the collector broader application access.
- Make vendored dependency and public-snapshot verification portable across
the maintained Linux gate and native macOS development environments.
- Keep Previews 15 immutable; applications select Preview 6 explicitly when
adopting collector-readable request evidence.
## v0.1.0-preview.5 — 2026-08-21
- Add storage-neutral passkey registration, discoverable login, and - Add storage-neutral passkey registration, discoverable login, and
operation-bound fresh assertions without adding self-registration, password operation-bound fresh assertions without adding self-registration, password
fallback, TOTP, email recovery, or application-owned routes. fallback, TOTP, email recovery, or application-owned routes.
@@ -17,6 +31,10 @@
- Pin WebAuthn protocol verification to `github.com/go-webauthn/webauthn` - Pin WebAuthn protocol verification to `github.com/go-webauthn/webauthn`
`v0.17.1` and record its source identity, module checksums, licence, and `v0.17.1` and record its source identity, module checksums, licence, and
transitive security boundary. transitive security boundary.
- Add self-service passkey enrollment and removal primitives with fresh
assertion, session revocation, and last-credential protection.
- Keep Previews 14 immutable; applications select Preview 5 explicitly when
adopting the passkey boundary.
## v0.1.0-preview.4 — 2026-08-18 ## v0.1.0-preview.4 — 2026-08-18
+3 -3
View File
@@ -2,7 +2,7 @@
# Gamertan Web Foundations # Gamertan Web Foundations
> Status: `v0.1.0-preview.4` public preview. APIs may change before a stable > Status: `v0.1.0-preview.6` public preview. APIs may change before a stable
> release; Linux is the maintained release platform. > release; Linux is the maintained release platform.
Small, composable Go packages for the unglamorous boundaries of a careful web Small, composable Go packages for the unglamorous boundaries of a careful web
@@ -24,14 +24,14 @@ Pin the preview in an application module, then import only the packages that
application needs: application needs:
```bash ```bash
go get gamertan.com/web@v0.1.0-preview.4 go get gamertan.com/web@v0.1.0-preview.6
go mod verify go mod verify
``` ```
An application may also name the first package it intends to adopt: An application may also name the first package it intends to adopt:
```bash ```bash
go get gamertan.com/web/requestmeta@v0.1.0-preview.4 go get gamertan.com/web/requestmeta@v0.1.0-preview.6
``` ```
The version belongs to the `gamertan.com/web` module. Go compiles and links The version belongs to the `gamertan.com/web` module. Go compiles and links
+9 -1
View File
@@ -25,7 +25,7 @@ The packages are ordinary Go imports. Pin the current preview and verify its
module checksum: module checksum:
```bash ```bash
go get gamertan.com/web/requestmeta@v0.1.0-preview.4 go get gamertan.com/web/requestmeta@v0.1.0-preview.6
go mod verify go mod verify
``` ```
@@ -45,6 +45,14 @@ handler = resolver.Middleware(handler)
The complete, copyable composition is in [`starters/basic`](../starters/basic). The complete, copyable composition is in [`starters/basic`](../starters/basic).
It binds to loopback, shuts down gracefully, and keeps request logging optional. It binds to loopback, shuts down gracefully, and keeps request logging optional.
`requestlog.OpenJSONL` creates a private mode-`0600` file. If a separate,
unprivileged collector such as Observatory is the only approved reader, prepare
a trusted setgid directory whose group is that collector, then opt into
`requestlog.OpenJSONLWithOptions(path, requestlog.JSONLOptions{FileMode: 0o640})`.
The application still owns rotation, retention, disk monitoring, and sink-error
health. Never use a world-readable log or add the collector to the application
account's broader groups merely to make collection convenient.
Configure trusted proxy networks narrowly. A forwarding header is not evidence Configure trusted proxy networks narrowly. A forwarding header is not evidence
by itself; it becomes usable only when the immediate peer and skipped proxy by itself; it becomes usable only when the immediate peer and skipped proxy
hops satisfy the resolver's trust policy. Metadata, authentication, or storage hops satisfy the resolver's trust policy. Metadata, authentication, or storage
+1 -1
View File
@@ -18,7 +18,7 @@ import "gamertan.com/web/requestmeta"
and request the containing module at an exact version: and request the containing module at an exact version:
```bash ```bash
go get gamertan.com/web/requestmeta@v0.1.0-preview.4 go get gamertan.com/web/requestmeta@v0.1.0-preview.6
``` ```
Only imported packages are compiled and linked. The packages nevertheless Only imported packages are compiled and linked. The packages nevertheless
+4 -2
View File
@@ -66,8 +66,10 @@ accepted from request input. Break-glass access lasts at most one hour and is
not a substitute for ordinary role policy. not a substitute for ordinary role policy.
Local storage adapters assume the parent directory and host account are trusted. Local storage adapters assume the parent directory and host account are trusted.
They reject a symlink at the configured final path and apply private file modes, They reject a symlink at the configured final path and apply bounded file modes,
but they do not defend against a concurrent privileged actor replacing path but they do not defend against a concurrent privileged actor replacing path
ancestors during an open. The synchronous JSONL adapter deliberately favors ancestors during an open. The synchronous JSONL adapter deliberately favors
durable, bounded evidence over maximum request throughput; the application owns durable, bounded evidence over maximum request throughput; the application owns
rotation, retention, disk monitoring, and health escalation. rotation, retention, disk monitoring, and health escalation. Its default is
mode `0600`; the sole wider option is mode `0640` for a deployment-assigned
collector group. The toolkit does not select or change that group.
+22 -2
View File
@@ -7,6 +7,7 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"errors" "errors"
"io/fs"
"os" "os"
"path/filepath" "path/filepath"
"sync" "sync"
@@ -20,20 +21,39 @@ type JSONL struct {
err error err error
} }
// JSONLOptions controls the local file boundary. A zero FileMode preserves the
// private 0600 default. Mode 0640 may be used when the deployment has assigned
// the file to one explicit collector group; world-readable or writable modes
// are never accepted.
type JSONLOptions struct {
FileMode fs.FileMode
}
func OpenJSONL(path string) (*JSONL, error) { func OpenJSONL(path string) (*JSONL, error) {
return OpenJSONLWithOptions(path, JSONLOptions{})
}
func OpenJSONLWithOptions(path string, options JSONLOptions) (*JSONL, error) {
if !filepath.IsAbs(path) || filepath.Clean(path) != path { if !filepath.IsAbs(path) || filepath.Clean(path) != path {
return nil, errors.New("requestlog: JSONL path must be clean and absolute") return nil, errors.New("requestlog: JSONL path must be clean and absolute")
} }
mode := options.FileMode
if mode == 0 {
mode = 0o600
}
if mode != 0o600 && mode != 0o640 {
return nil, errors.New("requestlog: JSONL mode must be 0600 or 0640")
}
if info, err := os.Lstat(path); err == nil && (info.Mode()&os.ModeSymlink != 0 || !info.Mode().IsRegular()) { if info, err := os.Lstat(path); err == nil && (info.Mode()&os.ModeSymlink != 0 || !info.Mode().IsRegular()) {
return nil, errors.New("requestlog: JSONL destination must be a regular file") return nil, errors.New("requestlog: JSONL destination must be a regular file")
} else if err != nil && !errors.Is(err, os.ErrNotExist) { } else if err != nil && !errors.Is(err, os.ErrNotExist) {
return nil, err return nil, err
} }
file, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o600) file, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, mode)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err = file.Chmod(0o600); err != nil { if err = file.Chmod(mode); err != nil {
file.Close() file.Close()
return nil, err return nil, err
} }
+27
View File
@@ -101,6 +101,33 @@ func TestJSONLRoundTripAndMode(t *testing.T) {
} }
} }
func TestJSONLAllowsExplicitCollectorGroupRead(t *testing.T) {
path := filepath.Join(t.TempDir(), "access.jsonl")
sink, err := OpenJSONLWithOptions(path, JSONLOptions{FileMode: 0o640})
if err != nil {
t.Fatal(err)
}
if err = sink.Close(); err != nil {
t.Fatal(err)
}
info, err := os.Stat(path)
if err != nil {
t.Fatal(err)
}
if runtime.GOOS != "windows" && info.Mode().Perm() != 0o640 {
t.Fatalf("mode=%o", info.Mode().Perm())
}
}
func TestJSONLRejectsOverlyPermissiveMode(t *testing.T) {
for _, mode := range []os.FileMode{0o400, 0o620, 0o644, 0o660, 0o666} {
path := filepath.Join(t.TempDir(), "access.jsonl")
if _, err := OpenJSONLWithOptions(path, JSONLOptions{FileMode: mode}); err == nil {
t.Fatalf("accepted mode %o", mode)
}
}
}
func TestPanicIsRecordedAndRepanicked(t *testing.T) { func TestPanicIsRecordedAndRepanicked(t *testing.T) {
sink := &memorySink{} sink := &memorySink{}
handler := Middleware(sink, Policy{})(http.HandlerFunc(func(http.ResponseWriter, *http.Request) { panic("expected") })) handler := Middleware(sink, Policy{})(http.HandlerFunc(func(http.ResponseWriter, *http.Request) { panic("expected") }))
+19 -7
View File
@@ -17,22 +17,34 @@ packages=(
webauthn webauthn
) )
install -D -m 0644 "$source_root/LICENSE" "$derived/LICENSE" install_file() {
source=$1
destination=$2
mkdir -p "$(dirname "$destination")"
install -m 0644 "$source" "$destination"
}
install_file "$source_root/LICENSE" "$derived/LICENSE"
for package in "${packages[@]}"; do for package in "${packages[@]}"; do
while IFS= read -r source; do for source in "$source_root/$package"/*.go; do
case $source in
*_test.go) continue ;;
esac
relative=${source#"$source_root"/} relative=${source#"$source_root"/}
install -D -m 0644 "$source" "$derived/$relative" install_file "$source" "$derived/$relative"
done < <(find "$source_root/$package" -maxdepth 1 -type f -name '*.go' ! -name '*_test.go' | sort) done
done done
while IFS= read -r source; do while IFS= read -r source; do
sed -i \ temporary="$source.tmp"
sed \
's#github.com/go-webauthn/webauthn#gamertan.com/web/internal/webauthnvendored#g' \ 's#github.com/go-webauthn/webauthn#gamertan.com/web/internal/webauthnvendored#g' \
"$source" "$source" >"$temporary"
mv "$temporary" "$source"
done < <(find "$derived" -type f -name '*.go' | sort) done < <(find "$derived" -type f -name '*.go' | sort)
cmp -s LICENSES/BSD-3-Clause-go-webauthn.txt "$embedded_root/LICENSE" cmp -s LICENSES/BSD-3-Clause-go-webauthn.txt "$embedded_root/LICENSE"
if ! diff -ru --no-dereference "$derived" "$embedded_root"; then if ! diff -ru "$derived" "$embedded_root"; then
echo 'compiled WebAuthn verifier differs from its audited mechanical derivation' >&2 echo 'compiled WebAuthn verifier differs from its audited mechanical derivation' >&2
exit 1 exit 1
fi fi
+4
View File
@@ -6,7 +6,11 @@ cd "$root"
test -f third_party/go-webauthn/LICENSE test -f third_party/go-webauthn/LICENSE
cmp -s LICENSES/BSD-3-Clause-go-webauthn.txt third_party/go-webauthn/LICENSE cmp -s LICENSES/BSD-3-Clause-go-webauthn.txt third_party/go-webauthn/LICENSE
if command -v sha256sum >/dev/null 2>&1; then
sha256sum -c third_party/go-webauthn.SHA256SUMS >/dev/null sha256sum -c third_party/go-webauthn.SHA256SUMS >/dev/null
else
shasum -a 256 -c third_party/go-webauthn.SHA256SUMS >/dev/null
fi
expected=$(sed -n 's# third_party/go-webauthn/.*#&#p' third_party/go-webauthn.SHA256SUMS | wc -l) expected=$(sed -n 's# third_party/go-webauthn/.*#&#p' third_party/go-webauthn.SHA256SUMS | wc -l)
actual=$(find third_party/go-webauthn -type f | wc -l) actual=$(find third_party/go-webauthn -type f | wc -l)
test "$expected" -eq "$actual" test "$expected" -eq "$actual"
+4 -1
View File
@@ -8,7 +8,10 @@ output=$1
[[ $output = /* && $output != / && ! -e $output ]] || usage [[ $output = /* && $output != / && ! -e $output ]] || usage
cd "$root" cd "$root"
[[ -z $(git status --porcelain=v1 --untracked-files=all) ]] || { echo "private source must be clean" >&2; exit 1; } [[ -z $(git status --porcelain=v1 --untracked-files=all) ]] || { echo "private source must be clean" >&2; exit 1; }
mapfile -t files < <(grep -Ev '^[[:space:]]*(#|$)' scripts/public-snapshot.allow) files=()
while IFS= read -r file; do
files+=("$file")
done < <(grep -Ev '^[[:space:]]*(#|$)' scripts/public-snapshot.allow)
[[ ${#files[@]} -gt 0 ]] || exit 1 [[ ${#files[@]} -gt 0 ]] || exit 1
for file in "${files[@]}"; do for file in "${files[@]}"; do
[[ $file != /* && $file != *..* ]] || { echo "invalid allowlisted path: $file" >&2; exit 1; } [[ $file != /* && $file != *..* ]] || { echo "invalid allowlisted path: $file" >&2; exit 1; }
+3 -3
View File
@@ -6,14 +6,14 @@ cd "$root"
temporary=$(mktemp -d) temporary=$(mktemp -d)
trap 'rm -rf "$temporary"' EXIT trap 'rm -rf "$temporary"' EXIT
./scripts/export-public.sh "$temporary/export" ./scripts/export-public.sh "$temporary/export"
(cd "$temporary/export" && find . -type f -printf '%P\n' | sort) >"$temporary/actual" (cd "$temporary/export" && find . -type f -print | sed 's#^\./##' | LC_ALL=C sort) >"$temporary/actual"
while IFS= read -r path; do while IFS= read -r path; do
if [[ $path = */ ]]; then if [[ $path = */ ]]; then
find "${path%/}" -type f -printf '%p\n' find "${path%/}" -type f -print
else else
echo "$path" echo "$path"
fi fi
done < <(grep -Ev '^[[:space:]]*(#|$)' scripts/public-snapshot.allow) | sort >"$temporary/expected" done < <(grep -Ev '^[[:space:]]*(#|$)' scripts/public-snapshot.allow) | LC_ALL=C sort >"$temporary/expected"
diff -u "$temporary/expected" "$temporary/actual" diff -u "$temporary/expected" "$temporary/actual"
private_word='PRI''VATE' private_word='PRI''VATE'
token_word='to''ken' token_word='to''ken'
+1
View File
@@ -5,6 +5,7 @@ root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
cd "$root" cd "$root"
./scripts/check-licenses.sh ./scripts/check-licenses.sh
./scripts/check-dependencies.sh ./scripts/check-dependencies.sh
./scripts/test-public-snapshot.sh
test -z "$(find . \( -path ./third_party -o -path ./internal/webauthnvendored \) -prune -o -name '*.go' -print0 | xargs -0 gofmt -l)" test -z "$(find . \( -path ./third_party -o -path ./internal/webauthnvendored \) -prune -o -name '*.go' -print0 | xargs -0 gofmt -l)"
go test ./... go test ./...
go test -race ./... go test -race ./...