summaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
authorYuriy Andamasov <yuriy@vyos.io>2026-07-28 05:36:57 +0300
committerYuriy Andamasov <yuriy@vyos.io>2026-07-28 05:36:57 +0300
commit0c9348035b583f3cd5fca72937a7ec1a61d7df45 (patch)
tree6113a587f5dbbe4b719ad2bbcd940f29395d89c3 /docs
parentcfca705f325a073c00e221a080eff968112d7653 (diff)
downloadvyos-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')
-rw-r--r--docs/_static/js/version-picker.js34
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) {