Add Forgejo Actions CI (nox checks, gitleaks, WuMing review) #11

Manually merged
marvin8 merged 7 commits from feat/issue-7-forgejo-actions-ci into main 2026-09-10 23:25:52 +00:00
Collaborator

Introduces CI for the repo:

  • .forgejo/workflows/ci.ymlchecks (uv sync → uv run noxuv build), gitleaks, and a WuMing pr-review job (DeepSeek-backed), on pull_request + push to main + workflow_dispatch, runner zhuiri
  • noxfile.pyty, ruff, complexipy, and pytest sessions (pytest on 3.12 + 3.13)
  • gitleaks entry in .pre-commit-config.yaml as the single version source
  • AGENTS.md — drops the no-CI note, adds the nox row

Also fixes two pre-existing failures that CI surfaced in src/yunjin/web/routes/reader.py index(): a heterogeneous dict caused ty diagnostics, and the function exceeded the complexipy limit — refactored to a TypedDict AggregateRow plus a row-builder helper.

Closes #7

Introduces CI for the repo: - `.forgejo/workflows/ci.yml` — `checks` (uv sync → `uv run nox` → `uv build`), `gitleaks`, and a WuMing `pr-review` job (DeepSeek-backed), on `pull_request` + `push` to `main` + `workflow_dispatch`, runner `zhuiri` - `noxfile.py` — `ty`, `ruff`, `complexipy`, and `pytest` sessions (pytest on 3.12 + 3.13) - gitleaks entry in `.pre-commit-config.yaml` as the single version source - `AGENTS.md` — drops the no-CI note, adds the nox row Also fixes two pre-existing failures that CI surfaced in `src/yunjin/web/routes/reader.py` `index()`: a heterogeneous dict caused ty diagnostics, and the function exceeded the complexipy limit — refactored to a `TypedDict` `AggregateRow` plus a row-builder helper. Closes #7
Extract per-aggregate row building into a helper returning a TypedDict
AggregateRow, fixing two ty diagnostics (unresolved union attribute) and
bringing index under the complexipy limit.

Refs #7
nox-uv sessions; pytest parameterised over Python 3.12 and 3.13.

Refs #7
👷 Add Forgejo Actions CI with checks, gitleaks, and WuMing review
All checks were successful
/ gitleaks (pull_request) Successful in 12s
/ pr-review (pull_request) Successful in 5m23s
/ checks (pull_request) Successful in 24m13s
3aaf40042e
Checks run via nox; gitleaks pins its version from the new pre-commit
entry; AGENTS.md no longer claims the repo has no CI.

Refs #7
forgejo-actions left a comment

WuMing

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

## WuMing Found **8** issue(s). See inline comments below.
@ -0,0 +11,4 @@
jobs:
pr-review:
runs-on: zhuiri

security [HIGH]

A08/A05: The checks job runs untrusted pull-request code (uv run nox) on a self-hosted runner without restricting forks or using ephemeral isolation. Attackers can execute arbitrary commands on the runner and access its environment/network.

**security** [HIGH] A08/A05: The checks job runs untrusted pull-request code (uv run nox) on a self-hosted runner without restricting forks or using ephemeral isolation. Attackers can execute arbitrary commands on the runner and access its environment/network. <!-- wuming:sha256:066a5dd88cb1a20bd4be1545786bbe7550fad0ec630bf05be7b22b3ec1d6c364 -->
@ -0,0 +15,4 @@
timeout-minutes: 20
if: github.event_name == 'pull_request'
steps:
- uses: https://forge.marvin8.zone/marvin8/wuming@main

security [HIGH]

A08: External action is pinned only to a mutable @main branch and receives forge.token and wuming_deepseek_key. A compromised branch could exfiltrate secrets or execute malicious code. Pin the action to a reviewed full commit SHA.

**security** [HIGH] A08: External action is pinned only to a mutable @main branch and receives forge.token and wuming_deepseek_key. A compromised branch could exfiltrate secrets or execute malicious code. Pin the action to a reviewed full commit SHA. <!-- wuming:sha256:4c32a1cd8b738d3cc68708f1e5c243ca1c81d36060263215af7e582173748d62 -->
AGENTS.md Outdated
@ -12,3 +12,2 @@
- Forge: https://forge.marvin8.zone/marvin8/yunjin (public; `origin` is the only remote)
- **No CI is configured on this repo.** Validation is local only: run the checks
below before committing. After opening a PR there is nothing for ci-watch to poll.
- **CI runs on every PR and push to `main`** (Forgejo Actions, runner `zhuiri`):

docs [LOW]

The trigger list reads as exhaustive ("on every PR and push to main") but omits workflow_dispatch, which the workflow also supports per the PR description. Add it, e.g. "... on every PR, push to main, and manual workflow_dispatch runs ...", so readers know they can trigger CI by hand.

**docs** [LOW] The trigger list reads as exhaustive ("on every PR and push to `main`") but omits `workflow_dispatch`, which the workflow also supports per the PR description. Add it, e.g. "... on every PR, push to `main`, and manual `workflow_dispatch` runs ...", so readers know they can trigger CI by hand. <!-- wuming:sha256:1e17f70f85d091db48cc6ebd8ee2e04fbacaf8d6d112e439bcf5380d855b62c0 -->
AGENTS.md Outdated
@ -13,2 +13,2 @@
- **No CI is configured on this repo.** Validation is local only: run the checks
below before committing. After opening a PR there is nothing for ci-watch to poll.
- **CI runs on every PR and push to `main`** (Forgejo Actions, runner `zhuiri`):
nox checks (ty/ruff/complexipy/pytest on 3.12+3.13), gitleaks, and a WuMing

docs [MEDIUM]

The parenthetical attaches "on 3.12+3.13" to the whole list, implying ty, ruff and complexipy are version-matrixed too. Per the new noxfile.py, only the pytest session runs on 3.12 and 3.13 (ty/ruff/complexipy run once on the default interpreter). Reword for accuracy: "nox checks (ty, ruff, complexipy, and pytest on 3.12 + 3.13)".

