Skip to content

Commit d6eeeec

Browse files
committed
expanded to 50 js libraries, generalized scans across frameworks, conditional checking based on project
1 parent c1ac8b1 commit d6eeeec

File tree

16 files changed

+398
-41
lines changed

16 files changed

+398
-41
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ VibeSafe helps developers quickly check their projects for common security issue
1111
* **Configuration Scanning:** Checks JSON and YAML files for common insecure settings (e.g., `DEBUG = true`, `devMode = true`, permissive CORS like `origin: '*'`)
1212
* **HTTP Client Issues:** Detects potential missing timeout or cancellation configurations in calls using `axios`, `fetch`, `got`, and `request`. (*See Limitations below*).
1313
* **Unvalidated Upload Detection:** Identifies potential missing file size/type restrictions in common upload libraries (`multer`, `formidable`, `express-fileupload`, `busboy`) and generic patterns (`new FormData()`, `<input type="file">`).
14-
* **Exposed Endpoint Detection:** Flags potentially sensitive endpoints (e.g., `/admin`, `/debug`, `/status`, `/info`, `/metrics`) in Express/Node.js applications using common routing patterns or string literals.
14+
* **Exposed Endpoint Detection:** Flags potentially sensitive endpoints (e.g., `/admin`, `/debug`, `/status`, `/info`, `/metrics`) in Node.js web applications using common routing patterns or string literals.
1515
* **Rate Limit Check (Heuristic):** Issues a project-level advisory if API routes are detected but no known rate-limiting package (e.g., `express-rate-limit`, `@upstash/ratelimit`) is found in dependencies.
1616
* **Improper Logging Detection:** Flags potential logging of full error objects (e.g., `console.error(err)`), which can leak stack traces, and detects logging of potentially sensitive data based on keywords (e.g., `password`, `email`, `token`).
1717
* **Multiple Output Formats:** Provides results via console output (with colors!), JSON (`--output`), or a Markdown report (`--report` with default `VIBESAFE-REPORT.md`).

