Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
4732f3e
test(security): reject noncanonical GitHub API authorities
seonghobae Sep 19, 2026
31b9b9c
fix(security): centralize GitHub API URL authority
seonghobae Sep 19, 2026
46aa717
fix(security): validate CodeQL GitHub API authority
seonghobae Sep 19, 2026
c420afc
fix(security): validate Strix GitHub API authority
seonghobae Sep 19, 2026
8d7bce4
fix(security): bind scanner suppression to authority proof
seonghobae Sep 19, 2026
9328266
chore(security): keep standalone URL guards local
seonghobae Sep 19, 2026
77c1dc2
docs(security): doctor GitHub API authority repair
seonghobae Sep 19, 2026
9844484
test(security): prove canonical GitHub API control
seonghobae Sep 19, 2026
4864f14
test(security): document URL boundary fixtures
seonghobae Sep 19, 2026
cb50c8a
docs(security): align URL authority acceptance matrix
seonghobae Sep 19, 2026
9ba43f2
fix(security): bind Semgrep ignore to guarded sink line
seonghobae Sep 19, 2026
7a00442
test(security): reject bearer-carrying GitHub redirects
seonghobae Sep 19, 2026
a2e9126
fix(security): block authenticated GitHub API redirects
seonghobae Sep 19, 2026
4c7bcbe
fix(security): block Strix GitHub API redirects
seonghobae Sep 19, 2026
e06b6dd
test(security): bind URL controls to no-redirect openers
seonghobae Sep 19, 2026
8803693
docs(security): trace GitHub redirect authority repair
seonghobae Sep 19, 2026
daea503
chore(security): restore CodeQL identity rationale comment
seonghobae Sep 19, 2026
72d6927
docs(strix): restore truncated-patch fallback rationale
seonghobae Sep 19, 2026
25f83aa
test(codeql): bind GHAS transport mocks to dedicated opener
seonghobae Sep 19, 2026
5747728
test(security): bind redirect rejection to production openers
seonghobae Sep 19, 2026
e0b0b4d
docs(security): record production-opener redirect proof
seonghobae Sep 19, 2026
033e475
docs(gap): track production-opener redirect proof
seonghobae Sep 19, 2026
663ffac
test(strix): bind transport mocks to dedicated opener
seonghobae Sep 19, 2026
9c19c6e
test(security): close GitHub opener evidence gaps
seonghobae Sep 19, 2026
1c80086
test(docs): reject unreachable opener lineage SHAs
seonghobae Sep 19, 2026
bd0f178
docs(doctoring): bind opener proof to published commit
seonghobae Sep 19, 2026
d63d7e9
docs(gap): record published opener lineage
seonghobae Sep 19, 2026
c37db54
test(security): expose vacuous G-17 lineage guard
seonghobae Sep 19, 2026
b339370
fix(security): verify G-17 evidence commit lineage
seonghobae Sep 19, 2026
b338d1e
docs(security): trace published evidence lineage validation
seonghobae Sep 19, 2026
64f33a8
test(docs): require foreign evidence owner identity
seonghobae Sep 19, 2026
d1e4380
docs(security): qualify foreign Semgrep evidence owner
seonghobae Sep 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
- Raised `hourly-review-repair.yml`'s discovery ceiling from 50 to 200 while rotating deterministic 50-PR deep-inspection windows by hourly run number. The scheduler hydrates only the selected window and stops immediately after its single dispatch, preserving access to newer PRs without quadrupling expensive review/check/comment work. See `docs/doctoring/hourly-review-repair-single-file-consolidation.md`'s 2026-09-03 follow-up.

