SECURITY.md (9353B)
1 # Security review
2
3 Reviewed locally on 2026-11-12. This is a static Git browser, not a Git hosting
4 service. No changes were published during this review.
5
6 ## Trust boundaries
7
8 Repository contents, Markdown, commit messages, author names, references, filenames,
9 and GitHub metadata are treated as untrusted data. Repository code, hooks, external
10 diff programs, text conversion commands, and submodule checkouts are not needed to
11 generate the site.
12
13 The operator, build scripts, process environment, Git configuration, cached bare
14 repositories, and selected output root are trusted. A user able to replace these
15 inputs or concurrently change filesystem paths has the builder's privileges. Path
16 checks and atomic file replacement prevent ordinary preexisting symlink attacks;
17 they are not an OS sandbox against a malicious process running as the same user.
18 Do not point the generator at an attacker-supplied `.git` directory with arbitrary
19 configuration. Fetch repository objects into a locally controlled bare repository.
20
21 The generator and its C Markdown parser should run as an unprivileged account.
22 `scripts/build.py` applies per-generator address-space and CPU limits and kills the
23 whole process group after a wall-clock timeout. Direct binary invocation does not
24 apply these OS limits; use the build script for the supported workflow.
25
26 ## Findings and changes
27
28 | Finding | Evidence and impact | Resolution |
29 | --- | --- | --- |
30 | Stored XSS in rendered READMEs | Raw scripts, event attributes, and JavaScript links survived rendering. Visiting a generated page could run repository-controlled script. | Sanitize with pinned Ammonia 4.2.4 and an explicit tag/attribute/URL allowlist. Reject unsafe metadata links. Add a script-blocking CSP to all HTML and preview responses. |
31 | Local file disclosure through metadata | Worktree `description` and `url` files could be symlinks to files outside the repository. Their contents appeared in generated pages. | Read only regular, bounded files in the resolved Git metadata directory. Never read tracked worktree metadata overrides. |
32 | Preview file disclosure and rebinding exposure | A symlink under the served root exposed an external file; an arbitrary Host header received HTTP 200. | Reject symlinks/junctions, traversal, alternate streams, and nonregular files. Validate Host, Origin, and Fetch Metadata. Bind to loopback and disable directory listings. |
33 | Output symlink overwrite | Existing output file/directory links could redirect writes outside the intended tree. | Reject linked output directories and unsafe paths. Write new temporary files and atomically rename them instead of following destination file links. |
34 | Active README assets and trusted-page rewriting | Any referenced blob could become a same-origin raw document; README rewriting also touched the page header. | Copy only signature-checked raster formats. Serve raw files with explicit MIME types and nosniff. Keep SVG on a separate origin. Rewrite only the sanitized README article. |
35 | Unbounded input and process lifetime | Large blobs, metadata, Markdown, patches, or hung subprocesses could exhaust resources. | Add byte limits, generator process limits, HTTP connection limits/timeouts, and build subprocess timeouts with process-group cleanup. |
36 | Outdated Git security baseline | Installed Ubuntu Git 2.53.0 predates the upstream 2026-11-10 fixes. | Build Git 2.56.1 privately from a checksum-pinned official release; select it for builds and refuse older baseline versions. System Git is unchanged. |
37
38 Object IDs are also validated as hexadecimal SHA-1/SHA-256 values before they can
39 become revision arguments, stream records, or output filenames. Online clone/fetch
40 uses the validated GitHub HTTPS URL, enables object validation, disables hooks,
41 credential helpers, non-HTTPS protocols, and submodule recursion.
42
43 ## Resource limits
44
45 | Input/process | Limit |
46 | --- | --- |
47 | Markdown README | 4 MiB input, 32 MiB MD4C output |
48 | Individual source/image blob | 32 MiB |
49 | Git metadata response or individual commit diff | 64 MiB |
50 | Repository description/owner/URL metadata file | 16 KiB |
51 | GitHub API page response | 8 MiB |
52 | Generator process | 2 GiB virtual address space, 120 CPU seconds, 180 wall seconds |
53 | Other build subprocess | 300 wall seconds unless explicitly overridden |
54 | Preview HTTP server | 32 concurrent connections, 10-second socket timeout |
55
56 Exceeding a limit fails the build before the staged site replaces the current
57 preview. Limits mitigate resource exhaustion; they do not impose a whole-build disk
58 quota or a total timeout across an arbitrary number of repositories. Keep adequate
59 disk space and remove old `.cache/previous-*` snapshots when no longer needed.
60
61 ## Verification
62
63 The adversarial suite creates temporary local repositories and harmless marker
64 payloads. Each XSS sample is generated independently, including raw script, event
65 handlers, entity/control-character scheme obfuscation, data URLs, forms, iframe
66 srcdoc, SVG, MathML mutation patterns, and DOM-clobbering attributes. Safe details,
67 tasks, tables, and fenced code remain covered by regression tests.
68
69 Other cases exercise metadata/output links, colon/backslash filenames, oversize
70 input, fake raster data, active SVG/HTML, header preservation, encoded traversal,
71 Host/Origin rejection, security headers, and raw-content MIME types. The full-site
72 audit checks all generated pages and local links, parses Atom feeds, and opens every
73 archive without extracting repository content.
74
75 Completed results: 8 Rust unit tests, the generator regression suite, 9 adversarial
76 tests under WSL, and 3 preview tests on native Windows passed. The Windows tests
77 include a directory junction; file symlink cases run under WSL because the Windows
78 account cannot create them. Four isolated Chromium browser cases ran without CSP
79 and retained no executable payload or unsafe DOM nodes. The normal preview also
80 loaded its stylesheet under the enforced CSP. The rebuilt site contained 21
81 repositories, 1,159 HTML pages, 16,986 local links, and 21 readable archives, with no
82 missing local links or disallowed executable elements detected by the site audit.
83
84 Run the commands in [README.md](README.md#verification). Results and reproductions
85 from this review are retained locally under `.cache/security-audit/`. The Cargo
86 lockfile's 67 registry packages were queried against OSV on the review date; no
87 matching published advisories were returned. That result does not prove that a
88 dependency is free of unknown vulnerabilities. Native Git, MD4C, Python, OpenSSL,
89 curl, and the browser also need ongoing security updates.
90
91 ## Hosting requirements and remaining boundaries
92
93 The local Python server is a development preview. Production uses GitHub Pages,
94 which does not apply the preview server's custom response headers. Generated HTML
95 still contains the script-blocking policy in `security/csp.txt` and a no-referrer
96 meta policy, and repository HTML is sanitized before deployment. Meta CSP cannot
97 enforce `frame-ancestors`, and HTML cannot set `X-Content-Type-Options` or
98 `X-Frame-Options`. These HTTP-layer protections are not claimed for the direct
99 GitHub Pages deployment.
100
101 To enforce the full response policy, use a proxy or host that can set the CSP from
102 `security/csp.txt` plus `frame-ancestors 'none'`, `X-Content-Type-Options: nosniff`,
103 `X-Frame-Options: DENY`, and `Referrer-Policy: no-referrer`. Changing DNS alone does
104 not add these headers. The DNS configuration and HTTPS certificate belong to the
105 GitHub Pages deployment; no Cloudflare proxy rules are managed by this repository.
106
107 CI validates pull requests with read-only repository permissions. Deployment runs
108 only after a successful build on `main`, in a separate job with Pages/OIDC access.
109 Checkout credentials are not persisted, actions are pinned, and build artifacts
110 are checked for unsafe files and broken links before upload. The Pages packager
111 retains the public `.github` repository but refuses `.git` metadata, symlinks, and
112 special files. No deployment credentials are passed to repository content.
113
114 External HTTPS images remain permitted and can disclose a visitor's IP address to
115 their host. Outbound links and downloaded source archives contain repository data;
116 sanitizing a page does not make external destinations or downloaded code trustworthy.
117 Archives preserve repository files, including symlink entries; never execute or
118 extract an untrusted archive without an appropriate separate boundary.
119
120 No finite review can guarantee the absence of vulnerabilities. This review combines
121 source inspection, reproducible exploits, regression tests, dependency checks, and
122 local browser verification; it is not an exhaustive fuzzing campaign or a formal
123 proof of the Rust/C boundary.
124
125 ## Primary references
126
127 - [Git security fixes announced on 2026-11-10](https://github.blog/open-source/git/git-security-vulnerabilities-announced-7/)
128 - [Official Git source checksums](https://www.kernel.org/pub/software/scm/git/sha256sums.asc)
129 - [Ammonia 4.2.4 API](https://docs.rs/ammonia/4.2.4/ammonia/struct.Builder.html)
130 - [MD4C source and API](https://github.com/mity/md4c)
131 - [Python HTTP server security considerations](https://docs.python.org/3/library/http.server.html#security-considerations)
132 - [OSV batch query API](https://google.github.io/osv.dev/post-v1-querybatch/)