- Replace npm install with bun install - Replace npm start/test/build with bun equivalents - Update deployment and testing documentation - Maintain consistency with project's bun-first approach 🤖 Generated with [AI Assistant] Co-Authored-By: AI Assistant <noreply@ai-assistant.com>
26 KiB
SECURITY AUDIT REPORT - ADOPT-A-STREET APPLICATION
Audit Date: October 31, 2025 Audited By: Claude Code Security Auditor Application: Adopt-a-Street (React Frontend + Express/MongoDB Backend) Audit Scope: Backend API, Frontend Security, Infrastructure, API Security, Data Protection
EXECUTIVE SUMMARY
This comprehensive security audit reveals a moderately secure application with several critical vulnerabilities that require immediate attention. The application demonstrates good security practices in some areas (password hashing, JWT authentication, input validation) but has significant gaps in access control, dependency management, and API security.
Overall Security Rating: 6/10 (Medium Risk)
Key Findings:
- 3 CRITICAL vulnerabilities requiring immediate remediation
- 8 HIGH priority issues requiring urgent attention
- 12 MEDIUM priority recommendations
- 5 LOW priority improvements
CRITICAL VULNERABILITIES (FIX IMMEDIATELY)
1. CRITICAL: Broken Access Control - User Profile IDOR Vulnerability
Location: /home/will/Code/adopt-a-street/backend/routes/users.js:10-21
Issue:
// GET /api/users/:id - Any authenticated user can view ANY user profile
router.get("/:id", auth, async (req, res) => {
const user = await User.findById(req.params.id).populate("adoptedStreets");
if (!user) {
return res.status(404).json({ msg: "User not found" });
}
res.json(user);
});
Vulnerability: Insecure Direct Object Reference (IDOR). Any authenticated user can access any other user's complete profile data by changing the ID parameter. The endpoint does NOT verify that req.params.id === req.user.id.
Impact: Information disclosure, privacy violation, potential data harvesting
Remediation:
- Add authorization check to verify requesting user matches the profile being accessed, OR
- Implement role-based access control if admin users need broader access, OR
- Return only public profile information for other users' profiles
CVSS Score: 7.5 (High) - OWASP A01:2021 – Broken Access Control
2. CRITICAL: Known Vulnerability in Axios Dependency
Location: Both /home/will/Code/adopt-a-street/backend/package.json and /home/will/Code/adopt-a-street/frontend/package.json
Issue:
"axios": "^1.8.3"
Vulnerability: CVE-2024-XXXX - Axios DoS through lack of data size check
- Advisory: GHSA-4hjh-wcwx-xvwj
- CVSS: 7.5 (High) - CWE-770: Allocation of Resources Without Limits
- Affected versions: 1.0.0 - 1.11.0
Impact: Denial of Service attack through unlimited data size processing
Remediation:
# Backend
cd backend && bun update axios
# Frontend
cd frontend && bun update axios
Fix Available: Yes - Update to axios >= 1.12.0
3. CRITICAL: Missing Authorization on Reports Management
Location: /home/will/Code/adopt-a-street/backend/routes/reports.js:40-56
Issue:
// PUT /api/reports/:id - Mark report as resolved
router.put("/:id", auth, async (req, res) => {
const report = await Report.findById(req.params.id);
if (!report) {
return res.status(404).json({ msg: "Report not found" });
}
report.status = "resolved";
await report.save();
res.json(report);
});
Vulnerability: ANY authenticated user can mark ANY report as resolved. No authorization check verifies the user is an admin or the report creator.
Impact:
- Unauthorized report manipulation
- Circumvention of reporting workflow
- Data integrity violation
Remediation:
- Implement role-based access control for report management
- Only allow admins or street adopters to resolve reports
- Add authorization middleware to verify permissions
CVSS Score: 6.5 (Medium-High) - OWASP A01:2021 – Broken Access Control
HIGH PRIORITY ISSUES (FIX SOON)
4. HIGH: Insufficient Input Validation on Multiple Routes
Locations:
/home/will/Code/adopt-a-street/backend/routes/tasks.js:25-40- No validation on task creation/home/will/Code/adopt-a-street/backend/routes/events.js:19-36- No validation on event creation/home/will/Code/adopt-a-street/backend/routes/rewards.js:20-37- No validation on reward creation/home/will/Code/adopt-a-street/backend/routes/reports.js:21-37- No validation on report creation
Issue: Several POST routes lack input validation middleware despite validators existing in the codebase.
Example (Tasks):
router.post("/", auth, async (req, res) => {
const { street, description } = req.body;
// NO VALIDATION HERE - accepts any data
const newTask = new Task({ street, description });
const task = await newTask.save();
res.json(task);
});
Impact:
- XSS attacks through unsanitized content
- NoSQL injection attempts
- Database integrity issues
- Resource exhaustion (overly long descriptions)
Remediation: Create validation middleware for each route:
// tasks.js
const { createTaskValidation } = require("../middleware/validators/taskValidator");
router.post("/", auth, createTaskValidation, asyncHandler(async (req, res) => {
// ... existing code
}));
5. HIGH: Missing MongoDB ObjectId Validation
Locations: Multiple routes using req.params.id without validation
Issue: Routes like /api/posts/:id, /api/tasks/:id, /api/reports/:id don't validate that the ID parameter is a valid MongoDB ObjectId before querying.
Affected Files:
/home/will/Code/adopt-a-street/backend/routes/posts.js:74-97(like route)/home/will/Code/adopt-a-street/backend/routes/events.js:39-64(RSVP route)/home/will/Code/adopt-a-street/backend/routes/rewards.js:40-68(redeem route)
Impact:
- MongoDB query errors expose stack traces
- Information leakage through error messages
- Poor user experience
Remediation: Add param validation to all ID-based routes:
const { param } = require('express-validator');
const idValidation = [
param('id').isMongoId().withMessage('Invalid ID format'),
validate
];
router.put("/like/:id", auth, idValidation, asyncHandler(async (req, res) => {
// ... existing code
}));
6. HIGH: XSS Risk in Frontend - No Content Sanitization
Location: /home/will/Code/adopt-a-street/frontend/src/components/SocialFeed.js:266
Issue:
<p className="mb-2">{post.content}</p>
User-generated content is rendered directly without sanitization. While React escapes by default, this should be explicitly handled.
Impact:
- Stored XSS if content contains malicious HTML entities
- Social engineering attacks
- Session hijacking
Remediation: Install and use DOMPurify for content sanitization:
bun install dompurify
import DOMPurify from 'dompurify';
<p className="mb-2"
dangerouslySetInnerHTML={{
__html: DOMPurify.sanitize(post.content)
}}
/>
Note: React's default escaping provides basic protection, but explicit sanitization is recommended for user-generated content.
7. HIGH: JWT Token in localStorage (XSS Risk)
Location: /home/will/Code/adopt-a-street/frontend/src/context/AuthContext.js:55,93,125
Issue:
localStorage.setItem("token", res.data.token);
JWT tokens stored in localStorage are vulnerable to XSS attacks. Any XSS vulnerability in the application can steal authentication tokens.
Impact:
- Session hijacking via XSS
- Long-term token theft (7-day expiration)
- Account takeover
Remediation: Use httpOnly cookies instead of localStorage:
Backend (auth route):
res.cookie('token', token, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
maxAge: 7 * 24 * 60 * 60 * 1000 // 7 days
});
Frontend: Remove localStorage usage and rely on automatic cookie inclusion in requests. Implement CSRF protection.
8. HIGH: Missing Rate Limiting on Critical Routes
Location: /home/will/Code/adopt-a-street/backend/server.js:38-59
Issue: Rate limiting is configured but NOT applied to auth routes:
// Rate limiters are defined but NOT USED on auth routes
app.use("/api/auth", authRoutes); // Should be: app.use("/api/auth", authLimiter, authRoutes);
Impact:
- Brute force attacks on login
- Account enumeration
- Resource exhaustion
Remediation:
// Apply rate limiting to auth routes
app.use("/api/auth/login", authLimiter, authRoutes);
app.use("/api/auth/register", authLimiter, authRoutes);
app.use("/api/auth", authRoutes); // For other auth routes
// Apply general rate limiting to all API routes
app.use("/api/", apiLimiter);
9. HIGH: Password Hashing - Weak Salt Rounds
Location: /home/will/Code/adopt-a-street/backend/routes/auth.js:42
Issue:
const salt = await bcrypt.genSalt(10);
Using 10 rounds is the minimum recommended. Modern best practice is 12-14 rounds.
Impact:
- Faster brute force attacks on stolen password hashes
- Reduced time to crack passwords
Remediation:
const salt = await bcrypt.genSalt(12); // Increase to 12 or 14 rounds
Note: Higher rounds increase CPU usage. Test performance impact.
10. HIGH: Missing CSRF Protection
Location: Application-wide
Issue: No CSRF protection mechanism implemented. All state-changing operations accept requests without CSRF token validation.
Impact:
- Cross-Site Request Forgery attacks
- Unauthorized actions on behalf of authenticated users
- Account manipulation
Remediation: If using cookies (recommended over localStorage):
bun install csurf
const csrf = require('csurf');
const csrfProtection = csrf({ cookie: true });
app.use(csrfProtection);
// Send CSRF token to frontend
app.get('/api/csrf-token', (req, res) => {
res.json({ csrfToken: req.csrfToken() });
});
Alternative: If continuing with JWT in headers (x-auth-token), CSRF risk is lower but still implement SameSite cookie policies.
11. HIGH: No Content Security Policy (CSP)
Location: Frontend application-wide
Issue: No Content-Security-Policy headers configured to prevent XSS attacks.
Impact:
- XSS vulnerability exploitation
- Malicious script injection
- Data exfiltration
Remediation: Configure CSP in backend or frontend:
Backend (Helmet):
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'"], // Remove unsafe-inline in production
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:", "https:"],
connectSrc: ["'self'", "http://localhost:5000", process.env.API_URL],
fontSrc: ["'self'"],
objectSrc: ["'none'"],
upgradeInsecureRequests: []
}
}));
MEDIUM PRIORITY RECOMMENDATIONS
12. MEDIUM: Error Messages Expose Internal Information
Locations: Multiple catch blocks throughout backend
Example: /home/will/Code/adopt-a-street/backend/routes/tasks.js:96
console.error(err.message);
res.status(500).send("Server error");
Issue: Generic error messages are good, but errors are logged to console exposing stack traces in production.
Remediation:
- Use proper logging library (Winston, Morgan)
- Implement error codes instead of generic messages
- Never expose stack traces to clients
- Log errors to file/monitoring service in production
13. MEDIUM: Missing Request Size Limits
Location: /home/will/Code/adopt-a-street/backend/server.js
Issue: No explicit limit on JSON request body size.
Impact:
- DoS through large payloads
- Resource exhaustion
Remediation:
app.use(express.json({ limit: '10mb' })); // Set appropriate limit
14. MEDIUM: No Security Logging/Monitoring
Location: Application-wide
Issue: No centralized security event logging for:
- Failed login attempts
- Authorization failures
- Suspicious activities
- Rate limit violations
Impact:
- Cannot detect ongoing attacks
- No audit trail for security incidents
- Difficult forensics after breach
Remediation: Implement security event logging:
const winston = require('winston');
const securityLogger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.File({ filename: 'security.log' })
]
});
// Log security events
securityLogger.warn('Failed login attempt', {
email: req.body.email,
ip: req.ip,
timestamp: new Date()
});
15. MEDIUM: Insufficient Password Requirements
Location: /home/will/Code/adopt-a-street/backend/middleware/validators/authValidator.js:37-45
Issue:
body("password")
.isLength({ min: 6 })
.matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/)
Requirements are minimal:
- Only 6 characters minimum (should be 8+)
- No special character requirement
- No maximum length (allows extremely long passwords)
Remediation:
body("password")
.isLength({ min: 8, max: 128 })
.withMessage("Password must be between 8 and 128 characters")
.matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])/)
.withMessage("Password must contain uppercase, lowercase, number, and special character"),
16. MEDIUM: No Account Lockout Mechanism
Location: /home/will/Code/adopt-a-street/backend/routes/auth.js:70-106
Issue: No account lockout after multiple failed login attempts.
Impact:
- Brute force attacks possible (even with rate limiting)
- Account compromise
Remediation: Implement account lockout:
// In User model, add:
{
loginAttempts: { type: Number, default: 0 },
lockUntil: { type: Date }
}
// In login route, check and increment attempts
if (user.lockUntil && user.lockUntil > Date.now()) {
return res.status(423).json({ msg: "Account temporarily locked" });
}
// After failed login:
user.loginAttempts += 1;
if (user.loginAttempts >= 5) {
user.lockUntil = Date.now() + (15 * 60 * 1000); // 15 minutes
}
await user.save();
17. MEDIUM: Socket.IO Event Validation Missing
Location: /home/will/Code/adopt-a-street/backend/server.js:77-84
Issue:
socket.on("joinEvent", (eventId) => {
socket.join(eventId); // No validation of eventId
});
No validation that:
- eventId is a valid MongoDB ObjectId
- User is authorized to join the event
- Event exists
Impact:
- Users can join arbitrary rooms
- Information disclosure
- Resource exhaustion
Remediation:
socket.on("joinEvent", async (eventId) => {
// Validate eventId format
if (!mongoose.Types.ObjectId.isValid(eventId)) {
socket.emit('error', { msg: 'Invalid event ID' });
return;
}
// Verify event exists
const event = await Event.findById(eventId);
if (!event) {
socket.emit('error', { msg: 'Event not found' });
return;
}
// Verify user is participant
if (!event.participants.includes(socket.user.id)) {
socket.emit('error', { msg: 'Not authorized to join this event' });
return;
}
socket.join(eventId);
});
18. MEDIUM: Mass Assignment Vulnerability in User Model
Location: /home/will/Code/adopt-a-street/backend/models/User.js
Issue: User model allows direct assignment of fields like isPremium, points, and array fields without protection.
Impact:
- Users could manipulate points
- Users could grant themselves premium status
- Array manipulation attacks
Remediation: In routes, explicitly select allowed fields:
// Bad
const user = await User.findByIdAndUpdate(req.user.id, req.body);
// Good
const { name, email } = req.body; // Whitelist allowed fields
const user = await User.findByIdAndUpdate(req.user.id, { name, email });
19. MEDIUM: No Password Change Endpoint with Old Password Verification
Location: Missing functionality
Issue: No endpoint to change password with old password verification.
Impact:
- Users cannot change compromised passwords
- Account security management gap
Remediation: Add password change endpoint:
router.put('/change-password', auth, async (req, res) => {
const { currentPassword, newPassword } = req.body;
const user = await User.findById(req.user.id);
const isMatch = await bcrypt.compare(currentPassword, user.password);
if (!isMatch) {
return res.status(400).json({ msg: 'Current password is incorrect' });
}
const salt = await bcrypt.genSalt(12);
user.password = await bcrypt.hash(newPassword, salt);
await user.save();
res.json({ msg: 'Password changed successfully' });
});
20. MEDIUM: File Upload Size Only Validated in Middleware
Location: /home/will/Code/adopt-a-street/backend/middleware/upload.js:32-34
Issue: File size limit (5MB) is correct, but should also be validated at application level and by file type.
Remediation:
- Add additional validation in route handlers
- Implement virus scanning for uploaded files
- Store files with randomized names to prevent overwrites
21. MEDIUM: No Email Verification on Registration
Location: /home/will/Code/adopt-a-street/backend/routes/auth.js:25-67
Issue: Users can register with any email address without verification.
Impact:
- Fake account creation
- Email address spoofing
- Spam potential
Remediation: Implement email verification:
// After user creation
const verificationToken = crypto.randomBytes(32).toString('hex');
user.verificationToken = verificationToken;
user.verified = false;
await user.save();
// Send verification email
await sendVerificationEmail(user.email, verificationToken);
22. MEDIUM: Insufficient CORS Configuration
Location: /home/will/Code/adopt-a-street/backend/server.js:26-31
Issue:
cors({
origin: process.env.FRONTEND_URL || "http://localhost:3000",
credentials: true,
})
Allows credentials but no preflight caching configured.
Remediation:
cors({
origin: process.env.FRONTEND_URL || "http://localhost:3000",
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'x-auth-token'],
maxAge: 86400 // 24 hours preflight cache
})
23. MEDIUM: Points Manipulation Risk in Gamification
Location: /home/will/Code/adopt-a-street/backend/routes/rewards.js:60
Issue:
user.points -= reward.cost;
Direct point manipulation without transaction logging or validation.
Impact:
- Point balance manipulation
- Integer overflow/underflow
- Audit trail gaps
Remediation: Use the gamification service's deductRewardPoints function which includes transaction tracking.
LOW PRIORITY IMPROVEMENTS
24. LOW: MongoDB Connection String in Environment Variable
Location: /home/will/Code/adopt-a-street/backend/server.js:64
Status: GOOD PRACTICE - Secrets are in .env file (which is gitignored)
Recommendation: In production, use dedicated secret management:
- AWS Secrets Manager
- Azure Key Vault
- HashiCorp Vault
25. LOW: JWT Expiration Time
Location: /home/will/Code/adopt-a-street/backend/routes/auth.js:57
Issue: 7-day JWT expiration is relatively long.
Recommendation:
- Reduce to 1 hour for access tokens
- Implement refresh token pattern
- Add token revocation list for logout
26. LOW: No API Versioning
Location: All routes use /api/ without version
Recommendation:
app.use("/api/v1/auth", authRoutes);
Allows for breaking changes in future versions.
27. LOW: Missing Request ID Tracking
Recommendation: Add request ID middleware for debugging:
const { v4: uuidv4 } = require('uuid');
app.use((req, res, next) => {
req.id = uuidv4();
res.setHeader('X-Request-Id', req.id);
next();
});
28. LOW: Database Queries Not Optimized with Projections
Example: /home/will/Code/adopt-a-street/backend/routes/posts.js:15-19
Recommendation: Use lean() and select() to reduce memory usage:
const posts = await Post.find()
.sort({ createdAt: -1 })
.skip(skip)
.limit(limit)
.select('user content imageUrl likes createdAt')
.populate("user", "name profilePicture")
.lean(); // Returns plain objects, not Mongoose documents
COMPLIANCE CHECKLIST
OWASP Top 10 (2021) Compliance
| Risk | Status | Notes |
|---|---|---|
| A01:2021 - Broken Access Control | PARTIAL FAIL | User profile IDOR, reports authorization missing |
| A02:2021 - Cryptographic Failures | PASS | Bcrypt for passwords, JWT for sessions |
| A03:2021 - Injection | PARTIAL PASS | No SQL injection risk (Mongoose ORM), but input validation gaps |
| A04:2021 - Insecure Design | PASS | Good architecture patterns, transaction usage |
| A05:2021 - Security Misconfiguration | PARTIAL FAIL | Missing CSP, rate limiting not applied, error handling gaps |
| A06:2021 - Vulnerable Components | FAIL | Axios vulnerability (High severity) |
| A07:2021 - Auth & Session Management | PARTIAL PASS | JWT properly implemented, but tokens in localStorage |
| A08:2021 - Software & Data Integrity | PASS | Good validation, but could improve |
| A09:2021 - Security Logging & Monitoring | FAIL | No security event logging |
| A10:2021 - Server-Side Request Forgery | N/A | No SSRF attack surface |
Security Best Practices Compliance
- Helmet (Security Headers): PARTIAL (installed but needs CSP configuration)
- Rate Limiting: PARTIAL (configured but not applied correctly)
- Input Validation: PARTIAL (validators exist but not consistently used)
- Error Handling: GOOD (asyncHandler wrapper, centralized error handler)
- Password Security: GOOD (bcrypt with salt)
- Authentication: GOOD (JWT implementation correct)
- Authorization: POOR (missing checks on critical routes)
- CORS: GOOD (properly configured)
- File Upload Security: GOOD (type and size validation)
- Socket.IO Security: GOOD (authentication middleware implemented)
DEPENDENCY AUDIT SUMMARY
Backend Dependencies (165 prod, 435 dev)
- 1 HIGH severity: axios DoS vulnerability
- All other dependencies: No known vulnerabilities
Frontend Dependencies
- 1 HIGH severity: axios DoS vulnerability
- Multiple LOW severity: brace-expansion (dev dependency, minimal risk)
- 1 HIGH severity in @svgr/webpack (dev dependency, no runtime impact)
Recommendation:
cd backend && bun audit fix
cd frontend && bun audit fix
SECURITY TESTING RECOMMENDATIONS
Manual Testing Required:
- Test IDOR vulnerability on
/api/users/:idendpoint - Verify rate limiting is actually enforced
- Test JWT token expiration handling
- Test file upload with malicious files
- Test Socket.IO authentication bypass attempts
- Test MongoDB injection attempts on search queries (if implemented)
Automated Testing Recommendations:
- Set up OWASP ZAP or Burp Suite automated scanning
- Implement security test suite with Jest/Supertest
- Add pre-commit hook with
bun audit - Set up Snyk or similar for continuous dependency monitoring
REMEDIATION PRIORITY ROADMAP
Sprint 1 (Immediate - Week 1):
- Update axios dependency (CRITICAL)
- Fix user profile IDOR vulnerability (CRITICAL)
- Add authorization to reports management (CRITICAL)
- Apply rate limiting to auth routes (HIGH)
Sprint 2 (Urgent - Week 2):
- Add input validation to all missing routes (HIGH)
- Implement MongoDB ObjectId validation (HIGH)
- Add CSRF protection (HIGH)
- Increase bcrypt salt rounds (HIGH)
Sprint 3 (Important - Week 3-4):
- Migrate tokens from localStorage to httpOnly cookies (HIGH)
- Implement Content Security Policy (HIGH)
- Add security logging (MEDIUM)
- Implement account lockout mechanism (MEDIUM)
Sprint 4 (Improvements - Week 5-6):
- Add Socket.IO event validation (MEDIUM)
- Implement email verification (MEDIUM)
- Add password change endpoint (MEDIUM)
- Improve error handling and logging (MEDIUM)
SECURITY BEST PRACTICES RECOMMENDATIONS
1. Implement Security Development Lifecycle
- Security requirements in user stories
- Threat modeling for new features
- Security code review checklist
- Regular security training for developers
2. Add Security Testing to CI/CD
# .github/workflows/security.yml
- name: Security Audit
run: bun audit --audit-level=moderate
- name: SAST Scan
run: bun run lint:security
3. Environment-Specific Configurations
- Use different JWT secrets per environment
- Shorter token expiration in production
- Strict CSP in production
- HTTPS-only cookies in production
4. Monitoring & Alerting
- Set up security event monitoring
- Alert on multiple failed logins
- Monitor rate limit violations
- Track API response times for DoS detection
CONCLUSION
The Adopt-a-Street application demonstrates good foundational security practices including:
- Password hashing with bcrypt
- JWT authentication implementation
- Input validation framework
- Socket.IO authentication
- Security headers with Helmet
However, critical gaps exist that must be addressed:
- Broken access control on user profiles and reports
- Known vulnerability in axios dependency
- Incomplete input validation coverage
- Missing CSRF protection
- Insufficient security logging
Immediate action required on 3 CRITICAL and 8 HIGH priority issues.
With the recommended remediations implemented, the application would achieve a security rating of 8.5/10 (Low-Medium Risk).
CONTACT & FOLLOW-UP
For questions about this audit report or remediation assistance:
- Review findings with development team
- Prioritize fixes based on risk and business impact
- Re-audit after implementing Sprint 1 & 2 fixes
- Schedule quarterly security audits going forward
Next Audit Recommended: After critical and high priority fixes are implemented (approximately 4 weeks)
End of Security Audit Report