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>
This commit is contained in:
2026-08-24 17:06:47 -04:00
parent 5563306368
commit a39e8f893c
12 changed files with 110 additions and 21 deletions
+12
View File
@@ -4,6 +4,18 @@
## 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
+3 -3
View File
@@ -2,7 +2,7 @@
# Gamertan Web Foundations
> Status: `v0.1.0-preview.5` 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.
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:
```bash
go get gamertan.com/web@v0.1.0-preview.5
go get gamertan.com/web@v0.1.0-preview.6
go mod verify
```
An application may also name the first package it intends to adopt:
```bash
go get gamertan.com/web/requestmeta@v0.1.0-preview.5
go get gamertan.com/web/requestmeta@v0.1.0-preview.6
```
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:
```bash
go get gamertan.com/web/requestmeta@v0.1.0-preview.5
go get gamertan.com/web/requestmeta@v0.1.0-preview.6
go mod verify
```
@@ -45,6 +45,14 @@ handler = resolver.Middleware(handler)
The complete, copyable composition is in [`starters/basic`](../starters/basic).
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
by itself; it becomes usable only when the immediate peer and skipped proxy
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:
```bash
go get gamertan.com/web/requestmeta@v0.1.0-preview.5
go get gamertan.com/web/requestmeta@v0.1.0-preview.6
```
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.
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
ancestors during an open. The synchronous JSONL adapter deliberately favors
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"
"encoding/json"
"errors"
"io/fs"
"os"
"path/filepath"
"sync"
@@ -20,20 +21,39 @@ type JSONL struct {
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) {
return OpenJSONLWithOptions(path, JSONLOptions{})
}
func OpenJSONLWithOptions(path string, options JSONLOptions) (*JSONL, error) {
if !filepath.IsAbs(path) || filepath.Clean(path) != path {
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()) {
return nil, errors.New("requestlog: JSONL destination must be a regular file")
} else if err != nil && !errors.Is(err, os.ErrNotExist) {
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 {
return nil, err
}
if err = file.Chmod(0o600); err != nil {
if err = file.Chmod(mode); err != nil {
file.Close()
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) {
sink := &memorySink{}
handler := Middleware(sink, Policy{})(http.HandlerFunc(func(http.ResponseWriter, *http.Request) { panic("expected") }))
+19 -7
View File
@@ -17,22 +17,34 @@ packages=(
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
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"/}
install -D -m 0644 "$source" "$derived/$relative"
done < <(find "$source_root/$package" -maxdepth 1 -type f -name '*.go' ! -name '*_test.go' | sort)
install_file "$source" "$derived/$relative"
done
done
while IFS= read -r source; do
sed -i \
temporary="$source.tmp"
sed \
'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)
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
exit 1
fi
+4
View File
@@ -6,7 +6,11 @@ cd "$root"
test -f 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
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)
actual=$(find third_party/go-webauthn -type f | wc -l)
test "$expected" -eq "$actual"
+4 -1
View File
@@ -8,7 +8,10 @@ output=$1
[[ $output = /* && $output != / && ! -e $output ]] || usage
cd "$root"
[[ -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
for file in "${files[@]}"; do
[[ $file != /* && $file != *..* ]] || { echo "invalid allowlisted path: $file" >&2; exit 1; }
+3 -3
View File
@@ -6,14 +6,14 @@ cd "$root"
temporary=$(mktemp -d)
trap 'rm -rf "$temporary"' EXIT
./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
if [[ $path = */ ]]; then
find "${path%/}" -type f -printf '%p\n'
find "${path%/}" -type f -print
else
echo "$path"
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"
private_word='PRI''VATE'
token_word='to''ken'
+1
View File
@@ -5,6 +5,7 @@ root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
cd "$root"
./scripts/check-licenses.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)"
go test ./...
go test -race ./...