src/frameworkDetection.ts

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
/**
2+
* frameworkDetection.ts
3+
*
4+
* Exports categorized lists of common JavaScript package names to help identify
5+
* the technologies used in a scanned project.
6+
*/
7+
8+
// Frontend Frameworks & Libraries
9+
export const frontendPackages: string[] = [
10+
'react',
11+
'@angular/core',
12+
'vue',
13+
'svelte',
14+
'next', // Also backend/fullstack, but strongly indicative of frontend presence
15+
'nuxt', // Also backend/fullstack
16+
'gatsby',
17+
'remix', // Also backend/fullstack
18+
'solid-js',
19+
'preact',
20+
'@emotion/react', // Common styling library often with React
21+
'styled-components', // Common styling library
22+
'jquery' // Still common in older projects or specific use cases
23+
];
24+
25+
// Backend Frameworks & Libraries
26+
export const backendPackages: string[] = [
27+
'express',
28+
'fastify',
29+
'koa',
30+
'@hapi/hapi', // Hapi v17+ scoped package
31+
'hapi', // Older Hapi
32+
'@nestjs/core',
33+
'sails',
34+
'@adonisjs/core',
35+
'loopback',
36+
'polka',
37+
'restify',
38+
'connect', // Base middleware framework, often used directly
39+
'meteor-base', // Meteor framework indicator
40+
'next' // Added Next.js here as well, since it includes a backend
41+
];
42+
43+
// Authentication & Authorization Libraries
44+
export const authPackages: string[] = [
45+
'passport',
46+
'jsonwebtoken',
47+
'bcrypt',
48+
'bcryptjs', // Alternative bcrypt implementation
49+
'@hapi/basic', // Hapi basic auth
50+
'express-session',
51+
'cookie-session',
52+
'@fastify/session',
53+
'@fastify/jwt',
54+
'next-auth',
55+
'node-jose', // JOSE standards (JWT, JWS, JWE)
56+
'oidc-provider', // OpenID Connect provider
57+
'keycloak-connect', // Keycloak adapter for Node.js
58+
'auth0', // Auth0 SDK
59+
'@clerk/nextjs', // Added Clerk for Next.js
60+
'@clerk/clerk-js' // Added Clerk core JS library
61+
];
62+
63+
// Common Middleware Packages (excluding auth, cors, file upload listed separately)
64+
export const middlewarePackages: string[] = [
65+
'helmet',
66+
'body-parser', // Very common, though often built-in now
67+
'morgan',
68+
'compression',
69+
'express-validator',
70+
'cookie-parser',
71+
'csurf', // CSRF protection
72+
'connect-timeout', // Request timeout middleware
73+
'response-time', // Response time header middleware
74+
'rate-limiter-flexible', // Rate limiting
75+
'express-rate-limit', // Rate limiting for Express
76+
'@fastify/rate-limit', // Rate limiting for Fastify
77+
'@fastify/helmet', // Helmet for Fastify
78+
'@fastify/cookie', // Cookie parsing for Fastify
79+
'pino-http' // Logging middleware (Pino)
80+
];
81+
82+
// HTTP Client Libraries
83+
export const httpClientPackages: string[] = [
84+
'axios',
85+
'node-fetch',
86+
'got',
87+
'superagent',
88+
'request', // Deprecated but still widely used
89+
'needle',
90+
'ky',
91+
'undici', // Node.js built-in fetch, also available as package
92+
'@actions/http-client' // For GitHub Actions
93+
];
94+
95+
// CORS Helper Libraries
96+
export const corsPackages: string[] = [
97+
'cors', // Standard CORS middleware for Express/Connect
98+
'@fastify/cors', // CORS plugin for Fastify
99+
'@koa/cors' // CORS middleware for Koa
100+
];
101+
102+
// File Upload Libraries
103+
export const fileUploadPackages: string[] = [
104+
'multer',
105+
'busboy', // Stream-based parser, often used by other libs
106+
'formidable',
107+
'express-fileupload',
108+
'@fastify/multipart',
109+
'connect-busboy' // Busboy middleware for Connect
110+
];
111+
112+
// Combine all lists for potential generic checks or easier iteration
113+
export const allKnownPackages: string[] = [
114+
...frontendPackages,
115+
...backendPackages,
116+
...authPackages,
117+
...middlewarePackages,
118+
...httpClientPackages,
119+
...corsPackages,
120+
...fileUploadPackages,
121+
];
122+
123+
// Simple check function (example - can be expanded)
124+
export const detectTechnologies = (dependencies: string[]): Record<string, boolean> => {
125+
const detected: Record<string, boolean> = {
126+
hasFrontend: false,
127+
hasBackend: false,
128+
hasAuth: false,
129+
hasMiddleware: false, // Generic middleware detection
130+
hasHttpClient: false,
131+
hasCors: false,
132+
hasFileUpload: false,
133+
};
134+
135+
const depSet = new Set(dependencies); // Keep Set for efficient lookups of other packages
136+
137+
if (frontendPackages.some(pkg => depSet.has(pkg))) detected.hasFrontend = true;
138+
if (backendPackages.some(pkg => depSet.has(pkg))) detected.hasBackend = true;
139+
140+
// Modified Auth Check: Check predefined list OR if any dependency starts with @clerk/
141+
if (authPackages.some(pkg => depSet.has(pkg)) || dependencies.some(dep => dep.startsWith('@clerk/'))) {
142+
detected.hasAuth = true;
143+
}
144+
145+
if (middlewarePackages.some(pkg => depSet.has(pkg))) detected.hasMiddleware = true;
146+
if (httpClientPackages.some(pkg => depSet.has(pkg))) detected.hasHttpClient = true;
147+
if (corsPackages.some(pkg => depSet.has(pkg))) detected.hasCors = true;
148+
if (fileUploadPackages.some(pkg => depSet.has(pkg))) detected.hasFileUpload = true;
149+
150+
return detected;
151+
};

src/index.ts

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { scanForExposedEndpoints, EndpointFinding } from './scanners/endpoints';
1717
import { checkRateLimitHeuristic, RateLimitFinding } from './scanners/rateLimiting';
1818
import { scanForLoggingIssues, LoggingFinding } from './scanners/logging';
1919
import { scanForHttpClientIssues, HttpClientFinding } from './scanners/httpClient';
20+
import { detectTechnologies } from './frameworkDetection';
2021

2122
// Define a combined finding type if needed later
2223

@@ -95,8 +96,34 @@ program.command('scan')
9596

9697
// --- Parse Dependencies (Phase 3.2) ---
9798
const dependencyInfoList = parseDependencies(detectedManagers);
99+
let detectedTech: Record<string, boolean> = {};
98100
if (dependencyInfoList.length > 0) {
99101
console.log(`Parsed ${dependencyInfoList.length} dependencies.`);
102+
// --- Detect Technologies (Phase 0 Integration) ---
103+
const dependencyNames = dependencyInfoList.map(dep => dep.name);
104+
detectedTech = detectTechnologies(dependencyNames);
105+
// console.log('Detected Technologies:', detectedTech); // Remove raw log
106+
107+
// --- Log Detected Technologies ---
108+
const detectedCategories = Object.entries(detectedTech)
109+
.filter(([, value]) => value)
110+
.map(([key]) => key);
111+
112+
if (detectedCategories.length > 0) {
113+
console.log(chalk.blue('Detected Technology Categories:'));
114+
detectedCategories.forEach(categoryKey => {
115+
// Simple conversion from camelCase key to Title Case string
116+
const categoryName = categoryKey
117+
.replace('has', '') // Remove 'has' prefix
118+
.replace(/([A-Z])/g, ' $1') // Add space before capital letters
119+
.replace(/^./, str => str.toUpperCase()) // Capitalize first letter
120+
.trim();
121+
console.log(chalk.blue(` - ${categoryName}`));
122+
});
123+
} else {
124+
// Optionally log if nothing specific was detected
125+
// console.log(chalk.dim('No specific framework/library categories detected based on dependencies.'));
126+
}
100127
}
101128

