From 3c8913bbc676603072b03d568438e5bbafbc83b4 Mon Sep 17 00:00:00 2001 From: Bryce Kwon Date: Fri, 16 Oct 2026 06:09:53 -1000 Subject: Gate a readme ending in a colon like a disk path trust-scan-config passed any readme holding a colon as a tracked file, while the parser takes one only when a path follows the colon, so a scanned repository could still name a file on disk outside itself. The two now share one test. --- source/cgit.c | 6 ++---- source/scan-tree.c | 6 +++--- source/shared.c | 7 +++++++ source/shared.h | 6 ++++++ tests/t0303-robustness.sh | 10 ++++++++++ 5 files changed, 28 insertions(+), 7 deletions(-) diff --git a/source/cgit.c b/source/cgit.c index 633b011..82c496e 100644 --- a/source/cgit.c +++ b/source/cgit.c @@ -1035,10 +1035,8 @@ static void parse_readme(const char *readme, char **filename, char **ref, struct if (!readme || !readme[0]) return; - // A colon separates a ref from a path, so a setting carrying one names - // a file tracked in the repository rather than one on disk. - colon = strchr(readme, ':'); - if (colon && strlen(colon) > 1) { + if (cgit_readme_is_tracked(readme)) { + colon = strchr(readme, ':'); if (colon == readme && ctx.qry.head) *ref = xstrdup(ctx.qry.head); else if (colon == readme && repo->defbranch) diff --git a/source/scan-tree.c b/source/scan-tree.c index 035e4e1..b941ec5 100644 --- a/source/scan-tree.c +++ b/source/scan-tree.c @@ -52,8 +52,8 @@ static int is_git_dir(const char *path) * A repository's own files belong to whoever can push to it, so the keys * that run a command, put raw markup on the page, hand a file to the browser * as markup, read a file off the disk or place a link wait on - * trust-scan-config. A readme naming a git object, the form with a colon, - * only reads from the repository and passes. + * trust-scan-config. A readme naming a tracked file only reads from the + * repository and passes. */ static int trusted_key(const char *name, const char *value) { @@ -70,7 +70,7 @@ static int trusted_key(const char *name, const char *value) !strcmp(name, "logo-link") || !strcmp(name, "clone-url") || !strcmp(name, "enable-html-serving") || - (!strcmp(name, "readme") && !strchr(value, ':')); + (!strcmp(name, "readme") && !cgit_readme_is_tracked(value)); if (!untrusted) return 1; fprintf(stderr, "[cgit] Ignoring %s in %s: trust-scan-config is not set\n", name, diff --git a/source/shared.c b/source/shared.c index befd3e5..6e69666 100644 --- a/source/shared.c +++ b/source/shared.c @@ -739,3 +739,10 @@ char *cgit_get_mimetype_for_filename(const char *filename) fclose(file); return NULL; } + +int cgit_readme_is_tracked(const char *readme) +{ + const char *colon = strchr(readme, ':'); + + return colon && colon[1]; +} diff --git a/source/shared.h b/source/shared.h index ca2ac6a..13932c8 100644 --- a/source/shared.h +++ b/source/shared.h @@ -109,6 +109,12 @@ extern char *cgit_expand_macros(const char *text); extern char *cgit_get_mimetype_for_filename(const char *filename); +/* + * Whether a readme setting names a file tracked in the repository, written + * as ref:path or :path, rather than a file on the server's disk. + */ +extern int cgit_readme_is_tracked(const char *readme); + /* * Whether a revision from the request is a hex object id or a possible ref * name, the only forms cgit hands to git. diff --git a/tests/t0303-robustness.sh b/tests/t0303-robustness.sh index e1b7b3a..5cc7507 100755 --- a/tests/t0303-robustness.sh +++ b/tests/t0303-robustness.sh @@ -117,6 +117,16 @@ test_expect_success 'a filesystem readme from a scanned repository is refused' ' grep "Ignoring readme in " err ' +# A colon with nothing after it names no ref, so the setting is a path on +# disk and has to wait on trust like any other. +test_expect_success 'a readme ending in a colon is refused like a path on disk' ' + echo "outside the repository" >"scan/outside:" && + echo "readme=../outside:" >scan/b.git/cgitrc && + CGIT_CONFIG="$PWD/gitcfgrc" QUERY_STRING="url=b.git/about/" cgit >tmp 2>err && + grep "Ignoring readme in " err && + ! grep "outside the repository" tmp +' + # A repository served as html runs its pages on the site's own origin, so # the switch waits on trust like the keys that place markup. The warning has # to name the repository, which it did not for a key read from git config. -- cgit