Repository navigation
feat(backend): implement payload sanitization and strict validation for Database Row-Level Security Service - #1609
Open
Conversation
…or Database Row-Level Security Service withMerchantContext previously passed merchantId straight to SET LOCAL accepting anything truthy, and the rollback path could mask the original error if ROLLBACK itself failed: - Adds a strict Zod schema for merchantId (length bounds + allowed character class) applied before any SQL runs; invalid IDs fail fast with a clear error before a connection is taken from the pool. - Sanitizes/normalizes the ID (trim + strip disallowed characters) for both the transaction and logging paths, and exposes sanitizeMerchantId for safe log enrichment. - Validates the callback is actually a function, so a bad call site fails deterministically instead of crashing mid-transaction. - Rollback is now wrapped so a rollback failure is logged rather than replacing the original error, and the client is always released. Adds unit tests covering accepted/rejected IDs, sanitization edge cases, and transaction commit/rollback/release ordering.
|
@Abidoyesimze is attempting to deploy a commit to the Emmanuel's projects Team on Vercel. A member of the Team first needs to authorize it. |
|
@Abidoyesimze Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
This branch has not been deployed
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.
Summary
Closes #1467
withMerchantContext()insrc/lib/db-rls.jspassedmerchantIdstraight intoSET LOCAL app.current_merchant_id— anything truthy was accepted, including empty-after-trim strings, 10k-character payloads and strings with spaces/semicolons. The rollback path could also mask the original error ifROLLBACKitself failed, and a non-function callback crashed mid-transaction.Changes:
merchantId: trimmed, 1–128 chars, character class[a-zA-Z0-9_\-:.@]. Invalid IDs fail fast with a clear error before a connection is taken from the pool, so bad input can't hold a pooled client while it fails.validateAndSanitizeMerchantId()— single validation/sanitization entry point returning{ valid, merchantId }or{ valid, error }.sanitizeMerchantId()— non-throwing helper for safe log enrichment (returnsnullrather than throwing on garbage input).callback(client)inside the transaction.ROLLBACKis wrapped so a rollback failure is logged rather than replacing the original error; the client is always released viafinally.Test plan
New
src/lib/db-rls.test.js(19 tests): accepted IDs (alphanumeric, UUIDs, emails, underscore/hyphen/dot/colon/at), rejected IDs (empty, whitespace-only, over 128 chars, spaces, quotes, semicolons, null/undefined/numbers/arrays), trim normalization, callback type rejection, and full transaction lifecycle —pool.connect→BEGIN→SET LOCAL→ callback →COMMIT→releaseordering, rollback-on-callback-error with release, rollback-failure not masking the original error, and connection-error propagation without leaking a client.Also passes:
src/lib/metricService.test.js(the main consumer ofwithMerchantContext, with the module mocked — no behavioural change to its contract).Full backend suite: no new failures vs.
origin/main(identical pre-existing failure count).Security notes
merchantIdbecomes the Postgresapp.current_merchant_idGUC that row-level security policies filter on. Unvalidated input here is a policy-bypass vector — a crafted ID could, depending on how policies compare the GUC, match more rows than intended, and oversized payloads were a cheap memory/CPU DoS against the pool.warnlevel only for the rejection reason — the value itself is included for operator debugging but never reaches metrics or health output.SET LOCALparameter and the value used for logging, so both paths use the same normalized ID.