## [Unreleased]
- **Bind GitHub REST redirect evidence to both production opener chains.** `.github#2279` now feeds a synthetic same-authority 302 through the CodeQL identity and Strix evidence clients' real module-level openers, proving the redirect target is never contacted and the bearer header is never forwarded. Removing `_RejectRedirects` from either opener makes the contract fail on the forbidden second request. Four stale Strix HTTP/transport/JSON fixtures now patch that same production seam; direct handler unit cases and standalone CodeQL materialization remain unchanged.
- **Define an evidence-backed repository README quality standard.** Added `docs/repository-readme-quality-standard.md` as the shared review contract for product-first structure, code-current onboarding, authority boundaries, durable quality signals, and repository/source/dependency license due diligence. Product repositories continue to own their own README prose; the standard is linked from the root documentation map and does not centralize or generate product claims.
- Include merge-scheduler entrypoint, core, and regression-test changes in
the existing runtime-quality workflow's trigger and suite selector. Scheduler
Expand Down
28 changes: 28 additions & 0 deletions docs/doctoring/github-api-published-lineage-authority.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# GitHub API evidence published-lineage authority

Status: Proposed repair evidence for `.github` PR #2279. Hosted exact-head security and independent review remain mandatory.

## Finding

The first published-lineage contract checked that the documentation named intended replacement SHAs and omitted two known unreachable candidates. That established expected spelling but not repository reachability. A 40-hex identifier can satisfy those assertions while referring to no commit published in the repository, so the contract did not make G-17's evidence lineage independently reconstructable.

Current-head review identified that gap and required the G-17 evidence identifiers themselves to resolve and belong to the current published branch ancestry.

## RED → repair

- Structural RED `c37db5405142da1d0fa2ae972cbacab28563c370` factors a G-17 evidence validator and adds a mutation control that substitutes the first evidence commit with the all-zero, commit-shaped identifier. The intentionally shape-only validator accepts that mutation, so the regression fails instead of giving false assurance.
- Minimal repair `b339370ed1e032527e504ca3500a2f0ca825ff77` keeps validation in the existing GitHub API authority contract. For every full SHA named in the single G-17 row it now requires both `git cat-file -e <sha>^{commit}` and `git merge-base --is-ancestor <sha> HEAD` to succeed. The negative mutation therefore fails closed, while the documented published evidence must be resolvable in current history.

The repair does not change either production HTTP client, credential handling, redirect policy, workflow threshold, or the standalone `$RUNNER_TEMP` CodeQL materialization boundary. It strengthens only executable evidence traceability.

## Invariants

1. G-17 has exactly one gap-register row.
2. Every full commit SHA named by that row resolves as a commit in the checked-out repository.
3. Every such evidence commit is an ancestor of the exact checked-out head; detached or unreachable object-store artifacts are not accepted as published lineage.
4. A syntactically valid but unreachable 40-hex identifier fails the contract.
5. Exact-head hosted CI/security gates and independent review remain distinct from this focused local invariant.

## Rejected alternatives

Checking only SHA syntax was rejected because it proves formatting rather than publication. Checking only that expected strings occur in Markdown was rejected because unreachable objects can still be named. GitHub API lookups were unnecessary for the repository-local invariant and would add network/credential authority to a test whose evidence is already in Git history.
73 changes: 73 additions & 0 deletions docs/doctoring/github-api-url-authority-2248.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# GitHub REST URL authority boundary for central CI clients

Status: Proposed repair for `.github` issue #2248; exact-head hosted security and independent review remain mandatory.

## Problem

Protected `.github/main` at `64aa08d7fa487deacd41c761c36277ca68cab6c9` contains two central CI HTTP clients:

- `scripts/ci/codeql_ghas_configuration_identity.py` for CodeQL analyses;
- `scripts/ci/strix_evidence_binding.py` for pull-request changed-file evidence.

The whole-tree Semgrep gate reported `python.lang.security.audit.dynamic-urllib-use-detected.dynamic-urllib-use-detected` at both original dynamic `urlopen` sites, and Bandit B310 reported the same class. A comment-only suppression would not prove the security premise that bearer-authenticated requests stay inside GitHub REST authority.

