mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
fix(workspace): do not follow links in the path-alias config walk
find_alias_files descended into every entry whose is_dir flag was set. On Windows that flag is also set for a directory junction and a directory symbolic link, so the tsconfig/jsconfig walk could leave the repository through a link. Discovery and the incremental walker already skip links (safe_stat, cbm_path_info_utf8().is_symlink); the alias walk now does the same on every platform, and skips a child whose metadata cannot be read. On POSIX the walk already stopped at links because readdir reports them as DT_LNK rather than DT_DIR; the symbolic-link test pins that contract. The junction test (cmd.exe mklink /J, no privilege needed) is the case that changes and is compiled on Windows only. Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
1 parent
96c3f41cf3
commit
154aba0062
2 files changed
+156
No files matched your search
@@ -433,6 +433,15 @@ static void find_alias_files(const char *abs_dir, const char *rel_dir, alias_con
|
||||
if (cbm_pipeline_relpath_is_excluded(child_rel, excluded_dirs, excluded_count)) {
|
||||
continue;
|
||||
}
|
||||
/* A link is not followed, as discovery does not follow one. is_dir is
|
||||
* also true for a junction or a directory symbolic link on Windows,
|
||||
* and either can lead out of the repository. A child whose metadata
|
||||
* cannot be read is skipped the same way. */
|
||||
cbm_path_info_t child_info;
|
||||
if (cbm_path_info_utf8(child_abs, &child_info) != CBM_PATH_INFO_OK ||
|
||||
child_info.is_symlink) {
|
||||
continue;
|
||||
}
|
||||
find_alias_files(child_abs, child_rel, out, count, max_count, depth + 1, excluded_dirs,
|
||||
excluded_count);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "test_framework.h"
|
||||
#include "../src/pipeline/path_alias.h"
|
||||
#include "../src/foundation/compat.h"
|
||||
#include "../src/foundation/compat_fs.h"
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
@@ -407,6 +408,148 @@ TEST(path_alias_loader_no_configs) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* ── Loader does not follow a linked directory ─────────────────── */
|
||||
|
||||
/* The walk must not leave the repository through a link, as discovery does
|
||||
* not. A config in a real subdirectory is collected; one reachable only
|
||||
* through a link to a directory outside the tree is not. */
|
||||
TEST(path_alias_loader_skips_linked_directory) {
|
||||
#ifdef _WIN32
|
||||
/* symlink() does not exist on Windows, and creating a symbolic link there
|
||||
* needs a privilege an ordinary account (and the CI runner) does not hold.
|
||||
* The Windows shape of this case is a directory junction (cmd.exe mklink
|
||||
* /J), which this file has no cmd.exe fixture for; the junction case is
|
||||
* not covered here. */
|
||||
SKIP_PLATFORM("Windows: symlink() unavailable; links need a privilege");
|
||||
#else
|
||||
char tmpl[256];
|
||||
snprintf(tmpl, sizeof(tmpl), "/tmp/cbm_palias_link_XXXXXX");
|
||||
char *root = cbm_mkdtemp(tmpl);
|
||||
ASSERT_NOT_NULL(root);
|
||||
char outside_tmpl[256];
|
||||
snprintf(outside_tmpl, sizeof(outside_tmpl), "/tmp/cbm_palias_outside_XXXXXX");
|
||||
char *outside = cbm_mkdtemp(outside_tmpl);
|
||||
ASSERT_NOT_NULL(outside);
|
||||
|
||||
char sub[512];
|
||||
snprintf(sub, sizeof(sub), "%s/pkg", root);
|
||||
cbm_mkdir(sub);
|
||||
|
||||
char path[512];
|
||||
snprintf(path, sizeof(path), "%s/pkg/tsconfig.json", root);
|
||||
ASSERT_EQ(write_file(path,
|
||||
"{\n \"compilerOptions\": {\n \"paths\": {\n"
|
||||
" \"@pkg/*\": [\"./src/*\"]\n }\n }\n}\n"),
|
||||
0);
|
||||
snprintf(path, sizeof(path), "%s/tsconfig.json", outside);
|
||||
ASSERT_EQ(write_file(path,
|
||||
"{\n \"compilerOptions\": {\n \"paths\": {\n"
|
||||
" \"@out/*\": [\"./src/*\"]\n }\n }\n}\n"),
|
||||
0);
|
||||
|
||||
char link[512];
|
||||
snprintf(link, sizeof(link), "%s/linked", root);
|
||||
ASSERT_EQ(symlink(outside, link), 0);
|
||||
|
||||
cbm_path_alias_collection_t *coll = cbm_load_path_aliases(root);
|
||||
ASSERT_NOT_NULL(coll);
|
||||
/* Only the real subdirectory contributes a scope. */
|
||||
ASSERT_EQ(coll->count, 1);
|
||||
ASSERT_STR_EQ(coll->scopes[0].dir_prefix, "pkg");
|
||||
ASSERT_NULL(cbm_path_alias_find_for_file(coll, "linked/src/x.ts"));
|
||||
cbm_path_alias_collection_free(coll);
|
||||
|
||||
unlink(link);
|
||||
snprintf(path, sizeof(path), "%s/pkg/tsconfig.json", root);
|
||||
unlink(path);
|
||||
snprintf(path, sizeof(path), "%s/pkg", root);
|
||||
rmdir(path);
|
||||
rmdir(root);
|
||||
snprintf(path, sizeof(path), "%s/tsconfig.json", outside);
|
||||
unlink(path);
|
||||
rmdir(outside);
|
||||
PASS();
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
/* ── Loader does not follow a directory junction (Windows) ─────── */
|
||||
|
||||
/* The Windows shape of the linked-directory case. A junction needs no
|
||||
* privilege (a symbolic link does), and the directory listing reports it
|
||||
* with the directory attribute, so is_dir alone would descend through it.
|
||||
* Compiled out elsewhere: cmd.exe and junctions exist only on Windows, so
|
||||
* this case runs on the Windows leg and nowhere else. */
|
||||
TEST(path_alias_loader_skips_junction) {
|
||||
char root_tmpl[512];
|
||||
char outside_tmpl[512];
|
||||
snprintf(root_tmpl, sizeof(root_tmpl), "%s/cbm_palias_junction_XXXXXX", cbm_tmpdir());
|
||||
snprintf(outside_tmpl, sizeof(outside_tmpl), "%s/cbm_palias_outside_XXXXXX", cbm_tmpdir());
|
||||
char *root = cbm_mkdtemp(root_tmpl);
|
||||
ASSERT_NOT_NULL(root);
|
||||
char *outside = cbm_mkdtemp(outside_tmpl);
|
||||
ASSERT_NOT_NULL(outside);
|
||||
|
||||
char sub[600];
|
||||
snprintf(sub, sizeof(sub), "%s/pkg", root);
|
||||
cbm_mkdir(sub);
|
||||
|
||||
char path[700];
|
||||
snprintf(path, sizeof(path), "%s/pkg/tsconfig.json", root);
|
||||
ASSERT_EQ(write_file(path,
|
||||
"{\n \"compilerOptions\": {\n \"paths\": {\n"
|
||||
" \"@pkg/*\": [\"./src/*\"]\n }\n }\n}\n"),
|
||||
0);
|
||||
snprintf(path, sizeof(path), "%s/tsconfig.json", outside);
|
||||
ASSERT_EQ(write_file(path,
|
||||
"{\n \"compilerOptions\": {\n \"paths\": {\n"
|
||||
" \"@out/*\": [\"./src/*\"]\n }\n }\n}\n"),
|
||||
0);
|
||||
|
||||
/* cbm_tmpdir() can expose the MSYS spelling C:/msys64/...; cmd's mklink
|
||||
* builtin treats the slash before "msys64" as another option delimiter.
|
||||
* Native backslashes are required only at this cmd.exe fixture boundary. */
|
||||
char junction[600];
|
||||
snprintf(junction, sizeof(junction), "%s/linked", root);
|
||||
char junction_native[sizeof(junction)];
|
||||
char outside_native[sizeof(outside_tmpl)];
|
||||
snprintf(junction_native, sizeof(junction_native), "%s", junction);
|
||||
snprintf(outside_native, sizeof(outside_native), "%s", outside);
|
||||
for (char *cursor = junction_native; *cursor; cursor++) {
|
||||
if (*cursor == '/') {
|
||||
*cursor = '\\';
|
||||
}
|
||||
}
|
||||
for (char *cursor = outside_native; *cursor; cursor++) {
|
||||
if (*cursor == '/') {
|
||||
*cursor = '\\';
|
||||
}
|
||||
}
|
||||
const char *junction_argv[] = {"cmd.exe", "/d", "/c", "mklink", "/J",
|
||||
junction_native, outside_native, NULL};
|
||||
ASSERT_EQ(cbm_exec_no_shell(junction_argv), 0);
|
||||
|
||||
cbm_path_alias_collection_t *coll = cbm_load_path_aliases(root);
|
||||
ASSERT_NOT_NULL(coll);
|
||||
/* Only the real subdirectory contributes a scope. */
|
||||
ASSERT_EQ(coll->count, 1);
|
||||
ASSERT_STR_EQ(coll->scopes[0].dir_prefix, "pkg");
|
||||
ASSERT_NULL(cbm_path_alias_find_for_file(coll, "linked/src/x.ts"));
|
||||
cbm_path_alias_collection_free(coll);
|
||||
|
||||
/* Removing the junction removes the link, not its target. */
|
||||
cbm_rmdir(junction);
|
||||
snprintf(path, sizeof(path), "%s/pkg/tsconfig.json", root);
|
||||
cbm_unlink(path);
|
||||
cbm_rmdir(sub);
|
||||
cbm_rmdir(root);
|
||||
snprintf(path, sizeof(path), "%s/tsconfig.json", outside);
|
||||
cbm_unlink(path);
|
||||
cbm_rmdir(outside);
|
||||
PASS();
|
||||
}
|
||||
#endif
|
||||
|
||||
void suite_path_alias(void);
|
||||
void suite_path_alias(void) {
|
||||
RUN_TEST(path_alias_at_wildcard);
|
||||
@@ -421,4 +564,8 @@ void suite_path_alias(void) {
|
||||
RUN_TEST(path_alias_loader_monorepo_dotdot_climb);
|
||||
RUN_TEST(path_alias_loader_honors_discovery_exclusions);
|
||||
RUN_TEST(path_alias_loader_no_configs);
|
||||
RUN_TEST(path_alias_loader_skips_linked_directory);
|
||||
#ifdef _WIN32
|
||||
RUN_TEST(path_alias_loader_skips_junction);
|
||||
#endif
|
||||
}
|
||||
Reference in new issue
Block a user