Files
anonymous_github/public/partials/explorer.htm
T
Thomas Durieux e4ffd74068 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>
2026-06-18 13:50:55 +02:00

128 lines
4.5 KiB
HTML

<div class="explorer-page" ng-init="sidebarCollapsed = window.matchMedia && window.matchMedia('(max-width: 767px)').matches">
<button
class="sidebar-toggle"
ng-show="files.length"
ng-click="sidebarCollapsed = !sidebarCollapsed"
aria-label="{{sidebarCollapsed ? 'Show files' : 'Hide files'}}"
>
<i class="fas" ng-class="sidebarCollapsed ? 'fa-folder-open' : 'fa-times'"></i>
<span ng-bind="sidebarCollapsed ? 'Files' : 'Close'"></span>
</button>
<div class="leftCol" ng-show="files.length" ng-class="{'collapsed': sidebarCollapsed}">
<div class="leftCol-head">
<span class="leftCol-eyebrow">Files</span>
<button class="leftCol-close" ng-click="sidebarCollapsed = true" aria-label="Close files">
<i class="fas fa-times"></i>
</button>
</div>
<div class="leftCol-search">
<div class="tree-search-box">
<i class="fas tree-search-icon" ng-class="fileSearchLoading ? 'fa-spinner fa-spin' : 'fa-search'"></i>
<input
type="text"
class="tree-search-input"
placeholder="Search files"
ng-model="fileSearchQuery"
ng-model-options="{ debounce: 300 }"
ng-change="onFileSearchChange()"
aria-label="Search files"
/>
<kbd class="tree-search-kbd" ng-hide="fileSearchQuery">{{isMac ? '⌘' : 'Ctrl+'}}K</kbd>
<button
class="tree-search-clear"
ng-show="fileSearchQuery"
ng-click="fileSearchQuery = ''; onFileSearchChange()"
aria-label="Clear search"
>&times;</button>
</div>
</div>
<div class="leftCol-project-header">
<span class="project-name" ng-bind="repoId"></span>
<span class="project-file-count">{{fileCounts[''] || files.length}} files</span>
</div>
<div class="leftCol-body">
<div
ng-if="options.truncatedFolders.length > 0"
class="paper-inline-warning"
role="alert"
>
<i class="fas fa-exclamation-triangle"></i>
{{ 'WARNINGS.repo_truncated' | translate }}
</div>
<tree class="files" file="files" search-query="fileSearchQuery" search-results="fileSearchResults"></tree>
</div>
<div class="leftCol-foot">
<span
class="last-update"
data-toggle="tooltip"
data-placement="top"
title="{{options.lastUpdateDate}}"
>
Updated {{options.lastUpdateDate|date}}
</span>
</div>
</div>
<div
class="leftCol-backdrop"
ng-show="files.length && !sidebarCollapsed"
ng-click="sidebarCollapsed = true"
></div>
<div class="explorer-main">
<div class="status-bar">
<ol class="breadcrumb paths" aria-label="Path">
<li class="breadcrumb-item" ng-repeat="p in paths" ng-bind="p"></li>
</ol>
<div class="status-bar-actions">
<a
ng-if="options.isAdmin || options.isOwner"
ng-href="/anonymize/{{repoId}}"
class="btn btn-sm"
aria-label="Edit"
><i class="far fa-edit"></i><span class="d-none d-md-inline"> Edit</span></a
>
<a
ng-show="content != null"
ng-href="{{url}}"
target="__self"
class="btn btn-sm"
aria-label="View raw current file"
title="View the raw content of the current file"
><i class="fas fa-file-alt"></i><span class="d-none d-md-inline"> Raw</span></a
>
<a
ng-show="content != null"
ng-href="{{url}}&download=true"
target="__self"
class="btn btn-sm"
aria-label="Download current file"
title="Download the current file"
><i class="fas fa-download"></i><span class="d-none d-md-inline"> Download</span></a
>
<a
ng-if="options.download"
ng-href="/api/repo/{{repoId}}/zip"
target="__self"
class="btn btn-sm"
aria-label="Download full repository as ZIP"
title="Download the full repository as a ZIP archive"
><i class="fas fa-file-archive"></i><span class="d-none d-md-inline"> Full repo ZIP</span></a
>
<a
ng-if="options.hasWebsite"
ng-href="/w/{{repoId}}/"
target="__self"
class="btn btn-sm"
aria-label="Website"
><i class="fas fa-globe"></i><span class="d-none d-md-inline"> Website</span></a
>
</div>
</div>
<div class="explorer-content">
<ng-include src="'./partials/pageView.htm'"></ng-include>
</div>
</div>
</div>