cellcast issues credentials for production clusters. A vulnerability here is a route to cluster access rather than a defect in a tool that only observes.
Please do not open a public GitHub issue for security-related reports.
Report vulnerabilities privately via the GitHub Security Advisory page for this repository:
https://github.com/ethan-kane-ops/cellcast/security/advisories/new
When submitting a report, please include:
- Affected component: hub, agent, client, or chart. The hub is the only one that can mint, so a finding there is more serious by default.
- Description: what the vulnerability is, and its impact stated as what an attacker ends up holding.
- Reproduction: step-by-step instructions, including the
Cluster,PlacementPolicyandTrustConfigmanifests, and the claims of the token presented. Redact token material; the claims are what matters. - Mitigation: any temporary workaround you have identified.
| Stage | Target |
|---|---|
| Acknowledge receipt | 48 hours |
| Triage and confirm severity | 7 days |
| Fix released for critical severity | 14 days |
Reporters will be credited in the release notes for the fix unless they request anonymity.
The documented trust boundaries are in docs/threat-model.md, and anything that crosses one is in scope. In particular:
- Authentication bypass. Any way to have the hub accept a token it should have rejected: a forged or replayed assertion, an issuer or audience that is not the configured one, a signature the hub does not actually verify.
- Policy bypass. Any way to be placed on, or minted for, a cell that no
matching
PlacementPolicypermits. This includes reaching aDARKcell without a policy that allows it, and any path where a client-supplied value changes which policy applies. - TTL bypass. Any minted credential outliving
--token-max-ttl, or a policy's own ceiling. - Credential disclosure. Token material reaching a log line, an audit record, a metric label, a Kubernetes Event, or an error returned to a caller. A test asserts that no field of the audit record can hold it; a way around that is a finding.
- Confused deputy in capacity ingest. Any way for one cell's agent to publish capacity for a different cell, which steers real deploys.
- Privilege escalation through the charts. RBAC in either chart that grants more than the component needs.
- An operator granting a policy more than they meant to. cellcast enforces the
policy that is written, and
--dry-run --explainshows what a policy does before it is relied on. - A compromised hub cluster. Whoever controls the hub's ServiceAccount can read the trust configuration it holds and mint what that configuration allows; that is the hub's whole job, and the threat model says so.
- Denial of service by a caller who is already authenticated and permitted.
- A cell whose service account issuer URL collides with another cell's, on a cluster that was configured that way. The hub binds a reporter identity to an issuer, so distinct issuers are a prerequisite the docs state plainly.
Only the latest minor release line receives security updates. Today that is
v0.1.x.
| Version | Supported |
|---|---|
| Latest minor | Yes |
| Anything older | No |
Pre-1.0, the API and the CRD schema may change between minor versions.
govulncheck runs over the source and over the built binaries. It reports only
advisories reachable through the call graph rather than every advisory touching
a dependency, so a non-empty result names something to fix rather than a list to
triage. It runs in just check-all, which just release runs, so a release
cannot ship a known reachable vulnerability.
CodeQL analyses the source on every push and weekly. OSSF Scorecard runs against
main and publishes its result. Both are computed against a public repository,
so neither produces a score until the repository is one.
The repository-side properties they inspect are already in place and held by
tests: base images pinned by digest rather than tag, no compiled artefact in the
tree, a private reporting route in this file, a declared licence matching the one
the charts advertise, a release that signs everything it publishes, and fuzz
targets over every parser that reads untrusted input. Every workflow declares a
scoped token and pins each action to a commit rather than a tag, which
just check enforces: a tag can be moved, and moving it hands a third party
write access on the next run.
Every published image and chart is signed with cosign keyless signing, and every
image carries an SBOM and SLSA build provenance as attestations. The exact
cosign verify command, including the identity and issuer to expect, is in the
README under Verifying what you install.
Keyless means the signature is tied to an identity rather than to a key, so a signature on its own proves nothing: verification has to name the expected signer. The release runs that verification against what it has just published and fails if the identity does not match.
Images are also reproducible from their tag. The base images are pinned by
digest, the build runs with -trimpath, and the timestamps come from the commit
rather than the build clock, so rebuilding a tag from source produces the same
bytes. See Releasing.