lib/sandbox/src/cwd.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const sys = @import("sys");
3
4 const resolve = @import("resolve.zig");
5
6 pub fn validate(path: []const u8) !void {
7 if (path.len == 0) return error.InvalidCwd;
8 if (std.fs.path.isAbsolute(path)) return error.AbsoluteCwd;
9 var iterator = std.fs.path.componentIterator(path);
10 while (iterator.next()) |component| {
11 if (std.mem.eql(u8, component.name, "..")) return error.CwdEscapesLayer;
12 }
13 }
14
15 pub fn ensureInside(root: sys.fs.Dir, child: sys.fs.Dir) !void {
16 var root_buffer: [std.fs.max_path_bytes]u8 = undefined;
17 const root_path = try resolve.dirPath(root, &root_buffer);
18 var child_buffer: [std.fs.max_path_bytes]u8 = undefined;
19 const child_path = try resolve.dirPath(child, &child_buffer);
20 if (!contains(root_path, child_path)) return error.CwdEscapesLayer;
21 }
22
23 fn contains(root: []const u8, child: []const u8) bool {
24 if (std.mem.eql(u8, root, child)) return true;
25 if (root.len == 1 and root[0] == std.fs.path.sep) return std.fs.path.isAbsolute(child);
26 if (!std.mem.startsWith(u8, child, root)) return false;
27 if (child.len <= root.len) return false;
28 return child[root.len] == std.fs.path.sep;
29 }
30
31 test "cwd rejects absolute and parent paths" {
32 try std.testing.expectError(error.AbsoluteCwd, validate("/tmp"));
33 try std.testing.expectError(error.CwdEscapesLayer, validate("../tmp"));
34 try std.testing.expectError(error.CwdEscapesLayer, validate("sub/../tmp"));
35 try validate(".");
36 try validate("sub");
37 }