Skip to content

[Security] graphify.yml installs and runs a third-party Python package in the job that holds contents: write, and WorkflowHygiene's INSTALL_COMMAND does not know pip #1623

Description

@pathosDev

Component

.github/workflows/graphify.yml — the single rebuild job; tests/unit/ci/WorkflowHygiene.test.ts — INSTALL_COMMAND and the keeps write access away from installs assertion.

Severity (your assessment)

LOW — same class and calibration as #622 (the README-badge job) and #621: a push credential in the same job as third-party install scripts, reachable only through an upstream compromise. Found while closing #1622; not folded in there because the fix is a job split, not a flag.

Exploit walkthrough

graphify.yml#rebuild holds contents: write (graphify.yml:47-48), runs python3 -m pip install --disable-pip-version-check 'graphifyy==0.9.46' (:63), executes the installed tool over the checkout (graphify update ., :66) and then git pushes to develop with the checkout's persisted credential (:78). So a third-party package's install hooks and its runtime execute inside a job that can write to the default branch. The top-level pin is exact, but pip has no lockfile here: the transitive closure resolves afresh on every weekly run, unpinned and un-hashed, and a compromised release of any dependency of graphifyy runs with a token that pushes to develop.

docs/…/operations/security/supply-chain.mdx states "no job that can write to the repository also runs third-party install scripts" and that WorkflowHygiene.test.ts asserts it. It does not see this one: INSTALL_COMMAND is /\b(bun install|bunx|npm ci|npm install|npx|pnpm install|yarn install)\b/ — no pip install, pipx, uv, pip3 — so the write-access assertion passes over this job by vocabulary rather than by verification.

Affected files

  • .github/workflows/graphify.yml:44-80
  • tests/unit/ci/WorkflowHygiene.test.ts — INSTALL_COMMAND (search for the regex) and the keeps write access away from installs test
  • docs/src/content/docs/operations/security/supply-chain.mdx (+ DE mirror) — the third claim in "Frozen installs, least privilege"

Fix sketch / approach

// 1. Split the job the way #622 split test.yml: an unprivileged `rebuild`
//    job installs graphify, runs `graphify update .` and uploads
//    graphify-out/ as an artifact; a `push` job with `contents: write` that
//    installs nothing downloads it, commits and pushes. The concurrency
//    group and the [skip ci] commit stay as they are.
// 2. Pin the Python closure while it exists anywhere in CI: a hashed
//    requirements file (`pip install --require-hashes -r …`) generated once
//    from the 0.9.46 resolve, bumped with the graphify version.
// 3. Teach INSTALL_COMMAND pip (`pip3? install`, `pipx`, `uv (pip|tool) install`).
//    With the split landed first, the assertion goes green on the new shape
//    and red on the old one — verify that order, do not add an exemption.

Acceptance criteria

  • No job holding contents: write in .github/workflows/ runs a pip/pipx/uv install, and INSTALL_COMMAND knows those spellings, so the write-access assertion covers them.
  • Applied to the current tree first, the widened regex fails on graphify.yml#rebuild and nothing else.
  • The weekly graph refresh still lands as chore(graphify): refresh knowledge graph [skip ci] on develop.

Disclosure status

Fresh — first public mention

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinginfrastructureCI / build / live-integration testspriority: lowNice-to-have / niche / demand-drivensecuritySecurity-relevant — see severity label for impact tierseverity: lowMinor / informational / mitigated-by-design

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions