1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
//! Path normalization and workspace confinement utilities.
use ;
use warn;
/// Normalize a path by logically resolving `.` and `..` components.
///
/// This function does **not** access the filesystem, so it works for paths
/// that may not exist on disk (e.g. in unit tests). It processes each
/// component in order:
///
/// - [`std::path::Component::CurDir`] (`.`) — skipped
/// - [`std::path::Component::ParentDir`] (`..`) — pops the last pushed
/// component (if any), preventing traversal above the root
/// - All other components — pushed onto the result
///
/// # Security
///
/// This function performs **purely logical** normalization. It does **not**
/// resolve symbolic links. If the path contains a symlink component, the
/// normalized result may point to a different location on disk than the
/// logical resolution suggests.
///
/// **For security-sensitive checks** (e.g. workspace confinement), prefer
/// [`canonicalize_path`] which calls [`std::fs::canonicalize`] and resolves
/// symlinks through the real filesystem. Use this function only as a
/// fallback for paths that may not yet exist on disk.
///
/// # Examples
///
/// ```
/// use std::path::{Path, PathBuf};
///
/// assert_eq!(
/// agy_bridge::policies::normalize_path(Path::new("/workspace/../etc/passwd")),
/// PathBuf::from("/etc/passwd"),
/// );
/// assert_eq!(
/// agy_bridge::policies::normalize_path(Path::new("/workspace/./subdir/file.rs")),
/// PathBuf::from("/workspace/subdir/file.rs"),
/// );
/// ```
/// Resolve a path to its canonical, absolute form via the filesystem.
///
/// Unlike [`normalize_path`], this function accesses the real filesystem and
/// fully resolves symbolic links, `..`, and `.` components. The returned
/// path contains no symlink segments and is suitable for security-sensitive
/// comparisons such as workspace confinement checks.
///
/// # Errors
///
/// Returns an error if any component of the path does not exist or is not
/// accessible.
///
/// # Examples
///
/// ```
/// use std::path::Path;
///
/// let canon =
/// agy_bridge::policies::canonicalize_path(Path::new("/tmp")).expect("/tmp must exist");
/// assert!(canon.is_absolute());
/// ```
/// Check whether `candidate` falls under any workspace root.
///
/// When the candidate path exists on disk, both it and each workspace root
/// are resolved through [`canonicalize_path`] (which follows symlinks).
/// If canonicalization fails for either side (e.g. the path does not exist
/// yet), the function falls back to [`normalize_path`] for that operand and
/// logs a warning.
///
/// # Examples
///
/// ```
/// use std::path::PathBuf;
///
/// let ws = [PathBuf::from("/workspace")];
/// assert!(agy_bridge::policies::is_path_in_workspace(
/// "/workspace/src/main.rs",
/// &ws
/// ));
/// assert!(!agy_bridge::policies::is_path_in_workspace(
/// "/workspace/../etc/passwd",
/// &ws
/// ));
/// ```