security(scratchpad): add R18 Firestore probe scripts (L/D1 at-rest, premium state, quiet-hours mirror, last marker)

This commit is contained in:
null 2026-06-28 16:35:25 -05:00
parent cdf84352d6
commit d30c6c2080
4 changed files with 67 additions and 0 deletions

View File

@ -0,0 +1 @@
L_R18_pass_1782681418

28
scratchpad/msg_atrest.js Normal file
View File

@ -0,0 +1,28 @@
// Pass L / D1: read latest messages in couples/{COUPLE}/conversations/main/messages.
// Print ONLY metadata + enc-status (never decrypted/plaintext content). Also assert a plaintext
// marker does NOT appear anywhere (at-rest leak check).
const admin = require('firebase-admin')
const path = require('path')
const fs = require('fs')
const SA = path.join(__dirname, '..', 'closer-app-22014-firebase-adminsdk-fbsvc-ed20bf6003.json')
const COUPLE = process.env.COUPLE_ID || 'Xal3Kw3gjSdn0niERYKJ'
const MARKER = (fs.existsSync(path.join(__dirname,'last_marker.txt')) ? fs.readFileSync(path.join(__dirname,'last_marker.txt'),'utf8').trim() : '')
admin.initializeApp({ credential: admin.credential.cert(require(SA)) })
const db = admin.firestore()
const CIPHER = /^(enc:v1:|sealed:v1:)/
const enc = v => typeof v==='string' ? (CIPHER.test(v)?`enc✓(${v.length})`:`RAW?(${v.length})`) : typeof v
;(async () => {
const col = db.collection('couples').doc(COUPLE).collection('conversations').doc('main').collection('messages')
const snap = await col.orderBy('createdAt','desc').limit(6).get()
console.log(`marker=${MARKER||'(none)'} latest ${snap.size} messages (newest first):`)
let leak = false
snap.forEach(d => {
const m = d.data()
const ts = m.createdAt && m.createdAt.toDate ? m.createdAt.toDate().toISOString() : m.createdAt
console.log(` ${ts} sender=${(m.senderId||'').slice(0,6)}… text=${enc(m.text)}`)
// leak check across the whole doc JSON
if (MARKER) { const blob = JSON.stringify(m); if (blob.includes(MARKER)) { leak = true; console.log(' ❌❌ PLAINTEXT MARKER LEAK in this doc') } }
})
if (MARKER) console.log(leak ? '=== LEAK: marker found in plaintext ===' : '=== OK: marker NOT present in plaintext (encrypted or not yet sent) ===')
process.exit(0)
})().catch(e=>{console.error('FATAL',e.message);process.exit(1)})

View File

@ -0,0 +1,26 @@
const admin = require('firebase-admin')
const path = require('path')
const SA = path.join(__dirname, '..', 'closer-app-22014-firebase-adminsdk-fbsvc-ed20bf6003.json')
const COUPLE = process.env.COUPLE_ID || 'Xal3Kw3gjSdn0niERYKJ'
admin.initializeApp({ credential: admin.credential.cert(require(SA)) })
const db = admin.firestore()
function shallow(v){ if(typeof v==='string') return v.length>40?`str(${v.length})`:v; if(Array.isArray(v)) return `[${v.length}]`; if(v&&typeof v==='object') return '{'+Object.keys(v).join(',')+'}'; return v }
;(async () => {
const c = await db.collection('couples').doc(COUPLE).get()
const cd = c.data() || {}
console.log('=== couple top-level keys ===')
for (const k of Object.keys(cd)) console.log(` ${k} = ${shallow(cd[k])}`)
// find uid-like values
const uids = new Set()
for (const [k,v] of Object.entries(cd)) {
if (typeof v==='string' && /^[A-Za-z0-9]{20,}$/.test(v)) uids.add(v)
if (Array.isArray(v)) v.forEach(x=>{ if(typeof x==='string'&&/^[A-Za-z0-9]{20,}$/.test(x)) uids.add(x) })
if (v&&typeof v==='object'&&!Array.isArray(v)) Object.keys(v).forEach(x=>{ if(/^[A-Za-z0-9]{20,}$/.test(x)) uids.add(x) })
}
console.log('=== candidate member uids ===', [...uids].map(u=>u.slice(0,10)+'…'))
for (const uid of uids) {
const ent = await db.collection('users').doc(uid).collection('entitlements').doc('premium').get()
console.log(` ${uid.slice(0,10)}… entitlements/premium:`, ent.exists?JSON.stringify(ent.data()):'MISSING(free)')
}
process.exit(0)
})().catch(e=>{console.error('FATAL',e.message);process.exit(1)})

12
scratchpad/qh_mirror.js Normal file
View File

@ -0,0 +1,12 @@
const admin = require('firebase-admin'); const path=require('path')
admin.initializeApp({credential:admin.credential.cert(require(path.join(__dirname,'..','closer-app-22014-firebase-adminsdk-fbsvc-ed20bf6003.json')))})
const db=admin.firestore()
const QA='Y05AKO2IlTPMa0JQW1BiNIM0uzK2', SAM='imDjjOTTQv'
;(async()=>{
for(const uid of [QA]){
const d=await db.collection('users').doc(uid).get(); const x=d.data()||{}
const keys=Object.keys(x).filter(k=>/quiet|timezone|tz|notif/i.test(k))
console.log(uid.slice(0,8)+'… quiet/tz keys:', keys.length?JSON.stringify(Object.fromEntries(keys.map(k=>[k,x[k]]))):'(none present)')
}
process.exit(0)
})().catch(e=>{console.error(e.message);process.exit(1)})