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:
@@ -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 1–5 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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") }))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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 ./...
|
||||
|
||||
Reference in New Issue
Block a user