**docs** [MEDIUM] The parenthetical attaches "on 3.12+3.13" to the whole list, implying ty, ruff and complexipy are version-matrixed too. Per the new noxfile.py, only the pytest session runs on 3.12 and 3.13 (ty/ruff/complexipy run once on the default interpreter). Reword for accuracy: "nox checks (ty, ruff, complexipy, and pytest on 3.12 + 3.13)". <!-- wuming:sha256:81533f294ddba739529fa1245f65d7767caeb218267762517efccc8bf4bfe90a -->
AGENTS.md Outdated
@ -15,0 +14,4 @@
nox checks (ty/ruff/complexipy/pytest on 3.12+3.13), gitleaks, and a WuMing
AI review on PRs. After opening a PR, run ci-watch against it. The first CI
run downloads the full dependency tree (sentence-transformers → torch); later
runs are cheap thanks to uv's wheel cache.

docs [LOW]

The claim that later runs are cheap assumes a persistent uv cache on the zhuiri runner, but no caching step (actions/cache or a persistent UV_CACHE_DIR) is described in the workflow. Verify caching is actually configured; otherwise every run re-resolves/re-downloads the torch tree and this sentence will mislead agents about CI duration.

**docs** [LOW] The claim that later runs are cheap assumes a persistent uv cache on the `zhuiri` runner, but no caching step (actions/cache or a persistent `UV_CACHE_DIR`) is described in the workflow. Verify caching is actually configured; otherwise every run re-resolves/re-downloads the torch tree and this sentence will mislead agents about CI duration. <!-- wuming:sha256:941826cd15b34ac6cb5b8185ca23a0edb87019bb430b86a7087dcbd9c3ad8eea -->
AGENTS.md Outdated
@ -24,3 +27,3 @@
| `complexipy` | Complexity check | agent |
| `prek run --all-files` | Pre-commit hooks | **human operator only** |
| `nox` | Full validation | **human operator only** |
| `nox` | Full validation (mirrors CI) | agent |

docs [MEDIUM]

"Full validation (mirrors CI)" overstates what nox covers: CI (line 14) also runs gitleaks and the WuMing review, which the nox sessions (ty/ruff/complexipy/pytest) do not include. Since gitleaks is exposed via prek run --all-files, which line 28 keeps human-operator-only, an agent running nox still will not cover the full CI surface. Clarify, e.g. "Full validation (same checks as the CI checks job; gitleaks still requires prek/human)".

**docs** [MEDIUM] "Full validation (mirrors CI)" overstates what nox covers: CI (line 14) also runs gitleaks and the WuMing review, which the nox sessions (ty/ruff/complexipy/pytest) do not include. Since gitleaks is exposed via `prek run --all-files`, which line 28 keeps human-operator-only, an agent running nox still will not cover the full CI surface. Clarify, e.g. "Full validation (same checks as the CI `checks` job; gitleaks still requires prek/human)". <!-- wuming:sha256:49d6f700c2209afabe9151111ad45d28a920986bb19971b6d077ec3e66bef75b -->
@ -1,6 +1,8 @@
"""Reader routes for Yunjin web interface."""
import sqlite3

tests [LOW]

Non-trivial logic change with no accompanying test changes: index() was refactored to delegate to the new _build_aggregate_row() helper and a new AggregateRow TypedDict. This reworks the aggregate enrichment (article counts, unread counts, tag collection, featured-image selection) and the sort key, so a regression here would silently change the reader index output. No test file appears anywhere in this diff. Add or update unit tests that exercise _build_aggregate_row() and index() (e.g. aggregates with zero articles, with/without images, mixed read/unread state, and ordering by unread count) to lock in the refactor.

**tests** [LOW] Non-trivial logic change with no accompanying test changes: `index()` was refactored to delegate to the new `_build_aggregate_row()` helper and a new `AggregateRow` TypedDict. This reworks the aggregate enrichment (article counts, unread counts, tag collection, featured-image selection) and the sort key, so a regression here would silently change the reader index output. No test file appears anywhere in this diff. Add or update unit tests that exercise `_build_aggregate_row()` and `index()` (e.g. aggregates with zero articles, with/without images, mixed read/unread state, and ordering by unread count) to lock in the refactor. <!-- wuming:sha256:c498b903b4c3334a2df3f18710a7043e90da52aa3d6d520e74dca27d2f0c4b36 -->
@ -40,45 +97,13 @@ def index() -> str:
db = get_db()
user_id = 1 # Default user for now (Phase 6 will add multi-user)

security [MEDIUM]

A01/A07: The route hardcodes user_id = 1 instead of deriving the current authenticated user from the session. Any request is treated as the default user, bypassing authentication and authorization once multi-user/session auth is present. Use the authenticated user identity.

**security** [MEDIUM] A01/A07: The route hardcodes user_id = 1 instead of deriving the current authenticated user from the session. Any request is treated as the default user, bypassing authentication and authorization once multi-user/session auth is present. Use the authenticated user identity. <!-- wuming:sha256:9658fd0b8be04cf547d092ecf4f6d14f9d5286e1994acfbcfb89bfa1be3d5bec -->
📝 Address WuMing review: clarify CI docs, add reader row-builder test
All checks were successful
/ gitleaks (pull_request) Successful in 15s
/ pr-review (pull_request) Successful in 3m54s
/ checks (pull_request) Successful in 21m51s
250fe3fbaf
Refs #7
Author
Collaborator

AGENTS.md line 13

The trigger list reads as exhaustive but omits workflow_dispatch.

Fixed in 250fe3f — the CI note now lists "every PR, push to main, and manual dispatch".

