go-ed25519-signed-request-e2e
Built the full <project> SECURITY-001 implementation from scratch in ~ (Go 1.26, stdlib only — crypto/ed25519 needs no external deps):
Signing scheme — every protected request carries X-Agent-ID, X-Agent-Ts (unix seconds), X-Agent-Sig (hex ed25519 signature over the exact payload METHOD\nPATH\nTS).
Endpoints (memory backend): unsigned POST /v1/agents bootstrap + four signed routes — POST /{id}/inbox, POST /{id}/ack, POST /{id}/delete, GET /{id}/retrieve.
Enforcement pipeline (Server.authorize, registry/server.go) — the wrong-key vs. cross-agent distinction is explicit:
1. All three headers present → else 401
2. X-Agent-Ts is an integer within ±300 s of now → else 401
3. X-Agent-ID names a registered agent → else 401 (no enumeration)
4. Hex sig verifies over METHOD\nPATH\nTS with that agent's registered pubkey → else 401
5. Replay guard: X-Agent-Ts strictly greater than the agent's last accepted TS → else 401
6. Authorization: path {id} must equal X-Agent-ID → else 403 (authenticated as A, acting on B's mailbox)
Key code — the gate and payload math:
// registry/server.go — signed() wraps each protected handler
status := s.authorize(r, r.PathValue("id"))
switch status {
case http.StatusUnauthorized:
writeErr(w, 401, "unsigned or invalid signature"); return
case http.StatusForbidden:
writeErr(w, 403, "signature valid for a different agent"); return
}
// authorize: step 4+6 (steps 1-3/5 elided)
payload := signature.Payload(r.Method, r.URL.Path, ts) // METHOD\nPATH\nTS
if err := signature.VerifyHex(agent.PubKey, payload, sig); err != nil {
return http.StatusUnauthorized // wrong key / tampered → 401
}
if !s.store.BumpTS(id, tsN) { return http.StatusUnauthorized } // replay
if pathAgentID != id { return http.StatusForbidden } // cross-agent → 403
// signature/signature.go — sig math
func Payload(method, path, ts string) []byte {
return []byte(strings.Join([]string{method, path, ts}, "\n"))
}
func SignHex(priv ed25519.PrivateKey, payload []byte) string {
return hex.EncodeToString(ed25519.Sign(priv, payload))
}
func VerifyHex(pub ed25519.PublicKey, payload []byte, sigHex string) error {
sig, err := hex.DecodeString(sigHex)
if err != nil { return fmt.Errorf("signature: malformed hex: %w", err) }
if len(sig) != ed25519.SignatureSize { return fmt.Errorf("signature: bad length %d", len(sig)) }
if !ed25519.Verify(pub, payload, sig) { return fmt.Errorf("signature: verification failed") }
return nil
}
Verified three ways, all passing: **1. Unit tests — sig math** (`signature/signature_test.go`, 7 tests): exact payload format `POST\n/v1/agents/agent-1/inbox\n1719999999` (3 fields, `\n` separators); sign/verify roundtrip (128-char hex); wrong pubkey fails; single-byte payload flip fails; single-hex-char sig flip fails; malformed hex and wrong-length sig rejected before crypto; pubkey parse edge cases. **2. Live battery — 13 probes** (`registry/server_test.go`, real `httptest.Server` over the memory backend, full HTTP stack, no mocks, all PASS): | # | Probe | Expect | |---|-------|--------| | 01–02 | register agents A, B (unsigned bootstrap) | 201 | | 03 | signed inbox post | 202 | | 04 | signed retrieve returns the message | 200 | | 05 | signed ack marks `acked_at` | 202 | | 06 | signed delete marks `deleted_at` | 202 | | 07 | **unsigned retrieve** | **401** | | 08 | **wrong-key cross-agent retrieve** (A's valid sig on B's path) | **403**, and B's mailbox verified empty | | 09 | same-agent wrong key (header A, B's key) | 401 | | 10 | tampered signature byte | 401 | | 11 | TS tamper (header fresh, sig over different TS — exercises payload binding, not window) | 401 | | 12 | stale TS (−3600 s, outside ±300 s window) | 401 | | 13 | method binding (sig over `GET\n…`, sent as `POST`) | 401 | **3. Binary E2E smoke** (`smoke/main.go` against the compiled `cmd/registry` on a real port): register → 201, signed inbox → 202, unsigned retrieve → 401. **Edge cases covered:** replay of a same-`ts` request rejected by the per-agent monotonic guard (the test client uses a monotonic signing clock, exactly what a correct agent does); unknown agent ID → 401 without distinguishing valid/invalid (no enumeration); body is deliberately outside the signed payload per spec — message IDs are server-issued and bearer-bound to the mailbox, so tampered bodies can't cross agent boundaries. `go vet`, `gofmt`, and `go test -race -count=5` all clean.
{"model": "deepseek-v4-flash", "problem_class": "go-ed25519-signed-request-e2e", "result": "passed", "tests": 20}Built the full <project> SECURITY-001 implementation from scratch in ~ (Go 1.26, stdlib only — crypto/ed25519 needs no external deps):
Signing scheme — every protected request carries X-Agent-ID, X-Agent-Ts (unix seconds), X-Agent-Sig (hex ed25519 signature over the exact payload METHOD\nPATH\nTS).
Endpoints (memory backend): unsigned POST /v1/agents bootstrap + four signed routes — POST /{id}/inbox, POST /{id}/ack, POST /{id}/delete, GET /{id}/retrieve.
Enforcement pipeline (Server.authorize, registry/server.go) — the wrong-key vs. cross-agent distinction is explicit:
1. All three headers present → else 401
2. X-Agent-Ts is an integer within ±300 s of now → else 401
3. X-Agent-ID names a registered agent → else 401 (no enumeration)
4. Hex sig verifies over METHOD\nPATH\nTS with that agent's registered pubkey → else 401
5. Replay guard: X-Agent-Ts strictly greater than the agent's last accepted TS → else 401
6. Authorization: path {id} must equal X-Agent-ID → else 403 (authenticated as A, acting on B's mailbox)
Key code — the gate and payload math:
// registry/server.go — signed() wraps each protected handler
status := s.authorize(r, r.PathValue("id"))
switch status {
case http.StatusUnauthorized:
writeErr(w, 401, "unsigned or invalid signature"); return
case http.StatusForbidden:
writeErr(w, 403, "signature valid for a different agent"); return
}
// authorize: step 4+6 (steps 1-3/5 elided)
payload := signature.Payload(r.Method, r.URL.Path, ts) // METHOD\nPATH\nTS
if err := signature.VerifyHex(agent.PubKey, payload, sig); err != nil {
return http.StatusUnauthorized // wrong key / tampered → 401
}
if !s.store.BumpTS(id, tsN) { return http.StatusUnauthorized } // replay
if pathAgentID != id { return http.StatusForbidden } // cross-agent → 403
// signature/signature.go — sig math
func Payload(method, path, ts string) []byte {
return []byte(strings.Join([]string{method, path, ts}, "\n"))
}
func SignHex(priv ed25519.PrivateKey, payload []byte) string {
return hex.EncodeToString(ed25519.Sign(priv, payload))
}
func VerifyHex(pub ed25519.PublicKey, payload []byte, sigHex string) error {
sig, err := hex.DecodeString(sigHex)
if err != nil { return fmt.Errorf("signature: malformed hex: %w", err) }
if len(sig) != ed25519.SignatureSize { return fmt.Errorf("signature: bad length %d", len(sig)) }
if !ed25519.Verify(pub, payload, sig) { return fmt.Errorf("signature: verification failed") }
return nil
}
Verified three ways, all passing: **1. Unit tests — sig math** (`signature/signature_test.go`, 7 tests): exact payload format `POST\n/v1/agents/agent-1/inbox\n1719999999` (3 fields, `\n` separators); sign/verify roundtrip (128-char hex); wrong pubkey fails; single-byte payload flip fails; single-hex-char sig flip fails; malformed hex and wrong-length sig rejected before crypto; pubkey parse edge cases. **2. Live battery — 13 probes** (`registry/server_test.go`, real `httptest.Server` over the memory backend, full HTTP stack, no mocks, all PASS): | # | Probe | Expect | |---|-------|--------| | 01–02 | register agents A, B (unsigned bootstrap) | 201 | | 03 | signed inbox post | 202 | | 04 | signed retrieve returns the message | 200 | | 05 | signed ack marks `acked_at` | 202 | | 06 | signed delete marks `deleted_at` | 202 | | 07 | **unsigned retrieve** | **401** | | 08 | **wrong-key cross-agent retrieve** (A's valid sig on B's path) | **403**, and B's mailbox verified empty | | 09 | same-agent wrong key (header A, B's key) | 401 | | 10 | tampered signature byte | 401 | | 11 | TS tamper (header fresh, sig over different TS — exercises payload binding, not window) | 401 | | 12 | stale TS (−3600 s, outside ±300 s window) | 401 | | 13 | method binding (sig over `GET\n…`, sent as `POST`) | 401 | **3. Binary E2E smoke** (`smoke/main.go` against the compiled `cmd/registry` on a real port): register → 201, signed inbox → 202, unsigned retrieve → 401. **Edge cases covered:** replay of a same-`ts` request rejected by the per-agent monotonic guard (the test client uses a monotonic signing clock, exactly what a correct agent does); unknown agent ID → 401 without distinguishing valid/invalid (no enumeration); body is deliberately outside the signed payload per spec — message IDs are server-issued and bearer-bound to the mailbox, so tampered bodies can't cross agent boundaries. `go vet`, `gofmt`, and `go test -race -count=5` all clean.
{"model": "deepseek-v4-flash", "problem_class": "go-ed25519-signed-request-e2e", "result": "passed", "tests": 20}