Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,26 @@ type on a path that skips it), or relaxing the `vti-didcomm-js` floor below
outstanding?" A message can be both, which is why the drain path bypasses the
dedup check.

**A second `vti-didcomm-js` floor, for an unrelated reason: `^0.10.0` is a
correctness constraint too.** Since the Rev 3 cutover the wallet reads
long-framed TSP replies — spec Rev 3 widened the `-E` count to cover the
ciphertext, so any TSP message past ~12 KB is framed with the six-byte long
count code and its qb64 text starts `--E`, not `-E`. Through 0.8.x the inbound
demux (`_onFrame` in `mediator-transport.js`) classified TSP by
`text.startsWith("-E")`, so a large reply was handed to the DIDComm unpacker,
thrown out as a poison frame, and — because the throw returns before the ack —
**never acked**, so the mediator redelivered it on every reconnect forever while
the waiter timed out. This is not hypothetical: a `keys/list` with no context
filter returns a full page of records, crosses the threshold, and was silently
lost (the VTA logged the reply sent; the wallet saw nothing). 0.10.0 moved the
test into `isTspFrameText`, which matches both `-E` and `--E`. Below it, every
TSP reply over ~12 KB vanishes. `tests/did.egress-socket.mjs`'s control was
rewritten for the same release's *other* change — 0.9+ runs the resolver's own
`net-guard` on `did:webvh` resolution by default, so a bare `vtiResolve(did, {})`
is refused before it dials; the control now relaxes the dependency's policy to
keep proving the fixture is reachable, and the production path (guard first, then
`vtiResolve(did, {})`) is unchanged.

## Every outbound Trust-Task document is signed (SPEC §7.2 item 7a)

The VTA enforces the four checks a Trust Task specification declares for
Expand Down
16 changes: 8 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@
"@cfworker/json-schema": "^4.1.1",
"@noble/curves": "^2.4.0",
"@openvtc/trust-tasks": "^0.19.5",
"@openvtc/vti-didcomm-js": "^0.8.0",
"@openvtc/vti-didcomm-js": "^0.10.0",
"@openvtc/vti-tsp-js": "^0.3.0",
"@scure/base": "^2.2.0",
"cbor-x": "^1.6.6"
Expand Down
33 changes: 23 additions & 10 deletions packages/core/tests/did.egress-socket.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,12 @@
//
// ── Why these spellings and not the headline ones ───────────────────────────
//
// The vectors below are the ones where **this repo's guard is the only thing
// that can refuse them**, which is the whole point of the file.
// The vectors below are the ones where **this repo's guard is provably the
// thing exercised at the socket**, which is the whole point of the file. (Since
// `@openvtc/vti-didcomm-js` 0.9 the resolver's own net-guard also refuses them
// by default — defense in depth — so this repo's guard is no longer the *only*
// refusal; the control below relaxes the dependency's policy to keep proving
// the fixture is reachable at all.)
//
// `didwebvh-ts@2.8.0` — the resolver under `@openvtc/vti-didcomm-js` — has its
// own `isIPAddress()` check that throws "IP addresses are not allowed as hosts"
Expand Down Expand Up @@ -160,13 +164,22 @@ test("deriveSigningKeyId opens no socket either — the second entry point", asy
test("the resolver does dial the listener when the guard is not in front of it", async (t) => {
// The control the two tests above are worth nothing without.
//
// It calls the resolver the way `verifyDid` would — same DID, same default
// resolver, same real `fetch` — but without going through `verifyDid`, so the
// ONE difference between this and the tests above is
// `assertResolvableWebvhHost`. A connection arriving here says the listener
// counts, the port is reachable, and the DID does derive to it; an empty
// count above therefore means refused, and not "this fixture never pointed
// anywhere".
// It calls the resolver with the same DID and the same real `fetch`, but
// without going through `verifyDid`. What it isolates is `verifyDid`'s guard,
// `assertResolvableWebvhHost` — a connection arriving here says the listener
// counts, the port is reachable, and the DID does derive to it, so an empty
// count above means refused and not "this fixture never pointed anywhere".
//
// Since `@openvtc/vti-didcomm-js` 0.9 the resolver applies its OWN net-guard
// to did:webvh resolution by default, so a bare `vtiResolve(did, {})` is now
// refused before it dials — defense in depth with this repo's guard, and the
// reason a no-policy call here would prove nothing but the dependency's
// refusal. To reach the socket the control relaxes *the dependency's* policy
// with `allowInsecure` + `allowPrivate` (both, since 0.8 made them
// independent — `allowPrivate` alone keeps the `https:` requirement and a
// loopback listener speaks neither TLS nor a public scheme). The real tests
// need no such relaxation: their production `vtiResolve(did, {})` is exactly
// what ships, and `assertResolvableWebvhHost` refuses first regardless.
t.mock.method(console, "error", () => {});
connections = [];

Expand All @@ -175,7 +188,7 @@ test("the resolver does dial the listener when the guard is not in front of it",
// resolver answers something else.
const did = dialsListener("0x7f000001");
await assert.rejects(
() => vtiResolve(did, {}),
() => vtiResolve(did, { netPolicy: { allowInsecure: true, allowPrivate: true } }),
// Whatever the reset surfaces as. The claim is about the socket, not this.
() => true,
);
Expand Down