The first repair made the initial URL predicate executable, but exact-head CodeRabbit review then identified a second authority transition: Python's default `HTTPRedirectHandler` can construct a redirected request from the already-authorized request and preserve request headers, including `Authorization`. Validating only the first `https://api.github.com/...` URL therefore did not prevent a 3xx response from redirecting the bearer token to another authority.

## Initial URL RED → repair

Structural RED `4732f3e29ab8cd0b88506beecd4e70bdfaafb8da` requires both clients to reject, before network/file opener execution:

- `http://api.github.com/...`;
- `https://api.github.com.evil.example/...`;
- `https://api.github.com@evil.example/...`;
- `https://api.github.com:443/...` because the canonical authority is exact;
- an otherwise canonical URL carrying a fragment;
- `file:///etc/passwd`.

The production predicate requires scheme exactly `https`, network authority exactly `api.github.com`, an absolute path, and no fragment. The positive control proves exact `https://api.github.com/...` reaches the injected opener and decodes JSON normally.

A temporary shared helper candidate was removed because `codeql-scan-dispatch.yml` materializes `codeql_ghas_configuration_identity.py` into `$RUNNER_TEMP` and executes it as a standalone file. The CodeQL helper therefore keeps its small fail-closed transport boundary self-contained instead of gaining a repository-local import dependency that the workflow does not materialize.

## Redirect RED → repair

CodeRabbit's current-head review of `9ba43f284da51bfa6aaa389d3fb67f8b232fbba5` correctly rejected the initial-only guard: default `urllib` redirect handling can create a new request after the first authority check and carry the bearer header to the new target.

Structural redirect RED `7a00442cbfd01408068a060c2bebba84041a33eb` adds hostile redirect targets for a lookalike HTTPS host, `http://api.github.com/...`, and `file:///...`. The contract requires both clients' redirect handlers to return no redirected request while the original request retains its bearer header; the repair also blocks same-authority redirects so there is no unreviewed second authority transition at all.

Production repair lineage:

- `a2e9126416c96bb8c5fa1e00190a8eca45758883` replaces CodeQL's default `urlopen` transport with a local `OpenerDirector` whose `_RejectRedirects` handler refuses every redirect;
- `4c7bcbeb06e421b98b0992b62cac06eaae45a98c` applies the same fail-closed boundary to the Strix evidence client;
- `e06b6dd84b012db9c3fafc09d417a85f4aaeff4c` adds direct-handler hostile cases, canonical opener positive controls, and same-authority redirects to the refusal contract;
- `57477289ebec5631b0c48f0bc419f336dbe19deb` closes the remaining executable-binding gap: both actual module-level production openers receive a synthetic 302 through their real HTTPS open/response chains, and the regression proves transport sees exactly the original canonical request plus bearer and never receives a redirected request.

The redirect repair removes the two dynamic `urlopen` sinks rather than broadening a Semgrep/Bandit suppression. A 3xx response now terminates as the opener's HTTP error path; no second request object is created and the bearer credential cannot be forwarded by redirect machinery. The executable proof patches only the actual opener's bounded HTTPS transport slot for a synthetic response; it does not replace `open()`, call the redirect handler directly as its oracle, or contact a network endpoint.

## Production opener-chain RED → evidence repair

Current-head review found that the direct `_RejectRedirects.redirect_request(...)` unit cases would remain green if either production `_GITHUB_API_OPENER` were accidentally rebuilt with Python's default redirect handler. Commit `57477289ebec5631b0c48f0bc419f336dbe19deb` therefore drives each public client path through its actual module-level opener. A synthetic HTTPS transport returns `302 Location: https://api.github.com/repos/ContextualWisdomLab/redirected`; the contract requires the client-specific HTTP error and exactly one transport call containing the original bearer header.

Mutation RED temporarily replaced both `build_opener(_RejectRedirects())` constructions with `build_opener()`. Both new tests failed on the forbidden second request and recorded `Authorization='Bearer test-token'` at that redirect target. Restoring the production constructors made the complete authority file GREEN (`31 passed`, including malformed-authority parse failures for both clients and all four redirect target classes). This binds the executable claim to the production handler chain without adding network I/O, sharing runtime helpers, or changing the standalone CodeQL module.

