Skip to content

Commit c647879

Browse files
committed
fix(foundation): match Windows system trees by first component only
"Users" was matched the way credential directory names are — against every path component. On Windows every user's files live under C:\Users\<name>, so that refused every ordinary project path, including a CI runner's own workspace. The Windows smoke job on the pull request caught it; the unit tests could not, because they only ever run compiled on macOS here. System trees (Windows, ProgramData, Program Files) now match only as the first component below the drive, which still refuses anything inside them at any depth. "Users" is handled separately as the tree root only, mirroring POSIX where "/Users" is refused but "/Users/dev/projects" is not. Tests cover the regression directly: C:/Users/dev/projects/app and C:/Users/runneradmin/work/repo are allowed, C:/Users is not, C:/Windows/System32 is refused at depth, and C:/dev/Windows-app — merely named after a system tree — is allowed. Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
1 parent 7fa5b07 commit c647879

2 files changed

Lines changed: 59 additions & 8 deletions

File tree

‎src/foundation/workspace.c‎

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -147,12 +147,44 @@ static const char *const WS_CREDENTIAL_NAMES[] = {
147147
".authinfo",
148148
};
149149

150-
/* Windows top-level trees below the drive. Small and stable enough to list,
151-
* which is what lets Windows use a shallower depth minimum. */
152-
static const char *const WS_WINDOWS_SYSTEM_DIRS[] = {
153-
"Windows", "Users", "ProgramData", "Program Files", "Program Files (x86)",
150+
/* Windows system trees, matched only as the FIRST component below the drive.
151+
* Nothing legitimate is indexed inside them at any depth.
152+
*
153+
* "Users" is deliberately NOT here. Every Windows user's files live under
154+
* C:\Users\<name>, so matching it the way credential names are matched — against
155+
* every component — refuses every ordinary Windows project path. It is handled
156+
* below as the tree root only, mirroring POSIX where "/Users" is refused but
157+
* "/Users/dev/projects" is not. */
158+
static const char *const WS_WINDOWS_SYSTEM_TREES[] = {
159+
"Windows",
160+
"ProgramData",
161+
"Program Files",
162+
"Program Files (x86)",
154163
};
155164

165+
/* Roots that are the tree itself rather than something inside it. */
166+
static const char *const WS_WINDOWS_USER_TREE = "Users";
167+
168+
/* Match only the first component below the volume prefix. */
169+
static bool ws_first_component_matches(const char *path, const char *const *names, size_t count,
170+
bool fold_case) {
171+
const char *p = path + ws_volume_prefix_len(path);
172+
while (ws_is_sep(*p)) {
173+
p++;
174+
}
175+
const char *start = p;
176+
while (*p && !ws_is_sep(*p)) {
177+
p++;
178+
}
179+
size_t len = (size_t)(p - start);
180+
for (size_t i = 0; i < count; i++) {
181+
if (ws_component_equals(start, len, names[i], fold_case)) {
182+
return true;
183+
}
184+
}
185+
return false;
186+
}
187+
156188
static bool ws_any_component_matches(const char *path, const char *const *names, size_t count,
157189
bool fold_case) {
158190
size_t prefix = ws_volume_prefix_len(path);
@@ -257,10 +289,17 @@ cbm_ws_verdict_t cbm_workspace_classify_root(const char *canonical_path, const c
257289
return CBM_WS_DENY_SENSITIVE;
258290
}
259291

260-
if (windows_style &&
261-
ws_any_component_matches(canonical_path, WS_WINDOWS_SYSTEM_DIRS,
262-
sizeof(WS_WINDOWS_SYSTEM_DIRS) / sizeof(WS_WINDOWS_SYSTEM_DIRS[0]),
263-
true)) {
292+
if (windows_style && ws_first_component_matches(canonical_path, WS_WINDOWS_SYSTEM_TREES,
293+
sizeof(WS_WINDOWS_SYSTEM_TREES) /
294+
sizeof(WS_WINDOWS_SYSTEM_TREES[0]),
295+
true)) {
296+
return CBM_WS_DENY_SENSITIVE;
297+
}
298+
299+
/* "C:/Users" is the user tree itself — refuse it as a root, but leave the
300+
* projects below it alone; that is where Windows users actually work. */
301+
if (windows_style && depth == 1 &&
302+
ws_first_component_matches(canonical_path, &WS_WINDOWS_USER_TREE, 1, true)) {
264303
return CBM_WS_DENY_SENSITIVE;
265304
}
266305

‎tests/test_workspace.c‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,18 @@ TEST(ws_credential_directories_are_sensitive_at_any_depth) {
107107
TEST(ws_windows_system_trees_are_sensitive) {
108108
ASSERT_EQ(cbm_workspace_classify_root("C:/Windows", HOME, CACHE), CBM_WS_DENY_SENSITIVE);
109109
ASSERT_EQ(cbm_workspace_classify_root("C:/Users", HOME, CACHE), CBM_WS_DENY_SENSITIVE);
110+
/* THE regression that matters on Windows: every user's work lives under
111+
* C:\Users\<name>, so matching "Users" against every component would refuse
112+
* every ordinary project path — including a CI runner's own workspace. Only
113+
* the tree root is refused. */
114+
ASSERT_EQ(cbm_workspace_classify_root("C:/Users/dev/projects/app", HOME, CACHE), CBM_WS_ALLOW);
115+
ASSERT_EQ(cbm_workspace_classify_root("C:/Users/runneradmin/work/repo", HOME, CACHE),
116+
CBM_WS_ALLOW);
117+
/* System trees are refused at any depth inside them, not just at the root. */
118+
ASSERT_EQ(cbm_workspace_classify_root("C:/Windows/System32", HOME, CACHE),
119+
CBM_WS_DENY_SENSITIVE);
120+
/* A project merely named after one is not one. */
121+
ASSERT_EQ(cbm_workspace_classify_root("C:/dev/Windows-app", HOME, CACHE), CBM_WS_ALLOW);
110122
ASSERT_EQ(cbm_workspace_classify_root("C:/ProgramData", HOME, CACHE), CBM_WS_DENY_SENSITIVE);
111123
ASSERT_EQ(cbm_workspace_classify_root("C:/Program Files/app", HOME, CACHE),
112124
CBM_WS_DENY_SENSITIVE);

0 commit comments

Comments
 (0)