chore(codeowners): add third independent reviewer - #138
Conversation
One caveat that decides whether this PR can do its own jobThe diff is one line: Worth recording explicitly, because #124 is precisely about believing a stated gate that is not mechanically enforced: GitHub resolves required code-owner reviews from the CODEOWNERS as it exists when the check runs, so on this PR's own run the candidate third owner is not yet an established reviewer. Practically that means #138 cannot be unblocked by the reviewer it adds — it needs That also changes how the three currently-green-but-BLOCKED PRs should be sequenced. #137, #140 and this one all report every check SUCCESS and are held only by The header comment above the pattern is also worth a second look once the third owner is live: it says review is satisfiable "by the organization administrator or the designated independent reviewer" (singular). After this change there are three owners, and the sentence no longer describes the rule in force. No review submitted here — this PR's whole purpose is who is qualified to review, so the answer has to be a human decision. |
Bindy-lbb
left a comment
There was a problem hiding this comment.
Independent CODEOWNER review (Bindy-lbb) at exact head 7a532f2.
Context: issue #124 (R4) tracks adding @waterbro-8 as a third CODEOWNER. The implementation candidate was behind main, and a duplicate PR (#145) had appeared via an unusual path — a fork PR from a 2-day-old, read-only account (sun-970) carrying a commit claiming author "PeterGuy326" but committer "liyuanyang" (not an org member), unsigned, with CI stuck in action_required. Rather than approve-and-run Actions for that unverified external account, I brought this PR (#138, which already had a single self-consistent PeterGuy326-authored commit) up to date with main via the standard update-branch API (merge commit 7a532f2, authored under my own identity since I initiated it — no new content), then re-verified.
Diff is still exactly the single approved line: adding @waterbro-8 to .github/CODEOWNERS, nothing else. CI 15/15 green on the exact head above. No admin bypass, no self-approval (I am not the subject being added, and I am not the branch's last content-pusher). No P0/P1. Approving.
Recommend closing #145 as superseded by this PR once merged, and recording the resolution on #124.
|
Thanks for the clean, minimal diff. Two suggestions before merge: 1. Update the header comment The comment above the pattern currently reads: After this change there are three CODEOWNERS, not a single "designated independent reviewer". Suggest updating to something like: This can be part of the same commit or a follow-up — but leaving the comment inaccurate after merge would be confusing for future readers. 2. Clarify merge sequencing with blocked PRs As @waterbro-8 noted, #137 and #140 are also green but blocked on |
Mechanical steps run; the merge is not, and the reason is one failing required check that this PR cannot fixDoing what was asked, in order:
That check is red on
|
Refs bytefolk#124 The charter previously asserted a two-owner CODEOWNERS roster and a single tag ruleset. A third owner reached main through bytefolk#138 under the bytefolk#124 R3 decision, and refs/tags/v* is covered by a second active ruleset that also blocks creation. De-duplicate the roster against .github/CODEOWNERS, record both rulesets and what a gated tag creation means for the runbook, replace the exhaustive required-check claim with an observed-jobs enumeration, and refresh the R2 attribution to R4.
… headers (#161) ## What this changes Implements the security-header split decided for this repository on 2026-09-03: **nginx is the single authority for `X-Content-Type-Options`, `X-Frame-Options` and `Referrer-Policy`, uniformly `no-referrer`, with the proxied path de-duplicated by `proxy_hide_header`; the API keeps `Content-Security-Policy`, `X-XSS-Protection` and `Content-Disposition`.** Closes #135 (the two live proxy defects) and Closes #136 (the ownership question that defect raised). This is the in-repo redo of #144, which came from a fork and is being closed under the fork-policy decision. It is not a cherry-pick of that commit — no commit from it is imported here. ### The two defects, as measured Both were real on `main@1332bf4` and neither is visible by reading the config, which is why the test starts an nginx rather than grepping one. 1. **`/assets/` lost all three headers.** An `add_header` inside a location replaces the inherited set instead of adding to it, and that block has always had `add_header Cache-Control`. Every cached bundle shipped with no `nosniff`, no `X-Frame-Options` and no `Referrer-Policy` at all. 2. **`/v1/` sent two conflicting `Referrer-Policy` values on one response.** The proxy said `same-origin`, the API says `no-referrer`, and nginx's `add_header` appends rather than replaces, so a client received both and the effective policy depended on which one the browser kept. `X-Content-Type-Options` and `X-Frame-Options` were also duplicated (same value twice). ### Why the API keeps sending the three it no longer owns `proxy_hide_header` makes the wire value nginx's, which is what "nginx 独占" requires, without deleting `nosniff`/`DENY`/`no-referrer` from `securityHeadersMiddleware`. A `memd` reached directly — the Helm path exposes the Service, and `docs/DEPLOYMENT.md` only makes the nginx guarantee for the container — keeps its defense in depth. If the intended reading was instead that the Go middleware should stop setting them, that is a one-line change here and it should be said before merge, because the alternative silently weakens the no-proxy deployment. ## Test evidence `scripts/test_nginx_security_headers.sh` renders the shipped template with the same `envsubst` filter and variables the container entrypoint uses, runs a fake upstream that answers exactly like `securityHeadersMiddleware` does, starts nginx against the config, and reads headers off the wire across five surfaces (`/`, `/assets/`, a 404 under `/assets/`, `/v1/`, `/healthz`). | run | result | | --- | --- | | template as shipped on `main` | **11 of 28 assertions fail** — 6 missing across the two `/assets/` surfaces, `same-origin` on `/` and `/healthz`, 3 duplicated on `/v1/` | | this branch | **28 of 28 pass** | | drop the `/assets/` restatement | 3 fail (`/assets/` 200) | | drop `always` from the `/assets/` restatement | 3 fail (`/assets/` 404 only) | | drop `proxy_hide_header` | 3 fail (`/v1/` duplicates) | Each of the three fix sites is therefore individually load-bearing, and the 404 surface earns its place: it is the only one that tests `always`. The harness also refuses to report success on a partial run (the assertion count is pinned), hard-fails when a caller names an `NGINX_BIN` that is not executable, and only reports `SKIP` when no nginx was found at all — so the CI leg cannot go green by measuring nothing. Executed locally against nginx **1.27.4**, the same minor the `web/Dockerfile` pins (`nginxinc/nginx-unprivileged:1.27.4-alpine3.21`). **Measured in CI on the built image, and it did go red there first.** The first run of the new step failed exactly where this paragraph expected the risk to be: `web/Dockerfile:17` ends on `nginxinc/nginx-unprivileged`, whose own build stops at `USER 101`, so `apk add` could not write the package database -- `ERROR: Unable to lock database: Permission denied`, `exit code: 99`. Commit `1c917f96` installs the four tools in a throwaway child image that returns to uid 101 afterwards, so the harness still runs unprivileged, as the shipped container does. On that head `Deployment profiles` is green and its job log carries `all 28 security-header assertions passed`, so the leg executed the contract against the nginx that ships rather than printing `SKIP`. The harness content measured above is unchanged by that commit; it touches only the workflow step. ## Not in scope here, and worth a decision Nothing in this repository sets a `Content-Security-Policy` for the SPA itself — `default-src 'none'` is an API-only value and would break the web app if applied to it. The decided split covers the three shared headers and leaves HTML/SPA CSP unowned. This PR deliberately does not invent one. ## Links and state - `Refs` the decided split; `Closes #135` / `Closes #136` on merge. - No Go code changed, so the existing `security_headers_test.go` and `content_*` tests are unaffected. - `Web` is red on this head and identically red on `main` itself (`Audit dependencies`, `browserslist <=4.28.6`, GHSA-73wf-gq98-2v4g). That is a pre-existing repository condition rather than something this branch introduced, and the queued fix is #159; the readback is on #138. - Opening this PR is not a claim that it should be merged. Independent review is the reviewers'; I have not requested or recorded one. --------- Co-authored-by: waterbro-8 <318569545+waterbro-8@users.noreply.github.com>
Refs bytefolk#124 The charter previously asserted a two-owner CODEOWNERS roster and a single tag ruleset. A third owner reached main through bytefolk#138 under the bytefolk#124 R3 decision, and refs/tags/v* is covered by a second active ruleset that also blocks creation. De-duplicate the roster against .github/CODEOWNERS, record both rulesets and what a gated tag creation means for the runbook, replace the exhaustive required-check claim with an observed-jobs enumeration, and refresh the R2 attribution to R4.
Tracking record
Refs #124
Bounded change
The exact diff against main changes one global CODEOWNERS line:
No collaborator role, branch-protection setting, required-review count, administrator bypass, tag ruleset, tag permission, release, or repository setting changes.
Requirement trace
Validation
Review and delivery gates
This clean candidate supersedes #132 because that branch has public commits with non-noreply personal metadata. #132 is left unchanged for audit and is not force-pushed or merged.
@Bindy-lbb must provide a current-head independent CODEOWNER approval after all required CI is green. The author and last pusher will not self-approve. Normal merge authorization, merge-SHA push/main checks, verification ledger, and product acceptance still apply.