feat(ci): substitute from the shared binary cache - #59
Conversation
tlater-famedly
left a comment
There was a problem hiding this comment.
I don't think we need a separate workflow for uploads; the cachix action will implicitly fill the cache with any used artifacts, and the other workflows will be exercising those. Let's reduce the amount of workflow clutter.
#50 already covers enabling cachix, on the other hand, it's just blocked by the fact that our CI secrets don't currently work this way.
Every workflow built the toolchain from source, while the Cachix the rest of the company uses sat next to it unused. The step lives in `steps.setup`, so every generated workflow gets it. Only `main` and the tags write: a pull request and a merge queue entry both build what a branch decided, and a run there that could write would let any branch put a store path in front of every other repository's builds. The ref decides this rather than the event name, which gets two cases wrong: a run started by hand is not a `push`, and a `push` is whatever branch a repository chose to run on. Reading may fail without failing the workflow. What a trusted ref builds is pushed by the same step, so a workflow that already runs on `main` fills the cache without a job of its own. The cache is named, not hardcoded. Product repositories keep `famedly`. This one is public, so it takes `famedly-oss`: GitHub will not hand a private cache's token to a public workflow, and a private cache cannot be read without one. Signed-off-by: Niklas Zender <n.zender@famedly.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit ad98ae0. Configure here.
| signingKey = "\${{ secrets.CACHIX_SIGNING_KEY_${suffix} }}"; | ||
|
|
||
| skipPush = "\${{ github.ref != 'refs/heads/main' && !startsWith(github.ref, 'refs/tags/') }}"; | ||
| }; |
There was a problem hiding this comment.
Cache credentials leak on PRs
High Severity
skipPush stops the action from uploading, but authToken and signingKey are still passed on every ref. cachix-action authenticates the runner (cachix authtoken persists the token), and later steps run pull-request code that can read those credentials and write to the shared cache — the poisoning path this change is meant to close.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit ad98ae0. Configure here.
| # Public, so the public cache. GitHub will not hand a private cache's | ||
| # token to a public workflow, and a private cache cannot be read | ||
| # without one. | ||
| famedly.standards.ci.binaryCache.name = "famedly-oss"; |
There was a problem hiding this comment.
Cache never fills from this repo
Medium Severity
This repository only runs CI on pull_request and merge_group, so skipPush is always true and nothing trusted ever pushes. There is no populate-binary-cache (or other main/tag) job, and the shared step always sets continueOnError, so famedly-oss stays empty and later runs still build from source.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit ad98ae0. Configure here.


Every workflow builds the toolchain from source, while the Cachix the rest of the company uses sits unused next to it.
Reading. A step in
steps.setup, so every generated workflow gets it. It may fail without failing the workflow: a cache out of reach should cost a run its time and not its result.Writing. Only
mainand tags. A pull request and a merge queue entry both build what a branch decided, and a run there that could write would let any branch put a store path in front of every other repository's builds. The ref decides this rather than the event name, which gets two cases wrong: a run started by hand is not apush, and apushis whatever branch a repository chose to run on.Filling. No extra workflow. The cachix action pushes what a trusted ref already built, so image and web jobs that run on
mainfill the cache without a job of their own.Which cache. Named, not hardcoded. Product repositories keep the default
famedly. This repository is public, so it takesfamedly-oss: GitHub will not hand a private cache's token to a public workflow, and a private cache cannot be read without one. Secrets follow the name:CACHIX_AUTH_TOKEN_<NAME>andCACHIX_SIGNING_KEY_<NAME>, hyphens turned into underscores.Addresses the review on the previous shape of this PR: the populate workflow is gone, and this public repository no longer talks to
famedly.Test plan
prek --all-files --stage pre-pushskipPushis false only forrefs/heads/mainandrefs/tags/*mainin a product repository pushes what it built, and a later pull request substitutes it