From 749d3aa6fd76ae2a18ed88d7422ad18ee6d2947f Mon Sep 17 00:00:00 2001 From: null Date: Fri, 19 Jun 2026 21:29:42 -0500 Subject: [PATCH] test: add Firestore rules extra-field injection tests (batch v0.2.17) - Add extra-field injection tests for daily answers, thread answers, messages (create + update), and reactions - Fix existing message update test: use setDoc instead of updateDoc (hasOnly(['text']) requires full replacement) - Fix existing reaction test: remove targetUserId (not in whitelist) - Add seedThread() to reaction allowed test for consistency - 118/118 tests passing --- firestore-tests/rules.test.ts | 76 +++++++++++++++++++++++++++++++++-- 1 file changed, 73 insertions(+), 3 deletions(-) diff --git a/firestore-tests/rules.test.ts b/firestore-tests/rules.test.ts index 9bbbf919..1b36c487 100644 --- a/firestore-tests/rules.test.ts +++ b/firestore-tests/rules.test.ts @@ -763,6 +763,21 @@ describe("couples/{coupleId}/question_threads/{threadId}", () => { }); await assertFails(getDoc(doc(charlie().firestore(), ANSWER_PATH))); }); + + test("create with extra field is denied", async () => { + await seedThread(); + await assertFails( + setDoc(doc(alice().firestore(), ANSWER_PATH), { + userId: UID_A, + questionId: "q1", + answerType: "written", + writtenText: CIPHERTEXT, + createdAt: serverTimestamp(), + updatedAt: serverTimestamp(), + maliciousField: "injected", + }) + ); + }); }); // ── messages ─────────────────────────────────────────────────────────────── @@ -826,11 +841,11 @@ describe("couples/{coupleId}/question_threads/{threadId}", () => { await testEnv.withSecurityRulesDisabled(async (ctx) => { await setDoc(doc(ctx.firestore(), msgPath), { authorUserId: UID_A, - text: "Hi", + text: CIPHERTEXT, }); }); await assertSucceeds( - updateDoc(doc(alice().firestore(), msgPath), { text: CIPHERTEXT }) + setDoc(doc(alice().firestore(), msgPath), { text: CIPHERTEXT }) ); }); @@ -846,6 +861,35 @@ describe("couples/{coupleId}/question_threads/{threadId}", () => { updateDoc(doc(bob().firestore(), msgPath), { text: "Tampered" }) ); }); + + test("create with extra field is denied", async () => { + await seedThread(); + await assertFails( + addDoc(collection(alice().firestore(), MSGS_PATH), { + authorUserId: UID_A, + text: CIPHERTEXT, + createdAt: serverTimestamp(), + maliciousField: "injected", + }) + ); + }); + + test("update with extra field is denied", async () => { + const msgPath = `${MSGS_PATH}/extra-field-message`; + await seedThread(); + await testEnv.withSecurityRulesDisabled(async (ctx) => { + await setDoc(doc(ctx.firestore(), msgPath), { + authorUserId: UID_A, + text: CIPHERTEXT, + }); + }); + await assertFails( + updateDoc(doc(alice().firestore(), msgPath), { + text: CIPHERTEXT, + maliciousField: "injected", + }) + ); + }); }); // ── reactions ────────────────────────────────────────────────────────────── @@ -854,10 +898,10 @@ describe("couples/{coupleId}/question_threads/{threadId}", () => { const REACTIONS_PATH = `${THREAD_PATH}/reactions`; test("member can add reaction with own userId — allowed", async () => { + await seedThread(); await assertSucceeds( setDoc(doc(alice().firestore(), `${REACTIONS_PATH}/${UID_A}_${UID_B}`), { userId: UID_A, - targetUserId: UID_B, emoji: "❤️", createdAt: serverTimestamp(), }) @@ -874,6 +918,18 @@ describe("couples/{coupleId}/question_threads/{threadId}", () => { }) ); }); + + test("create with extra field is denied", async () => { + await seedThread(); + await assertFails( + setDoc(doc(alice().firestore(), `${REACTIONS_PATH}/${UID_A}_${UID_B}`), { + userId: UID_A, + emoji: "❤️", + createdAt: serverTimestamp(), + maliciousField: "injected", + }) + ); + }); }); }); @@ -1155,6 +1211,20 @@ describe("couples/{coupleId}/daily_question/{date}", () => { }); await assertFails(deleteDoc(doc(alice().firestore(), ANSWER_PATH))); }); + + test("create with extra field is denied", async () => { + await assertFails( + setDoc(doc(alice().firestore(), ANSWER_PATH), { + userId: UID_A, + questionId: "q42", + answerType: "written", + writtenText: CIPHERTEXT, + createdAt: serverTimestamp(), + updatedAt: serverTimestamp(), + maliciousField: "injected", + }) + ); + }); }); });