The broader focused run then exposed four pre-existing Strix fixtures still patching the removed module-level `urlopen` symbol: HTTP error, URL error, malformed JSON, and success. Their RED result was `2 failed, 77 passed` because monkeypatch setup stopped before those cases reached production. They now patch `binding._GITHUB_API_OPENER.open`, matching the real call path; the three-file CodeQL/Strix/authority suite passes in both normal and `GITHUB_ACTIONS=true` modes (`87 passed` each), with 100% statement and branch coverage across the two affected production modules.

A clean worktree at predecessor `25f83aaee9eb97e423f6ef2467e722035bc2e362` reproduced those two Strix failures in the full suite (`2 failed, 3354 passed, 28 skipped, 40 subtests`) and the repository-wide pre-existing 98% coverage gate (`262` missed statements). The repair removes the two causal suite failures and all misses in the two affected production modules; it does not claim to close unrelated coverage debt in `actions_queue_health*`, Rust materialization, Noema document handling, or scheduler code.

## Alternatives rejected

Broad Semgrep/Bandit suppression, path exclusion, or threshold weakening were rejected because they hide unrelated findings. Revalidating only the final response URL was rejected because the unauthorized network contact would already have occurred. Preserving redirects while stripping only `Authorization` was rejected because the client would still contact a target outside the stated GitHub REST authority. A custom redirect-following policy was unnecessary for these CI reads; blocking redirects entirely is the smaller authority surface.

## Evidence and acceptance

