Clinejection: keep the issue triager away from release credentials
Incidents
An issue triager needs to read an issue and assign a label. It should have no path to publishing the software it is helping maintain. The February 2026 Clinejection disclosure shows how that separation can disappear across several otherwise ordinary CI features.
The disclosure and the confirmed release
On February 9, researcher Adnan Khan published Clinejection. In a repository mirror, he reproduced an issue-title injection that persuaded the triage agent to execute a package installation and expose his test API key. He described how execution in that workflow could poison caches consumed by a nightly release job, reaching publishing credentials. His original account explicitly left the initial vector behind suspicious real-world cache activity unconfirmed.
The later release incident is confirmed separately. Cline's February 17
advisory
says a compromised npm token published cline@2.3.0 with an added postinstall
that installed OpenClaw. OpenClaw was a legitimate package; its installation was
unauthorized. The CLI binary was unchanged, and the VS Code extension and
JetBrains plugin were unaffected. Cline released 2.4.0, revoked the token, and
reported switching npm publishing to OIDC provenance through GitHub Actions.
Khan's updated timeline includes Cline's acknowledgment that its earlier rotation had deleted the wrong npm token, leaving the exposed one active. The advisory confirms the unauthorized publish; it does not independently establish every step of the proposed injection-to-cache chain.
There are two credential problems to solve
The agent's model-provider key and the release job's publishing key serve different purposes. They need different protections.
For the model key, the goal is to let the agent make an authorized inference request without being able to read the reusable credential. For publishing, the goal is to ensure only a trusted release process can exercise release authority. Hiding a publishing token does not make attacker-controlled release code safe.
That distinction matters when adopting a secrets manager. If a poisoned release job can call the vault, decrypt its credentials, and publish, moving the token out of the CI settings page has not broken the attack path.
Give the triager a narrowly configured broker
For the agent's model requests, run
seekrit-proxy in a trusted runtime outside the
triage sandbox. Give its service token access to a dedicated environment
containing the triage model key and no publishing credentials.
A minimal reverse-proxy route for an Anthropic Messages client can look like this:
listen = "127.0.0.1:8080"
[[route]]
prefix = "/anthropic"
upstream = "https://api.anthropic.com"
methods = ["POST"]
paths = ["/v1/messages"]
allow = ["ANTHROPIC_API_KEY"]
This example assumes the sandbox can reach that loopback listener while being isolated from the proxy's process and files. For separate network namespaces, use a private broker address and restrict access to the intended workload.
Save it as seekrit-proxy.toml in that trusted runtime, provision its
SEEKRIT_TOKEN, and start the installed binary with
seekrit-proxy --config seekrit-proxy.toml.
The agent-side configuration contains only a base URL and placeholder:
export ANTHROPIC_BASE_URL=http://127.0.0.1:8080/anthropic
export ANTHROPIC_API_KEY='{{seekrit:ANTHROPIC_API_KEY}}'
The proxy strips the route prefix, checks the upstream method and path, and
substitutes the real key. Add other API operations only when the particular
client requires them. Keep SEEKRIT_TOKEN exclusively in the broker runtime,
protect the broker's configuration from the agent, and enforce sandbox egress
through the broker. A same-user process and an optional proxy environment
variable do not provide that isolation.
Now an injected command that prints the agent's API-key variable gets a placeholder. It can still spend inference through the allowed route, so provider quotas and usage alerts remain useful.
Keep release authority in a separate trust domain
Brokering the model key does not prevent cache poisoning. The release design still needs to ensure that untrusted triage work cannot supply executable state to a privileged job.
- Give triage only the tools and GitHub permissions its task needs.
- Build releases from reviewed inputs on clean runners. Avoid restoring executable caches writable by less trusted workflows.
- Separate nightly and production publishing identities at the registry, not just by naming two CI variables differently.
- Prefer supported OIDC publishing flows to stored registry tokens, while still protecting the workflow that can request an OIDC identity.
Where a publisher still requires a static credential, put it in a separate Seekrit application environment with its own runtime token. Inject it only into the trusted publishing process. The process can read that value, so installing untrusted dependencies inside it remains unsafe. See the CI/CD guide for runtime wiring.
Verify the credential actually stopped working
Treat revocation as an outcome to check. Identify the exposed token, revoke it at its issuer, and use a controlled, non-mutating check to confirm the old token is rejected. Review registry releases and CI activity for the exposure window.
If a Seekrit service token was also exposed, revoke it and replace the runtime identity. Rotate upstream credentials that it could have revealed. Removing a vault grant cannot erase a value already decrypted or invalidate that value at its provider.
The useful end state is specific: the triager can call its model and perform its assigned repository operations; the release system can publish reviewed code; neither inherits the other's credentials or executable state. Seekrit's broker and environment-bound tokens support that separation. They depend on the CI design preserving it.