Take in an admin's edits of the state files while running (closes #68)
check / check (push) Successful in 2m56s

smallwebwaf watches SWWAF_STATE_DIR with fsnotify and takes in an edit of
a state file as soon as it is saved, in place of what it held. It tells
its own writes from an admin's by the SHA-256 of what it last read or
wrote, and each write takes in an edit made since first. An edit that
does not parse is renamed to <name>.bad at the file's next write. Every
ban on a netblock is checked, so an added ban that starts before the
others still refuses. README.md says how to add and lift a ban.

Judgement call: a broken edit is set aside at the next write, since an
editor's file can be read half written.

Model: opus-5-5
This commit is contained in:
2026-10-06 08:46:39 +00:00
parent 68f687cb0c
commit 09eac675dc
12 changed files with 883 additions and 167 deletions
+35
View File
@@ -0,0 +1,35 @@
package state
import (
"os"
"path/filepath"
"testing"
)
// The test is on write itself: a state file is read before it is
// written, and a directory in its place fails that read first.
func TestFailedRenameLeavesNoTemporaryFile(t *testing.T) {
t.Parallel()
dir := t.TempDir()
// A directory named bans.json cannot be renamed over.
err := os.Mkdir(filepath.Join(dir, bansJSON), 0o700)
if err != nil {
t.Fatalf("mkdir: %v", err)
}
err = write(dir, bansJSON, []byte("{}\n"))
if err == nil {
t.Error("writing over a directory did not fail")
}
entries, err := os.ReadDir(dir)
if err != nil {
t.Fatalf("read %s: %v", dir, err)
}
if len(entries) != 1 || entries[0].Name() != bansJSON {
t.Errorf("%s holds %v, want only bans.json", dir, entries)
}
}