fix(update,session): brain_update and session_log confine caller paths (#169)
Review round 1 on PR #170 found two more handlers with the #169 class of bug (verified by execution): - brain_update resolved its caller path with resolveWithin, a lexical containment check: any brain file could be superseded (classification.yaml, raw/, sessions/), and a final symlink was written through. UpdateNote now resolves like brain_edit (notes.Resolve, ScopeNotes: knowledge/ and wiki/ .md notes), and reads and rewrites through brainfs (ReadRegular, WriteRegular). - session_log joined the caller's session_id onto the sessions dir and opened it O_CREATE|O_APPEND: an id with path segments created or appended a .jsonl file anywhere the pod could write. The id must now be a plain name (^[A-Za-z0-9_][A-Za-z0-9._-]{0,127}$, which every live session file matches), and the open adds O_NOFOLLOW|O_NONBLOCK. Read checks the id too. - Tunnel tests: the .txt case now names an existing file (it failed on "missing" before), the symlink test adds an in-brain target, and the FIFO comment no longer claims the handle check is the only guard. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -38,7 +38,7 @@ func TestWriteTunnelRefusesATargetThatTraversesOutOfTheBrain(t *testing.T) {
|
||||
|
||||
func TestWriteTunnelRefusesUncleanOrNonWikiPaths(t *testing.T) {
|
||||
brain0 := t.TempDir()
|
||||
for _, p := range []string{"wiki/a/facts/s.md", "wiki/b/facts/t.md", "raw/r.md"} {
|
||||
for _, p := range []string{"wiki/a/facts/s.md", "wiki/b/facts/t.md", "wiki/b/facts/t.txt", "raw/r.md"} {
|
||||
require.NoError(t, os.MkdirAll(filepath.Dir(filepath.Join(brain0, p)), 0o755))
|
||||
require.NoError(t, os.WriteFile(filepath.Join(brain0, p), []byte("x\n"), 0o644))
|
||||
}
|
||||
@@ -52,11 +52,13 @@ func TestWriteTunnelRefusesUncleanOrNonWikiPaths(t *testing.T) {
|
||||
} {
|
||||
assert.Error(t, brain.WriteTunnel(brain0, "wiki/a/facts/s.md", target), target)
|
||||
}
|
||||
got, err := os.ReadFile(filepath.Join(brain0, "raw", "r.md"))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "x\n", string(got))
|
||||
for _, p := range []string{"raw/r.md", "wiki/b/facts/t.txt"} {
|
||||
got, err := os.ReadFile(filepath.Join(brain0, p))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "x\n", string(got), p)
|
||||
}
|
||||
require.NoError(t, brain.WriteTunnel(brain0, "wiki/a/facts/s.md", "wiki/b/facts/t.md"), "positive control")
|
||||
got, err = os.ReadFile(filepath.Join(brain0, "wiki", "b", "facts", "t.md"))
|
||||
got, err := os.ReadFile(filepath.Join(brain0, "wiki", "b", "facts", "t.md"))
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, string(got), "[[a/facts/s]]")
|
||||
}
|
||||
@@ -75,6 +77,17 @@ func TestWriteTunnelDoesNotWriteThroughASymlinkedNote(t *testing.T) {
|
||||
got, err := os.ReadFile(victim)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "untouched\n", string(got))
|
||||
|
||||
// The same with a link to a file elsewhere in the brain, which os.Root
|
||||
// would follow.
|
||||
inVictim := filepath.Join(brain0, "raw", "v.md")
|
||||
require.NoError(t, os.MkdirAll(filepath.Dir(inVictim), 0o755))
|
||||
require.NoError(t, os.WriteFile(inVictim, []byte("untouched\n"), 0o644))
|
||||
require.NoError(t, os.Symlink(filepath.Join("..", "..", "..", "raw", "v.md"), filepath.Join(tdir, "u.md")))
|
||||
assert.Error(t, brain.WriteTunnel(brain0, "wiki/a/facts/s.md", "wiki/b/facts/u.md"))
|
||||
got, err = os.ReadFile(inVictim)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "untouched\n", string(got))
|
||||
got, err = os.ReadFile(src)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "x\n", string(got), "the source is not touched when the target is refused")
|
||||
|
||||
Reference in New Issue
Block a user