Follow Discord
Sweep 06 Nov 2026 · 01:32Z Build v2.1.319 537 read Stable v2.1.311 Latest v2.1.319 Next v2.1.319 Feeds RSS JSON llms.txt llms-full.txt Unofficial
One change · claude-code

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
/
lines
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 
Feedback