-
Notifications
You must be signed in to change notification settings - Fork 141
fix(experiences): scope idempotency dedup to the author #1803
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -95,11 +95,26 @@ const interviewExperienceSchema = new mongoose.Schema( | |
| { timestamps: true }, | ||
| ); | ||
|
|
||
| // Idempotency is scoped to the author so a re-used key can never collide | ||
| // with another submitter's document: authenticated submissions dedupe by | ||
| // userId, anonymous submissions by clientKey. | ||
| interviewExperienceSchema.index( | ||
| { idempotencyKey: 1 }, | ||
| { userId: 1, idempotencyKey: 1 }, | ||
| { | ||
| unique: true, | ||
| partialFilterExpression: { | ||
| userId: { $type: "objectId" }, | ||
| idempotencyKey: { $type: "string", $gt: "" }, | ||
| }, | ||
| }, | ||
| ); | ||
|
|
||
| interviewExperienceSchema.index( | ||
| { clientKey: 1, idempotencyKey: 1 }, | ||
| { | ||
| unique: true, | ||
| partialFilterExpression: { | ||
| clientKey: { $type: "string", $gt: "" }, | ||
|
Comment on lines
101
to
+117
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win Make authenticated and anonymous ownership scopes mutually exclusive. An authenticated request can include a valid
📍 Affects 3 files
🤖 Prompt for AI Agents |
||
| idempotencyKey: { $type: "string", $gt: "" }, | ||
| }, | ||
| }, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Use the normalized idempotency key in duplicate-key recovery.
Line 57 stores the trimmed key, but line 104 queries with the raw request value. If two concurrent requests submit
" submit-key-abc12345 ", the losing request cannot find the winning record and returns HTTP 500.backend/controllers/interviewExperienceController.js#L104-L104: retain the validated, trimmed idempotency key and author filter for use in the duplicate-key branch.backend/tests/interviewExperienceController.unit.test.js#L230-L263: add a duplicate-key race test with surrounding whitespace and assert that both lookups use the trimmed key.🧰 Tools
🪛 ast-grep (0.45.1)
[error] 101-104: Untrusted HTTP request data (req.body / req.query / req.params) flows into a MongoDB/Mongoose query, enabling NoSQL injection — an attacker can supply objects like {"$gt":""} or {"$where":"..."} to bypass filters or run arbitrary JavaScript. Never pass raw request data as a query object or operator value; validate and cast each field to its expected primitive type (e.g. with a schema validator), or whitelist allowed operators before querying.
Context: InterviewExperience.findOne({
idempotencyKey: req.body.idempotencyKey,
...buildAuthorFilter(req, req.body.clientKey),
})
Note: [CWE-943] Improper Neutralization of Special Elements in Data Query Logic.
(nosql-injection-mongo-request-javascript)
📍 Affects 2 files
backend/controllers/interviewExperienceController.js#L104-L104(this comment)backend/tests/interviewExperienceController.unit.test.js#L230-L263🤖 Prompt for AI Agents