Skip to content

Security

Diatreme holds a GitHub App private key and mints short-lived, write-scoped installation tokens for other people's repositories. This page describes how it protects that, and what you're responsible for.

To report a vulnerability, use private vulnerability reporting. Don't open a public issue. The full policy, scope and response targets are in SECURITY.md.

What stops one repository minting another's token

/token takes an OIDC token and a repository. It mints only for the repository in the token's own repository claim: if the body asks for a different one, the request is refused with 403 repo_mismatch. The claim comes from GitHub, not from the caller, so a repository cannot ask for a token it wasn't issued for.

Verification pins the issuer before selecting its key set, so a token with a forged iss can't steer the broker into verifying against a different tenant's keys. Audience is checked against an explicit list. Expiry and not-before are enforced.

Self-hosted deployments can narrow further with ALLOWED_REPOSITORIES.

Token scope and lifetime

Minted tokens carry contents: write and pull_requests: write by default, and are scoped to a single repository. They inherit GitHub's lifetime, currently one hour. A deployment can narrow the permission set with TOKEN_PERMISSIONS, but nothing can widen a token past what the App itself was granted at install time.

The action masks the token with ::add-mask:: the moment it arrives, so it's redacted in the run log.

Secret handling

  • The App private key, the bearer secret and the webhook secret live in the platform's secret store:
  • Python/Lambda broker: AWS SSM Parameter Store and Lambda environment.
  • TypeScript Worker: Cloudflare Secrets and environment (.dev.vars is gitignored; only .dev.vars.example with placeholders is tracked).
  • The bearer on /sign and /releases is compared in constant time.
  • /webhook verifies an HMAC-SHA256 over the raw request body before parsing it, so an unsigned payload is never interpreted.
  • Nothing logs a token, or any slice of one. Failure logs carry claim metadata and the classified reason, never the credential, and every field is truncated so a hostile token can't flood the log.

Attack surface kept deliberately small

The TypeScript Cloudflare Worker implementation keeps workers_dev and preview_urls off in wrangler.jsonc. Either would expose a second publicly reachable hostname for a live token minter, inheriting production secrets and bypassing the rules bound to the custom domain. The custom domain is the only intended door. (The production Python Lambda broker has equivalent controls at the API Gateway layer.)

Repository owner and name are validated against A-Za-z0-9_.- before they're interpolated into any API URL.

What you're responsible for

Pin by SHA for strict supply-chain reproducibility. @v2 is a floating tag this repository force-updates after every stable release, so it moves under you by design. uses: MagmaMoose/diatreme@<sha> doesn't.

Grant the job only the permissions it needs. id-token: write is required for the OIDC exchange. contents: write and pull-requests: write are needed only for the modes that use them. See Setup.

Treat the broker hostname as part of your trust boundary. Overriding token-broker-url points your OIDC tokens at someone else's server. Those tokens are short-lived and repository-scoped, but they are still credentials. Don't override it unless you run the broker.

Supported versions

Version Supported
v2.x Yes
v1.x and earlier No, end of life. Move to @v2.