Skip to content

Commit 0315203

Browse files
committed
Add base64 encoding for post body to bypass WAF restrictions and improve error handling in submission process
1 parent a6a5c8a commit 0315203

2 files changed

Lines changed: 59 additions & 25 deletions

File tree

main.jac

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1368,6 +1368,23 @@ def:priv is_http_url(u: str) -> bool {
13681368
}
13691369

13701370

1371+
def:priv decode_body_md(body_md: str, is_b64: bool) -> str {
1372+
# The /submit client base64-encodes the post body so its raw markdown — shell
1373+
# snippets like `curl … | bash`, URLs — doesn't trip the edge WAF's content
1374+
# rules, which 403 the walker request before it ever reaches us. Decode it back
1375+
# to real markdown here. Plain-text bodies (is_b64=False, e.g. an older client)
1376+
# pass through untouched.
1377+
if not is_b64 {
1378+
return body_md;
1379+
}
1380+
try {
1381+
return str(base64.b64decode(body_md.encode("ascii")).decode("utf-8"));
1382+
} except Exception {
1383+
return body_md;
1384+
}
1385+
}
1386+
1387+
13711388
def:priv build_post_markdown(
13721389
title: str, slug: str, categories: list, description: str,
13731390
author_id: str, body: str,
@@ -2467,6 +2484,7 @@ walker:priv SubmitPost {
24672484
has categories: list = [];
24682485
has description: str = "";
24692486
has body_md: str = "";
2487+
has body_b64: bool = False; # body_md is base64 (client encodes it past the WAF)
24702488
has author_bio: str = "";
24712489
has images: list = [];
24722490
has is_repost: bool = False;
@@ -2487,7 +2505,7 @@ walker:priv SubmitPost {
24872505
}
24882506
title = self.title.strip();
24892507
slug = self.slug.strip().lower();
2490-
body = self.body_md;
2508+
body = decode_body_md(self.body_md, self.body_b64);
24912509

24922510
if not title {
24932511
report {"ok": False, "error": "title is required"};
@@ -2615,6 +2633,7 @@ walker:priv UpdatePost {
26152633
has categories: list = [];
26162634
has description: str = "";
26172635
has body_md: str = "";
2636+
has body_b64: bool = False; # body_md is base64 (client encodes it past the WAF)
26182637
has images: list = [];
26192638
has is_repost: bool = False;
26202639
has repost_url: str = "";
@@ -2653,7 +2672,7 @@ walker:priv UpdatePost {
26532672

26542673
title = self.title.strip();
26552674
slug = self.slug.strip().lower(); # slug is fixed for an edit
2656-
body = self.body_md;
2675+
body = decode_body_md(self.body_md, self.body_b64);
26572676
if not title {
26582677
report {"ok": False, "error": "title is required"};
26592678
return;
@@ -2785,6 +2804,7 @@ walker:priv EditPublishedPost {
27852804
has categories: list = [];
27862805
has description: str = "";
27872806
has body_md: str = "";
2807+
has body_b64: bool = False; # body_md is base64 (client encodes it past the WAF)
27882808
has images: list = [];
27892809
has pr_number: int = 0; # >0 = continue the caller's own open edit PR
27902810
has is_repost: bool = False;
@@ -2805,6 +2825,7 @@ walker:priv EditPublishedPost {
28052825
return;
28062826
}
28072827
slug = self.slug.strip().lower();
2828+
body_md = decode_body_md(self.body_md, self.body_b64);
28082829
main_post = fetch_main_post(gh_token, slug);
28092830
if main_post is None {
28102831
report {"ok": False, "error": "no published post with that slug"};
@@ -2829,7 +2850,7 @@ walker:priv EditPublishedPost {
28292850
report {"ok": False, "error": "select at least one valid category"};
28302851
return;
28312852
}
2832-
if len(self.body_md.strip()) < 50 {
2853+
if len(body_md.strip()) < 50 {
28332854
report {"ok": False, "error": "post body is too short"};
28342855
return;
28352856
}
@@ -2848,7 +2869,7 @@ walker:priv EditPublishedPost {
28482869
author_override = TEAM_AUTHOR_ID;
28492870
}
28502871
post_md = build_edited_markdown(
2851-
original, title, cats, self.description.strip(), self.body_md,
2872+
original, title, cats, self.description.strip(), body_md,
28522873
self.is_repost, repost_url, self.repost_source.strip(), author_override,
28532874
);
28542875
# Write back to the post's ACTUAL file (filename != slug), so the PR edits

pages/submit.jac

Lines changed: 34 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ def install_submit_helpers() -> None {
3131
return;
3232
}
3333
s = document.createElement("script");
34-
s.textContent = "window.__readFileB64=function(file){return new Promise(function(res,rej){var r=new FileReader();r.onload=function(){var s=String(r.result||'');var i=s.indexOf('base64,');res({name:file.name,b64:i>=0?s.slice(i+7):''});};r.onerror=function(){rej(new Error('read failed'));};r.readAsDataURL(file);});};window.__loadScriptOnce=function(src){return new Promise(function(res,rej){var f=document.querySelector('script[data-so=\"'+src+'\"]');if(f){res();return;}var el=document.createElement('script');el.src=src;el.setAttribute('data-so',src);el.onload=function(){res();};el.onerror=function(){rej(new Error('load failed'));};document.head.appendChild(el);});};";
34+
s.textContent = "window.__readFileB64=function(file){return new Promise(function(res,rej){var r=new FileReader();r.onload=function(){var s=String(r.result||'');var i=s.indexOf('base64,');res({name:file.name,b64:i>=0?s.slice(i+7):''});};r.onerror=function(){rej(new Error('read failed'));};r.readAsDataURL(file);});};window.__loadScriptOnce=function(src){return new Promise(function(res,rej){var f=document.querySelector('script[data-so=\"'+src+'\"]');if(f){res();return;}var el=document.createElement('script');el.src=src;el.setAttribute('data-so',src);el.onload=function(){res();};el.onerror=function(){rej(new Error('load failed'));};document.head.appendChild(el);});};window.__b64EncodeUtf8=function(s){var b=new TextEncoder().encode(s);var bin='';for(var i=0;i<b.length;i++){bin+=String.fromCharCode(b[i]);}return btoa(bin);};";
3535
document.head.appendChild(s);
3636
}
3737

@@ -286,8 +286,8 @@ def:pub page() -> JsxElement {
286286
auth_error = str(data["error"]);
287287
}
288288
}
289-
} except Exception {
290-
auth_error = "Sign-in failed. Please try again.";
289+
} except Exception as e {
290+
auth_error = f"Sign-in failed: {e}";
291291
}
292292
# Strip the ?code= from the URL so a refresh doesn't re-exchange.
293293
try {
@@ -324,8 +324,8 @@ def:pub page() -> JsxElement {
324324
# public_repo: lets us fork the repo + open the PR AS the user.
325325
url = f"https://github.com/login/oauth/authorize?client_id={client_id}&redirect_uri={ru}&scope=public_repo";
326326
window.location.href = url;
327-
} except Exception {
328-
auth_error = "Could not start sign-in.";
327+
} except Exception as e {
328+
auth_error = f"Could not start sign-in: {e}";
329329
}
330330
}
331331

@@ -433,28 +433,41 @@ def:pub page() -> JsxElement {
433433
return;
434434
}
435435
submitting = True;
436+
# base64 the body so its raw markdown (shell snippets, URLs) gets past the
437+
# edge WAF, which otherwise 403s this walker request before it reaches the
438+
# server. The walker decodes it back when body_b64 is true.
439+
enc_body: str = body;
440+
body_is_b64: bool = False;
441+
install_submit_helpers();
442+
try {
443+
enc_body = str(window.__b64EncodeUtf8(body or ""));
444+
body_is_b64 = True;
445+
} except Exception {
446+
enc_body = body;
447+
body_is_b64 = False;
448+
}
436449
try {
437450
# Declared here so both branches assign the SAME binding — assigning
438451
# only inside the if/else block-scopes `res` and it's undefined below.
439452
res: any = None;
440453
if editing_published {
441454
res = root spawn EditPublishedPost(
442455
gh_enc=gh_enc, slug=slug, title=title, categories=cats,
443-
description=description, body_md=body, images=images, pr_number=edit_pr,
456+
description=description, body_md=enc_body, body_b64=body_is_b64, images=images, pr_number=edit_pr,
444457
is_repost=is_repost, repost_url=repost_url, repost_source=repost_source,
445458
as_team=as_team,
446459
);
447460
} elif edit_pr > 0 {
448461
res = root spawn UpdatePost(
449462
gh_enc=gh_enc, pr_number=edit_pr, title=title, slug=slug,
450-
categories=cats, description=description, body_md=body, images=images,
463+
categories=cats, description=description, body_md=enc_body, body_b64=body_is_b64, images=images,
451464
is_repost=is_repost, repost_url=repost_url, repost_source=repost_source,
452465
as_team=as_team,
453466
);
454467
} else {
455468
res = root spawn SubmitPost(
456469
gh_enc=gh_enc, title=title, slug=slug, categories=cats,
457-
description=description, body_md=body, author_bio=bio, images=images,
470+
description=description, body_md=enc_body, body_b64=body_is_b64, author_bio=bio, images=images,
458471
is_repost=is_repost, repost_url=repost_url, repost_source=repost_source,
459472
as_team=as_team,
460473
);
@@ -478,10 +491,10 @@ def:pub page() -> JsxElement {
478491
submit_error = str(data["error"]);
479492
}
480493
} else {
481-
submit_error = "No response from server.";
494+
submit_error = "No response from the server (the request may have been blocked before it reached the app).";
482495
}
483-
} except Exception {
484-
submit_error = "Network error while saving.";
496+
} except Exception as e {
497+
submit_error = f"Save failed: {e}";
485498
}
486499
submitting = False;
487500
}
@@ -520,8 +533,8 @@ def:pub page() -> JsxElement {
520533
subs_error = str(data["error"]);
521534
}
522535
}
523-
} except Exception {
524-
subs_error = "Could not load that submission for editing.";
536+
} except Exception as e {
537+
subs_error = f"Could not load that submission for editing: {e}";
525538
}
526539
subs_busy = "";
527540
}
@@ -542,8 +555,8 @@ def:pub page() -> JsxElement {
542555
subs_error = str(data["error"]);
543556
}
544557
}
545-
} except Exception {
546-
subs_error = "Could not create preview.";
558+
} except Exception as e {
559+
subs_error = f"Could not create preview: {e}";
547560
}
548561
subs_busy = "";
549562
}
@@ -576,8 +589,8 @@ def:pub page() -> JsxElement {
576589
subs_error = str(data["error"]);
577590
}
578591
}
579-
} except Exception {
580-
subs_error = "Could not load submissions.";
592+
} except Exception as e {
593+
subs_error = f"Could not load submissions: {e}";
581594
}
582595
subs_loading = False;
583596
}
@@ -595,8 +608,8 @@ def:pub page() -> JsxElement {
595608
editable_error = str(data["error"]);
596609
}
597610
}
598-
} except Exception {
599-
editable_error = "Could not load posts.";
611+
} except Exception as e {
612+
editable_error = f"Could not load posts: {e}";
600613
}
601614
editable_loading = False;
602615
}
@@ -628,8 +641,8 @@ def:pub page() -> JsxElement {
628641
editable_error = str(data["error"]);
629642
}
630643
}
631-
} except Exception {
632-
editable_error = "Could not load that post for editing.";
644+
} except Exception as e {
645+
editable_error = f"Could not load that post for editing: {e}";
633646
}
634647
editable_busy = "";
635648
}

0 commit comments

Comments
 (0)