fix(ingestion): confine /ingest-path and brain_ingest paths to brain/raw/
Both read a caller-chosen server path with extract.Text, so any file the pod could read (the ServiceAccount token, /etc/passwd) could be ingested into the brain and read back with brain_query. ResolveIngestPath resolves relative paths against brain/raw/ (the inbox the watcher already owns), evaluates symlinks, and refuses anything that lands outside. A directory ingest re-resolves every walked file, so a symlinked file inside raw/ cannot lead out. Existing ingest-path tests moved their fixtures under brain/raw/. Refs #149 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -543,6 +543,12 @@ func (s *Server) brainIngest(ctx context.Context, args json.RawMessage) (json.Ra
|
||||
}
|
||||
|
||||
if a.Path != "" {
|
||||
// Confined to brain/raw/ with symlinks evaluated (hyperguild#149).
|
||||
resolved, err := api.ResolveIngestPath(s.brainDir, a.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
a.Path = resolved
|
||||
text, err := extract.Text(a.Path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("extract: %w", err)
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package mcp_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/mathiasbq/hyperguild/ingestion/internal/mcp"
|
||||
"github.com/mathiasbq/hyperguild/ingestion/internal/pipeline"
|
||||
)
|
||||
|
||||
// hyperguild#149: brain_ingest's `path` read any file the pod could read and
|
||||
// ingested it into the brain, where brain_query reads it back out.
|
||||
func TestBrainIngestRefusesAPathOutsideTheRawInbox(t *testing.T) {
|
||||
stub := pipeline.Config{Complete: func(context.Context, string, string) (string, error) {
|
||||
return `[{"title":"Leak","type":"concept","content":"x"}]`, nil
|
||||
}}
|
||||
srv := mcp.NewServer(newBrainRepo(t), &stub, nil, nil)
|
||||
resp := authCall(t, srv, "", "brain_ingest", map[string]any{"path": "/etc/hostname"})
|
||||
require.NotNil(t, resp["error"], "a path outside brain/raw/ must be refused")
|
||||
assert.Contains(t, resp["error"].(map[string]any)["message"], "brain/raw/")
|
||||
}
|
||||
Reference in New Issue
Block a user