Severity
P0 / critical security
Review baseline
0bd0e314696e520aa47620cbe8f008d010e01e57
Summary
require_protocol_bearer authorizes the system token or any valid locally signed JWT, without checking the token capability/scope. Job runtime JWTs are deliberately exposed to workflow steps as ACTIONS_RUNTIME_TOKEN, so a job credential can authenticate to runner-protocol endpoints that are not job-scoped.
The protected router includes destructive and cross-identity operations such as runner deletion, session creation/deletion, job completion, timeline/log mutation and broker operations.
Example source-derived attack
A workflow step holding its own ACTIONS_RUNTIME_TOKEN can authenticate to a path such as:
DELETE /_apis/distributedtask/pools/1/agents/{victim_runner_id}
Authorization: Bearer <job runtime JWT>
require_protocol_bearer accepts the token solely because its HMAC verifies. The handler then trusts the runner id in the URL and can purge that runner identity. The guest control socket permits the /_apis/... protocol surface, so this is reachable from inside runner VMs.
The same capability-confusion pattern affects other runner/session/reporting routes that rely on the generic protocol gate rather than an exact typed identity.
Impact
A compromised or intentionally malicious job may be able to:
- terminate or deregister sibling runners;
- disrupt/requeue sibling jobs;
- create/delete sessions outside its own identity;
- mutate another job's timeline/log/reporting state if identifiers are learned;
- call broker/runner-management surfaces with a credential intended only for its own job runtime.
Recommended remediation
Replace generic local-JWT acceptance with typed capabilities, e.g.:
SystemIdentity
RunnerManageIdentity
RunnerListenIdentity { runner_id }
JobRuntimeIdentity { plan_id, job_id }
DebugWorkerIdentity { job_id }
Every handler should require the narrowest identity type and verify that path/body runner, session, plan and job identifiers match it. A valid signature should establish who issued the credential, not grant access to every protocol endpoint.
The existing debug-session split between WorkerJob and JobRuntimeIdentity is a good pattern to extend.
Acceptance criteria
- A job runtime token is rejected by runner-management and runner-listen endpoints.
- A runner-listen token cannot mutate another runner's identity/session.
- Job-scoped reporting endpoints reject mismatched plan/job ids.
- All protocol aliases have explicit capability tests, including guest
SocketSurface access.
- Negative tests prove a workflow step cannot deregister another runner with
ACTIONS_RUNTIME_TOKEN.
Severity
P0 / critical security
Review baseline
0bd0e314696e520aa47620cbe8f008d010e01e57Summary
require_protocol_bearerauthorizes the system token or any valid locally signed JWT, without checking the token capability/scope. Job runtime JWTs are deliberately exposed to workflow steps asACTIONS_RUNTIME_TOKEN, so a job credential can authenticate to runner-protocol endpoints that are not job-scoped.The protected router includes destructive and cross-identity operations such as runner deletion, session creation/deletion, job completion, timeline/log mutation and broker operations.
Example source-derived attack
A workflow step holding its own
ACTIONS_RUNTIME_TOKENcan authenticate to a path such as:require_protocol_beareraccepts the token solely because its HMAC verifies. The handler then trusts the runner id in the URL and can purge that runner identity. The guest control socket permits the/_apis/...protocol surface, so this is reachable from inside runner VMs.The same capability-confusion pattern affects other runner/session/reporting routes that rely on the generic protocol gate rather than an exact typed identity.
Impact
A compromised or intentionally malicious job may be able to:
Recommended remediation
Replace generic local-JWT acceptance with typed capabilities, e.g.:
Every handler should require the narrowest identity type and verify that path/body runner, session, plan and job identifiers match it. A valid signature should establish who issued the credential, not grant access to every protocol endpoint.
The existing debug-session split between
WorkerJobandJobRuntimeIdentityis a good pattern to extend.Acceptance criteria
SocketSurfaceaccess.ACTIONS_RUNTIME_TOKEN.