@7h3/protocol 0.4.0 → 0.5.1
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 -323
- 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 +3662 -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 -79
- package/policyEnforcer.d.ts +50 -0
- package/policyTelemetryFeedback.d.ts +11 -0
- package/protocol.d.ts +66 -0
- 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 -92
- 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 -385
- 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.png +0 -0
- 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/go.mod +0 -3
- 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/webhook.go +0 -136
- package/sdk/python/README.md +0 -18
- package/sdk/python/protocol_7h3/__init__.py +0 -46
- package/sdk/python/protocol_7h3/http.py +0 -212
- package/sdk/python/protocol_7h3/keys.py +0 -149
- package/sdk/python/protocol_7h3/protocol.py +0 -525
- package/sdk/python/protocol_7h3/queue.py +0 -118
- 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_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/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/conformance.test.ts +0 -136
- package/src/conformanceVectors.ts +0 -99
- 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 -171
- 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 -163
- package/src/index.ts +0 -32
- 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/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.test.ts +0 -155
- package/src/protocol.ts +0 -413
- 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 -141
- package/src/replayStores.ts +0 -82
- 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/webhookBinding.test.ts +0 -144
- package/src/webhookBinding.ts +0 -115
- package/src/wsBinding.test.ts +0 -221
- package/src/wsBinding.ts +0 -100
- package/tsconfig.json +0 -15
- package/tsconfig.lib.json +0 -23
- package/vite.lib.config.ts +0 -16
package/docs/TELEMETRY.md
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
# Telemetry Events
|
|
2
|
-
|
|
3
|
-
Transport-level telemetry can be attached to `receiveEnvelope` via `telemetry` callback.
|
|
4
|
-
|
|
5
|
-
## Event phases
|
|
6
|
-
|
|
7
|
-
- `decoded`
|
|
8
|
-
- `rejected_clock_skew`
|
|
9
|
-
- `rejected_validation`
|
|
10
|
-
- `rejected_replay`
|
|
11
|
-
- `rejected_missing_signature`
|
|
12
|
-
- `rejected_missing_material`
|
|
13
|
-
- `rejected_bad_signature`
|
|
14
|
-
- `accepted`
|
|
15
|
-
|
|
16
|
-
## Usage
|
|
17
|
-
|
|
18
|
-
```ts
|
|
19
|
-
import { receiveEnvelope } from './src/protocolTransport'
|
|
20
|
-
|
|
21
|
-
await receiveEnvelope(rawEnvelope, {
|
|
22
|
-
nowMs: Date.now(),
|
|
23
|
-
replayCache,
|
|
24
|
-
secretResolver: async () => sharedSecret,
|
|
25
|
-
telemetry: async (event) => {
|
|
26
|
-
console.log(event.phase, event.sender, event.messageId, event.reason)
|
|
27
|
-
},
|
|
28
|
-
})
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
## Gateway audit events
|
|
32
|
-
|
|
33
|
-
`createAipMcpGatewayRuntime` supports `onAuditEvent` for request/policy traces.
|
|
34
|
-
|
|
35
|
-
Phases:
|
|
36
|
-
|
|
37
|
-
- `request_received`
|
|
38
|
-
- `policy`
|
|
39
|
-
- `verification_failed`
|
|
40
|
-
- `request_success`
|
|
41
|
-
- `request_error`
|
package/docs/THREAT_MODEL.md
DELETED
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
# 7h3 Protocol AIP Threat Model (v1.0 draft)
|
|
2
|
-
|
|
3
|
-
## Scope
|
|
4
|
-
|
|
5
|
-
This model covers `aip/0.1` envelope transport and verification paths in:
|
|
6
|
-
|
|
7
|
-
- `src/protocol.ts`
|
|
8
|
-
- `src/protocolTransport.ts`
|
|
9
|
-
- `src/mcpGateway.ts`
|
|
10
|
-
- `sdk/rust/src/lib.rs`
|
|
11
|
-
|
|
12
|
-
## Assets
|
|
13
|
-
|
|
14
|
-
- Message integrity and authenticity
|
|
15
|
-
- Agent identity binding (`sender`, `keyId`, signature material)
|
|
16
|
-
- Freshness (TTL + replay resistance)
|
|
17
|
-
- Gateway policy decisions (allowlist/auth/rate-limit)
|
|
18
|
-
|
|
19
|
-
## Assumptions
|
|
20
|
-
|
|
21
|
-
- Clocks are roughly synchronized within deployment skew limits.
|
|
22
|
-
- Secret/private-key material is provisioned securely outside protocol logic.
|
|
23
|
-
- Transport channel can be observed/modified by adversaries; protocol must detect tamper.
|
|
24
|
-
|
|
25
|
-
## Threats and controls
|
|
26
|
-
|
|
27
|
-
## Tampering
|
|
28
|
-
|
|
29
|
-
- Threat: attacker modifies envelope body/header in transit.
|
|
30
|
-
- Controls:
|
|
31
|
-
- canonical payload signing (`HS256` / `ED25519`)
|
|
32
|
-
- signature verification before acceptance
|
|
33
|
-
- canonical key order fixed to prevent ambiguous signing forms
|
|
34
|
-
|
|
35
|
-
## Replay
|
|
36
|
-
|
|
37
|
-
- Threat: attacker replays previously valid signed envelopes.
|
|
38
|
-
- Controls:
|
|
39
|
-
- TTL checks (`timestampMs + ttlMs`)
|
|
40
|
-
- replay cache consumption (`messageId` + `sender` + `nonce` semantics)
|
|
41
|
-
|
|
42
|
-
## Sender impersonation
|
|
43
|
-
|
|
44
|
-
- Threat: attacker signs as trusted sender with incorrect key material.
|
|
45
|
-
- Controls:
|
|
46
|
-
- verifier resolves material by `(keyId, sender)` context
|
|
47
|
-
- `signatureResolver`/`secretResolver` mapping policy in transport layer
|
|
48
|
-
|
|
49
|
-
## Algorithm confusion / downgrade
|
|
50
|
-
|
|
51
|
-
- Threat: attacker swaps `alg` or relies on omitted compact alg fields.
|
|
52
|
-
- Controls:
|
|
53
|
-
- explicit algorithm field in canonical signature object
|
|
54
|
-
- compact wire supports `sig.a`; default compatibility path only for legacy HS256
|
|
55
|
-
- verifier rejects material when resolved algorithm does not match envelope signature
|
|
56
|
-
|
|
57
|
-
## Gateway abuse
|
|
58
|
-
|
|
59
|
-
- Threat: unauthorized or abusive JSON-RPC method calls.
|
|
60
|
-
- Controls:
|
|
61
|
-
- method allowlist
|
|
62
|
-
- authorization policy hook
|
|
63
|
-
- rate-limit policy hook with context-aware keys
|
|
64
|
-
|
|
65
|
-
## Mitigated risks
|
|
66
|
-
|
|
67
|
-
- **Distributed replay** — a Redis-backed `DistributedReplayStore` is shipped
|
|
68
|
-
(`createRedisReplayStore`, `src/replayStores.ts`). It uses atomic `SET NX PX`,
|
|
69
|
-
batches via pipeline (`reserveMany`), and degrades to a local store on a Redis
|
|
70
|
-
outage so a node never blindly accepts replays and never hard-stops. See
|
|
71
|
-
`DISTRIBUTED_REPLAY.md`.
|
|
72
|
-
- **Fleet-wide key revocation** — a shared `RevocationStore`
|
|
73
|
-
(`createRedisRevocationStore`, `src/revocation.ts`) is consulted on the verify
|
|
74
|
-
path via `withRevocationCheck`. A key revoked on one node is rejected on all
|
|
75
|
-
nodes; reads are cached and fail **closed** by default. See `KEY_REVOCATION.md`.
|
|
76
|
-
|
|
77
|
-
## Remaining risks (open)
|
|
78
|
-
|
|
79
|
-
- No formal fuzz campaign yet for parser/canonicalization boundaries.
|
|
80
|
-
- Revocation/replay stores rely on a correctly provisioned, available Redis (or
|
|
81
|
-
equivalent) control plane; operators own its HA and clock synchronization.
|
|
82
|
-
- No third-party cryptographic audit yet.
|
|
83
|
-
|
|
84
|
-
## Required production mitigations
|
|
85
|
-
|
|
86
|
-
- Keep signature verification enabled by default in production paths.
|
|
87
|
-
- Deploy a shared replay store (`createRedisReplayStore`) for horizontally scaled gateways.
|
|
88
|
-
- Wire a shared revocation store (`createRedisRevocationStore` + `withRevocationCheck`)
|
|
89
|
-
and enforce key lifecycle policy (rotation, expiry, revocation) in the control plane.
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
# AIP Versioning Policy
|
|
2
|
-
|
|
3
|
-
## Protocol versions
|
|
4
|
-
|
|
5
|
-
- Protocol version is declared in `header.version` (for example `aip/0.1`).
|
|
6
|
-
- New major protocol versions must not silently alter canonicalization or signature semantics.
|
|
7
|
-
- Backward-incompatible wire or verification changes require a new minor/major protocol tag.
|
|
8
|
-
|
|
9
|
-
## Compatibility rules
|
|
10
|
-
|
|
11
|
-
- Patch releases: bug fixes only, no wire-format or canonicalization changes.
|
|
12
|
-
- Minor releases: additive changes only (new optional fields/capabilities, no required-field breakage).
|
|
13
|
-
- Major releases: allowed to remove or alter semantics with explicit migration documentation.
|
|
14
|
-
|
|
15
|
-
## Signature profile policy
|
|
16
|
-
|
|
17
|
-
- Supported profiles are explicitly declared by `signature.alg`.
|
|
18
|
-
- Adding a new signature profile is a minor release only if existing profiles continue to verify unchanged.
|
|
19
|
-
- Removing a profile requires a major release.
|
|
20
|
-
|
|
21
|
-
## SDK versioning
|
|
22
|
-
|
|
23
|
-
- TypeScript, Python, and Rust SDKs follow semver independently.
|
|
24
|
-
- Conformance fixture changes require SDK conformance updates in the same release train.
|
|
25
|
-
|
|
26
|
-
## Release evidence requirements
|
|
27
|
-
|
|
28
|
-
- Conformance suites (TS/Python/Rust) pass.
|
|
29
|
-
- Bench regression checks attached for performance-sensitive changes.
|
|
30
|
-
- Migration guide updated for any behavior change that impacts consumers.
|
package/docs/assets/banner.png
DELETED
|
Binary file
|
package/eslint.config.js
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import js from '@eslint/js'
|
|
2
|
-
import globals from 'globals'
|
|
3
|
-
import tseslint from 'typescript-eslint'
|
|
4
|
-
|
|
5
|
-
export default tseslint.config(
|
|
6
|
-
{ ignores: ['dist', 'node_modules', 'sdk/rust/target'] },
|
|
7
|
-
{
|
|
8
|
-
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
|
9
|
-
files: ['**/*.ts'],
|
|
10
|
-
languageOptions: {
|
|
11
|
-
ecmaVersion: 2022,
|
|
12
|
-
globals: { ...globals.node, ...globals.browser },
|
|
13
|
-
},
|
|
14
|
-
},
|
|
15
|
-
)
|
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Fuzz harness: wire decoder resilience.
|
|
3
|
-
*
|
|
4
|
-
* Invariant: decodeEnvelope must NEVER throw on any input — arbitrary strings
|
|
5
|
-
* and arbitrary bytes both. On garbage it must return {ok: false}.
|
|
6
|
-
*
|
|
7
|
-
* Strategy: start from the conformance corpus, apply mutations, run N rounds.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { readFileSync } from 'node:fs'
|
|
11
|
-
import { join, dirname } from 'node:path'
|
|
12
|
-
import { fileURLToPath } from 'node:url'
|
|
13
|
-
import { decodeEnvelope } from '../../src/index.js'
|
|
14
|
-
|
|
15
|
-
const __dir = dirname(fileURLToPath(import.meta.url))
|
|
16
|
-
const VECTORS_PATH = join(__dir, '../../conformance/7h3_v0_1.json')
|
|
17
|
-
const ROUNDS = parseInt(process.env.FUZZ_ROUNDS ?? '50000', 10)
|
|
18
|
-
|
|
19
|
-
const { vectors, ed25519Vectors = [] } = JSON.parse(readFileSync(VECTORS_PATH, 'utf-8'))
|
|
20
|
-
|
|
21
|
-
// --- corpus: valid JSON and compact encodings from conformance vectors ---
|
|
22
|
-
const corpus: (string | Uint8Array)[] = []
|
|
23
|
-
for (const v of [...vectors, ...ed25519Vectors]) {
|
|
24
|
-
const env = v.envelope
|
|
25
|
-
corpus.push(JSON.stringify(env))
|
|
26
|
-
// compact encoding
|
|
27
|
-
corpus.push(
|
|
28
|
-
JSON.stringify({
|
|
29
|
-
v: env.header?.version,
|
|
30
|
-
mid: env.header?.messageId,
|
|
31
|
-
ts: env.header?.timestampMs,
|
|
32
|
-
ttl: env.header?.ttlMs,
|
|
33
|
-
s: env.header?.sender,
|
|
34
|
-
n: env.header?.nonce,
|
|
35
|
-
i: env.body?.intent,
|
|
36
|
-
c: env.body?.content,
|
|
37
|
-
}),
|
|
38
|
-
)
|
|
39
|
-
}
|
|
40
|
-
// seed with known-bad corpus
|
|
41
|
-
corpus.push('', '{}', '[]', 'null', '"string"', '{', '{"header":{}}', '{"header":{},"body":{}}')
|
|
42
|
-
|
|
43
|
-
// --- mutators ---
|
|
44
|
-
type Mutator = (input: string, rand: () => number) => string
|
|
45
|
-
|
|
46
|
-
const mutators: Mutator[] = [
|
|
47
|
-
// bit flip a byte
|
|
48
|
-
(s, r) => {
|
|
49
|
-
const buf = Buffer.from(s, 'utf-8')
|
|
50
|
-
if (!buf.length) return s
|
|
51
|
-
buf[Math.floor(r() * buf.length)] ^= 1 << Math.floor(r() * 8)
|
|
52
|
-
return buf.toString('utf-8')
|
|
53
|
-
},
|
|
54
|
-
// insert random byte
|
|
55
|
-
(s, r) => {
|
|
56
|
-
const buf = Buffer.from(s, 'utf-8')
|
|
57
|
-
const idx = Math.floor(r() * (buf.length + 1))
|
|
58
|
-
const b = Math.floor(r() * 256)
|
|
59
|
-
return Buffer.concat([buf.subarray(0, idx), Buffer.from([b]), buf.subarray(idx)]).toString('utf-8')
|
|
60
|
-
},
|
|
61
|
-
// delete a byte
|
|
62
|
-
(s, r) => {
|
|
63
|
-
const buf = Buffer.from(s, 'utf-8')
|
|
64
|
-
if (!buf.length) return s
|
|
65
|
-
const idx = Math.floor(r() * buf.length)
|
|
66
|
-
return Buffer.concat([buf.subarray(0, idx), buf.subarray(idx + 1)]).toString('utf-8')
|
|
67
|
-
},
|
|
68
|
-
// truncate
|
|
69
|
-
(s, r) => {
|
|
70
|
-
const buf = Buffer.from(s, 'utf-8')
|
|
71
|
-
return buf.subarray(0, Math.floor(r() * buf.length)).toString('utf-8')
|
|
72
|
-
},
|
|
73
|
-
// mutate a JSON field
|
|
74
|
-
(s, r) => {
|
|
75
|
-
try {
|
|
76
|
-
const obj = JSON.parse(s) as Record<string, unknown>
|
|
77
|
-
const keys = Object.keys(obj)
|
|
78
|
-
if (!keys.length) return s
|
|
79
|
-
const key = keys[Math.floor(r() * keys.length)]
|
|
80
|
-
const replacement = [null, 0, 1, -1, '', [], {}, true, false][Math.floor(r() * 9)]
|
|
81
|
-
obj[key] = replacement
|
|
82
|
-
return JSON.stringify(obj)
|
|
83
|
-
} catch {
|
|
84
|
-
return s
|
|
85
|
-
}
|
|
86
|
-
},
|
|
87
|
-
// replace a known token with another
|
|
88
|
-
(s, r) => {
|
|
89
|
-
const tokens = ['7h3/0.1', 'TASK', 'PING', 'RESULT', 'HS256', 'ED25519', '"sender"', '"body"']
|
|
90
|
-
const replacements = ['aip/0.2', 'INVALID', '', '0', 'NONE', 'RSA', '"SENDER"', '"Body"']
|
|
91
|
-
const i = Math.floor(r() * tokens.length)
|
|
92
|
-
return s.split(tokens[i]).join(replacements[i])
|
|
93
|
-
},
|
|
94
|
-
]
|
|
95
|
-
|
|
96
|
-
// --- runner ---
|
|
97
|
-
let crashes = 0
|
|
98
|
-
let okFalse = 0
|
|
99
|
-
let okTrue = 0
|
|
100
|
-
|
|
101
|
-
function rand(): number {
|
|
102
|
-
return Math.random()
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
let current = corpus[0] as string
|
|
106
|
-
|
|
107
|
-
for (let i = 0; i < ROUNDS; i++) {
|
|
108
|
-
// pick a mutator and apply it
|
|
109
|
-
const mutator = mutators[Math.floor(rand() * mutators.length)]
|
|
110
|
-
current = mutator(current, rand)
|
|
111
|
-
|
|
112
|
-
// occasionally reset to a fresh corpus item
|
|
113
|
-
if (i % 500 === 0) {
|
|
114
|
-
current = corpus[Math.floor(rand() * corpus.length)] as string
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
try {
|
|
118
|
-
const result = decodeEnvelope(current)
|
|
119
|
-
if (result.ok) {
|
|
120
|
-
okTrue++
|
|
121
|
-
} else {
|
|
122
|
-
okFalse++
|
|
123
|
-
}
|
|
124
|
-
} catch (err) {
|
|
125
|
-
crashes++
|
|
126
|
-
console.error(`\nCRASH at round ${i}:`)
|
|
127
|
-
console.error(' Input (first 200 chars):', JSON.stringify(current.slice(0, 200)))
|
|
128
|
-
console.error(' Error:', err)
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
console.log(`[harness-decode] rounds=${ROUNDS} ok=${okTrue} ok:false=${okFalse} crashes=${crashes}`)
|
|
133
|
-
if (crashes > 0) {
|
|
134
|
-
console.error(`FAIL: ${crashes} crash(es) detected`)
|
|
135
|
-
process.exit(1)
|
|
136
|
-
}
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Fuzz harness: signature verification with tampered envelopes.
|
|
3
|
-
*
|
|
4
|
-
* Invariants:
|
|
5
|
-
* 1. verifyEnvelopeHmac must NEVER throw — tampered or malformed envelopes
|
|
6
|
-
* must return false, not crash.
|
|
7
|
-
* 2. A valid envelope always verifies as true (regression check).
|
|
8
|
-
* 3. Single-byte tampering of any signed field must make verification fail.
|
|
9
|
-
*
|
|
10
|
-
* Strategy: start from valid signed envelopes, apply structural and byte mutations.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { readFileSync } from 'node:fs'
|
|
14
|
-
import { join, dirname } from 'node:path'
|
|
15
|
-
import { fileURLToPath } from 'node:url'
|
|
16
|
-
import { createEnvelope, signEnvelopeHmac, verifyEnvelopeHmac } from '../../src/index.js'
|
|
17
|
-
|
|
18
|
-
const __dir = dirname(fileURLToPath(import.meta.url))
|
|
19
|
-
const VECTORS_PATH = join(__dir, '../../conformance/7h3_v0_1.json')
|
|
20
|
-
const ROUNDS = parseInt(process.env.FUZZ_ROUNDS ?? '20000', 10)
|
|
21
|
-
|
|
22
|
-
const { vectors } = JSON.parse(readFileSync(VECTORS_PATH, 'utf-8'))
|
|
23
|
-
|
|
24
|
-
const SECRET = vectors[0].secret as string
|
|
25
|
-
|
|
26
|
-
// Build and sign a fresh envelope so we have a known-valid baseline
|
|
27
|
-
const BASE_ENVELOPE = await signEnvelopeHmac(
|
|
28
|
-
createEnvelope({ sender: 'fuzzer', recipient: 'target', intent: 'TASK', content: 'baseline' }),
|
|
29
|
-
SECRET,
|
|
30
|
-
)
|
|
31
|
-
|
|
32
|
-
// Regression: valid envelope must verify
|
|
33
|
-
const baseOk = await verifyEnvelopeHmac(BASE_ENVELOPE, SECRET)
|
|
34
|
-
if (!baseOk) {
|
|
35
|
-
console.error('FAIL: baseline verification returned false — test setup error')
|
|
36
|
-
process.exit(1)
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// --- corpus of tampered envelopes ---
|
|
40
|
-
function deepClone<T>(x: T): T {
|
|
41
|
-
return JSON.parse(JSON.stringify(x)) as T
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
type Env = typeof BASE_ENVELOPE
|
|
45
|
-
|
|
46
|
-
async function tamper(env: Env, rand: () => number): Promise<Env> {
|
|
47
|
-
const e = deepClone(env)
|
|
48
|
-
const target = rand() < 0.5 ? 'header' : 'body'
|
|
49
|
-
const obj = e[target] as unknown as Record<string, unknown>
|
|
50
|
-
const keys = Object.keys(obj)
|
|
51
|
-
const key = keys[Math.floor(rand() * keys.length)]
|
|
52
|
-
const v = obj[key]
|
|
53
|
-
// Mutate the value
|
|
54
|
-
if (typeof v === 'string') {
|
|
55
|
-
if (v.length === 0) {
|
|
56
|
-
obj[key] = 'mutated'
|
|
57
|
-
} else {
|
|
58
|
-
const buf = Buffer.from(v, 'utf-8')
|
|
59
|
-
const idx = Math.floor(rand() * buf.length)
|
|
60
|
-
buf[idx] ^= 1 << Math.floor(rand() * 8)
|
|
61
|
-
obj[key] = buf.toString('utf-8')
|
|
62
|
-
}
|
|
63
|
-
} else if (typeof v === 'number') {
|
|
64
|
-
// Ensure delta is non-zero: map [0, 999] → [-500, -1] ∪ [1, 500]
|
|
65
|
-
const raw = Math.floor(rand() * 1000)
|
|
66
|
-
const delta = raw < 500 ? raw - 500 : raw - 499 // skips 0
|
|
67
|
-
obj[key] = v + delta
|
|
68
|
-
} else {
|
|
69
|
-
obj[key] = null
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Guard: if the mutation produced no net change (e.g. bit flip round-tripped
|
|
73
|
-
// through invalid UTF-8 to the same replacement character), force a known change.
|
|
74
|
-
if (JSON.stringify(e) === JSON.stringify(env)) {
|
|
75
|
-
const guard = e.body as unknown as Record<string, unknown>
|
|
76
|
-
guard['content'] = '__forced_tamper__'
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return e
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// --- runner ---
|
|
83
|
-
let crashes = 0
|
|
84
|
-
let tamperFalsePositive = 0
|
|
85
|
-
|
|
86
|
-
for (let i = 0; i < ROUNDS; i++) {
|
|
87
|
-
const rand = () => Math.random()
|
|
88
|
-
const tampered = await tamper(BASE_ENVELOPE, rand)
|
|
89
|
-
|
|
90
|
-
try {
|
|
91
|
-
const result = await verifyEnvelopeHmac(tampered, SECRET)
|
|
92
|
-
if (result === true) {
|
|
93
|
-
tamperFalsePositive++
|
|
94
|
-
console.error(`\nFAIL false positive at round ${i} — tampered envelope verified as ok`)
|
|
95
|
-
console.error(' Tampered:', JSON.stringify(tampered).slice(0, 300))
|
|
96
|
-
}
|
|
97
|
-
} catch (err) {
|
|
98
|
-
crashes++
|
|
99
|
-
console.error(`\nCRASH at round ${i}:`)
|
|
100
|
-
console.error(' Input:', JSON.stringify(tampered).slice(0, 300))
|
|
101
|
-
console.error(' Error:', err)
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Occasionally test with a structurally broken envelope (missing fields)
|
|
105
|
-
if (i % 200 === 0) {
|
|
106
|
-
const broken = { header: {}, body: {}, signature: BASE_ENVELOPE.signature } as unknown as Env
|
|
107
|
-
try {
|
|
108
|
-
await verifyEnvelopeHmac(broken, SECRET)
|
|
109
|
-
} catch (err) {
|
|
110
|
-
crashes++
|
|
111
|
-
console.error(`\nCRASH (broken envelope) at round ${i}:`, err)
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
console.log(
|
|
117
|
-
`[harness-verify] rounds=${ROUNDS} tamper-false-positives=${tamperFalsePositive} crashes=${crashes}`,
|
|
118
|
-
)
|
|
119
|
-
if (crashes > 0 || tamperFalsePositive > 0) {
|
|
120
|
-
process.exit(1)
|
|
121
|
-
}
|
package/fuzz/ts/run.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Fuzz campaign runner — executes all TypeScript harnesses in sequence.
|
|
3
|
-
*
|
|
4
|
-
* Usage:
|
|
5
|
-
* npm run fuzz:ts # default 50k/20k rounds
|
|
6
|
-
* FUZZ_ROUNDS=200000 npm run fuzz:ts
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { spawnSync } from 'node:child_process'
|
|
10
|
-
import { dirname, join } from 'node:path'
|
|
11
|
-
import { fileURLToPath } from 'node:url'
|
|
12
|
-
|
|
13
|
-
const __dir = dirname(fileURLToPath(import.meta.url))
|
|
14
|
-
|
|
15
|
-
const HARNESSES = ['harness-decode.ts', 'harness-verify.ts']
|
|
16
|
-
|
|
17
|
-
let failed = false
|
|
18
|
-
for (const harness of HARNESSES) {
|
|
19
|
-
process.stdout.write(`Running ${harness} ... `)
|
|
20
|
-
const result = spawnSync('npx', ['tsx', join(__dir, harness)], {
|
|
21
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
22
|
-
env: { ...process.env },
|
|
23
|
-
})
|
|
24
|
-
const out = result.stdout.toString().trim()
|
|
25
|
-
const err = result.stderr.toString().trim()
|
|
26
|
-
if (result.status !== 0) {
|
|
27
|
-
console.error(`FAIL\n${out}\n${err}`)
|
|
28
|
-
failed = true
|
|
29
|
-
} else {
|
|
30
|
-
console.log(`PASS ${out}`)
|
|
31
|
-
if (err) console.error(err)
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
process.exit(failed ? 1 : 0)
|
package/mcp-server/README.md
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
# @7h3/protocol-mcp
|
|
2
|
-
|
|
3
|
-
MCP server for [@7h3/protocol](https://github.com/IceMasterT/7h3-protocol-aip) — install into Claude to generate AIP secrets, keypairs, and server boilerplate.
|
|
4
|
-
|
|
5
|
-
## Install into Claude Code
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
claude mcp add aip -- npx @7h3/protocol-mcp
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
## Install into Claude Desktop
|
|
12
|
-
|
|
13
|
-
Add to your `claude_desktop_config.json`:
|
|
14
|
-
|
|
15
|
-
```json
|
|
16
|
-
{
|
|
17
|
-
"mcpServers": {
|
|
18
|
-
"aip": {
|
|
19
|
-
"command": "npx",
|
|
20
|
-
"args": ["@7h3/protocol-mcp"]
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
## Tools
|
|
27
|
-
|
|
28
|
-
| Tool | What it does |
|
|
29
|
-
|---|---|
|
|
30
|
-
| `aip_generate_secret` | Generates a 32-byte HMAC secret — store as `AIP_SECRET` |
|
|
31
|
-
| `aip_generate_keypair` | Generates an Ed25519 keypair — store keys as env vars |
|
|
32
|
-
| `aip_wrap_mcp_server` | Outputs ready-to-paste boilerplate for your MCP server |
|
|
33
|
-
| `aip_sign` | Signs a test envelope (debugging / fixture generation) |
|
|
34
|
-
| `aip_verify` | Verifies an envelope signature and shape |
|
|
35
|
-
|
|
36
|
-
## License
|
|
37
|
-
|
|
38
|
-
MIT
|