fix(js): validate reply prefix in decodeResult#1315
Conversation
`decodePayload` already verifies the Sails header (v2) or the `(service, method)` prefix (v1) before SCALE-decoding, but `decodeResult` didn't — it silently interpreted the first 16 bytes (v2) or the first two compact-length-prefixed strings (v1) without checking they matched the method being decoded. Passing headerless or mismatched reply bytes therefore produced garbage or a cryptic SCALE error rather than a clear "wrong method" signal. `decodeResult` now calls the same `_assertMatchingHeader` helper as `decodePayload` in v2, and compares the service/function name prefixes against the closed-over method identity in v1. Also documents the `sails-js/types`, `sails-js/parser`, and `sails-js/util` subpath exports in both READMEs — they were wired in `package.json` but never mentioned in the docs, so downstream tooling has been falling back to `any` casts instead of importing the `ISailsTypeDef` / `ISailsPrimitiveDef` interfaces. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request introduces validation for the reply prefix in decodeResult for both Sails (v1) and SailsProgram (v2) to ensure the data matches the expected service and method. It also documents subpath exports (sails-js/types, sails-js/parser, and sails-js/util) in the README files. Review feedback highlights that the changelog's claim regarding consistency with v1's decodePayload is inaccurate, as decodePayload and event.decode in v1 still lack similar validation. Additionally, it is noted that the sails-js/parser subpath documentation is missing from the v1 README.
Previously the only decodeResult coverage in sails-idl-v2 was the two error-path tests. Add a happy-path test that extracts a method's real 16-byte header from `encodePayload(...)`, uses it as the reply prefix, and verifies the decoded return value — this protects against the header-validation change silently breaking the valid path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When reply bytes are too short for the service/function prefix, getServiceNamePrefix / getFnNamePrefix throw from the compact SCALE codec with an opaque message. Catch that and re-throw with the same `Invalid prefix for <service>.<method> result` shape the caller already sees for mismatched prefixes, so downstream consumers only have to handle one error surface. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Addresses review comments on #1315: - v1 decodePayload (both function and constructor) and event.decode previously skipped prefix validation, so the gap was wider than the CHANGELOG originally claimed. Extract `_assertMatchingServicePrefix` and `_assertMatchingCtorPrefix` helpers and apply them to every v1 decode site for parity with v2's `_assertMatchingHeader` coverage. - Correct the CHANGELOG wording to accurately describe the fix as covering every v1/v2 decode entry point rather than claiming parity with a `decodePayload` that was also missing validation. - Document the `sails-js/parser` subpath in the v1 README with a note clarifying it's the IDL v2 parser; v1 users still import from the standalone `sails-js-parser` package. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Addressed all three review comments in
Coverage bumped to 7 tests for v1 (3 existing + 4 new validation tests across |
Two follow-ups from the adversarial review on #1315: 1. Accept Uint8Array in _assertMatchingServicePrefix. Callers previously doing `fn.decodeResult(message.payload as any)` or `event.decode(message.payload as any)` with Uint8Array worked at runtime because registry.createType accepts it. The new guard went through getServiceNamePrefix/getFnNamePrefix, which expect hex strings, so those `as any` callers regressed. Normalize via u8aToHex up front, matching the ctor helper. 2. Sanitize echoed prefix strings in error messages. The `got <service>.<fn>` text was interpolated verbatim from untrusted reply bytes, so a malformed or malicious payload could inject control characters / ANSI escapes into logs and error sinks. Truncate to 64 chars and replace non-printable bytes with "?" before echoing. Tests cover both: a Uint8Array round-trip and a control-character prefix that verifies ESC/BEL are stripped from the thrown message. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Ran Applied (commit
Deferred:
Updated coverage: v1 9/9 tests pass (2 new), v2 12/12 pass. Lint + Prettier clean. |
|
Filed the deferred |
Summary
SailsProgramdecodeResultand v1SailsdecodeResultnow validate the reply's Sails header /(service, method)prefix before SCALE-decoding, matching the existing behavior ofdecodePayload.Invalid Sails header for …/Invalid prefix for … resulterror.sails-js/types,sails-js/parser, andsails-js/utilsubpath exports in both READMEs so downstream tooling can importISailsTypeDef/ISailsPrimitiveDefinstead of falling back toanycasts.Why
decodePayload, eventdecode(), and ctordecodePayload()already validate the method prefix — see_assertMatchingHeaderatjs/src/sails-idl-v2.tsand thegetServiceNamePrefix/getFnNamePrefixchecks atjs/src/sails.ts.decodeResultwas the only public decode entry point that skipped this validation. The asymmetry is easy to miss because internal builders (e.g.QueryBuilderWithHeader) strip the prefix themselves viaresult.payload.slice(this._prefixByteLength), sodecodeResultis only hit by external consumers — which is exactly where a clear error matters most.This came up while integrating
sails-jsinto a downstream wallet / tooling stack: passing a reply payload that had already been stripped by an intermediate layer todecodeResultreturned plausible-looking but wrong values instead of throwing.Behavior change
Callers who were (deliberately or accidentally) relying on
decodeResultaccepting prefix-less bytes will now see a thrown error instead of garbage output. Given the prior behavior produced undefined/garbage results rather than anything semantically useful, this should be considered a bug fix rather than a breaking change — but flagging it explicitly for reviewers.Test plan
js/test/idl-v2-parser-type-resolver.test.tscovering the v2 error paths (bogus 16-byte prefix without magic bytes →Invalid Sails header; prefix whoseentry_idbelongs to a different method →Header mismatch).js/test/encode-decode.test.tscovering the v1 happy path and the mismatched-prefix error path.pnpm jest idl-v2-parser-type-resolver— all 11 tests passpnpm jest encode-decode— all 3 tests passpnpm lint— no issuesnpx prettier --checkon edited files — cleanNot covered here (deliberately deferred)
js/package.jsonis still at0.5.1despite thejs/v1.0.0-beta.1git tag;sails-js-parserstandalone vs thesails-js/parsersubpath naming overlap). This is a release-cadence / publishing decision rather than a code fix, and should be handled as part of the 1.0 bump rather than dragged in here.🤖 Generated with Claude Code