Skip to content

harden: float-syntax refusal, strict unknown-field reject, CSV/syslog escaping, uniform hex normalization#10

Merged
EulBite merged 1 commit into
mainfrom
harden/bughunt-2026-06-01
Jun 1, 2026
Merged

harden: float-syntax refusal, strict unknown-field reject, CSV/syslog escaping, uniform hex normalization#10
EulBite merged 1 commit into
mainfrom
harden/bughunt-2026-06-01

Conversation

@EulBite

@EulBite EulBite commented Jun 1, 2026

Copy link
Copy Markdown
Owner

Summary

Second adversarial bug-hunt of the public verifier. The core cryptographic
contract held up: no reachable strict false-accept, no panic on hostile
input, canonical form idempotent and injective on broad random sampling.
This PR fixes the low-severity hardening items that the hunt surfaced. Each
was reproduced with an executable proof-of-concept before being fixed.

Fixes

canonical: refuse float-syntax numbers (cross-language parity).
serde_json's decimal-to-f64 rounding diverges from a JavaScript
JSON.parse for inputs near 2^53: the token 9007199254740991.0 parses to
9007199254740990.0 under serde_json but to 9007199254740991 under V8.
The original token is gone by canonicalization time, so the value a JS
verifier would compute cannot be reproduced, and emitting the mis-rounded
value would silently break the byte-for-byte cross-language contract.
Float-syntax numbers (including integer-valued floats like 2.0 and -0)
are now refused; integer-syntax numbers up to 2^53-1 are unaffected.
Producers already encode amounts as strings, so the demo is unchanged.

verify_demo (strict): reject unrecognized record keys.
serde silently drops a key that is not a WalEntry field or alias, so a
flipped, renamed, or injected field name could ride along on an
otherwise-valid record while it still verified Valid. The strict profile
now rejects such records (RejectedReason::UnknownField). The lenient
profile keeps tolerating unknown fields for heterogeneous producers.

core: uniform hex-anchor normalization across surfaces.
The pinned pubkey and expected root are now normalized identically (trim,
strip an optional 0x/0X, lowercase) in spine-core, so the CLI, the
wasm playground facade, and a direct core caller accept the same inputs.
Previously a 0x-prefixed or whitespace-padded anchor verified on the CLI
but errored in the browser. A root that normalizes to empty is treated as
"no anchor", matching the facade.

export (CSV): neutralize spreadsheet formula injection.
A CSV cell beginning with =, +, -, @, tab, or CR is interpreted as a
formula by Excel/LibreOffice/Sheets. Attacker-controlled event_type /
source (and a negative timestamp_ns) are now quote-prefixed per the
OWASP guidance.

export (syslog): escape Unicode line separators.
U+0085 (NEL), U+2028 (LINE SEPARATOR) and U+2029 (PARAGRAPH SEPARATOR) are
line breaks outside the C0 range; a Unicode-newline-aware SIEM could split
on them. They are now escaped in the quoted MSG body alongside CR/LF.

Validation

  • cargo test --workspace --all-features: green (added regression tests for
    every fix above).
  • cargo clippy --workspace --all-targets --all-features -- -D warnings: clean.
  • cargo fmt --all --check: clean.
  • demo-seeder (seed 42) self-verifies; chain root c36bd135... unchanged.
  • wasm-pack build + Node integration: all checks pass.

Contract note

Two items tighten the input contract (refuse float-syntax numbers; strict
rejects unknown keys), consistent with the prior tightening that refused
integers >= 2^53. The integer_valued_float cross-language vector is
removed.

…/syslog escaping, uniform hex-anchor normalization

Second adversarial bug-hunt pass. Each item was reproduced with an
executable PoC before fixing; the full test suite, clippy -D warnings,
fmt, demo-seeder (seed 42), wasm-pack build and the Node integration
all pass, and the demo chain root is unchanged.

canonical: refuse every float-syntax JSON number, including
integer-valued floats such as 2.0 and -0. serde_json's decimal->f64
rounding diverges from a JavaScript JSON.parse near 2^53 (the token
9007199254740991.0 parses to 9007199254740990.0 under serde_json but to
9007199254740991 under V8), and the original token is gone by the time
canonicalization runs, so the value a JS verifier would compute cannot be
reproduced. Integer-syntax numbers up to 2^53-1 are unaffected. Removes
the integer_valued_float cross-language vector.

verify_demo (strict): reject any record key that is not a recognized
field or alias, so a flipped, renamed, or injected field name can no
longer ride along on an otherwise-valid record. The lenient profile keeps
tolerating unknown fields for heterogeneous producers.

verify_demo / verify / lib: normalize every caller-supplied hex anchor
(pinned pubkey and expected root) identically across surfaces - trim,
strip an optional 0x/0X prefix, lowercase - so the CLI, the wasm facade
and a direct core caller accept the same inputs. A root that normalizes
to empty is treated as "no anchor", matching the facade.

export (CSV): prefix any cell beginning with = + - @ tab or CR with a
single quote to neutralize spreadsheet formula injection from
attacker-controlled event_type/source.

export (syslog): escape U+0085, U+2028 and U+2029 in the quoted MSG body
so a Unicode-newline-aware SIEM cannot be tricked into splitting a record.
@EulBite EulBite merged commit 1402285 into main Jun 1, 2026
3 checks passed
@EulBite EulBite deleted the harden/bughunt-2026-06-01 branch June 1, 2026 06:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant