@@ -367,6 +367,47 @@ end`)
367367 }
368368}
369369
370+ func TestRequireRejectsBackslashPathTraversal (t * testing.T ) {
371+ engine := MustNewEngine (Config {ModulePaths : []string {filepath .Join ("testdata" , "modules" )}})
372+
373+ script , err := engine .Compile (`def run()
374+ require("nested\\..\\..\\etc\\passwd")
375+ end` )
376+ if err != nil {
377+ t .Fatalf ("compile failed: %v" , err )
378+ }
379+
380+ if _ , err := script .Call (context .Background (), "run" , nil , CallOptions {}); err == nil {
381+ t .Fatalf ("expected error for backslash path traversal" )
382+ } else if ! strings .Contains (err .Error (), "escapes search paths" ) {
383+ t .Fatalf ("unexpected error: %v" , err )
384+ }
385+ }
386+
387+ func TestRequireNormalizesPathSeparators (t * testing.T ) {
388+ engine := MustNewEngine (Config {ModulePaths : []string {filepath .Join ("testdata" , "modules" )}})
389+
390+ script , err := engine .Compile (`def run(value)
391+ unix_style = require("shared/math")
392+ windows_style = require("shared\\math")
393+ unix_style.double(value) + windows_style.double(value)
394+ end` )
395+ if err != nil {
396+ t .Fatalf ("compile failed: %v" , err )
397+ }
398+
399+ result , err := script .Call (context .Background (), "run" , []Value {NewInt (3 )}, CallOptions {})
400+ if err != nil {
401+ t .Fatalf ("call failed: %v" , err )
402+ }
403+ if result .Kind () != KindInt || result .Int () != 12 {
404+ t .Fatalf ("expected 12, got %#v" , result )
405+ }
406+ if len (engine .modules ) != 1 {
407+ t .Fatalf ("expected normalized requires to share cache entry, got %d modules" , len (engine .modules ))
408+ }
409+ }
410+
370411func TestRequireRelativePathRequiresModuleCaller (t * testing.T ) {
371412 engine := MustNewEngine (Config {ModulePaths : []string {filepath .Join ("testdata" , "modules" )}})
372413
@@ -489,6 +530,42 @@ end`)
489530 }
490531}
491532
533+ func TestRequireSearchPathRejectsSymlinkEscape (t * testing.T ) {
534+ if runtime .GOOS == "windows" {
535+ t .Skip ("symlink behavior is environment-specific on Windows" )
536+ }
537+
538+ moduleRoot := t .TempDir ()
539+ outsideRoot := t .TempDir ()
540+
541+ secretModule := filepath .Join (outsideRoot , "secret.vibe" )
542+ if err := os .WriteFile (secretModule , []byte (`def hidden()
543+ 42
544+ end
545+ ` ), 0o644 ); err != nil {
546+ t .Fatalf ("write secret module: %v" , err )
547+ }
548+
549+ symlinkPath := filepath .Join (moduleRoot , "link" )
550+ if err := os .Symlink (outsideRoot , symlinkPath ); err != nil {
551+ t .Skipf ("symlink unavailable: %v" , err )
552+ }
553+
554+ engine := MustNewEngine (Config {ModulePaths : []string {moduleRoot }})
555+ script , err := engine .Compile (`def run()
556+ require("link/secret")
557+ end` )
558+ if err != nil {
559+ t .Fatalf ("compile failed: %v" , err )
560+ }
561+
562+ if _ , err := script .Call (context .Background (), "run" , nil , CallOptions {}); err == nil {
563+ t .Fatalf ("expected symlink escape error" )
564+ } else if ! strings .Contains (err .Error (), "escapes module root" ) {
565+ t .Fatalf ("unexpected error: %v" , err )
566+ }
567+ }
568+
492569func TestRequireRelativePathRejectsOutOfRootCachedModule (t * testing.T ) {
493570 if runtime .GOOS == "windows" {
494571 t .Skip ("symlink behavior is environment-specific on Windows" )
0 commit comments