security: scope reusable-workflow secrets (least privilege) over secrets: inherit #9

Merged
steve merged 3 commits from sec/scope-secrets into main 2026-06-28 01:17:16 +00:00
Owner

Fixes the real finding the swarm raised on the mort/executus rollout PRs: secrets: inherit over-shares — it forwards every caller secret (registry/Komodo/Discord/DB creds) to the reusable review workflow, which only needs the review ones.

Change

  • review-reusable.yml: declare on.workflow_call.secrets: (all required: false) so a caller can forward only what the reviewer uses.
  • adversarial-review.yml (gadfly's own caller) + examples/reusable.yml: replace secrets: inherit with an explicit forward of just OLLAMA_CLOUD_API_KEY / CLAUDE_CODE_OAUTH_TOKEN / findings tokens. GITEA_TOKEN stays automatic.
  • Docs (README, examples/README) updated; comments also advise pinning consumers to an immutable @<sha> instead of @main (the second finding).

Why this PR self-validates

gadfly's own caller uses a local reusable ref (./.gitea/workflows/review-reusable.yml), so this PR's own review run exercises the new explicit-secrets contract end-to-end on the act_runner. If the cloud + Claude Code models authenticate and the comment posts, the path is proven — then I'll adopt it on mort #1338 / executus #13 and pin them to this repo's merge SHA.

Advisory-only invariant unchanged.

🤖 Generated with Claude Code

Fixes the real finding the swarm raised on the mort/executus rollout PRs: **`secrets: inherit` over-shares** — it forwards *every* caller secret (registry/Komodo/Discord/DB creds) to the reusable review workflow, which only needs the review ones. ### Change - **`review-reusable.yml`**: declare `on.workflow_call.secrets:` (all `required: false`) so a caller can forward *only* what the reviewer uses. - **`adversarial-review.yml`** (gadfly's own caller) + **`examples/reusable.yml`**: replace `secrets: inherit` with an explicit forward of just `OLLAMA_CLOUD_API_KEY` / `CLAUDE_CODE_OAUTH_TOKEN` / findings tokens. `GITEA_TOKEN` stays automatic. - Docs (README, examples/README) updated; comments also advise pinning consumers to an immutable `@<sha>` instead of `@main` (the second finding). ### Why this PR self-validates gadfly's own caller uses a **local** reusable ref (`./.gitea/workflows/review-reusable.yml`), so **this PR's own review run exercises the new explicit-secrets contract end-to-end** on the act_runner. If the cloud + Claude Code models authenticate and the comment posts, the path is proven — *then* I'll adopt it on mort #1338 / executus #13 and pin them to this repo's merge SHA. Advisory-only invariant unchanged. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
steve added 1 commit 2026-06-28 00:45:32 +00:00
security: scope reusable-workflow secrets (least privilege) over secrets: inherit
Adversarial Review (Gadfly) / review (pull_request) Failing after 2s
Build & push image / build-and-push (pull_request) Successful in 6s
f06fe5ef72
The swarm (reviewing the mort/executus rollout PRs) correctly flagged that
`secrets: inherit` forwards EVERY caller secret to the reusable review
workflow — registry/deploy/db creds the reviewer never touches. Fix:

- review-reusable.yml: declare workflow_call.secrets (all optional) so a
  caller can forward only what the reviewer needs.
- adversarial-review.yml (gadfly's own caller) + examples/reusable.yml:
  replace `secrets: inherit` with an explicit forward of just
  OLLAMA_CLOUD_API_KEY / CLAUDE_CODE_OAUTH_TOKEN / findings tokens.
  GITEA_TOKEN stays automatic.
- Docs (README, examples) updated; also advise pinning consumers to an
  immutable @<sha> instead of @main (supply-chain, the other finding).

gadfly's own review on this PR exercises the explicit-secrets path (local
reusable ref) — validating it on the act_runner before mort/executus adopt it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
steve added 1 commit 2026-06-28 00:53:01 +00:00
fix: source GITEA_TOKEN from github.token (auto) under explicit secret forwarding
Build & push image / build-and-push (pull_request) Successful in 7s
Adversarial Review (Gadfly) / review (pull_request) Successful in 8m2s
18de9b8ebc
The first attempt failed at entrypoint.sh:61 'GITEA_TOKEN required' — with
explicit secrets (no `inherit`), secrets.GITEA_TOKEN resolves empty in the
reusable job. github.token comes from the github context (not a forwarded
secret), so it's present regardless. The forwarded provider/findings secrets
arrived correctly; only the auto-token sourcing was wrong.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
steve closed this pull request 2026-06-28 00:56:31 +00:00
steve reopened this pull request 2026-06-28 00:56:37 +00:00

🪰 Gadfly — live review status

9/9 reviewers finished · updated 2026-06-28 01:04:38Z

claude-code/opus · claude-code — done

  • security — No material issues found
  • correctness — No material issues found
  • error-handling — No material issues found

claude-code/opus:max · claude-code — done

  • security — No material issues found
  • correctness — No material issues found
  • error-handling — No material issues found

claude-code/sonnet · claude-code — done

  • security — No material issues found
  • correctness — Minor issues
  • error-handling — No material issues found

deepseek-v4-pro:cloud · ollama-cloud — done

  • security — Minor issues
  • correctness — Minor issues
  • error-handling — No material issues found

glm-5.1:cloud · ollama-cloud — done

  • security — Minor issues
  • correctness — Minor issues
  • error-handling — Minor issues

glm-5.2:cloud · ollama-cloud — done

  • security — Minor issues
  • correctness — Minor issues
  • error-handling — No material issues found

minimax-m3:cloud · ollama-cloud — done

  • security — No material issues found
  • correctness — No material issues found
  • error-handling — No material issues found

nemotron-3-super:cloud · ollama-cloud — done

  • security — No material issues found
  • correctness — No material issues found
  • error-handling — No material issues found

qwen3-coder:480b-cloud · ollama-cloud — done

  • security — No material issues found
  • correctness — No material issues found
  • error-handling — No material issues found

Live status board. Findings are posted in each model's own comment. Advisory only — does not block merge.

<!-- gadfly-status-board --> ## 🪰 Gadfly — live review status 9/9 reviewers finished · updated 2026-06-28 01:04:38Z #### `claude-code/opus` · claude-code — ✅ done - ✅ **security** — No material issues found - ✅ **correctness** — No material issues found - ✅ **error-handling** — No material issues found #### `claude-code/opus:max` · claude-code — ✅ done - ✅ **security** — No material issues found - ✅ **correctness** — No material issues found - ✅ **error-handling** — No material issues found #### `claude-code/sonnet` · claude-code — ✅ done - ✅ **security** — No material issues found - ✅ **correctness** — Minor issues - ✅ **error-handling** — No material issues found #### `deepseek-v4-pro:cloud` · ollama-cloud — ✅ done - ✅ **security** — Minor issues - ✅ **correctness** — Minor issues - ✅ **error-handling** — No material issues found #### `glm-5.1:cloud` · ollama-cloud — ✅ done - ✅ **security** — Minor issues - ✅ **correctness** — Minor issues - ✅ **error-handling** — Minor issues #### `glm-5.2:cloud` · ollama-cloud — ✅ done - ✅ **security** — Minor issues - ✅ **correctness** — Minor issues - ✅ **error-handling** — No material issues found #### `minimax-m3:cloud` · ollama-cloud — ✅ done - ✅ **security** — No material issues found - ✅ **correctness** — No material issues found - ✅ **error-handling** — No material issues found #### `nemotron-3-super:cloud` · ollama-cloud — ✅ done - ✅ **security** — No material issues found - ✅ **correctness** — No material issues found - ✅ **error-handling** — No material issues found #### `qwen3-coder:480b-cloud` · ollama-cloud — ✅ done - ✅ **security** — No material issues found - ✅ **correctness** — No material issues found - ✅ **error-handling** — No material issues found <sub>Live status board. Findings are posted in each model's own comment. Advisory only — does not block merge.</sub>

🪰 Gadfly review — glm-5.1:cloud (ollama-cloud)

Verdict: Minor issues — 3 reviewers: security, correctness, error-handling

🔒 Security — Minor issues

VERDICT: Minor issues

  • .gitea/workflows/review-reusable.yml:95 — stale comment says "via secrets: inherit": The env-block comment reads # --- provider auth (via secrets: inherit; empty if consumer unset) -, but the PR replaced secrets: inherit with explicit per-secret forwarding. The comment is now inaccurate and could mislead a future editor into reverting to secrets: inherit. It should be updated to reflect the explicit-secret-forwarding contract.

  • examples/reusable.yml:49 — example still pins @main: Lines 47–48 explicitly warn: "Pin @ to an immutable Gadfly commit (replace @main below) so a push to gadfly can't silently change the code that runs with your forwarded secrets." Yet line 49 still reads uses: steve/gadfly/.gitea/workflows/review-reusable.yml@main. The insecure default is what copy-paste users will deploy, undermining the supply-chain-integrity objective.

🎯 Correctness — Minor issues

VERDICT: Minor issues

  • .gitea/workflows/review-reusable.yml:95 — Stale comment still reads # --- provider auth (via secrets: inherit; empty if consumer unset) - but the entire point of this PR is to move away from secrets: inherit toward explicit workflow_call.secrets forwarding. The comment is misleading to future maintainers who may read it as guidance rather than the surrounding code. Should be updated to reflect the declared-secret model (e.g., via declared workflow_call secrets). Impact: cosmetic/documentation, no functional effect.

  • GITEA_TOKEN switch from secrets.GITEA_TOKENgithub.token (line 87) — Confirmed correct. github.token is a context variable always available to both caller and callee jobs, whereas secrets.GITEA_TOKEN would be empty under explicit forwarding since GITEA_TOKEN is not declared in workflow_call.secrets. No issue here.

  • All secrets.* env references in the reusable match a workflow_call.secrets declaration — Confirmed by cross-referencing lines 96–109 (env block) against lines 56–65 (declarations). Every secret the reusable uses is declared, and every declared secret is consumed. No mismatch found.

🧯 Error handling & edge cases — Minor issues

VERDICT: Minor issues

  • .gitea/workflows/review-reusable.yml:95 — Stale comment referencing secrets: inherit: The comment # --- provider auth (via secrets: inherit; empty if consumer unset) - on line 95 was not updated in this PR despite the change away from secrets: inherit to explicit workflow_call.secrets forwarding. While this doesn't cause a runtime error, it's a misleading comment in a security-sensitive section. A future maintainer reading this could reasonably believe secrets are still inherited, and make incorrect decisions about what the reusable workflow can access. The comment should be updated to reflect the explicit-secret-forwarding model (e.g., # --- provider auth (via declared workflow_call secrets; empty if caller doesn't forward) -).

Automated adversarial review by Gadfly. Advisory only — does not block merge. · ⏱️ reviewed in 1m 29s

<!-- gadfly-review:ollama:glm-5.1:cloud --> ### 🪰 Gadfly review — `glm-5.1:cloud` (ollama-cloud) **Verdict: Minor issues** — 3 reviewers: security, correctness, error-handling <details><summary><b>🔒 Security</b> — Minor issues</summary> **VERDICT: Minor issues** - **`.gitea/workflows/review-reusable.yml:95` — stale comment says "via secrets: inherit"**: The env-block comment reads `# --- provider auth (via secrets: inherit; empty if consumer unset) -`, but the PR replaced `secrets: inherit` with explicit per-secret forwarding. The comment is now inaccurate and could mislead a future editor into reverting to `secrets: inherit`. It should be updated to reflect the explicit-secret-forwarding contract. - **`examples/reusable.yml:49` — example still pins `@main`**: Lines 47–48 explicitly warn: *"Pin @<sha> to an immutable Gadfly commit (replace @main below) so a push to gadfly can't silently change the code that runs with your forwarded secrets."* Yet line 49 still reads `uses: steve/gadfly/.gitea/workflows/review-reusable.yml@main`. The insecure default is what copy-paste users will deploy, undermining the supply-chain-integrity objective. </details> <details><summary><b>🎯 Correctness</b> — Minor issues</summary> **VERDICT: Minor issues** - **`.gitea/workflows/review-reusable.yml:95`** — Stale comment still reads `# --- provider auth (via secrets: inherit; empty if consumer unset) -` but the entire point of this PR is to move away from `secrets: inherit` toward explicit `workflow_call.secrets` forwarding. The comment is misleading to future maintainers who may read it as guidance rather than the surrounding code. Should be updated to reflect the declared-secret model (e.g., `via declared workflow_call secrets`). **Impact:** cosmetic/documentation, no functional effect. - **`GITEA_TOKEN` switch from `secrets.GITEA_TOKEN` → `github.token` (line 87)** — Confirmed correct. `github.token` is a context variable always available to both caller and callee jobs, whereas `secrets.GITEA_TOKEN` would be empty under explicit forwarding since `GITEA_TOKEN` is not declared in `workflow_call.secrets`. No issue here. - **All `secrets.*` env references in the reusable match a `workflow_call.secrets` declaration** — Confirmed by cross-referencing lines 96–109 (env block) against lines 56–65 (declarations). Every secret the reusable uses is declared, and every declared secret is consumed. No mismatch found. </details> <details><summary><b>🧯 Error handling & edge cases</b> — Minor issues</summary> VERDICT: Minor issues - **`.gitea/workflows/review-reusable.yml:95` — Stale comment referencing `secrets: inherit`**: The comment `# --- provider auth (via secrets: inherit; empty if consumer unset) -` on line 95 was not updated in this PR despite the change away from `secrets: inherit` to explicit `workflow_call.secrets` forwarding. While this doesn't cause a runtime error, it's a misleading comment in a security-sensitive section. A future maintainer reading this could reasonably believe secrets are still inherited, and make incorrect decisions about what the reusable workflow can access. The comment should be updated to reflect the explicit-secret-forwarding model (e.g., `# --- provider auth (via declared workflow_call secrets; empty if caller doesn't forward) -`). </details> <sub>Automated adversarial review by Gadfly. Advisory only — does not block merge. · ⏱️ reviewed in 1m 29s</sub>

🪰 Gadfly review — claude-code/opus (claude-code)

Verdict: No material issues found — 3 reviewers: security, correctness, error-handling

🔒 Security — No material issues found

I've verified the change against the actual workflow code and the consuming scripts. Here's my security-lens review.

VERDICT: No material issues found

This PR is itself a security hardening change (replacing secrets: inherit with least-privilege explicit forwarding). I traced the token and secret flow to confirm it introduces no new exposure or authz gap:

  • GITEA_TOKEN: ${{ github.token }} swap (review-reusable.yml:87) — verified safe. github.token is the auto-provisioned job token, present regardless of secret forwarding. Its capabilities are bounded by the job's permissions: block (review-reusable.yml:69-72: contents: read, issues: write, pull-requests: write). If anything this is tighter least-privilege than the old secrets.GITEA_TOKEN path. The token is consumed exactly as before — clone URL (entrypoint.sh:126), collaborator authz gate (entrypoint.sh:81), and comment POST/PATCH (run.sh, status-board.sh). No security control lost the credential it depends on.

  • Declared-vs-referenced secrets are consistent — all 10 secrets referenced in the env: block (review-reusable.yml:96-109) are declared under on.workflow_call.secrets: (lines 56-65). No env var silently resolves to empty due to an undeclared secret, and no secret is declared-but-leaked. GADFLY_ENDPOINT_M1/M5 (which can carry an embedded provider key in their provider|url|key DSN) are correctly classified as secrets, not inputs.

  • Reduced secret blast radius — the core change is the security win: the reviewer image no longer receives the caller's registry/deploy/DB creds it never uses. The @<sha> pinning guidance added to the docs further mitigates the supply-chain risk of @main silently changing what runs with forwarded secrets. Both are correct hardening.

  • No new tainted-input path — the change touches only secret/token plumbing; untrusted fields like COMMENT_BODY/PR_BRANCH are unchanged by this diff (pre-existing, out of this PR's scope).

All findings above were verified by reading review-reusable.yml in full and grepping every GITEA_TOKEN/github.token consumer across entrypoint.sh, scripts/run.sh, and scripts/status-board.sh. Nothing in the security lane is materially wrong.

🎯 Correctness — No material issues found

I've verified the change against the actual checked-out code. This PR is config/YAML only; through the Correctness lens I checked the behavioral substance rather than the prose.

VERDICT: No material issues found

Findings (all verified by reading the files):

  • github.token substitution for the API token is consistent and complete. review-reusable.yml:87 now sets GITEA_TOKEN: ${{ github.token }}. I confirmed every downstream consumer reads the env var GITEA_TOKEN (not a secret): entrypoint.sh:61 (:? guard), :65/:81/:108/:126 (clone URL + API), and scripts/run.sh:45/:80/:89/:92/:102-103. So the rename of the source (secrets.GITEA_TOKENgithub.token) is purely on the right-hand side; the variable name the scripts depend on is unchanged. No broken reference. The reusable job declares issues: write/pull-requests: write (:69-72), which is what github.token needs to upsert the comment — correct.

  • Caller forwards only declared secrets — no workflow_call validation error. adversarial-review.yml forwards OLLAMA_CLOUD_API_KEY, CLAUDE_CODE_OAUTH_TOKEN, GADFLY_FINDINGS_URL, GADFLY_FINDINGS_TOKEN. All four are declared in review-reusable.yml:55-65. Passing an undeclared secret in a secrets: map is a hard error in Actions; here every forwarded name is declared, so this is safe.

  • The forwarded set actually covers the models this caller runs. The models: list is all *:cloud (→ OLLAMA_CLOUD_API_KEY) plus claude-code/sonnet|opus|opus:max (→ CLAUDE_CODE_OAUTH_TOKEN). Both keys are forwarded, so dropping secrets: inherit does not starve any configured model of its credential. The omitted keys (OPENAI/ANTHROPIC/GOOGLE/GADFLY_API_KEY, GADFLY_ENDPOINT_M1/M5) are unused by this model list, so their absence is correct, not a regression.

  • github.token availability — the one runtime assumption I could not statically verify. The whole change hinges on github.token being populated inside a workflow_call job on Gitea's act_runner (and secrets.GITEA_TOKEN being empty under explicit forwarding, as the comment at :83-86 claims). I can't confirm act_runner's context behavior from the repo. If github.token were empty, entrypoint.sh:61's :? guard fails fast (advisory-only invariant preserved — it won't silently mis-post). The PR's self-validation design (local ./ ref exercising this path) is the right way to confirm it; flagging only as the assumption to watch, not as a defect.

Nothing in the Correctness lane is materially wrong.

Outside my lens: the step comment at review-reusable.yml:95 ("via secrets: inherit") is now stale post-change — a docs/maintainability nit for that reviewer.

🧯 Error handling & edge cases — No material issues found

I've verified the consumers of the changed values. Here's my review through the error-handling & edge-cases lens.

VERDICT: No material issues found

This PR is almost entirely declarative YAML and docs (secret declarations, an env-source swap, comment/README updates). I checked the unhappy paths it touches against the actual scripts that consume these values:

  • Empty-token edge case is handled, and the handling is unchanged. The diff repoints GITEA_TOKEN from secrets.GITEA_TOKEN to github.token (review-reusable.yml:87). Both entrypoint.sh:61 and scripts/run.sh:45 guard with : "${GITEA_TOKEN:?GITEA_TOKEN required}", so an empty token fails fast with a clear message rather than silently making unauthenticated API calls. The diff doesn't weaken this guard — it only changes the value's source. (Verified by reading both scripts.)
  • No swallowed errors or missing cleanup introduced. No script/control-flow code changed; the API-call error handling in run.sh (|| echo '[]', || true) and entrypoint.sh is untouched.
  • Secret declaration list is consistent with consumption. The declared workflow_call.secrets (lines 56–65) exactly match the env keys mapped at lines 96–109, so no consumed-but-undeclared secret silently resolves empty beyond what was already possible. Unset optional secrets resolving to "" is the pre-existing, guarded behavior for the provider keys (each provider path tolerates its own key being empty). (Verified by cross-referencing the two blocks.)

One edge case worth the author's attention, but it is primarily a correctness question outside this lens and I could not verify it from the repo: the whole change rests on github.token being auto-populated in this act_runner's workflow_call context for all trigger event types (PR, issue_comment, manual dispatch). If it is ever empty for some event, entrypoint.sh:61 hard-exits before posting — fail-fast, not a silent error, so even the worst case is safe. The PR's self-validation run exercises only the issue_comment/PR path, so confirm the other triggers too. No error-handling defect here regardless of outcome.

Nothing in my lane is materially wrong.

Automated adversarial review by Gadfly. Advisory only — does not block merge. · ⏱️ reviewed in 3m 14s

<!-- gadfly-review:ollama:claude-code/opus --> ### 🪰 Gadfly review — `claude-code/opus` (claude-code) **Verdict: No material issues found** — 3 reviewers: security, correctness, error-handling <details><summary><b>🔒 Security</b> — No material issues found</summary> I've verified the change against the actual workflow code and the consuming scripts. Here's my security-lens review. ## VERDICT: No material issues found This PR is itself a security hardening change (replacing `secrets: inherit` with least-privilege explicit forwarding). I traced the token and secret flow to confirm it introduces no new exposure or authz gap: - **`GITEA_TOKEN: ${{ github.token }}` swap (`review-reusable.yml:87`)** — verified safe. `github.token` is the auto-provisioned job token, present regardless of secret forwarding. Its capabilities are bounded by the job's `permissions:` block (`review-reusable.yml:69-72`: `contents: read`, `issues: write`, `pull-requests: write`). If anything this is *tighter* least-privilege than the old `secrets.GITEA_TOKEN` path. The token is consumed exactly as before — clone URL (`entrypoint.sh:126`), collaborator authz gate (`entrypoint.sh:81`), and comment POST/PATCH (`run.sh`, `status-board.sh`). No security control lost the credential it depends on. - **Declared-vs-referenced secrets are consistent** — all 10 secrets referenced in the `env:` block (`review-reusable.yml:96-109`) are declared under `on.workflow_call.secrets:` (lines 56-65). No env var silently resolves to empty due to an undeclared secret, and no secret is declared-but-leaked. `GADFLY_ENDPOINT_M1/M5` (which can carry an embedded provider key in their `provider|url|key` DSN) are correctly classified as secrets, not inputs. - **Reduced secret blast radius** — the core change is the security win: the reviewer image no longer receives the caller's registry/deploy/DB creds it never uses. The `@<sha>` pinning guidance added to the docs further mitigates the supply-chain risk of `@main` silently changing what runs with forwarded secrets. Both are correct hardening. - **No new tainted-input path** — the change touches only secret/token plumbing; untrusted fields like `COMMENT_BODY`/`PR_BRANCH` are unchanged by this diff (pre-existing, out of this PR's scope). All findings above were verified by reading `review-reusable.yml` in full and grepping every `GITEA_TOKEN`/`github.token` consumer across `entrypoint.sh`, `scripts/run.sh`, and `scripts/status-board.sh`. Nothing in the security lane is materially wrong. </details> <details><summary><b>🎯 Correctness</b> — No material issues found</summary> I've verified the change against the actual checked-out code. This PR is config/YAML only; through the Correctness lens I checked the behavioral substance rather than the prose. ## VERDICT: No material issues found Findings (all verified by reading the files): - **`github.token` substitution for the API token is consistent and complete.** `review-reusable.yml:87` now sets `GITEA_TOKEN: ${{ github.token }}`. I confirmed every downstream consumer reads the env var `GITEA_TOKEN` (not a secret): `entrypoint.sh:61` (`:?` guard), `:65/:81/:108/:126` (clone URL + API), and `scripts/run.sh:45/:80/:89/:92/:102-103`. So the rename of the *source* (`secrets.GITEA_TOKEN` → `github.token`) is purely on the right-hand side; the variable name the scripts depend on is unchanged. No broken reference. The reusable job declares `issues: write`/`pull-requests: write` (`:69-72`), which is what `github.token` needs to upsert the comment — correct. - **Caller forwards only declared secrets — no `workflow_call` validation error.** `adversarial-review.yml` forwards `OLLAMA_CLOUD_API_KEY`, `CLAUDE_CODE_OAUTH_TOKEN`, `GADFLY_FINDINGS_URL`, `GADFLY_FINDINGS_TOKEN`. All four are declared in `review-reusable.yml:55-65`. Passing an undeclared secret in a `secrets:` map is a hard error in Actions; here every forwarded name is declared, so this is safe. - **The forwarded set actually covers the models this caller runs.** The `models:` list is all `*:cloud` (→ `OLLAMA_CLOUD_API_KEY`) plus `claude-code/sonnet|opus|opus:max` (→ `CLAUDE_CODE_OAUTH_TOKEN`). Both keys are forwarded, so dropping `secrets: inherit` does not starve any configured model of its credential. The omitted keys (`OPENAI/ANTHROPIC/GOOGLE/GADFLY_API_KEY`, `GADFLY_ENDPOINT_M1/M5`) are unused by this model list, so their absence is correct, not a regression. - **`github.token` availability — the one runtime assumption I could not statically verify.** The whole change hinges on `github.token` being populated inside a `workflow_call` job on Gitea's act_runner (and `secrets.GITEA_TOKEN` being empty under explicit forwarding, as the comment at `:83-86` claims). I can't confirm act_runner's context behavior from the repo. If `github.token` were empty, `entrypoint.sh:61`'s `:?` guard fails fast (advisory-only invariant preserved — it won't silently mis-post). The PR's self-validation design (local `./` ref exercising this path) is the right way to confirm it; flagging only as the assumption to watch, not as a defect. Nothing in the Correctness lane is materially wrong. Outside my lens: the step comment at `review-reusable.yml:95` ("via secrets: inherit") is now stale post-change — a docs/maintainability nit for that reviewer. </details> <details><summary><b>🧯 Error handling & edge cases</b> — No material issues found</summary> I've verified the consumers of the changed values. Here's my review through the error-handling & edge-cases lens. ## VERDICT: No material issues found This PR is almost entirely declarative YAML and docs (secret declarations, an env-source swap, comment/README updates). I checked the unhappy paths it touches against the actual scripts that consume these values: - **Empty-token edge case is handled, and the handling is unchanged.** The diff repoints `GITEA_TOKEN` from `secrets.GITEA_TOKEN` to `github.token` (`review-reusable.yml:87`). Both `entrypoint.sh:61` and `scripts/run.sh:45` guard with `: "${GITEA_TOKEN:?GITEA_TOKEN required}"`, so an empty token fails fast with a clear message rather than silently making unauthenticated API calls. The diff doesn't weaken this guard — it only changes the value's source. (Verified by reading both scripts.) - **No swallowed errors or missing cleanup introduced.** No script/control-flow code changed; the API-call error handling in `run.sh` (`|| echo '[]'`, `|| true`) and `entrypoint.sh` is untouched. - **Secret declaration list is consistent with consumption.** The declared `workflow_call.secrets` (lines 56–65) exactly match the env keys mapped at lines 96–109, so no consumed-but-undeclared secret silently resolves empty beyond what was already possible. Unset optional secrets resolving to `""` is the pre-existing, guarded behavior for the provider keys (each provider path tolerates its own key being empty). (Verified by cross-referencing the two blocks.) One edge case worth the author's attention, but it is **primarily a correctness question outside this lens** and I could not verify it from the repo: the whole change rests on `github.token` being auto-populated in this act_runner's `workflow_call` context for *all* trigger event types (PR, `issue_comment`, manual dispatch). If it is ever empty for some event, `entrypoint.sh:61` hard-exits before posting — fail-fast, not a silent error, so even the worst case is safe. The PR's self-validation run exercises only the `issue_comment`/PR path, so confirm the other triggers too. No error-handling defect here regardless of outcome. Nothing in my lane is materially wrong. </details> <sub>Automated adversarial review by Gadfly. Advisory only — does not block merge. · ⏱️ reviewed in 3m 14s</sub>

🪰 Gadfly review — glm-5.2:cloud (ollama-cloud)

Verdict: Minor issues — 3 reviewers: security, correctness, error-handling

🔒 Security — Minor issues

Verdict: Minor issues

Through the security (least-privilege / secret-scoping) lens, the core change is sound: replacing secrets: inherit with an explicit declared workflow_call.secrets: block and forwarding only what the reviewer uses is the correct direction. Verified against review-reusable.yml:55-65 (declared secrets, all required: false) and the two callers (adversarial-review.yml:45-49, examples/reusable.yml:53-57). The GITEA_TOKEN: ${{ github.token }} switch at review-reusable.yml:87 is also correct — the auto job token comes from the context, not a forwarded secret, so it survives the move off secrets: inherit.

Findings:

  • review-reusable.yml:95 — stale/misleading comment "via secrets: inherit". Confirmed in source: the env block reading secrets.OLLAMA_CLOUD_API_KEY etc. is still labeled # --- provider auth (via secrets: inherit; empty if consumer unset) -. After this PR those values come from the declared workflow_call.secrets under explicit forwarding, not secrets: inherit. Not a live vulnerability (the values are still scoped correctly), but the comment mis-states the trust boundary and could mislead a future maintainer into re-broadening the contract. Suggested fix: rewrite to "via declared workflow_call secrets; empty if caller doesn't forward the key."

  • examples/reusable.yml:49@main still used despite the security warning it ships. Confirmed: the PR adds a strong comment ("pin @<sha>… so a push to gadfly can't change what runs with your forwarded secrets") at lines 47-48 directly above uses: steve/gadfly/.gitea/workflows/review-reusable.yml@main at line 49. Shipping the example pinned to @main means a copy-paste consumer silently runs attacker-controllable code (a future push to this repo's review-reusable.yml) with the consumer's forwarded provider credentials in env. This is the exact supply-chain secret-exposure risk the PR's own prose warns about; the example is the template real consumers will copy verbatim. Suggested fix: either use a real immutable SHA in the example (even if it needs bumping per release) or make the @main a clearly-failing placeholder like @REPLACE-WITH-IMMUTABLE-SHA so a copied stub can't run as-is.

  • CLAUDE.md:50 — documentation still instructs secrets: inherit. Confirmed in source (line 50): an ~8-line caller + secrets: inherit (Phase 4). Not touched by the diff, but it's the in-repo design doc still describing the subscribe path that way. Minor (docs drift), but it undercuts the least-privilege guidance for anyone reading CLAUDE.md. Suggested fix: update to reference the explicit-secret forwarding form.

No blocking secret-leak issues found in my lane: the declared-secret set matches exactly the env vars consumed at review-reusable.yml:96-109 (no secret is read that isn't declared, so nothing silently falls back to broad inheritance), and github.token correctly replaces the previously-forwarded secrets.GITEA_TOKEN.

🎯 Correctness — Minor issues

VERDICT: Minor issues found

  • review-reusable.yml:95 — Stale inline comment still reads # --- provider auth (via secrets: inherit; empty if consumer unset) - directly above the provider-auth env block. The reusable workflow no longer relies on secrets: inherit; it now uses explicitly declared workflow_call.secrets that callers forward selectively. The header comment block (lines 17–22, 48–54) was updated to describe the new explicit-forwarding contract, but this inline header at line 95 was left describing the deprecated secrets: inherit path. Minor doc drift that could mislead a future editor into re-adding secrets: inherit. Verified by reading review-reusable.yml:95 against the surrounding declared-secrets block (:55–65) and the updated header comments.

The "Not issues" observations in the draft hold up: the adversarial-review.yml caller forwards exactly the secrets its model list consumes (OLLAMA_CLOUD_API_KEY for the :cloud models, CLAUDE_CODE_OAUTH_TOKEN for the claude-code/* models, plus the two findings-telemetry secrets); the omitted provider keys resolve to empty in the reusable env and are harmless. The declared secrets: block with all required: false is consistent with callers forwarding a subset, and examples/reusable.yml forwarding only OLLAMA_CLOUD_API_KEY matches its documented Ollama-Cloud-default usage.

🧯 Error handling & edge cases — No material issues found

Verdict: No material issues found

Through the error-handling / edge-case lens, the diff is config-only YAML; it adds no new code paths that swallow errors or skip cleanup. I verified the one unhappy path it touches — an empty GITEA_TOKEN:

  • entrypoint.sh:61 guards with : "${GITEA_TOKEN:?GITEA_TOKEN required}", and the env is reused for cloning (entrypoint.sh:126) and API calls (entrypoint.sh:65,81,108). The diff switches the source from secrets.GITEA_TOKEN to github.token (review-reusable.yml:87). If that context value were ever empty, the run fails loudly at line 61 rather than proceeding with a blank credential — i.e. the edge case is handled, not swallowed. That is the correct behavior for an unhappy path.

  • The newly declared workflow_call.secrets: are all required: false (review-reusable.yml:56-65), matching the "optional, resolves to empty, harmless" contract the comments describe. The caller in adversarial-review.yml:45-49 forwards only names that are declared in that list (OLLAMA_CLOUD_API_KEY, CLAUDE_CODE_OAUTH_TOKEN, GADFLY_FINDINGS_URL, GADFLY_FINDINGS_TOKEN) — so no "undeclared secret" hard error; the examples/reusable.yml:54 stub likewise forwards only OLLAMA_CLOUD_API_KEY. Verified against the declaration block.

Minor (not blocking, arguably outside this lens — comment accuracy, not a runtime edge):

  • review-reusable.yml:95 still reads # --- provider auth (via secrets: inherit; empty if consumer unset) -, but this PR moves away from secrets: inherit to declared/forwarded secrets. The stale wording could mislead a future maintainer into re-adding secrets: inherit. Suggest updating to "via declared workflow_call secrets; empty if caller doesn't forward it."

Unverified (not reported as a finding): whether github.token is reliably populated by this Gitea/act_runner version in a workflow_call — but if it were empty, the :? guard above makes the failure loud rather than silent, so it stays out of "swallowed error" territory.

Automated adversarial review by Gadfly. Advisory only — does not block merge. · ⏱️ reviewed in 1m 35s

<!-- gadfly-review:ollama:glm-5.2:cloud --> ### 🪰 Gadfly review — `glm-5.2:cloud` (ollama-cloud) **Verdict: Minor issues** — 3 reviewers: security, correctness, error-handling <details><summary><b>🔒 Security</b> — Minor issues</summary> **Verdict: Minor issues** Through the security (least-privilege / secret-scoping) lens, the core change is sound: replacing `secrets: inherit` with an explicit declared `workflow_call.secrets:` block and forwarding only what the reviewer uses is the correct direction. Verified against `review-reusable.yml:55-65` (declared secrets, all `required: false`) and the two callers (`adversarial-review.yml:45-49`, `examples/reusable.yml:53-57`). The `GITEA_TOKEN: ${{ github.token }}` switch at `review-reusable.yml:87` is also correct — the auto job token comes from the context, not a forwarded secret, so it survives the move off `secrets: inherit`. Findings: - **`review-reusable.yml:95` — stale/misleading comment "via secrets: inherit".** Confirmed in source: the env block reading `secrets.OLLAMA_CLOUD_API_KEY` etc. is still labeled `# --- provider auth (via secrets: inherit; empty if consumer unset) -`. After this PR those values come from the *declared* `workflow_call.secrets` under explicit forwarding, not `secrets: inherit`. Not a live vulnerability (the values are still scoped correctly), but the comment mis-states the trust boundary and could mislead a future maintainer into re-broadening the contract. Suggested fix: rewrite to "via declared workflow_call secrets; empty if caller doesn't forward the key." - **`examples/reusable.yml:49` — `@main` still used despite the security warning it ships.** Confirmed: the PR adds a strong comment ("pin `@<sha>`… so a push to gadfly can't change what runs with your forwarded secrets") at lines 47-48 directly above `uses: steve/gadfly/.gitea/workflows/review-reusable.yml@main` at line 49. Shipping the example pinned to `@main` means a copy-paste consumer silently runs attacker-controllable code (a future push to this repo's `review-reusable.yml`) *with the consumer's forwarded provider credentials in env*. This is the exact supply-chain secret-exposure risk the PR's own prose warns about; the example is the template real consumers will copy verbatim. Suggested fix: either use a real immutable SHA in the example (even if it needs bumping per release) or make the `@main` a clearly-failing placeholder like `@REPLACE-WITH-IMMUTABLE-SHA` so a copied stub can't run as-is. - **`CLAUDE.md:50` — documentation still instructs `secrets: inherit`.** Confirmed in source (line 50): `an ~8-line caller + `secrets: inherit` (Phase 4).` Not touched by the diff, but it's the in-repo design doc still describing the subscribe path that way. Minor (docs drift), but it undercuts the least-privilege guidance for anyone reading CLAUDE.md. Suggested fix: update to reference the explicit-secret forwarding form. No blocking secret-leak issues found in my lane: the declared-secret set matches exactly the env vars consumed at `review-reusable.yml:96-109` (no secret is read that isn't declared, so nothing silently falls back to broad inheritance), and `github.token` correctly replaces the previously-forwarded `secrets.GITEA_TOKEN`. </details> <details><summary><b>🎯 Correctness</b> — Minor issues</summary> **VERDICT: Minor issues found** - **`review-reusable.yml:95`** — Stale inline comment still reads `# --- provider auth (via secrets: inherit; empty if consumer unset) -` directly above the provider-auth env block. The reusable workflow no longer relies on `secrets: inherit`; it now uses explicitly declared `workflow_call.secrets` that callers forward selectively. The header comment block (lines 17–22, 48–54) was updated to describe the new explicit-forwarding contract, but this inline header at line 95 was left describing the deprecated `secrets: inherit` path. Minor doc drift that could mislead a future editor into re-adding `secrets: inherit`. Verified by reading `review-reusable.yml:95` against the surrounding declared-secrets block (`:55–65`) and the updated header comments. The "Not issues" observations in the draft hold up: the `adversarial-review.yml` caller forwards exactly the secrets its model list consumes (`OLLAMA_CLOUD_API_KEY` for the `:cloud` models, `CLAUDE_CODE_OAUTH_TOKEN` for the `claude-code/*` models, plus the two findings-telemetry secrets); the omitted provider keys resolve to empty in the reusable env and are harmless. The declared `secrets:` block with all `required: false` is consistent with callers forwarding a subset, and `examples/reusable.yml` forwarding only `OLLAMA_CLOUD_API_KEY` matches its documented Ollama-Cloud-default usage. </details> <details><summary><b>🧯 Error handling & edge cases</b> — No material issues found</summary> **Verdict: No material issues found** Through the error-handling / edge-case lens, the diff is config-only YAML; it adds no new code paths that swallow errors or skip cleanup. I verified the one unhappy path it touches — an empty `GITEA_TOKEN`: - `entrypoint.sh:61` guards with `: "${GITEA_TOKEN:?GITEA_TOKEN required}"`, and the env is reused for cloning (`entrypoint.sh:126`) and API calls (`entrypoint.sh:65,81,108`). The diff switches the source from `secrets.GITEA_TOKEN` to `github.token` (`review-reusable.yml:87`). If that context value were ever empty, the run **fails loudly** at line 61 rather than proceeding with a blank credential — i.e. the edge case is handled, not swallowed. That is the correct behavior for an unhappy path. - The newly declared `workflow_call.secrets:` are all `required: false` (`review-reusable.yml:56-65`), matching the "optional, resolves to empty, harmless" contract the comments describe. The caller in `adversarial-review.yml:45-49` forwards only names that are declared in that list (OLLAMA_CLOUD_API_KEY, CLAUDE_CODE_OAUTH_TOKEN, GADFLY_FINDINGS_URL, GADFLY_FINDINGS_TOKEN) — so no "undeclared secret" hard error; the `examples/reusable.yml:54` stub likewise forwards only `OLLAMA_CLOUD_API_KEY`. Verified against the declaration block. Minor (not blocking, arguably outside this lens — comment accuracy, not a runtime edge): - `review-reusable.yml:95` still reads `# --- provider auth (via secrets: inherit; empty if consumer unset) -`, but this PR moves *away* from `secrets: inherit` to declared/forwarded secrets. The stale wording could mislead a future maintainer into re-adding `secrets: inherit`. Suggest updating to "via declared workflow_call secrets; empty if caller doesn't forward it." Unverified (not reported as a finding): whether `github.token` is reliably populated by this Gitea/act_runner version in a `workflow_call` — but if it were empty, the `:?` guard above makes the failure loud rather than silent, so it stays out of "swallowed error" territory. </details> <sub>Automated adversarial review by Gadfly. Advisory only — does not block merge. · ⏱️ reviewed in 1m 35s</sub>

🪰 Gadfly review — claude-code/sonnet (claude-code)

Verdict: Minor issues — 3 reviewers: security, correctness, error-handling

🔒 Security — No material issues found

Verdict: No material issues found

I verified the actual checked-out state of all three workflow files, and the scripts that consume GITEA_TOKEN.

  • GITEA_TOKEN: ${{ github.token }} — confirmed against the full file. run.sh and status-board.sh both require GITEA_TOKEN for API calls (comment POST/PATCH, PR diff fetch). Switching from secrets.GITEA_TOKEN (a PAT that would now be absent under explicit forwarding) to github.token (the auto job-scoped token) is correct and strictly more secure: the auto-token is ephemeral, rotates per-job, and is constrained to the permissions: block (issues: write, pull-requests: write). In Gitea's workflow_call context github.token is scoped to the calling repository, so comment posting still lands on the caller's repo. Verified by reading scripts/run.sh and scripts/status-board.sh — they expect an env var named GITEA_TOKEN; the name mapping is correct.

  • Secret declarations in workflow_call.secrets:GADFLY_ENDPOINT_M1 / GADFLY_ENDPOINT_M5 are correctly typed as secrets (they can embed API keys per the DSN format); verified they appear in the secrets: block, not inputs:. All ten secrets are required: false, so an unset secret resolves to empty without breaking the job.

  • GADFLY_FINDINGS_URL / GADFLY_FINDINGS_TOKEN forwarded in adversarial-review.yml and declared in the reusable — confirmed both sides match.

  • Supply-chain note in examples/reusable.yml still uses @main as the uses: ref — but this is an intentional template placeholder; the surrounding comment explicitly directs users to replace it with an immutable SHA. This is documentation intent, not a code defect.

The change is a net improvement: it eliminates the over-broad secrets: inherit surface, replaces a long-lived PAT dependency with the ephemeral auto-token, and the declared-secrets approach limits cross-contamination of unrelated caller credentials into the review workflow.

🎯 Correctness — Minor issues

The comment at line 95 is confirmed as stated in the draft. The finding is accurate.

Minor issues

The core change is semantically correct. In Gitea Actions (and GitHub Actions), ${{ github.token }} in a workflow_call reusable workflow is always populated by the runner as the auto job token — it is part of the github context, not the secrets context, so it is present regardless of secrets: inherit. The old ${{ secrets.GITEA_TOKEN }} would be empty under explicit secret forwarding (since GITEA_TOKEN is not in the forwarded list), triggering the : "${GITEA_TOKEN:?GITEA_TOKEN required}" guard at both entrypoint.sh:61 and run.sh:45. The switch to github.token is the correct fix.

Permission scopes are coherent: both the caller (adversarial-review.yml:22-25) and the example consumer (examples/reusable.yml:31-34) declare issues: write and pull-requests: write, matching what the reusable workflow declares at review-reusable.yml:69-72. The auto token will carry those scopes into the container steps that POST comments and reactions.

The declared secrets: block (lines 55-65) and the env: mappings (lines 96-109) are consistent — every declared secret is mapped, every mapped secret is declared. Verified by reading both sections.


  • review-reusable.yml:95 — stale comment contradicts the PR's change. The comment reads:
    # --- provider auth (via secrets: inherit; empty if consumer unset) -
    
    The whole point of this PR is to move away from secrets: inherit to explicit forwarding. This comment now misleads a reader into thinking secrets: inherit is still the mechanism. It should say something like "via explicit workflow_call secret forwarding; resolves to empty if caller doesn't forward or consumer hasn't set the secret". Verified by reading the file — the comment is at line 95 of the checked-out review-reusable.yml.
🧯 Error handling & edge cases — No material issues found

All 10 declared secrets are properly wired. My investigation is complete.


Verdict: No material issues found

Through the error-handling & edge-cases lens, this change is clean:

  • GITEA_TOKEN: ${{ github.token }} switch is actually more robust. Under the old secrets.GITEA_TOKEN with explicit forwarding, that secret resolves to empty — which hits the ${GITEA_TOKEN:?GITEA_TOKEN required} hard-exit in entrypoint.sh and run.sh. github.token is always populated in a workflow_call context (it carries the caller's auto-job token), so the mandatory-token guard can never trip on a missing value anymore. (Verified: entrypoint.sh and run.sh both use ${GITEA_TOKEN:?…} as an immediate-exit guard.)

  • All 10 declared secrets have corresponding env-var mappings. Every on.workflow_call.secrets entry is referenced in the job's env: block. None are declared but silently dropped. (Verified: lines 96–109 of review-reusable.yml.)

  • Empty-secret resolution is already handled gracefully. Provider keys are all required: false; run.sh already checks for a missing Ollama Cloud key (and similar) before running, emitting a warning comment rather than crashing. Unset secrets become empty env vars — which is the existing "this provider isn't configured" path. Nothing new is broken here.

  • No new unhandled error paths. The diff touches only YAML wiring and doc comments — no new logic in entrypoint.sh, run.sh, or the Go binary. All unhappy paths (empty token, missing API key, curl failure) were pre-existing and remain unchanged.

Automated adversarial review by Gadfly. Advisory only — does not block merge. · ⏱️ reviewed in 7m 54s

<!-- gadfly-review:ollama:claude-code/sonnet --> ### 🪰 Gadfly review — `claude-code/sonnet` (claude-code) **Verdict: Minor issues** — 3 reviewers: security, correctness, error-handling <details><summary><b>🔒 Security</b> — No material issues found</summary> **Verdict: No material issues found** I verified the actual checked-out state of all three workflow files, and the scripts that consume `GITEA_TOKEN`. - **`GITEA_TOKEN: ${{ github.token }}`** — confirmed against the full file. `run.sh` and `status-board.sh` both require `GITEA_TOKEN` for API calls (comment POST/PATCH, PR diff fetch). Switching from `secrets.GITEA_TOKEN` (a PAT that would now be absent under explicit forwarding) to `github.token` (the auto job-scoped token) is correct and strictly *more* secure: the auto-token is ephemeral, rotates per-job, and is constrained to the `permissions:` block (`issues: write`, `pull-requests: write`). In Gitea's `workflow_call` context `github.token` is scoped to the calling repository, so comment posting still lands on the caller's repo. Verified by reading `scripts/run.sh` and `scripts/status-board.sh` — they expect an env var named `GITEA_TOKEN`; the name mapping is correct. - **Secret declarations in `workflow_call.secrets:`** — `GADFLY_ENDPOINT_M1` / `GADFLY_ENDPOINT_M5` are correctly typed as secrets (they can embed API keys per the DSN format); verified they appear in the `secrets:` block, not `inputs:`. All ten secrets are `required: false`, so an unset secret resolves to empty without breaking the job. - **`GADFLY_FINDINGS_URL` / `GADFLY_FINDINGS_TOKEN`** forwarded in `adversarial-review.yml` and declared in the reusable — confirmed both sides match. - **Supply-chain note in examples/reusable.yml** still uses `@main` as the `uses:` ref — but this is an intentional template placeholder; the surrounding comment explicitly directs users to replace it with an immutable SHA. This is documentation intent, not a code defect. The change is a net improvement: it eliminates the over-broad `secrets: inherit` surface, replaces a long-lived PAT dependency with the ephemeral auto-token, and the declared-secrets approach limits cross-contamination of unrelated caller credentials into the review workflow. </details> <details><summary><b>🎯 Correctness</b> — Minor issues</summary> The comment at line 95 is confirmed as stated in the draft. The finding is accurate. **Minor issues** The core change is semantically correct. In Gitea Actions (and GitHub Actions), `${{ github.token }}` in a `workflow_call` reusable workflow is always populated by the runner as the auto job token — it is part of the `github` context, not the `secrets` context, so it is present regardless of `secrets: inherit`. The old `${{ secrets.GITEA_TOKEN }}` would be empty under explicit secret forwarding (since GITEA_TOKEN is not in the forwarded list), triggering the `: "${GITEA_TOKEN:?GITEA_TOKEN required}"` guard at both `entrypoint.sh:61` and `run.sh:45`. The switch to `github.token` is the correct fix. Permission scopes are coherent: both the caller (`adversarial-review.yml:22-25`) and the example consumer (`examples/reusable.yml:31-34`) declare `issues: write` and `pull-requests: write`, matching what the reusable workflow declares at `review-reusable.yml:69-72`. The auto token will carry those scopes into the container steps that POST comments and reactions. The declared `secrets:` block (lines 55-65) and the `env:` mappings (lines 96-109) are consistent — every declared secret is mapped, every mapped secret is declared. Verified by reading both sections. --- - **`review-reusable.yml:95` — stale comment contradicts the PR's change.** The comment reads: ```yaml # --- provider auth (via secrets: inherit; empty if consumer unset) - ``` The whole point of this PR is to move *away* from `secrets: inherit` to explicit forwarding. This comment now misleads a reader into thinking `secrets: inherit` is still the mechanism. It should say something like "via explicit workflow_call secret forwarding; resolves to empty if caller doesn't forward or consumer hasn't set the secret". Verified by reading the file — the comment is at line 95 of the checked-out `review-reusable.yml`. </details> <details><summary><b>🧯 Error handling & edge cases</b> — No material issues found</summary> All 10 declared secrets are properly wired. My investigation is complete. --- **Verdict: No material issues found** Through the error-handling & edge-cases lens, this change is clean: - **`GITEA_TOKEN: ${{ github.token }}` switch is actually more robust.** Under the old `secrets.GITEA_TOKEN` with explicit forwarding, that secret resolves to empty — which hits the `${GITEA_TOKEN:?GITEA_TOKEN required}` hard-exit in `entrypoint.sh` and `run.sh`. `github.token` is always populated in a `workflow_call` context (it carries the caller's auto-job token), so the mandatory-token guard can never trip on a missing value anymore. (Verified: `entrypoint.sh` and `run.sh` both use `${GITEA_TOKEN:?…}` as an immediate-exit guard.) - **All 10 declared secrets have corresponding env-var mappings.** Every `on.workflow_call.secrets` entry is referenced in the job's `env:` block. None are declared but silently dropped. (Verified: lines 96–109 of `review-reusable.yml`.) - **Empty-secret resolution is already handled gracefully.** Provider keys are all `required: false`; `run.sh` already checks for a missing Ollama Cloud key (and similar) before running, emitting a warning comment rather than crashing. Unset secrets become empty env vars — which is the existing "this provider isn't configured" path. Nothing new is broken here. - **No new unhandled error paths.** The diff touches only YAML wiring and doc comments — no new logic in `entrypoint.sh`, `run.sh`, or the Go binary. All unhappy paths (empty token, missing API key, curl failure) were pre-existing and remain unchanged. </details> <sub>Automated adversarial review by Gadfly. Advisory only — does not block merge. · ⏱️ reviewed in 7m 54s</sub>

🪰 Gadfly review — minimax-m3:cloud (ollama-cloud)

Verdict: No material issues found — 3 reviewers: security, correctness, error-handling

🔒 Security — No material issues found

I now have a complete picture for the security lens. Time to write up the review.

VERDICT: No material issues found (in my security lens)

I traced the change end-to-end against the actual code:

Verified by reading:

  • .gitea/workflows/review-reusable.yml — the new workflow_call.secrets: block (lines 55-65) is properly declared; the switch from secrets.GITEA_TOKEN to github.token (line 87) correctly reflects Gitea's auto job token (verified by grep: no other reference to a user-defined GITEA_TOKEN secret exists in the repo, only the auto token).
  • .gitea/workflows/adversarial-review.yml (lines 42-49) — correctly forwards exactly the four secrets the dogfood swarm uses, and switches from secrets: inherit to explicit forwarding.
  • examples/reusable.yml (lines 49-57) — same explicit-forward pattern; CLAUDE_CODE_OAUTH_TOKEN and the findings pair are commented out with a hint that they should be uncommented only if used.
  • entrypoint.sh:60-65: "${GITEA_TOKEN:?…}" and the Authorization: token ${GITEA_TOKEN} curl path; the env is sourced from the workflow env block, which github.token populates even under explicit secret forwarding.
  • cmd/gadfly/engine.go:216-241 (claudeEnv) and cmd/gadfly/engine_test.go:120-150 — confirmed the claude-code subprocess gets CLAUDE_CODE_OAUTH_TOKEN via the engine's keep-list (test asserts it's kept; GITEA_TOKEN and provider keys are stripped) and TestClaudeEnvFilters already pins that behavior. So CLAUDE_CODE_OAUTH_TOKEN is needed by the reusable even though it isn't used by the parent container, and the forward is correct.
  • grep for secrets: inherit across the repo — only the new doc-comments and CHANGELOG-style historical mentions remain; no live workflow still uses it.
  • grep for secrets.GITEA_TOKEN in examples/ — the non-reusable examples (adversarial-review.yml, claude-code.yml, endpoint-aliases.yml, local-ollama.yml, openai-compatible.yml) still pass secrets.GITEA_TOKEN, but that's a Gitea-Actions built-in token (not a user-defined secret), so secrets.GITEA_TOKEN resolves to the auto token there too — harmless, and out of scope for this PR (those examples aren't callers of the reusable).

What the change gets right, security-wise:

  • Eliminates over-sharing of every caller secret (registry / deploy / DB creds, etc.) into the reusable review workflow — the stated goal, and it's achieved correctly.
  • All declared workflow_call.secrets are required: false, so callers can forward a strict subset.
  • The github.token switch is correct and avoids a regression that would have emptied GITEA_TOKEN under the new explicit-forward contract (the comment at lines 83-87 documents this nicely).
  • Pinning advice (@<sha> not @main) is good defense against silent upstream code drift that would run with forwarded secrets.

One small, non-blocking observation (advisory, verified):

  • examples/reusable.yml:55-57 shows CLAUDE_CODE_OAUTH_TOKEN and the findings pair as commented-out examples under a section that the comment block (lines 9-14) describes as the secrets the reviewer needs. A copy-paste reader who follows the comment block literally won't enable CLAUDE_CODE_OAUTH_TOKEN, then if their GADFLY_MODELS includes claude-code/<model> they'll get an auth failure at runtime. The README at lines 107-109 already says the default path needs this token, and the inline examples/claude-code.yml shows the standalone path — but the slim reusable example doesn't make the "if your models include claude-code, uncomment this" condition obvious. This is a docs clarity nit rather than a security bug; happy to leave it as-is given the PR's scope.

No blocking or material issues in this lens.

🎯 Correctness — No material issues found

VERDICT: No material issues found

Through the Correctness lens, the change is a clean semantic refactor:

  • .gitea/workflows/review-reusable.yml:55-65on.workflow_call.secrets: correctly declares each secret as required: false, so an unforwarded secret resolves to empty (verified the env block at lines 96-109 expects that and the image treats empty as "use built-in default"). Verified valid YAML structure.

  • .gitea/workflows/review-reusable.yml:87 — switching GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}${{ github.token }} is the right substitution: in a workflow_call context the auto job token lives on github.token, not as a forwarded secret. The inline comment at lines 83-86 explains exactly this, and verified entrypoint.sh:61 still requires GITEA_TOKEN (the env var), so the value just has to come from a different source. Semantics preserved.

  • .gitea/workflows/adversarial-review.yml:42-49 — local uses: ./.gitea/workflows/review-reusable.yml still goes through the workflow_call contract on this Gitea (verified by the same env-block expectations), so explicit forwarding is required and now present. The 4 forwarded secrets match the swarm config (cloud + Claude Code + findings); other provider keys are intentionally not forwarded because the model list (minimax-m3:cloud,glm-5.*:cloud,…,qwen3-coder:480b-cloud,claude-code/*) doesn't reference OpenAI/Anthropic/Google/foreman endpoints. No correctness regression.

  • examples/reusable.yml:53-57 — the explicit secrets: mapping nests correctly under jobs.review; commented-out lines inside a YAML mapping are valid; examples/reusable.yml:49 still pins @main but the comment above (lines 47-48) explicitly tells the consumer to replace it with @<sha> — that's documentation, not a logic defect.

  • The non-reusable stubs (examples/adversarial-review.yml, local-ollama.yml, etc.) keep ${{ secrets.GITEA_TOKEN }} — correct, since those run as their own jobs (not workflow_call) and inherit the auto-injected runner secret normally. Not in this PR's scope but verified consistent.

No off-by-one, no wrong constant/threshold, no broken formula, no semantic regression in the secret-scoping contract.

🧯 Error handling & edge cases — No material issues found

VERDICT: No material issues found

Through the error-handling & edge-case lens, this PR is clean.

What I verified by reading the files:

  • .gitea/workflows/review-reusable.yml:87 (GITEA_TOKEN: ${{ github.token }}) — the most behavior-changing edit. Under explicit secret forwarding the old secrets.GITEA_TOKEN would resolve to empty, so the diff moves to github.token (the auto job token). The permissions: block at lines 70-72 already declares issues: write + pull-requests: write, so the token has the scopes needed to post the review comment and clone the PR. The explanatory comment at lines 83-86 makes the rationale grep-able. No error swallow, no silent empty-token fallback.
  • secrets: inherit → explicit forwarding — every declared secret is required: false, and the reused cmd/gadfly/emit.go:97-100 already guards with if base == "" { return } so an unset GADFLY_FINDINGS_URL (the most failure-prone env) cleanly disables telemetry instead of failing. The same "empty ⇒ harmless" pattern is what every other consumer key relies on, and there's no new code path that breaks when a forwarded secret is blank.
  • GITEA_TOKEN not declared as a workflow_call secret — confirmed intentional. It's an auto job token, not a forwarded secret, and github.token is the right source. A consumer still on secrets: inherit would still work because github.token is context-provided, not a declared workflow_call secret. No off-by-one / boundary risk introduced.
  • timeout_minutes input (default 45, caller passes 90) — declared as { type: number, default: 45 } at line 47, wired to timeout-minutes: ${{ inputs.timeout_minutes }} at line 77, and adversarial-review.yml:58 passes timeout_minutes: 90. Type/range handling unchanged.
  • examples/reusable.yml — the commented-out CLAUDE_CODE_OAUTH_TOKEN / findings block (lines 55-57) is documented inline as opt-in, so a copy-paste consumer that forgets to enable them just runs a smaller config rather than hitting a missing-secret panic.
  • emit.go's "never return, never exit, never touch stdout" contract at lines 92-95 is unchanged and not regressed by the env plumbing change in the workflow.
  • No new error/panic/cleanup paths in the diff; no integer overflow, nil-deref, or empty-collection risks introduced. The retry/timeout/cleanup semantics in entrypoint.sh and cmd/gadfly are untouched.

Outside my lens: the diff is correctly a security scoping change; correctness and security are covered by other reviewers.

Automated adversarial review by Gadfly. Advisory only — does not block merge. · ⏱️ reviewed in 2m 1s

<!-- gadfly-review:ollama:minimax-m3:cloud --> ### 🪰 Gadfly review — `minimax-m3:cloud` (ollama-cloud) **Verdict: No material issues found** — 3 reviewers: security, correctness, error-handling <details><summary><b>🔒 Security</b> — No material issues found</summary> I now have a complete picture for the security lens. Time to write up the review. ## VERDICT: **No material issues found** (in my security lens) I traced the change end-to-end against the actual code: **Verified by reading:** - `.gitea/workflows/review-reusable.yml` — the new `workflow_call.secrets:` block (lines 55-65) is properly declared; the switch from `secrets.GITEA_TOKEN` to `github.token` (line 87) correctly reflects Gitea's auto job token (verified by `grep`: no other reference to a user-defined `GITEA_TOKEN` secret exists in the repo, only the auto token). - `.gitea/workflows/adversarial-review.yml` (lines 42-49) — correctly forwards exactly the four secrets the dogfood swarm uses, and switches from `secrets: inherit` to explicit forwarding. - `examples/reusable.yml` (lines 49-57) — same explicit-forward pattern; `CLAUDE_CODE_OAUTH_TOKEN` and the findings pair are commented out with a hint that they should be uncommented only if used. - `entrypoint.sh:60-65` — `: "${GITEA_TOKEN:?…}"` and the `Authorization: token ${GITEA_TOKEN}` curl path; the env is sourced from the workflow env block, which `github.token` populates even under explicit secret forwarding. - `cmd/gadfly/engine.go:216-241` (`claudeEnv`) and `cmd/gadfly/engine_test.go:120-150` — confirmed the claude-code subprocess gets `CLAUDE_CODE_OAUTH_TOKEN` via the engine's keep-list (test asserts it's kept; `GITEA_TOKEN` and provider keys are stripped) and `TestClaudeEnvFilters` already pins that behavior. So `CLAUDE_CODE_OAUTH_TOKEN` is needed by the reusable even though it isn't used by the parent container, and the forward is correct. - `grep` for `secrets: inherit` across the repo — only the new doc-comments and CHANGELOG-style historical mentions remain; no live workflow still uses it. - `grep` for `secrets.GITEA_TOKEN` in `examples/` — the *non-reusable* examples (`adversarial-review.yml`, `claude-code.yml`, `endpoint-aliases.yml`, `local-ollama.yml`, `openai-compatible.yml`) still pass `secrets.GITEA_TOKEN`, but that's a Gitea-Actions *built-in* token (not a user-defined secret), so `secrets.GITEA_TOKEN` resolves to the auto token there too — harmless, and out of scope for this PR (those examples aren't callers of the reusable). **What the change gets right, security-wise:** - Eliminates over-sharing of every caller secret (registry / deploy / DB creds, etc.) into the reusable review workflow — the stated goal, and it's achieved correctly. - All declared `workflow_call.secrets` are `required: false`, so callers can forward a strict subset. - The `github.token` switch is correct and avoids a regression that would have emptied `GITEA_TOKEN` under the new explicit-forward contract (the comment at lines 83-87 documents this nicely). - Pinning advice (`@<sha>` not `@main`) is good defense against silent upstream code drift that would run with forwarded secrets. **One small, non-blocking observation (advisory, verified):** - `examples/reusable.yml:55-57` shows `CLAUDE_CODE_OAUTH_TOKEN` and the findings pair as commented-out examples under a section that the comment block (lines 9-14) describes as the secrets the reviewer *needs*. A copy-paste reader who follows the comment block literally won't enable `CLAUDE_CODE_OAUTH_TOKEN`, then if their `GADFLY_MODELS` includes `claude-code/<model>` they'll get an auth failure at runtime. The README at lines 107-109 already says the default path needs this token, and the inline `examples/claude-code.yml` shows the standalone path — but the slim reusable example doesn't make the "if your models include claude-code, uncomment this" condition obvious. This is a docs clarity nit rather than a security bug; happy to leave it as-is given the PR's scope. No blocking or material issues in this lens. </details> <details><summary><b>🎯 Correctness</b> — No material issues found</summary> ## VERDICT: No material issues found Through the **Correctness** lens, the change is a clean semantic refactor: - **`.gitea/workflows/review-reusable.yml:55-65`** — `on.workflow_call.secrets:` correctly declares each secret as `required: false`, so an unforwarded secret resolves to empty (verified the env block at lines 96-109 expects that and the image treats empty as "use built-in default"). Verified valid YAML structure. - **`.gitea/workflows/review-reusable.yml:87`** — switching `GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}` → `${{ github.token }}` is the right substitution: in a `workflow_call` context the auto job token lives on `github.token`, not as a forwarded secret. The inline comment at lines 83-86 explains exactly this, and verified `entrypoint.sh:61` still requires `GITEA_TOKEN` (the env var), so the value just has to come from a different source. Semantics preserved. - **`.gitea/workflows/adversarial-review.yml:42-49`** — local `uses: ./.gitea/workflows/review-reusable.yml` still goes through the `workflow_call` contract on this Gitea (verified by the same env-block expectations), so explicit forwarding is required and now present. The 4 forwarded secrets match the swarm config (cloud + Claude Code + findings); other provider keys are intentionally not forwarded because the model list (`minimax-m3:cloud,glm-5.*:cloud,…,qwen3-coder:480b-cloud,claude-code/*`) doesn't reference OpenAI/Anthropic/Google/foreman endpoints. No correctness regression. - **`examples/reusable.yml:53-57`** — the explicit `secrets:` mapping nests correctly under `jobs.review`; commented-out lines inside a YAML mapping are valid; `examples/reusable.yml:49` still pins `@main` but the comment above (lines 47-48) explicitly tells the consumer to replace it with `@<sha>` — that's documentation, not a logic defect. - The non-reusable stubs (`examples/adversarial-review.yml`, `local-ollama.yml`, etc.) keep `${{ secrets.GITEA_TOKEN }}` — correct, since those run as their own jobs (not `workflow_call`) and inherit the auto-injected runner secret normally. Not in this PR's scope but verified consistent. No off-by-one, no wrong constant/threshold, no broken formula, no semantic regression in the secret-scoping contract. </details> <details><summary><b>🧯 Error handling & edge cases</b> — No material issues found</summary> ## VERDICT: No material issues found Through the error-handling & edge-case lens, this PR is clean. What I verified by reading the files: - **`.gitea/workflows/review-reusable.yml:87` (`GITEA_TOKEN: ${{ github.token }}`)** — the most behavior-changing edit. Under explicit secret forwarding the old `secrets.GITEA_TOKEN` would resolve to empty, so the diff moves to `github.token` (the auto job token). The `permissions:` block at lines 70-72 already declares `issues: write` + `pull-requests: write`, so the token has the scopes needed to post the review comment and clone the PR. The explanatory comment at lines 83-86 makes the rationale grep-able. No error swallow, no silent empty-token fallback. - **`secrets: inherit` → explicit forwarding** — every declared secret is `required: false`, and the reused `cmd/gadfly/emit.go:97-100` already guards with `if base == "" { return }` so an unset `GADFLY_FINDINGS_URL` (the most failure-prone env) cleanly disables telemetry instead of failing. The same "empty ⇒ harmless" pattern is what every other consumer key relies on, and there's no new code path that breaks when a forwarded secret is blank. - **GITEA_TOKEN not declared as a workflow_call secret** — confirmed intentional. It's an auto job token, not a forwarded secret, and `github.token` is the right source. A consumer still on `secrets: inherit` would still work because `github.token` is context-provided, not a declared workflow_call secret. No off-by-one / boundary risk introduced. - **`timeout_minutes` input (default 45, caller passes 90)** — declared as `{ type: number, default: 45 }` at line 47, wired to `timeout-minutes: ${{ inputs.timeout_minutes }}` at line 77, and `adversarial-review.yml:58` passes `timeout_minutes: 90`. Type/range handling unchanged. - **`examples/reusable.yml`** — the commented-out `CLAUDE_CODE_OAUTH_TOKEN` / findings block (lines 55-57) is documented inline as opt-in, so a copy-paste consumer that forgets to enable them just runs a smaller config rather than hitting a missing-secret panic. - **`emit.go`'s "never return, never exit, never touch stdout" contract** at lines 92-95 is unchanged and not regressed by the env plumbing change in the workflow. - **No new error/panic/cleanup paths** in the diff; no integer overflow, nil-deref, or empty-collection risks introduced. The retry/timeout/cleanup semantics in `entrypoint.sh` and `cmd/gadfly` are untouched. Outside my lens: the diff is correctly a security scoping change; correctness and security are covered by other reviewers. </details> <sub>Automated adversarial review by Gadfly. Advisory only — does not block merge. · ⏱️ reviewed in 2m 1s</sub>

🪰 Gadfly review — deepseek-v4-pro:cloud (ollama-cloud)

Verdict: Minor issues — 3 reviewers: security, correctness, error-handling

🔒 Security — Minor issues

Both findings confirmed against the actual code. Outputting the corrected review.

Verdict: Minor issues

  • review-reusable.yml:95 — stale comment still says "via secrets: inherit". The env-block comment # --- provider auth (via secrets: inherit; empty if consumer unset) - is now misleading. The mechanism changed from secrets: inherit passthrough to explicit workflow_call.secrets declarations. A future maintainer reading this could be confused about how secrets actually flow into the container. Fix: s/via secrets: inherit/via workflow_call secrets/ or similar.

  • examples/reusable.yml:49 — example still references @main (mutable ref). The uses: line says @main despite the comment directly above it telling users to "Pin @<sha> to an immutable Gadfly commit (replace @main below)." If a consumer copies this file verbatim without changing the ref, a push to steve/gadfly main can silently change the code that runs with their forwarded secrets — a supply-chain risk. The comment mitigates this for attentive users, but "secure by default" would mean the example itself uses a pinned SHA (or at least @<tag>).

🎯 Correctness — Minor issues

Verdict: Minor issues

  • review-reusable.yml:87github.token may not be populated in a Gitea workflow_call reusable workflow (unverified). The old code relied on secrets.GITEA_TOKEN arriving via secrets: inherit. The new code switches to ${{ github.token }}, asserting it's "the auto job token from the github CONTEXT" and "present even without secrets: inherit." In GitHub Actions this is true — github.token is the caller's token and is available in reusable workflows. But Gitea's act_runner (nektos/act-based) is not GitHub Actions; whether it populates github.token inside a workflow_call callee is a behavioral dependency this repo has never exercised before (confirmed via grep: no other file in this repo references github.token). If act_runner leaves it empty, the review job silently loses the ability to post its comment — the PR's own self-review would fail to appear, which is the intended validation, but the code as committed bakes in an unverified assumption.
    Suggested fix: Either (a) verify github.token works in a Gitea reusable workflow before merging, or (b) declare GITEA_TOKEN as an additional workflow_call secret (required: false) and have callers forward ${{ secrets.GITEA_TOKEN }} alongside the other secrets — this keeps the auto token explicit and avoids depending on context semantics that may differ from GitHub Actions.

  • review-reusable.yml:95 — stale comment still says secrets: inherit. The env-block comment reads # --- provider auth (via secrets: inherit; empty if consumer unset) -. The secrets now arrive through the explicitly declared workflow_call.secrets block, not secrets: inherit. The comment is misleading for anyone reading the file to understand the data flow. (Verified by reading the file at line 95.)
    Suggested fix: Change to # --- provider auth (via workflow_call secrets; empty if consumer unset) -.

🧯 Error handling & edge cases — No material issues found

No material issues found

The diff is clean through the error-handling lens. Here's what I verified:

  • github.token substitution (review-reusable.yml:87): The change from secrets.GITEA_TOKEN to github.token is necessary because under explicit secret forwarding, secrets.GITEA_TOKEN would be empty (the auto job token isn't a forwarded workflow_call secret). If github.token were somehow unavailable in the reusable workflow context, entrypoint.sh:61 would fail immediately with GITEA_TOKEN required — a safe, loud failure, not a silent one. Verified by reading both files end-to-end.

  • All secrets required: false (review-reusable.yml:55-65): Unforwarded secrets resolve to empty strings. The entrypoint (entrypoint.sh:139-142) defaults findings vars to empty with ${VAR:-}, and the Go binary gates on non-empty before using any provider key. No panic or crash path from a missing secret. Verified by reading the full entrypoint and the env plumbing in the reusable workflow.

  • Caller forwards only needed secrets (adversarial-review.yml:45-49): The four forwarded secrets match what the swarm actually uses (Ollama Cloud + Claude Code + findings telemetry). The five undeclared provider secrets (OPENAI_API_KEY, ANTHROPIC_API_KEY, GOOGLE_API_KEY, GADFLY_API_KEY, GADFLY_ENDPOINT_M1/M5) correctly resolve to empty — harmless for this caller. Verified by cross-referencing the caller's models: line against the reusable workflow's env map.

  • examples/reusable.yml forwards only OLLAMA_CLOUD_API_KEY with the rest commented out — appropriate for the default Ollama Cloud use case. No error-handling gap.

  • No missing cleanup/rollback: The diff doesn't introduce any resource acquisition that would need a defer or rollback. The entrypoint's existing rm -rf "$REPO_DIR" and status-board signal-via-touchfile pattern are unchanged.

Automated adversarial review by Gadfly. Advisory only — does not block merge. · ⏱️ reviewed in 3m 1s

<!-- gadfly-review:ollama:deepseek-v4-pro:cloud --> ### 🪰 Gadfly review — `deepseek-v4-pro:cloud` (ollama-cloud) **Verdict: Minor issues** — 3 reviewers: security, correctness, error-handling <details><summary><b>🔒 Security</b> — Minor issues</summary> Both findings confirmed against the actual code. Outputting the corrected review. ## Verdict: Minor issues - **`review-reusable.yml:95` — stale comment still says "via secrets: inherit"**. The env-block comment `# --- provider auth (via secrets: inherit; empty if consumer unset) -` is now misleading. The mechanism changed from `secrets: inherit` passthrough to explicit `workflow_call.secrets` declarations. A future maintainer reading this could be confused about how secrets actually flow into the container. Fix: s/`via secrets: inherit`/`via workflow_call secrets`/ or similar. - **`examples/reusable.yml:49` — example still references `@main` (mutable ref)**. The `uses:` line says `@main` despite the comment directly above it telling users to "Pin @\<sha\> to an immutable Gadfly commit (replace @main below)." If a consumer copies this file verbatim without changing the ref, a push to `steve/gadfly` main can silently change the code that runs with their forwarded secrets — a supply-chain risk. The comment mitigates this for attentive users, but "secure by default" would mean the example itself uses a pinned SHA (or at least `@<tag>`). </details> <details><summary><b>🎯 Correctness</b> — Minor issues</summary> ## Verdict: Minor issues - **`review-reusable.yml:87` — `github.token` may not be populated in a Gitea `workflow_call` reusable workflow (unverified).** The old code relied on `secrets.GITEA_TOKEN` arriving via `secrets: inherit`. The new code switches to `${{ github.token }}`, asserting it's "the auto job token from the github CONTEXT" and "present even without `secrets: inherit`." In GitHub Actions this is true — `github.token` is the caller's token and is available in reusable workflows. But Gitea's act_runner (nektos/act-based) is not GitHub Actions; whether it populates `github.token` inside a `workflow_call` callee is a behavioral dependency this repo has never exercised before (confirmed via grep: no other file in this repo references `github.token`). If act_runner leaves it empty, the review job silently loses the ability to post its comment — the PR's own self-review would fail to appear, which is the intended validation, but the code as committed bakes in an unverified assumption. **Suggested fix:** Either (a) verify `github.token` works in a Gitea reusable workflow before merging, or (b) declare `GITEA_TOKEN` as an additional `workflow_call` secret (`required: false`) and have callers forward `${{ secrets.GITEA_TOKEN }}` alongside the other secrets — this keeps the auto token explicit and avoids depending on context semantics that may differ from GitHub Actions. - **`review-reusable.yml:95` — stale comment still says `secrets: inherit`.** The env-block comment reads `# --- provider auth (via secrets: inherit; empty if consumer unset) -`. The secrets now arrive through the explicitly declared `workflow_call.secrets` block, not `secrets: inherit`. The comment is misleading for anyone reading the file to understand the data flow. (Verified by reading the file at line 95.) **Suggested fix:** Change to `# --- provider auth (via workflow_call secrets; empty if consumer unset) -`. </details> <details><summary><b>🧯 Error handling & edge cases</b> — No material issues found</summary> **No material issues found** The diff is clean through the error-handling lens. Here's what I verified: - **`github.token` substitution** (`review-reusable.yml:87`): The change from `secrets.GITEA_TOKEN` to `github.token` is necessary because under explicit secret forwarding, `secrets.GITEA_TOKEN` would be empty (the auto job token isn't a forwarded `workflow_call` secret). If `github.token` were somehow unavailable in the reusable workflow context, `entrypoint.sh:61` would fail immediately with `GITEA_TOKEN required` — a safe, loud failure, not a silent one. Verified by reading both files end-to-end. - **All secrets `required: false`** (`review-reusable.yml:55-65`): Unforwarded secrets resolve to empty strings. The entrypoint (`entrypoint.sh:139-142`) defaults findings vars to empty with `${VAR:-}`, and the Go binary gates on non-empty before using any provider key. No panic or crash path from a missing secret. Verified by reading the full entrypoint and the env plumbing in the reusable workflow. - **Caller forwards only needed secrets** (`adversarial-review.yml:45-49`): The four forwarded secrets match what the swarm actually uses (Ollama Cloud + Claude Code + findings telemetry). The five undeclared provider secrets (`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `GOOGLE_API_KEY`, `GADFLY_API_KEY`, `GADFLY_ENDPOINT_M1/M5`) correctly resolve to empty — harmless for this caller. Verified by cross-referencing the caller's `models:` line against the reusable workflow's env map. - **`examples/reusable.yml`** forwards only `OLLAMA_CLOUD_API_KEY` with the rest commented out — appropriate for the default Ollama Cloud use case. No error-handling gap. - **No missing cleanup/rollback**: The diff doesn't introduce any resource acquisition that would need a `defer` or rollback. The entrypoint's existing `rm -rf "$REPO_DIR"` and status-board signal-via-touchfile pattern are unchanged. </details> <sub>Automated adversarial review by Gadfly. Advisory only — does not block merge. · ⏱️ reviewed in 3m 1s</sub>

🪰 Gadfly review — nemotron-3-super:cloud (ollama-cloud)

Verdict: No material issues found — 3 reviewers: security, correctness, error-handling

🔒 Security — No material issues found

No material issues found

The PR successfully implements least-privilege secret forwarding for reusable workflows, replacing secrets: inherit with explicit secret forwarding in all relevant workflows. Key security improvements verified:

  • .gitea/workflows/review-reusable.yml now declares all possible secrets with required: false, allowing callers to forward only needed secrets.
  • .gitea/workflows/adversarial-review.yml (gadfly's own workflow) now forwards only OLLAMA_CLOUD_API_KEY, CLAUDE_CODE_OAUTH_TOKEN, GADFLY_FINDINGS_URL, and GADFLY_FINDINGS_TOKEN instead of secrets: inherit, preventing unnecessary exposure of other secrets (e.g., registry/deploy/DB credentials).
  • The workflow also correctly switched GITEA_TOKEN from ${{ secrets.GITEA_TOKEN }} to ${{ github.token }} since the automatic token isn't a forwarded secret.
  • Examples and documentation updated to reflect least-privilege forwarding and advise pinning to immutable SHAs (though examples still use @main as starter kits, with guidance to pin post-validation).

All changes align with the security goal of minimizing secret exposure in reusable workflows. No unverified or insecure patterns were found in the reviewed files.

Note: The PR author notes they'll pin consumers to an immutable SHA after validating this PR (as mentioned in the description). While the examples still reference @main, this is advisory only and the local reusable workflow used by gadfly itself is not vulnerable to supply-chain tampering. No blocking security issues were identified.

🎯 Correctness — No material issues found

No material issues found

🧯 Error handling & edge cases — No material issues found

No material issues found

The changes improve security and error handling by:

  1. Fixing GITEA_TOKEN handling in review-reusable.yml: Changed from ${{ secrets.GITEA_TOKEN }} to ${{ github.token }} with explanatory comments. This prevents authentication failures when using explicit secret forwarding (since the automatic GitHub token isn't passed via secrets mechanism). Verified by reading the updated lines 83-87 in .gitea/workflows/review-reusable.yml.

  2. Least-privilege secret forwarding: Both adversarial-review.yml and examples/reusable.yml now explicitly forward only required secrets instead of using secrets: inherit, reducing exposure of unrelated secrets. Verified by reading the changes in those files.

  3. Documentation updates clarify the security implications and pinning recommendations. Verified in README.md and examples/README.md.

No error handling issues, edge cases, or missing cleanup were identified in the changes. The self-validating nature of the PR (using the updated workflow in adversarial-review.yml) further confirms the changes work correctly.

Automated adversarial review by Gadfly. Advisory only — does not block merge. · ⏱️ reviewed in 2m 29s

<!-- gadfly-review:ollama:nemotron-3-super:cloud --> ### 🪰 Gadfly review — `nemotron-3-super:cloud` (ollama-cloud) **Verdict: No material issues found** — 3 reviewers: security, correctness, error-handling <details><summary><b>🔒 Security</b> — No material issues found</summary> ## No material issues found The PR successfully implements least-privilege secret forwarding for reusable workflows, replacing `secrets: inherit` with explicit secret forwarding in all relevant workflows. Key security improvements verified: - `.gitea/workflows/review-reusable.yml` now declares all possible secrets with `required: false`, allowing callers to forward only needed secrets. - `.gitea/workflows/adversarial-review.yml` (gadfly's own workflow) now forwards only `OLLAMA_CLOUD_API_KEY`, `CLAUDE_CODE_OAUTH_TOKEN`, `GADFLY_FINDINGS_URL`, and `GADFLY_FINDINGS_TOKEN` instead of `secrets: inherit`, preventing unnecessary exposure of other secrets (e.g., registry/deploy/DB credentials). - The workflow also correctly switched `GITEA_TOKEN` from `${{ secrets.GITEA_TOKEN }}` to `${{ github.token }}` since the automatic token isn't a forwarded secret. - Examples and documentation updated to reflect least-privilege forwarding and advise pinning to immutable SHAs (though examples still use `@main` as starter kits, with guidance to pin post-validation). All changes align with the security goal of minimizing secret exposure in reusable workflows. No unverified or insecure patterns were found in the reviewed files. **Note**: The PR author notes they'll pin consumers to an immutable SHA after validating this PR (as mentioned in the description). While the examples still reference `@main`, this is advisory only and the local reusable workflow used by gadfly itself is not vulnerable to supply-chain tampering. No blocking security issues were identified. </details> <details><summary><b>🎯 Correctness</b> — No material issues found</summary> No material issues found </details> <details><summary><b>🧯 Error handling & edge cases</b> — No material issues found</summary> No material issues found The changes improve security and error handling by: 1. Fixing GITEA_TOKEN handling in review-reusable.yml: Changed from `${{ secrets.GITEA_TOKEN }}` to `${{ github.token }}` with explanatory comments. This prevents authentication failures when using explicit secret forwarding (since the automatic GitHub token isn't passed via secrets mechanism). Verified by reading the updated lines 83-87 in .gitea/workflows/review-reusable.yml. 2. Least-privilege secret forwarding: Both adversarial-review.yml and examples/reusable.yml now explicitly forward only required secrets instead of using `secrets: inherit`, reducing exposure of unrelated secrets. Verified by reading the changes in those files. 3. Documentation updates clarify the security implications and pinning recommendations. Verified in README.md and examples/README.md. No error handling issues, edge cases, or missing cleanup were identified in the changes. The self-validating nature of the PR (using the updated workflow in adversarial-review.yml) further confirms the changes work correctly. </details> <sub>Automated adversarial review by Gadfly. Advisory only — does not block merge. · ⏱️ reviewed in 2m 29s</sub>

🪰 Gadfly review — qwen3-coder:480b-cloud (ollama-cloud)

Verdict: No material issues found — 3 reviewers: security, correctness, error-handling

🔒 Security — No material issues found

VERDICT: No material issues found

The changes in this pull request improve the security posture by implementing the principle of least privilege for secret handling in reusable workflows. Here are the key improvements:

  1. The review-reusable.yml workflow now declares explicit secrets in workflow_call with all secrets marked as required: false, allowing callers to forward only the secrets the reviewer actually needs.

  2. The calling workflows (.gitea/workflows/adversarial-review.yml and examples/reusable.yml) have been updated to explicitly list the secrets they pass, rather than using secrets: inherit. This prevents unnecessary exposure of secrets like registry credentials, deployment keys, and database credentials to the reusable workflow.

  3. The documentation has been updated to reflect these changes and advise users to pin to immutable SHAs rather than using @main.

  4. The use of github.token instead of secrets.GITEA_TOKEN ensures that the automatic job token is correctly used even when explicit secret forwarding is employed.

These changes effectively address the security concern of over-sharing secrets while maintaining the functionality of the workflows. The implementation has been verified through the self-validation mechanism described in the PR, where the gadfly's own caller uses a local reusable ref, thus exercising the new explicit-secrets contract end-to-end.

🎯 Correctness — No material issues found

VERDICT: No material issues found

The changes correctly implement the least-privilege principle for secrets handling in GitHub Actions reusable workflows. The modifications are logically sound and address the stated security concern of over-sharing secrets. Key aspects verified:

  1. The reusable workflow now declares all possible secrets with required: false, allowing callers to explicitly forward only what's needed
  2. Callers (both internal and example) have been updated to use explicit secret forwarding instead of secrets: inherit
  3. The change from secrets.GITEA_TOKEN to github.token correctly handles the automatic job token
  4. Documentation updates accurately reflect the new secret handling approach

The implementation correctly addresses the core correctness issue of secret scope without introducing new logical errors.

🧯 Error handling & edge cases — No material issues found

No material issues found.

The changes correctly implement least-privilege secret forwarding by:

  1. Declaring explicit workflow_call.secrets in review-reusable.yml
  2. Replacing secrets: inherit with explicit secret forwarding in caller workflows
  3. Correctly switching from secrets.GITEA_TOKEN to github.token since the job token is automatically available via the GitHub context, not as a forwarded secret
  4. All edge cases around missing/empty secrets are handled gracefully since unset secrets resolve to empty strings which the downstream code already handles

The changes have been verified to work end-to-end by the PR's own review run as noted in the description. No error handling or edge case issues were introduced.

Automated adversarial review by Gadfly. Advisory only — does not block merge. · ⏱️ reviewed in 20s

<!-- gadfly-review:ollama:qwen3-coder:480b-cloud --> ### 🪰 Gadfly review — `qwen3-coder:480b-cloud` (ollama-cloud) **Verdict: No material issues found** — 3 reviewers: security, correctness, error-handling <details><summary><b>🔒 Security</b> — No material issues found</summary> VERDICT: No material issues found The changes in this pull request improve the security posture by implementing the principle of least privilege for secret handling in reusable workflows. Here are the key improvements: 1. The `review-reusable.yml` workflow now declares explicit `secrets` in `workflow_call` with all secrets marked as `required: false`, allowing callers to forward only the secrets the reviewer actually needs. 2. The calling workflows (`.gitea/workflows/adversarial-review.yml` and `examples/reusable.yml`) have been updated to explicitly list the secrets they pass, rather than using `secrets: inherit`. This prevents unnecessary exposure of secrets like registry credentials, deployment keys, and database credentials to the reusable workflow. 3. The documentation has been updated to reflect these changes and advise users to pin to immutable SHAs rather than using `@main`. 4. The use of `github.token` instead of `secrets.GITEA_TOKEN` ensures that the automatic job token is correctly used even when explicit secret forwarding is employed. These changes effectively address the security concern of over-sharing secrets while maintaining the functionality of the workflows. The implementation has been verified through the self-validation mechanism described in the PR, where the gadfly's own caller uses a local reusable ref, thus exercising the new explicit-secrets contract end-to-end. </details> <details><summary><b>🎯 Correctness</b> — No material issues found</summary> VERDICT: No material issues found The changes correctly implement the least-privilege principle for secrets handling in GitHub Actions reusable workflows. The modifications are logically sound and address the stated security concern of over-sharing secrets. Key aspects verified: 1. The reusable workflow now declares all possible secrets with `required: false`, allowing callers to explicitly forward only what's needed 2. Callers (both internal and example) have been updated to use explicit secret forwarding instead of `secrets: inherit` 3. The change from `secrets.GITEA_TOKEN` to `github.token` correctly handles the automatic job token 4. Documentation updates accurately reflect the new secret handling approach The implementation correctly addresses the core correctness issue of secret scope without introducing new logical errors. </details> <details><summary><b>🧯 Error handling & edge cases</b> — No material issues found</summary> No material issues found. The changes correctly implement least-privilege secret forwarding by: 1. Declaring explicit `workflow_call.secrets` in `review-reusable.yml` 2. Replacing `secrets: inherit` with explicit secret forwarding in caller workflows 3. Correctly switching from `secrets.GITEA_TOKEN` to `github.token` since the job token is automatically available via the GitHub context, not as a forwarded secret 4. All edge cases around missing/empty secrets are handled gracefully since unset secrets resolve to empty strings which the downstream code already handles The changes have been verified to work end-to-end by the PR's own review run as noted in the description. No error handling or edge case issues were introduced. </details> <sub>Automated adversarial review by Gadfly. Advisory only — does not block merge. · ⏱️ reviewed in 20s</sub>

🪰 Gadfly review — claude-code/opus:max (claude-code)

Verdict: No material issues found — 3 reviewers: security, correctness, error-handling

🔒 Security — No material issues found

I have enough to assess this through the Security lens. Let me note the verification I did:

  • Read review-reusable.yml (full) — confirmed the workflow_call.secrets: block, all required: false, and the GITEA_TOKEN: ${{ github.token }} change at line 87.
  • Read adversarial-review.yml (full) — confirmed the explicit secrets: forward replaced secrets: inherit, and the comment-trigger actor gate (if:) is unchanged.
  • Grepped all GITEA_TOKEN / github.token usages across scripts/, entrypoint.sh, .gitea/ — confirmed how the token is consumed (run.sh, status-board.sh, entrypoint.sh).

VERDICT: No material issues found

This PR is itself a security-hardening change (least-privilege secret scoping), and through the Security lens it holds up. Findings:

  • Secret exposure is reduced, not widened. adversarial-review.yml:45-49 forwards only 4 secrets vs. the previous secrets: inherit (which handed the reusable every caller secret — registry/deploy/db creds). The workflow_call.secrets: declarations (review-reusable.yml:55-65) are all required: false, so an unforwarded secret resolves to empty and simply disables that provider — no auth-bypass or fail-open path. Verified by reading both files.

  • secrets.GITEA_TOKEN${{ github.token }} is sound and necessary, not a privilege change. Under explicit forwarding, secrets.GITEA_TOKEN would resolve empty inside the reusable (it's not a declared/forwarded workflow_call secret), which would break comment-posting auth. github.token is the same automatic job token, sourced from the github context rather than the secrets map, and is gated by the workflow's permissions: block (contents: read, issues/pull-requests: write — line 69-72). Token scope is unchanged; the token is used only in Authorization: token headers and the clone URL (run.sh, entrypoint.sh:126), none of which this PR touches. Verified by grep + reading the consumers.

  • Authz on the secret-bearing comment trigger is intact. The issue_comment actor gate (adversarial-review.yml:36-41, restricting to steve/fizi/dazed) is preserved and mirrored by the in-container allowed_users check (line 57) — the defense-in-depth against an untrusted user triggering a secret-bearing run is unchanged by this diff.

  • No new tainted-input path. This diff is YAML secret plumbing + docs; it introduces no new handling of untrusted PR/comment input (github.event.comment.body etc. flows are unchanged), no injection sink, no SSRF, and leaks no credentials into logs or image layers.

One advisory note within-lens: the docs correctly push consumers toward pinning @<sha> instead of @main (review-reusable.yml:26-27, examples/reusable.yml), which closes the real supply-chain risk that a push to gadfly could change the code running against a consumer's forwarded secrets. That's the right hardening guidance and matches the explicit-secrets intent.

Nothing in the Security lane is materially wrong here.

🎯 Correctness — No material issues found

I've verified the change against the actual code. Let me confirm my understanding of the consumption side is complete.

The central change is GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}${{ github.token }} (review-reusable.yml:87), motivated by: under explicit forwarding, secrets.GITEA_TOKEN is no longer auto-inherited, so it would resolve empty. I verified GITEA_TOKEN is hard-required at entrypoint.sh:61 (:?GITEA_TOKEN required) and used for all API/clone/comment calls — so if it resolved empty, the run would abort before posting.


VERDICT: No material issues found

Through the correctness lens, this is a clean configuration change. My checks:

  • github.token substitution (review-reusable.yml:87) — logically correct. Under explicit secrets: forwarding, secrets.GITEA_TOKEN is not a declared workflow_call secret, so it would resolve to empty and entrypoint.sh:61 would hard-abort. Sourcing from the github context token instead is the right fix. Verified the token's only consumers (entrypoint.sh, scripts/run.sh, scripts/status-board.sh) all read the GITEA_TOKEN env var, not the secret directly, so the swap is transparent to them. The declared permissions: block (issues/PR write) governs the token's scope unchanged.

  • Forwarded-secret set matches the model fleet — verified. gadfly's own caller (adversarial-review.yml:46-49) forwards exactly OLLAMA_CLOUD_API_KEY + CLAUDE_CODE_OAUTH_TOKEN (its with.models uses *:cloud and claude-code/*) plus the two findings vars. No provider key the configured models need is dropped. All 10 declared workflow_call.secrets (lines 56-65) have matching env mappings (lines 96-109).

  • required: false on every secret — correct. An unforwarded secret resolves to empty, which the entrypoint/image treat as "use built-in default," consistent with the pre-existing input semantics.

One thing I could not verify (out of tool reach, not a defect I can confirm): that github.token actually resolves non-empty on this Gitea act_runner inside a workflow_call reusable. This is the load-bearing runtime assumption of the whole PR. The change is designed to self-validate (the local-ref review run either posts a comment or hard-fails at entrypoint.sh:61), and the prior commit message indicates it was exercised — so I'm not flagging it as a bug, only noting it's the single behavior to confirm from the live run rather than from the diff.

Stale-but-harmless: the env-block comment at review-reusable.yml:95 still says "via secrets: inherit" — cosmetic, no behavioral impact (belongs to a docs/maintainability lens, not correctness).

🧯 Error handling & edge cases — No material issues found

VERDICT: No material issues found

I reviewed this change strictly through the error handling & edge cases lens. The diff is YAML-workflow + docs only; it introduces no new code paths, and I verified that the unhappy paths it touches are already handled gracefully in the consuming scripts.

What I checked (and why it's fine):

  • Unforwarded secrets resolving to empty. The PR's central claim is that an unset/unpassed secret "resolves to empty, harmless." Verified against the code: every provider key in review-reusable.yml:96-108 flows into run.sh, which degrades rather than crashes. scripts/run.sh:131-138 posts a ⚠️ No Ollama Cloud key configured… skipped notice when OLLAMA_CLOUD_API_KEY/GADFLY_API_KEY are empty; the claude-code/antigravity branches similarly emit ⚠️ notices and continue. So a consumer who forwards too few secrets gets a skipped-lens notice, not an unhandled error. Claim holds.

  • adversarial-review.yml forwards a sufficient set. The model list uses cloud models (need OLLAMA_CLOUD_API_KEY — forwarded) and claude-code/* (need CLAUDE_CODE_OAUTH_TOKEN — forwarded). No model in the suite depends on an un-forwarded key, so even the graceful-skip path isn't triggered here. And entrypoint.sh runs each model under ... || log "model failed (continuing)" (entrypoint.sh, review_one), so one bad lane can't abort the swarm.

  • GITEA_TOKEN source switch (secrets.GITEA_TOKENgithub.token). This is the only functionally meaningful change. Both entrypoint.sh:62 and scripts/run.sh:43 guard it with : "${GITEA_TOKEN:?GITEA_TOKEN required}", so an empty value fails fast and explicitly rather than silently posting with an empty Authorization: token header. The failure-handling behavior here is unchanged from before — only the token's source changed — so no new edge case is introduced on this path.

No swallowed errors, missing cleanup, nil/empty/boundary mishandling, or new panics are introduced by this diff within my lens.

Outside my lens (one line, flagging only): whether github.token is actually non-empty inside a Gitea workflow_call job is the load-bearing assumption of this PR; if it ever resolves empty the :? guard turns the review into a non-zero exit — worth confirming under correctness/advisory-invariant review, which covers it.

Automated adversarial review by Gadfly. Advisory only — does not block merge. · ⏱️ reviewed in 3m 24s

<!-- gadfly-review:ollama:claude-code/opus:max --> ### 🪰 Gadfly review — `claude-code/opus:max` (claude-code) **Verdict: No material issues found** — 3 reviewers: security, correctness, error-handling <details><summary><b>🔒 Security</b> — No material issues found</summary> I have enough to assess this through the Security lens. Let me note the verification I did: - **Read `review-reusable.yml`** (full) — confirmed the `workflow_call.secrets:` block, all `required: false`, and the `GITEA_TOKEN: ${{ github.token }}` change at line 87. - **Read `adversarial-review.yml`** (full) — confirmed the explicit `secrets:` forward replaced `secrets: inherit`, and the comment-trigger actor gate (`if:`) is unchanged. - **Grepped all `GITEA_TOKEN` / `github.token` usages** across `scripts/`, `entrypoint.sh`, `.gitea/` — confirmed how the token is consumed (`run.sh`, `status-board.sh`, `entrypoint.sh`). --- ## VERDICT: No material issues found This PR is itself a security-hardening change (least-privilege secret scoping), and through the Security lens it holds up. Findings: - **Secret exposure is reduced, not widened.** `adversarial-review.yml:45-49` forwards only 4 secrets vs. the previous `secrets: inherit` (which handed the reusable *every* caller secret — registry/deploy/db creds). The `workflow_call.secrets:` declarations (`review-reusable.yml:55-65`) are all `required: false`, so an unforwarded secret resolves to empty and simply disables that provider — no auth-bypass or fail-open path. Verified by reading both files. - **`secrets.GITEA_TOKEN` → `${{ github.token }}` is sound and necessary, not a privilege change.** Under explicit forwarding, `secrets.GITEA_TOKEN` would resolve empty inside the reusable (it's not a declared/forwarded `workflow_call` secret), which would break comment-posting auth. `github.token` is the same automatic job token, sourced from the `github` context rather than the secrets map, and is gated by the workflow's `permissions:` block (`contents: read`, `issues/pull-requests: write` — line 69-72). Token scope is unchanged; the token is used only in `Authorization: token` headers and the clone URL (`run.sh`, `entrypoint.sh:126`), none of which this PR touches. Verified by grep + reading the consumers. - **Authz on the secret-bearing comment trigger is intact.** The `issue_comment` actor gate (`adversarial-review.yml:36-41`, restricting to `steve/fizi/dazed`) is preserved and mirrored by the in-container `allowed_users` check (line 57) — the defense-in-depth against an untrusted user triggering a secret-bearing run is unchanged by this diff. - **No new tainted-input path.** This diff is YAML secret plumbing + docs; it introduces no new handling of untrusted PR/comment input (`github.event.comment.body` etc. flows are unchanged), no injection sink, no SSRF, and leaks no credentials into logs or image layers. One advisory note within-lens: the docs correctly push consumers toward pinning `@<sha>` instead of `@main` (`review-reusable.yml:26-27`, `examples/reusable.yml`), which closes the real supply-chain risk that a push to gadfly could change the code running against a consumer's forwarded secrets. That's the right hardening guidance and matches the explicit-secrets intent. Nothing in the Security lane is materially wrong here. </details> <details><summary><b>🎯 Correctness</b> — No material issues found</summary> I've verified the change against the actual code. Let me confirm my understanding of the consumption side is complete. The central change is `GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}` → `${{ github.token }}` (review-reusable.yml:87), motivated by: under explicit forwarding, `secrets.GITEA_TOKEN` is no longer auto-inherited, so it would resolve empty. I verified `GITEA_TOKEN` is hard-required at `entrypoint.sh:61` (`:?GITEA_TOKEN required`) and used for all API/clone/comment calls — so if it resolved empty, the run would abort before posting. --- VERDICT: **No material issues found** Through the correctness lens, this is a clean configuration change. My checks: - **`github.token` substitution (review-reusable.yml:87) — logically correct.** Under explicit `secrets:` forwarding, `secrets.GITEA_TOKEN` is not a declared `workflow_call` secret, so it would resolve to empty and `entrypoint.sh:61` would hard-abort. Sourcing from the `github` *context* token instead is the right fix. Verified the token's only consumers (`entrypoint.sh`, `scripts/run.sh`, `scripts/status-board.sh`) all read the `GITEA_TOKEN` env var, not the secret directly, so the swap is transparent to them. The declared `permissions:` block (issues/PR write) governs the token's scope unchanged. - **Forwarded-secret set matches the model fleet — verified.** gadfly's own caller (adversarial-review.yml:46-49) forwards exactly `OLLAMA_CLOUD_API_KEY` + `CLAUDE_CODE_OAUTH_TOKEN` (its `with.models` uses `*:cloud` and `claude-code/*`) plus the two findings vars. No provider key the configured models need is dropped. All 10 declared `workflow_call.secrets` (lines 56-65) have matching env mappings (lines 96-109). - **`required: false` on every secret — correct.** An unforwarded secret resolves to empty, which the entrypoint/image treat as "use built-in default," consistent with the pre-existing input semantics. One thing I **could not verify** (out of tool reach, not a defect I can confirm): that `github.token` actually resolves non-empty on this Gitea `act_runner` inside a `workflow_call` reusable. This is the load-bearing runtime assumption of the whole PR. The change is *designed* to self-validate (the local-ref review run either posts a comment or hard-fails at `entrypoint.sh:61`), and the prior commit message indicates it was exercised — so I'm not flagging it as a bug, only noting it's the single behavior to confirm from the live run rather than from the diff. Stale-but-harmless: the env-block comment at review-reusable.yml:95 still says "via secrets: inherit" — cosmetic, no behavioral impact (belongs to a docs/maintainability lens, not correctness). </details> <details><summary><b>🧯 Error handling & edge cases</b> — No material issues found</summary> ## VERDICT: No material issues found I reviewed this change strictly through the **error handling & edge cases** lens. The diff is YAML-workflow + docs only; it introduces no new code paths, and I verified that the unhappy paths it *touches* are already handled gracefully in the consuming scripts. **What I checked (and why it's fine):** - **Unforwarded secrets resolving to empty.** The PR's central claim is that an unset/unpassed secret "resolves to empty, harmless." Verified against the code: every provider key in `review-reusable.yml:96-108` flows into `run.sh`, which degrades rather than crashes. `scripts/run.sh:131-138` posts a `⚠️ No Ollama Cloud key configured… skipped` notice when `OLLAMA_CLOUD_API_KEY`/`GADFLY_API_KEY` are empty; the claude-code/antigravity branches similarly emit `⚠️` notices and continue. So a consumer who forwards too few secrets gets a skipped-lens notice, not an unhandled error. Claim holds. - **`adversarial-review.yml` forwards a sufficient set.** The model list uses cloud models (need `OLLAMA_CLOUD_API_KEY` — forwarded) and `claude-code/*` (need `CLAUDE_CODE_OAUTH_TOKEN` — forwarded). No model in the suite depends on an un-forwarded key, so even the graceful-skip path isn't triggered here. And `entrypoint.sh` runs each model under `... || log "model failed (continuing)"` (`entrypoint.sh`, `review_one`), so one bad lane can't abort the swarm. - **`GITEA_TOKEN` source switch (`secrets.GITEA_TOKEN` → `github.token`).** This is the only functionally meaningful change. Both `entrypoint.sh:62` and `scripts/run.sh:43` guard it with `: "${GITEA_TOKEN:?GITEA_TOKEN required}"`, so an empty value fails fast and explicitly rather than silently posting with an empty `Authorization: token` header. The failure-handling behavior here is unchanged from before — only the token's source changed — so no new edge case is introduced on this path. No swallowed errors, missing cleanup, nil/empty/boundary mishandling, or new panics are introduced by this diff within my lens. *Outside my lens (one line, flagging only): whether `github.token` is actually non-empty inside a Gitea `workflow_call` job is the load-bearing assumption of this PR; if it ever resolves empty the `:?` guard turns the review into a non-zero exit — worth confirming under correctness/advisory-invariant review, which covers it.* </details> <sub>Automated adversarial review by Gadfly. Advisory only — does not block merge. · ⏱️ reviewed in 3m 24s</sub>
steve added 1 commit 2026-06-28 01:00:42 +00:00
docs: drop stale 'secrets: inherit' mentions (reusable comment + CLAUDE.md)
Build & push image / build-and-push (pull_request) Successful in 6s
daff6d08a1
Self-review on PR #9 flagged two doc-drift spots left over from the
explicit-secret-forwarding switch. Cosmetic.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
steve merged commit 20a5c431f2 into main 2026-06-28 01:17:16 +00:00
steve deleted branch sec/scope-secrets 2026-06-28 01:17:16 +00:00
Sign in to join this conversation.
No Reviewers
No Label
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: steve/gadfly#9