Skip to content

Security hardening: Firestore/Storage rules, password hashing, XSS, deps#2

Open
EdmundLimBoEn wants to merge 2 commits into
GeeksHacking:mainfrom
EdmundLimBoEn:security/harden-forum-app
Open

Security hardening: Firestore/Storage rules, password hashing, XSS, deps#2
EdmundLimBoEn wants to merge 2 commits into
GeeksHacking:mainfrom
EdmundLimBoEn:security/harden-forum-app

Conversation

@EdmundLimBoEn

Copy link
Copy Markdown

Summary

Security hardening pass on the Geek-Collab forum app. Scope is deliberately harden in place — a full server-side auth migration (Firebase Authentication) is intentionally deferred. Every finding is labeled fixed / mitigated / deferred in SECURITY_AUDIT.md.

A scan agent produced the audit; fixes were split across an auth/data-layer track and an output-sanitization track, then independently reviewed (which caught and fixed two additional issues, incl. a plaintext-cache leak).

What changed

Access control & data layer

  • Firestore + Storage rules added (firestore.rules, storage.rules, wired into firebase.json). Previously there were none — every collection (including stored passwords) was world-readable and world-writable. New rules are default-deny with per-field type/enum/length validation, a hasOnly key allowlist (rejects a plaintext password key), and http(s)-only URL checks.
  • Passwords no longer stored in plaintext. Hashed at rest with a per-user random salt (expo-crypto SHA-256). Legacy plaintext records are transparently migrated to salted hashes on read/login.
  • Destructive full-collection delete-by-diff sync removed — it was a one-call "wipe every doc not in my local list" primitive. Remote writes are now additive/targeted.
  • Admin allowlist centralized and those usernames are reserved at signup so they can't be registered by an attacker.
  • Input length caps + URL-scheme validation added in the data and create paths.

Output sanitization (web XSS)

  • User markdown is rendered via react-native-markdown-display; on web a link/image href like javascript:/data:/vbscript:/file: could execute. Added urlSafety.js and gated links and images through a scheme allowlist (http/https/mailto) at both render sites. Added maxLength caps on text inputs.

Dependencies

  • npm audit fix for non-breaking advisories.

Honest scope note

Because the app has no server-side identity, these rules provide schema validation and abuse-resistance, not true per-user authorization. Authoritative admin/ban/mute/moderation enforcement and full credential confidentiality require a server identity (Firebase Auth) and are deferred — called out in code comments and SECURITY_AUDIT.md.

Verification

  • Full app bundles cleanly from App.js (esbuild, all imports resolve).
  • Rules reviewed for default-deny and no accidental allow-all.
  • Independent review of the auth/data diff applied two follow-up fixes (plaintext-cache leak on read; hasOnly key restriction).

Scope: harden the anonymous-client forum app in place. A full server-side
auth migration (Firebase Auth) is intentionally deferred; see SECURITY_AUDIT.md
for what is fixed vs mitigated vs deferred.

Access control & data layer:
- Add firestore.rules and storage.rules (default-deny + schema/type/length
  validation, hasOnly key allowlist, http(s)-only URL checks) and wire them
  into firebase.json. Honest comments note these give validation/abuse-
  resistance, not true per-user authZ (needs server identity).
- Stop persisting plaintext passwords. Hash at rest with a per-user random
  salt (expo-crypto SHA-256). Legacy plaintext records are migrated to
  salted hashes on read/login; rules forbid a plaintext `password` key.
- Remove the destructive full-collection delete-by-diff sync (one-call wipe
  primitive); remote writes are now additive/targeted.
- Centralize the client-side admin allowlist and reserve those usernames at
  signup so they cannot be registered.
- Add input length caps / URL-scheme validation in the data + create paths.

Output sanitization (web XSS):
- Add urlSafety.js and gate markdown links/images through a scheme allowlist
  (http/https/mailto) at both render sites, neutralizing javascript:/data:/
  vbscript:/file: URLs. Add maxLength caps on text inputs.

Dependencies:
- npm audit fix for non-breaking advisories.
Replace the single-round SHA-256 password hash with PBKDF2-HMAC-SHA256
(150k iterations) via Web Crypto, which is available on the web deployment
target (react-native-web / Firebase Hosting) and Node. This sharply raises
the offline brute-force cost of any leaked hash from the world-readable
users collection. A single-round SHA-256 via expo-crypto remains only as a
labeled weak fallback where Web Crypto is unavailable.

- Hashes are self-describing ("pbkdf2$<iters>$<hex>" / "sha256$<hex>"); new
  verifyPassword() re-derives with the stored algorithm + parameters, so
  mixed runtimes and the fallback verify correctly.
- getUser uses verifyPassword instead of a raw string compare.
- Raise minimum password length 6 -> 10.
- SECURITY_AUDIT.md finding GeeksHacking#2 updated; residual (client-side verification
  vs a world-readable doc, weak fallback) noted as subsumed by the deferred
  server-auth migration.

Verified: PBKDF2 round-trips (correct accepts, wrong password/salt reject),
derive ~12ms at 150k iters; full app still bundles.
@EdmundLimBoEn

Copy link
Copy Markdown
Author

Follow-up: addressed an automated review finding on password hashing. Upgraded the password KDF from single-round SHA-256 to PBKDF2-HMAC-SHA256 (150k iterations) via Web Crypto (available on the web deployment target and Node), keeping single-round SHA-256 only as a labeled weak fallback where Web Crypto is unavailable. Hashes are now self-describing (pbkdf2$<iters>$<hex>) and verified by re-deriving with the stored parameters; getUser uses the new format-aware verifyPassword. Minimum password length raised 6 → 10. Verified the KDF round-trips (correct accepts; wrong password/salt reject; ~12ms derive) and the app still bundles. Residual — verifying against a world-readable document is inherently client-side and the fallback path stays weak — is subsumed by the deferred server-auth migration noted in SECURITY_AUDIT.md.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant