Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion internal/build/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ func (b *Builder) Build(ctx context.Context, targets []*modules.Module) ([]Resul
// set those up uniformly regardless of cache state.

// TODO(MeteorsLiu): Source cache dir (belongs in the vcs layer)
tmpSourceDir, err := os.MkdirTemp("", fmt.Sprintf("source-%s-%s*", strings.ReplaceAll(mod.Path, "/", "-"), mod.Version))
tmpSourceDir, err := os.MkdirTemp("", fmt.Sprintf("source-%s-%s*", strings.ReplaceAll(mod.Path, "/", "-"), strings.ReplaceAll(mod.Version, "/", "-")))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Version not sanitized/validated (installDir uncovered)

This fix sanitizes the temp-dir name, but the same raw mod.Version still flows unescaped into installDir (internal/build/cache.go:83, the persistent output dir). A slash version creates a nested dir tree there, and because filepath.Join calls Clean, a ..-containing version could escape workspaceDir.

Root cause: mod.Version isn't validated at the trust boundary (unlike mod.Path). Per-char / replacement misses .., backslashes, and control chars. Consider validating/escaping the version centrally (e.g. module.EscapeVersion/allowlist) so both this call site and installDir are covered. A brief comment explaining the git-ref motivation would also help.

if err != nil {
return Result{}, err
}
Expand Down
27 changes: 27 additions & 0 deletions internal/build/build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,33 @@ func TestBuild_EmptyTargets(t *testing.T) {
}
}

func TestBuild_SlashInVersion(t *testing.T) {
store := setupTestStore(t)
b := setupBuilder(t, store, "amd64-linux")
wantRef := "refs/heads/feature/foo"
var gotRef string
sourceRepo := newMockRepo(filepath.Join(testSourceDir, "test/liba"))
sourceRepo.syncRef = &gotRef
b.newRepo = func(string) (vcs.Repo, error) {
return sourceRepo, nil
}
root := &modules.Module{
Formula: &internalformula.Formula{
OnBuild: func(*classfile.Context) {},
},
FS: os.DirFS(testFormulaDir),
Path: "test/liba",
Version: wantRef,
}

if _, err := b.Build(context.Background(), []*modules.Module{root}); err != nil {
t.Fatalf("Build() failed for slash-containing version: %v", err)
}
if gotRef != wantRef {
t.Fatalf("repo.Sync ref = %q, want %q", gotRef, wantRef)
}
}

func TestBuild_RecoversFormulaHookPanic(t *testing.T) {
wantErr := errors.New("hook failed")
tests := []struct {
Expand Down
5 changes: 5 additions & 0 deletions internal/build/mock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
// mockRepo implements vcs.Repo interface for testing.
type mockRepo struct {
testdataDir string
syncRef *string
}

func (m *mockRepo) Tags(ctx context.Context) ([]string, error) {
Expand All @@ -26,6 +27,10 @@ func (m *mockRepo) At(ref, localDir string) fs.FS {
}

func (m *mockRepo) Sync(ctx context.Context, ref, path, destDir string) error {
if m.syncRef != nil {
*m.syncRef = ref
}

// Strip "github.com/" prefix if present
path = strings.TrimPrefix(path, "github.com/")

Expand Down
2 changes: 1 addition & 1 deletion internal/modules/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ func resolveDeps(mod module.Version, modFS fs.ReadFileFS, frla *formula.Formula,
// TODO(MeteorsLiu): Design source cache dir
// In the most common case, onRequire only read one file like CMakelist.txt, etc.
// So missing cache here is acceptable.
tmpSourceDir, err := os.MkdirTemp("", fmt.Sprintf("source-%s-%s*", strings.ReplaceAll(mod.Path, "/", "-"), mod.Version))
tmpSourceDir, err := os.MkdirTemp("", fmt.Sprintf("source-%s-%s*", strings.ReplaceAll(mod.Path, "/", "-"), strings.ReplaceAll(mod.Version, "/", "-")))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P3] Duplicated sanitization expression across packages

This exact os.MkdirTemp pattern expression is now copy-pasted in both internal/build/build.go:276 and here. The two copies can silently drift if sanitization is later extended in only one. Extracting a small shared helper would keep them provably consistent.

if err != nil {
return nil, err
}
Expand Down
10 changes: 10 additions & 0 deletions internal/modules/load_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,16 @@ func TestResolveDeps_WithOnRequire_EchoOnly_FallbackToVersionsJson(t *testing.T)
}
}

func TestResolveDeps_OnRequire_SlashInVersion(t *testing.T) {
frla := loadTestFormula(t, "testdata/load/towner/withreq", "towner/withreq", "1.0.0")
modFS := os.DirFS("testdata/load/towner/withreq").(fs.ReadFileFS)
mod := module.Version{Path: "towner/withreq", Version: "refs/heads/feature/foo"}

if _, err := resolveDeps(mod, modFS, frla, classfile.Matrix{}); err != nil {
t.Fatalf("resolveDeps failed for slash-containing version: %v", err)
}
}

func TestResolveDeps_WithOnRequire_AddsDeps(t *testing.T) {
frla := loadTestFormula(t, "testdata/load/towner/withdeps", "towner/withdeps", "1.0.0")
modFS := os.DirFS("testdata/load/towner/withdeps").(fs.ReadFileFS)
Expand Down
Loading