diff options
| author | Yuriy Andamasov <yuriy@vyos.io> | 2026-07-28 05:36:57 +0300 |
|---|---|---|
| committer | Yuriy Andamasov <yuriy@vyos.io> | 2026-07-28 05:36:57 +0300 |
| commit | 0c9348035b583f3cd5fca72937a7ec1a61d7df45 (patch) | |
| tree | 6113a587f5dbbe4b719ad2bbcd940f29395d89c3 /docs/_static/js | |
| parent | cfca705f325a073c00e221a080eff968112d7653 (diff) | |
| download | vyos-documentation-claude/vyos-docs-security-remediate-2cff61.tar.gz vyos-documentation-claude/vyos-docs-security-remediate-2cff61.zip | |
security: normalize percent escapes per run, not per segmentclaude/vyos-docs-security-remediate-2cff61
Round-2 adversarial finding (Codex, medium): whole-segment decode meant
one malformed escape (a%20b%zz) threw for the segment and double-encoded
the valid escapes beside it. encodeSegment now decodes+re-encodes each
well-formed %HH run independently; literal spans (including a bare '%')
always pass through encodeURIComponent, so taint neutralization holds
unconditionally; a run decoding to invalid UTF-8 stays verbatim (already
pure %HH text).
workers suite 108/108 (+2 discriminating regression tests).
🤖 Generated by [robots](https://vyos.io)
Diffstat (limited to 'docs/_static/js')
| -rw-r--r-- | docs/_static/js/version-picker.js | 34 |
1 files changed, 23 insertions, 11 deletions
diff --git a/docs/_static/js/version-picker.js b/docs/_static/js/version-picker.js index d6faa93d..84f68fd6 100644 --- a/docs/_static/js/version-picker.js +++ b/docs/_static/js/version-picker.js @@ -35,21 +35,33 @@ return null; } + /* Normalize one path segment: each well-formed %HH run is decoded and re-encoded on its + * own — per run, not per segment, so one malformed escape cannot double-encode the valid + * ones beside it (location.pathname hands escapes back verbatim, and blind re-encoding + * would turn %2E into %252E and miss on the HEAD probe). Literal spans, including a bare + * '%', always go through encodeURIComponent, so taint neutralization holds unconditionally; + * a run decoding to invalid UTF-8 is kept verbatim, being already pure %HH text. + * Decoding cannot resurrect a dot-segment: the URL parser resolves '.' / '..' and their + * percent-encoded forms during navigation, so pathname never presents them. */ + function encodeSegment(seg) { + var out = ''; + var re = /(?:%[0-9A-Fa-f]{2})+/g; + var last = 0; + var m; + while ((m = re.exec(seg))) { + out += encodeURIComponent(seg.slice(last, m.index)); + try { out += encodeURIComponent(decodeURIComponent(m[0])); } catch (e) { out += m[0]; } + last = m.index + m[0].length; + } + return out + encodeURIComponent(seg.slice(last)); + } + /* Percent-encode a multi-segment path one segment at a time, so the '/' separators * survive. Real docs paths are plain ASCII sphinx slugs (letters/digits/-/_/./html), * where this is a no-op — it exists to keep DOM-derived text (location.pathname, - * select.value) from reaching a location.href sink unescaped. Each segment is decoded - * first, because location.pathname hands back well-formed escapes verbatim: without it - * a deep link like /index%2Ehtml would re-encode to %252E and miss on the HEAD probe. - * A malformed escape ('%zz') throws, and the raw segment is encoded defensively. - * Decoding cannot resurrect a dot-segment: the URL parser resolves '.' / '..' and their - * percent-encoded forms during navigation, so pathname never presents them. */ + * select.value) from reaching a location.href sink unescaped. */ function encodePath(rest) { - return rest.split('/').map(function (seg) { - var decoded; - try { decoded = decodeURIComponent(seg); } catch (e) { decoded = seg; } - return encodeURIComponent(decoded); - }).join('/'); + return rest.split('/').map(function (seg) { return encodeSegment(seg); }).join('/'); } function targetUrlFor(loc, targetSlug) { |
