Add documentation site and reader refinements #51

Manually merged
marvin8 merged 10 commits from feat/reader-tweaks-and-docs into main 2026-09-26 02:00:58 +00:00
Collaborator

Documentation site (#41)

  • Material-for-MkDocs site (mkdocs.yml + docs/), modelled on cang's: Home, Installation, Configuration (every YUNJIN_* variable), CLI reference, Reader (index/aggregate/article pages, settings, ignore rules, keyboard shortcuts), and Container pages.
  • CI deploy-docs job publishes versioned docs on release: mike deploy --branch pages for the tag plus latest, then uploads to git-pages at yunjin.marvin8.zone. First deploy creates the pages branch.
  • Optional mkdocstrings API reference skipped for now.

Reader refinements

  • Reader cards show only the group title; the overview stays on the group page (#48).
  • The hide-read preference moves from URL parameters to a persistent cookie (1 year, HttpOnly, SameSite=Lax) with a POST /hide-read toggle; all query-parameter threading through links, forms and redirects is gone (#49).
  • Footer: Source Code now points at the forge, Documentation links to yunjin.marvin8.zone/<version>, and the version renders from package metadata instead of hardcoded text (#50).

Closes #48
Closes #49
Closes #50
Closes #41

## Documentation site (#41) - Material-for-MkDocs site (`mkdocs.yml` + `docs/`), modelled on cang's: Home, Installation, Configuration (every `YUNJIN_*` variable), CLI reference, Reader (index/aggregate/article pages, settings, ignore rules, keyboard shortcuts), and Container pages. - CI `deploy-docs` job publishes versioned docs on release: `mike deploy --branch pages` for the tag plus `latest`, then uploads to git-pages at `yunjin.marvin8.zone`. First deploy creates the `pages` branch. - Optional mkdocstrings API reference skipped for now. ## Reader refinements - Reader cards show only the group title; the overview stays on the group page (#48). - The hide-read preference moves from URL parameters to a persistent cookie (1 year, HttpOnly, SameSite=Lax) with a `POST /hide-read` toggle; all query-parameter threading through links, forms and redirects is gone (#49). - Footer: Source Code now points at the forge, Documentation links to `yunjin.marvin8.zone/<version>`, and the version renders from package metadata instead of hardcoded text (#50). Closes #48 Closes #49 Closes #50 Closes #41
🐛 Point the footer at the forge and link versioned docs
All checks were successful
/ gitleaks (pull_request) Successful in 15s
/ checks (pull_request) Successful in 2m6s
/ publish-container (pull_request) Has been skipped
/ deploy-docs (pull_request) Has been skipped
/ pr-review (pull_request) Successful in 2m13s
/ container (pull_request) Successful in 1m27s
fb5ea54ceb
forgejo-actions left a comment

WuMing

Found 3 issue(s). See inline comments below.

## WuMing Found **3** issue(s). See inline comments below.
@ -123,0 +127,4 @@
needs: [gitleaks, checks]
if: github.event_name == 'release' && !github.event.release.prerelease
steps:
- uses: actions/checkout@v4

security [LOW]

A08: The workflow uses actions/checkout with a mutable major-version tag instead of a pinned commit SHA. If the tag is moved or compromised, the action can run malicious code in the release pipeline. Pin to a full commit SHA and review updates.

**security** [LOW] A08: The workflow uses actions/checkout with a mutable major-version tag instead of a pinned commit SHA. If the tag is moved or compromised, the action can run malicious code in the release pipeline. Pin to a full commit SHA and review updates. <!-- wuming:sha256:b50b3596d8ed21f83bbb61fd8d05690ec1f949dbc962101fa1c4a79f4d64396d -->
marvin8 marked this conversation as resolved
@ -123,0 +137,4 @@
- name: Verify tag is on main
run: git merge-base --is-ancestor HEAD origin/main
- uses: astral-sh/setup-uv@v5

security [LOW]

A08: astral-sh/setup-uv is referenced by the mutable tag v5. Unpinned CI actions are an unverified pipeline step and can be replaced without review, leading to supply-chain compromise.

**security** [LOW] A08: astral-sh/setup-uv is referenced by the mutable tag v5. Unpinned CI actions are an unverified pipeline step and can be replaced without review, leading to supply-chain compromise. <!-- wuming:sha256:80bcac7c88df9b8485e9ae9f7f7c47e77d4568c76d4893bcef679962fd8fcd37 -->
marvin8 marked this conversation as resolved
@ -123,0 +170,4 @@
path: _site
- name: Upload to grebedoc
uses: https://codeberg.org/git-pages/action@v2

security [MEDIUM]

A08: The git-pages publishing action is loaded from a mutable v2 tag and receives a deployment token. A compromised or moved tag could exfiltrate the token or publish malicious pages. Pin the action to a full commit SHA and ensure the repository/action is trusted.

**security** [MEDIUM] A08: The git-pages publishing action is loaded from a mutable v2 tag and receives a deployment token. A compromised or moved tag could exfiltrate the token or publish malicious pages. Pin the action to a full commit SHA and ensure the repository/action is trusted. <!-- wuming:sha256:3dd838f549ebea9e136e09cc516deb5c27fa1ee7593e3047c51a7e7f9913f7c4 -->
marvin8 marked this conversation as resolved
Author
Collaborator

❓ Repo-convention call — A08 action pinning (duplicates of the #46 escalation)

.forgejo/workflows/ci.yml — actions/checkout@v4 · .forgejo/workflows/ci.yml — astral-sh/setup-uv@v5 · .forgejo/workflows/ci.yml — git-pages action@v2

A08: The workflow uses actions/checkout with a mutable major-version tag instead of a pinned commit SHA…

Duplicates of the findings escalated on #46 (comments 3534/3535) and left as a repo-wide decision. actions/checkout@v4 and astral-sh/setup-uv@v5 pre-exist across this workflow and the sibling repos; the new deploy-docs job deliberately copies cang's proven release job, git-pages action included — the stated template for #41. Pinning yunjin's references alone would diverge from the fleet; pinning everywhere is a convention change beyond this PR's scope.

Over to Marvin8: say the word and I'll pin all action references to commit SHAs in a follow-up PR (and propose the same for cang for parity).

### ❓ Repo-convention call — A08 action pinning (duplicates of the #46 escalation) [`.forgejo/workflows/ci.yml — actions/checkout@v4`](https://forge.marvin8.zone/marvin8/yunjin/pulls/51#issuecomment-3670) · [`.forgejo/workflows/ci.yml — astral-sh/setup-uv@v5`](https://forge.marvin8.zone/marvin8/yunjin/pulls/51#issuecomment-3671) · [`.forgejo/workflows/ci.yml — git-pages action@v2`](https://forge.marvin8.zone/marvin8/yunjin/pulls/51#issuecomment-3672) > A08: The workflow uses actions/checkout with a mutable major-version tag instead of a pinned commit SHA… Duplicates of the findings escalated on #46 (comments 3534/3535) and left as a repo-wide decision. `actions/checkout@v4` and `astral-sh/setup-uv@v5` pre-exist across this workflow and the sibling repos; the new `deploy-docs` job deliberately copies cang's proven release job, git-pages action included — the stated template for #41. Pinning yunjin's references alone would diverge from the fleet; pinning everywhere is a convention change beyond this PR's scope. Over to Marvin8: say the word and I'll pin all action references to commit SHAs in a follow-up PR (and propose the same for cang for parity).
Author
Collaborator

Decision recorded: no action — repo-wide action pinning is overkill for a personal hobby project. The three A08 findings stand declined; no follow-up PR.

Decision recorded: no action — repo-wide action pinning is overkill for a personal hobby project. The three A08 findings stand declined; no follow-up PR.
📝 Refresh AGENTS.md to match current CI and layout
All checks were successful
/ gitleaks (pull_request) Successful in 14s
/ checks (pull_request) Successful in 2m0s
/ publish-container (pull_request) Has been skipped
/ deploy-docs (pull_request) Has been skipped
/ pr-review (pull_request) Successful in 2m20s
/ container (pull_request) Successful in 1m25s
4a46279401
forgejo-actions left a comment

WuMing

Found 7 issue(s). See inline comments below.

## WuMing Found **7** issue(s). See inline comments below.
@ -123,0 +164,4 @@
uv run mike deploy --branch pages --push --update-aliases "$VERSION" latest
- name: Checkout pages branch
uses: actions/checkout@v4

config [MEDIUM]

This second checkout leaves persist-credentials at its default (true), so actions/checkout writes the Forgejo auth header/token into _site/.git/config. _site is then handed to the publishing action (source: _site), so if that action uploads dotfiles/.git, the repository token is published to a public site. Fix by adding persist-credentials: false to this checkout step (the push already uses the extraheader configured in the earlier step), and/or delete _site/.git before uploading.

**config** [MEDIUM] This second checkout leaves `persist-credentials` at its default (true), so `actions/checkout` writes the Forgejo auth header/token into `_site/.git/config`. `_site` is then handed to the publishing action (`source: _site`), so if that action uploads dotfiles/`.git`, the repository token is published to a public site. Fix by adding `persist-credentials: false` to this checkout step (the push already uses the extraheader configured in the earlier step), and/or delete `_site/.git` before uploading. <!-- wuming:sha256:37757d3bc0b780099df5a70242e88ab0bb506cd5820bad3b3a9dfb257e1ea2bc -->
marvin8 marked this conversation as resolved
@ -123,0 +170,4 @@
path: _site
- name: Upload to grebedoc
uses: https://codeberg.org/git-pages/action@v2

security [HIGH]

A08: The Codeberg git-pages action is pinned to mutable tag v2 and receives a privileged forge token. An attacker who moves that tag can execute arbitrary code and exfiltrate the token. Pin the action to a full commit SHA and use a least-privilege token.

**security** [HIGH] A08: The Codeberg git-pages action is pinned to mutable tag v2 and receives a privileged forge token. An attacker who moves that tag can execute arbitrary code and exfiltrate the token. Pin the action to a full commit SHA and use a least-privilege token. <!-- wuming:sha256:339918ed2e6ec178749dc70c2211f7807d12f837926f23c7c213c1fa0c470f02 -->
marvin8 marked this conversation as resolved
AGENTS.md Outdated
@ -33,7 +36,8 @@ TDD, main-off-limits invariant) apply in full and are not repeated here.
| `nox` | Runs `ty`, `ruff`, `complexipy`, `pytest` (the nox part of the CI checks job) | agent |

docs [MEDIUM]

This row still describes the nox check set as only ty, ruff, complexipy, pytest, but the checks job description updated above (lines 15–16) now also lists pysentry, deptry, zaojun, and tests on 3.12 + 3.13 + 3.14. The row explicitly claims to describe "the nox part of the CI checks job", so the two descriptions of the same job disagree — update the row to match.

**docs** [MEDIUM] This row still describes the nox check set as only `ty`, `ruff`, `complexipy`, `pytest`, but the `checks` job description updated above (lines 15–16) now also lists `pysentry`, `deptry`, `zaojun`, and tests on 3.12 + 3.13 + 3.14. The row explicitly claims to describe "the nox part of the CI checks job", so the two descriptions of the same job disagree — update the row to match. <!-- wuming:sha256:0938489747968a0c8ced6063ec0ee9ec3bb54638ef27080d5ca9cd08585f30fd -->
docs/install.md Outdated
@ -0,0 +5,4 @@
## Requirements
- Python **3.12+** (3.13 works too; supported range is `>=3.12,<3.14`)

docs [HIGH]

The stated support range >=3.12,<3.14 contradicts the rest of this PR: AGENTS.md (line 39) now says Python >=3.12 and that nox runs the tests on 3.12, 3.13 and 3.14, and README.md (line 39) says "Python 3.12+". If 3.14 is tested/supported, the upper bound here is wrong; if <3.14 is still correct, the other documents are wrong. Reconcile the three so readers get one consistent requirement (and fix the odd "3.13 works too" phrasing).

**docs** [HIGH] The stated support range `>=3.12,<3.14` contradicts the rest of this PR: AGENTS.md (line 39) now says Python `>=3.12` and that nox runs the tests on 3.12, 3.13 *and 3.14*, and README.md (line 39) says "Python 3.12+". If 3.14 is tested/supported, the upper bound here is wrong; if `<3.14` is still correct, the other documents are wrong. Reconcile the three so readers get one consistent requirement (and fix the odd "3.13 works too" phrasing). <!-- wuming:sha256:7488f80b06cff49ded8bd97af196960192068ae88b83d3bde602084eb8b21676 -->
docs/install.md Outdated
@ -0,0 +58,4 @@
# Install with development dependencies
uv sync --all-groups
# Run the full check suite (types, lint, complexity, tests)

docs [LOW]

The inline comment for uv run nox lists only "types, lint, complexity, tests", while README.md (line 86) and AGENTS.md (lines 15–16) describe the same suite as also including security (pysentry) and dependency (deptry) checks. Align the summary with those documents to avoid implying the suite is smaller than it is.

**docs** [LOW] The inline comment for `uv run nox` lists only "types, lint, complexity, tests", while README.md (line 86) and AGENTS.md (lines 15–16) describe the same suite as also including security (`pysentry`) and dependency (`deptry`) checks. Align the summary with those documents to avoid implying the suite is smaller than it is. <!-- wuming:sha256:583a606003d54d23706bda57a141b89e87fc8e35b4ba6a096cdba683b2880bba -->
@ -0,0 +49,4 @@
- Reader: reader.md
- Container: container.md
markdown_extensions:

config [HIGH]

markdown_extensions: is indented two spaces, unlike its siblings nav: (line 44) and extra: (line 82), so it is no longer a top-level key. YAML will try to read it as a continuation of the nav block sequence and fail with a parser error (e.g. "expected , but found '?'"), so mkdocs build/mike deploy will abort and the docs job will never publish. Dedent markdown_extensions: and its whole list (lines 52-80) to column 0 so it is a top-level key.

**config** [HIGH] `markdown_extensions:` is indented two spaces, unlike its siblings `nav:` (line 44) and `extra:` (line 82), so it is no longer a top-level key. YAML will try to read it as a continuation of the `nav` block sequence and fail with a parser error (e.g. "expected <block end>, but found '?'"), so `mkdocs build`/`mike deploy` will abort and the docs job will never publish. Dedent `markdown_extensions:` and its whole list (lines 52-80) to column 0 so it is a top-level key. <!-- wuming:sha256:69cf734fb58cbf17f2249f35859337e13e5b1c1a705884b39e0a1f0c27b040b5 -->
marvin8 marked this conversation as resolved
@ -232,0 +265,4 @@
response = redirect(next_path)
if turn_on:
response.set_cookie(

security [LOW]

A05: The hide-read cookie is set with HttpOnly and SameSite but no Secure flag, so it may be transmitted over plaintext HTTP if the app is ever accessed without HTTPS. Add secure=True to ensure the cookie is only sent over HTTPS.

**security** [LOW] A05: The hide-read cookie is set with HttpOnly and SameSite but no Secure flag, so it may be transmitted over plaintext HTTP if the app is ever accessed without HTTPS. Add secure=True to ensure the cookie is only sent over HTTPS. <!-- wuming:sha256:37023fc8993d07034826da6161532b92661f1255b0f81145dc5877c2071134b5 -->
marvin8 marked this conversation as resolved
🔥 Remove stale Roadmap.md
All checks were successful
/ gitleaks (pull_request) Successful in 13s
/ checks (pull_request) Successful in 1m51s
/ publish-container (pull_request) Has been skipped
/ deploy-docs (pull_request) Has been skipped
/ pr-review (pull_request) Successful in 2m18s
/ container (pull_request) Successful in 1m25s
a2ab8dd448
forgejo-actions left a comment

WuMing

Found 4 issue(s). See inline comments below.

## WuMing Found **4** issue(s). See inline comments below.
@ -123,0 +150,4 @@
run: |
git config user.email "ci@forgejo"
git config user.name "Forgejo Actions"
git config http.https://forge.marvin8.zone/.extraheader "Authorization: token $FORGEJO_TOKEN"

security [MEDIUM]

A02: The Forgejo token is persisted to the repository-local .git/config in plaintext. Later steps (including third-party actions) can read the file, and the token may be visible in process listings. Prefer a scoped per-command git -c http.extraheader=... for the single push, or use a credential helper that does not persist the secret.

**security** [MEDIUM] A02: The Forgejo token is persisted to the repository-local .git/config in plaintext. Later steps (including third-party actions) can read the file, and the token may be visible in process listings. Prefer a scoped per-command `git -c http.extraheader=...` for the single push, or use a credential helper that does not persist the secret. <!-- wuming:sha256:1ee0a9b260f40b5b31c3324a392971782ad2347359e42683b211d959cc8bae59 -->
marvin8 marked this conversation as resolved
docs/cli.md Outdated
@ -0,0 +85,4 @@
### `ignore-tag` / `unignore-tag`
Add or remove a tag from the ignore list. Ignored tags are excluded from
clustering.

docs [MEDIUM]

"clustering" is stale terminology: the embedding/DBSCAN clustering pipeline has been removed (see AGENTS.md, "the legacy embedding + DBSCAN pipeline has been fully removed"), and docs/reader.md:77 says ignored tags are excluded "when the LLM groups articles". Reword to "excluded during LLM aggregation" so the CLI reference matches the rest of the docs.

**docs** [MEDIUM] "clustering" is stale terminology: the embedding/DBSCAN clustering pipeline has been removed (see AGENTS.md, "the legacy embedding + DBSCAN pipeline has been fully removed"), and docs/reader.md:77 says ignored tags are excluded "when the LLM groups articles". Reword to "excluded during LLM aggregation" so the CLI reference matches the rest of the docs. <!-- wuming:sha256:e54b8ac20558e17fd2a07fde1746b3e00a4fe7f4040b6182660a9a1a137ef644 -->
docs/index.md Outdated
@ -0,0 +38,4 @@
- [Reader](reader.md) — the web interface, settings, and shortcuts
- [Container](container.md) — running the published image
## Licence

docs [LOW]

Inconsistent spelling of the same heading: this page uses British "Licence" while README.md line 90 uses "License". Pick one spelling (the docs otherwise favour British English, e.g. "colour", "sanitised") and apply it to both files.

**docs** [LOW] Inconsistent spelling of the same heading: this page uses British "Licence" while README.md line 90 uses "License". Pick one spelling (the docs otherwise favour British English, e.g. "colour", "sanitised") and apply it to both files. <!-- wuming:sha256:777fcc7d684294b6a212f0dd2565df28452397d206e4f81d7a9064eefed0693a -->
@ -232,0 +269,4 @@
_HIDE_READ_COOKIE,
"1",
max_age=_HIDE_READ_COOKIE_MAX_AGE,
httponly=True,

security [LOW]

A05: The hide_read cookie is set without Secure. If the application is served over HTTPS, the preference cookie can still be transmitted over plaintext HTTP and observed or set by a network attacker. Add secure=True and use the same flag when deleting the cookie.

**security** [LOW] A05: The `hide_read` cookie is set without `Secure`. If the application is served over HTTPS, the preference cookie can still be transmitted over plaintext HTTP and observed or set by a network attacker. Add `secure=True` and use the same flag when deleting the cookie. <!-- wuming:sha256:30df055b690afb2c8f689702d0336ec867cb925c0e49a2a425ab0031425991d1 -->
marvin8 marked this conversation as resolved
🔧 Address WuMing review waves on PR #51
All checks were successful
/ gitleaks (pull_request) Successful in 21s
/ checks (pull_request) Successful in 1m48s
/ publish-container (pull_request) Has been skipped
/ deploy-docs (pull_request) Has been skipped
/ pr-review (pull_request) Successful in 2m46s
/ container (pull_request) Successful in 1m26s
a5538ad3d3
Author
Collaborator

Review response — waves 344/345

✅ Fixed in a5538ad

.forgejo/workflows/ci.yml — pages checkout — the _site checkout now sets persist-credentials: false, so no token is written into the uploaded tree.

docs/install.md — Python range — now "Python 3.12, 3.13, or 3.14", matching pyproject (>=3.12) and nox (3.12–3.14).

AGENTS.md — nox row and docs/install.md — nox comment — both now list the full session set (ty, ruff, complexipy, pysentry, pytest on 3.12–3.14, deptry, zaojun).

docs/cli.md — stale wording — "excluded during LLM aggregation".

docs/index.md — spelling — unified on "License" (matches the LICENSE file and README).

🔴 Not actioned

mkdocs.yml — YAML claim — false positive: markdown_extensions: is a top-level key at column 0, aligned with nav: and extra: (the diff indentation WuMing saw comes from the diff gutter). mkdocs build --strict exits 0 — verified again on this push. The traceback behind the finding is plain yaml.safe_load choking on mkdocs' !!python/name: tags, which every mkdocs config with emoji extensions contains and which mkdocs itself resolves.

.forgejo/workflows/ci.yml — A08 pinning — duplicate of the declined pinning decision (recorded here): repo-wide action pinning is overkill for this hobby project.

reader.py — Secure flag · reader.py — Secure flag (repeat) — declined: secure=True would silently break the preference on plain-HTTP self-hosted deployments (dev server, LAN, TLS-terminating reverse proxy), and the cookie carries only a hide-read view preference, not credentials.

.forgejo/workflows/ci.yml — extraheader — declined: the Configure-git step is cang's proven template verbatim; with the persist-credentials: false fix above, the uploaded _site checkout holds no credentials and the header only ever lives in the trusted job's own workspace. Same over-hardening bucket as the declined pinning wave — happy to revisit if the convention changes.

### Review response — waves 344/345 #### ✅ Fixed in `a5538ad` [`.forgejo/workflows/ci.yml — pages checkout`](https://forge.marvin8.zone/marvin8/yunjin/pulls/51#issuecomment-3680) — the `_site` checkout now sets `persist-credentials: false`, so no token is written into the uploaded tree. [`docs/install.md — Python range`](https://forge.marvin8.zone/marvin8/yunjin/pulls/51#issuecomment-3681) — now "Python 3.12, 3.13, or 3.14", matching pyproject (`>=3.12`) and nox (3.12–3.14). [`AGENTS.md — nox row`](https://forge.marvin8.zone/marvin8/yunjin/pulls/51#issuecomment-3682) and [`docs/install.md — nox comment`](https://forge.marvin8.zone/marvin8/yunjin/pulls/51#issuecomment-3683) — both now list the full session set (`ty`, `ruff`, `complexipy`, `pysentry`, `pytest` on 3.12–3.14, `deptry`, `zaojun`). [`docs/cli.md — stale wording`](https://forge.marvin8.zone/marvin8/yunjin/pulls/51#issuecomment-3688) — "excluded during LLM aggregation". [`docs/index.md — spelling`](https://forge.marvin8.zone/marvin8/yunjin/pulls/51#issuecomment-3689) — unified on "License" (matches the `LICENSE` file and README). #### 🔴 Not actioned [`mkdocs.yml — YAML claim`](https://forge.marvin8.zone/marvin8/yunjin/pulls/51#issuecomment-3679) — false positive: `markdown_extensions:` is a top-level key at column 0, aligned with `nav:` and `extra:` (the diff indentation WuMing saw comes from the diff gutter). `mkdocs build --strict` exits 0 — verified again on this push. The traceback behind the finding is plain `yaml.safe_load` choking on mkdocs' `!!python/name:` tags, which every mkdocs config with emoji extensions contains and which mkdocs itself resolves. [`.forgejo/workflows/ci.yml — A08 pinning`](https://forge.marvin8.zone/marvin8/yunjin/pulls/51#issuecomment-3684) — duplicate of the declined pinning decision ([recorded here](https://forge.marvin8.zone/marvin8/yunjin/pulls/51#issuecomment-3677)): repo-wide action pinning is overkill for this hobby project. [`reader.py — Secure flag`](https://forge.marvin8.zone/marvin8/yunjin/pulls/51#issuecomment-3685) · [`reader.py — Secure flag (repeat)`](https://forge.marvin8.zone/marvin8/yunjin/pulls/51#issuecomment-3691) — declined: `secure=True` would silently break the preference on plain-HTTP self-hosted deployments (dev server, LAN, TLS-terminating reverse proxy), and the cookie carries only a hide-read view preference, not credentials. [`.forgejo/workflows/ci.yml — extraheader`](https://forge.marvin8.zone/marvin8/yunjin/pulls/51#issuecomment-3690) — declined: the Configure-git step is cang's proven template verbatim; with the `persist-credentials: false` fix above, the uploaded `_site` checkout holds no credentials and the header only ever lives in the trusted job's own workspace. Same over-hardening bucket as the declined pinning wave — happy to revisit if the convention changes.
forgejo-actions left a comment

WuMing

Found 1 issue(s). See inline comments below.

## WuMing Found **1** issue(s). See inline comments below.
@ -123,0 +171,4 @@
persist-credentials: false
- name: Upload to grebedoc
uses: https://codeberg.org/git-pages/action@v2

security [HIGH]

Third-party CI actions are referenced by mutable tags instead of immutable commit SHAs. This action is passed a Forgejo token (line 178), so a compromised or moved @v2 tag could execute arbitrary code in the release workflow and exfiltrate secrets. Pin actions/checkout@v4, astral-sh/setup-uv@v5, and this action to full commit SHAs. A08: Software and Data Integrity Failures.

**security** [HIGH] Third-party CI actions are referenced by mutable tags instead of immutable commit SHAs. This action is passed a Forgejo token (line 178), so a compromised or moved @v2 tag could execute arbitrary code in the release workflow and exfiltrate secrets. Pin actions/checkout@v4, astral-sh/setup-uv@v5, and this action to full commit SHAs. A08: Software and Data Integrity Failures. <!-- wuming:sha256:a2c5a627d2cfcdd6ef7aa53815cf68ddffb24d5a3f63f57d7d57b189e6d4c7a5 -->
marvin8 marked this conversation as resolved
Author
Collaborator

.forgejo/workflows/ci.yml line 173

Third-party CI actions are referenced by mutable tags instead of immutable commit SHAs…

🔴 Duplicate — identical in substance to the finding answered here and here: repo-wide action pinning was considered and declined as overkill for this hobby project (decision). No further action.

[`.forgejo/workflows/ci.yml` line 173](https://forge.marvin8.zone/marvin8/yunjin/pulls/51#issuecomment-3700) > Third-party CI actions are referenced by mutable tags instead of immutable commit SHAs… 🔴 Duplicate — identical in substance to the finding answered [here](https://forge.marvin8.zone/marvin8/yunjin/pulls/51#issuecomment-3695) and [here](https://forge.marvin8.zone/marvin8/yunjin/pulls/51#issuecomment-3674): repo-wide action pinning was considered and declined as overkill for this hobby project ([decision](https://forge.marvin8.zone/marvin8/yunjin/pulls/51#issuecomment-3677)). No further action.
marvin8 requested changes 2026-09-26 01:26:45 +00:00
Dismissed
@ -3,3 +3,3 @@
**RSS Topic Aggregator and Feed Reader**
> Cloud Brocade - Weaving together similar news topics from multiple RSS feeds into coherent, related aggregates.
> Cloud Brocade — weaving similar articles from multiple RSS feeds into
Owner

Why doesn't this mention the tools actual name "Yunjin" and its chinese characters. The english translation is nice to have and should stay.

Why doesn't this mention the tools actual name "Yunjin" and its chinese characters. The english translation is nice to have and should stay.
📝 Name Yunjin in the README tagline
All checks were successful
/ gitleaks (pull_request) Successful in 15s
/ checks (pull_request) Successful in 1m49s
/ publish-container (pull_request) Has been skipped
/ deploy-docs (pull_request) Has been skipped
/ pr-review (pull_request) Successful in 2m22s
/ container (pull_request) Successful in 1m32s
0d45b2f2fe
Author
Collaborator

README.md line 5

Why doesn't this mention the tools actual name "Yunjin" and its chinese characters…

✅ Fixed in commit 0d45b2f — the tagline now reads: Yunjin (云锦, "Cloud Brocade") — weaving similar articles from multiple RSS feeds into coherent, related topic aggregates.

[`README.md` line 5](https://forge.marvin8.zone/marvin8/yunjin/pulls/51#issuecomment-3713) > Why doesn't this mention the tools actual name "Yunjin" and its chinese characters… ✅ Fixed in commit `0d45b2f` — the tagline now reads: Yunjin (云锦, "Cloud Brocade") — weaving similar articles from multiple RSS feeds into coherent, related topic aggregates.
forgejo-actions left a comment

WuMing

Found 3 issue(s). See inline comments below.

## WuMing Found **3** issue(s). See inline comments below.
@ -123,0 +127,4 @@
needs: [gitleaks, checks]
if: github.event_name == 'release' && !github.event.release.prerelease
steps:
- uses: actions/checkout@v4

security [MEDIUM]

A08: Third-party CI action is referenced by the mutable tag v4 rather than a pinned full-length commit SHA. A compromised or republished tag can execute arbitrary code in the CI job. Pin actions to exact SHAs and enable tag/branch protection review.

**security** [MEDIUM] A08: Third-party CI action is referenced by the mutable tag v4 rather than a pinned full-length commit SHA. A compromised or republished tag can execute arbitrary code in the CI job. Pin actions to exact SHAs and enable tag/branch protection review. <!-- wuming:sha256:a6c1189ede2e39f44f32b652dc2dc4d5d972c2f5eb20d4320919ec34fb128473 -->
@ -123,0 +137,4 @@
- name: Verify tag is on main
run: git merge-base --is-ancestor HEAD origin/main
- uses: astral-sh/setup-uv@v5

security [MEDIUM]

A08: Third-party CI action is referenced by the mutable tag v5 rather than a pinned full-length commit SHA. A compromised or republished tag can execute arbitrary code in the CI job. Pin actions to exact SHAs and enable tag/branch protection review.

**security** [MEDIUM] A08: Third-party CI action is referenced by the mutable tag v5 rather than a pinned full-length commit SHA. A compromised or republished tag can execute arbitrary code in the CI job. Pin actions to exact SHAs and enable tag/branch protection review. <!-- wuming:sha256:6339f55e63d776f629fa769eacf8decba61df7381e6f47be152dfaebbebe6e26 -->
@ -123,0 +150,4 @@
run: |
git config user.email "ci@forgejo"
git config user.name "Forgejo Actions"
git config http.https://forge.marvin8.zone/.extraheader "Authorization: token $FORGEJO_TOKEN"

security [MEDIUM]

A02: The Forgejo token is expanded on the command line and persisted in git config as an extraheader. Shell expansion means the secret appears in the process argument list and can be logged if tracing is enabled. Use a credential helper or GIT_ASKPASS script that reads the token from the environment without placing it in argv or .git/config.

**security** [MEDIUM] A02: The Forgejo token is expanded on the command line and persisted in git config as an extraheader. Shell expansion means the secret appears in the process argument list and can be logged if tracing is enabled. Use a credential helper or GIT_ASKPASS script that reads the token from the environment without placing it in argv or .git/config. <!-- wuming:sha256:7e625dab542b4a91bc08255621330dbf79edb5f31240f55f0e71029815f5b155 -->
Author
Collaborator

Duplicates of declined findings (wave 350)

.forgejo/workflows/ci.yml — checkout@v4 · setup-uv@v5 — A08 pinning, declined: repo-wide action pinning was considered and rejected as overkill for this hobby project (decision, answer, repeat).

.forgejo/workflows/ci.yml — extraheader — A02, declined: cang's proven template verbatim; the uploaded _site checkout holds no credentials and the token only ever exists inside the trusted job's own workspace (answer).

Identical to the findings answered above. No further action.

### Duplicates of declined findings (wave 350) [`.forgejo/workflows/ci.yml` — checkout@v4](https://forge.marvin8.zone/marvin8/yunjin/pulls/51#issuecomment-3718) · [`setup-uv@v5`](https://forge.marvin8.zone/marvin8/yunjin/pulls/51#issuecomment-3719) — A08 pinning, declined: repo-wide action pinning was considered and rejected as overkill for this hobby project ([decision](https://forge.marvin8.zone/marvin8/yunjin/pulls/51#issuecomment-3677), [answer](https://forge.marvin8.zone/marvin8/yunjin/pulls/51#issuecomment-3695), [repeat](https://forge.marvin8.zone/marvin8/yunjin/pulls/51#issuecomment-3705)). [`.forgejo/workflows/ci.yml` — extraheader](https://forge.marvin8.zone/marvin8/yunjin/pulls/51#issuecomment-3717) — A02, declined: cang's proven template verbatim; the uploaded `_site` checkout holds no credentials and the token only ever exists inside the trusted job's own workspace ([answer](https://forge.marvin8.zone/marvin8/yunjin/pulls/51#issuecomment-3695)). Identical to the findings answered above. No further action.
marvin8 approved these changes 2026-09-26 01:59:55 +00:00
marvin8 manually merged commit fdae0a149f into main 2026-09-26 02:00:58 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
marvin8/yunjin!51
No description provided.