Verify session identity in self-hosted environments changedself-hosted-environments-identity
Nearest release: v2.1.314, published 5 hours after upstream edited the page. Shown because the two are within 24 hours of each other. Nothing here says the release caused the edit.
Upstream edited this page at 29 Oct 2026 18:56 UTC, give or take a minute or two: the time comes from Anthropic’s own sitemap rather than from a commit. This site recorded the change at 30 Oct 2026 18:07 UTC.
Upstream edited
Recorded here
Lines+78added
Lines−78removed
From line
86
where the diff opens
First seen
14 Aug 2026
this site's first read of the page
Recorded edits13to this page, all time
The whole hunk
from line 86, old and new numbered
/
from line 86
8686
8787The checks map directly onto standard JWT libraries. The examples below implement the full sequence in Node.js with [`jose`](https://www.npmjs.com/package/jose), which handles JWKS fetching, caching, and `kid` selection, and in Python with [`PyJWT`](https://pyjwt.readthedocs.io/) and its built-in JWKS client.
8888
89**Node.js (jose)**
89> [tab] Node.js (jose)
90>
91> ```typescript theme={null}
92> import { createRemoteJWKSet, jwtVerify } from "jose";
93>
94> const JWKS = createRemoteJWKSet(
95> new URL("https://api.anthropic.com/v1/code/.well-known/jwks.json")
96> );
97>
98> const PREFIX = "sk-ant-cc-";
99> const EXPECTED_POOL_ID = "ccpool_...";
100>
101> export async function verifySessionToken(raw: string) {
102> if (!raw.startsWith(PREFIX)) {
103> throw new Error("not a self-hosted runner session token");
104> }
105> const jwt = raw.slice(PREFIX.length);
106>
107> const { payload } = await jwtVerify(jwt, JWKS, {
108> issuer: "ccr",
109> audience: EXPECTED_POOL_ID,
110> algorithms: ["ES256"],
111> });
112>
113> if (payload["ccr:role"] !== "session_worker") {
114> throw new Error("token is not a session_worker token");
115> }
116>
117> const act = payload.act as { email?: string; sub?: string };
118> return {
119> sessionId: payload["ccr:session_id"] as string,
120> poolId: payload["ccr:pool_id"] as string,
121> orgId: payload["ccr:org_id"] as string,
122> creatorEmail: act?.email,
123> creatorSub: act?.sub,
124> };
125> }
126> ```
90127
91```typescript theme={null}
92import { createRemoteJWKSet, jwtVerify } from "jose";
93
94const JWKS = createRemoteJWKSet(
95 new URL("https://api.anthropic.com/v1/code/.well-known/jwks.json")
96);
97
98const PREFIX = "sk-ant-cc-";
99const EXPECTED_POOL_ID = "ccpool_...";
100
101export async function verifySessionToken(raw: string) {
102 if (!raw.startsWith(PREFIX)) {
103 throw new Error("not a self-hosted runner session token");
104 }
105 const jwt = raw.slice(PREFIX.length);
106
107 const { payload } = await jwtVerify(jwt, JWKS, {
108 issuer: "ccr",
109 audience: EXPECTED_POOL_ID,
110 algorithms: ["ES256"],
111 });
112
113 if (payload["ccr:role"] !== "session_worker") {
114 throw new Error("token is not a session_worker token");
115 }
116
117 const act = payload.act as { email?: string; sub?: string };
118 return {
119 sessionId: payload["ccr:session_id"] as string,
120 poolId: payload["ccr:pool_id"] as string,
121 orgId: payload["ccr:org_id"] as string,
122 creatorEmail: act?.email,
123 creatorSub: act?.sub,
124 };
125}
126```
127
128**Python (PyJWT)**
129
130```python theme={null}
131import jwt
132from jwt import PyJWKClient
133
134JWKS_URL = "https://api.anthropic.com/v1/code/.well-known/jwks.json"
135PREFIX = "sk-ant-cc-"
136EXPECTED_POOL_ID = "ccpool_..."
137
138jwks = PyJWKClient(JWKS_URL)
139
140
141def verify_session_token(raw: str) -> dict:
142 if not raw.startswith(PREFIX):
143 raise ValueError("not a self-hosted runner session token")
144 token = raw.removeprefix(PREFIX)
145
146 signing_key = jwks.get_signing_key_from_jwt(token)
147 payload = jwt.decode(
148 token,
149 signing_key.key,
150 algorithms=["ES256"],
151 issuer="ccr",
152 audience=EXPECTED_POOL_ID,
153 )
154
155 if payload.get("ccr:role") != "session_worker":
156 raise ValueError("token is not a session_worker token")
157
158 act = payload.get("act") or {}
159 return {
160 "session_id": payload["ccr:session_id"],
161 "pool_id": payload["ccr:pool_id"],
162 "org_id": payload["ccr:org_id"],
163 "creator_email": act.get("email"),
164 "creator_sub": act.get("sub"),
165 }
166```
128> [tab] Python (PyJWT)
129>
130> ```python theme={null}
131> import jwt
132> from jwt import PyJWKClient
133>
134> JWKS_URL = "https://api.anthropic.com/v1/code/.well-known/jwks.json"
135> PREFIX = "sk-ant-cc-"
136> EXPECTED_POOL_ID = "ccpool_..."
137>
138> jwks = PyJWKClient(JWKS_URL)
139>
140>
141> def verify_session_token(raw: str) -> dict:
142> if not raw.startswith(PREFIX):
143> raise ValueError("not a self-hosted runner session token")
144> token = raw.removeprefix(PREFIX)
145>
146> signing_key = jwks.get_signing_key_from_jwt(token)
147> payload = jwt.decode(
148> token,
149> signing_key.key,
150> algorithms=["ES256"],
151> issuer="ccr",
152> audience=EXPECTED_POOL_ID,
153> )
154>
155> if payload.get("ccr:role") != "session_worker":
156> raise ValueError("token is not a session_worker token")
157>
158> act = payload.get("act") or {}
159> return {
160> "session_id": payload["ccr:session_id"],
161> "pool_id": payload["ccr:pool_id"],
162> "org_id": payload["ccr:org_id"],
163> "creator_email": act.get("email"),
164> "creator_sub": act.get("sub"),
165> }
166> ```
167167
168168### Verify the token inside the session
169169
No line in this hunk matches that.