mirror of
https://github.com/fosrl/pangolin.git
synced 2026-09-22 05:49:05 +02:00
fix(rules): decode percent-encoded PATH rule patterns before matching
isPathAllowed decodes the incoming request path (and Badger already sends Go's decoded req.URL.Path), but compared it against the rule pattern as raw text. isValidUrlGlobPattern rejects raw spaces and non-ASCII and only accepts them percent-encoded, so a PATH rule such as `/my%20docs/*` or `/caf%C3%A9` was stored as `my%20docs` / `caf%C3%A9` and compared against `my docs` / `café`, and could therefore never match any request. Run the pattern through the same decodeAndResolvePath normalisation as the request path so both sides are compared in decoded form. Claude-Session: https://claude.ai/code/session_0134ujLF81GyXsCByibLcYsz
This commit is contained in:
@@ -20,6 +20,11 @@ function getSegmentRegex(patternPart: string): RegExp {
|
||||
// resolves `.` / `..` segments, so a request like `/public%2F..%2Fadmin/`
|
||||
// or `/public/../admin/` is matched as `/admin/`, not as a literal segment
|
||||
// or a wildcard-swallowed sequence under `/public/*`.
|
||||
//
|
||||
// Applied to both the request path and the rule pattern: the pattern
|
||||
// validator only accepts spaces / non-ASCII in percent-encoded form, so a
|
||||
// rule like `/my%20docs/*` must be compared against the decoded segment
|
||||
// `my docs`, not the literal text `my%20docs`.
|
||||
function decodeAndResolvePath(p: string): string[] {
|
||||
const rawParts = p.split("/").filter(Boolean);
|
||||
|
||||
@@ -48,7 +53,7 @@ function decodeAndResolvePath(p: string): string[] {
|
||||
}
|
||||
|
||||
export function isPathAllowed(pattern: string, path: string): boolean {
|
||||
const patternParts = pattern.split("/").filter(Boolean);
|
||||
const patternParts = decodeAndResolvePath(pattern);
|
||||
const pathParts = decodeAndResolvePath(path);
|
||||
|
||||
function matchSegments(
|
||||
|
||||
@@ -386,6 +386,38 @@ function runSpecialCharacterTests() {
|
||||
console.log("All special character tests passed!");
|
||||
}
|
||||
|
||||
function runEncodedPatternTests() {
|
||||
console.log("\nRunning percent-encoded pattern tests...");
|
||||
|
||||
// isValidUrlGlobPattern accepts percent-encoded sequences and rejects
|
||||
// raw spaces / non-ASCII, so `%20` and `%C3%A9` are the only way to write
|
||||
// a PATH rule for such a path. Badger sends the request path already
|
||||
// decoded (Go's req.URL.Path), and isPathAllowed decodes it again, so the
|
||||
// rule pattern must be decoded the same way or it can never match.
|
||||
assertEquals(
|
||||
isPathAllowed("/my%20docs/*", "/my docs/report.pdf"),
|
||||
true,
|
||||
"Percent-encoded space in pattern should match decoded request path"
|
||||
);
|
||||
assertEquals(
|
||||
isPathAllowed("/my%20docs/*", "/my%20docs/report.pdf"),
|
||||
true,
|
||||
"Percent-encoded space in pattern should match raw-encoded request path"
|
||||
);
|
||||
assertEquals(
|
||||
isPathAllowed("/caf%C3%A9", "/café"),
|
||||
true,
|
||||
"Percent-encoded UTF-8 in pattern should match decoded request path"
|
||||
);
|
||||
assertEquals(
|
||||
isPathAllowed("/my%20docs/*", "/my-docs/report.pdf"),
|
||||
false,
|
||||
"Decoded pattern must still reject a different path"
|
||||
);
|
||||
|
||||
console.log("All percent-encoded pattern tests passed!");
|
||||
}
|
||||
|
||||
function runRegionTests() {
|
||||
console.log("\nRunning isIpInRegion tests...");
|
||||
|
||||
@@ -446,6 +478,7 @@ function runRegionTests() {
|
||||
try {
|
||||
runTests();
|
||||
runSpecialCharacterTests();
|
||||
runEncodedPatternTests();
|
||||
runRegionTests();
|
||||
console.log("\n✅ All tests passed!");
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user