Skip to content
This repository was archived by the owner on Oct 10, 2026. It is now read-only.

v0.7.0 - two environments, one subject

Latest

Choose a tag to compare

@Dashtid Dashtid released this 08 Oct 18:55
· 5 commits to main since this release
v0.7.0
efbb1e8

Three things happened in this release. The github-aws work that had sat on main
since 2026-09-03 ships (suite 0.6.0 -> 0.9.0). The corpus's observed count moves
for the first time since August, 11 -> 16, on tokens a real GitHub Actions job
minted - and one of the two probes found a subject collision. And a suite that
Microsoft's documentation had quietly falsified is corrected, with the
contradiction between Microsoft's own two pages encoded rather than papered over.

Totals: 14 suites, 163 vectors, 16 observed / 147 documented.

% is not escaped: two environments, one subject (observed)

GitHub documents that a : inside a metadata value is minted as %3A, so an
environment named Production:V1 produces
repo:OWNER/REPO:environment:Production%3AV1. The corpus had encoded that rule
on 2026-09-02 and recorded the open question beside it: nothing says % itself
is escaped. Probed on 2026-10-08 in a scratch repository: two environments were
created through the REST API, Production:V1 and Production%3AV1, and GitHub
listed them as two distinct environments with two ids. One job in each, same run.
Both minted the byte-identical sub. Only the environment claim, which carries
the raw name, and environment_node_id tell them apart.

So a trust policy correctly pinned to Production:V1 admits every job of its
twin - and GitHub documents that "anyone that can edit workflows in the
repository can create environments via a workflow file", with no protection rules
on the new environment. A required-reviewers bypass that no wildcard advertises.

  • New: gh-aws-environment-colon-twin-collides (dangerous). Mitigation stated in
    the vector: pin AWS's environment condition key alongside sub, or keep :
    out of environment names.
  • Promoted on the same transcript: gh-aws-environment-colon-encoded-exact,
    gh-aws-environment-colon-literal-pin-denies.

job_workflow_ref stays name-based under immutable subjects (observed)

The same run answered the second open question. A repository created that day
minted the immutable owner@id/repo@id subject with no opt-in - the format
GitHub scheduled for 2026-07-15 - and in the same token job_workflow_ref,
workflow_ref and repository carried no ids at all, for a direct job and for a
workflow_call job alike. The immutable format changes sub and nothing else.

  • New: gh-aws-jwr-key-stays-name-based-under-immutable-sub (caution): a
    job_workflow_ref pin keeps every property of a name-based pin on an
    immutable-subject repository.
  • Promoted: gh-aws-immutable-subject-classic-pattern (the new-repository
    observation).

A new observation method, github-actions-oidc-claims, records exactly what it
proves: the issuer side - what GitHub puts in the token. The AWS match of that
string stays documented, and every promoted vector's evidence says which half
is which. Four committed transcripts under observations/2026-10-08/ carry the
verbatim claim sets; the signed tokens were never printed. The guards in
tests/test_observations.py extend to the new method: each promoted vector's
subject context and claim shapes are checked against the committed token, and the
account-id scan exempts only the token's own check_run_id / run_id claims.

Microsoft falsified gitlab-azure-flexible 0.1.0. Corrected (0.2.0, one new vector)

That suite shipped on Microsoft listing sub as the only claim a GitLab flexible
federated identity credential may reference - hence a path-reuse squatter that
"no flexible expression can exclude". Microsoft's flexible-FIC page (ms.date
2026-09-18) now lists project_id (eq) for GitLab and says the expression "must
match the sub and project_id claims" when the sub is mutable. Its separate
"Mutable subjects" page (ms.date 2026-07-28) says sub plus one or more of
project_id / namespace_id / user_id, "regardless of whether sub starts with
project_path or project_id". Two Microsoft pages, two rules.

