Skip to content

Commit d91b2e0

Browse files
committed
Harden sanitizeUrl against percent-encoded bypasses and cover i18n trust pages
Decode percent-encoded characters and strip ASCII control characters before protocol checking to block encoded dangerous protocols like %6a%61%76%61%73%63%72%69%70%74 (javascript). Add sanitizeUrl to repo.url hrefs in zh-cn, ko, and ja trust pages so all data-driven URLs are consistently sanitized. Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
1 parent 64efda4 commit d91b2e0

4 files changed

Lines changed: 19 additions & 4 deletions

File tree

src/lib/sanitize-url.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,19 @@ const allowedProtocols = new Set(['http:', 'https:', 'mailto:']);
77
*/
88
export function sanitizeUrl(url: string): string {
99
try {
10-
const parsed = new URL(url, 'https://placeholder.invalid');
10+
// Decode percent-encoded characters to catch encoded dangerous protocols
11+
// like %6a%61%76%61%73%63%72%69%70%74 (javascript) or %64%61%74%61 (data)
12+
let decoded = url;
13+
try {
14+
decoded = decodeURIComponent(url);
15+
} catch {
16+
// Malformed percent-encoding is fine; continue with the original
17+
}
18+
19+
// Strip ASCII control characters that browsers silently remove
20+
const cleaned = decoded.replace(/[\x00-\x1f\x7f]/g, '');
21+
22+
const parsed = new URL(cleaned, 'https://placeholder.invalid');
1123
return allowedProtocols.has(parsed.protocol) ? url : '#';
1224
} catch {
1325
return '#';

src/pages/trust/ja/index.astro

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
---
22
import TrustTopbar from '../../../components/TrustTopbar.astro';
33
import t from '../../../i18n/ja/trust.json';
4+
import { sanitizeUrl } from '../../../lib/sanitize-url';
45
56
const isSubdomain = Astro.url.hostname === 'trust.openclaw.ai';
67
const trustHref = isSubdomain ? '/ja' : '/trust/ja';
@@ -595,7 +596,7 @@ const langLinks = {
595596
<p>{t.phase4.reportBox.intro}</p>
596597
<ul style="margin-top: 12px; margin-bottom: 12px;">
597598
{t.phase4.reportBox.repos.map(repo => (
598-
<li><strong>{repo.label}</strong> - <a href={repo.url}>{repo.name}</a></li>
599+
<li><strong>{repo.label}</strong> - <a href={sanitizeUrl(repo.url)}>{repo.name}</a></li>
599600
))}
600601
</ul>
601602
<p set:html={t.phase4.reportBox.fallback} />

src/pages/trust/ko/index.astro

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
---
22
import TrustTopbar from '../../../components/TrustTopbar.astro';
33
import t from '../../../i18n/ko/trust.json';
4+
import { sanitizeUrl } from '../../../lib/sanitize-url';
45
56
const isSubdomain = Astro.url.hostname === 'trust.openclaw.ai';
67
const trustHref = isSubdomain ? '/ko' : '/trust/ko';
@@ -602,7 +603,7 @@ const langLinks = {
602603
<p>{t.phase4.reportBox.intro}</p>
603604
<ul style="margin-top: 12px; margin-bottom: 12px;">
604605
{t.phase4.reportBox.repos.map((repo: { label: string; url: string; name: string }) => (
605-
<li><strong>{repo.label}</strong> - <a href={repo.url}>{repo.name}</a></li>
606+
<li><strong>{repo.label}</strong> - <a href={sanitizeUrl(repo.url)}>{repo.name}</a></li>
606607
))}
607608
</ul>
608609
<p set:html={t.phase4.reportBox.fallback} />

src/pages/trust/zh-cn/index.astro

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
---
22
import TrustTopbar from '../../../components/TrustTopbar.astro';
33
import t from '../../../i18n/zh-cn/trust.json';
4+
import { sanitizeUrl } from '../../../lib/sanitize-url';
45
56
const isSubdomain = Astro.url.hostname === 'trust.openclaw.ai';
67
const trustHref = isSubdomain ? '/zh-cn' : '/trust/zh-cn';
@@ -595,7 +596,7 @@ const langLinks = {
595596
<p>{t.phase4.reportBox.intro}</p>
596597
<ul style="margin-top: 12px; margin-bottom: 12px;">
597598
{t.phase4.reportBox.repos.map(repo => (
598-
<li><strong>{repo.label}</strong> - <a href={repo.url}>{repo.name}</a></li>
599+
<li><strong>{repo.label}</strong> - <a href={sanitizeUrl(repo.url)}>{repo.name}</a></li>
599600
))}
600601
</ul>
601602
<p set:html={t.phase4.reportBox.fallback} />

0 commit comments

Comments
 (0)