fix(02): resolve symlinks before fixture and vars path checks (WR-07)
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
18
tide/flow.go
18
tide/flow.go
@@ -184,7 +184,20 @@ func materializeSidecar(base string, resp *Response) error {
|
|||||||
if base != "" {
|
if base != "" {
|
||||||
path = filepath.Join(base, resp.BodyFile)
|
path = filepath.Join(base, resp.BodyFile)
|
||||||
}
|
}
|
||||||
raw, err := os.ReadFile(path)
|
resolved, err := resolvePath(path)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("tide: read body_file %s: %w", resp.BodyFile, err)
|
||||||
|
}
|
||||||
|
if base != "" {
|
||||||
|
root, err := resolvePath(base)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("tide: fixture dir: %w", err)
|
||||||
|
}
|
||||||
|
if resolved != root && !strings.HasPrefix(resolved, root+string(os.PathSeparator)) {
|
||||||
|
return fmt.Errorf("tide: body_file %q escapes the fixture directory", resp.BodyFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
raw, err := os.ReadFile(resolved)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("tide: read body_file %s: %w", resp.BodyFile, err)
|
return fmt.Errorf("tide: read body_file %s: %w", resp.BodyFile, err)
|
||||||
}
|
}
|
||||||
@@ -208,6 +221,9 @@ func validateSidecar(path string) error {
|
|||||||
if clean == ".." || strings.HasPrefix(clean, "../") {
|
if clean == ".." || strings.HasPrefix(clean, "../") {
|
||||||
return fmt.Errorf("body_file %q escapes the fixture directory", path)
|
return fmt.Errorf("body_file %q escapes the fixture directory", path)
|
||||||
}
|
}
|
||||||
|
if _, err := resolvePath(path); err != nil {
|
||||||
|
return fmt.Errorf("body_file %q: %w", path, err)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -192,6 +192,9 @@ func validateFixturePath(p string) error {
|
|||||||
if clean == ".." || strings.HasPrefix(clean, "../") {
|
if clean == ".." || strings.HasPrefix(clean, "../") {
|
||||||
return fmt.Errorf("fixture %q escapes the fixture directory", p)
|
return fmt.Errorf("fixture %q escapes the fixture directory", p)
|
||||||
}
|
}
|
||||||
|
if _, err := resolvePath(p); err != nil {
|
||||||
|
return fmt.Errorf("fixture %q: %w", p, err)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -253,6 +253,23 @@ func TestProxyRejectsNonLoopbackOverflowAndCredentials(t *testing.T) {
|
|||||||
}); err == nil || !strings.Contains(err.Error(), "outside") {
|
}); err == nil || !strings.Contains(err.Error(), "outside") {
|
||||||
t.Fatalf("vars inside fixtures: %v", err)
|
t.Fatalf("vars inside fixtures: %v", err)
|
||||||
}
|
}
|
||||||
|
if err := os.WriteFile(inside, []byte("{}\n"), 0o600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
linkDir := t.TempDir()
|
||||||
|
link := filepath.Join(linkDir, "vars-link.yaml")
|
||||||
|
if err := os.Symlink(inside, link); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if _, err := NewProxy(ProxyConfig{
|
||||||
|
Listen: "127.0.0.1:0",
|
||||||
|
Upstream: upstream.URL,
|
||||||
|
Fixtures: fixtures,
|
||||||
|
VarsPath: link,
|
||||||
|
Rules: mustParseRules(t, testRulesYAML()),
|
||||||
|
}); err == nil || !strings.Contains(err.Error(), "outside") {
|
||||||
|
t.Fatalf("symlink vars into fixtures: %v", err)
|
||||||
|
}
|
||||||
outside := filepath.Join(t.TempDir(), "vars.yaml")
|
outside := filepath.Join(t.TempDir(), "vars.yaml")
|
||||||
p, err := NewProxy(ProxyConfig{
|
p, err := NewProxy(ProxyConfig{
|
||||||
Listen: "127.0.0.1:0",
|
Listen: "127.0.0.1:0",
|
||||||
|
|||||||
@@ -686,11 +686,11 @@ func varsOutsideFixtures(varsPath, fixtures string) error {
|
|||||||
if varsPath == "" || fixtures == "" {
|
if varsPath == "" || fixtures == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
absVars, err := filepath.Abs(varsPath)
|
absVars, err := resolvePath(varsPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("tide: vars path: %w", err)
|
return fmt.Errorf("tide: vars path: %w", err)
|
||||||
}
|
}
|
||||||
absFix, err := filepath.Abs(fixtures)
|
absFix, err := resolvePath(fixtures)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("tide: fixtures path: %w", err)
|
return fmt.Errorf("tide: fixtures path: %w", err)
|
||||||
}
|
}
|
||||||
@@ -700,6 +700,24 @@ func varsOutsideFixtures(varsPath, fixtures string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func resolvePath(path string) (string, error) {
|
||||||
|
abs, err := filepath.Abs(path)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if eval, err := filepath.EvalSymlinks(abs); err == nil {
|
||||||
|
return eval, nil
|
||||||
|
}
|
||||||
|
parentEval, err := filepath.EvalSymlinks(filepath.Dir(abs))
|
||||||
|
if err != nil {
|
||||||
|
if _, statErr := os.Lstat(abs); statErr == nil {
|
||||||
|
return "", fmt.Errorf("eval symlinks %s: %w", path, err)
|
||||||
|
}
|
||||||
|
return filepath.Clean(abs), nil
|
||||||
|
}
|
||||||
|
return filepath.Join(parentEval, filepath.Base(abs)), nil
|
||||||
|
}
|
||||||
|
|
||||||
func mergeRouteCaptures(step *Step, rules Rules) {
|
func mergeRouteCaptures(step *Step, rules Rules) {
|
||||||
if step == nil || len(step.Capture) > 0 {
|
if step == nil || len(step.Capture) > 0 {
|
||||||
return
|
return
|
||||||
|
|||||||
Reference in New Issue
Block a user