Security review: CI monitoring and self-healing (issue-462)
- Mechanism: the-loop checklist (
reference/security.md), checked against the diff. - Outcome: pass.
- Findings: none.
- New network path:
GitHubClient.job_log_tailmakes two requests per log. The first is the API'sGET …/actions/jobs/{id}/logs, authenticated, through PyGithub'srequestBlobAndCheck, which does not follow the302. The second fetches the signedLocationwith the standard library._urlopenbuilds its opener from exactly the HTTPS, proxy, redirect and error handlers, so anhttp:,file:orftp:URL, whether given directly or reached by redirect, has no handler and fails. The request carries noAuthorizationheader, so the token never leaves the API host. A test asserts afile:redirect is refused with no fetch. - Bounded cost: at most five logs per call. Each is streamed in 64 KiB chunks, only the last 256 KiB is held, and reading stops at 32 MiB (tested with lowered caps). The budget remembers at most 1,024 checks, least recently seen evicted first (tested).
- Input validation:
job_idmust be a positive integer (_number),owner/repopass_coordinates, and the ref's host passes_trusted. A bad job id, an untrusted host, a bare PR number without--work-item, and a negative--log-linesare all refused before any request (tested). - Prompt injection: job logs and check summaries are written by code the pull request controls. The log reaches the session only when it runs
pr checks. The CI section the gate adds carries the check's name, conclusion, SHA, URL and PR, not its summary or log, and says the log is untrusted. The event excerpt below the section is the pre-existing allow-listed one (issue-243), already framed as UNTRUSTED. The skill (SKILL.md,workflow.md§ Self-healing CI) repeats the rule. - Suppression: the gate can only withhold a delivery the session would otherwise have received; it creates none. Every withheld event is logged (
dispatch.droppedwith its reason and check). A check's count resets only on a passing run of the same check name on the same pull request, and a pass means the check passed. - Authorization: CI events are actor-less, so the gate sits behind the existing ingress guards and adds no way in.
pr checksis read-only, so it is open likepr status. No write path changed. - Secrets in logs: GitHub Actions masks registered secrets before storing a log.
pr checksreturns whatgh run view --logalready returned to the same token.
- New network path:
- Risk tier: 3. It changes which events reach a session and adds a read verb. No sensitive path (auth, secrets, CI config, migrations) is touched. That is below the tier-4 threshold for a named human security sign-off; the PR approval is the human gate.