Credentials and trust boundary (hosted)
On the hosted service, your runner holds the credentials it works with, in Secrets in its own namespace. The TruStacks control plane holds no credential for your cluster and does not store your LLM key. It holds one thing that touches your repos: the credential for the shared TruStacks GitHub App, which it uses to mint short-lived tokens for the repos you grant.
Every credential, at a glance
| Credential | Lives in | Held by TruStacks? | How it gets there |
|---|---|---|---|
| Enrollment token | Secret trustacks-runner-enrollment in your namespace | A hash only | You supply it at install |
| Runner identity keypair | Runner memory, plus Secret trustacks-runner-identity | Public key only | Generated by the runner on first start |
| Runner session | Runner memory | Issued and checked by the control plane | Exchanged for the enrollment token; short-lived |
| LLM API key | Secret trustacks-runner-llm in your namespace | In memory only, in transit (see below) | You paste it in the app; it is sealed to your runner |
| GitHub installation tokens | Runner memory | Minted by the control plane | Sealed to your runner per job; expire within an hour |
| TruStacks GitHub App private key | TruStacks control plane | Yes | Never leaves TruStacks |
| ArgoCD’s access to your platform repo | Your ArgoCD | No | You configure it |
| Registry pull credentials for your images | Your cluster | No | You configure them |
| Cloud, cluster-admin, production secrets | Your environment | No | TruStacks never asks for them |
Sealed delivery
Every credential the control plane sends to a runner is sealed to that runner’s public key, on top of TLS.
- On first start the runner generates an X25519 keypair. It registers the public key with the control plane when it enrolls.
- When the control plane has something for the runner (your LLM key, a GitHub token for a job), it encrypts it to that specific runner’s key. Only that runner can open it; another runner in the same workspace cannot.
- The runner long-polls for pending material, unseals it, and writes or uses it inside its own namespace, through its own ServiceAccount.
The control plane never pushes into your cluster and never holds a
kubeconfig for it. That is why the chart grants the runner get, create,
and update on Secrets in its own namespace: the runner is the only writer.
Your LLM key
When you save a key in Settings, the control plane keeps it in an in-memory buffer for a few minutes, until your runner collects it. It is not written to the TruStacks database or a secret store. What the control plane keeps is the outcome: provider, whether the key validated, and when.
If your runner is offline when you save, the key times out of the buffer and the app asks you to save again once the runner is back. To rotate, save a new key the same way.
The one in-the-clear hop. Between your browser and the control plane the key is protected by TLS, not sealed. It is sealed from the control plane to your runner.
The enrollment token
The token is a reusable secret that does not expire, because the runner re-presents it when it re-enrolls. We store only its hash. It is not the only long-lived secret in your namespace: the runner’s identity keypair and your LLM key live in Secrets there too. Protect all three the same way.
Removing a runner in Runners drops that runner’s registered key, so nothing more is sealed to it. It does not revoke the enrollment token, and while the token is valid the removed runner enrolls again at its next session renewal. To cut off one runner, revoke the tokens and then remove it. If a token is exposed, revoke it yourself: Revoke and generate a new token on Runners revokes every token in the workspace and mints one replacement. A revoked token can never enroll a new runner. Runners on 0.2.14-rc26 or later keep renewing their session on their own registered key, with the session sealed to that key, so a copy of the token is useless without the runner’s private key. Older runners disconnect within about 15 minutes until you give them the new token (see Revoke enrollment tokens). Treat the token like any other cluster secret, and prefer supplying it from a Secret you manage (see Runner install).
The GitHub App
TruStacks operates one shared GitHub App. You install it on your account or organization and choose which repos it can see. You never register an App of your own, and you never hand TruStacks a personal access token.
Permissions it asks for
| Permission | Access | Used for |
|---|---|---|
| Contents | Read and write | Clone service and platform repos; push proposal branches to the platform repo |
| Workflows | Read and write | Proposals add or change files under .github/workflows/ in the platform repo |
| Pull requests | Read and write | Open pull requests; watch whether they merge |
| Metadata | Read | Required by GitHub for every App |
These apply to every repo you grant the installation. GitHub has no way to give one App different permissions on different repos. So the “read-only” role for a service repo is enforced by TruStacks, not by GitHub: TruStacks does not write to repos bound as services. Choosing which repos the installation can see limits where the App can write, not whether it can: on every repo you grant, it has Contents write.
Agents are built to push only to proposal branches and open pull requests. They do not push to your default branch, approve, or merge. That is TruStacks behavior, not a GitHub restriction. To have GitHub enforce it, protect the default branch of every repo you grant (branch protection or a ruleset that requires a pull request and review) and leave the TruStacks GitHub App off the bypass list.
What the control plane does with it
The control plane holds the App’s private key. With it, it:
- mints short-lived installation tokens, sealed to your runner, for cloning and opening pull requests;
- reads
environment-profile.yamlfrom the root of your platform repo; - watches the default branch of your service repos, to propose image tag updates;
- watches proposal pull requests, to learn when they merge.
Uninstalling the App in GitHub cuts all of this off immediately.
What the runner never holds
- Cluster-admin or cross-namespace access. Its ServiceAccount is scoped to its own namespace. The optional ArgoCD observer adds read-only access to ArgoCD Applications in one namespace, nothing else.
- Cloud provider credentials. Infrastructure changes reach your cluster through your GitOps controller after a human merges.
- Production application secrets. Generated manifests reference Secrets by name; agents never resolve them.
- A way around your branch protection. Its installation tokens carry Contents write on the repos you grant, so protect their default branches and keep the TruStacks GitHub App off the bypass list; then a token cannot push there either.
Where to go next
- How TruStacks runs · what crosses the boundary, in plain terms
- Runner install · RBAC, egress, and verification
- GitOps integration · ArgoCD’s own credentials, separate from the runner’s