Skip to content

Commit 4fc066b

Browse files
kjgbotkjgbotagent-relayclaude
authored
migration(rs256): phase 120 — Add RS256 signing path (additive, HS256 stays default) (#21)
* migration(rs256): phase 120 — Add RS256 signing path (additive, HS256 stays default) Generated by workflows/120-*.ts via scripts/run-rs256-migration.sh. Spec: specs/api-keys-and-rs256-migration.md (phase 120). Co-Authored-By: agent-relay <agent@agent-relay.com> * phase 120: fix kid derivation + enforce 2048-bit RSA minimum - kid now computed as RFC 7638 JWK thumbprint: SHA-256 of the canonical JWK (lex-sorted required members, no whitespace), base64url-encoded. Removes the YYYY-MM component that caused kid to change at every month rollover, which would have silently broken verification under phase 122's RS256 cutover. - importPrivateKey rejects RSA keys < 2048 bits; logs a warning at <3072 bits per specs/token-format.md. - Single rfc7638Thumbprint() helper shared by sign path and JWKS route so both always produce matching kid values. - Regression tests: deterministic kid, time-independent kid (directly covers the month-rollover bug), sign-kid == jwks-kid, RFC 7638 known-answer, 1024-bit key rejection. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: kjgbot <kjgbot@agentrelay.dev> Co-authored-by: agent-relay <agent@agent-relay.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 042ceb6 commit 4fc066b

12 files changed

Lines changed: 1291 additions & 13 deletions

File tree

.rs256-migration-state.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,9 @@
66
"119": {
77
"status": "passed",
88
"ts": "2026-04-22T19:34:50Z"
9+
},
10+
"120": {
11+
"status": "passed",
12+
"ts": "2026-04-22T19:50:05Z"
913
}
1014
}
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
{
2+
"id": "traj_9l17u8ax5m7h",
3+
"version": 1,
4+
"task": {
5+
"title": "120-rs256-signing-phase2a-workflow",
6+
"description": "Add RS256 signing path + JWKS RSA publication; HS256 stays default",
7+
"source": {
8+
"system": "workflow-runner",
9+
"id": "411f89adf66e635a1dd8463d"
10+
}
11+
},
12+
"status": "completed",
13+
"startedAt": "2026-04-22T19:35:03.581Z",
14+
"completedAt": "2026-04-22T19:48:52.150Z",
15+
"agents": [
16+
{
17+
"name": "orchestrator",
18+
"role": "workflow-runner",
19+
"joinedAt": "2026-04-22T19:35:03.582Z"
20+
},
21+
{
22+
"name": "implementer",
23+
"role": "specialist",
24+
"joinedAt": "2026-04-22T19:35:07.057Z"
25+
}
26+
],
27+
"chapters": [
28+
{
29+
"id": "chap_j7nrv4vhkl70",
30+
"title": "Planning",
31+
"agentName": "orchestrator",
32+
"startedAt": "2026-04-22T19:35:03.582Z",
33+
"endedAt": "2026-04-22T19:35:06.929Z",
34+
"events": [
35+
{
36+
"ts": 1776886503582,
37+
"type": "note",
38+
"content": "Purpose: Add RS256 signing path + JWKS RSA publication; HS256 stays default"
39+
},
40+
{
41+
"ts": 1776886503582,
42+
"type": "note",
43+
"content": "Approach: 18-step dag workflow — Parsed 18 steps, 2 parallel tracks, 16 dependent steps, DAG validated, no cycles"
44+
}
45+
]
46+
},
47+
{
48+
"id": "chap_8jv8j46qcjt2",
49+
"title": "Execution: read-spec, read-existing-sign",
50+
"agentName": "orchestrator",
51+
"startedAt": "2026-04-22T19:35:06.929Z",
52+
"endedAt": "2026-04-22T19:35:07.054Z",
53+
"events": []
54+
},
55+
{
56+
"id": "chap_pb6c12rf2p1a",
57+
"title": "Convergence: read-spec + read-existing-sign",
58+
"agentName": "orchestrator",
59+
"startedAt": "2026-04-22T19:35:07.054Z",
60+
"endedAt": "2026-04-22T19:35:07.058Z",
61+
"events": [
62+
{
63+
"ts": 1776886507056,
64+
"type": "reflection",
65+
"content": "read-spec + read-existing-sign resolved. 2/2 steps completed. All steps completed on first attempt. Unblocking: write-tests.",
66+
"raw": {
67+
"confidence": 0.75,
68+
"focalPoints": [
69+
"read-spec: completed",
70+
"read-existing-sign: completed"
71+
]
72+
},
73+
"significance": "high"
74+
}
75+
]
76+
},
77+
{
78+
"id": "chap_0vw7awaigvyj",
79+
"title": "Execution: write-tests",
80+
"agentName": "implementer",
81+
"startedAt": "2026-04-22T19:35:07.058Z",
82+
"endedAt": "2026-04-22T19:39:46.857Z",
83+
"events": [
84+
{
85+
"ts": 1776886507058,
86+
"type": "note",
87+
"content": "\"write-tests\": Write failing tests at /Users/khaliqgant/Projects/AgentWorkforce/relayauth/packages/server/src/__tests__/sign-rs256.test",
88+
"raw": {
89+
"agent": "implementer"
90+
}
91+
},
92+
{
93+
"ts": 1776886786790,
94+
"type": "completion-evidence",
95+
"content": "\"write-tests\" verification-based completion — Verification passed (3 signal(s), 2 file change(s), exit=0; signals=0, OpenAI Codex v0.117.0 (research preview), Verification passed; files=created:packages/server/src/__tests__/jwks-rsa.test.ts, created:packages/server/src/__tests__/sign-rs256.test.ts; exit=0)",
96+
"raw": {
97+
"stepName": "write-tests",
98+
"completionMode": "verification",
99+
"reason": "Verification passed",
100+
"evidence": {
101+
"summary": "3 signal(s), 2 file change(s), exit=0",
102+
"signals": [
103+
"0",
104+
"OpenAI Codex v0.117.0 (research preview)",
105+
"Verification passed"
106+
],
107+
"files": [
108+
"created:packages/server/src/__tests__/jwks-rsa.test.ts",
109+
"created:packages/server/src/__tests__/sign-rs256.test.ts"
110+
],
111+
"exitCode": 0
112+
}
113+
},
114+
"significance": "medium"
115+
},
116+
{
117+
"ts": 1776886786790,
118+
"type": "finding",
119+
"content": "\"write-tests\" completed → - `packages/server/src/__tests__/jwks-rsa.test.ts`",
120+
"significance": "medium"
121+
}
122+
]
123+
},
124+
{
125+
"id": "chap_7htdqnlsz4c6",
126+
"title": "Execution: implement",
127+
"agentName": "implementer",
128+
"startedAt": "2026-04-22T19:39:46.857Z",
129+
"endedAt": "2026-04-22T19:45:17.056Z",
130+
"events": [
131+
{
132+
"ts": 1776886786857,
133+
"type": "note",
134+
"content": "\"implement\": Implement the signing + JWKS changes",
135+
"raw": {
136+
"agent": "implementer"
137+
}
138+
},
139+
{
140+
"ts": 1776887116986,
141+
"type": "completion-evidence",
142+
"content": "\"implement\" verification-based completion — Verification passed (3 signal(s), 6 file change(s), exit=0; signals=0, OpenAI Codex v0.117.0 (research preview), Verification passed; files=modified:packages/server/src/env.ts, created:packages/server/src/lib/jwk.ts, created:packages/server/src/lib/sign-rs256.ts, created:packages/server/src/lib/sign.ts, modified:packages/server/src/routes/jwks.ts, modified:packages/server/src/server.ts; exit=0)",
143+
"raw": {
144+
"stepName": "implement",
145+
"completionMode": "verification",
146+
"reason": "Verification passed",
147+
"evidence": {
148+
"summary": "3 signal(s), 6 file change(s), exit=0",
149+
"signals": [
150+
"0",
151+
"OpenAI Codex v0.117.0 (research preview)",
152+
"Verification passed"
153+
],
154+
"files": [
155+
"modified:packages/server/src/env.ts",
156+
"created:packages/server/src/lib/jwk.ts",
157+
"created:packages/server/src/lib/sign-rs256.ts",
158+
"created:packages/server/src/lib/sign.ts",
159+
"modified:packages/server/src/routes/jwks.ts",
160+
"modified:packages/server/src/server.ts"
161+
],
162+
"exitCode": 0
163+
}
164+
},
165+
"significance": "medium"
166+
},
167+
{
168+
"ts": 1776887116986,
169+
"type": "finding",
170+
"content": "\"implement\" completed → - updated server env/config bindings",
171+
"significance": "medium"
172+
}
173+
]
174+
},
175+
{
176+
"id": "chap_ctm54krxnc83",
177+
"title": "Execution: self-review",
178+
"agentName": "implementer",
179+
"startedAt": "2026-04-22T19:45:17.056Z",
180+
"endedAt": "2026-04-22T19:48:52.150Z",
181+
"events": [
182+
{
183+
"ts": 1776887117056,
184+
"type": "note",
185+
"content": "\"self-review\": Self-review every file you changed",
186+
"raw": {
187+
"agent": "implementer"
188+
}
189+
},
190+
{
191+
"ts": 1776887244654,
192+
"type": "decision",
193+
"content": "Resolve RELAYAUTH_SIGNING_KEY_PEM only inside the RS256 sign path instead of copying it into startup config: Resolve RELAYAUTH_SIGNING_KEY_PEM only inside the RS256 sign path instead of copying it into startup config",
194+
"raw": {
195+
"question": "Resolve RELAYAUTH_SIGNING_KEY_PEM only inside the RS256 sign path instead of copying it into startup config",
196+
"chosen": "Resolve RELAYAUTH_SIGNING_KEY_PEM only inside the RS256 sign path instead of copying it into startup config",
197+
"alternatives": [],
198+
"reasoning": "Keeps the private PEM out of long-lived server config objects while preserving explicit bindings/tests and satisfying the requirement that the secret is read at sign time only."
199+
},
200+
"significance": "high"
201+
}
202+
]
203+
}
204+
],
205+
"retrospective": {
206+
"summary": "Hardened RS256 signer secret handling, kept JWKS dual-publication, and verified dispatcher/JWKS behavior with passing targeted tests and typecheck.",
207+
"approach": "Standard approach",
208+
"confidence": 0.9
209+
},
210+
"commits": [],
211+
"filesChanged": [],
212+
"projectId": "/Users/khaliqgant/Projects/AgentWorkforce/relayauth",
213+
"tags": []
214+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Trajectory: 120-rs256-signing-phase2a-workflow
2+
3+
> **Status:** ✅ Completed
4+
> **Task:** 411f89adf66e635a1dd8463d
5+
> **Confidence:** 90%
6+
> **Started:** April 22, 2026 at 09:35 PM
7+
> **Completed:** April 22, 2026 at 09:48 PM
8+
9+
---
10+
11+
## Summary
12+
13+
Hardened RS256 signer secret handling, kept JWKS dual-publication, and verified dispatcher/JWKS behavior with passing targeted tests and typecheck.
14+
15+
**Approach:** Standard approach
16+
17+
---
18+
19+
## Key Decisions
20+
21+
### Resolve RELAYAUTH_SIGNING_KEY_PEM only inside the RS256 sign path instead of copying it into startup config
22+
- **Chose:** Resolve RELAYAUTH_SIGNING_KEY_PEM only inside the RS256 sign path instead of copying it into startup config
23+
- **Reasoning:** Keeps the private PEM out of long-lived server config objects while preserving explicit bindings/tests and satisfying the requirement that the secret is read at sign time only.
24+
25+
---
26+
27+
## Chapters
28+
29+
### 1. Planning
30+
*Agent: orchestrator*
31+
32+
### 2. Execution: read-spec, read-existing-sign
33+
*Agent: orchestrator*
34+
35+
### 3. Convergence: read-spec + read-existing-sign
36+
*Agent: orchestrator*
37+
38+
- read-spec + read-existing-sign resolved. 2/2 steps completed. All steps completed on first attempt. Unblocking: write-tests.
39+
40+
### 4. Execution: write-tests
41+
*Agent: implementer*
42+
43+
### 5. Execution: implement
44+
*Agent: implementer*
45+
46+
### 6. Execution: self-review
47+
*Agent: implementer*
48+
49+
- Resolve RELAYAUTH_SIGNING_KEY_PEM only inside the RS256 sign path instead of copying it into startup config: Resolve RELAYAUTH_SIGNING_KEY_PEM only inside the RS256 sign path instead of copying it into startup config

.trajectories/index.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"version": 1,
3-
"lastUpdated": "2026-04-22T19:34:48.598Z",
3+
"lastUpdated": "2026-04-22T19:50:04.007Z",
44
"trajectories": {
55
"traj_a11zh8l3gbp5": {
66
"title": "Work on the relay: combine relayauth + relayfile for agent sandboxing",
@@ -923,6 +923,12 @@
923923
"status": "active",
924924
"startedAt": "2026-04-22T19:11:05.487Z",
925925
"path": "/Users/khaliqgant/Projects/AgentWorkforce/relayauth/.trajectories/active/traj_dmmcvsc1kghh.json"
926+
},
927+
"traj_9l17u8ax5m7h": {
928+
"title": "120-rs256-signing-phase2a-workflow",
929+
"status": "active",
930+
"startedAt": "2026-04-22T19:35:03.581Z",
931+
"path": "/Users/khaliqgant/Projects/AgentWorkforce/relayauth/.trajectories/active/traj_9l17u8ax5m7h.json"
926932
}
927933
}
928934
}

0 commit comments

Comments
 (0)