fix(security): reject unverifiable X-Cloud-Sig in cloud-sync HMAC check (#13679 PR A) - #13804
Merged
diegosouzapw merged 2 commits intoSep 16, 2026
Merged
Conversation
…ck (#13679 PR A) verifyCloudSignature() previously fell open whenever OMNIROUTE_CLOUD_SYNC_SECRET was unset: any X-Cloud-Sig header, including a forged/garbage one, was accepted unconditionally. A MITM on the CLOUD_URL channel (or a compromised/misconfigured CLOUD_URL) could send an arbitrary signature and have it accepted. Per the owner's decision on the #13679 umbrella (PR A): a PRESENT-but-unverifiable signature is now rejected outright, regardless of any flag. A new opt-in OMNIROUTE_CLOUD_SYNC_ENFORCE_SIGNATURE=true flag (default OFF) also rejects an ABSENT signature, bringing the v3.9 enforce-by-default plan forward early without breaking v3.8.x peers that haven't rotated in a shared secret yet. Regression test: tests/unit/security/cloudsync-signature-fail-open-13679.test.ts
This was referenced Sep 16, 2026
muhamadgalihsaputra
pushed a commit
to niyatna/NiyatnaRoute
that referenced
this pull request
Sep 27, 2026
…ck (diegosouzapw#13679 PR A) (diegosouzapw#13804) Merged in the 2026-09-16 sweep of the maintainer's own open PRs, at the owner's explicit instruction. No push was made to the PR branch: the merge took the head as the owning session left it (verified OPEN, non-draft and MERGEABLE against the release tip immediately before merging).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refs #13679
This is PR A of the #13679 umbrella (16 verified insecure defaults) — only item #1 (cloud-sync
HMAC fail-open). The umbrella stays open until all 6 planned PRs (A→F) land.
Not covered here (tracked in the same umbrella, separate PRs):
Root cause (short)
verifyCloudSignature()insrc/lib/cloudSync.tsfailed open wheneverOMNIROUTE_CLOUD_SYNC_SECRETwas unset: anyX-Cloud-Sigheader — including a completelyforged/garbage one — was accepted ("we can't verify, but the server is at least trying — pass
through"). A MITM on the
CLOUD_URLchannel, or a misconfigured/compromisedCLOUD_URL, couldsend an arbitrary signature value and have it accepted unconditionally, since the code never
actually checked it against anything in that branch.
Fix
Per the owner's decision on the #13679 umbrella (chat, 2026-09-15), PR A ships both sub-fixes for
v3.8.x, with the full default flip deferred to v3.9:
X-Cloud-Sigis now rejected whenever no local secret isconfigured — we have no key to verify it against, so an unverifiable signature is treated as
invalid instead of blindly trusted. This closes the "forge any signature and it's accepted"
fail-open case regardless of the flag below.
OMNIROUTE_CLOUD_SYNC_ENFORCE_SIGNATURE=trueflag brings thev3.9 enforce-by-default behavior forward early — when set, an absent
X-Cloud-Sigis alsorejected. Left OFF by default so v3.8.x peers that haven't rotated in a shared secret yet keep
working unsigned (documented in the updated code comment); the default flips to enforced in
v3.9.
Only the "no local secret configured" branch of
verifyCloudSignature()changed. The"secret configured" branch (proper HMAC-SHA256 +
timingSafeEqualcomparison) was alreadycorrect and is unchanged.
Regression test
tests/unit/security/cloudsync-signature-fail-open-13679.test.ts(new, permanent suite)RED (on unfixed
origin/release/v3.8.51code):GREEN (after the fix):
Command:
DATA_DIR=$(mktemp -d) timeout 300 node --import tsx/esm --test --test-force-exit tests/unit/security/cloudsync-signature-fail-open-13679.test.tsGates run
npx eslint --suppressions-location config/quality/eslint-suppressions.json src/lib/cloudSync.ts tests/unit/security/cloudsync-signature-fail-open-13679.test.ts→ clean, exit 0npm run typecheck:core→ clean, exit 0node scripts/check/check-file-size.mjs→ no violation onsrc/lib/cloudSync.tsnode scripts/check/check-complexity.mjs→ OKnode scripts/check/check-cognitive-complexity.mjs→ OKnode scripts/check/check-test-discovery.mjs→ new test file discoveredtests/unit/security/cloud-sync-hmac.test.ts+tests/unit/cloud-sync.test.ts→ 9/9 pass, no assertion needed changing (the pre-existing"falls through (legacy mode) when secret is unset" test only asserts the result is a boolean,
compatible with the unchanged default-off legacy pass-through path)
Existing tests aligned
None needed changing — all pre-existing
cloudSync/verifyCloudSignatureassertions alreadymatched the corrected contract (they exercise the "secret configured" branch, or only assert the
result type for the "no header, no secret" legacy case).
Deviation from the plan-file
The plan-file's own repro test asserted that BOTH a forged signature and a fully absent
X-Cloud-Sigmust be rejected under default settings. Per the owner's decision block (whichsupersedes the plan-file body), the shipped v3.8.x behavior instead keeps the absent-signature
("legacy peer") case passing by default and gates full enforcement behind
OMNIROUTE_CLOUD_SYNC_ENFORCE_SIGNATURE— the default flips in v3.9. The regression test waswritten to match this reconciled decision rather than the plan-file's original blanket-rejection
repro.