Security
Relevant source files
The following files were used as context for generating this wiki page:
The GUC Resume Enhancer application incorporates a multi-layered security strategy to protect user data, manage access control, and secure communications with third-party AI services. Security measures span authentication, data encryption, storage policies, and robust API endpoint protection. The system uses NextAuth for session management, encrypts sensitive data like API keys, and leverages Supabase's security features for database and file storage.
The core security components include session-based authentication for all user-facing operations, role-based access control for administrative functions, and stringent validation on all data inputs, particularly file uploads. API keys for LLM providers are handled with care, offering both secure server-side encrypted storage for authenticated users and local browser storage for a "light" mode experience.
Authentication and Authorization
The application uses NextAuth.js for handling user authentication and session management. It is configured with a Prisma adapter to persist user and session data in the PostgreSQL database.
Sources: lib/auth.ts, api/resumes/upload/route.ts, admin/resumes/page.tsx
Session Management
All sensitive API routes verify the user's session before processing a request. This is achieved by calling getServerSession(authOptions) and checking for a valid session.user.id. If no valid session is found, the API returns a 401 Unauthorized response.
Sources: api/resumes/upload/route.ts:49-52, api/enhance-resume/[id]/route.ts:114-117
The diagram below shows the sequence for a protected API call.
Role-Based Access Control (RBAC)
The system implements basic role-based access control. The User model in the database contains a role field, which defaults to "user". Certain pages, like the admin dashboard, are protected to only allow users with the "admin" role.
Sources: schema.prisma:41, admin/resumes/page.tsx:13-16
// admin/resumes/page.tsx:13-16
// Check if user has admin role
const userRole = (session.user as { role?: string }).role;
if (userRole !== "admin") {
redirect("/dashboard");
}
API Key Management
The application handles API keys for third-party AI services (OpenAI, Anthropic, Gemini) in two distinct ways: securely in the database for authenticated users, and in the browser's local storage for "Light Mode" users.
Encrypted Database Storage
For authenticated users, API keys are encrypted and stored in the UserApiKey table.
Sources: lib/crypto.ts, schema.prisma:93-107, api/enhance-resume/[id]/route.ts:143-146
The ENCRYPTION_KEY environment variable, which must be 32 characters long, is used for AES-256-GCM encryption.
| Field | Description |
|---|---|
encryptedKey | The AES-256-GCM encrypted API key. |
provider | The AI provider (e.g., 'openai'). |
userId | Foreign key linking to the User. |
Sources: schema.prisma:98, INTEGRATION_GUIDE.md:91
The encryption and decryption flow is as follows:
The decryptApiKey function is called just before making a request to an LLM provider.
Sources: api/enhance-resume/[id]/route.ts:146
Local Storage for "Light Mode"
For users who do not sign up, the application provides a "Light Mode" where API keys are stored directly in the browser's localStorage. This is less secure but allows for functionality without account creation. A warning is displayed to the user about this storage method.
Sources: api-keys-light/page.tsx:81-85, lib/api-key-storage.ts
Data and Storage Security
User-uploaded resumes and their associated data are protected through a combination of Supabase Storage policies and database-level security.
Supabase Storage Policies
The application uses a private Supabase bucket named resumes. Access to this bucket is governed by Row Level Security (RLS) policies that ensure users can only access their own files.
Sources: SUPABASE_SETUP.md:27-31
The following policies are enforced on the storage.objects table for the resumes bucket:
| Policy Name | Action | Description |
|---|---|---|
Users can upload their own resumes | INSERT | Allows an authenticated user to upload a file only into a folder matching their own auth.uid(). |
Users can read their own resumes | SELECT | Allows an authenticated user to read a file only from a folder matching their own auth.uid(). |
Users can delete their own resumes | DELETE | Allows an authenticated user to delete a file only from a folder matching their own auth.uid(). |
Sources: SUPABASE_SETUP.md:40-62
The file storage path is structured to enforce this isolation: resumes/{userId}/{timestamp}-resume.pdf.
Sources: SUPABASE_SETUP.md:143-148
Database Row Level Security (RLS)
The setup guide recommends enabling RLS on database tables like Resume for an additional layer of security. This ensures that even if an application-level bug existed, a user could not directly query the database for another user's resume data.
Sources: SUPABASE_SETUP.md:121-137
Example RLS policy for the Resume table:
CREATE POLICY "Users can view their own resumes"
ON "Resume" FOR SELECT
TO authenticated
USING (auth.uid()::text = "userId");
Sources: SUPABASE_SETUP.md:126-130
API Endpoint Security
In addition to authentication, API endpoints implement specific security measures like input validation and sanitization.
File Upload Validation
The /api/resumes/upload endpoint enforces several checks on incoming files to prevent abuse and attacks.
| Check | Detail |
|---|---|
| Authentication | Request must come from an authenticated user. |
| File Presence | A file must be present in the form data. |
| File Type | Must be application/pdf or application/vnd.openxmlformats-officedocument.wordprocessingml.document (DOCX). |
| File Size | Must not exceed 10MB. |
| File Name Sanitization | The file name is sanitized to prevent path traversal and remove special characters. |
Sources: api/resumes/upload/route.ts:55-75, 93
The sanitization function is a key defense:
// api/resumes/upload/route.ts:9-18
function sanitizeFileName(fileName: string): string {
// Remove any path separators and null bytes
let sanitized = fileName.replace(/[\/\\]/g, '').replace(/\x00/g, '');
// Remove leading dots
sanitized = sanitized.replace(/^\.+/, '');
// Remove special characters but keep alphanumeric, dots, hyphens, underscores
sanitized = sanitized.replace(/[^a-zA-Z0-9._\-]/g, '_');
// Limit length to 255 characters
sanitized = sanitized.substring(0, 255);
return sanitized;
}
Input Parameter Whitelisting
The /api/enhance-resume/[id] endpoint validates the enhancementType parameter against a predefined list of allowed values. This prevents unexpected or malicious inputs from being passed to the AI models.
Sources: api/enhance-resume/[id]/route.ts:109, 124-129
// api/enhance-resume/[id]/route.ts:109
const ALLOWED_ENHANCEMENT_TYPES = ['skills_certifications', 'project_experience', 'client_quality'];
// api/enhance-resume/[id]/route.ts:124-129
if (!ALLOWED_ENHANCEMENT_TYPES.includes(enhancementType)) {
return NextResponse.json(
{ message: `Invalid enhancement type. Allowed types: ${ALLOWED_ENHANCEMENT_TYPES.join(', ')}` },
{ status: 400 }
);
}
Summary
The security posture of the GUC Resume Enhancer is built on standard, robust practices. Authentication and authorization are handled by NextAuth, with role-based checks for sensitive areas. Critical data, such as user API keys, are protected using strong AES-256-GCM encryption. File and data access are strictly controlled through Supabase's RLS policies, ensuring user data is properly isolated. Finally, API endpoints are hardened with strict input validation and sanitization rules, providing a comprehensive defense-in-depth security architecture.