Security hardening + gist UI fixes (#731)

* security: harden against XSS, ReDoS, path traversal, and injection

Defensive fixes across the server, storage, and viewer:

- XSS (CWE-79): sanitise rendered notebooks with DOMPurify, escape file
  names interpolated into AngularJS expressions (escapeNgString), set
  Mermaid securityLevel to 'strict', and stop urlRel2abs from returning
  javascript:/vbscript:/data:text/html URLs.
- Path traversal / zip-slip (CWE-22/23/24): validate URL-derived path
  components before they reach the storage layer (file/webview routes +
  StorageBase.assertSafePath) and sanitise zip entry names on extract for
  both the filesystem and S3 backends.
- ReDoS (CWE-1333): escape anonymization terms with catastrophic
  backtracking shapes to literals instead of compiling them as regexes.
- Secret hardening (CWE-798): require SESSION_SECRET / OAuth creds / DB
  password in production, random dev SESSION_SECRET fallback.
- Rate-limit spoofing (CWE-290): derive request.ip via trust-proxy hop
  count instead of the client-settable cf-connecting-ip header.
- NoSQL injection (CWE-943): allow only plain field paths as admin sort keys.
- Reject malformed streamer requests missing required string fields.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(ui): make gists reachable/visible and clarify the ZIP button

- Gist & PR routes now accept a trailing slash (/gist/:id/:path*?), so the
  dashboard links (which end in "/") resolve to the gist/PR page instead of
  falling through to the 404 route (#725).
- Gist viewer picks the default tab after content loads, defaulting to
  "files" when files exist; previously the ng-init ran before the async
  load and a files-only gist rendered blank under the hidden comments tab.
- Explorer toolbar: relabel ZIP to "Full repo ZIP" with a tooltip, and add
  tooltips to Raw/Download clarifying they apply to the current file (#721).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix: report SAML-enforced orgs clearly instead of "token expired"

When a repo's organization enforces SAML SSO, GitHub returns a 403 whose
message differs from the OAuth-App-restriction case. That 403 fell through
to the generic handler and surfaced as "token_expired", pushing users to
re-login when the real fix is authorizing their token for the org. Detect
the "SAML enforcement" message and raise a dedicated, actionable error
instead (#379, #550).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* security: catch nested quantified groups in ReDoS guard and backslash path traversal

- hasCatastrophicBacktracking now scans across nested parens ([\s\S]*?)
  so shapes like ((a+))+ are detected; comment reframed as a heuristic
  backstop rather than a proof.
- file route path-traversal check now rejects backslash separators and a
  leading backslash, covering Windows-style "..\" payloads (CWE-22/25).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(dev): track dev-proxy script, ignore .DS_Store and .claude/

scripts/dev-proxy.js is referenced by the "dev:ui" npm script but was
never committed, breaking the command on a fresh clone. Add it and
ignore local-only macOS/Claude Code files.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Thomas DurieuxandClaude Opus 4.8 authored and GitHub committed 2026-06-18 13:50:55 +02:00
1 parent bdfcc56d81
commit e4ffd74068
21 files changed
+484 -23

No files matched your search

+35 -5
View File
@@ -88,13 +88,13 @@ angular
controller: "claimController",
title: "Claim an anonymization – Anonymous GitHub",
})
.when("/pr/:pullRequestId", {
.when("/pr/:pullRequestId/:path*?", {
templateUrl: "/partials/pullRequest.htm",
controller: "pullRequestController",
title: "Anonymous pull request – Anonymous GitHub",
reloadOnUrl: false,
})
.when("/gist/:gistId", {
.when("/gist/:gistId/:path*?", {
templateUrl: "/partials/gist.htm",
controller: "gistController",
title: "Anonymous gist – Anonymous GitHub",
@@ -593,6 +593,23 @@ angular
return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
}
// Escape a value for safe interpolation into a single-quoted
// AngularJS expression string (e.g. ng-click="openFolder('...')")
// that itself sits inside a double-quoted HTML attribute which is
// later $compile()d. Backslash/quote are escaped at the Angular
// string level; &<>" are HTML-encoded for the attribute. Without
// this a file name like `');$emit(...)//` would break out of the
// expression string and execute (DOM XSS, CWE-79).
function escapeNgString(str) {
return String(str)
.replace(/\\/g, "\\\\")
.replace(/'/g, "\\'")
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;");
}
function buildSearchFilter() {
const results = $scope.searchResults;
if (!results || !results.length) return null;
@@ -675,11 +692,12 @@ angular
cssClasses.push("truncated");
}
const ngPath = escapeNgString(path);
output += `<li class="${cssClasses.join(
" "
)}" ng-class="{active: isActive('${path}'), open: ${filterSet ? "opens['" + path + "'] !== false" : "opens['" + path + "']"}}" title="${escapeHtml(sizeTitle)}">`;
)}" ng-class="{active: isActive('${ngPath}'), open: ${filterSet ? "opens['" + ngPath + "'] !== false" : "opens['" + ngPath + "']"}}" title="${escapeHtml(sizeTitle)}">`;
if (dir) {
output += `<a ng-click="openFolder('${path}', $event)"><span class="tree-toggle"></span><span class="tree-icon-folder"></span><span class="tree-name">${escapeHtml(name)}</span>`;
output += `<a ng-click="openFolder('${ngPath}', $event)"><span class="tree-toggle"></span><span class="tree-icon-folder"></span><span class="tree-name">${escapeHtml(name)}</span>`;
if (truncated) {
output += `<span class="truncated-warning" title="{{ 'WARNINGS.folder_truncated' | translate }}"><i class="fas fa-exclamation-triangle"></i></span>`;
}
@@ -911,7 +929,13 @@ angular
const notebook = nb.parse(json);
try {
$element.html("");
$element.append(notebook.render());
// notebook.render() turns notebook JSON (markdown cells, cell
// outputs) into HTML without sanitising it — a malicious
// notebook could embed <script>/onerror handlers that execute
// in the viewer's browser (XSS, CWE-79). Run the rendered
// output through DOMPurify before inserting it.
const rendered = notebook.render();
$element.html(DOMPurify.sanitize(rendered));
Prism.highlightAll();
} catch (error) {
$element.html("Unable to render the notebook.");
@@ -3118,6 +3142,12 @@ angular
$http.get(`/api/gist/${$scope.gistId}/content`).then(
(res) => {
$scope.details = res.data;
// Pick the default tab once the content is loaded. The ng-init in
// the template runs before this async response arrives (details is
// still null then), so without this a files-only gist would default
// to the hidden "comments" tab and render blank.
const hasFiles = res.data && res.data.files && res.data.files.length;
$scope.tabState = { active: hasFiles ? "files" : "comments" };
if (callback) callback(res.data);
},
(err) => {
+1 -1
View File
@@ -6,5 +6,5 @@ ${e}</blockquote>
`}table(e,t){return"<table>\n<thead>\n"+e+"</thead>\n"+(t=t&&`<tbody>${t}</tbody>`)+"</table>\n"}tablerow(e){return`<tr>
${e}</tr>
`}tablecell(e,t){var i=t.header?"th":"td";return(t.align?`<${i} align="${t.align}">`:`<${i}>`)+e+`</${i}>
`}strong(e){return`<strong>${e}</strong>`}em(e){return`<em>${e}</em>`}codespan(e){return`<code>${e}</code>`}br(){return"<br>"}del(e){return`<del>${e}</del>`}link(e,t,i){var n=c(e);if(null===n)return i;let s='<a href="'+(e=n)+'"';return t&&(s+=' title="'+t+'"'),s+=">"+i+"</a>"}image(e,t,i){var n=c(e);if(null===n)return i;let s=`<img src="${e=n}" alt="${i}"`;return t&&(s+=` title="${t}"`),s+=">"}text(e){return e}}class O{strong(e){return e}em(e){return e}codespan(e){return e}del(e){return e}html(e){return e}text(e){return e}link(e,t,i){return""+i}image(e,t,i){return""+i}br(){return""}}class D{options;renderer;textRenderer;constructor(e){this.options=e||t.defaults,this.options.renderer=this.options.renderer||new A,this.renderer=this.options.renderer,this.renderer.options=this.options,this.textRenderer=new O}static parse(e,t){return new D(t).parse(e)}static parseInline(e,t){return new D(t).parseInline(e)}parse(n,s=!0){let o="";for(let i=0;i<n.length;i++){var a=n[i];if(this.options.extensions&&this.options.extensions.renderers&&this.options.extensions.renderers[a.type]){const n=a,s=this.options.extensions.renderers[n.type].call({parser:this},n);if(!1!==s||!["space","hr","heading","code","table","blockquote","list","html","paragraph","text"].includes(n.type)){o+=s||"";continue}}switch(a.type){case"space":continue;case"hr":o+=this.renderer.hr();continue;case"heading":{const n=a;o+=this.renderer.heading(this.parseInline(n.tokens),n.depth,this.parseInline(n.tokens,this.textRenderer).replace(F,(e,t)=>"colon"===(t=t.toLowerCase())?":":"#"===t.charAt(0)?"x"===t.charAt(1)?String.fromCharCode(parseInt(t.substring(2),16)):String.fromCharCode(+t.substring(1)):""));continue}case"code":{const n=a;o+=this.renderer.code(n.text,n.lang,!!n.escaped);continue}case"table":{const n=a;let e="",t="";for(let e=0;e<n.header.length;e++)t+=this.renderer.tablecell(this.parseInline(n.header[e].tokens),{header:!0,align:n.align[e]});e+=this.renderer.tablerow(t);let i="";for(let e=0;e<n.rows.length;e++){const o=n.rows[e];t="";for(let e=0;e<o.length;e++)t+=this.renderer.tablecell(this.parseInline(o[e].tokens),{header:!1,align:n.align[e]});i+=this.renderer.tablerow(t)}o+=this.renderer.table(e,i);continue}case"blockquote":{const n=a,s=this.parse(n.tokens);o+=this.renderer.blockquote(s);continue}case"list":{const n=a,s=n.ordered,e=n.start,r=n.loose;let i="";for(let t=0;t<n.items.length;t++){const o=n.items[t],c=o.checked,u=o.task;let e="";if(o.task){const n=this.renderer.checkbox(!!c);r?0<o.tokens.length&&"paragraph"===o.tokens[0].type?(o.tokens[0].text=n+" "+o.tokens[0].text,o.tokens[0].tokens&&0<o.tokens[0].tokens.length&&"text"===o.tokens[0].tokens[0].type&&(o.tokens[0].tokens[0].text=n+" "+o.tokens[0].tokens[0].text)):o.tokens.unshift({type:"text",text:n+" "}):e+=n+" "}e+=this.parse(o.tokens,r),i+=this.renderer.listitem(e,u,!!c)}o+=this.renderer.list(i,s,e);continue}case"html":{const n=a;o+=this.renderer.html(n.text,n.block);continue}case"paragraph":{const n=a;o+=this.renderer.paragraph(this.parseInline(n.tokens));continue}case"text":{let e=a,t=e.tokens?this.parseInline(e.tokens):e.text;for(;i+1<n.length&&"text"===n[i+1].type;)e=n[++i],t+="\n"+(e.tokens?this.parseInline(e.tokens):e.text);o+=s?this.renderer.paragraph(t):t;continue}default:{const n='Token with "'+a.type+'" type was not found.';if(this.options.silent)return console.error(n),"";throw new Error(n)}}}return o}parseInline(t,i){i=i||this.renderer;let n="";for(let e=0;e<t.length;e++){var s=t[e];if(this.options.extensions&&this.options.extensions.renderers&&this.options.extensions.renderers[s.type]){const t=this.options.extensions.renderers[s.type].call({parser:this},s);if(!1!==t||!["escape","html","link","image","strong","em","codespan","br","del","text"].includes(s.type)){n+=t||"";continue}}switch(s.type){case"escape":{const t=s;n+=i.text(t.text);break}case"html":{const t=s;n+=i.html(t.text);break}case"link":{const t=s;n+=i.link(t.href,t.title,this.parseInline(t.tokens,i));break}case"image":{const t=s;n+=i.image(t.href,t.title,t.text);break}case"strong":{const t=s;n+=i.strong(this.parseInline(t.tokens,i));break}case"em":{const t=s;n+=i.em(this.parseInline(t.tokens,i));break}case"codespan":{const t=s;n+=i.codespan(t.text);break}case"br":n+=i.br();break;case"del":{const t=s;n+=i.del(this.parseInline(t.tokens,i));break}case"text":{const t=s;n+=i.text(t.text);break}default:{const t='Token with "'+s.type+'" type was not found.';if(this.options.silent)return console.error(t),"";throw new Error(t)}}}return n}}class N{options;constructor(e){this.options=e||t.defaults}static passThroughHooks=new Set(["preprocess","postprocess","processAllTokens"]);preprocess(e){return e}postprocess(e){return e}processAllTokens(e){return e}}class ce{defaults={async:!1,breaks:!1,extensions:null,gfm:!0,hooks:null,pedantic:!1,renderer:null,silent:!1,tokenizer:null,walkTokens:null};options=this.setOptions;parse=this.#e(T.lex,D.parse);parseInline=this.#e(T.lexInline,D.parseInline);Parser=D;Renderer=A;TextRenderer=OLine truncated
`}strong(e){return`<strong>${e}</strong>`}em(e){return`<em>${e}</em>`}codespan(e){return`<code>${e}</code>`}br(){return"<br>"}del(e){return`<del>${e}</del>`}link(e,t,i){var n=c(e);if(null===n)return i;let s='<a href="'+(e=n)+'"';return t&&(s+=' title="'+t+'"'),s+=">"+i+"</a>"}image(e,t,i){var n=c(e);if(null===n)return i;let s=`<img src="${e=n}" alt="${i}"`;return t&&(s+=` title="${t}"`),s+=">"}text(e){return e}}class O{strong(e){return e}em(e){return e}codespan(e){return e}del(e){return e}html(e){return e}text(e){return e}link(e,t,i){return""+i}image(e,t,i){return""+i}br(){return""}}class D{options;renderer;textRenderer;constructor(e){this.options=e||t.defaults,this.options.renderer=this.options.renderer||new A,this.renderer=this.options.renderer,this.renderer.options=this.options,this.textRenderer=new O}static parse(e,t){return new D(t).parse(e)}static parseInline(e,t){return new D(t).parseInline(e)}parse(n,s=!0){let o="";for(let i=0;i<n.length;i++){var a=n[i];if(this.options.extensions&&this.options.extensions.renderers&&this.options.extensions.renderers[a.type]){const n=a,s=this.options.extensions.renderers[n.type].call({parser:this},n);if(!1!==s||!["space","hr","heading","code","table","blockquote","list","html","paragraph","text"].includes(n.type)){o+=s||"";continue}}switch(a.type){case"space":continue;case"hr":o+=this.renderer.hr();continue;case"heading":{const n=a;o+=this.renderer.heading(this.parseInline(n.tokens),n.depth,this.parseInline(n.tokens,this.textRenderer).replace(F,(e,t)=>"colon"===(t=t.toLowerCase())?":":"#"===t.charAt(0)?"x"===t.charAt(1)?String.fromCharCode(parseInt(t.substring(2),16)):String.fromCharCode(+t.substring(1)):""));continue}case"code":{const n=a;o+=this.renderer.code(n.text,n.lang,!!n.escaped);continue}case"table":{const n=a;let e="",t="";for(let e=0;e<n.header.length;e++)t+=this.renderer.tablecell(this.parseInline(n.header[e].tokens),{header:!0,align:n.align[e]});e+=this.renderer.tablerow(t);let i="";for(let e=0;e<n.rows.length;e++){const o=n.rows[e];t="";for(let e=0;e<o.length;e++)t+=this.renderer.tablecell(this.parseInline(o[e].tokens),{header:!1,align:n.align[e]});i+=this.renderer.tablerow(t)}o+=this.renderer.table(e,i);continue}case"blockquote":{const n=a,s=this.parse(n.tokens);o+=this.renderer.blockquote(s);continue}case"list":{const n=a,s=n.ordered,e=n.start,r=n.loose;let i="";for(let t=0;t<n.items.length;t++){const o=n.items[t],c=o.checked,u=o.task;let e="";if(o.task){const n=this.renderer.checkbox(!!c);r?0<o.tokens.length&&"paragraph"===o.tokens[0].type?(o.tokens[0].text=n+" "+o.tokens[0].text,o.tokens[0].tokens&&0<o.tokens[0].tokens.length&&"text"===o.tokens[0].tokens[0].type&&(o.tokens[0].tokens[0].text=n+" "+o.tokens[0].tokens[0].text)):o.tokens.unshift({type:"text",text:n+" "}):e+=n+" "}e+=this.parse(o.tokens,r),i+=this.renderer.listitem(e,u,!!c)}o+=this.renderer.list(i,s,e);continue}case"html":{const n=a;o+=this.renderer.html(n.text,n.block);continue}case"paragraph":{const n=a;o+=this.renderer.paragraph(this.parseInline(n.tokens));continue}case"text":{let e=a,t=e.tokens?this.parseInline(e.tokens):e.text;for(;i+1<n.length&&"text"===n[i+1].type;)e=n[++i],t+="\n"+(e.tokens?this.parseInline(e.tokens):e.text);o+=s?this.renderer.paragraph(t):t;continue}default:{const n='Token with "'+a.type+'" type was not found.';if(this.options.silent)return console.error(n),"";throw new Error(n)}}}return o}parseInline(t,i){i=i||this.renderer;let n="";for(let e=0;e<t.length;e++){var s=t[e];if(this.options.extensions&&this.options.extensions.renderers&&this.options.extensions.renderers[s.type]){const t=this.options.extensions.renderers[s.type].call({parser:this},s);if(!1!==t||!["escape","html","link","image","strong","em","codespan","br","del","text"].includes(s.type)){n+=t||"";continue}}switch(s.type){case"escape":{const t=s;n+=i.text(t.text);break}case"html":{const t=s;n+=i.html(t.text);break}case"link":{const t=s;n+=i.link(t.href,t.title,this.parseInline(t.tokens,i));break}case"image":{const t=s;n+=i.image(t.href,t.title,t.text);break}case"strong":{const t=s;n+=i.strong(this.parseInline(t.tokens,i));break}case"em":{const t=s;n+=i.em(this.parseInline(t.tokens,i));break}case"codespan":{const t=s;n+=i.codespan(t.text);break}case"br":n+=i.br();break;case"del":{const t=s;n+=i.del(this.parseInline(t.tokens,i));break}case"text":{const t=s;n+=i.text(t.text);break}default:{const t='Token with "'+s.type+'" type was not found.';if(this.options.silent)return console.error(t),"";throw new Error(t)}}}return n}}class N{options;constructor(e){this.options=e||t.defaults}static passThroughHooks=new Set(["preprocess","postprocess","processAllTokens"]);preprocess(e){return e}postprocess(e){return e}processAllTokens(e){return e}}class ce{defaults={async:!1,breaks:!1,extensions:null,gfm:!0,hooks:null,pedantic:!1,renderer:null,silent:!1,tokenizer:null,walkTokens:null};options=this.setOptions;parse=this.#e(T.lex,D.parse);parseInline=this.#e(T.lexInline,D.parseInline);Parser=D;Renderer=A;TextRenderer=OLine truncated
`},marked.setOptions({renderer:i,pedantic:!1,gfm:!0,breaks:!1,sanitize:!1,smartLists:!0,smartypants:!1,xhtml:!1,headerIds:!1,katex:katex}),t&&marked.use(baseUrl(t)),marked.use(markedKatex({throwOnError:!1})),marked.use(markedMermaid()),DOMPurify.sanitize(marked.parse(e,{renderer:i}))}
+6 -1
View File
@@ -39,7 +39,12 @@ function markedMermaid(options) {
mermaid.initialize({
startOnLoad: false,
theme: 'default',
securityLevel: 'loose'
// 'strict' keeps Mermaid's own HTML/script sanitisation and
// disables click-binding callbacks. 'loose' (the previous
// value) lets diagram syntax inject clickable elements with
// JavaScript handlers that run in the viewer's browser
// (XSS, CWE-79).
securityLevel: 'strict'
});
window.mermaidInitialized = true;
}
+11 -2
View File
@@ -54,12 +54,21 @@ function urlRel2abs(
) {
/* Only accept commonly trusted protocols:
* Only data-image URLs are accepted, Exotic flavours (escaped slash,
* html-entitied characters) are not supported to keep the function fast */
* html-entitied characters) are not supported to keep the function fast.
* "javascript:" is intentionally NOT allowed — returning such a URL
* unchanged would let it reach an href attribute and execute on click
* (XSS, CWE-79). */
if (
/^(https?|file|ftps?|mailto|javascript|data:image\/[^;]{2,9};):/i.test(url)
/^(https?|file|ftps?|mailto|data:image\/[^;]{2,9};):/i.test(url)
) {
return url; //Url is already absolute
}
// Block any other explicit scheme (javascript:, vbscript:, data:text/html,
// …) so it can't slip through as an "absolute" URL via the relative-path
// handling below.
if (/^\s*[a-z][a-z0-9+.-]*:/i.test(url)) {
return "";
}
if (url.substring(0, 2) == "//") return location.protocol + url;
else if (url.charAt(0) == "/") return baseUrl + url;
+1 -1
View File
@@ -1 +1 @@
function markedMermaid(e){return{extensions:[{name:"mermaid",level:"block",start(e){return e.match(/^```mermaid/m)?.index},tokenizer(e,t){e=/^```mermaid\n([\s\S]*?)\n```/.exec(e);if(e)return{type:"mermaid",raw:e[0],text:e[1].trim()}},renderer(e){const t="mermaid-"+Math.random().toString(36).substr(2,9);e=`<div class="mermaid" id="${t}">${e.text}</div>`;return"undefined"==typeof mermaid&&"function"==typeof window.loadMermaid&&window.loadMermaid(),setTimeout(()=>{if("undefined"!=typeof mermaid){window.mermaidInitialized||(mermaid.initialize({startOnLoad:!1,theme:"default",securityLevel:"loose"}),window.mermaidInitialized=!0);try{var e=document.getElementById(t);e&&!e.getAttribute("data-processed")&&(mermaid.init(void 0,e),e.setAttribute("data-processed","true"))}catch(e){console.error("Mermaid rendering error:",e)}}},100),e}}]}}"undefined"==typeof PDFJS&&(("undefined"!=typeof window?window:this).PDFJS={}),function(){function r(e,t){return new n(this.slice(e,t))}function s(e,t){arguments.length<2&&(t=0);for(var n=0,i=e.length;n<i;++n,++t)this[t]=255&e[n]}function n(e){if("number"==typeof e)for(var t=[],n=0;n<e;++n)t[n]=0;else if("slice"in e)t=e.slice(0);else{t=[];for(var n=0,i=e.length;n<i;++n)t[n]=e[n]}return t.subarray=r,(t.buffer=t).byteLength=t.length,t.set=s,"object"==typeof e&&e.buffer&&(t.buffer=e.buffer),t}"undefined"!=typeof Uint8Array?(void 0===Uint8Array.prototype.subarray&&(Uint8Array.prototype.subarray=function(e,t){return new Uint8Array(this.slice(e,t))},Float32Array.prototype.subarray=function(e,t){return new Float32Array(this.slice(e,t))}),"undefined"==typeof Float64Array&&(window.Float64Array=Float32Array)):(window.Uint8Array=n,window.Uint32Array=n,window.Int32Array=n,window.Uint16Array=n,window.Float32Array=n,window.Float64Array=n)}(),window.URL||(window.URL=window.webkitURL),void 0===Object.create&&(Object.create=function(e){function t(){}return t.prototype=e,new t}),function(){if(void 0!==Object.defineProperty){var t=!0;try{Object.defineProperty(new Image,"id",{value:"test"});function e(){}e.prototype={get id(){}},Object.defineProperty(new e,"id",{value:"",configurable:!0,enumerable:!0,writable:!1})}catch(e){t=!1}if(t)return}Object.defineProperty=function(e,t,n){delete e[t],"get"in n&&e.__defineGetter__(t,n.get),"set"in n&&e.__defineSetter__(t,n.set),"value"in n&&(e.__defineSetter__(t,function(e){return this.__defineGetter__(t,function(){return e}),e}),e[t]=n.value)}}(),void 0===Object.keys&&(Object.keys=function(e){var t,n=[];for(t in e)e.hasOwnProperty(t)&&n.push(t);return n}),function(){var e;"undefined"==typeof FileReader||"readAsArrayBuffer"in(e=FileReader.prototype)||Object.defineProperty(e,"readAsArrayBuffer",{value:function(e){var t=new FileReader,s=this;t.onload=function(e){e=e.target.result;for(var t=new ArrayBuffer(e.length),n=new Uint8Array(t),i=0,r=e.length;i<r;i++)n[i]=e.charCodeAt(i);Object.defineProperty(s,"result",{value:t,enumerable:!0,writable:!1,configurable:!0}),(e=document.createEvent("HTMLEvents")).initEvent("load",!1,!1),s.dispatchEvent(e)},t.readAsBinaryString(e)}})}(),function(){var e=XMLHttpRequest.prototype;"overrideMimeType"in e||Object.defineProperty(e,"overrideMimeType",{value:function(){}}),"response"in e||"mozResponseArrayBuffer"in e||"mozResponse"in e||"responseArrayBuffer"in e||("undefined"!=typeof VBArray?Object.defineProperty(e,"response",{get:function(){return new Uint8Array(new VBArray(this.responseBody).toArray())}}):("function"==typeof e.overrideMimeType&&Object.defineProperty(e,"responseType",{set:function(){this.overrideMimeType("text/plain; charset=x-user-defined")}}),Object.defineProperty(e,"response",{get:function(){for(var e=this.responseText,t=e.length,n=new Uint8Array(t),i=0;i<t;++i)n[i]=255&e.charCodeAt(i);return n}})))}(),"btoa"in window||(window.btoa=function(e){for(var t="",n=0,i=e.length;n<i;n+=3)var r=255&e.charCodeAt(n),s=255&e.charCodeAt(n+1),o=255&e.charCodeAt(n+2),a=(3&r)<<4|s>>4,s=n+1<i?(15&s)<<2|o>>6:64,o=n+2<i?63&o:64,t=t+("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".charAt(r>>2)+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".charAt(a)+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".charAt(s)+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".charAt(o));return t}),"atob"in window||(window.atob=function(e){if(1==(e=e.replace(/=+$/,"")).length%4)throw Error("bad atob input");for(var t,n,i=0,r=0,s="";n=e.charAt(r++);~n&&(t=i%4?64*t+n:n,i++%4)&&(s+=String.fromCharCode(255&t>>(-2*i&6))))n="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".indexOf(n);return s}),void 0===Function.prototype.bind&&(Function.prototype.bind=function(t){var n=this,i=Array.prototype.slice.call(arguments,1);return function(){var e=Array.prototype.concat.apply(i,arguments);return n.apply(t,e)}}),"dataset"in document.createElement("div")||Object.defineProperty(HTMLElement.prototype,"dataset",{get:function(){if(this._dataset)return this._dataset;for(var e={},t=0,n=tLine truncated
function markedMermaid(e){return{extensions:[{name:"mermaid",level:"block",start(e){return e.match(/^```mermaid/m)?.index},tokenizer(e,t){e=/^```mermaid\n([\s\S]*?)\n```/.exec(e);if(e)return{type:"mermaid",raw:e[0],text:e[1].trim()}},renderer(e){const t="mermaid-"+Math.random().toString(36).substr(2,9);e=`<div class="mermaid" id="${t}">${e.text}</div>`;return"undefined"==typeof mermaid&&"function"==typeof window.loadMermaid&&window.loadMermaid(),setTimeout(()=>{if("undefined"!=typeof mermaid){window.mermaidInitialized||(mermaid.initialize({startOnLoad:!1,theme:"default",securityLevel:"strict"}),window.mermaidInitialized=!0);try{var e=document.getElementById(t);e&&!e.getAttribute("data-processed")&&(mermaid.init(void 0,e),e.setAttribute("data-processed","true"))}catch(e){console.error("Mermaid rendering error:",e)}}},100),e}}]}}"undefined"==typeof PDFJS&&(("undefined"!=typeof window?window:this).PDFJS={}),function(){function r(e,t){return new n(this.slice(e,t))}function s(e,t){arguments.length<2&&(t=0);for(var n=0,i=e.length;n<i;++n,++t)this[t]=255&e[n]}function n(e){if("number"==typeof e)for(var t=[],n=0;n<e;++n)t[n]=0;else if("slice"in e)t=e.slice(0);else{t=[];for(var n=0,i=e.length;n<i;++n)t[n]=e[n]}return t.subarray=r,(t.buffer=t).byteLength=t.length,t.set=s,"object"==typeof e&&e.buffer&&(t.buffer=e.buffer),t}"undefined"!=typeof Uint8Array?(void 0===Uint8Array.prototype.subarray&&(Uint8Array.prototype.subarray=function(e,t){return new Uint8Array(this.slice(e,t))},Float32Array.prototype.subarray=function(e,t){return new Float32Array(this.slice(e,t))}),"undefined"==typeof Float64Array&&(window.Float64Array=Float32Array)):(window.Uint8Array=n,window.Uint32Array=n,window.Int32Array=n,window.Uint16Array=n,window.Float32Array=n,window.Float64Array=n)}(),window.URL||(window.URL=window.webkitURL),void 0===Object.create&&(Object.create=function(e){function t(){}return t.prototype=e,new t}),function(){if(void 0!==Object.defineProperty){var t=!0;try{Object.defineProperty(new Image,"id",{value:"test"});function e(){}e.prototype={get id(){}},Object.defineProperty(new e,"id",{value:"",configurable:!0,enumerable:!0,writable:!1})}catch(e){t=!1}if(t)return}Object.defineProperty=function(e,t,n){delete e[t],"get"in n&&e.__defineGetter__(t,n.get),"set"in n&&e.__defineSetter__(t,n.set),"value"in n&&(e.__defineSetter__(t,function(e){return this.__defineGetter__(t,function(){return e}),e}),e[t]=n.value)}}(),void 0===Object.keys&&(Object.keys=function(e){var t,n=[];for(t in e)e.hasOwnProperty(t)&&n.push(t);return n}),function(){var e;"undefined"==typeof FileReader||"readAsArrayBuffer"in(e=FileReader.prototype)||Object.defineProperty(e,"readAsArrayBuffer",{value:function(e){var t=new FileReader,s=this;t.onload=function(e){e=e.target.result;for(var t=new ArrayBuffer(e.length),n=new Uint8Array(t),i=0,r=e.length;i<r;i++)n[i]=e.charCodeAt(i);Object.defineProperty(s,"result",{value:t,enumerable:!0,writable:!1,configurable:!0}),(e=document.createEvent("HTMLEvents")).initEvent("load",!1,!1),s.dispatchEvent(e)},t.readAsBinaryString(e)}})}(),function(){var e=XMLHttpRequest.prototype;"overrideMimeType"in e||Object.defineProperty(e,"overrideMimeType",{value:function(){}}),"response"in e||"mozResponseArrayBuffer"in e||"mozResponse"in e||"responseArrayBuffer"in e||("undefined"!=typeof VBArray?Object.defineProperty(e,"response",{get:function(){return new Uint8Array(new VBArray(this.responseBody).toArray())}}):("function"==typeof e.overrideMimeType&&Object.defineProperty(e,"responseType",{set:function(){this.overrideMimeType("text/plain; charset=x-user-defined")}}),Object.defineProperty(e,"response",{get:function(){for(var e=this.responseText,t=e.length,n=new Uint8Array(t),i=0;i<t;++i)n[i]=255&e.charCodeAt(i);return n}})))}(),"btoa"in window||(window.btoa=function(e){for(var t="",n=0,i=e.length;n<i;n+=3)var r=255&e.charCodeAt(n),s=255&e.charCodeAt(n+1),o=255&e.charCodeAt(n+2),a=(3&r)<<4|s>>4,s=n+1<i?(15&s)<<2|o>>6:64,o=n+2<i?63&o:64,t=t+("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".charAt(r>>2)+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".charAt(a)+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".charAt(s)+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".charAt(o));return t}),"atob"in window||(window.atob=function(e){if(1==(e=e.replace(/=+$/,"")).length%4)throw Error("bad atob input");for(var t,n,i=0,r=0,s="";n=e.charAt(r++);~n&&(t=i%4?64*t+n:n,i++%4)&&(s+=String.fromCharCode(255&t>>(-2*i&6))))n="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".indexOf(n);return s}),void 0===Function.prototype.bind&&(Function.prototype.bind=function(t){var n=this,i=Array.prototype.slice.call(arguments,1);return function(){var e=Array.prototype.concat.apply(i,arguments);return n.apply(t,e)}}),"dataset"in document.createElement("div")||Object.defineProperty(HTMLElement.prototype,"dataset",{get:function(){if(this._dataset)return this._dataset;for(var e={},t=0,n=Line truncated