Repository navigation
feat(workflows): package fail-closed registry audit #211
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
Show all changes
51 commits
Select commit
Hold shift + click to select a range
e7c776a
test: define fail-closed workflow registry audit contract
seonghobae 578b101
feat: add fail-closed workflow registry audit
seonghobae 59b6a94
fix: keep workflow audit token on fixed GitHub API
seonghobae be28004
test: require protected-ref movement detection
seonghobae f84b5c6
fix: fail closed when protected ref moves
seonghobae 6f6eaa0
test: lock authenticated audit reads to GitHub API
seonghobae a4f9dc7
test(workflows): expose same-count pagination drift
seonghobae 8d0c6ed
fix(workflows): verify multi-page registry stability
seonghobae d1b8449
test(workflows): verify stable multi-page registry pass
seonghobae c33759e
test(workflows): require fixed HTTPS transport boundary
seonghobae 580b757
fix(workflows): pin audit transport to HTTPS host
seonghobae 93ba3ba
test(workflows): require fixed aiohttp origin boundary
seonghobae ab948ba
fix(workflows): use fixed-origin aiohttp transport
seonghobae 3c6ae8a
test(workflows): expose commit-tree identity mismatch
seonghobae df2d4de
fix(workflows): resolve commit tree before registry audit
seonghobae fabaad7
test(workflows): separate protected commit and tree fixtures
seonghobae 4dd0979
test(workflows): resolve protected commit tree in ref movement
seonghobae 1514b16
test(workflows): resolve commit tree in stability fixtures
seonghobae 1197fd4
test(workflows): expose rate-limit diagnostic ambiguity
seonghobae 9d54970
fix(workflows): classify GitHub rate-limit failures
seonghobae a2920a5
test(workflows): prove slash ref path regression
seonghobae c2ca8b5
fix(workflows): preserve slash refs in GitHub paths
seonghobae 8a1215c
test(workflows): reproduce dynamic registry identity gap
seonghobae 7aa1ab9
fix(workflows): classify dynamic registry identities safely
seonghobae 3cd6896
test(workflows): reproduce protected-ref namespace confusion
seonghobae 5faf4be
fix(workflows): reject protected-ref namespace prefixes
seonghobae 7618fc4
test(workflows): reject nonfinite audit timeouts
seonghobae 2f6788c
fix(workflows): require finite audit timeout
seonghobae 842610c
test(security): reject repository dot segments before transport
seonghobae bdb0fb3
fix(security): reject repository dot segments
seonghobae a50d803
test(audit): cover negative infinite timeout
seonghobae 5c7c3e2
test(workflows): prove GitHub audit response is unbounded
seonghobae 8770ad8
fix(workflows): bound GitHub audit response memory
seonghobae c54978c
test(workflows): exercise bounded aiohttp response stream
seonghobae 09fc0de
test(workflows): prove recursive JSON fails closed
seonghobae 5defb01
fix(workflows): normalize recursive JSON failure
seonghobae 156f134
test(workflows): bound registry pagination cardinality
seonghobae 77732cd
fix(workflows): bound registry audit cardinality
seonghobae 150b71d
test(workflows): reject unknown workflow registry state
seonghobae 6a48171
fix(workflows): reject unknown workflow registry states
seonghobae ca65227
test(workflows): require exact registry primitive types
seonghobae 6140e45
test(workflows): reject hostile registry mappings
seonghobae 124e5b3
fix(workflows): bound malformed registry primitive types
seonghobae 7a71400
test(workflows): reject hostile audit boundary subclasses
seonghobae b336272
revert(workflows): restore pre-run audit branch after blocked fix
seonghobae 4e87d9e
test(workflows): reject hostile audit boundary subclasses
seonghobae 1127c75
fix(workflows): reject hostile audit boundary subclasses
seonghobae cc4362a
Merge branch 'main' into fix/workflow-registry-audit-d0a4b30
opencode-agent[bot] 4b1ca74
feat(workflows): package fail-closed registry audit
cursoragent 5957c3b
test(workflows): bound audit receipt timestamps
seonghobae 0e3fa51
fix(workflows): validate canonical audit timestamps
seonghobae File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| # ADR 0016: Read-only exact-SHA workflow registry audit | ||
|
|
||
| - **Status:** Accepted for the read-only detector slice | ||
| - **Date:** 2026-08-16 | ||
|
|
||
| ## Context | ||
|
|
||
| GitHub Actions can retain active workflow identities after their repository | ||
| YAML is removed, or can register platform-managed `dynamic/` identities that | ||
| never had a protected-tree blob. Operators need a fail-closed way to list those | ||
| identities against one immutable protected commit. Automatic disable, rerun, or | ||
| YAML rewrite would change production control-plane state from a detector. | ||
|
|
||
| GitHub's Git database and Actions registry are separate evidence sources. A | ||
| tree response `sha` is a tree object identity, not a commit SHA. A multi-page | ||
| registry read can splice two registry generations even when `total_count` is | ||
| unchanged. JSON subclasses can lie about equality if identity members are | ||
| compared before an exact-type check. | ||
|
|
||
| NIST SP 800-218 (SSDF) and SLSA v1.0 treat build-system integrity as a | ||
| provenance control: verify the source that is supposed to produce automation | ||
| before changing that automation. in-toto's farm-to-table argument is the same | ||
| shape: bind an observation to an exact artifact identity rather than to a | ||
| moving name. | ||
|
|
||
| ## Decision | ||
|
|
||
| `pg_llm_batch.workflow_registry_audit` is a packaged, installable, read-only | ||
| auditor. | ||
|
|
||
| 1. Transport is GET-only to `https://api.github.com`, path-only, no redirects, | ||
| finite timeout, default TLS verification, no automatic retry. | ||
| 2. The CLI always verifies the live protected ref before and after the SHA | ||
| audit. Library callers may classify one SHA without live-ref checks. | ||
| 3. Commit objects are resolved first; tree reads use `commit.tree.sha`. | ||
| 4. Multi-page registries require a second identical pass. | ||
| 5. Active repository-backed paths absent from the exact tree are candidates | ||
| only. `dynamic/` identities are receipted and never orphaned. | ||
| 6. Exact decoder types are required for containers and for identity members | ||
| before any `==` / `!=`. | ||
| 7. Exit codes are `0` (clean), `1` (failed closed), and `2` (candidates). | ||
| 8. The tool never disables, enables, edits, or reruns workflows. | ||
|
|
||
| ## Consequences | ||
|
|
||
| Operators can install `pg-llm-batch` and run | ||
| `pg-llm-batch-workflow-audit` without a special PYTHONPATH. Coverage, | ||
| docstring, and compile gates now see the production module because it lives | ||
| under `pg_llm_batch`. A buyer can distinguish a moving branch, a truncated | ||
| tree, a rate-limit, and a true orphan candidate. | ||
|
|
||
| The detector does not replace branch protection, required reviews, or a human | ||
| decision to disable a workflow. False confidence from a stale PR body or from | ||
| comparing a tree SHA to a commit SHA is rejected. | ||
|
|
||
| ## Rollback | ||
|
|
||
| Rollback is code-only: remove the package module, console script, checkout | ||
| shim, ADR, doctoring, changelog, and regression files. No database migration | ||
| or Actions mutation is required. Adding mutation to this tool requires a | ||
| separate reviewed control-plane contract. | ||
|
|
||
| ## References | ||
|
|
||
| GitHub. (n.d.). *REST API endpoints for GitHub Actions workflows*. GitHub Docs. | ||
| Retrieved August 16, 2026, from | ||
| https://docs.github.com/en/rest/actions/workflows | ||
|
|
||
| GitHub. (n.d.). *REST API endpoints for Git trees*. GitHub Docs. Retrieved | ||
| August 16, 2026, from | ||
| https://docs.github.com/en/rest/git/trees | ||
|
|
||
| National Institute of Standards and Technology. (2022). *Secure software | ||
| development framework (SSDF) version 1.1: Recommendations for mitigating the | ||
| risk of software vulnerabilities* (NIST SP 800-218). | ||
| https://doi.org/10.6028/NIST.SP.800-218 | ||
|
|
||
| The Linux Foundation. (2023). *Supply-chain Levels for Software Artifacts | ||
| (SLSA) v1.0*. https://slsa.dev/spec/v1.0/ | ||
|
|
||
| Torres-Arias, S., Afzali, H., Kuppusamy, T. K., Curtmola, R., & Cappos, J. | ||
| (2019). in-toto: Providing farm-to-table guarantees for bits and bytes. In | ||
| *Proceedings of the 28th USENIX Security Symposium* (pp. 1393–1410). USENIX | ||
| Association. https://www.usenix.org/conference/usenixsecurity19/presentation/torres-arias | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,130 @@ | ||
| # Workflow registry audit doctoring | ||
|
|
||
| ## Purpose | ||
|
|
||
| This record tells an operator how to detect GitHub Actions identities that are | ||
| active in a repository registry but absent from an exact protected source tree, | ||
| without disabling, editing, rerunning, or recreating any workflow. | ||
|
|
||
| Use the installed command after `pip install pg-llm-batch` or an editable | ||
| checkout. Do not treat a missing checkout-root module as the supported | ||
| interface. The detector follows NIST SP 800-218 and SLSA v1.0 by binding the | ||
| receipt to an exact source commit before any operator reviews a candidate. | ||
|
|
||
| ```bash | ||
| export GITHUB_TOKEN="$(gh auth token)" # contents:read + actions:read is enough | ||
| pg-llm-batch-workflow-audit \ | ||
| --repository ContextualWisdomLab/pg-llm-batch \ | ||
| --protected-ref main \ | ||
| --protected-sha "$(git rev-parse origin/main)" | ||
| ``` | ||
|
|
||
| Equivalent module form: | ||
|
|
||
| ```bash | ||
| python -m pg_llm_batch.workflow_registry_audit \ | ||
| --repository ContextualWisdomLab/pg-llm-batch \ | ||
| --protected-ref main \ | ||
| --protected-sha "$(git rev-parse origin/main)" | ||
| ``` | ||
|
|
||
| ## What to do with the result | ||
|
|
||
| - Exit `0`: the live protected ref still matches the supplied SHA, the registry | ||
| was stable across a complete pagination pass, and no active | ||
| repository-backed identity is missing from that exact tree. Keep operating. | ||
| - Exit `2`: stdout is a JSON receipt whose `active_absent_workflows` lists | ||
| candidates only. Open a separate operator review. Do not disable those | ||
| workflows from this tool. | ||
| - Exit `1`: the audit failed closed. Read the single stderr line, then correct | ||
| the repository selector, SHA, ref, token scope, rate-limit budget, or | ||
| truncated-tree condition and run the same command again. | ||
|
|
||
| GitHub-managed `dynamic/` identities appear in `workflow_records` with | ||
| `source_kind=platform_dynamic` and `source_present=null`. They are never | ||
| orphans. Do not delete platform-managed identities because they lack a | ||
| `.github/workflows/` blob. | ||
|
|
||
| The library function `audit_repository_workflows` can classify one exact SHA | ||
| without live-ref pre/post checks. The CLI always uses | ||
| `audit_live_protected_ref_workflows` so a moving protected branch cannot | ||
| certify a stale head. | ||
|
|
||
| ## Trust and security boundary | ||
|
|
||
| The client sends path-only GET requests to the fixed origin | ||
| `https://api.github.com`, disables redirects, and uses the default TLS | ||
| verifier. It never retries automatically. Non-success bodies are not read. | ||
| Rate-limit evidence is limited to HTTP 429 or HTTP 403 plus | ||
| `X-RateLimit-Remaining: 0` or `Retry-After`. | ||
|
|
||
| JSON objects, arrays, strings, integers, and booleans are accepted only as | ||
| exact decoder built-in types. Identity members (`ref`, commit `sha`, tree | ||
| `sha`) are type-checked before equality. A hostile subclass cannot certify the | ||
| caller SHA while resolving a different tree. | ||
|
|
||
| `GITHUB_TOKEN` is used only as an `Authorization` header. It is never copied | ||
| into stdout, stderr, receipts, or exception cause chains. | ||
|
|
||
| This tool does not select tenant scope, touch PostgreSQL, or mutate Actions | ||
| state. It is a read-only control-plane detector for this repository and for | ||
| hosts that embed `pg-llm-batch` as a module. | ||
|
|
||
| ## Recovery and rollback | ||
|
|
||
| No schema, secret, or workflow mutation is performed. Recovery is to rerun the | ||
| same read after correcting the failed input or GitHub condition. Rollback is | ||
| removing the console script, package module, ADR, and this record. Reintroducing | ||
| workflow disable/enable from this tool is not a routine rollback step. | ||
|
|
||
| ## Verification | ||
|
|
||
| Permanent regression coverage requires that: | ||
|
|
||
| - hostile `str` subclasses cannot satisfy commit, tree, or ref identity by | ||
| lying about equality; | ||
| - raising identity subclasses become `WorkflowRegistryAuditError` without | ||
| leaking custom exception text; | ||
| - `dynamic/` identities are receipted and never orphaned; | ||
| - unknown workflow states fail closed; | ||
| - multi-page registries are accepted only after a second identical pass; | ||
| - truncated trees, moving protected refs, and oversize responses fail closed; | ||
| - the console script `pg-llm-batch-workflow-audit` is declared in package | ||
| metadata; and | ||
| - README, architecture, ADR, changelog, and this doctoring record all tell the | ||
| operator to review candidates instead of disabling workflows. | ||
|
|
||
| ## References | ||
|
|
||
| GitHub. (n.d.). *REST API endpoints for GitHub Actions workflows*. GitHub Docs. | ||
| Retrieved August 16, 2026, from | ||
| https://docs.github.com/en/rest/actions/workflows | ||
|
|
||
| GitHub. (n.d.). *REST API endpoints for Git commits*. GitHub Docs. Retrieved | ||
| August 16, 2026, from | ||
| https://docs.github.com/en/rest/git/commits | ||
|
|
||
| GitHub. (n.d.). *REST API endpoints for Git trees*. GitHub Docs. Retrieved | ||
| August 16, 2026, from | ||
| https://docs.github.com/en/rest/git/trees | ||
|
|
||
| GitHub. (n.d.). *REST API endpoints for Git references*. GitHub Docs. Retrieved | ||
| August 16, 2026, from | ||
| https://docs.github.com/en/rest/git/refs | ||
|
|
||
| GitHub. (n.d.). *Rate limits for the REST API*. GitHub Docs. Retrieved August | ||
| 16, 2026, from | ||
| https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api | ||
|
|
||
| National Institute of Standards and Technology. (2022). *Secure software | ||
| development framework (SSDF) version 1.1: Recommendations for mitigating the | ||
| risk of software vulnerabilities* (NIST SP 800-218). | ||
| https://doi.org/10.6028/NIST.SP.800-218 | ||
|
|
||
| The Linux Foundation. (2023). *Supply-chain Levels for Software Artifacts | ||
| (SLSA) v1.0*. https://slsa.dev/spec/v1.0/ | ||
|
|
||
| Torres-Arias, S., Afzali, H., Kuppusamy, T. K., Curtmola, R., & Cappos, J. | ||
| (2019). in-toto: Providing farm-to-table guarantees for bits and bytes. In | ||
| *Proceedings of the 28th USENIX Security Symposium* (pp. 1393–1410). USENIX | ||
| Association. https://www.usenix.org/conference/usenixsecurity19/presentation/torres-arias |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This identifier collides with open #212
docs/adr/0016-postgres-logical-restore-seek.md. Protected main ends at 0015; open recovery writers already claim 0016-0020. Reallocate this decision to ADR 0021 and keep a repository test that rejects duplicateNNNN-*.mdprefixes after merge. #222 does that.