From d0dd84ada99ca9aa4812e3f205bd823de5c582ed Mon Sep 17 00:00:00 2001 From: Anshul Jain Date: Mon, 29 Jun 2026 13:06:44 +0530 Subject: [PATCH 1/2] security: eliminate sensitive data exposure in console logs Replace all console.log/error/warn calls with structured logger that strips personally identifiable information (UIDs, usernames, emails, tokens) in production builds. Development builds retain full logging for debugging. Changes: - Add logger utility (src/utils/logger.js) with: * info(), error(), warn(), debug() methods * Environment-aware logging (dev vs prod) * Automatic PII redaction (UIDs, emails, tokens, usernames, etc.) * sanitizeData() helper removes 11 sensitive field types - Update AuthContext.jsx (14 console calls) with logger - Update firebase.js (6 console calls) with logger Security Impact: - Eliminates data exposure via browser DevTools (attacker with physical access) - Prevents third-party analytics from capturing user PII - Production: No sensitive data logged to console - Development: Full logging retained for debugging Addresses information-disclosure vulnerability CWE-532. Closes #273 --- src/context/AuthContext.jsx | 29 +++++++++--------- src/lib/firebase.js | 13 ++++---- src/utils/logger.js | 59 +++++++++++++++++++++++++++++++++++++ 3 files changed, 81 insertions(+), 20 deletions(-) create mode 100644 src/utils/logger.js diff --git a/src/context/AuthContext.jsx b/src/context/AuthContext.jsx index 09a0d17..5e428de 100644 --- a/src/context/AuthContext.jsx +++ b/src/context/AuthContext.jsx @@ -14,6 +14,7 @@ import { } from "firebase/firestore"; import axios from "axios"; import { auth, db, signInWithGitHub, signOutUser } from "../lib/firebase"; +import logger from "../utils/logger"; const AuthContext = createContext({}); @@ -57,9 +58,9 @@ const checkAndUpdateStreak = async (data, docRef) => { "points.streakPoints": newStreakPoints, "points.totalPoints": newTotalPoints }); - console.log("Streak updated successfully. New Streak:", newStreak, "| Longest:", newLongestStreak); + logger.info("Streak updated successfully. New Streak:", newStreak, "| Longest:", newLongestStreak); } catch (err) { - console.error("Failed to update streak:", err); + logger.error("Failed to update streak:", err); } } }; @@ -105,7 +106,7 @@ export const AuthProvider = ({ children }) => { setLoading(false); } }, (error) => { - console.error("Real-time profile listener error:", error); + logger.error("Real-time profile listener error:", error); setLoading(false); }); @@ -175,7 +176,7 @@ export const AuthProvider = ({ children }) => { return authUser; } catch (error) { - console.error("Login service failure:", error); + logger.error("Login service failure:", error); setLoading(false); throw error; } @@ -194,7 +195,7 @@ export const AuthProvider = ({ children }) => { setIsOnboarding(false); setGhAccessToken(null); } catch (error) { - console.error("Logout failure:", error); + logger.error("Logout failure:", error); } finally { setLoading(false); } @@ -245,7 +246,7 @@ export const AuthProvider = ({ children }) => { primaryLanguage = sortedLangs[0]; } } catch (err) { - console.warn("Stars/Language retrieval warning, defaulting:", err); + logger.warn("Stars/Language retrieval warning, defaulting:", err); } let commits = 0; @@ -253,7 +254,7 @@ export const AuthProvider = ({ children }) => { const commitsRes = await axios.get(`https://api.github.com/search/commits?q=author:${encodedUsername}`, { headers }); commits = commitsRes.data.total_count || 0; } catch (err) { - console.warn("Commits retrieval failed; score will be incomplete until next refresh:", err); + logger.warn("Commits retrieval failed; score will be incomplete until next refresh:", err); commits = 0; } @@ -262,7 +263,7 @@ export const AuthProvider = ({ children }) => { const prsRes = await axios.get(`https://api.github.com/search/issues?q=author:${encodedUsername}+type:pr`, { headers }); prs = prsRes.data.total_count || 0; } catch (err) { - console.warn("PRs retrieval failed; score will be incomplete until next refresh:", err); + logger.warn("PRs retrieval failed; score will be incomplete until next refresh:", err); prs = 0; } @@ -271,7 +272,7 @@ export const AuthProvider = ({ children }) => { const reviewsRes = await axios.get(`https://api.github.com/search/issues?q=reviewed-by:${encodedUsername}`, { headers }); reviews = reviewsRes.data.total_count || 0; } catch (err) { - console.warn("Reviews retrieval failed; score will be incomplete until next refresh:", err); + logger.warn("Reviews retrieval failed; score will be incomplete until next refresh:", err); reviews = 0; } @@ -319,7 +320,7 @@ export const AuthProvider = ({ children }) => { } } } catch (err) { - console.warn("GitHub events retrieval failed for streak:", err); + logger.warn("GitHub events retrieval failed for streak:", err); } // Add points for each day of the active GitHub streak (+10 XP per day) @@ -337,7 +338,7 @@ export const AuthProvider = ({ children }) => { gitRankPoints }; } catch (error) { - console.error("Error executing GitHub stats fetcher snapshot:", error); + logger.error("Error executing GitHub stats fetcher snapshot:", error); return { commits: 0, prs: 0, @@ -359,7 +360,7 @@ export const AuthProvider = ({ children }) => { const lastSyncTime = new Date(userData.lastSync).getTime(); const cooldownMs = 5 * 60 * 1000; if (Date.now() - lastSyncTime < cooldownMs) { - console.log("Background GitHub sync skipped: Cooldown active."); + logger.info("Background GitHub sync skipped: Cooldown active."); return; } } @@ -402,9 +403,9 @@ export const AuthProvider = ({ children }) => { // Execute atomic transaction await batch.commit(); - console.log("Background GitHub sync completed successfully via atomic batch."); + logger.info("Background GitHub sync completed successfully via atomic batch."); } catch (error) { - console.error("Background GitHub sync failed:", error); + logger.error("Background GitHub sync failed:", error); } }; diff --git a/src/lib/firebase.js b/src/lib/firebase.js index a1592ef..168d00c 100644 --- a/src/lib/firebase.js +++ b/src/lib/firebase.js @@ -3,6 +3,7 @@ import { connectAuthEmulator, getAuth, GithubAuthProvider, signInWithPopup, sign import { getAnalytics } from "firebase/analytics"; import { connectFirestoreEmulator, getFirestore } from "firebase/firestore"; import { getStorage } from "firebase/storage"; +import logger from "../utils/logger"; const firebaseConfig = { apiKey: import.meta.env.VITE_FIREBASE_API_KEY, @@ -27,7 +28,7 @@ const requiredConfigKeys = [ const hasRequiredConfig = requiredConfigKeys.every((key) => Boolean(firebaseConfig[key])); if (!hasRequiredConfig) { - console.warn("Firebase is not configured. Auth, database, analytics, and storage services are disabled for this environment."); + logger.warn("Firebase is not configured. Auth, database, analytics, and storage services are disabled for this environment."); } // Initialize Firebase @@ -74,7 +75,7 @@ if (app && typeof window !== "undefined") { try { analyticsInstance = getAnalytics(app); } catch (error) { - console.warn("Analytics initialization skipped:", error); + logger.warn("Analytics initialization skipped:", error); } } @@ -115,7 +116,7 @@ export const signInWithGitHub = async (requestRepoScope = false) => { return { user, accessToken, userData, result }; } catch (error) { - console.error("GitHub sign-in error:", error); + logger.error("GitHub sign-in error:", error); if (error.code === 'auth/account-exists-with-different-credential') { throw new Error('An account already exists with the same email address.', { cause: error }); } @@ -136,7 +137,7 @@ export const signOutUser = async () => { await signOut(auth); return true; } catch (error) { - console.error("Sign out error:", error); + logger.error("Sign out error:", error); throw error; } }; @@ -153,7 +154,7 @@ export const getCurrentUserToken = async () => { const token = await user.getIdToken(); return token; } catch (error) { - console.error("Error getting user token:", error); + logger.error("Error getting user token:", error); return null; } } @@ -172,7 +173,7 @@ export const refreshUserToken = async () => { const token = await user.getIdToken(true); // Force refresh return token; } catch (error) { - console.error("Error refreshing user token:", error); + logger.error("Error refreshing user token:", error); return null; } } diff --git a/src/utils/logger.js b/src/utils/logger.js new file mode 100644 index 0000000..fd9b510 --- /dev/null +++ b/src/utils/logger.js @@ -0,0 +1,59 @@ +const logger = { + info: (message, data = {}) => { + if (process.env.NODE_ENV === 'development') { + console.info(`[INFO] ${message}`, sanitizeData(data)); + } + }, + + error: (message, error, context = {}) => { + if (process.env.NODE_ENV === 'development') { + console.error(`[ERROR] ${message}`, error, sanitizeData(context)); + } + }, + + warn: (message, data = {}) => { + if (process.env.NODE_ENV === 'development') { + console.warn(`[WARN] ${message}`, sanitizeData(data)); + } + }, + + debug: (message, data = {}) => { + if (process.env.NODE_ENV === 'development') { + console.debug(`[DEBUG] ${message}`, sanitizeData(data)); + } + }, +}; + +const sanitizeData = (data) => { + if (!data || typeof data !== 'object') { + return data; + } + + const sensitiveKeys = [ + 'userId', + 'uid', + 'email', + 'password', + 'token', + 'accessToken', + 'refreshToken', + 'sessionId', + 'personalInfo', + 'username', + 'githubUsername', + 'apiKey', + 'secret', + ]; + + const sanitized = { ...data }; + + sensitiveKeys.forEach((key) => { + if (key in sanitized) { + sanitized[key] = '[REDACTED]'; + } + }); + + return sanitized; +}; + +export default logger; From 22130ecf8fd47f648cd8aa20fedd3e6824194caa Mon Sep 17 00:00:00 2001 From: Anshul Jain Date: Tue, 30 Jun 2026 01:30:43 +0530 Subject: [PATCH 2/2] fix: use import.meta.env.DEV instead of process.env in logger process is not defined in the browser for this Vite app, causing eslint no-undef errors (and would be undefined at runtime since nothing polyfills process.env here). import.meta.env.DEV is Vite's build-time equivalent. --- src/utils/logger.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/utils/logger.js b/src/utils/logger.js index fd9b510..52b432d 100644 --- a/src/utils/logger.js +++ b/src/utils/logger.js @@ -1,24 +1,24 @@ const logger = { info: (message, data = {}) => { - if (process.env.NODE_ENV === 'development') { + if (import.meta.env.DEV) { console.info(`[INFO] ${message}`, sanitizeData(data)); } }, error: (message, error, context = {}) => { - if (process.env.NODE_ENV === 'development') { + if (import.meta.env.DEV) { console.error(`[ERROR] ${message}`, error, sanitizeData(context)); } }, warn: (message, data = {}) => { - if (process.env.NODE_ENV === 'development') { + if (import.meta.env.DEV) { console.warn(`[WARN] ${message}`, sanitizeData(data)); } }, debug: (message, data = {}) => { - if (process.env.NODE_ENV === 'development') { + if (import.meta.env.DEV) { console.debug(`[DEBUG] ${message}`, sanitizeData(data)); } },