fix: add Content Security Policy and security headers to hosting (#578) - #762
Conversation
…hankumar0036singh#578) Web deployment shipped no CSP or hardening headers, leaving the app open to XSS and clickjacking. Adds to the hosting section of firebase.json: - CSP: default-src 'self', scripts from self only, inline styles allowed (React Native Web injects style attributes), https: for connect-src (Firestore/functions/Auth), blob/data for images, no framing, no object-src. - X-Frame-Options: DENY, X-Content-Type-Options: nosniff, Referrer-Policy: strict-origin-when-cross-origin, Permissions-Policy restricting camera/mic/geolocation. Verified: firebase.json parses as valid JSON.
📝 WalkthroughWalkthroughFirebase Hosting now serves Content Security Policy and additional browser security headers for all routes. ChangesHosting security headers
Estimated code review effort: 2 (Simple) | ~10 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@firebase.json`:
- Around line 16-17: Update the Content-Security-Policy value in firebase.json
to replace scheme-wide connect-src https: and wss: permissions with an explicit
allowlist of the Firebase, Render, EmailJS, Google, and other service origins
used by the web app. Also restrict img-src https: to approved image hosts unless
arbitrary HTTPS images are required.
- Around line 16-17: Update the Content-Security-Policy value in firebase.json
to permit the external font origins used by EventDetail.js: add
https://fonts.googleapis.com to style-src and https://fonts.gstatic.com to
font-src, or replace those external font dependencies with self-hosted assets
while preserving the existing CSP restrictions.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
| "key": "Content-Security-Policy", | ||
| "value": "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https:; font-src 'self' data:; connect-src 'self' https: wss:; frame-ancestors 'none'; object-src 'none'; base-uri 'self'; form-action 'self'" |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -e
printf '%s\n' '--- firebase.json ---'
cat -n firebase.json
printf '%s\n' '--- relevant source and configuration files ---'
git ls-files | rg '(^|/)(firebase\.json|package\.json|vite\.config\..*|next\.config\..*|angular\.json|src/|public/|\.env[^/]*)' | head -200
printf '%s\n' '--- explicit external origins and CSP-related references ---'
rg -n --hidden -g '!node_modules' -g '!dist' -g '!build' \
'https?://|wss?://|Content-Security-Policy|connect-src|img-src|script-src|frame-ancestors|firebase' . \
| head -300Repository: roshankumar0036singh/Uni-Event
Length of output: 37329
🏁 Script executed:
#!/bin/bash
set -e
python3 - <<'PY'
import json
from pathlib import Path
p = Path("firebase.json")
data = json.loads(p.read_text())
print(json.dumps(data, indent=2))
PYRepository: roshankumar0036singh/Uni-Event
Length of output: 2014
🏁 Script executed:
#!/bin/bash
set -e
printf '%s\n' '--- application configuration ---'
for f in app/package.json app/.env.example app/src/lib/config.js app/src/lib/firebaseConfig.js app/src/lib/EmailService.js app/src/lib/notificationService.js app/src/lib/usePushNotifications.js; do
if [ -f "$f" ]; then
printf '\n--- %s ---\n' "$f"
cat -n "$f"
fi
done
printf '%s\n' '--- network-related call sites and URL literals ---'
rg -n --glob '!package-lock.json' --glob '!comments.txt' --glob '!*.map' \
'(fetch\s*\(|axios|httpsCallable|connectFirestoreEmulator|connectDatabaseEmulator|connectStorageEmulator|initializeApp|storageBucket|firebaseio\.com|googleapis\.com|emailjs|wss?://|https?://)' \
app/src app/public app/.env.example cloud-functions/.env.example \
| head -400Repository: roshankumar0036singh/Uni-Event
Length of output: 38904
🏁 Script executed:
#!/bin/bash
set -e
python3 - <<'PY'
import json, re
from pathlib import Path
from urllib.parse import urlsplit
files = []
for root in ("app/src", "app/public"):
p = Path(root)
if p.exists():
files.extend(x for x in p.rglob("*") if x.is_file())
url_re = re.compile(r"""https?://[^\s"'`);,]+|wss?://[^\s"'`);,]+""")
origins = {}
for path in files:
text = path.read_text(errors="ignore")
for raw in url_re.findall(text):
u = urlsplit(raw.rstrip("]}>"))
if u.scheme and u.netloc:
origins.setdefault(f"{u.scheme}://{u.netloc}", set()).add(str(path))
for origin in sorted(origins):
print(origin)
for path in sorted(origins[origin]):
print(f" {path}")
firebase = json.loads(Path("firebase.json").read_text())
csp = next(
h["value"] for rule in firebase["hosting"]["headers"]
for h in rule["headers"] if h["key"] == "Content-Security-Policy"
)
print("\nCSP source tokens:")
for directive in csp.split(";"):
name, *sources = directive.strip().split()
if name in {"img-src", "connect-src"}:
print(f"{name}: {sources}")
PYRepository: roshankumar0036singh/Uni-Event
Length of output: 2032
🏁 Script executed:
#!/bin/bash
set -e
python3 - <<'PY'
from urllib.parse import urlsplit
def matches_scheme_source(source, url):
s = urlsplit(source)
u = urlsplit(url)
return bool(s.scheme and u.scheme.lower() == s.scheme.lower())
tests = [
("https:", "https://api.emailjs.com/api/v1.0/email/send"),
("https:", "https://attacker.example/upload"),
("https:", "http://attacker.example/upload"),
("wss:", "wss://attacker.example/socket"),
("wss:", "ws://attacker.example/socket"),
]
for source, url in tests:
print(f"{source:7} {url:55} -> {matches_scheme_source(source, url)}")
PYRepository: roshankumar0036singh/Uni-Event
Length of output: 530
Restrict CSP network sources to approved origins.
connect-src https: wss: permits requests and WebSocket connections to any HTTPS or WSS origin. Replace these scheme-wide sources with the Firebase, Render, EmailJS, Google, and other service origins required by the web app.
Keep img-src https: only if users must load images from arbitrary HTTPS hosts. Otherwise, replace it with an explicit image-host allowlist.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@firebase.json` around lines 16 - 17, Update the Content-Security-Policy value
in firebase.json to replace scheme-wide connect-src https: and wss: permissions
with an explicit allowlist of the Firebase, Render, EmailJS, Google, and other
service origins used by the web app. Also restrict img-src https: to approved
image hosts unless arbitrary HTTPS images are required.
Source: MCP tools
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n --glob '!firebase.json' --glob '!package-lock.json' \
'https?://|wss?://|<script|stylesheet|action=' .Repository: roshankumar0036singh/Uni-Event
Length of output: 26551
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- tracked configuration and web-entry candidates ---'
git ls-files | rg '(^|/)(firebase\.json|package\.json|app\.json|app\.config\.(js|ts)|index\.(html|js|tsx?)|manifest\.json|webpack|vite|metro|public/|src/)' | head -200
printf '%s\n' '--- firebase.json ---'
cat -n firebase.json
printf '%s\n' '--- app package and configuration ---'
for f in app/package.json app/app.json app/app.config.js app/app.config.ts app/index.html app/public/index.html; do
if [ -f "$f" ]; then
echo "--- $f ---"
cat -n "$f"
fi
done
printf '%s\n' '--- browser-facing source references ---'
rg -n --glob 'app/**' --glob '!app/package-lock.json' \
'firebase-messaging|importScripts|GoogleAuthProvider|signInWith|fetch\(|XMLHttpRequest|WebSocket|new URL|window\.location|location\.assign|<form|action=|`@import`|fonts\.googleapis|gstatic|accounts\.google|oauth2\.googleapis|api\.emailjs|images\.unsplash|via\.placeholder|ui-avatars|dummyimage' .Repository: roshankumar0036singh/Uni-Event
Length of output: 50387
🏁 Script executed:
#!/bin/bash
set -euo pipefail
log=/tmp/coderabbit-shell-logs/shell-output-3MW8de
printf '%s\n' '--- log beginning: tracked candidates and configuration ---'
sed -n '1,260p' "$log"
printf '%s\n' '--- tracked web files (excluding generated bundle internals) ---'
git ls-files app | rg '(^|/)(index\.html|app\.json|app\.config\.(js|ts)|package\.json|firebase-messaging-sw|manifest\.json|web-build/[^/]+$|web-build/.*/[^/]+\.html$)' | head -200
printf '%s\n' '--- firebase hosting configuration ---'
cat -n firebase.json
printf '%s\n' '--- web HTML and service-worker references ---'
for f in app/web-build/index.html app/public/firebase-messaging-sw.example.js app/web-build/firebase-messaging-sw.js; do
if [ -f "$f" ]; then
echo "--- $f ---"
cat -n "$f"
fi
done
printf '%s\n' '--- focused browser resource references (source only) ---'
rg -n --glob 'app/src/**' --glob 'app/public/**' --glob 'app/index.html' \
'fonts\.googleapis|fonts\.gstatic|gstatic|importScripts|<script|stylesheet|`@import`|fetch\(|WebSocket|window\.open|form-action|action=' .Repository: roshankumar0036singh/Uni-Event
Length of output: 20132
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- certificate and browser flow excerpts ---'
sed -n '660,700p' app/src/screens/EventDetail.js
sed -n '80,125p' app/src/screens/AuthScreen.js
sed -n '1,135p' app/src/lib/CalendarService.js
printf '%s\n' '--- deterministic CSP compatibility probe ---'
python3 - <<'PY'
import json
from pathlib import Path
from urllib.parse import urlparse
config = json.loads(Path("firebase.json").read_text())
csp = next(
h["value"]
for rule in config["hosting"]["headers"]
if rule["source"] == "**"
for h in rule["headers"]
if h["key"].lower() == "content-security-policy"
)
directives = {}
for item in csp.split(";"):
parts = item.strip().split()
if parts:
directives[parts[0]] = set(parts[1:])
def allowed(directive, url):
origin = f"{urlparse(url).scheme}://{urlparse(url).netloc}"
sources = directives[directive]
return "'self'" in sources and origin == "https://unievent.example" or origin in sources or f"{urlparse(url).scheme}:" in sources
print("style-src allows fonts.googleapis.com:", allowed("style-src", "https://fonts.googleapis.com/css2?family=Montserrat"))
print("font-src allows fonts.gstatic.com:", allowed("font-src", "https://fonts.gstatic.com/s/montserrat/example.woff2"))
print("connect-src allows Google Calendar HTTPS:", allowed("connect-src", "https://www.googleapis.com/calendar/v3"))
print("form-action allows same-origin form:", "'self'" in directives["form-action"])
print("index external script count:", sum(
"src=" in line and not 'src="/' in line
for line in Path("app/web-build/index.html").read_text().splitlines()
))
PYRepository: roshankumar0036singh/Uni-Event
Length of output: 8685
Allow the certificate flow’s external fonts, or self-host them. EventDetail.js imports https://fonts.googleapis.com and loads fonts from fonts.gstatic.com, but this CSP allows neither origin. The certificate will use fallback fonts.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@firebase.json` around lines 16 - 17, Update the Content-Security-Policy value
in firebase.json to permit the external font origins used by EventDetail.js: add
https://fonts.googleapis.com to style-src and https://fonts.gstatic.com to
font-src, or replace those external font dependencies with self-hosted assets
while preserving the existing CSP restrictions.
Source: MCP tools



Closes #578
Problem
Web deployment shipped with no CSP or hardening headers — inline XSS payloads would execute and the app could be framed by malicious sites.
Fix
Added a headers block to the hosting section of
firebase.json:default-src 'self'; scripts from self only;'unsafe-inline'for styles only (React Native Web renders inline style attributes);https:for connect-src (Firestore, functions, Firebase Auth); blob/data sources for images;frame-ancestors 'none';object-src 'none'.Verification
firebase.jsonparses as valid JSON (python3 -m json.tool).Summary by CodeRabbit