102129
// --- Secrets Scan (Phase 2.1 / 2.3) ---
@@ -133,7 +160,7 @@ program.command('scan')
133160
filesForUploadScan.forEach(filePath => {
134161
try {
135162
const content = fs.readFileSync(filePath, 'utf-8');
136-
const findings = scanForUnvalidatedUploads(filePath, content);
163+
const findings = scanForUnvalidatedUploads(filePath, content, detectedTech.hasBackend);
137164
const relativeFindings = findings.map(f => ({ ...f, file: path.relative(rootDir, f.file) }));
138165
allUploadFindings = allUploadFindings.concat(relativeFindings);
139166
} catch (error: any) {
@@ -150,7 +177,7 @@ program.command('scan')
150177
filesForEndpointScan.forEach(filePath => {
151178
try {
152179
const content = fs.readFileSync(filePath, 'utf-8');
153-
const findings = scanForExposedEndpoints(filePath, content);
180+
const findings = scanForExposedEndpoints(filePath, content, detectedTech.hasBackend);
154181
const relativeFindings = findings.map(f => ({ ...f, file: path.relative(rootDir, f.file) }));
155182
allEndpointFindings = allEndpointFindings.concat(relativeFindings);
156183
} catch (error: any) {
@@ -161,8 +188,8 @@ program.command('scan')
161188

162189
// --- Rate Limit Heuristic Check (Phase 6.4 - Revised) ---
163190
console.log('Checking for presence of known rate limiting packages and API routes...');
164-
// Pass all parsed dependencies and the files likely containing routes
165-
allRateLimitFindings = checkRateLimitHeuristic(dependencyInfoList, filesForEndpointScan);
191+
// Pass all parsed dependencies, files, and detected tech context
192+
allRateLimitFindings = checkRateLimitHeuristic(dependencyInfoList, filesForEndpointScan, detectedTech);
166193
if (allRateLimitFindings.length > 0) {
167194
console.log(chalk.yellow('Found API routes but no known rate-limiting package in dependencies. Added project-level advisory.'));
168195
} else {
@@ -174,7 +201,7 @@ program.command('scan')
174201
filesForEndpointScan.forEach(filePath => {
175202
try {
176203
const content = fs.readFileSync(filePath, 'utf-8');
177-
const findings = scanForLoggingIssues(filePath, content);
204+
const findings = scanForLoggingIssues(filePath, content, detectedTech.hasBackend);
178205
const relativeFindings = findings.map(f => ({ ...f, file: path.relative(rootDir, f.file) }));
179206
allLoggingFindings = allLoggingFindings.concat(relativeFindings);
180207
} catch (error: any) {
@@ -187,7 +214,7 @@ program.command('scan')
187214
filesForEndpointScan.forEach(filePath => {
188215
try {
189216
const content = fs.readFileSync(filePath, 'utf-8');
190-
const findings = scanForHttpClientIssues(filePath, content);
217+
const findings = scanForHttpClientIssues(filePath, content, detectedTech.hasBackend);
191218
const relativeFindings = findings.map(f => ({ ...f, file: path.relative(rootDir, f.file) }));
192219
allHttpClientFindings = allHttpClientFindings.concat(relativeFindings);
193220
} catch (error: any) {
@@ -275,7 +302,7 @@ program.command('scan')
275302
uploads: reportUploadFindings,
276303
endpoints: reportEndpointFindings,
277304
rateLimiting: reportRateLimitFindings,
278-
logging: allLoggingFindings, // Use the full list here
305+
logging: reportLoggingFindings,
279306
httpClients: reportHttpClientFindings,
280307
info: infoSecretFindings,
281308
gitignoreWarnings: gitignoreWarnings,

src/reporting/aiSuggestions.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ import { EndpointFinding } from '../scanners/endpoints';
77
import { RateLimitFinding } from '../scanners/rateLimiting';
88
import { LoggingFinding } from '../scanners/logging';
99
import { HttpClientFinding } from '../scanners/httpClient';
10-
import ora from 'ora';
11-
import { GitignoreWarning } from '../utils/fileTraversal';
1210
import chalk from 'chalk';
11+
import { GitignoreWarning } from '../utils/fileTraversal';
12+
1313

1414
// Initialize OpenAI client
1515
// The constructor automatically looks for process.env.OPENAI_API_KEY
@@ -108,7 +108,7 @@ export async function generateAISuggestions(reportData: ReportData, apiKey: stri
108108

109109
try {
110110
const completion = await openai.chat.completions.create({
111-
model: "gpt-4o-mini",
111+
model: "gpt-4.1-nano",
112112
messages: [
113113
{ role: "system", content: "You are a helpful security assistant providing fix suggestions for code vulnerabilities." },
114114
{ role: "user", content: prompt }

src/scanners/dependencies.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,12 @@ export function parseDependencies(detectedFiles: { [key in PackageManager]?: { m
260260
// Flag to ensure we only parse node deps once
261261
let nodeDepsParsed = false;
262262

263+
// TODO: Implement package-lock.json (and yarn.lock, pnpm-lock.yaml) parsing.
264+
// - If a lock file is detected (detectedFiles[manager].lock), prioritize parsing it
265+
// to get exact installed versions and transitive dependencies.
266+
// - Pass the resulting DependencyInfo list (with exact versions) to lookupCves.
267+
// - Fall back to parsing the manifest file (e.g., package.json) only if no lock file exists.
268+
263269
for (const manager in detectedFiles) {
264270
const files = detectedFiles[manager as PackageManager];
265271
if (!files || !files.manifest) continue; // Need manifest to parse deps

src/scanners/endpoints.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ export interface EndpointFinding {
1111
}
1212

1313
// Regex patterns for common debug/admin paths
14-
// Includes common framework patterns (like Express) and simple string literals
15-
// - Looks for .get, .post, .put, .delete, .use, .all followed by ('/admin...') or similar
16-
// - Looks for string literals like '/admin', "/debug" etc.
14+
// Includes common web framework patterns and simple string literals
15+
// - Looks for common HTTP method/routing function calls (.get, .post, .use, etc.) followed by a sensitive path string
16+
// - Looks for sensitive path string literals like '/admin', "/debug" etc.
1717
const DEBUG_ADMIN_ENDPOINT_REGEX = /(\.get|\.post|\.put|\.delete|\.use|\.all)\s*\(\s*['"](\/debug|\/admin|\/status|\/info|\/healthz?|\/metrics|\/console|\/manage|\/config)[\/\w\-\:]*['"]/gi;
1818
const DEBUG_ADMIN_STRING_LITERAL_REGEX = /['"](\/debug|\/admin|\/status|\/info|\/healthz?|\/metrics|\/console|\/manage|\/config)[\/\w\-\:]*['"]/gi;
1919

@@ -24,13 +24,18 @@ const DEBUG_ADMIN_STRING_LITERAL_REGEX = /['"](\/debug|\/admin|\/status|\/info|\
2424
* @param content The content of the file.
2525
* @returns An array of EndpointFinding objects.
2626
*/
27-
export function scanForExposedEndpoints(filePath: string, content: string): EndpointFinding[] {
27+
export function scanForExposedEndpoints(filePath: string, content: string, hasBackend: boolean): EndpointFinding[] {
28+
// If no backend framework detected, skip this scan
29+
if (!hasBackend) {
30+
return [];
31+
}
32+
2833
const findings: EndpointFinding[] = [];
2934
const lines = content.split('\n');
3035

3136
let match;
3237

33-
// Check for framework patterns first (e.g., app.get('/admin'))
38+
// Check for framework-style route definition patterns first (e.g., app.get('/admin'))
3439
DEBUG_ADMIN_ENDPOINT_REGEX.lastIndex = 0;
3540
while ((match = DEBUG_ADMIN_ENDPOINT_REGEX.exec(content)) !== null) {
3641
const fullMatch = match[0];

src/scanners/httpClient.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,15 @@ export interface HttpClientFinding {
1818
* TODO: Implement retry logic checks.
1919
* @param filePath Absolute path to the file.
2020
* @param content The content of the file.
21+
* @param hasBackend Indicates whether the file is likely a backend context.
2122
* @returns An array of HttpClientFinding objects.
2223
*/
23-
export function scanForHttpClientIssues(filePath: string, content: string): HttpClientFinding[] {
24+
export function scanForHttpClientIssues(filePath: string, content: string, hasBackend: boolean): HttpClientFinding[] {
25+
// Skip if not likely a backend context
26+
if (!hasBackend) {
27+
return [];
28+
}
29+
2430
const findings: HttpClientFinding[] = [];
2531
try {
2632
const ast = parse(content, { loc: true, range: true, comment: false }); // loc: true gives line/column numbers

0 commit comments

Comments
 (0)