[`AGENTS.md` line 13](https://forge.marvin8.zone/marvin8/yunjin/pulls/11#issuecomment-2434) > The trigger list reads as exhaustive but omits workflow_dispatch. ✅ Fixed in `250fe3f` — the CI note now lists "every PR, push to `main`, and manual dispatch".
Author
Collaborator

AGENTS.md line 14

The parenthetical attaches "on 3.12+3.13" to the whole list, implying ty, ruff and complexipy are version-matrixed too.

Fixed in 250fe3f — reworded so pytest alone is marked as matrixed on 3.12 + 3.13.

[`AGENTS.md` line 14](https://forge.marvin8.zone/marvin8/yunjin/pulls/11#issuecomment-2435) > The parenthetical attaches "on 3.12+3.13" to the whole list, implying ty, ruff and complexipy are version-matrixed too. ✅ Fixed in `250fe3f` — reworded so `pytest` alone is marked as matrixed on 3.12 + 3.13.
Author
Collaborator

AGENTS.md line 17

The claim that later runs are cheap assumes a persistent uv cache on the runner with no caching step.

Fixed in 250fe3f — removed the unverified cache claim; the note now only warns that the first run downloads the full dependency tree.

[`AGENTS.md` line 17](https://forge.marvin8.zone/marvin8/yunjin/pulls/11#issuecomment-2436) > The claim that later runs are cheap assumes a persistent uv cache on the runner with no caching step. ✅ Fixed in `250fe3f` — removed the unverified cache claim; the note now only warns that the first run downloads the full dependency tree.
Author
Collaborator

AGENTS.md line 29

"Full validation (mirrors CI)" overstates what nox covers.

Fixed in 250fe3f — the row now reads "Runs ty, ruff, complexipy, pytest (the CI checks job)".

[`AGENTS.md` line 29](https://forge.marvin8.zone/marvin8/yunjin/pulls/11#issuecomment-2437) > "Full validation (mirrors CI)" overstates what nox covers. ✅ Fixed in `250fe3f` — the row now reads "Runs `ty`, `ruff`, `complexipy`, `pytest` (the CI checks job)".
Author
Collaborator

src/yunjin/web/routes/reader.py line 40

The route hardcodes user_id = 1 instead of deriving the current authenticated user from the session.

🔴 Not actioned — user_id = 1 is a pre-existing placeholder for the multi-user work (Phase 6), carried over unchanged from the original index() (the comment on that line even says "Default user for now (Phase 6 will add multi-user)"). It is out of scope for this CI PR; the refactor only moved the code, it did not introduce the hardcoding.

[`src/yunjin/web/routes/reader.py` line 40](https://forge.marvin8.zone/marvin8/yunjin/pulls/11#issuecomment-2440) > The route hardcodes user_id = 1 instead of deriving the current authenticated user from the session. 🔴 Not actioned — `user_id = 1` is a pre-existing placeholder for the multi-user work (Phase 6), carried over unchanged from the original `index()` (the comment on that line even says "Default user for now (Phase 6 will add multi-user)"). It is out of scope for this CI PR; the refactor only moved the code, it did not introduce the hardcoding.
Author
Collaborator

src/yunjin/web/routes/reader.py line 3

Non-trivial logic change with no accompanying test changes.

Fixed in 250fe3f — added TestReaderRoutes::test_build_aggregate_row covering article count, unread count, tags, and featured-image selection for the new helper.

[`src/yunjin/web/routes/reader.py` line 3](https://forge.marvin8.zone/marvin8/yunjin/pulls/11#issuecomment-2441) > Non-trivial logic change with no accompanying test changes. ✅ Fixed in `250fe3f` — added `TestReaderRoutes::test_build_aggregate_row` covering article count, unread count, tags, and featured-image selection for the new helper.
Author
Collaborator

.forgejo/workflows/ci.yml line 18

External action pinned only to a mutable @main branch and receives forge.token and wuming_deepseek_key.

🔴 Not actioned — @main is intentional: the wuming action is Marvin8's own action on his own forge, and this is the established pattern on his other repos (kokopelli, cang). Confirmed with Marvin8 to leave as-is.

[`.forgejo/workflows/ci.yml` line 18](https://forge.marvin8.zone/marvin8/yunjin/pulls/11#issuecomment-2438) > External action pinned only to a mutable @main branch and receives forge.token and wuming_deepseek_key. 🔴 Not actioned — `@main` is intentional: the `wuming` action is Marvin8's own action on his own forge, and this is the established pattern on his other repos (kokopelli, cang). Confirmed with Marvin8 to leave as-is.
Author
Collaborator

.forgejo/workflows/ci.yml line 14

The checks job runs untrusted pull-request code on a self-hosted runner without restricting forks or using ephemeral runners.

🔴 Not actioned — confirmed with Marvin8: this is the accepted model. PRs on this repo are reviewed by Marvin8 before merge, so CI effectively runs once the branch has been eyeballed; no fork-restriction changes at this time.

[`.forgejo/workflows/ci.yml` line 14](https://forge.marvin8.zone/marvin8/yunjin/pulls/11#issuecomment-2439) > The checks job runs untrusted pull-request code on a self-hosted runner without restricting forks or using ephemeral runners. 🔴 Not actioned — confirmed with Marvin8: this is the accepted model. PRs on this repo are reviewed by Marvin8 before merge, so CI effectively runs once the branch has been eyeballed; no fork-restriction changes at this time.
forgejo-actions left a comment

WuMing

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

## WuMing Found **4** issue(s). See inline comments below.
@ -0,0 +26,4 @@
runs-on: zhuiri
timeout-minutes: 10
steps:
- uses: actions/checkout@v4

config [LOW]

actions/checkout@v4 defaults to fetch-depth: 1, so only the tip commit is present. gitleaks detect walks git history, and with a shallow clone it can only see the single checked-out commit, missing secrets introduced/removed in earlier commits. Add with: fetch-depth: 0 (or pass --no-git/use gitleaks dir if you intentionally only want to scan the working tree).

**config** [LOW] `actions/checkout@v4` defaults to `fetch-depth: 1`, so only the tip commit is present. `gitleaks detect` walks git history, and with a shallow clone it can only see the single checked-out commit, missing secrets introduced/removed in earlier commits. Add `with: fetch-depth: 0` (or pass `--no-git`/use `gitleaks dir` if you intentionally only want to scan the working tree). <!-- wuming:sha256:5859d04e588ccbc17a9955853058b48509ebb70b5ea3e9e34364ceae40a670f2 -->
@ -0,0 +34,4 @@
GITLEAKS_CHECKSUMS="gitleaks_${GITLEAKS_VERSION}_checksums.txt"
curl -sSfL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/${GITLEAKS_FILE}" -o "/tmp/${GITLEAKS_FILE}"
curl -sSfL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/${GITLEAKS_CHECKSUMS}" -o "/tmp/${GITLEAKS_CHECKSUMS}"
cd /tmp && grep "${GITLEAKS_FILE}" "${GITLEAKS_CHECKSUMS}" | sha256sum -c

config [HIGH]

cd /tmp is executed in the step's shell (each run: block is a single shell invocation), so the working directory stays /tmp for the remaining commands. That means gitleaks detect --source . on line 39 scans /tmp instead of the checked-out repository, and the secret scan silently passes without ever inspecting the code. Wrap the checksum step in a subshell or use absolute paths, e.g. (cd /tmp && grep ... | sha256sum -c) and then gitleaks detect --source "$GITHUB_WORKSPACE" --verbose --redact.

**config** [HIGH] `cd /tmp` is executed in the step's shell (each `run:` block is a single shell invocation), so the working directory stays `/tmp` for the remaining commands. That means `gitleaks detect --source .` on line 39 scans `/tmp` instead of the checked-out repository, and the secret scan silently passes without ever inspecting the code. Wrap the checksum step in a subshell or use absolute paths, e.g. `(cd /tmp && grep ... | sha256sum -c)` and then `gitleaks detect --source "$GITHUB_WORKSPACE" --verbose --redact`. <!-- wuming:sha256:3cb84fa7f867c850c7f5337ef3e2e29a80882a2a6aa088f37bd9af0fbf0465ae -->
AGENTS.md Outdated
@ -15,0 +13,4 @@
- **CI runs on every PR, push to `main`, and manual dispatch** (Forgejo Actions,
runner `zhuiri`): the `checks` job (nox: `ty`, `ruff`, `complexipy`, `pytest`
`pytest` matrixed on 3.12 + 3.13), a gitleaks scan, and a WuMing AI review on
PRs. After opening a PR, run ci-watch against it. The first CI run downloads

docs [MEDIUM]

Unbalanced parenthesis. The parenthetical opened on line 13 with "(Forgejo Actions," is already closed on line 14 by the ")" after zhuiri, and the second parenthetical "(nox: …" closes on line 15 with "3.13)". The ")" in "PRs)" therefore has no matching "(" and renders as a stray character, suggesting a dropped clause. Fix by ending the sentence with "PRs." (removing the stray ")"), or, if the intent was for the whole job list to sit inside one parenthetical, remove the ")" after zhuiri on line 14 so the pair matches.

**docs** [MEDIUM] Unbalanced parenthesis. The parenthetical opened on line 13 with "(Forgejo Actions," is already closed on line 14 by the ")" after `zhuiri`, and the second parenthetical "(nox: …" closes on line 15 with "3.13)". The ")" in "PRs)" therefore has no matching "(" and renders as a stray character, suggesting a dropped clause. Fix by ending the sentence with "PRs." (removing the stray ")"), or, if the intent was for the whole job list to sit inside one parenthetical, remove the ")" after `zhuiri` on line 14 so the pair matches. <!-- wuming:sha256:23db8906d6b7b6ee20909fe1f2db7c74e0f7d0cfd16c4fd62d345cfb478d6904 -->
@ -40,45 +97,13 @@ def index() -> str:
db = get_db()
user_id = 1 # Default user for now (Phase 6 will add multi-user)

security [MEDIUM]

A01: The reader route still hard-codes user_id = 1 and uses it for per-user read-state lookups without an authentication/authorization check. Once multi-user access exists, any request can read or act as the default user. Resolve the current user from the authenticated session instead of using a fixed ID.

**security** [MEDIUM] A01: The reader route still hard-codes `user_id = 1` and uses it for per-user read-state lookups without an authentication/authorization check. Once multi-user access exists, any request can read or act as the default user. Resolve the current user from the authenticated session instead of using a fixed ID. <!-- wuming:sha256:1f0e8fac89d97adb863cedbe5bd96664d3990a66318842fd3be59312744613c1 -->
🐛 Fix gitleaks job scanning /tmp instead of repo, split install step
Some checks failed
/ gitleaks (pull_request) Failing after 17s
/ checks (pull_request) Has been cancelled
/ pr-review (pull_request) Has been cancelled
d873323818
cd /tmp persisted across the multi-line run block, so gitleaks detect --source .
scanned /tmp. Split into install + run steps (matching kokopelli) and add
fetch-depth: 0 so gitleaks sees full history. Also de-nest the CI note in
AGENTS.md.

Refs #7
Author
Collaborator

.forgejo/workflows/ci.yml line 37

cd /tmp persists across the multi-line run block, so gitleaks detect --source . scans /tmp.

Fixed in d873323 — split into separate "Install gitleaks" and "Run gitleaks" steps (kokopelli's known-good shape), so the cd /tmp no longer leaks into the scan step.

[`.forgejo/workflows/ci.yml` line 37](https://forge.marvin8.zone/marvin8/yunjin/pulls/11#issuecomment-2453) > `cd /tmp` persists across the multi-line run block, so `gitleaks detect --source .` scans /tmp. ✅ Fixed in `d873323` — split into separate "Install gitleaks" and "Run gitleaks" steps (kokopelli's known-good shape), so the `cd /tmp` no longer leaks into the scan step.
Author
Collaborator

.forgejo/workflows/ci.yml line 29

actions/checkout@v4 defaults to fetch-depth: 1, so gitleaks can't walk history.

Fixed in d873323 — gitleaks job checkout now uses fetch-depth: 0.

[`.forgejo/workflows/ci.yml` line 29](https://forge.marvin8.zone/marvin8/yunjin/pulls/11#issuecomment-2454) > `actions/checkout@v4` defaults to fetch-depth: 1, so gitleaks can't walk history. ✅ Fixed in `d873323` — gitleaks job checkout now uses `fetch-depth: 0`.
Author
Collaborator

AGENTS.md line 16

Unbalanced parenthesis on the CI note.

Fixed in d873323 — the parens were actually balanced, but the nested parentheticals were genuinely hard to parse, so I de-nested the sentence (the Forgejo Actions parenthetical is now its own sentence, and the checks detail has a single paren).

[`AGENTS.md` line 16](https://forge.marvin8.zone/marvin8/yunjin/pulls/11#issuecomment-2455) > Unbalanced parenthesis on the CI note. ✅ Fixed in `d873323` — the parens were actually balanced, but the nested parentheticals were genuinely hard to parse, so I de-nested the sentence (the Forgejo Actions parenthetical is now its own sentence, and the checks detail has a single paren).
Author
Collaborator

src/yunjin/web/routes/reader.py line 40

The reader route still hard-codes user_id = 1.

🔴 Not actioned — same pre-existing placeholder as the earlier comment on this line; see the reply to comment 2440. Multi-user is Phase 6, out of scope for this PR.

[`src/yunjin/web/routes/reader.py` line 40](https://forge.marvin8.zone/marvin8/yunjin/pulls/11#issuecomment-2456) > The reader route still hard-codes `user_id = 1`. 🔴 Not actioned — same pre-existing placeholder as the earlier comment on this line; see the reply to comment 2440. Multi-user is Phase 6, out of scope for this PR.
🔧 Allowlist detect-secrets baseline from gitleaks scan
All checks were successful
/ gitleaks (pull_request) Successful in 24s
/ pr-review (pull_request) Successful in 4m42s
/ checks (pull_request) Successful in 21m34s
b2f56deb9e
gitleaks was flagging .secrets.baseline's hashed_secret identifiers
(generic-api-key false positive). Extend default gitleaks rules with an
allowlist for the baseline file.

Refs #7
forgejo-actions left a comment

WuMing

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

## WuMing Found **8** issue(s). See inline comments below.
@ -0,0 +15,4 @@
timeout-minutes: 20
if: github.event_name == 'pull_request'
steps:
- uses: https://forge.marvin8.zone/marvin8/wuming@main

config [HIGH]

The third-party action is pinned to a mutable branch (@main). Any force-push or compromise of that repository will execute arbitrary code in this job, which has access to secrets.wuming_deepseek_key. Pin the action to an immutable tag or, preferably, a full commit SHA (e.g. .../wuming@<40-char-sha>), and bump it deliberately.

**config** [HIGH] The third-party action is pinned to a mutable branch (`@main`). Any force-push or compromise of that repository will execute arbitrary code in this job, which has access to `secrets.wuming_deepseek_key`. Pin the action to an immutable tag or, preferably, a full commit SHA (e.g. `.../wuming@<40-char-sha>`), and bump it deliberately. <!-- wuming:sha256:3f39f150925d20367eeebe218e4106c676b308323e5925e5bd1de0a9617af1a1 -->

security [HIGH]

A08: The pr-review job executes a third-party action from a self-hosted Forgejo URL at the mutable @main branch. This unpinned action receives Forgejo token and DeepSeek API key credentials, so a compromised or malicious action version could exfiltrate secrets or modify the repository. Pin the action to a reviewed commit SHA and/or vendor it, and apply least-privilege credentials.

**security** [HIGH] A08: The pr-review job executes a third-party action from a self-hosted Forgejo URL at the mutable @main branch. This unpinned action receives Forgejo token and DeepSeek API key credentials, so a compromised or malicious action version could exfiltrate secrets or modify the repository. Pin the action to a reviewed commit SHA and/or vendor it, and apply least-privilege credentials. <!-- wuming:sha256:3f39f150925d20367eeebe218e4106c676b308323e5925e5bd1de0a9617af1a1 -->
@ -0,0 +17,4 @@
steps:
- uses: https://forge.marvin8.zone/marvin8/wuming@main
with:
forgejo_token: ${{ forge.token }}

config [HIGH]

forge.token is not a defined context in Forgejo Actions (there is no forge context; the available contexts are github, secrets, env, vars, job, jobs, steps, runner, strategy, matrix, needs, inputs). The expression will therefore not resolve to the workflow's automatic token and the WuMing step will receive an empty/invalid token. Use ${{ secrets.GITHUB_TOKEN }} or ${{ github.token }}.

**config** [HIGH] `forge.token` is not a defined context in Forgejo Actions (there is no `forge` context; the available contexts are github, secrets, env, vars, job, jobs, steps, runner, strategy, matrix, needs, inputs). The expression will therefore not resolve to the workflow's automatic token and the WuMing step will receive an empty/invalid token. Use `${{ secrets.GITHUB_TOKEN }}` or `${{ github.token }}`. <!-- wuming:sha256:d2072310b06612cef88b7a1977b0e9be6a85773a6147f1b7fcb029474cfb7a59 -->
@ -0,0 +39,4 @@
cd /tmp && grep "${GITLEAKS_FILE}" "${GITLEAKS_CHECKSUMS}" | sha256sum -c
tar -xzf "/tmp/${GITLEAKS_FILE}" -C /usr/local/bin gitleaks
- name: Run gitleaks
run: gitleaks detect --config .gitleaks.toml --source . --verbose --redact

config [MEDIUM]

gitleaks detect has been deprecated since gitleaks v8.19 and the job installs v8.30.1 (version taken from .pre-commit-config.yaml). On current releases it emits a deprecation warning and may be removed entirely, breaking the job. Use the replacement subcommand, e.g. gitleaks git --config .gitleaks.toml --source . --verbose --redact (or gitleaks dir for a filesystem scan).

**config** [MEDIUM] `gitleaks detect` has been deprecated since gitleaks v8.19 and the job installs v8.30.1 (version taken from .pre-commit-config.yaml). On current releases it emits a deprecation warning and may be removed entirely, breaking the job. Use the replacement subcommand, e.g. `gitleaks git --config .gitleaks.toml --source . --verbose --redact` (or `gitleaks dir` for a filesystem scan). <!-- wuming:sha256:c56c1168844d53dedd17a114db0ffc328c8195a63a7779c1bd9b64c2e88c58f8 -->
@ -46,3 +46,2 @@
- repo: https://codeberg.org/marvin8/zaojun
rev: 1.0.1
- repo: https://github.com/gitleaks/gitleaks

config [LOW]

The gitleaks hook repo is pinned via rev: v8.30.1, which pre-commit resolves by cloning the repo; the hook itself is declared upstream as language: golang, so contributors must have a Go toolchain installed for pre-commit run to work locally. Since the same rev is parsed by CI to download the release binary, keep this tag in sync and consider documenting the Go requirement (or using language: system/the published binary) so the local hook and the CI job stay consistent.

**config** [LOW] The gitleaks hook repo is pinned via `rev: v8.30.1`, which pre-commit resolves by cloning the repo; the hook itself is declared upstream as `language: golang`, so contributors must have a Go toolchain installed for `pre-commit run` to work locally. Since the same rev is parsed by CI to download the release binary, keep this tag in sync and consider documenting the Go requirement (or using `language: system`/the published binary) so the local hook and the CI job stay consistent. <!-- wuming:sha256:2a1e3bdc7ca3d72264180d6ed137cafdffdfeed40b3ab1ea3aec975c9b616280 -->
@ -13,2 +13,2 @@
- **No CI is configured on this repo.** Validation is local only: run the checks
below before committing. After opening a PR there is nothing for ci-watch to poll.
- **CI runs on every PR, push to `main`, and manual dispatch** (Forgejo Actions,
runner `zhuiri`). Jobs: `checks` (nox: `ty`, `ruff`, `complexipy`, `pytest`

docs [LOW]

The job enumeration is hard to parse: it is a comma-separated list whose first element contains a nested colon list plus an em-dash aside, so gitleaks and pr-review visually read as part of the checks job. Reformat as a bullet list, e.g. "Jobs: checks (ty, ruff, complexipy, pytest — pytest on 3.12 + 3.13), gitleaks, pr-review (WuMing, PRs only)."

**docs** [LOW] The job enumeration is hard to parse: it is a comma-separated list whose first element contains a nested colon list plus an em-dash aside, so `gitleaks` and `pr-review` visually read as part of the `checks` job. Reformat as a bullet list, e.g. "Jobs: `checks` (`ty`, `ruff`, `complexipy`, `pytest` — pytest on 3.12 + 3.13), `gitleaks`, `pr-review` (WuMing, PRs only)." <!-- wuming:sha256:a01cebaf5324dda480c123d0adcada9830366f87f3f2d310ea34847256af29f5 -->
@ -15,0 +13,4 @@
- **CI runs on every PR, push to `main`, and manual dispatch** (Forgejo Actions,
runner `zhuiri`). Jobs: `checks` (nox: `ty`, `ruff`, `complexipy`, `pytest`
`pytest` matrixed on 3.12 + 3.13), `gitleaks`, and a WuMing AI `pr-review` on
PRs. After opening a PR, run ci-watch against it. The first CI run downloads

docs [MEDIUM]

This bullet replaced the previous instruction to validate locally before committing ("run the checks below before committing") with a purely post-PR step ("run ci-watch against it"). As written, an agent's only documented validation path is after the PR is opened, which invites pushing unvalidated code. Add an explicit pre-PR instruction, e.g. "Run nox locally before opening a PR; then run ci-watch against the PR once CI starts."

**docs** [MEDIUM] This bullet replaced the previous instruction to validate locally before committing ("run the checks below before committing") with a purely post-PR step ("run ci-watch against it"). As written, an agent's only documented validation path is after the PR is opened, which invites pushing unvalidated code. Add an explicit pre-PR instruction, e.g. "Run `nox` locally before opening a PR; then run ci-watch against the PR once CI starts." <!-- wuming:sha256:32a615571bba0561daf0b34984fedc2f0786889511d858db3a16e7b920f8f317 -->
@ -24,3 +27,3 @@
| `complexipy` | Complexity check | agent |
| `prek run --all-files` | Pre-commit hooks | **human operator only** |
| `nox` | Full validation | **human operator only** |
| `nox` | Runs `ty`, `ruff`, `complexipy`, `pytest` (the CI checks job) | agent |

docs [LOW]

Equating the nox session with the CI checks job is inaccurate: the CI checks job also performs uv sync and uv build, and the CI pytest session is matrixed over 3.12 and 3.13 while a local nox run uses a single interpreter. Reword to avoid implying exact parity, e.g. "| nox | Runs the same checks as CI (ty, ruff, complexipy, pytest) | agent |".

**docs** [LOW] Equating the `nox` session with the CI `checks` job is inaccurate: the CI `checks` job also performs `uv sync` and `uv build`, and the CI `pytest` session is matrixed over 3.12 and 3.13 while a local `nox` run uses a single interpreter. Reword to avoid implying exact parity, e.g. "| `nox` | Runs the same checks as CI (`ty`, `ruff`, `complexipy`, `pytest`) | agent |". <!-- wuming:sha256:23bd09be9702ed74ff4f1ef2d3a40e0b4c2b9c90dbfdd99588171e9a1d118b33 -->
📝 Migrate to gitleaks git subcommand, clarify CI docs
All checks were successful
/ gitleaks (pull_request) Successful in 13s
/ pr-review (pull_request) Successful in 4m35s
/ checks (pull_request) Successful in 21m18s
5e08d4ae66
gitleaks detect is deprecated since v8.19; use gitleaks git. Restore the
local-validation instruction and de-nest the CI job list in AGENTS.md.

Refs #7
Author
Collaborator

.forgejo/workflows/ci.yml line 18

The third-party action is pinned to a mutable branch (@main).

🔴 Not actioned — same item as comment 2438, already decided with Marvin8: the wuming action is his own action on his own forge and @main is the established convention across his repos. See the reply to 2438.

[`.forgejo/workflows/ci.yml` line 18](https://forge.marvin8.zone/marvin8/yunjin/pulls/11#issuecomment-2466) > The third-party action is pinned to a mutable branch (@main). 🔴 Not actioned — same item as comment 2438, already decided with Marvin8: the `wuming` action is his own action on his own forge and `@main` is the established convention across his repos. See the reply to 2438.
Author
Collaborator

.forgejo/workflows/ci.yml line 20

forge.token is not a defined context in Forgejo Actions.

🔴 Not actioned — forge is Forgejo's native context (the GitHub equivalent of github); forge.token is valid on this instance. Evidence: the identical expression is used in the working CI of kokopelli and cang, and this PR's own pr-review job ran successfully with forge.token on the green run.

[`.forgejo/workflows/ci.yml` line 20](https://forge.marvin8.zone/marvin8/yunjin/pulls/11#issuecomment-2467) > `forge.token` is not a defined context in Forgejo Actions. 🔴 Not actioned — `forge` is Forgejo's native context (the GitHub equivalent of `github`); `forge.token` is valid on this instance. Evidence: the identical expression is used in the working CI of kokopelli and cang, and this PR's own `pr-review` job ran successfully with `forge.token` on the green run.
Author
Collaborator

.forgejo/workflows/ci.yml line 42

gitleaks detect has been deprecated since gitleaks v8.19.

Fixed in 5e08d4a — migrated to gitleaks git --config .gitleaks.toml --redact --verbose . (verified against gitleaks README: detect/protect deprecated in v8.19.0; git is the replacement and takes the repo path positionally).

[`.forgejo/workflows/ci.yml` line 42](https://forge.marvin8.zone/marvin8/yunjin/pulls/11#issuecomment-2468) > `gitleaks detect` has been deprecated since gitleaks v8.19. ✅ Fixed in `5e08d4a` — migrated to `gitleaks git --config .gitleaks.toml --redact --verbose .` (verified against gitleaks README: `detect`/`protect` deprecated in v8.19.0; `git` is the replacement and takes the repo path positionally).
Author
Collaborator

.pre-commit-config.yaml line 47

The gitleaks hook repo is pinned via rev which pre-commit resolves by cloning.

🔴 Not actioned — that's standard pre-commit behaviour for every hook in this file (ruff, uv, complexipy, detect-secrets all resolve by cloning a pinned rev). Same pattern as kokopelli/cang.

[`.pre-commit-config.yaml` line 47](https://forge.marvin8.zone/marvin8/yunjin/pulls/11#issuecomment-2469) > The gitleaks hook repo is pinned via rev which pre-commit resolves by cloning. 🔴 Not actioned — that's standard pre-commit behaviour for every hook in this file (ruff, uv, complexipy, detect-secrets all resolve by cloning a pinned `rev`). Same pattern as kokopelli/cang.
Author
Collaborator

AGENTS.md line 16

This bullet replaced the previous instruction to validate locally before committing with a CI-only note.

Fixed in 5e08d4a — the note now instructs "Run uv run nox locally before pushing" alongside the CI description.

[`AGENTS.md` line 16](https://forge.marvin8.zone/marvin8/yunjin/pulls/11#issuecomment-2470) > This bullet replaced the previous instruction to validate locally before committing with a CI-only note. ✅ Fixed in `5e08d4a` — the note now instructs "Run `uv run nox` locally before pushing" alongside the CI description.
Author
Collaborator

AGENTS.md line 29

Equating the nox session with the CI checks job is inaccurate.

Fixed in 5e08d4a — the nox row now reads "the nox part of the CI checks job".

[`AGENTS.md` line 29](https://forge.marvin8.zone/marvin8/yunjin/pulls/11#issuecomment-2471) > Equating the nox session with the CI checks job is inaccurate. ✅ Fixed in `5e08d4a` — the nox row now reads "the nox part of the CI checks job".
Author
Collaborator

AGENTS.md line 14

The job enumeration is hard to parse.

Fixed in 5e08d4a — the three CI jobs are now a bulleted sub-list with one job per line.

[`AGENTS.md` line 14](https://forge.marvin8.zone/marvin8/yunjin/pulls/11#issuecomment-2472) > The job enumeration is hard to parse. ✅ Fixed in `5e08d4a` — the three CI jobs are now a bulleted sub-list with one job per line.
Author
Collaborator

.forgejo/workflows/ci.yml line 18

The pr-review job executes a third-party action from a self-hosted URL at a mutable @main branch.

🔴 Not actioned — duplicate of 2438/2466; the @main pin is intentional and confirmed with Marvin8 (his own action on his own forge).

[`.forgejo/workflows/ci.yml` line 18](https://forge.marvin8.zone/marvin8/yunjin/pulls/11#issuecomment-2473) > The pr-review job executes a third-party action from a self-hosted URL at a mutable @main branch. 🔴 Not actioned — duplicate of 2438/2466; the `@main` pin is intentional and confirmed with Marvin8 (his own action on his own forge).
forgejo-actions left a comment

WuMing

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

## WuMing Found **6** issue(s). See inline comments below.
@ -0,0 +10,4 @@
cancel-in-progress: true
jobs:
pr-review:

config [MEDIUM]

The workflow never declares a permissions: block. The pr-review job needs write access to the API to publish review comments; relying on the instance default token scope is fragile and grants more access than required to checks/gitleaks. Add a top-level permissions: contents: read and widen only for this job, e.g. permissions: { contents: read, pull-requests: write, issues: write }.

**config** [MEDIUM] The workflow never declares a `permissions:` block. The `pr-review` job needs write access to the API to publish review comments; relying on the instance default token scope is fragile and grants more access than required to `checks`/`gitleaks`. Add a top-level `permissions: contents: read` and widen only for this job, e.g. `permissions: { contents: read, pull-requests: write, issues: write }`. <!-- wuming:sha256:850d392881b1e138d715b0f35aa2487a0713690b8a1366fd726d4ff87273914b -->
@ -0,0 +15,4 @@
timeout-minutes: 20
if: github.event_name == 'pull_request'
steps:
- uses: https://forge.marvin8.zone/marvin8/wuming@main

config [HIGH]

The third-party review action is pinned to the mutable branch @main while being handed a repository token and a DeepSeek API key (deepseek_key). Any upstream force-push or compromise executes arbitrary code with those secrets. Pin to an immutable ref — ideally a full 40-char commit SHA (e.g. .../wuming@<sha>), or at minimum a released tag — and update it deliberately.

**config** [HIGH] The third-party review action is pinned to the mutable branch `@main` while being handed a repository token and a DeepSeek API key (`deepseek_key`). Any upstream force-push or compromise executes arbitrary code with those secrets. Pin to an immutable ref — ideally a full 40-char commit SHA (e.g. `.../wuming@<sha>`), or at minimum a released tag — and update it deliberately. <!-- wuming:sha256:59bff2dd827cca8c687a9ea39754a351d8448127304d084fc642c5e9d7d0fb61 -->

security [HIGH]

The workflow uses an external action from a moving branch (@main) and passes secrets to it (forgejo_token, wuming_deepseek_key). This is an A08 software/data integrity issue: if the wuming repository is compromised or changed, the action can exfiltrate CI secrets. Pin the action to a full commit SHA or an immutable audited tag and minimize secret exposure.

**security** [HIGH] The workflow uses an external action from a moving branch (@main) and passes secrets to it (forgejo_token, wuming_deepseek_key). This is an A08 software/data integrity issue: if the wuming repository is compromised or changed, the action can exfiltrate CI secrets. Pin the action to a full commit SHA or an immutable audited tag and minimize secret exposure. <!-- wuming:sha256:59bff2dd827cca8c687a9ea39754a351d8448127304d084fc642c5e9d7d0fb61 -->
@ -0,0 +17,4 @@
steps:
- uses: https://forge.marvin8.zone/marvin8/wuming@main
with:
forgejo_token: ${{ forge.token }}

config [HIGH]

forge is not a valid Forgejo/Gitea Actions context (the supported contexts are github, secrets, vars, env, job, steps, runner, ...). This expression resolves to an empty string, so the WuMing review action receives no Forgejo token and cannot fetch the diff or post its review. Use forgejo_token: ${{ github.token }} (or ${{ secrets.GITHUB_TOKEN }}), matching the github.* context already used on lines 9 and 16.

**config** [HIGH] `forge` is not a valid Forgejo/Gitea Actions context (the supported contexts are `github`, `secrets`, `vars`, `env`, `job`, `steps`, `runner`, ...). This expression resolves to an empty string, so the WuMing review action receives no Forgejo token and cannot fetch the diff or post its review. Use `forgejo_token: ${{ github.token }}` (or `${{ secrets.GITHUB_TOKEN }}`), matching the `github.*` context already used on lines 9 and 16. <!-- wuming:sha256:b5544c2cc88e643eb20009d423b833d95fa6b22d6ce06625f0f23cff71789d24 -->
@ -0,0 +37,4 @@
curl -sSfL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/${GITLEAKS_FILE}" -o "/tmp/${GITLEAKS_FILE}"
curl -sSfL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/${GITLEAKS_CHECKSUMS}" -o "/tmp/${GITLEAKS_CHECKSUMS}"
cd /tmp && grep "${GITLEAKS_FILE}" "${GITLEAKS_CHECKSUMS}" | sha256sum -c
tar -xzf "/tmp/${GITLEAKS_FILE}" -C /usr/local/bin gitleaks

config [LOW]

The gitleaks tarball is extracted straight into /usr/local/bin, which requires a privileged/root runner. If the zhuiri runner is not containerised as root this step fails with a permissions error. Extract to a workspace-local directory (tar -xzf ... -C "$HOME/.local/bin") and add it to PATH, or prepend sudo only if the runner guarantees passwordless sudo.

**config** [LOW] The gitleaks tarball is extracted straight into `/usr/local/bin`, which requires a privileged/root runner. If the `zhuiri` runner is not containerised as root this step fails with a permissions error. Extract to a workspace-local directory (`tar -xzf ... -C "$HOME/.local/bin"`) and add it to `PATH`, or prepend `sudo` only if the runner guarantees passwordless sudo. <!-- wuming:sha256:9549d4a60f144b2ec961b8f146bbd5336c4efa265d0377f950f1c2bea1b18f9a -->
@ -0,0 +1,8 @@
[extend]
useDefault = true
[allowlist]

config [MEDIUM]

The top-level singular [allowlist] table is the legacy form; gitleaks v8.19+ deprecated it in favour of the plural [[allowlists]] array (and the pinned version here is v8.30.1). Depending on the build this either emits a deprecation warning or is ignored, silently disabling the .secrets.baseline exclusion so detect-secrets hashes start failing the scan. Rewrite as [[allowlists]] with the same description/paths keys.

**config** [MEDIUM] The top-level singular `[allowlist]` table is the legacy form; gitleaks v8.19+ deprecated it in favour of the plural `[[allowlists]]` array (and the pinned version here is v8.30.1). Depending on the build this either emits a deprecation warning or is ignored, silently disabling the `.secrets.baseline` exclusion so detect-secrets hashes start failing the scan. Rewrite as `[[allowlists]]` with the same `description`/`paths` keys. <!-- wuming:sha256:d500dac83b792be8147f9c2c1dbcac5d68ffbf5f13b1da4438dd7a0513bae055 -->
marvin8 manually merged commit cb10edb394 into main 2026-09-10 23:25:52 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No assignees
2 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!11
No description provided.