diff --git a/.gitignore b/.gitignore index 038e279..b45f41f 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,12 @@ build repo/ db_backups message.txt + +# macOS +.DS_Store + +# Local Claude Code settings +.claude/ # Created by https://www.gitignore.io/api/node # Edit at https://www.gitignore.io/?templates=node diff --git a/public/asset-manifest.json b/public/asset-manifest.json index 777e8ec..b0a3ce9 100644 --- a/public/asset-manifest.json +++ b/public/asset-manifest.json @@ -1,6 +1,6 @@ { - "core.min.js": "core.3db744fc07.min.js", - "vendor.min.js": "vendor.09f02f70c0.min.js", + "core.min.js": "core.6332b3c288.min.js", + "vendor.min.js": "vendor.d7d972f465.min.js", "mermaid.min.js": "mermaid.f848a72d16.min.js", "all.min.css": "all.1a9babcb45.min.css" } \ No newline at end of file diff --git a/public/i18n/locale-en.json b/public/i18n/locale-en.json index 8041325..1ff3bf1 100644 --- a/public/i18n/locale-en.json +++ b/public/i18n/locale-en.json @@ -10,6 +10,7 @@ "user_not_found": "The requested user could not be found.", "user_banned": "Your account has been banned. Contact the admin for more information.", "repo_access_limited": "GitHub blocked access because the repository's organization restricts third-party OAuth apps. Ask an org owner to approve Anonymous GitHub under Settings → Third-party Access → OAuth app policy, or anonymize a personal fork instead.", + "repo_saml_enforcement": "The repository's organization enforces SAML single sign-on. Authorize your token for that organization (GitHub → Settings → Applications, or re-run the org's SSO sign-in), then retry. Alternatively, anonymize a personal fork.", "repo_not_found": "The repository was not found on GitHub. Check the URL and spelling, make sure you are signed in to the account that can see it, and confirm the repo isn't hidden under an org that restricts third-party app access.", "repo_empty": "The selected branch has no commits on GitHub. Push at least one commit, or pick a different branch, then retry.", "repo_not_accessible": "Anonymous GitHub cannot access this repository. Verify the repository exists and that Anonymous GitHub has been authorized for the owning organization.", @@ -52,6 +53,7 @@ "path_not_specified": "A file path must be specified.", "path_not_defined": "The file path has not been resolved yet.", "invalid_file_path": "The requested file path is not valid.", + "invalid_request": "The request is missing required fields or is malformed.", "no_file_selected": "Please select a file.", "file_not_found": "The requested file is not found.", "file_not_accessible": "The requested file is not accessible.", diff --git a/public/partials/explorer.htm b/public/partials/explorer.htm index 9947490..1aabcde 100644 --- a/public/partials/explorer.htm +++ b/public/partials/explorer.htm @@ -88,7 +88,8 @@ ng-href="{{url}}" target="__self" class="btn btn-sm" - aria-label="Raw" + aria-label="View raw current file" + title="View the raw content of the current file" > Raw Download ZIP Full repo ZIP /g, ">").replace(/"/g, """); } + // 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, "&") + .replace(//g, ">") + .replace(/"/g, """); + } + 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 += `
  • `; + )}" ng-class="{active: isActive('${ngPath}'), open: ${filterSet ? "opens['" + ngPath + "'] !== false" : "opens['" + ngPath + "']"}}" title="${escapeHtml(sizeTitle)}">`; if (dir) { - output += `${escapeHtml(name)}`; + output += `${escapeHtml(name)}`; if (truncated) { output += ``; } @@ -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