Skip to content

fix(ci): zip-slip-safe extraction in sync-skills workflow#70

Open
phjlljp wants to merge 1 commit into
PostHog:mainfrom
phjlljp:fix/sync-skills-zip-slip
Open

fix(ci): zip-slip-safe extraction in sync-skills workflow#70
phjlljp wants to merge 1 commit into
PostHog:mainfrom
phjlljp:fix/sync-skills-zip-slip

Conversation

@phjlljp
Copy link
Copy Markdown

@phjlljp phjlljp commented May 8, 2026

Summary

  • Replaces the three unzip -o calls inside sync-skills.yml with a safe_unzip shell function backed by python's zipfile module. Every entry's resolved path is checked against the destination root; absolute paths and .. traversal are rejected before any member is extracted.
  • Adds permissions: {} at the workflow root. The destructive writes (commit, PR creation, auto-merge) flow through the explicit GH App token already plumbed into the workflow, so the implicit GITHUB_TOKEN does not need any scope.

Why

unzip -o happily writes to whatever path the archive specifies — including ../foo and /etc/foo — meaning a tampered skills.zip from PostHog/posthog or skills-mcp-resources.zip from PostHog/context-mill can write outside skills/ during the daily sync. The reachable blast radius on the runner includes .github/workflows/, hooks/, scripts/, and .git/ — all files the next workflow run picks up via actions/checkout. Even though the immediate trust chain (PostHog/posthog → ai-plugin) is internal, the workflow runs daily on a cron with broad write authority, so it deserves defensive parsing rather than implicit trust.

The auto-merge step at the end of the workflow is intentionally untouched — that is a maintainer policy decision and out of scope for the security fix.

Test plan

  • python3 -c "import yaml; yaml.safe_load(open('.github/workflows/sync-skills.yml'))" parses cleanly
  • Empirically tested the extractor logic against three crafted archives:
    • clean.zip with normal entries → extracts (rc=0)
    • traverse.zip containing ../escaped.txt → refused (rc=1, "traversal or absolute path")
    • absolute.zip containing /etc/test.txt → refused (rc=1, "traversal or absolute path")
  • CI green on this PR
  • Next scheduled sync run completes successfully against legitimate agent-skills-latest and context-mill releases

Notes

This is one of three security PRs from a strict-zero-trust audit; the other two cover POSTHOG_HOST validation (#68) and the gate-exec-write.sh parser bypasses (#69). They land independently of this one.

Plain `unzip -o` does not validate archive entry names. A malicious
release zip in PostHog/posthog or PostHog/context-mill — or any
upstream typosquat — could carry `../foo` or `/abs/foo` entries that
write outside `skills/` during the daily sync, tampering with workflow
files, hooks, or other ai-plugin source. Replace the three `unzip -o`
calls with a `safe_unzip` helper that uses python's `zipfile` and
rejects any entry whose resolved path would escape the destination.

Also adds `permissions: {}` at workflow root. The actual writes flow
through the explicit GH App token already plumbed in, so the implicit
`GITHUB_TOKEN` does not need any scopes — default-deny is the right
posture.

This PR intentionally does not touch the auto-merge step at the end of
the workflow. That is a maintainer policy decision and out of scope for
the security fix.
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.

1 participant