avelino/awesome-go site generator does not validate entry link URI schemes, letting javascript: links into category page href attributes that execute arbitrary script on a single click (CWE-79)
Author: r20z19Created Sep 11, 2026Updated Sep 11, 2026
# avelino/awesome-go site generator does not validate entry link URI schemes, letting javascript: links into category page href attributes that execute arbitrary script on a single click (CWE-79)
Last saved at 2026-09-11
## Asset
The static site generator embedded in the avelino/awesome-go repository (SOURCE_CODE) — `main.go`, `tmpl/category-index.tmpl.html` — whose output is auto-deployed to the production site category pages **https://awesome-go.com/<category>/** by `.github/workflows/site-deploy.yaml`.
- Repository: https://github.com/avelino/awesome-go
- Audited baseline: `main` branch HEAD commit `1ed3a46319b9e85d2fedcacc8cece2e0faf456b6`. The project has no formal versioned releases, so the affected code is delineated by commit (including the audited HEAD).
- Deployment chain: push to `main` → `site-deploy.yaml` runs `go run .` → Netlify (`production-deploy: true`) → production site.
## Weakness
Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (cwe-79)
## Description
> **Version declaration:** The audited source is avelino/awesome-go `main` branch HEAD `1ed3a46`. All code references below were verified against the actual repository tree in this submission. This report is prepared in accordance with the platform Policy and Disclosure Guidelines. The finding is **code-level confirmed**: the source chain is closed end-to-end, and a poisoned category-page artifact produced by the real build pipeline is attached as the white-box logic reproduction result; no live production request testing was performed.
### Summary
For external entries that are not github.com/gitlab.com links, the category page template writes `{{.URL}}` **verbatim** into the `` attribute with `text/template` (`tmpl/category-index.tmpl.html:113`), appending a UTM query string afterwards. Nowhere along the pipeline — goldmark markdown link rendering → goquery attribute extraction → `parseRepoURL` → template rendering — **does any URI scheme allowlist exist**. goldmark does not filter link schemes, so a contributor only needs to submit one entry of the form `[title]()` in a PR to have the production category page render a link that **executes arbitrary script on a single click**. The `?utm_campaign=awesomego...` query string appended by the template can be commented out with a trailing `//` in the payload, keeping the statement valid JavaScript. The site sets no CSP, and `rel="noopener nofollow"` does not prevent `javascript:` URL execution on click.
### Finding A: no scheme allowlist anywhere in the pipeline
**Files and key lines:**
1. `main.go:439-461` `parseRepoURL` — only recognizes `github.com`/`gitlab.com` hostnames to decide whether a project detail page is generated (`u.Hostname()` check); the scheme is **neither validated nor rewritten**; unrecognized links return `ok=false`.
2. `main.go:464-491` `buildProjects` — entries failing `parseRepoURL` merely skip detail-page generation (`if !ok { continue }`, `main.go:470-472`); the entry stays in the category data with `Link.URL` unchanged as the attacker's raw string.
3. goldmark markdown links (including angle-bracket destinations `[x]()`) — rendered as `` with the **scheme passed through as-is** (GFM does not filter link schemes by default).
4. `main.go:322-331` `extractCategory` — `selLink.Attr("href")` retrieves the full `javascript:` string into `Link.URL`.
### Finding B: the template else branch injects the URL verbatim into href and appends the query string
**File:** `tmpl/category-index.tmpl.html:108-116`
```html
{{range .Links}}
{{if .ProjectSlug}}
{{.Description}}
{{else}}
{{.Description}}
{{end}}
{{end}}
```
Non-github/gitlab entries take the `{{else}}` branch (:113): `text/template` injects `{{.URL}}` verbatim into the `href` attribute. The appended `?utm_campaign=...` would normally break the JS statement (`javascript:alert(3)?utm...` is a syntax error), but the attacker can neutralize it with a trailing `//`, turning the appended string into a line comment:
```
javascript:alert(3)//?utm_campaign=awesomego&utm_medium=referral&utm_source=awesomego
```
The browser treats `//...` as a JS comment and the statement remains executable. `rel="noopener nofollow"` only affects window/referrer semantics; it does not block `javascript:` URL execution on click.
### Finding C: no CSP backstop
`netlify.toml` defines no headers, the repository has no `_headers` file, and the generated HTML carries no `Content-Security-Policy` meta — so `javascript:` URLs are not blocked by any CSP.
### Versions Verified
| Version | Scheme allowlist | Verbatim href sink | Appended UTM commentable | Live link in artifacts |
|---|---|---|---|---|
| avelino/awesome-go main @ `1ed3a46` (audited tree) | None (`main.go:439-461` recognizes hosts only) | Present (`category-index.tmpl.html:113`) | Yes (trailing `//`) | Confirmed (see below) |
The poisoned category page built locally with the real generator contains a live `javascript:` link (evidence file `workdir/poc/env_awesome-go/out/security-tools/index.html:115`). No fix commit was found in the audited tree.
---
## Steps To Reproduce
**Pre-conditions:** An attacker can open a PR modifying `README.md` in avelino/awesome-go; the PR is auto-merged once quality checks pass and the site is redeployed. The site sets no CSP.
**Step 1 — submit a PR containing a javascript: link.** Add an entry to any category list (the angle-bracket destination makes the markdown parser accept the unusual characters):
```markdown
- [V3 URI Demo]() - desc
```
**Step 2 — merge triggers the build.** CI runs `go run .`: goldmark renders the line as `` → goquery extracts the `href` into `Link.URL` (`main.go:324`) → `parseRepoURL` returns false for the non-github link (`main.go:470-472`) and the entry remains in the category data → `renderCategories` renders `category-index.tmpl.html:113` (else branch) with `text/template` → Netlify deploys.
**Step 3 — visit the category page and click the entry.** Expected: the entry's `href` is an attacker-controlled `javascript:` URL and one click executes the script. Actual (derived from source logic; recorded artifact from the real local build at `workdir/poc/env_awesome-go/out/security-tools/index.html:115`):
```html
V3 URI Demo - ...
```
At execution time the `//` comments out the appended UTM string and `alert(3)` runs. Expected and actual match: a single click executes the script.
**Reproduction note:** The above is a white-box logic reproduction result — the README entry → goldmark scheme pass-through → goquery attribute extraction → text/template href injection → browser click-execution call chain is proven segment by segment from the source; the link form in the artifact is asserted verbatim against the real build output (V3 check in `workdir/poc/verify_out.py` PASS; run log `workdir/poc/poc_output.txt`). Click-execution of `javascript:` URLs is deterministic HTML/JS behavior. Reproduction script: `workdir/poc/build_poc_site.sh`; attacker input: `workdir/poc/malicious_README.md` (V3 entry).
---
## Impact
| Aspect | Detail |
|---|---|
| **Attack requirement** | One merged PR; the victim must **click** the attacker's entry link once on a category page (the entry text is fully attacker-controlled and can masquerade as a normal tool name) |
| **Permission boundary** | No special privileges needed; the script executes within the awesome-go.com origin — the boundary expands from "contributor" to "browser of visitors who click the link" |
| **Confidentiality** | After the click, the script can read same-origin page data and exfiltrate it; combined with page content it enables convincing social engineering |
| **Integrity** | Full control of the page presentation after the click: phishing redirects, fake login forms, content tampering |
| **Availability** | Pop-up loops / forced redirects can render the page unusable |
| **Severity** | Medium. CVSS 3.1 `AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N` ≈ **5.0** (UI:R because one click is required). Compared with the zero-interaction findings it depends on social engineering, but the cost of disguising the payload entry is negligible |
| **CVE eligibility** | Yes. Attacker-controlled input (README link destination), a clear root cause (no scheme allowlist anywhere + text/template href injection), a concrete dangerous sink (category page ``), and a clear impact boundary (visitors who click the entry) — an independent vulnerability of the site generator product itself |
| **Application methods** | ① Channel designated by SECURITY.md: https://github.com/avelino/awesome-go/issues/new (public issue; the scope explicitly covers the static site generator); ② maintainer email [email protected] (mailto:[email protected]); ③ VulDB: https://vuldb.com/?submit (login required); ④ MITRE CNA-LR CVE form: https://mitre.github.io/mitre-cve-roles/cve-id-request/ (cveform.mitre.org now 302-redirects there). GitHub Private Vulnerability Reporting was verified via the API as disabled (`{"enabled": false}`) and is therefore not listed |
**Suggested fix:** Enforce an `http`/`https` scheme allowlist on the final rendered `Link.URL` (independently of `parseRepoURL`); rebuild links from a `url.URL` struct in templates instead of string concatenation; add a CSP.
Source: avelino/awesome-go