All five judgments are corrected (the lever exists; a sub-only expression on a
path-based sub no longer satisfies the documented rule), the immutable-gap tag
moves off the GitLab squatter vector (its id is kept stable for citations; the
catalog's example is now the Terraform Cloud vector, whose gap still holds), and
one vector is added: gl-flex-and-project-id-excludes-squatter (safe, no-match),
the same squatter token refused by and claims['project_id'] eq '20'. What the
two pages disagree on is recorded and asserted nowhere.

AWS's ten GitHub condition keys, graded (github-aws 0.7.0, six vectors)

AWS's condition-keys reference documents exactly ten GitHub-specific keys for role
trust policies, every one "Available in session: No". The corpus graded three; it
now grades all ten plus sub and aud.

  • AWS's own example pins a GitHub handle, one row below the statement that actor
    IDs are immutable: gh-aws-docs-actor-name-pin-recycled (dangerous),
    gh-aws-actor-id-pin-survives-rename.
  • Three keys carry no repository at all. ref is the bare branch, so an exact
    refs/heads/main pin admits main of every repository registered against the
    provider; workflow is the YAML name:, chosen by whoever writes the file.
    Wildcard-free, so a review grading by * objects to nothing:
    gh-aws-ref-key-alone-omits-the-repository,
    gh-aws-workflow-name-pin-attacker-chosen.
  • repository_owner_id is immutable and org-wide - the breadth of
    gh-aws-org-wide-wildcard-repo without the wildcard that makes it visible:
    gh-aws-repository-owner-id-alone-is-org-wide.
  • AWS's six-key example evaluated as a block:
    gh-aws-all-docs-github-tab-example, held at caution.

New vocabulary tag: select-claim-key.

Immutable-format completeness (github-aws 0.8.0, three vectors)

Verified verbatim from GitHub's reference and changelog: repositories created
before 2026-07-15 keep the classic format unless they opt in; renames and
transfers after that date switch.

  • gh-aws-rename-flips-format-and-breaks-the-policy (caution): a routine rename
    flips the subject format and the policy stops matching, with neither the
    repository settings nor the policy edited.
  • gh-aws-format-tolerant-wildcard-admits-prefix-orgs (dangerous): the obvious
    repair, repo:octo-org*/octo-repo*:..., each * meant only to absorb an
    optional @ID. IAM's * is not delimiter-aware, so the boundary becomes any
    account starting with octo-org holding any repo starting with octo-repo.
  • gh-aws-immutable-custom-sub-keeps-the-ids (dangerous): the immutable twin of
    the repo-only customized sub - still admits everything, now rename-proof.

Two GCP vectors claimed an error-free rejection, wrongly

gh-gcp-name-pin-breaks-on-rename said deployments stop "with no error";
gh-gcp-classic-sub-immutable-break said the credential is "silently rejected".
GCP refuses a credential whose attribute condition is false and says so
("Otherwise, the credential is rejected"; the response text is "The given
credential is rejected by the attribute condition"). What is silent is write
time, as on Azure, where the same wording was corrected on 2026-08-25 by a
one-off sweep phrased for Azure - which is how these survived it.
test_no_vector_claims_an_error_free_rejection now runs on every suite and was
checked against both the pre-fix GCP and the pre-08-25 Azure wording.
github-gcp 0.1.0 -> 0.1.1. No verdict moved.

Three corpus invariants, now tested

A self-consistency audit came back clean and found that none of its three checks
was being tested. Now in tests/test_vectors.py, each mutation-tested:
cross-referenced vector ids resolve; no two vectors disagree on an identical
token and condition; no vector is an exact duplicate of another.

Housekeeping

  • pyproject.toml declares [project.urls]. Nine releases had shipped with none,
    so the PyPI page carried no link to the repository, the vectors or the issues.
  • README links that PyPI rendered as dead relative paths are absolute, and the
    README now cites CVE-2026-82856 - another scanner shipping the set-operator miss
    this corpus grades - as evidence that it is a bug class.
  • scripts/coverage.py writes the README with LF on every platform.
  • The upstream scoreboard is re-dated: four PRs open, none merged or reviewed by
    a maintainer as of 2026-10-08. The three Checkov PRs stay open past the
    previously recorded 2026-10-15 close-by; the measured reasoning is in
    BACKLOG.md and ROADMAP.md.