From 6cf6e608d80d79ccbe1e249b02c091675fb49c30 Mon Sep 17 00:00:00 2001 From: Molecule AI Core-BE Date: Thu, 14 May 2026 22:52:44 +0000 Subject: [PATCH] fix(staging): add isCPTemplateConfigFile filter to collectCPConfigFiles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cherry-picks the filter from main commit 8fced202: only transport config.yaml and files under prompts/ from the template directory to the control plane. Arbitrary template files (adapter.py, Dockerfile, etc.) are now excluded regardless of size, reducing the transport surface. Also adds a test case verifying adapter.py is excluded even when within the size limit. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- .../internal/provisioner/cp_provisioner.go | 13 +++++++++++++ .../internal/provisioner/cp_provisioner_test.go | 12 ++++++++++++ 2 files changed, 25 insertions(+) diff --git a/workspace-server/internal/provisioner/cp_provisioner.go b/workspace-server/internal/provisioner/cp_provisioner.go index f7cbbbf2..dfd1afe5 100644 --- a/workspace-server/internal/provisioner/cp_provisioner.go +++ b/workspace-server/internal/provisioner/cp_provisioner.go @@ -257,6 +257,16 @@ func (p *CPProvisioner) Start(ctx context.Context, cfg WorkspaceConfig) (string, const cpConfigFilesMaxBytes = 12 << 10 +// isCPTemplateConfigFile restricts which files from a template directory are +// eligible for transport to the control plane. Only config.yaml (the runtime +// entrypoint config) and files under prompts/ (system prompts) are needed; +// shipping arbitrary files (e.g. adapter.py, Dockerfile) is both unnecessary +// and a potential data-exfiltration surface. +func isCPTemplateConfigFile(name string) bool { + name = filepath.ToSlash(filepath.Clean(name)) + return name == "config.yaml" || strings.HasPrefix(name, "prompts/") +} + func collectCPConfigFiles(cfg WorkspaceConfig) (map[string]string, error) { files := make(map[string]string) total := 0 @@ -310,6 +320,9 @@ func collectCPConfigFiles(cfg WorkspaceConfig) (map[string]string, error) { if err != nil { return err } + if !isCPTemplateConfigFile(rel) { + return nil + } data, err := os.ReadFile(path) if err != nil { return err diff --git a/workspace-server/internal/provisioner/cp_provisioner_test.go b/workspace-server/internal/provisioner/cp_provisioner_test.go index aac85e89..cde5ea1c 100644 --- a/workspace-server/internal/provisioner/cp_provisioner_test.go +++ b/workspace-server/internal/provisioner/cp_provisioner_test.go @@ -1,6 +1,7 @@ package provisioner import ( + "bytes" "context" "encoding/base64" "encoding/json" @@ -291,6 +292,11 @@ func TestStart_CollectsConfigFiles(t *testing.T) { if err := os.WriteFile(filepath.Join(tmpl, "config.yaml"), []byte("name: test\n"), 0o600); err != nil { t.Fatal(err) } + // adapter.py is within the size limit but is NOT config.yaml or prompts/, + // so isCPTemplateConfigFile must exclude it from the transport. + if err := os.WriteFile(filepath.Join(tmpl, "adapter.py"), bytes.Repeat([]byte("x"), cpConfigFilesMaxBytes), 0o600); err != nil { + t.Fatal(err) + } var gotBody cpProvisionRequest srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -339,6 +345,12 @@ func TestStart_CollectsConfigFiles(t *testing.T) { if !foundGenerated { t.Errorf("ConfigFiles missing generated.json from ConfigFiles") } + // adapter.py must NOT be in ConfigFiles — isCPTemplateConfigFile filters it out + for name := range gotBody.ConfigFiles { + if name == "adapter.py" { + t.Errorf("adapter.py should not be in ConfigFiles — isCPTemplateConfigFile must filter it out") + } + } } // TestStart_SymlinkTemplatePathError — a symlink TemplatePath should cause