Cross-repo artifact isolation is a storage path prefix over one shared credential, not an access boundary #15

Open
opened 2026-07-05 19:43:29 +00:00 by stephen · 0 comments
Owner

Why

Artifacts are "scoped per repository" only by a storage key prefix (<prefix>/<scope>/artifacts/<run-id>/...), where the scope is read from the repository input, GITHUB_REPOSITORY, or RASTER_ARTIFACTS_SCOPE, all set inside the CI job, while every repo shares one S3 credential. So any repo's workflow can set the scope to another repo and read its artifacts; the repository input is the sanctioned way to do exactly that. On a shared runner pool this lets one repo read another repo's build artifacts. This mirrors the shared-cache isolation concern (cache-action #95) on the artifact plane.

Current code

The download action exposes cross-repo reads:

# action.yml:27-29
repository:
  description: 'Download artifacts from a different repository (owner/repo). Requires backend access to that scope.'
  required: false

The scope is whatever the job supplies, with no check that it matches the running repo:

// src/lib/config.js:48
const repo = overrides.repository || process.env.GITHUB_REPOSITORY || '';
// src/lib/download-impl.js:28,32
const repository = core.getInput('repository');
...
repository: repository || undefined,

The write side documents the override as the "isolation prefix" over the same shared RASTER_ARTIFACTS_S3_* credentials:

upload-artifact-action/README.md:83
| `RASTER_ARTIFACTS_SCOPE` | Override the per-repo isolation prefix. |

Suggested first PR

Treat the prefix as convenience, not a security boundary: document explicitly that cross-repo / cross-tenant isolation requires separate per-scope credentials (or a signed scope claim) at the storage backend, and gate the repository / RASTER_ARTIFACTS_SCOPE override behind an opt-in so a plain job cannot silently read another repo's scope. Longer term, mint per-repo (or per-run) scoped credentials instead of one shared key.

Found in the 2026-07-05 next-wave sweep; grounding re-verified at filing time.

## Why Artifacts are "scoped per repository" only by a storage key prefix (`<prefix>/<scope>/artifacts/<run-id>/...`), where the scope is read from the `repository` input, `GITHUB_REPOSITORY`, or `RASTER_ARTIFACTS_SCOPE`, all set inside the CI job, while every repo shares one S3 credential. So any repo's workflow can set the scope to another repo and read its artifacts; the `repository` input is the sanctioned way to do exactly that. On a shared runner pool this lets one repo read another repo's build artifacts. This mirrors the shared-cache isolation concern (cache-action #95) on the artifact plane. ## Current code The download action exposes cross-repo reads: ```yaml # action.yml:27-29 repository: description: 'Download artifacts from a different repository (owner/repo). Requires backend access to that scope.' required: false ``` The scope is whatever the job supplies, with no check that it matches the running repo: ```js // src/lib/config.js:48 const repo = overrides.repository || process.env.GITHUB_REPOSITORY || ''; ``` ```js // src/lib/download-impl.js:28,32 const repository = core.getInput('repository'); ... repository: repository || undefined, ``` The write side documents the override as the "isolation prefix" over the same shared `RASTER_ARTIFACTS_S3_*` credentials: ``` upload-artifact-action/README.md:83 | `RASTER_ARTIFACTS_SCOPE` | Override the per-repo isolation prefix. | ``` ## Suggested first PR Treat the prefix as convenience, not a security boundary: document explicitly that cross-repo / cross-tenant isolation requires separate per-scope credentials (or a signed scope claim) at the storage backend, and gate the `repository` / `RASTER_ARTIFACTS_SCOPE` override behind an opt-in so a plain job cannot silently read another repo's scope. Longer term, mint per-repo (or per-run) scoped credentials instead of one shared key. Found in the 2026-07-05 next-wave sweep; grounding re-verified at filing time.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
rasterstate/download-artifact-action#15
No description provided.