Sample Audit Reports
See exactly what you get. These are real reports generated by our AI auditing engine.
Code Review Report
ScanMyCode.dev Code Audit (AUD-K8X2P4QR7MNJ)
Repository: acme-corp/webshop-frontend
Review Date: February 12, 2026
Reviewer: ScanMyCode.dev Code Audit (AUD-K8X2P4QR7MNJ)
Tech Stack: Next.js 15, React 19, TypeScript, Prisma, PostgreSQL, Tailwind CSS 4
Score
Overall Score: 68 / 100
The webshop frontend has a solid foundation with modern tooling and good TypeScript adoption. Component composition is generally well-done, and the use of Prisma for data access prevents most SQL injection risks. However, the application has several high-severity issues including a memory leak in the dashboard WebSocket, missing error boundaries that can crash the entire app, and hardcoded configuration values including an API key in source code. The codebase also lacks consistent error handling patterns and has a growing 600-line checkout component that needs decomposition.
Strengths
- •Good TypeScript adoption: Strict mode enabled, interfaces defined for all API responses and component props.
- •Prisma ORM usage: Prevents SQL injection and provides type-safe database queries throughout the application.
- •Consistent naming conventions: Components use PascalCase, hooks use
useprefix, utilities use camelCase. - •Proper React patterns: Effective use of
useMemoanduseCallbackto prevent unnecessary re-renders in product grid. - •Tailwind CSS organization: Custom design tokens defined in
tailwind.config.ts, consistent spacing and color usage.
Findings Overview
| # | Finding | Severity | Location |
|---|---|---|---|
| 1 | Memory leak in WebSocket useEffect hook | High | src/pages/dashboard.tsx:67-84 |
| 2 | No React Error Boundaries | High | src/App.tsx |
| 3 | API key hardcoded in source | High | src/config/app.ts:3 |
| 4 | Missing cleanup in event listeners | Medium | src/hooks/useResize.ts:12 |
| 5 | Monolithic 600-line checkout component | Medium | src/pages/checkout.tsx |
| 6 | Inconsistent error handling across API calls | Medium | Multiple files |
| 7 | Missing loading states on data fetches | Low | src/pages/products.tsx |
| 8 | Unused imports and dead code | Low | Multiple files |
Detailed Findings
1. Memory leak in WebSocket useEffect hook
The dashboard component opens a WebSocket connection for real-time order updates but never closes it on unmount. Each time the user navigates away and back, a new connection is created while the previous one remains open, accumulating connections and event handlers over time.
Current code:
// src/pages/dashboard.tsx - No cleanup on unmount
useEffect(() => {
const ws = new WebSocket('wss://api.example.com/orders');
ws.onmessage = (event) => {
setOrders(prev => [...prev, JSON.parse(event.data)]);
};
ws.onerror = (err) => {
console.error('WebSocket error:', err);
};
// Missing return cleanup!
}, []);Recommended fix:
useEffect(() => {
const ws = new WebSocket('wss://api.example.com/orders');
ws.onmessage = (event) => {
setOrders(prev => [...prev, JSON.parse(event.data)]);
};
ws.onerror = (err) => {
console.error('WebSocket error:', err);
};
return () => {
ws.close();
};
}, []);Why this matters: Each navigation to and from the dashboard creates a new WebSocket without closing the previous one. After 10 navigations, 10 WebSocket connections are active simultaneously, each appending duplicate data to state. This causes increasing memory usage and can lead to browser tab crashes in long-running sessions.
2. No React Error Boundaries
The application has no ErrorBoundary components anywhere in the component tree. A runtime error in any component (e.g., accessing a property of undefined in a product card) will crash the entire application with a blank white screen.
Current code:
// src/App.tsx - No error boundary
function App() {
return (
<Router>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/products" element={<Products />} />
<Route path="/checkout" element={<Checkout />} />
</Routes>
</Router>
);
}Recommended fix:
// src/components/ErrorBoundary.tsx
'use client'
import { Component, ReactNode } from 'react'
interface Props { children: ReactNode; fallback?: ReactNode }
interface State { hasError: boolean }
class ErrorBoundary extends Component<Props, State> {
state: State = { hasError: false }
static getDerivedStateFromError(): State {
return { hasError: true }
}
componentDidCatch(error: Error, info: React.ErrorInfo) {
console.error('ErrorBoundary caught:', error, info)
}
render() {
if (this.state.hasError) {
return this.props.fallback || <FallbackErrorPage />
}
return this.props.children
}
}
// src/App.tsx - With error boundary
function App() {
return (
<ErrorBoundary>
<Router>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/products" element={<Products />} />
<Route path="/checkout" element={<Checkout />} />
</Routes>
</Router>
</ErrorBoundary>
);
}Why this matters: Without error boundaries, a single rendering error in any component takes down the entire application. In an e-commerce context, a malformed product response from the API could make the checkout page inaccessible, directly impacting revenue.
3. API key hardcoded in source
A Stripe API key is hardcoded directly in the configuration file. This key is committed to version control and will be included in the client-side JavaScript bundle, making it visible to anyone who inspects the browser DevTools.
Current code:
// src/config/app.ts
const config = {
stripeKey: 'sk_live_51N8x2KLm4...',
apiUrl: 'https://api.production.com',
maxRetries: 5,
};Recommended fix:
// src/config/app.ts
const config = {
stripePublishableKey: process.env.NEXT_PUBLIC_STRIPE_KEY,
apiUrl: process.env.NEXT_PUBLIC_API_URL,
maxRetries: parseInt(process.env.MAX_RETRIES || '3'),
};
// .env.local (never committed)
// NEXT_PUBLIC_STRIPE_KEY=pk_live_...
// STRIPE_SECRET_KEY=sk_live_... (server-side only)
// NEXT_PUBLIC_API_URL=https://api.production.comWhy this matters: A secret key in source code is permanently in git history, even if deleted later. Anyone with repository access (current and former employees, compromised CI) can extract it. For a Stripe secret key, this means full access to payment operations.
... and 5 more findings in the full report.
Recommendations
- Fix the WebSocket memory leak immediately. Add cleanup return functions to all useEffect hooks that create subscriptions, timers, or connections.
- Add ErrorBoundary components at the route level and around critical sections (checkout, payment). This prevents cascading failures.
- Rotate the exposed Stripe key and move all secrets to environment variables. Audit git history for other committed secrets.
- Break up the checkout component into smaller, testable components:
CheckoutForm,OrderSummary,PaymentSection,ShippingForm. - Standardize error handling with a shared
useApiCallhook that handles loading, error, and success states consistently.
Want this level of detail for your code?
Get a comprehensive code review delivered within 24 hours.
Security Audit Report
ScanMyCode.dev Security Audit (AUD-R4M7JW3NPQXZ)
Repository: acme-corp/api-backend
Review Date: February 10, 2026
Reviewer: ScanMyCode.dev Security Audit (AUD-R4M7JW3NPQXZ)
Tech Stack: Express.js, Node.js 20, PostgreSQL, JWT, bcrypt, Redis
Score
Overall Score: 38 / 100
CRITICAL: The API backend has multiple severe security vulnerabilities that make it unsuitable for production deployment. The most dangerous issues are unparameterized SQL queries allowing injection attacks, completely unprotected admin endpoints, and AWS credentials committed to source code. The application does use bcrypt for password hashing and has CSRF tokens on forms, but these positives are far outweighed by the critical gaps. The authentication system lacks rate limiting, allowing unlimited brute-force attempts against user accounts.
Strengths
- •Passwords hashed with bcrypt: Cost factor 12, which provides adequate protection against offline brute-force attacks.
- •CSRF tokens on forms: All form submissions include and validate CSRF tokens.
- •HTTPS enforced: Strict transport security headers are configured correctly.
- •Input length limits: Request body size limits are set to 10KB on most endpoints.
Findings Overview
| # | Finding | Severity | Location |
|---|---|---|---|
| 1 | SQL Injection in user search endpoint | Critical | src/routes/users.ts:34 |
| 2 | Unauthenticated admin API endpoints | Critical | src/routes/admin/*.ts |
| 3 | AWS credentials hardcoded in source | Critical | src/config/aws.ts:12-15 |
| 4 | No rate limiting on login endpoint | High | src/routes/auth.ts:45 |
| 5 | JWT secret is only 8 characters | High | src/lib/jwt.ts:3 |
| 6 | Missing Content-Security-Policy header | Medium | src/middleware/headers.ts |
| 7 | Session tokens do not expire | Medium | src/lib/session.ts:22 |
Detailed Findings
1. SQL Injection in user search endpoint
The user search endpoint directly interpolates the query parameter into a SQL string without parameterization. An attacker can inject arbitrary SQL, including UNION-based attacks to extract data from other tables, or destructive statements to modify/delete data.
Current code:
// src/routes/users.ts - Direct string interpolation in SQL
app.get('/api/users/search', async (req, res) => {
const { q } = req.query;
const users = await db.query(
`SELECT * FROM users WHERE name LIKE '%${q}%'`
);
res.json(users);
});
// Attack: /api/users/search?q=' UNION SELECT username,password FROM users--Recommended fix:
app.get('/api/users/search', async (req, res) => {
const { q } = req.query;
const users = await db.query(
'SELECT * FROM users WHERE name LIKE $1',
[`%${q}%`]
);
res.json(users);
});Why this matters: SQL injection is consistently ranked #1 in the OWASP Top 10. With this vulnerability, an attacker can extract the entire database (user credentials, payment data, personal information), modify records, or delete tables. A single vulnerable endpoint compromises all data in the database.
2. Unauthenticated admin API endpoints
Every admin endpoint is completely open. There is no authentication, session validation, or role-based access control. Anyone who discovers the endpoint URLs can view all user data, modify orders, and delete records.
Current code:
// src/routes/admin/users.ts - No auth check
export default async function handler(req, res) {
const users = await db.query('SELECT * FROM users');
const orders = await db.query('SELECT * FROM orders');
res.json({ users, orders });
}Recommended fix:
import { withAuth } from '@/lib/auth-middleware';
export default withAuth(async function handler(req, res) {
if (req.user.role !== 'admin') {
return res.status(403).json({ error: 'Forbidden' });
}
const users = await db.query('SELECT id, name, email FROM users');
res.json({ users });
}, { role: 'admin' });Why this matters: Without authentication, the admin endpoints are effectively public. An attacker or even an automated scanner can discover these endpoints and gain full read/write access to all application data, including personally identifiable information.
... and 5 more findings in the full report.
Recommendations
- URGENT: Fix the SQL injection vulnerability before any deployment. This is the single most dangerous finding - switch to parameterized queries or use an ORM.
- Add authentication middleware to all admin routes. Implement role-based access control and verify both authentication and authorization.
- Rotate all hardcoded credentials immediately. Assume any credential that has been in source code is compromised. Move to environment variables or a secrets manager.
- Implement rate limiting on the login endpoint. Maximum 5 attempts per minute per IP, with exponential backoff and account lockout after 15 failed attempts.
- Generate a strong JWT secret (minimum 256 bits / 32 bytes of cryptographic randomness) and store it as an environment variable.
Want this level of detail for your code?
Get a comprehensive security audit delivered within 24 hours.
Performance Audit Report
ScanMyCode.dev Performance Audit (AUD-V5N9GT2HKDWF)
Repository: acme-corp/dashboard-app
Review Date: February 8, 2026
Reviewer: ScanMyCode.dev Performance Audit (AUD-V5N9GT2HKDWF)
Tech Stack: Next.js 15, React 19, PostgreSQL, Prisma, Tailwind CSS 4
Score
Overall Score: 52 / 100
The dashboard application has significant performance bottlenecks that will become critical as the user base grows. The most impactful issue is a classic N+1 query pattern that generates 51 database queries where 1-2 would suffice, causing the dashboard API to take 3.2 seconds. Combined with missing database indexes on frequently queried foreign keys, the application will not scale beyond ~100 concurrent users without major refactoring. The frontend also loads full-resolution images without optimization, resulting in an 8MB initial page load.
Strengths
- •Server-side rendering: Pages are SSR'd, providing good initial load performance for content above the fold.
- •React.memo usage: Critical list components are wrapped in
React.memoto prevent unnecessary re-renders. - •Database connection pooling: Prisma is configured with appropriate connection pool limits for the expected load.
Findings Overview
| # | Finding | Severity | Location |
|---|---|---|---|
| 1 | N+1 query pattern in dashboard data loading | Critical | src/lib/data.ts:23-35 |
| 2 | Missing database indexes on foreign keys | Critical | Database schema |
| 3 | Full-resolution images loaded without optimization | High | src/components/ProductGrid.tsx |
| 4 | Bundle includes 800KB of unused lodash methods | Medium | package.json |
| 5 | No query result caching | Medium | src/lib/data.ts |
Detailed Findings
1. N+1 query pattern in dashboard data loading
getDashboardData() fetches all users in one query, then executes a separate query per user to fetch their orders. With 50 users displayed on the dashboard, this results in 51 database round-trips on every page load.
Current code:
// src/lib/data.ts - 51 database queries!
async function getDashboardData() {
const users = await db.query('SELECT * FROM users LIMIT 50');
const dashboardData = [];
for (const user of users) {
const orders = await db.query(
'SELECT * FROM orders WHERE user_id = $1',
[user.id]
);
dashboardData.push({ user, orders });
}
return dashboardData; // 1 + 50 = 51 queries
}Recommended fix:
// Option 1: Single JOIN query
async function getDashboardData() {
const data = await db.query(`
SELECT u.*, json_agg(o.*) as orders
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id
LIMIT 50
`);
return data;
}
// Option 2: Prisma eager loading
const users = await prisma.user.findMany({
take: 50,
include: { orders: true }
});Why this matters: This is a classic N+1 performance anti-pattern. The dashboard API response time is 3.2 seconds due to 51 sequential database round-trips. With a JOIN or eager loading, this drops to ~280ms (91% faster). As the user count grows, response times will grow linearly.
2. Missing database indexes on foreign keys
The orders table has no index on user_id or product_id. Every query that filters by these columns results in a full table scan across all 100,000+ rows.
Current code:
-- No indexes on foreign keys
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL, -- No index!
product_id INTEGER NOT NULL, -- No index!
amount DECIMAL(10,2),
created_at TIMESTAMP
);
-- This scans ALL 100,000 rows every time:
SELECT * FROM orders WHERE user_id = 123;
-- EXPLAIN: Seq Scan on orders (cost=0.00..15000.00)Recommended fix:
-- Add indexes on frequently queried columns
CREATE INDEX idx_orders_user_id ON orders(user_id);
CREATE INDEX idx_orders_product_id ON orders(product_id);
CREATE INDEX idx_orders_created_at ON orders(created_at);
-- Composite index for common dashboard query
CREATE INDEX idx_orders_user_created
ON orders(user_id, created_at DESC);
-- Now uses index scan:
SELECT * FROM orders WHERE user_id = 123;
-- EXPLAIN: Index Scan using idx_orders_user_id (cost=0.29..8.31)Why this matters: Without indexes, every order lookup requires scanning the entire table. A query that should take 5ms takes 2-5 seconds. Combined with the N+1 pattern (Finding #1), the dashboard makes 50 full table scans per load.
... and 3 more findings in the full report.
Recommendations
- Fix the N+1 query pattern by using a single JOIN query or Prisma's
includefor eager loading. This alone will reduce dashboard load time from 3.2s to ~280ms. - Add database indexes on all foreign key columns and frequently filtered columns. This is the highest-impact change per effort.
- Use `next/image` for all product images. It provides automatic resizing, lazy loading, and WebP conversion, reducing page weight from 8MB to ~200KB.
- Replace full lodash import with individual function imports (
lodash/debounceinstead oflodash) to eliminate 800KB of unused code from the bundle. - Add query result caching with a 30-second TTL for dashboard data that doesn't need to be real-time.
Want this level of detail for your code?
Get a comprehensive performance audit delivered within 24 hours.