@7h3/protocol 0.5.0 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -1317
- package/agentAdapter.d.ts +26 -0
- package/auditLog.d.ts +54 -0
- package/capability.d.ts +67 -0
- package/cborCodec.d.ts +34 -0
- package/conformanceVectors.d.ts +20 -0
- package/encryption.d.ts +85 -0
- package/envelopeCbor.d.ts +33 -0
- package/frameworkAdapters.d.ts +72 -0
- package/gateway.d.ts +53 -0
- package/grpcBinding.d.ts +29 -0
- package/httpBinding.d.ts +50 -0
- package/index.d.ts +39 -0
- package/index.js +3670 -0
- package/keyInfra.d.ts +64 -0
- package/keyRegistry.d.ts +9 -0
- package/keyRotation.d.ts +20 -0
- package/mcpGateway.d.ts +37 -0
- package/mcpTransports.d.ts +62 -0
- package/mcpWrapper.d.ts +83 -0
- package/otel.d.ts +65 -0
- package/package.json +13 -121
- package/policyEnforcer.d.ts +50 -0
- package/policyTelemetryFeedback.d.ts +11 -0
- package/{src/protocol.d.ts → protocol.d.ts} +6 -2
- package/protocolAgent.d.ts +58 -0
- package/protocolBinary.d.ts +8 -0
- package/protocolCapabilities.d.ts +24 -0
- package/protocolReplay.d.ts +35 -0
- package/protocolTransport.d.ts +73 -0
- package/queueBinding.d.ts +43 -0
- package/rateLimiter.d.ts +18 -0
- package/{src/redisClient.ts → redisClient.d.ts} +25 -75
- package/replayStores.d.ts +98 -0
- package/revocation.d.ts +71 -0
- package/routePolicy.d.ts +30 -0
- package/runtimePolicy.d.ts +24 -0
- package/runtimePolicyManager.d.ts +15 -0
- package/runtimePolicyPresets.d.ts +11 -0
- package/signedResponse.d.ts +21 -0
- package/stream.d.ts +59 -0
- package/telemetry.d.ts +79 -0
- package/webhookBinding.d.ts +30 -0
- package/wsBinding.d.ts +52 -0
- package/.dockerignore +0 -19
- package/.github/dependabot.yml +0 -32
- package/.github/workflows/ci.yml +0 -31
- package/.github/workflows/publish.yml +0 -59
- package/.github/workflows/scorecard.yml +0 -37
- package/7h3.example.yaml +0 -125
- package/CHANGELOG.md +0 -152
- package/CONTRIBUTING.md +0 -82
- package/Dockerfile +0 -73
- package/GOVERNANCE.md +0 -62
- package/SECURITY.md +0 -70
- package/bench-results/replay-cache-full-1777891033256.json +0 -10
- package/bench-results/replay-cache-full-1777896317488.json +0 -10
- package/bench-results/replay-cache-full-1777900993184.json +0 -10
- package/bench-results/replay-cache-full-1777901019285.json +0 -10
- package/bench-results/replay-cache-quick-1777870170126.json +0 -10
- package/bench-results/signature-profiles-quick-1775875160079.json +0 -85
- package/bench-results/signature-profiles-quick-1775983539716.json +0 -85
- package/bench-results/signature-profiles-quick-1776237913190.json +0 -85
- package/bench-results/wire-codecs-full-1777891019803.json +0 -93
- package/bench-results/wire-codecs-full-1777896260964.json +0 -93
- package/bench-results/wire-codecs-full-1777901004247.json +0 -93
- package/bench-results/wire-codecs-quick-1775972879056.json +0 -93
- package/bench-results/wire-codecs-quick-1775983541111.json +0 -93
- package/bench-results/wire-codecs-quick-1776237914299.json +0 -93
- package/bench-results/wire-codecs-quick-1777841285236.json +0 -93
- package/bench-results/wire-codecs-quick-1777841321772.json +0 -93
- package/bench-results/wire-codecs-quick-1777841330408.json +0 -93
- package/bench-results/wire-codecs-quick-1777852886082.json +0 -93
- package/bench-results/wire-codecs-quick-1777852988773.json +0 -93
- package/bench-results/wire-codecs-quick-1777870188095.json +0 -93
- package/bench-results/wire-codecs-quick-1777870263918.json +0 -93
- package/bench-results/wire-codecs-quick-1777870455034.json +0 -93
- package/bench-results/wire-codecs-quick-1778816163081.json +0 -93
- package/bench-results/wire-codecs-quick-1778843936130.json +0 -93
- package/bin/7h3.ts +0 -406
- package/conformance/7h3_v0_1.json +0 -77
- package/conformance/7h3_v0_1_binary.json +0 -20
- package/conformance/aip_v0_1_binary.json +0 -20
- package/docker-compose.yaml +0 -77
- package/docs/ADOPTION_PLAN.md +0 -120
- package/docs/AGENTS.md +0 -77
- package/docs/AIP_RFC_v0.1.md +0 -97
- package/docs/AI_DECISION_CARD.md +0 -122
- package/docs/AI_RUNTIME_POLICY.json +0 -126
- package/docs/AI_RUNTIME_POLICY.yaml +0 -110
- package/docs/BACKPRESSURE_TUNING.md +0 -65
- package/docs/BENCHMARK_CLAIM_MATRIX.md +0 -42
- package/docs/BENCHMARK_REPORT_TEMPLATE.md +0 -169
- package/docs/BINARY_CODEC_BENCH.md +0 -23
- package/docs/CLEAN_CLONE_RUNBOOK.md +0 -36
- package/docs/CLOCK_SKEW_POLICY.md +0 -30
- package/docs/DISTRIBUTED_REPLAY.md +0 -142
- package/docs/FUZZ_CAMPAIGN.md +0 -121
- package/docs/GATEWAY.md +0 -195
- package/docs/KEY_MANAGEMENT_POLICY.md +0 -53
- package/docs/KEY_REVOCATION.md +0 -69
- package/docs/MCP_WRAPPER.md +0 -159
- package/docs/MIGRATION_GUIDE.md +0 -40
- package/docs/OPERATORS.md +0 -184
- package/docs/PERF_REGRESSION_POLICY.md +0 -34
- package/docs/PROJECT_EXAMINATION_2026-05-31.md +0 -219
- package/docs/RELEASE_BENCHMARK_REPORT_2026-05-15.md +0 -135
- package/docs/RELEASE_GATE.md +0 -25
- package/docs/RELEASE_NOTES_v0.1.0.md +0 -54
- package/docs/SECURITY_REVIEW_2026-06-05.md +0 -165
- package/docs/TELEMETRY.md +0 -41
- package/docs/THREAT_MODEL.md +0 -89
- package/docs/VERSIONING_POLICY.md +0 -30
- package/docs/assets/banner-github.png +0 -0
- package/docs/assets/banner.png +0 -0
- package/docs/assets/banner.svg +0 -123
- package/eslint.config.js +0 -15
- package/fuzz/ts/harness-decode.ts +0 -136
- package/fuzz/ts/harness-verify.ts +0 -121
- package/fuzz/ts/run.ts +0 -35
- package/mcp-server/README.md +0 -38
- package/mcp-server/package-lock.json +0 -1187
- package/mcp-server/package.json +0 -35
- package/mcp-server/src/index.ts +0 -236
- package/mcp-server/tsconfig.json +0 -14
- package/scripts/aip-framework-quickstart.ts +0 -110
- package/scripts/aip-mcp-gateway.ts +0 -38
- package/scripts/aip-mcp-wrap-demo.ts +0 -72
- package/scripts/aip-quickstart.ts +0 -60
- package/scripts/bench-diff.ts +0 -118
- package/scripts/bench-protocol-e2e.ts +0 -937
- package/scripts/bench-protocol-openloop.ts +0 -1397
- package/scripts/bench-replay-cache.ts +0 -76
- package/scripts/bench-signature-profiles.ts +0 -180
- package/scripts/bench-wire-codecs.ts +0 -161
- package/scripts/build-binary-conformance.ts +0 -36
- package/scripts/build-release-dashboard.ts +0 -175
- package/scripts/canary-rollout.ts +0 -38
- package/scripts/mcpGatewayCli.test.ts +0 -116
- package/scripts/prepare-aip-package.ts +0 -88
- package/scripts/regen-conformance-sigs.ts +0 -18
- package/scripts/release-gate.ts +0 -19
- package/scripts/validate-runtime-policy.ts +0 -18
- package/sdk/browser/index.test.ts +0 -162
- package/sdk/browser/index.ts +0 -257
- package/sdk/browser/package.json +0 -13
- package/sdk/go/cbor.go +0 -551
- package/sdk/go/cbor_test.go +0 -232
- package/sdk/go/encryption.go +0 -280
- package/sdk/go/encryption_test.go +0 -318
- package/sdk/go/go.mod +0 -7
- package/sdk/go/go.sum +0 -4
- package/sdk/go/http.go +0 -135
- package/sdk/go/protocol.go +0 -324
- package/sdk/go/protocol_test.go +0 -334
- package/sdk/go/replay.go +0 -121
- package/sdk/go/replay_test.go +0 -149
- package/sdk/go/webhook.go +0 -136
- package/sdk/pq/package-lock.json +0 -1358
- package/sdk/pq/package.json +0 -42
- package/sdk/pq/src/index.test.ts +0 -143
- package/sdk/pq/src/index.ts +0 -166
- package/sdk/pq/tsconfig.json +0 -14
- package/sdk/pq/vitest.config.ts +0 -7
- package/sdk/python/README.md +0 -18
- package/sdk/python/protocol_7h3/__init__.py +0 -46
- package/sdk/python/protocol_7h3/encryption.py +0 -252
- package/sdk/python/protocol_7h3/http.py +0 -212
- package/sdk/python/protocol_7h3/keys.py +0 -149
- package/sdk/python/protocol_7h3/pq.py +0 -244
- package/sdk/python/protocol_7h3/protocol.py +0 -525
- package/sdk/python/protocol_7h3/queue.py +0 -118
- package/sdk/python/protocol_7h3/replay.py +0 -98
- package/sdk/python/protocol_7h3/webhook.py +0 -116
- package/sdk/python/pyproject.toml +0 -40
- package/sdk/python/tests/test_conformance.py +0 -110
- package/sdk/python/tests/test_encryption.py +0 -206
- package/sdk/python/tests/test_http.py +0 -305
- package/sdk/python/tests/test_keys.py +0 -417
- package/sdk/python/tests/test_queue.py +0 -120
- package/sdk/python/tests/test_webhook.py +0 -345
- package/sdk/rust/Cargo.lock +0 -371
- package/sdk/rust/Cargo.toml +0 -25
- package/sdk/rust/README.md +0 -31
- package/sdk/rust/fuzz/Cargo.toml +0 -29
- package/sdk/rust/fuzz/fuzz_targets/fuzz_canonicalize.rs +0 -46
- package/sdk/rust/fuzz/fuzz_targets/fuzz_decode.rs +0 -11
- package/sdk/rust/src/bin/aip_mcp_gateway.rs +0 -59
- package/sdk/rust/src/http.rs +0 -145
- package/sdk/rust/src/keys.rs +0 -161
- package/sdk/rust/src/lib.rs +0 -688
- package/sdk/rust/src/queue.rs +0 -79
- package/sdk/rust/src/webhook.rs +0 -86
- package/sdk/rust/tests/conformance.rs +0 -148
- package/sdk/rust/tests/gateway.rs +0 -130
- package/sdk/rust/tests/http_webhook_queue.rs +0 -201
- package/sdk/rust/tests/keys.rs +0 -189
- package/sdk/threshold/index.d.ts +0 -68
- package/sdk/threshold/index.d.ts.map +0 -1
- package/sdk/threshold/index.js +0 -254
- package/sdk/threshold/package-lock.json +0 -1361
- package/sdk/threshold/package.json +0 -39
- package/sdk/threshold/src/index.d.ts +0 -68
- package/sdk/threshold/src/index.d.ts.map +0 -1
- package/sdk/threshold/src/index.js +0 -254
- package/sdk/threshold/src/index.test.ts +0 -238
- package/sdk/threshold/src/index.ts +0 -355
- package/sdk/threshold/tsconfig.json +0 -19
- package/sdk/threshold/vitest.config.ts +0 -12
- package/src/agentAdapter.test.ts +0 -48
- package/src/agentAdapter.ts +0 -56
- package/src/auditLog.test.ts +0 -145
- package/src/auditLog.ts +0 -147
- package/src/capability.test.ts +0 -504
- package/src/capability.ts +0 -380
- package/src/cborCodec.test.ts +0 -263
- package/src/cborCodec.ts +0 -339
- package/src/conformance.test.ts +0 -136
- package/src/conformanceVectors.ts +0 -99
- package/src/encryption.test.ts +0 -206
- package/src/encryption.ts +0 -245
- package/src/envelopeCbor.ts +0 -140
- package/src/frameworkAdapters.test.ts +0 -290
- package/src/frameworkAdapters.ts +0 -261
- package/src/gateway.test.ts +0 -343
- package/src/gateway.ts +0 -246
- package/src/grpcBinding.test.ts +0 -211
- package/src/grpcBinding.ts +0 -103
- package/src/httpBinding.test.ts +0 -376
- package/src/httpBinding.ts +0 -189
- package/src/index.ts +0 -39
- package/src/keyInfra.test.ts +0 -278
- package/src/keyInfra.ts +0 -228
- package/src/keyRegistry.ts +0 -59
- package/src/keyRotation.test.ts +0 -78
- package/src/keyRotation.ts +0 -72
- package/src/mcpGateway.test.ts +0 -129
- package/src/mcpGateway.ts +0 -250
- package/src/mcpTransports.test.ts +0 -92
- package/src/mcpTransports.ts +0 -169
- package/src/mcpWrapper.test.ts +0 -179
- package/src/mcpWrapper.ts +0 -206
- package/src/otel.ts +0 -136
- package/src/policyEnforcer.test.ts +0 -99
- package/src/policyEnforcer.ts +0 -169
- package/src/policyTelemetryFeedback.test.ts +0 -25
- package/src/policyTelemetryFeedback.ts +0 -38
- package/src/protocol.bench.ts +0 -37
- package/src/protocol.d.ts.map +0 -1
- package/src/protocol.js +0 -294
- package/src/protocol.test.ts +0 -155
- package/src/protocol.ts +0 -414
- package/src/protocolAgent.test.ts +0 -105
- package/src/protocolAgent.ts +0 -169
- package/src/protocolBinary.test.ts +0 -165
- package/src/protocolBinary.ts +0 -312
- package/src/protocolCapabilities.ts +0 -70
- package/src/protocolFuzz.advanced.test.ts +0 -235
- package/src/protocolFuzz.test.ts +0 -111
- package/src/protocolNegative.test.ts +0 -97
- package/src/protocolReplay.test.ts +0 -71
- package/src/protocolReplay.ts +0 -194
- package/src/protocolTransport.test.ts +0 -556
- package/src/protocolTransport.ts +0 -483
- package/src/queueBinding.test.ts +0 -130
- package/src/queueBinding.ts +0 -102
- package/src/rateLimiter.test.ts +0 -96
- package/src/rateLimiter.ts +0 -46
- package/src/redisIntegration.test.ts +0 -134
- package/src/replayStores.test.ts +0 -273
- package/src/replayStores.ts +0 -215
- package/src/revocation.test.ts +0 -98
- package/src/revocation.ts +0 -0
- package/src/routePolicy.test.ts +0 -87
- package/src/routePolicy.ts +0 -72
- package/src/runtimePolicy.test.ts +0 -49
- package/src/runtimePolicy.ts +0 -81
- package/src/runtimePolicyManager.test.ts +0 -29
- package/src/runtimePolicyManager.ts +0 -50
- package/src/runtimePolicyPresets.ts +0 -43
- package/src/signedResponse.test.ts +0 -111
- package/src/signedResponse.ts +0 -83
- package/src/stream.test.ts +0 -254
- package/src/stream.ts +0 -417
- package/src/telemetry.test.ts +0 -251
- package/src/telemetry.ts +0 -299
- package/src/webhookBinding.test.ts +0 -144
- package/src/webhookBinding.ts +0 -115
- package/src/wsBinding.test.ts +0 -221
- package/src/wsBinding.ts +0 -200
- package/tsconfig.json +0 -15
- package/tsconfig.lib.json +0 -23
- package/vite.lib.config.ts +0 -16
- package/vitest.config.ts +0 -11
package/src/keyInfra.ts
DELETED
|
@@ -1,228 +0,0 @@
|
|
|
1
|
-
import { generateEd25519KeypairBase64Url } from './protocol'
|
|
2
|
-
import type { KeyRegistry } from './keyRegistry'
|
|
3
|
-
|
|
4
|
-
// ── Well-Known Document ──────────────────────────────────────────────────────
|
|
5
|
-
|
|
6
|
-
export interface KeyEntry {
|
|
7
|
-
id: string
|
|
8
|
-
algorithm: 'Ed25519'
|
|
9
|
-
publicKey: string // SPKI base64url
|
|
10
|
-
created: number // Unix ms
|
|
11
|
-
expires?: number
|
|
12
|
-
revoked?: boolean
|
|
13
|
-
revokedAt?: number
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface WellKnownKeysDocument {
|
|
17
|
-
version: '7h3/0.1'
|
|
18
|
-
updated: number
|
|
19
|
-
keys: KeyEntry[]
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function serveWellKnownKeys(doc: WellKnownKeysDocument): string {
|
|
23
|
-
return JSON.stringify(doc)
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function parseWellKnownKeys(json: string): WellKnownKeysDocument {
|
|
27
|
-
const d = JSON.parse(json) as WellKnownKeysDocument
|
|
28
|
-
if (d.version !== '7h3/0.1') throw new Error(`unsupported version: ${d.version}`)
|
|
29
|
-
return d
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Fetch .well-known/7h3-keys from a base URL (uses global fetch)
|
|
33
|
-
export async function fetchWellKnownKeys(
|
|
34
|
-
baseUrl: string,
|
|
35
|
-
opts?: { timeout?: number }
|
|
36
|
-
): Promise<WellKnownKeysDocument> {
|
|
37
|
-
const url = baseUrl.replace(/\/$/, '') + '/.well-known/7h3-keys'
|
|
38
|
-
const controller = new AbortController()
|
|
39
|
-
const timer = opts?.timeout
|
|
40
|
-
? setTimeout(() => controller.abort(), opts.timeout)
|
|
41
|
-
: undefined
|
|
42
|
-
try {
|
|
43
|
-
const resp = await fetch(url, { signal: controller.signal })
|
|
44
|
-
if (!resp.ok) throw new Error(`HTTP ${resp.status} from ${url}`)
|
|
45
|
-
return parseWellKnownKeys(await resp.text())
|
|
46
|
-
} finally {
|
|
47
|
-
if (timer !== undefined) clearTimeout(timer)
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// ── HTTP-backed KeyRegistry (auto-discovers from .well-known) ────────────────
|
|
52
|
-
|
|
53
|
-
export function createHttpKeyRegistry(opts?: {
|
|
54
|
-
timeout?: number
|
|
55
|
-
cacheMs?: number
|
|
56
|
-
}): KeyRegistry {
|
|
57
|
-
const cache = new Map<string, { doc: WellKnownKeysDocument; expiresAt: number }>()
|
|
58
|
-
const cacheMs = opts?.cacheMs ?? 60_000
|
|
59
|
-
const timeout = opts?.timeout ?? 5_000
|
|
60
|
-
|
|
61
|
-
async function getDoc(baseUrl: string): Promise<WellKnownKeysDocument> {
|
|
62
|
-
const now = Date.now()
|
|
63
|
-
const hit = cache.get(baseUrl)
|
|
64
|
-
if (hit && hit.expiresAt > now) return hit.doc
|
|
65
|
-
const doc = await fetchWellKnownKeys(baseUrl, { timeout })
|
|
66
|
-
cache.set(baseUrl, { doc, expiresAt: now + cacheMs })
|
|
67
|
-
return doc
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return {
|
|
71
|
-
async getPublicKey(senderId: string): Promise<string | null> {
|
|
72
|
-
// senderId format: "service@example.com" or "agent.example.com"
|
|
73
|
-
// extract domain for well-known lookup
|
|
74
|
-
const domain = senderId.includes('@')
|
|
75
|
-
? senderId.split('@')[1]
|
|
76
|
-
: senderId.split('.').slice(-2).join('.')
|
|
77
|
-
if (!domain) return null
|
|
78
|
-
try {
|
|
79
|
-
const doc = await getDoc(`https://${domain}`)
|
|
80
|
-
const now = Date.now()
|
|
81
|
-
const entry = doc.keys.find(
|
|
82
|
-
k =>
|
|
83
|
-
(k.id === senderId || k.id === senderId.split('@')[0]) &&
|
|
84
|
-
!k.revoked &&
|
|
85
|
-
(!k.expires || k.expires > now)
|
|
86
|
-
)
|
|
87
|
-
return entry?.publicKey ?? null
|
|
88
|
-
} catch {
|
|
89
|
-
return null
|
|
90
|
-
}
|
|
91
|
-
},
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// ── Key Rotation Manager ─────────────────────────────────────────────────────
|
|
96
|
-
|
|
97
|
-
export interface ManagedKeyPair {
|
|
98
|
-
id: string
|
|
99
|
-
publicKey: string
|
|
100
|
-
privateKey: string
|
|
101
|
-
createdAt: number
|
|
102
|
-
expiresAt?: number
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
export interface KeyRotationOptions {
|
|
106
|
-
maxAgeMs: number
|
|
107
|
-
overlapMs?: number
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
export class KeyRotationManager {
|
|
111
|
-
private readonly maxAgeMs: number
|
|
112
|
-
private readonly overlapMs: number
|
|
113
|
-
private keys: ManagedKeyPair[] = []
|
|
114
|
-
|
|
115
|
-
constructor(opts: KeyRotationOptions) {
|
|
116
|
-
this.maxAgeMs = opts.maxAgeMs
|
|
117
|
-
this.overlapMs = opts.overlapMs ?? Math.floor(opts.maxAgeMs * 0.1)
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
addKey(pair: ManagedKeyPair): void {
|
|
121
|
-
this.keys.push(pair)
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
getCurrentKey(): ManagedKeyPair | null {
|
|
125
|
-
const now = Date.now()
|
|
126
|
-
const active = this.keys.filter(k => !k.expiresAt || k.expiresAt > now)
|
|
127
|
-
if (active.length === 0) return null
|
|
128
|
-
return active.reduce((a, b) => (a.createdAt > b.createdAt ? a : b))
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
async rotateIfNeeded(): Promise<ManagedKeyPair | null> {
|
|
132
|
-
const now = Date.now()
|
|
133
|
-
const current = this.getCurrentKey()
|
|
134
|
-
if (current && now - current.createdAt < this.maxAgeMs - this.overlapMs) return null
|
|
135
|
-
// Expire the current key after overlap period
|
|
136
|
-
if (current && !current.expiresAt) {
|
|
137
|
-
current.expiresAt = now + this.overlapMs
|
|
138
|
-
}
|
|
139
|
-
const { publicKey, privateKey } = await generateEd25519KeypairBase64Url()
|
|
140
|
-
const newKey: ManagedKeyPair = {
|
|
141
|
-
id: `key-${now}-${Math.random().toString(36).slice(2, 8)}`,
|
|
142
|
-
publicKey,
|
|
143
|
-
privateKey,
|
|
144
|
-
createdAt: now,
|
|
145
|
-
}
|
|
146
|
-
this.keys.push(newKey)
|
|
147
|
-
return newKey
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
getWellKnownDocument(): WellKnownKeysDocument {
|
|
151
|
-
const now = Date.now()
|
|
152
|
-
return {
|
|
153
|
-
version: '7h3/0.1',
|
|
154
|
-
updated: now,
|
|
155
|
-
keys: this.keys.map(k => ({
|
|
156
|
-
id: k.id,
|
|
157
|
-
algorithm: 'Ed25519' as const,
|
|
158
|
-
publicKey: k.publicKey,
|
|
159
|
-
created: k.createdAt,
|
|
160
|
-
...(k.expiresAt ? { expires: k.expiresAt } : {}),
|
|
161
|
-
...(k.expiresAt && k.expiresAt < now ? { revoked: true, revokedAt: k.expiresAt } : {}),
|
|
162
|
-
})),
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
toKeyRegistry(): KeyRegistry {
|
|
167
|
-
return {
|
|
168
|
-
getPublicKey: async (senderId: string): Promise<string | null> => {
|
|
169
|
-
const now = Date.now()
|
|
170
|
-
// Find by ID or return the current active key's public key
|
|
171
|
-
const byId = this.keys.find(k => k.id === senderId && (!k.expiresAt || k.expiresAt > now))
|
|
172
|
-
return byId?.publicKey ?? this.getCurrentKey()?.publicKey ?? null
|
|
173
|
-
},
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// ── Revocation Registry ──────────────────────────────────────────────────────
|
|
179
|
-
|
|
180
|
-
export interface RevocationEntry {
|
|
181
|
-
id: string
|
|
182
|
-
revokedAt: number
|
|
183
|
-
reason?: string
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
export interface RevocationList {
|
|
187
|
-
version: '7h3/0.1'
|
|
188
|
-
updated: number
|
|
189
|
-
revokedKeys: RevocationEntry[]
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
export class RevocationRegistry {
|
|
193
|
-
private revoked = new Map<string, RevocationEntry>()
|
|
194
|
-
|
|
195
|
-
revoke(keyId: string, reason?: string): void {
|
|
196
|
-
this.revoked.set(keyId, { id: keyId, revokedAt: Date.now(), reason })
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
isRevoked(keyId: string): boolean {
|
|
200
|
-
return this.revoked.has(keyId)
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
getList(): RevocationList {
|
|
204
|
-
return {
|
|
205
|
-
version: '7h3/0.1',
|
|
206
|
-
updated: Date.now(),
|
|
207
|
-
revokedKeys: [...this.revoked.values()],
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
importList(list: RevocationList): void {
|
|
212
|
-
for (const entry of list.revokedKeys) {
|
|
213
|
-
if (!this.revoked.has(entry.id)) this.revoked.set(entry.id, entry)
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// Returns a KeyRegistry wrapper that rejects revoked keys
|
|
218
|
-
wrapRegistry(inner: KeyRegistry): KeyRegistry {
|
|
219
|
-
const self = this
|
|
220
|
-
return {
|
|
221
|
-
async getPublicKey(senderId: string) {
|
|
222
|
-
if (self.isRevoked(senderId)) return null
|
|
223
|
-
return inner.getPublicKey(senderId)
|
|
224
|
-
},
|
|
225
|
-
getSharedSecret: inner.getSharedSecret?.bind(inner),
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
}
|
package/src/keyRegistry.ts
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
// Core interface all bindings use to look up keys
|
|
2
|
-
export interface KeyRegistry {
|
|
3
|
-
getPublicKey(senderId: string): Promise<string | null> // Ed25519 SPKI base64url
|
|
4
|
-
getSharedSecret?(keyId: string): Promise<string | null> // HMAC secret (optional)
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
// Simple in-memory registry from a static map
|
|
8
|
-
export function createStaticKeyRegistry(keys: Record<string, string>): KeyRegistry {
|
|
9
|
-
return {
|
|
10
|
-
async getPublicKey(senderId) { return keys[senderId] ?? null },
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
// Multi-registry: tries registries in order, returns first non-null
|
|
15
|
-
export function createCompositeKeyRegistry(...registries: KeyRegistry[]): KeyRegistry {
|
|
16
|
-
return {
|
|
17
|
-
async getPublicKey(senderId) {
|
|
18
|
-
for (const r of registries) {
|
|
19
|
-
const key = await r.getPublicKey(senderId)
|
|
20
|
-
if (key !== null) return key
|
|
21
|
-
}
|
|
22
|
-
return null
|
|
23
|
-
},
|
|
24
|
-
async getSharedSecret(keyId) {
|
|
25
|
-
for (const r of registries) {
|
|
26
|
-
if (!r.getSharedSecret) continue
|
|
27
|
-
const secret = await r.getSharedSecret(keyId)
|
|
28
|
-
if (secret !== null) return secret
|
|
29
|
-
}
|
|
30
|
-
return null
|
|
31
|
-
},
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Caching wrapper around any KeyRegistry (avoids repeated network calls)
|
|
36
|
-
export function createCachingKeyRegistry(
|
|
37
|
-
inner: KeyRegistry,
|
|
38
|
-
opts?: { ttlMs?: number }
|
|
39
|
-
): KeyRegistry {
|
|
40
|
-
const ttl = opts?.ttlMs ?? 60_000
|
|
41
|
-
const cache = new Map<string, { value: string | null; expiresAt: number }>()
|
|
42
|
-
async function get(key: string, fetch: () => Promise<string | null>): Promise<string | null> {
|
|
43
|
-
const now = Date.now()
|
|
44
|
-
const cached = cache.get(key)
|
|
45
|
-
if (cached && cached.expiresAt > now) return cached.value
|
|
46
|
-
const value = await fetch()
|
|
47
|
-
cache.set(key, { value, expiresAt: now + ttl })
|
|
48
|
-
return value
|
|
49
|
-
}
|
|
50
|
-
return {
|
|
51
|
-
async getPublicKey(senderId) {
|
|
52
|
-
return get(`pk:${senderId}`, () => inner.getPublicKey(senderId))
|
|
53
|
-
},
|
|
54
|
-
async getSharedSecret(keyId) {
|
|
55
|
-
if (!inner.getSharedSecret) return null
|
|
56
|
-
return get(`ss:${keyId}`, () => inner.getSharedSecret!(keyId))
|
|
57
|
-
},
|
|
58
|
-
}
|
|
59
|
-
}
|
package/src/keyRotation.test.ts
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import { createEnvelope, signEnvelopeHmac } from './protocol'
|
|
3
|
-
import { RollingKeyring, createKeyringSignatureResolver } from './keyRotation'
|
|
4
|
-
import { InMemoryReplayCache } from './protocolReplay'
|
|
5
|
-
import { receiveEnvelope } from './protocolTransport'
|
|
6
|
-
|
|
7
|
-
describe('rolling keyring', () => {
|
|
8
|
-
it('selects newest active signing key and enforces revocation', () => {
|
|
9
|
-
const ring = new RollingKeyring([
|
|
10
|
-
{
|
|
11
|
-
sender: 'agent.alpha',
|
|
12
|
-
keyId: 'k-old',
|
|
13
|
-
alg: 'HS256',
|
|
14
|
-
status: 'verify-only',
|
|
15
|
-
notBeforeMs: 1000,
|
|
16
|
-
notAfterMs: 5000,
|
|
17
|
-
material: { alg: 'HS256', secret: 'old-secret' },
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
sender: 'agent.alpha',
|
|
21
|
-
keyId: 'k-new',
|
|
22
|
-
alg: 'HS256',
|
|
23
|
-
status: 'active',
|
|
24
|
-
notBeforeMs: 4000,
|
|
25
|
-
notAfterMs: 9000,
|
|
26
|
-
material: { alg: 'HS256', secret: 'new-secret' },
|
|
27
|
-
},
|
|
28
|
-
])
|
|
29
|
-
|
|
30
|
-
expect(ring.selectSigningKey('agent.alpha', 'HS256', 4500)?.keyId).toBe('k-new')
|
|
31
|
-
expect(ring.revoke('agent.alpha', 'k-new', 4600)).toBe(true)
|
|
32
|
-
expect(ring.selectSigningKey('agent.alpha', 'HS256', 4700)).toBeNull()
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
it('resolves verification material and integrates with transport signatureResolver', async () => {
|
|
36
|
-
const ring = new RollingKeyring([
|
|
37
|
-
{
|
|
38
|
-
sender: 'agent.alpha',
|
|
39
|
-
keyId: 'k1',
|
|
40
|
-
alg: 'HS256',
|
|
41
|
-
status: 'active',
|
|
42
|
-
notBeforeMs: 1000,
|
|
43
|
-
notAfterMs: 9000,
|
|
44
|
-
material: { alg: 'HS256', secret: 'shared-secret' },
|
|
45
|
-
},
|
|
46
|
-
])
|
|
47
|
-
|
|
48
|
-
const envelope = await signEnvelopeHmac(
|
|
49
|
-
createEnvelope({
|
|
50
|
-
sender: 'agent.alpha',
|
|
51
|
-
intent: 'PING',
|
|
52
|
-
content: 'hello',
|
|
53
|
-
messageId: 'kr-1',
|
|
54
|
-
nonce: 'kr-n1',
|
|
55
|
-
nowMs: 2000,
|
|
56
|
-
ttlMs: 60_000,
|
|
57
|
-
}),
|
|
58
|
-
'shared-secret',
|
|
59
|
-
'k1',
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
const ok = await receiveEnvelope(envelope, {
|
|
63
|
-
nowMs: 3000,
|
|
64
|
-
replayCache: new InMemoryReplayCache(),
|
|
65
|
-
signatureResolver: createKeyringSignatureResolver(ring, () => 3000),
|
|
66
|
-
})
|
|
67
|
-
expect(ok.ok).toBe(true)
|
|
68
|
-
|
|
69
|
-
ring.revoke('agent.alpha', 'k1', 3100)
|
|
70
|
-
const blocked = await receiveEnvelope(envelope, {
|
|
71
|
-
nowMs: 3200,
|
|
72
|
-
replayCache: new InMemoryReplayCache(),
|
|
73
|
-
signatureResolver: createKeyringSignatureResolver(ring, () => 3200),
|
|
74
|
-
})
|
|
75
|
-
expect(blocked.ok).toBe(false)
|
|
76
|
-
expect(blocked.diagnostics.some((d) => d.message.includes('No signature verification material'))).toBe(true)
|
|
77
|
-
})
|
|
78
|
-
})
|
package/src/keyRotation.ts
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import type { ProtocolEnvelope, SignatureVerificationMaterial } from './protocol'
|
|
2
|
-
|
|
3
|
-
export type KeyStatus = 'active' | 'verify-only' | 'revoked'
|
|
4
|
-
|
|
5
|
-
export interface KeyRecord {
|
|
6
|
-
sender: string
|
|
7
|
-
keyId: string
|
|
8
|
-
alg: SignatureVerificationMaterial['alg']
|
|
9
|
-
status: KeyStatus
|
|
10
|
-
notBeforeMs: number
|
|
11
|
-
notAfterMs: number
|
|
12
|
-
material: SignatureVerificationMaterial
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export class RollingKeyring {
|
|
16
|
-
private readonly records: KeyRecord[]
|
|
17
|
-
|
|
18
|
-
constructor(records: KeyRecord[] = []) {
|
|
19
|
-
this.records = [...records]
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
add(record: KeyRecord): void {
|
|
23
|
-
this.records.push(record)
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
revoke(sender: string, keyId: string, revokedAtMs = Date.now()): boolean {
|
|
27
|
-
const record = this.records.find((entry) => entry.sender === sender && entry.keyId === keyId)
|
|
28
|
-
if (!record) return false
|
|
29
|
-
record.status = 'revoked'
|
|
30
|
-
record.notAfterMs = Math.min(record.notAfterMs, revokedAtMs)
|
|
31
|
-
return true
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
selectSigningKey(sender: string, alg: SignatureVerificationMaterial['alg'], nowMs = Date.now()): KeyRecord | null {
|
|
35
|
-
const candidates = this.records
|
|
36
|
-
.filter(
|
|
37
|
-
(entry) =>
|
|
38
|
-
entry.sender === sender &&
|
|
39
|
-
entry.alg === alg &&
|
|
40
|
-
entry.status === 'active' &&
|
|
41
|
-
entry.notBeforeMs <= nowMs &&
|
|
42
|
-
nowMs < entry.notAfterMs,
|
|
43
|
-
)
|
|
44
|
-
.sort((a, b) => b.notBeforeMs - a.notBeforeMs)
|
|
45
|
-
return candidates[0] ?? null
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
resolveVerificationMaterial(
|
|
49
|
-
signature: NonNullable<ProtocolEnvelope['signature']>,
|
|
50
|
-
sender: string,
|
|
51
|
-
nowMs = Date.now(),
|
|
52
|
-
): SignatureVerificationMaterial | undefined {
|
|
53
|
-
const candidates = this.records
|
|
54
|
-
.filter(
|
|
55
|
-
(entry) =>
|
|
56
|
-
entry.sender === sender &&
|
|
57
|
-
entry.keyId === signature.keyId &&
|
|
58
|
-
entry.alg === signature.alg &&
|
|
59
|
-
entry.status !== 'revoked' &&
|
|
60
|
-
entry.notBeforeMs <= nowMs &&
|
|
61
|
-
nowMs < entry.notAfterMs,
|
|
62
|
-
)
|
|
63
|
-
.sort((a, b) => b.notBeforeMs - a.notBeforeMs)
|
|
64
|
-
|
|
65
|
-
return candidates[0]?.material
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export function createKeyringSignatureResolver(keyring: RollingKeyring, nowMsProvider: () => number = () => Date.now()) {
|
|
70
|
-
return async (signature: NonNullable<ProtocolEnvelope['signature']>, sender: string): Promise<SignatureVerificationMaterial | undefined> =>
|
|
71
|
-
keyring.resolveVerificationMaterial(signature, sender, nowMsProvider())
|
|
72
|
-
}
|
package/src/mcpGateway.test.ts
DELETED
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import { createAipMcpGatewayRuntime } from './mcpGateway'
|
|
3
|
-
|
|
4
|
-
describe('AIP MCP gateway runtime', () => {
|
|
5
|
-
it('returns JSON-RPC result for allowed method', async () => {
|
|
6
|
-
const runtime = createAipMcpGatewayRuntime({ sharedSecret: 'gateway-test-secret' })
|
|
7
|
-
|
|
8
|
-
const line = JSON.stringify({
|
|
9
|
-
jsonrpc: '2.0',
|
|
10
|
-
id: 1,
|
|
11
|
-
method: 'tools/call',
|
|
12
|
-
params: { name: 'planner' },
|
|
13
|
-
})
|
|
14
|
-
|
|
15
|
-
const responseLine = await runtime.handleLine(line)
|
|
16
|
-
expect(responseLine).not.toBeNull()
|
|
17
|
-
const response = JSON.parse(responseLine ?? '{}') as {
|
|
18
|
-
jsonrpc: string
|
|
19
|
-
id: number
|
|
20
|
-
result?: { ok: boolean; capability: string }
|
|
21
|
-
error?: { code: number; message: string }
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
expect(response.jsonrpc).toBe('2.0')
|
|
25
|
-
expect(response.id).toBe(1)
|
|
26
|
-
expect(response.error).toBeUndefined()
|
|
27
|
-
expect(response.result?.ok).toBe(true)
|
|
28
|
-
expect(response.result?.capability).toBe('mcp.tools/call')
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
it('returns invalid request for malformed JSON-RPC line', async () => {
|
|
32
|
-
const runtime = createAipMcpGatewayRuntime({ sharedSecret: 'gateway-test-secret' })
|
|
33
|
-
const responseLine = await runtime.handleLine('{"bad": true}')
|
|
34
|
-
const response = JSON.parse(responseLine ?? '{}') as { error?: { code: number; message: string } }
|
|
35
|
-
|
|
36
|
-
expect(response.error?.code).toBe(-32600)
|
|
37
|
-
expect(response.error?.message).toBe('Invalid Request')
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
it('returns method not found for disallowed method', async () => {
|
|
41
|
-
const runtime = createAipMcpGatewayRuntime({
|
|
42
|
-
sharedSecret: 'gateway-test-secret',
|
|
43
|
-
allowedMethods: ['tools/call'],
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
const line = JSON.stringify({
|
|
47
|
-
jsonrpc: '2.0',
|
|
48
|
-
id: 'x',
|
|
49
|
-
method: 'resources/read',
|
|
50
|
-
params: {},
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
const responseLine = await runtime.handleLine(line)
|
|
54
|
-
const response = JSON.parse(responseLine ?? '{}') as { error?: { code: number; message: string } }
|
|
55
|
-
|
|
56
|
-
expect(response.error?.code).toBe(-32601)
|
|
57
|
-
expect(response.error?.message).toContain("is not allowed")
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
it('returns authorization error when policy hook blocks request', async () => {
|
|
61
|
-
const runtime = createAipMcpGatewayRuntime({
|
|
62
|
-
sharedSecret: 'gateway-test-secret',
|
|
63
|
-
authorizeMethod: async () => false,
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
const line = JSON.stringify({
|
|
67
|
-
jsonrpc: '2.0',
|
|
68
|
-
id: 'auth-2',
|
|
69
|
-
method: 'tools/call',
|
|
70
|
-
params: {},
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
const responseLine = await runtime.handleLine(line)
|
|
74
|
-
const response = JSON.parse(responseLine ?? '{}') as { error?: { code: number; message: string } }
|
|
75
|
-
|
|
76
|
-
expect(response.error?.code).toBe(-32001)
|
|
77
|
-
expect(response.error?.message).toContain('not authorized')
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
it('returns rate-limit error when limiter blocks request', async () => {
|
|
81
|
-
const runtime = createAipMcpGatewayRuntime({
|
|
82
|
-
sharedSecret: 'gateway-test-secret',
|
|
83
|
-
rateLimiter: {
|
|
84
|
-
consume: async () => false,
|
|
85
|
-
},
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
const line = JSON.stringify({
|
|
89
|
-
jsonrpc: '2.0',
|
|
90
|
-
id: 'rl-3',
|
|
91
|
-
method: 'tools/call',
|
|
92
|
-
params: {},
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
const responseLine = await runtime.handleLine(line)
|
|
96
|
-
const response = JSON.parse(responseLine ?? '{}') as { error?: { code: number; message: string } }
|
|
97
|
-
|
|
98
|
-
expect(response.error?.code).toBe(-32002)
|
|
99
|
-
expect(response.error?.message).toContain('rate-limited')
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
it('ignores empty lines', async () => {
|
|
103
|
-
const runtime = createAipMcpGatewayRuntime({ sharedSecret: 'gateway-test-secret' })
|
|
104
|
-
await expect(runtime.handleLine(' ')).resolves.toBeNull()
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
it('emits audit events for policy and request phases', async () => {
|
|
108
|
-
const phases: string[] = []
|
|
109
|
-
const runtime = createAipMcpGatewayRuntime({
|
|
110
|
-
sharedSecret: 'gateway-test-secret',
|
|
111
|
-
onAuditEvent: async (event) => {
|
|
112
|
-
phases.push(event.phase)
|
|
113
|
-
},
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
const line = JSON.stringify({
|
|
117
|
-
jsonrpc: '2.0',
|
|
118
|
-
id: 'audit-1',
|
|
119
|
-
method: 'tools/call',
|
|
120
|
-
params: { name: 'planner' },
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
const responseLine = await runtime.handleLine(line)
|
|
124
|
-
expect(responseLine).not.toBeNull()
|
|
125
|
-
expect(phases).toContain('request_received')
|
|
126
|
-
expect(phases).toContain('policy')
|
|
127
|
-
expect(phases).toContain('request_success')
|
|
128
|
-
})
|
|
129
|
-
})
|