@7h3/protocol 0.1.2 → 0.4.0
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/.dockerignore +19 -0
- package/.github/dependabot.yml +32 -0
- package/.github/workflows/ci.yml +31 -0
- package/.github/workflows/publish.yml +59 -0
- package/.github/workflows/scorecard.yml +37 -0
- package/7h3.example.yaml +125 -0
- package/CHANGELOG.md +92 -0
- package/CONTRIBUTING.md +82 -0
- package/Dockerfile +73 -0
- package/GOVERNANCE.md +62 -0
- package/README.md +323 -6
- package/SECURITY.md +70 -0
- package/bench-results/replay-cache-full-1777891033256.json +10 -0
- package/bench-results/replay-cache-full-1777896317488.json +10 -0
- package/bench-results/replay-cache-full-1777900993184.json +10 -0
- package/bench-results/replay-cache-full-1777901019285.json +10 -0
- package/bench-results/replay-cache-quick-1777870170126.json +10 -0
- package/bench-results/signature-profiles-quick-1775875160079.json +85 -0
- package/bench-results/signature-profiles-quick-1775983539716.json +85 -0
- package/bench-results/signature-profiles-quick-1776237913190.json +85 -0
- package/bench-results/wire-codecs-full-1777891019803.json +93 -0
- package/bench-results/wire-codecs-full-1777896260964.json +93 -0
- package/bench-results/wire-codecs-full-1777901004247.json +93 -0
- package/bench-results/wire-codecs-quick-1775972879056.json +93 -0
- package/bench-results/wire-codecs-quick-1775983541111.json +93 -0
- package/bench-results/wire-codecs-quick-1776237914299.json +93 -0
- package/bench-results/wire-codecs-quick-1777841285236.json +93 -0
- package/bench-results/wire-codecs-quick-1777841321772.json +93 -0
- package/bench-results/wire-codecs-quick-1777841330408.json +93 -0
- package/bench-results/wire-codecs-quick-1777852886082.json +93 -0
- package/bench-results/wire-codecs-quick-1777852988773.json +93 -0
- package/bench-results/wire-codecs-quick-1777870188095.json +93 -0
- package/bench-results/wire-codecs-quick-1777870263918.json +93 -0
- package/bench-results/wire-codecs-quick-1777870455034.json +93 -0
- package/bench-results/wire-codecs-quick-1778816163081.json +93 -0
- package/bench-results/wire-codecs-quick-1778843936130.json +93 -0
- package/bin/7h3.ts +385 -0
- package/conformance/7h3_v0_1.json +77 -0
- package/conformance/7h3_v0_1_binary.json +20 -0
- package/conformance/aip_v0_1_binary.json +20 -0
- package/docker-compose.yaml +77 -0
- package/docs/ADOPTION_PLAN.md +120 -0
- package/docs/AGENTS.md +77 -0
- package/docs/AIP_RFC_v0.1.md +97 -0
- package/docs/AI_DECISION_CARD.md +122 -0
- package/docs/AI_RUNTIME_POLICY.json +126 -0
- package/docs/AI_RUNTIME_POLICY.yaml +110 -0
- package/docs/BACKPRESSURE_TUNING.md +65 -0
- package/docs/BENCHMARK_CLAIM_MATRIX.md +42 -0
- package/docs/BENCHMARK_REPORT_TEMPLATE.md +169 -0
- package/docs/BINARY_CODEC_BENCH.md +23 -0
- package/docs/CLEAN_CLONE_RUNBOOK.md +36 -0
- package/docs/CLOCK_SKEW_POLICY.md +30 -0
- package/docs/DISTRIBUTED_REPLAY.md +142 -0
- package/docs/FUZZ_CAMPAIGN.md +121 -0
- package/docs/GATEWAY.md +195 -0
- package/docs/KEY_MANAGEMENT_POLICY.md +53 -0
- package/docs/KEY_REVOCATION.md +69 -0
- package/docs/MCP_WRAPPER.md +159 -0
- package/docs/MIGRATION_GUIDE.md +40 -0
- package/docs/OPERATORS.md +184 -0
- package/docs/PERF_REGRESSION_POLICY.md +34 -0
- package/docs/PROJECT_EXAMINATION_2026-05-31.md +219 -0
- package/docs/RELEASE_BENCHMARK_REPORT_2026-05-15.md +135 -0
- package/docs/RELEASE_GATE.md +25 -0
- package/docs/RELEASE_NOTES_v0.1.0.md +54 -0
- package/docs/SECURITY_REVIEW_2026-06-05.md +165 -0
- package/docs/TELEMETRY.md +41 -0
- package/docs/THREAT_MODEL.md +89 -0
- package/docs/VERSIONING_POLICY.md +30 -0
- package/docs/assets/banner.png +0 -0
- package/eslint.config.js +15 -0
- package/fuzz/ts/harness-decode.ts +136 -0
- package/fuzz/ts/harness-verify.ts +121 -0
- package/fuzz/ts/run.ts +35 -0
- package/mcp-server/README.md +38 -0
- package/mcp-server/package-lock.json +1187 -0
- package/mcp-server/package.json +35 -0
- package/mcp-server/src/index.ts +236 -0
- package/mcp-server/tsconfig.json +14 -0
- package/package.json +79 -13
- package/scripts/aip-framework-quickstart.ts +110 -0
- package/scripts/aip-mcp-gateway.ts +38 -0
- package/scripts/aip-mcp-wrap-demo.ts +72 -0
- package/scripts/aip-quickstart.ts +60 -0
- package/scripts/bench-diff.ts +118 -0
- package/scripts/bench-protocol-e2e.ts +937 -0
- package/scripts/bench-protocol-openloop.ts +1397 -0
- package/scripts/bench-replay-cache.ts +76 -0
- package/scripts/bench-signature-profiles.ts +180 -0
- package/scripts/bench-wire-codecs.ts +161 -0
- package/scripts/build-binary-conformance.ts +36 -0
- package/scripts/build-release-dashboard.ts +175 -0
- package/scripts/canary-rollout.ts +38 -0
- package/scripts/mcpGatewayCli.test.ts +116 -0
- package/scripts/prepare-aip-package.ts +88 -0
- package/scripts/regen-conformance-sigs.ts +18 -0
- package/scripts/release-gate.ts +19 -0
- package/scripts/validate-runtime-policy.ts +18 -0
- package/sdk/browser/index.test.ts +162 -0
- package/sdk/browser/index.ts +257 -0
- package/sdk/browser/package.json +13 -0
- package/sdk/go/go.mod +3 -0
- package/sdk/go/http.go +135 -0
- package/sdk/go/protocol.go +324 -0
- package/sdk/go/protocol_test.go +334 -0
- package/sdk/go/webhook.go +136 -0
- package/sdk/python/README.md +18 -0
- package/sdk/python/protocol_7h3/__init__.py +46 -0
- package/sdk/python/protocol_7h3/http.py +212 -0
- package/sdk/python/protocol_7h3/keys.py +149 -0
- package/sdk/python/protocol_7h3/protocol.py +525 -0
- package/sdk/python/protocol_7h3/queue.py +118 -0
- package/sdk/python/protocol_7h3/webhook.py +116 -0
- package/sdk/python/pyproject.toml +40 -0
- package/sdk/python/tests/test_conformance.py +110 -0
- package/sdk/python/tests/test_http.py +305 -0
- package/sdk/python/tests/test_keys.py +417 -0
- package/sdk/python/tests/test_queue.py +120 -0
- package/sdk/python/tests/test_webhook.py +345 -0
- package/sdk/rust/Cargo.lock +371 -0
- package/sdk/rust/Cargo.toml +25 -0
- package/sdk/rust/README.md +31 -0
- package/sdk/rust/fuzz/Cargo.toml +29 -0
- package/sdk/rust/fuzz/fuzz_targets/fuzz_canonicalize.rs +46 -0
- package/sdk/rust/fuzz/fuzz_targets/fuzz_decode.rs +11 -0
- package/sdk/rust/src/bin/aip_mcp_gateway.rs +59 -0
- package/sdk/rust/src/http.rs +145 -0
- package/sdk/rust/src/keys.rs +161 -0
- package/sdk/rust/src/lib.rs +688 -0
- package/sdk/rust/src/queue.rs +79 -0
- package/sdk/rust/src/webhook.rs +86 -0
- package/sdk/rust/tests/conformance.rs +148 -0
- package/sdk/rust/tests/gateway.rs +130 -0
- package/sdk/rust/tests/http_webhook_queue.rs +201 -0
- package/sdk/rust/tests/keys.rs +189 -0
- package/src/agentAdapter.test.ts +48 -0
- package/src/agentAdapter.ts +56 -0
- package/src/auditLog.test.ts +145 -0
- package/src/auditLog.ts +147 -0
- package/src/conformance.test.ts +136 -0
- package/src/conformanceVectors.ts +99 -0
- package/src/frameworkAdapters.test.ts +290 -0
- package/src/frameworkAdapters.ts +261 -0
- package/src/gateway.test.ts +343 -0
- package/src/gateway.ts +171 -0
- package/src/grpcBinding.test.ts +211 -0
- package/src/grpcBinding.ts +103 -0
- package/src/httpBinding.test.ts +376 -0
- package/src/httpBinding.ts +163 -0
- package/src/index.ts +32 -0
- package/src/keyInfra.test.ts +278 -0
- package/src/keyInfra.ts +228 -0
- package/src/keyRegistry.ts +59 -0
- package/src/keyRotation.test.ts +78 -0
- package/src/keyRotation.ts +72 -0
- package/src/mcpGateway.test.ts +129 -0
- package/src/mcpGateway.ts +250 -0
- package/src/mcpTransports.test.ts +92 -0
- package/src/mcpTransports.ts +169 -0
- package/src/mcpWrapper.test.ts +179 -0
- package/src/mcpWrapper.ts +206 -0
- package/src/policyEnforcer.test.ts +99 -0
- package/src/policyEnforcer.ts +169 -0
- package/src/policyTelemetryFeedback.test.ts +25 -0
- package/src/policyTelemetryFeedback.ts +38 -0
- package/src/protocol.bench.ts +37 -0
- package/src/protocol.test.ts +155 -0
- package/src/protocol.ts +413 -0
- package/src/protocolAgent.test.ts +105 -0
- package/src/protocolAgent.ts +169 -0
- package/src/protocolBinary.test.ts +165 -0
- package/src/protocolBinary.ts +312 -0
- package/src/protocolCapabilities.ts +70 -0
- package/src/protocolFuzz.advanced.test.ts +235 -0
- package/src/protocolFuzz.test.ts +111 -0
- package/src/protocolNegative.test.ts +97 -0
- package/src/protocolReplay.test.ts +71 -0
- package/src/protocolReplay.ts +194 -0
- package/src/protocolTransport.test.ts +556 -0
- package/src/protocolTransport.ts +483 -0
- package/src/queueBinding.test.ts +130 -0
- package/src/queueBinding.ts +102 -0
- package/src/rateLimiter.test.ts +96 -0
- package/src/rateLimiter.ts +46 -0
- package/src/redisClient.ts +140 -0
- package/src/redisIntegration.test.ts +134 -0
- package/src/replayStores.test.ts +141 -0
- package/src/replayStores.ts +82 -0
- package/src/revocation.test.ts +98 -0
- package/src/revocation.ts +0 -0
- package/src/routePolicy.test.ts +87 -0
- package/src/routePolicy.ts +72 -0
- package/src/runtimePolicy.test.ts +49 -0
- package/src/runtimePolicy.ts +81 -0
- package/src/runtimePolicyManager.test.ts +29 -0
- package/src/runtimePolicyManager.ts +50 -0
- package/src/runtimePolicyPresets.ts +43 -0
- package/src/signedResponse.test.ts +111 -0
- package/src/signedResponse.ts +83 -0
- package/src/webhookBinding.test.ts +144 -0
- package/src/webhookBinding.ts +115 -0
- package/src/wsBinding.test.ts +221 -0
- package/src/wsBinding.ts +100 -0
- package/tsconfig.json +15 -0
- package/tsconfig.lib.json +23 -0
- package/vite.lib.config.ts +16 -0
- package/agentAdapter.d.ts +0 -26
- package/conformanceVectors.d.ts +0 -20
- package/frameworkAdapters.d.ts +0 -72
- package/index.d.ts +0 -20
- package/index.js +0 -1702
- package/keyRotation.d.ts +0 -20
- package/mcpGateway.d.ts +0 -37
- package/mcpTransports.d.ts +0 -62
- package/mcpWrapper.d.ts +0 -83
- package/policyEnforcer.d.ts +0 -50
- package/policyTelemetryFeedback.d.ts +0 -11
- package/protocol.d.ts +0 -66
- package/protocolAgent.d.ts +0 -58
- package/protocolBinary.d.ts +0 -8
- package/protocolCapabilities.d.ts +0 -24
- package/protocolReplay.d.ts +0 -35
- package/protocolTransport.d.ts +0 -73
- package/redisClient.d.ts +0 -49
- package/replayStores.d.ts +0 -32
- package/revocation.d.ts +0 -71
- package/runtimePolicy.d.ts +0 -24
- package/runtimePolicyManager.d.ts +0 -15
- package/runtimePolicyPresets.d.ts +0 -11
|
@@ -0,0 +1,136 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
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
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
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)
|
|
@@ -0,0 +1,38 @@
|
|
|
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
|