FlawOpen

flawopen.com/Directory/226

● CWE-367 / CWE-829 · High
FlawOpen Security Research

Marimo GHSL-2026-226: Test-Release Workflow TOCTOU

A same-second timestamp race let a pull-request update pass a maintainer’s test-release approval, run in the test-release job, and pass artifacts to OIDC-enabled publishing jobs for TestPyPI and npm. The vulnerable workflow was deleted; no CVE was assigned.

💡 Plain English Explainer (ELI5)

A lab manager signs off on one sample, but the lab checks only the second on a wall clock. If someone swaps the sample within that same second, the replacement is mistaken for the approved one. Marimo’s workflow made that mistake with a pull-request commit, then carried the built package into publishing jobs.

Core Concepts & Subsystem Terms

TOCTOU
Time-of-check to time-of-use: a resource changes after a security check but before the system uses it.
OIDC publishing
A CI job exchanges a GitHub Actions identity token for short-lived credentials at a package registry, instead of storing a long-lived password.
Workflow artifact
A file bundle transferred between GitHub Actions jobs. A downstream job must treat artifacts from untrusted workflows as untrusted input.
GHSL-2026-226
GitHub Security Lab’s advisory identifier for this Marimo workflow flaw. The advisory did not assign a CVE.
CWE-367 / CWE-829
The advisory identifies a time-of-check/time-of-use race and execution of functionality from an untrusted control sphere.

Framework mappings

  • CWE reference for the weakness described in this guide: CWE-367 CWE-829

Step-by-Step Attack Flow

Step 1

A maintainer authorizes a test release

A maintainer comments on a pull request to start Marimo’s test-release workflow. The authorization is intended for the pull-request state the maintainer reviewed.

Step 2

The pull-request head changes in the same second

The workflow compares the repository’s pushed_at timestamp with the comment’s created_at timestamp after converting both to whole seconds. A same-second push compares equal.

Step 3

The vulnerable check accepts equality

The deleted workflow rejected only pushed_at values strictly greater than the comment time. Equality passed, and the workflow recorded the updated pull-request commit.

Step 4

Untrusted code builds package artifacts

The job checked out that commit and ran repository-controlled actions and build scripts. Its outputs were uploaded, then consumed by downstream publishing jobs without rebuilding from a trusted commit.

Step 5

Publishing authority reaches the untrusted output

The TestPyPI and npm jobs used OIDC publishing permissions. The advisory explains that the artifact could reach package publication; it does not report that a real package was compromised.

Source Code: Flaw vs. Secure Implementation

GHSL-2026-226 says the vulnerable workflow was deleted in PR #10765; this is not a claim that the project applied the comparator change shown here. The old check used -gt, so equal whole-second timestamps passed. The illustrative -ge guard rejects equality, but timestamp checks are weaker than binding approval to the exact reviewed commit. Keep untrusted pull-request builds separate from OIDC-enabled publishing jobs, rebuild from the authorized immutable SHA, and validate provenance before publication. No exploit payload is included.

UNPATCHED FLAW
# Deleted marimo-bot workflow: whole-second comparison
if [[ $(date -d "$pushed_at" +%s) -gt $(date -d "$COMMENT_AT" +%s) ]]; then
    exit 1
fi
# The resulting head_sha was checked out and its build artifacts
# were passed to separate package-publishing jobs.
HARDENED SECURE PATCH
# Illustrative control only; GHSL reports the vulnerable
# workflow was deleted rather than patched in place.
if [[ $(date -d "$pushed_at" +%s) -ge $(date -d "$COMMENT_AT" +%s) ]]; then
    exit 1
fi
# Stronger design: authorize an immutable reviewed SHA, rebuild
# from that SHA in a trusted job, then publish verified outputs.

Engineering & System Hardening Checklist

Sources