Primary scanner rule inspected at [semgrep/semgrep-rules revision `40b8c63f75dc7c22c8a77482d73bfb864b146f7e`](https://github.com/semgrep/semgrep-rules/commit/40b8c63f75dc7c22c8a77482d73bfb864b146f7e): `python/lang/security/audit/dynamic-urllib-use-detected.yaml`. Python stdlib `HTTPRedirectHandler` behavior was inspected during review because redirect construction is the second network-authority decision that the original source predicate did not control.

Acceptance requires all of the following on the exact PR head:

1. `tests/test_github_api_url_boundary.py` passes initial hostile-authority, direct-handler redirect-refusal, actual-production-opener synthetic-302, and canonical positive-control cases for both clients;
2. existing CodeQL GHAS identity and Strix evidence-binding suites remain green;
3. Semgrep and Python/Bandit no longer report the #2248 baseline findings and introduce no replacement Medium+ finding;
4. no security rule, path, threshold, or required check is weakened;
5. independent current-head review confirms redirects cannot create a second request carrying the bearer token;
6. the standalone `$RUNNER_TEMP` CodeQL materialization contract remains intact.

Hosted exact-head evidence is mandatory. Source inspection, structural RED/repair lineage, and review comments are not substitutes for repository/security GREEN.
13 changes: 13 additions & 0 deletions docs/product-technical-gap-baseline.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ flowchart LR
| G-14 | release/changelog/version 증거가 각 PR에 분산되고 현재 central repo 보호 main의 release candidate가 명확하지 않다 | 운영자는 어떤 기능이 supportable release인지 확인할 수 없다 | merge 후 release readiness ledger, CHANGELOG, semantic version/tag, rollback/operability evidence를 함께 갱신한다 |
| G-15 | 첨부파일 처리 경계가 제품별로 다르고, 1MB 상한은 업무 데이터와 맞지 않으며 미지원 MIME/컨테이너가 parser registry에서 명시적으로 pending/quarantine 되는지 확인되지 않았다. 현재 20MB 초과 파일 가능성과 PDF/HWP/HWPX·이미지·압축파일의 parse/sidecar 흐름을 하나의 exact contract로 묶지 못했다 | 큰 업무 첨부를 거부하거나 파싱 실패를 조용히 잃으면 고객의 메일·문서 업무가 중단된다 | naruon/newsdom-api 소유 PR에서 streaming upload, configurable bounded limit above 20MB, MIME sniffing, parser capability registry, quarantine/retry, source-position provenance, and ADR를 추가하고 size/unsupported-type/zip-bomb tests를 required evidence로 만든다 |
| G-16 | Required Pingora policy treated a changed documentation PNG screenshot as UTF-8 runtime evidence | Valid UI evidence blocked otherwise valid product PRs before policy evaluation | This branch verifies bounded PNG magic before exemption while runtime paths and malformed assets continue to fail closed; protected-main delivery remains the release gate |
| G-17 | `.github#2279` blocked authenticated GitHub REST redirects in source, but redirect tests invoked `_RejectRedirects` directly and four Strix transport fixtures still patched the removed `urlopen` seam | A future opener-composition regression could forward a bearer token on a 3xx while redirect tests stayed green; Strix error mapping could fail before exercising production | Proposed `57477289ebec5631b0c48f0bc419f336dbe19deb` sends all four synthetic redirect classes through both real module-level openers; `663ffac390d27ab21daa58b91b624d3f00dce7de` moves every Strix fixture to the production opener; `9c19c6e00eafc028068719ab482282c1256f8893` adds malformed-authority coverage and records the owner evidence. Mutation RED proves the default opener contacts a second same-authority URL with the bearer header. The focused suite passes twice (`87 passed` normal and `GITHUB_ACTIONS=true`) with 100% statement/branch coverage on both affected modules. Exact-head hosted security and independent review remain required |

## 4. 열린 PR live inventory

Expand Down Expand Up @@ -3411,3 +3412,15 @@ workflow instead of two, org-wide. `strix.yml` (the other single-consumer gate)
alone -- it is a documented multi-PR hot-file collision zone. Contract:
`tests/test_docs_only_pr_runner_admission.py::test_sast_semgrep_folds_the_gate_into_its_single_consumer_at_step_level`,
`tests/test_required_security_runner_image_contract.py`.

## 2026-09-19 GitHub API production-opener redirect proof

**Status:** Proposed on `ContextualWisdomLab/.github#2279`; exact-head hosted checks and qualifying independent review remain mandatory.

**Context Map / owner.** The central `.github` CI bounded context owns the bearer-authenticated CodeQL-analysis and Strix changed-file GitHub REST clients. GitHub remains the upstream REST authority. Product repositories consume only the released central workflow contract; they do not copy either client.

**Gap.** Initial URL admission and direct `_RejectRedirects.redirect_request()` unit cases did not prove that each module-level production `OpenerDirector` actually retained the no-redirect handler chain. A future opener reconstruction could silently re-enable authenticated redirects while the prior tests stayed green.

**Action.** Exact `57477289ebec5631b0c48f0bc419f336dbe19deb` adds a dependency-free synthetic-302 transport to `tests/test_github_api_url_boundary.py`. For both actual production openers, the case drives a canonical bearer request through the real HTTPS open/response chain, requires the typed HTTP-302 failure mapping, and proves transport receives exactly one original request; lookalike HTTPS, HTTP, `file:`, and same-authority redirect targets never receive a second request or bearer. Exact `e0b0b4d4fff5b6ea88236a1e91dcd7dbb3be09b5` repairs the doctoring claim so direct-handler coverage is not mislabeled as production-chain proof.

**Evidence / remaining condition.** The standalone fixture mechanism was executed locally against Python stdlib and produced one canonical request followed by terminal HTTP 302 for every hostile target. This is mechanism evidence, not repository acceptance. Final authority requires focused/full exact-tree GREEN, fresh exact-head Security/SAST/Python Security/CodeQL/runtime-quality checks, no unresolved actionable review, ordinary protected-main integration, and downstream consumer validation. No scanner suppression, redirect allowlist widening, provider fallback, workflow gate weakening, or credential-boundary change is included.
Loading
Loading