Skip to content

security: harden supply-chain posture against maintainer-account and dep-compromise attacks #656

Description

@Freezaa9

Why

Three recent incidents make it worth auditing our supply-chain posture as a public, pip-installable + npm-adjacent project:

  1. Mistral client-pythonGHSA-wx9m-wx4f-4cmg (advisory)
  2. TanStack npm packages compromised — "Mini Shai-Hulud" supply-chain attack (Socket writeup)
  3. guardrails-ai 0.10.1 __init__.py suspicious content (Socket file view) — relevant to us because guardrails-ai is a runtime extra of idun-agent-engine (we just moved it to optional in fix(engine): make guardrails-ai an optional extra to unblock PyPI quarantine #642, but adopters who opt in still pull it).

These attacks fall into three buckets we should be defending against:

  • Maintainer-account takeover → malicious publish. Phishing or session-cookie theft on a maintainer's PyPI/npm/GitHub account leads to a poisoned release that auto-installs in CI everywhere.
  • Transitive-dep compromise. A dependency (direct or transitive) gets a malicious version published; our wheels pull it in.
  • Post-install / import-time code execution. Malicious code in setup.py / __init__.py / npm postinstall fires on pip install or npm install.

What we already have

Gaps to close

A. Publishing pipeline

  • Migrate TestPyPI publishes to OIDC trusted publishing. Today publish_engine_testpypi.yml and publish_schema_testpypi.yml still use the long-lived TEST_PYPI_API_TOKEN secret. A token leak from a workflow log or a compromised fork PR would be enough to ship a poisoned dev snapshot that internal smoke tests then pull. Same OIDC model as release.yml.
  • Sigstore signing + PEP 740 attestations on all published wheels. pypa/gh-action-pypi-publish@release/v1 supports attestations: true when used with trusted publishing. Produces verifiable provenance ("this wheel was built by this workflow run at this commit") that downstream consumers can check.
  • Require manual approval (GitHub environments) for the production release.yml publish job. Even with OIDC, a compromised main could trigger a tagged release. An environment: pypi-prod with required reviewers gates this.
  • Publish an SBOM (CycloneDX or SPDX) alongside each release. Lets adopters diff what changed between versions and audit transitive deps.

B. Maintainer-account hardening (organisation policy)

  • Enforce 2FA on the Idun-Group GitHub org (org setting → security → require 2FA).
  • Document a hardware-key requirement for accounts with publish access on PyPI / npm. PyPI now supports WebAuthn; npm requires it for maintainers of high-impact packages.
  • Restrict PyPI / npm publish roles to the minimum number of maintainers. Audit current owner/maintainer lists for idun-agent-engine, idun-agent-schema, and the npm side (if applicable). Remove inactive accounts.
  • Document a credential-rotation + incident runbook in SECURITY.md covering: maintainer-account suspicion → revoke tokens → yank affected versions on PyPI → notify Socket / GHSA → post-mortem template. Shai-Hulud spread because nobody had a 1-hour-from-detection plan.

C. Dependency hygiene

  • Lockfile audit on every PR. We have uv.lock and pnpm-lock.yaml. Add a workflow step that fails the PR if a lockfile changed without a corresponding pyproject.toml / package.json change (catches lockfile-only injection).
  • pip install --require-hashes for the engine wheel's own deps in CI smoke tests. Catches a TanStack-style mid-air swap during install.
  • OpenSSF Scorecard workflow on main — public score + alerts on regressions.
  • Pin guardrails-ai to a specific known-good version range in the [guardrails] extra rather than a floor pin, given the 0.10.1 incident. Add a comment linking to the Socket report. (Now that fix(engine): make guardrails-ai an optional extra to unblock PyPI quarantine #642 made it optional, the blast radius is smaller, but adopters who opt in still need a safe default range.)

D. Workflow-token blast radius

  • Set permissions: contents: read as the default at the workflow level for every workflow, then escalate per-job. Reduces what a compromised action can do.
  • Disallow pull_request_target unless we explicitly need it; prefer pull_request everywhere. pull_request_target runs with the target branch's secrets — the attack vector behind several recent action-compromise incidents.
  • Block third-party action submissions in CI. Only allow actions from actions/, pypa/, astral-sh/, Idun-Group/, and an explicit allowlist. Enforced via repo settings (Settings → Actions → "Allow specified actions").

Acceptance

This issue is closed when:

  • Section A items A1–A4 have shipped and are linked here.
  • Section B is reflected in updated SECURITY.md + org settings (org-side changes confirmed by screenshot in a comment).
  • Section C has at least the lockfile-audit step, OpenSSF Scorecard, and guardrails-ai pin range in place.
  • Section D's permissions: default + action allowlist are in repo settings.

Out of scope

  • Runtime-side hardening of agent execution (sandboxing, network egress controls) — separate concern, separate issue.
  • Customer-side guidance for verifying installed wheels — belongs in docs/, follow-up after attestations ship.

References: #634 (Socket gates), #638 (SHA-pin + dep-audit + SECURITY.md), #641 (CVE fixes + pinact), #642 (guardrails-ai optional extra).

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions