package brain_test import ( "os" "path/filepath" "strings" "sync" "sync/atomic" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/mathiasbq/hyperguild/ingestion/internal/brain" ) // brain_tunnel passes caller-supplied source and target paths to WriteTunnel, // which checked only "wiki//..." and then joined them onto the brain // dir: ".." segments resolved outside the brain and appendSeeAlso rewrote the // file there. func TestWriteTunnelRefusesATargetThatTraversesOutOfTheBrain(t *testing.T) { brain0 := t.TempDir() src := filepath.Join(brain0, "wiki", "a", "facts", "s.md") require.NoError(t, os.MkdirAll(filepath.Dir(src), 0o755)) require.NoError(t, os.WriteFile(src, []byte("---\ntitle: S\n---\nbody\n"), 0o644)) victim := filepath.Join(t.TempDir(), "victim.md") require.NoError(t, os.WriteFile(victim, []byte("untouched\n"), 0o644)) up := strings.Repeat("../", strings.Count(filepath.Clean(brain0), "/")+3) target := "wiki/b/x/" + up + strings.TrimPrefix(victim, "/") err := brain.WriteTunnel(brain0, "wiki/a/facts/s.md", target) assert.Error(t, err) got, rerr := os.ReadFile(victim) require.NoError(t, rerr) assert.Equal(t, "untouched\n", string(got), "a file outside the brain was modified") } func TestWriteTunnelRefusesUncleanOrNonWikiPaths(t *testing.T) { brain0 := t.TempDir() 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)) } for _, target := range []string{ "wiki/b/facts/../../../raw/r.md", "wiki/b/./facts/t.md", "wiki/b//facts/t.md", "/wiki/b/facts/t.md", "wiki/b/facts/t.txt", "wiki/b/facts/", } { assert.Error(t, brain.WriteTunnel(brain0, "wiki/a/facts/s.md", target), target) } 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")) require.NoError(t, err) assert.Contains(t, string(got), "[[a/facts/s]]") } func TestWriteTunnelDoesNotWriteThroughASymlinkedNote(t *testing.T) { brain0 := t.TempDir() src := filepath.Join(brain0, "wiki", "a", "facts", "s.md") require.NoError(t, os.MkdirAll(filepath.Dir(src), 0o755)) require.NoError(t, os.WriteFile(src, []byte("x\n"), 0o644)) tdir := filepath.Join(brain0, "wiki", "b", "facts") require.NoError(t, os.MkdirAll(tdir, 0o755)) victim := filepath.Join(t.TempDir(), "victim.md") require.NoError(t, os.WriteFile(victim, []byte("untouched\n"), 0o644)) require.NoError(t, os.Symlink(victim, filepath.Join(tdir, "t.md"))) assert.Error(t, brain.WriteTunnel(brain0, "wiki/a/facts/s.md", "wiki/b/facts/t.md")) 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") } // The target is swapped between a regular note and a symlink to an in-brain // file outside wiki/ while WriteTunnel runs: the read and the write each // refuse a symlink, so the other file is never rewritten. func TestWriteTunnelNeverRewritesASymlinkSwappedInForTheTarget(t *testing.T) { brain0 := t.TempDir() src := filepath.Join(brain0, "wiki", "a", "facts", "s.md") require.NoError(t, os.MkdirAll(filepath.Dir(src), 0o755)) require.NoError(t, os.WriteFile(src, []byte("x\n"), 0o644)) tdir := filepath.Join(brain0, "wiki", "b", "facts") require.NoError(t, os.MkdirAll(tdir, 0o755)) victim := filepath.Join(brain0, "raw", "victim.md") require.NoError(t, os.MkdirAll(filepath.Dir(victim), 0o755)) require.NoError(t, os.WriteFile(victim, []byte("untouched\n"), 0o644)) tgt := filepath.Join(tdir, "t.md") require.NoError(t, os.WriteFile(tgt, []byte("t\n"), 0o644)) var stop atomic.Bool var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() tmp := tgt + ".swap" for i := 0; !stop.Load(); i++ { _ = os.Remove(tmp) if i%2 == 0 { _ = os.WriteFile(tmp, []byte("t\n"), 0o644) } else { _ = os.Symlink(filepath.Join("..", "..", "..", "raw", "victim.md"), tmp) } _ = os.Rename(tmp, tgt) } }() ok := 0 for deadline := time.Now().Add(1500 * time.Millisecond); time.Now().Before(deadline); { if brain.WriteTunnel(brain0, "wiki/a/facts/s.md", "wiki/b/facts/t.md") == nil { ok++ } _ = os.WriteFile(src, []byte("x\n"), 0o644) } stop.Store(true) wg.Wait() got, err := os.ReadFile(victim) require.NoError(t, err) assert.Equal(t, "untouched\n", string(got), "the swapped-in symlink's target was rewritten") assert.Positive(t, ok, "positive control: some tunnels were written to the regular target") }