Skip to content

fix(release-gate): nested shell publishers + OIDC allowlist (#184) - #185

Open
marcohost33-maker wants to merge 2 commits into
mainfrom
fix/184-nested-publisher
Open

marcohost33-maker wants to merge 2 commits into
mainfrom
fix/184-nested-publisher

Conversation

@marcohost33-maker

Copy link
Copy Markdown
Owner

Closes #184.

Was

  1. Credential-Grenze (Hauptlinie): jeder Job mit id-token: write muss in OIDC_ALLOWLIST stehen (pypi.yml/publish, scorecard.yml/analysis); allowlisted Release-Jobs muessen auf Environment pypi deployen. Workflow-Level-Grants, unlesbare permissions und Secret-Referenzen *PYPI*|*TWINE*|*PUBLISH* fallen fail-closed.
  2. Tiefenverteidigung: publish_evidence steigt in das Skript-Argument von sh|bash|dash|zsh|ksh|ash -c (auch -ec, -o pipefail -c, --noprofile -c) und eval ab, Tiefe begrenzt; nicht tokenisierbar oder zu tief = zaehlt als Publisher.

Belege @ 7a1c373

  • tests/test_release_gate_oidc_allowlist.py + review-findings: 89 passed lokal.
  • Diskriminierung: Abstieg deaktiviert -> 7 der neuen Tests rot; restauriert -> gruen.
  • Volle Suite: 1618 passed, 14 skipped, 1 failed = test_version_single_source (lokal veraltete installierte Dist 0.5.0, unabhaengig von diesem PR).
  • python .github/scripts/check_release_pins.py -> exit 0 auf dem Repo-Stand.

Restgrenze (bewusst)

Statische Shell-Erkennung bleibt unvollstaendig (curl an die Upload-API, Wrapper-Skripte). Die tragende Grenze ist PyPI Trusted Publishing gebunden an pypi.yml + Environment pypi ohne Legacy-Token -- diese PyPI-/Environment-Konfiguration ist Marco-Gate (007acc2 REV 3.1).

Herkunft: Basis-Patch 007acc2 (Claude, 2026-09-30); Abstieg + Tests hier ergaenzt. Merge erst nach unabhaengiger Pruefrunde gegen diesen Kopf.

🤖 Generated with Claude Code

…#184)

Two layers against the #184 bypass (`bash -c 'twine --verbose upload'` in an
unlisted workflow was classified as "no publisher"):

1. Credential boundary: every job granting `id-token: write` must be in
   OIDC_ALLOWLIST; allowlisted release jobs must deploy to environment `pypi`;
   workflow-level grants, unreadable permissions and publish-secret references
   fail closed. Shell text can never be parsed completely (curl, wrappers), so
   the OIDC grant is the boundary the gate actually controls.
2. Defence in depth: publish_evidence descends into the script argument of
   sh/bash/dash/zsh/ksh/ash -c (combined flags, -o opt, --long) and of eval,
   bounded depth; an untokenisable or too-deep nested script counts as
   publishing.

Base patch: 007acc2 Claude answer 2026-09-30 (liouscope_184_oidc-allowlist).
Nested descent + tests added here. Discrimination: disabling the descent turns
all 7 nested tests red; full suite 1618 passed (1 pre-existing env failure:
stale installed dist 0.5.0 vs 0.6.0.dev0 in test_version_single_source).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

…permission forms (#185 review)

Aegis round on 7a1c373 (P2-1): the nested-shell descent missed
`bash -eo pipefail -c`, GitHub's own `bash --noprofile --norc -eo pipefail`,
`bash -c -- '...'`, `--rcfile/--init-file FILE -c`. `_shell_c_script` now
follows bash option parsing (o-bundles consume their value, value-taking long
options, `--` ends options, operand after a c flag is the script); a c flag
without an operand raises -> counted as publishing.

Also (P3-2/P3-3): publish secrets via `secrets['..']`/`secrets[".."]` and
`toJSON(secrets)`, `secrets: inherit` on a job, `id-token` scope key matched
case-insensitively, allowlisted jobs must list permissions explicitly.

Tests: release-gate files 103 passed; against 7a1c373 13 of the new tests
fail (the script-file control passes on both). Gate on repo: exit 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@marcohost33-maker

Copy link
Copy Markdown
Owner Author

Pruefrunde 1 (Aegis, unabhaengig, read-only) @7a1c373 -> NICHT MERGE-REIF, behoben in c5ea225:

  • P2-1 Flag-Parser verfehlte bash -eo pipefail -c, bash --noprofile --norc -eo pipefail -c (GitHub-Default-Schale), bash -c -- '..', --rcfile/--init-file FILE -c -> _shell_c_script folgt jetzt bash-Optionsparsing; -c ohne Operand = fail-closed.
  • P3-2 Secret-Formen secrets['..'], toJSON(secrets), secrets: inherit; P3-3 allowlisted Job braucht explizite permissions; id-token-Key case-insensitiv.
  • Belege: 103 passed (Release-Gate-Tests); gegen 7a1c373 schlagen 13 neue Tests fehl, Kontrolle (Skriptdatei) gruen auf beiden.
  • Bewusst offen (P3-1): generische Wrapper ("$SHELL" -c, Pipe in sh, python -c os.system, curl) -- die OIDC-Allowlist ist die tragende Grenze.
  • P2-2 Marco-Gate: Environment pypi hat live protection_rules: [], deployment_branch_policy: null, can_admins_bypass: true -> die Bindung environment: pypi schuetzt erst, wenn dort Regeln stehen (nur Tags v*, optional Required Reviewer).

Merge erst nach Pruefrunde 2 gegen c5ea225 (sicherheitsnaher Waechter, Schablone A7).

@marcohost33-maker

Copy link
Copy Markdown
Owner Author

Entscheid Marco 2026-10-01 (E1001-S2-01), nur vermerkt: Environment pypi: nur Tags v* duerfen deployen (Deployment-Policy), PyPI Trusted Publisher an Environment pypi binden, kein PyPI-Token, kein Required Reviewer. Setzen der Repo-/PyPI-Einstellung bleibt bei Marco. Dieser PR bleibt bis Pruefrunde 2 gegen c5ea225 ungemergt.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Release gate: nested shell publisher command can evade unlisted-workflow detection

1 participant