Skip to content

feat: initial cache-poisoning audit#294

Merged
woodruffw merged 13 commits into
zizmorcore:mainfrom
tewokumu:ufs/patch-12
Dec 19, 2024
Merged

feat: initial cache-poisoning audit#294
woodruffw merged 13 commits into
zizmorcore:mainfrom
tewokumu:ufs/patch-12

Conversation

@ubiratansoares

@ubiratansoares ubiratansoares commented Dec 13, 2024

Copy link
Copy Markdown
Contributor

A first iteration on #261

There is a lot to unpack in this PR already, so I added several inline comments, and I'll go for docs as soon as we are good with the overal design 🙂

First consideration : for now, I'm matching as triggers for this audit only release and push (with tags filters).
I'm aware that more scenarios exist : I myself use another pattern (on top of workflow_dispatch), happy to get input and iterate, or eventually tackle them as a follow-up. I miss a check for the default branch of the repo, but since it requires querying the Github API, I've skipped it for now.

Second consideration: because of the multitude of caching-aware Actions that exist (and how they behave), and because we should aim to consume a static API at some point in the future, I've modelled a CacheAwareAction struct, which is the very core of this PR.

In general, I tried to capture with this data holder:

  • that some actions will run caching behavior by default, both for saving and restoring (eg actions/setup-go)
  • that some actions have an input that allows either with opt-in or opt-out from restoring cache (eg actions/cache)
  • that some actions define opt-in not by specifying a boolean input, but actually a set of values (eg actions/setup-node)
  • eventually the user opts-in for caching on top of an expression: there is not much to do, but I keep track of that

There is a static list of CacheAwareAction, covering all Actions provided by Github and a few others. Happy to add as many as we want to have 🔥

Third consideration: I defined a strategy for this audit on top of a chat I had with Adnan, focusing on the unzip part of cache-poisoning attacks. This means I'm not considering actions/cache/save in the Actions we inspect for.

Most of the Actions I've listed bring both saving/restoring out-of-the-box, but some of them define a lookup-only input (as per actions/cache and actions/cache/restore do), that only queries the Github API for cache entries but do not download any caches. Maybe this is incomplete/insufficient, and I'd love to get inputs here!

Last but not least : I added several examples of vulnerable Workflows and snapshot test all of them.

Most likely I missed other things, but hopefully this PR sesa a place to get started 🙂

Comment thread src/audit/cache_poisoning.rs
@woodruffw

Copy link
Copy Markdown
Member

Thanks @ubiratansoares! I'll try and do an initial pass on this today or tomorrow.

Comment thread src/audit/cache_poisoning.rs
Comment thread src/audit/cache_poisoning.rs Outdated
Comment thread src/audit/cache_poisoning.rs Outdated

let trigger = &step.workflow().on;

if !self.trigger_used_when_publishing_artifacts(trigger) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good for now, thanks!

Thinking out loud: another future proxy we could use for "is this a publishing workflow" is whether or not the workflow contains any well-known publishing actions, e.g. gh-action-pypi-publish.

Not something to do with this iteration, but worth thinking about 🙂

@woodruffw woodruffw added enhancement New feature or request new-audit New audits labels Dec 13, 2024
Comment thread src/audit/cache_poisoning.rs
@ubiratansoares

Copy link
Copy Markdown
Contributor Author

@woodruffw Addressed your suggestions, let me know whether I missed something! Docs to follow 🙂

@woodruffw

Copy link
Copy Markdown
Member

Thanks! I think that's everything -- just undraft and give me a ping when you're ready for a final review!

@ubiratansoares

ubiratansoares commented Dec 17, 2024

Copy link
Copy Markdown
Contributor Author

@woodruffw Added some docs here, let me know what you think :)

Some follow-ups from this PR, so we can keep its size manageable for the review:

  • Evaluate the presence of well-know publish-like Actions (e.g. gh-action-pypi-publish) in addition to the trigger-based evaluation
  • Improve evaluation for push trigger with well-know branch paths (eg, release-* branches) ?
  • Add more Actions in the list of well-know cache-aware Actions 😄

@ubiratansoares ubiratansoares marked this pull request as ready for review December 17, 2024 09:33
Comment thread docs/audits.md Outdated
@ubiratansoares

ubiratansoares commented Dec 17, 2024

Copy link
Copy Markdown
Contributor Author

Added this additional commit to highlight another (potential) follow-up : should we evaluate step.if to avoid some false positives, since the user can avoid cache restoration by disabling the a cache-aware step?

Comment thread src/audit/cache_poisoning.rs Outdated
Comment thread src/audit/cache_poisoning.rs Outdated
Comment thread src/models.rs Outdated
Comment on lines +593 to +595
pub(crate) fn as_template(&self) -> String {
format!("{}/{}", self.owner, self.repo)
}

@ubiratansoares ubiratansoares Dec 17, 2024

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created this new method as a helper, since I need to get a template from what we have in well-know list, essentially due the asymmetrical nature of RepositoryUses::matches

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't love that we have to re-build the slug here, but that's my fault for building a bad abstraction here 😅 -- I'll try and clean this up as part of the final review.

@woodruffw

Copy link
Copy Markdown
Member

Thanks! I'll give this a final pass tomorrow, but this is looking great to me!

Let's track the follow-ups in #294 (comment) with a new issue 🙂

Signed-off-by: William Woodruff <william@yossarian.net>
Allow `RepositoryUses::matches` to take
`impl TryInto<RepositoryUses>` instead, which has
an auto-derived infallible impl for `RepositoryUses`
itself.

Signed-off-by: William Woodruff <william@yossarian.net>
GHA doesn't know the difference between `true`
and `'true'`, so neither should we.

Signed-off-by: William Woodruff <william@yossarian.net>
Signed-off-by: William Woodruff <william@yossarian.net>
@woodruffw

woodruffw commented Dec 19, 2024

Copy link
Copy Markdown
Member

ee7d227 (#294) tweaks the audit slightly so that it catches our own problematic pypi.yml workflow. I need to think about how best to fix that, plus how best to ensure this audit sees our fix.

Signed-off-by: William Woodruff <william@yossarian.net>
Signed-off-by: William Woodruff <william@yossarian.net>
@woodruffw woodruffw changed the title feat: initial iteration on cache-poisoning audit feat: initial cache-poisoning audit Dec 19, 2024
@woodruffw woodruffw enabled auto-merge (squash) December 19, 2024 03:40

@woodruffw woodruffw left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, great work @ubiratansoares!

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

Labels

enhancement New feature or request new-audit New audits

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants