Initial commit: ai-code-trust CLI tool v1.0.0
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
.venv/
|
||||||
|
*.log
|
||||||
|
.verdict
|
||||||
Generated
+51
@@ -0,0 +1,51 @@
|
|||||||
|
{
|
||||||
|
"name": "ai-code-trust",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "ai-code-trust",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "^25.9.3"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"ai-code-trust": "dist/index.js"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"typescript": "^5.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/node": {
|
||||||
|
"version": "25.9.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.3.tgz",
|
||||||
|
"integrity": "sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"undici-types": ">=7.24.0 <7.24.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/typescript": {
|
||||||
|
"version": "5.9.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||||
|
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"bin": {
|
||||||
|
"tsc": "bin/tsc",
|
||||||
|
"tsserver": "bin/tsserver"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.17"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/undici-types": {
|
||||||
|
"version": "7.24.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz",
|
||||||
|
"integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==",
|
||||||
|
"license": "MIT"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"name": "ai-code-trust",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "CLI tool to audit and assess trust in AI-generated code",
|
||||||
|
"main": "dist/index.js",
|
||||||
|
"bin": {
|
||||||
|
"ai-code-trust": "dist/index.js"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc",
|
||||||
|
"test": "node --test dist/**/*.test.js",
|
||||||
|
"lint": "tsc --noEmit"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"ai",
|
||||||
|
"code",
|
||||||
|
"trust",
|
||||||
|
"audit",
|
||||||
|
"security"
|
||||||
|
],
|
||||||
|
"author": "BunBun Labs",
|
||||||
|
"license": "MIT",
|
||||||
|
"devDependencies": {
|
||||||
|
"typescript": "^5.4.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "^25.9.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
import { test } from 'node:test';
|
||||||
|
import assert from 'node:assert';
|
||||||
|
import { detectPatterns, getSeverityCounts, PatternMatch } from './detectors.js';
|
||||||
|
import { analyzeFileContent, analyzeDirectory } from './analyzer.js';
|
||||||
|
|
||||||
|
test('detectPatterns finds AI comment patterns', () => {
|
||||||
|
const code = `
|
||||||
|
// This function handles the main logic
|
||||||
|
function main() {
|
||||||
|
return 42;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
const result = detectPatterns(code);
|
||||||
|
assert(result.patterns.length > 0, 'Should detect AI comment pattern');
|
||||||
|
assert(result.patterns.some(p => p.description.includes('AI comment')));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('detectPatterns finds critical SQL injection', () => {
|
||||||
|
const code = `
|
||||||
|
const query = "SELECT * FROM users WHERE id = " + userId;
|
||||||
|
db.execute(query);
|
||||||
|
`;
|
||||||
|
const result = detectPatterns(code);
|
||||||
|
assert(result.patterns.some(p => p.severity === 'critical' && p.description.includes('SQL injection')));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('detectPatterns finds hardcoded credentials', () => {
|
||||||
|
const code = `const apiKey = "sk-1234567890abcdef";
|
||||||
|
const password = "secret123";
|
||||||
|
`;
|
||||||
|
const result = detectPatterns(code);
|
||||||
|
assert(result.patterns.some(p => p.severity === 'critical' && p.description.includes('credential')));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('detectPatterns finds eval usage', () => {
|
||||||
|
const code = `eval(userInput);`;
|
||||||
|
const result = detectPatterns(code);
|
||||||
|
assert(result.patterns.some(p => p.severity === 'critical' && p.description.includes('eval')));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('analyzeFileContent analyzes a single file', () => {
|
||||||
|
const code = `const password = "hardcoded123";
|
||||||
|
// This function handles authentication
|
||||||
|
function auth() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
const result = analyzeFileContent('test.js', code);
|
||||||
|
assert(result !== null);
|
||||||
|
assert(result!.trustScore < 100); // should have deductions
|
||||||
|
assert(result!.detection.patterns.length > 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('analyzeDirectory returns summary', () => {
|
||||||
|
// This test runs against the actual src directory
|
||||||
|
const summary = analyzeDirectory('./src');
|
||||||
|
assert(summary.scannedFiles > 0);
|
||||||
|
assert(summary.totalLines > 0);
|
||||||
|
assert(summary.averageTrustScore >= 0 && summary.averageTrustScore <= 100);
|
||||||
|
assert(['low', 'medium', 'high', 'critical'].includes(summary.riskLevel));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getSeverityCounts aggregates correctly', () => {
|
||||||
|
const patterns: PatternMatch[] = [
|
||||||
|
{ severity: 'low', line: 1, description: 'x', pattern: 'x' },
|
||||||
|
{ severity: 'low', line: 2, description: 'x', pattern: 'x' },
|
||||||
|
{ severity: 'high', line: 3, description: 'x', pattern: 'x' },
|
||||||
|
];
|
||||||
|
const counts = getSeverityCounts(patterns);
|
||||||
|
assert.strictEqual(counts.low, 2);
|
||||||
|
assert.strictEqual(counts.medium, 0);
|
||||||
|
assert.strictEqual(counts.high, 1);
|
||||||
|
assert.strictEqual(counts.critical, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('clean code gets high trust score', () => {
|
||||||
|
const code = `
|
||||||
|
function add(a, b) {
|
||||||
|
return a + b;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
const result = detectPatterns(code);
|
||||||
|
assert(result.confidence < 0.5); // low confidence of AI generation
|
||||||
|
const file = analyzeFileContent('clean.js', code);
|
||||||
|
assert(file !== null);
|
||||||
|
assert(file!.trustScore >= 90, `Expected >= 90, got ${file!.trustScore}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('trust score penalizes multiple issues', () => {
|
||||||
|
const code = `
|
||||||
|
// This function handles user authentication
|
||||||
|
function login(userId, password) {
|
||||||
|
const query = "SELECT * FROM users WHERE id = " + userId;
|
||||||
|
eval(password);
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
const file = analyzeFileContent('bad.js', code);
|
||||||
|
assert(file !== null);
|
||||||
|
assert(file!.trustScore < 50, `Expected < 50, got ${file!.trustScore}`);
|
||||||
|
assert(file!.detection.patterns.some(p => p.severity === 'critical'));
|
||||||
|
});
|
||||||
+208
@@ -0,0 +1,208 @@
|
|||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
import { detectPatterns, getSeverityCounts, DetectionResult } from './detectors.js';
|
||||||
|
|
||||||
|
export interface FileResult {
|
||||||
|
file: string;
|
||||||
|
lines: number;
|
||||||
|
detection: DetectionResult;
|
||||||
|
trustScore: number; // 0-100, higher = more trustworthy
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AnalysisConfig {
|
||||||
|
include: string[];
|
||||||
|
exclude: string[];
|
||||||
|
maxSize: number; // max file size in bytes
|
||||||
|
minScore: number; // minimum trust score threshold
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AnalysisSummary {
|
||||||
|
totalFiles: number;
|
||||||
|
scannedFiles: number;
|
||||||
|
totalLines: number;
|
||||||
|
issuesFound: number;
|
||||||
|
criticalIssues: number;
|
||||||
|
highIssues: number;
|
||||||
|
mediumIssues: number;
|
||||||
|
lowIssues: number;
|
||||||
|
averageTrustScore: number;
|
||||||
|
filesWithIssues: number;
|
||||||
|
cleanFiles: number;
|
||||||
|
riskLevel: 'low' | 'medium' | 'high' | 'critical';
|
||||||
|
fileResults: FileResult[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_CONFIG: AnalysisConfig = {
|
||||||
|
include: ['.js', '.ts', '.jsx', '.tsx', '.py', '.java', '.go', '.rb', '.php', '.cs', '.cpp', '.c', '.swift', '.kt', '.rs'],
|
||||||
|
exclude: ['node_modules', 'dist', '.git', 'vendor', 'build', 'coverage', '.next', 'out', '__pycache__', '.venv', 'venv'],
|
||||||
|
maxSize: 1024 * 1024, // 1MB
|
||||||
|
minScore: 50
|
||||||
|
};
|
||||||
|
|
||||||
|
function shouldScan(filePath: string, config: AnalysisConfig): boolean {
|
||||||
|
const ext = path.extname(filePath).toLowerCase();
|
||||||
|
if (!config.include.includes(ext)) return false;
|
||||||
|
|
||||||
|
const parts = filePath.split(path.sep);
|
||||||
|
for (const ex of config.exclude) {
|
||||||
|
if (parts.some(part => part === ex)) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateTrustScore(detection: DetectionResult, lines: number): number {
|
||||||
|
const counts = getSeverityCounts(detection.patterns);
|
||||||
|
|
||||||
|
// Base score: 100
|
||||||
|
let score = 100;
|
||||||
|
|
||||||
|
// Deduct for each severity level
|
||||||
|
score -= counts.critical * 25;
|
||||||
|
score -= counts.high * 15;
|
||||||
|
score -= counts.medium * 8;
|
||||||
|
score -= counts.low * 2;
|
||||||
|
|
||||||
|
// Deduct for AI pattern density (patterns per 100 lines)
|
||||||
|
const density = (detection.patterns.length / Math.max(lines, 1)) * 100;
|
||||||
|
score -= Math.min(density * 3, 30); // cap at 30 points
|
||||||
|
|
||||||
|
// Boost for very clean files
|
||||||
|
if (detection.patterns.length === 0) {
|
||||||
|
score += 5; // bonus for clean file
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clamp to 0-100
|
||||||
|
return Math.max(0, Math.min(100, Math.round(score)));
|
||||||
|
}
|
||||||
|
|
||||||
|
function analyzeFile(filePath: string, basePath: string): FileResult | null {
|
||||||
|
try {
|
||||||
|
const stat = fs.statSync(filePath);
|
||||||
|
if (stat.size > DEFAULT_CONFIG.maxSize) {
|
||||||
|
return null; // skip large files
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = fs.readFileSync(filePath, 'utf-8');
|
||||||
|
const lines = content.split('\n').length;
|
||||||
|
const detection = detectPatterns(content);
|
||||||
|
const trustScore = calculateTrustScore(detection, lines);
|
||||||
|
|
||||||
|
return {
|
||||||
|
file: path.relative(basePath, filePath),
|
||||||
|
lines,
|
||||||
|
detection,
|
||||||
|
trustScore
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
return null; // skip unreadable files
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function analyzeDirectory(dirPath: string, config: Partial<AnalysisConfig> = {}): AnalysisSummary {
|
||||||
|
const fullConfig = { ...DEFAULT_CONFIG, ...config };
|
||||||
|
const results: FileResult[] = [];
|
||||||
|
let totalFiles = 0;
|
||||||
|
let totalLines = 0;
|
||||||
|
|
||||||
|
function scanDir(dir: string) {
|
||||||
|
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
||||||
|
|
||||||
|
for (const entry of entries) {
|
||||||
|
const fullPath = path.join(dir, entry.name);
|
||||||
|
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
let skip = false;
|
||||||
|
for (const ex of fullConfig.exclude) {
|
||||||
|
if (entry.name === ex) {
|
||||||
|
skip = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!skip) {
|
||||||
|
scanDir(fullPath);
|
||||||
|
}
|
||||||
|
} else if (entry.isFile()) {
|
||||||
|
totalFiles++;
|
||||||
|
if (shouldScan(fullPath, fullConfig)) {
|
||||||
|
const result = analyzeFile(fullPath, dirPath);
|
||||||
|
if (result) {
|
||||||
|
results.push(result);
|
||||||
|
totalLines += result.lines;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
scanDir(dirPath);
|
||||||
|
|
||||||
|
// Calculate summary
|
||||||
|
let issuesFound = 0;
|
||||||
|
let criticalIssues = 0;
|
||||||
|
let highIssues = 0;
|
||||||
|
let mediumIssues = 0;
|
||||||
|
let lowIssues = 0;
|
||||||
|
let filesWithIssues = 0;
|
||||||
|
let totalTrustScore = 0;
|
||||||
|
|
||||||
|
for (const result of results) {
|
||||||
|
const counts = getSeverityCounts(result.detection.patterns);
|
||||||
|
issuesFound += result.detection.patterns.length;
|
||||||
|
criticalIssues += counts.critical || 0;
|
||||||
|
highIssues += counts.high || 0;
|
||||||
|
mediumIssues += counts.medium || 0;
|
||||||
|
lowIssues += counts.low || 0;
|
||||||
|
|
||||||
|
if (result.detection.patterns.length > 0) {
|
||||||
|
filesWithIssues++;
|
||||||
|
}
|
||||||
|
totalTrustScore += result.trustScore;
|
||||||
|
}
|
||||||
|
|
||||||
|
const averageTrustScore = results.length > 0 ? Math.round(totalTrustScore / results.length) : 100;
|
||||||
|
|
||||||
|
// Determine risk level
|
||||||
|
let riskLevel: 'low' | 'medium' | 'high' | 'critical' = 'low';
|
||||||
|
if (criticalIssues > 0) {
|
||||||
|
riskLevel = 'critical';
|
||||||
|
} else if (highIssues > 0 || averageTrustScore < 50) {
|
||||||
|
riskLevel = 'high';
|
||||||
|
} else if (mediumIssues > 0 || averageTrustScore < 70) {
|
||||||
|
riskLevel = 'medium';
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
totalFiles,
|
||||||
|
scannedFiles: results.length,
|
||||||
|
totalLines,
|
||||||
|
issuesFound,
|
||||||
|
criticalIssues,
|
||||||
|
highIssues,
|
||||||
|
mediumIssues,
|
||||||
|
lowIssues,
|
||||||
|
averageTrustScore,
|
||||||
|
filesWithIssues,
|
||||||
|
cleanFiles: results.length - filesWithIssues,
|
||||||
|
riskLevel,
|
||||||
|
fileResults: results.sort((a, b) => a.trustScore - b.trustScore) // sort by trust score ascending
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function analyzeFileContent(filePath: string, content?: string): FileResult | null {
|
||||||
|
try {
|
||||||
|
const actualContent = content || fs.readFileSync(filePath, 'utf-8');
|
||||||
|
const lines = actualContent.split('\n').length;
|
||||||
|
const detection = detectPatterns(actualContent);
|
||||||
|
const trustScore = calculateTrustScore(detection, lines);
|
||||||
|
|
||||||
|
return {
|
||||||
|
file: path.basename(filePath),
|
||||||
|
lines,
|
||||||
|
detection,
|
||||||
|
trustScore
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,261 @@
|
|||||||
|
// AI-generated code pattern detectors
|
||||||
|
// Heuristic-based detection of common AI coding tool patterns
|
||||||
|
|
||||||
|
export interface PatternMatch {
|
||||||
|
pattern: string;
|
||||||
|
severity: 'low' | 'medium' | 'high' | 'critical';
|
||||||
|
description: string;
|
||||||
|
line: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DetectionResult {
|
||||||
|
confidence: number; // 0-1
|
||||||
|
patterns: PatternMatch[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Known AI-generated code signatures
|
||||||
|
const AI_PATTERNS = [
|
||||||
|
// Overly verbose comments explaining obvious code
|
||||||
|
{
|
||||||
|
regex: /\/\/\s*(This function|This method|This class|This variable|This code|This loop|This block)/i,
|
||||||
|
severity: 'low' as const,
|
||||||
|
description: 'Boilerplate AI comment pattern'
|
||||||
|
},
|
||||||
|
// Excessive inline documentation
|
||||||
|
{
|
||||||
|
regex: /\/\/.\s*(defines|creates|initializes|sets up|handles|manages|processes|implements)/i,
|
||||||
|
severity: 'low' as const,
|
||||||
|
description: 'Explanatory comment pattern typical of AI output'
|
||||||
|
},
|
||||||
|
// Generic error handling without context
|
||||||
|
{
|
||||||
|
regex: /catch\s*\([^)]*\)\s*\{\s*\n?\s*(console\.error|console\.log|print)\s*\(/i,
|
||||||
|
severity: 'medium' as const,
|
||||||
|
description: 'Generic catch-all error logging without meaningful handling'
|
||||||
|
},
|
||||||
|
// TODO/FIXME comments from incomplete generation
|
||||||
|
{
|
||||||
|
regex: /(TODO|FIXME|HACK|XXX|NOTE|REVIEW)\s*:/i,
|
||||||
|
severity: 'medium' as const,
|
||||||
|
description: 'Incomplete implementation marker'
|
||||||
|
},
|
||||||
|
// Suspicious variable names
|
||||||
|
{
|
||||||
|
regex: /\b(data|result|output|input|value|item|temp|tmp|obj|arr|str|num|val|func|cb)\d*\b/g,
|
||||||
|
severity: 'low' as const,
|
||||||
|
description: 'Generic variable naming pattern'
|
||||||
|
},
|
||||||
|
// Overly defensive null checks in sequence
|
||||||
|
{
|
||||||
|
regex: /if\s*\(\s*[^)]+\s*!==?\s*(null|undefined|None)\s*\)\s*\{[^}]*\}\s*(?:else\s*\{[^}]*\})?/g,
|
||||||
|
severity: 'low' as const,
|
||||||
|
description: 'Defensive null checking pattern'
|
||||||
|
},
|
||||||
|
// Copy-paste license headers without context
|
||||||
|
{
|
||||||
|
regex: /MIT License|Apache License|BSD License|GNU General Public License/i,
|
||||||
|
severity: 'low' as const,
|
||||||
|
description: 'License header (may be auto-inserted)'
|
||||||
|
},
|
||||||
|
// JSDoc with AI-style verbose descriptions
|
||||||
|
{
|
||||||
|
regex: /\/\*\*\s*\n\s*\*\s*(This is|This function|A utility|Helper function|Main function)/i,
|
||||||
|
severity: 'low' as const,
|
||||||
|
description: 'Verbose JSDoc pattern'
|
||||||
|
},
|
||||||
|
// Suspicious date handling
|
||||||
|
{
|
||||||
|
regex: /new\s+Date\s*\(\s*\)\s*\.[a-zA-Z]+\s*\(/i,
|
||||||
|
severity: 'low' as const,
|
||||||
|
description: 'Date manipulation without timezone consideration'
|
||||||
|
},
|
||||||
|
// JSON.parse without try-catch
|
||||||
|
{
|
||||||
|
regex: /JSON\.parse\s*\([^)]+\)\s*(?!\s*catch)/i,
|
||||||
|
severity: 'high' as const,
|
||||||
|
description: 'JSON.parse without error handling — common AI-generated security risk'
|
||||||
|
},
|
||||||
|
// eval or Function constructor usage
|
||||||
|
{
|
||||||
|
regex: /\beval\s*\(|new\s+Function\s*\(/i,
|
||||||
|
severity: 'critical' as const,
|
||||||
|
description: 'Dangerous eval/Function constructor usage'
|
||||||
|
},
|
||||||
|
// SQL string concatenation
|
||||||
|
{
|
||||||
|
regex: /(?:SELECT|INSERT|UPDATE|DELETE).*(?:\+|\$\{|`[^`]*\$\{)/i,
|
||||||
|
severity: 'critical' as const,
|
||||||
|
description: 'Potential SQL injection via string concatenation'
|
||||||
|
},
|
||||||
|
// Hardcoded credentials
|
||||||
|
{
|
||||||
|
regex: /(?:password|secret|token|api_key|apikey|access_token)\s*[:=]\s*["'][^"']{4,}["']/i,
|
||||||
|
severity: 'critical' as const,
|
||||||
|
description: 'Hardcoded credential detected'
|
||||||
|
},
|
||||||
|
// Insecure randomness
|
||||||
|
{
|
||||||
|
regex: /Math\.random\s*\(\s*\)/i,
|
||||||
|
severity: 'medium' as const,
|
||||||
|
description: 'Math.random() used for security-sensitive operation'
|
||||||
|
},
|
||||||
|
// Prototype pollution
|
||||||
|
{
|
||||||
|
regex: /Object\.prototype\.\w+\s*=/i,
|
||||||
|
severity: 'high' as const,
|
||||||
|
description: 'Prototype modification — potential prototype pollution'
|
||||||
|
},
|
||||||
|
// Disabled security (unsafe-inline, etc.)
|
||||||
|
{
|
||||||
|
regex: /unsafe-inline|unsafe-eval|'unsafe-inline'|'unsafe-eval'/i,
|
||||||
|
severity: 'high' as const,
|
||||||
|
description: 'Disabled security policy in configuration'
|
||||||
|
},
|
||||||
|
// Clipboard access without permission check
|
||||||
|
{
|
||||||
|
regex: /navigator\.clipboard\.writeText|document\.execCommand\s*\(\s*['"]copy['"]/i,
|
||||||
|
severity: 'medium' as const,
|
||||||
|
description: 'Clipboard access without explicit permission handling'
|
||||||
|
},
|
||||||
|
// File path traversal
|
||||||
|
{
|
||||||
|
regex: /(?:readFile|writeFile|createReadStream|createWriteStream)\s*\(\s*[^)]*(?:\+|\$\{|req\.|request\.)/i,
|
||||||
|
severity: 'high' as const,
|
||||||
|
description: 'Potential path traversal via user input'
|
||||||
|
},
|
||||||
|
// Missing CSRF protection
|
||||||
|
{
|
||||||
|
regex: /fetch\s*\(\s*[^)]*\)\s*\{[^}]*method\s*:\s*['"]POST['"]/i,
|
||||||
|
severity: 'medium' as const,
|
||||||
|
description: 'POST request without visible CSRF token'
|
||||||
|
},
|
||||||
|
// InnerHTML with user input
|
||||||
|
{
|
||||||
|
regex: /\.innerHTML\s*=\s*[^;]*(?:\+|\$\{|req\.|request\.)/i,
|
||||||
|
severity: 'high' as const,
|
||||||
|
description: 'XSS risk: innerHTML with user-controlled input'
|
||||||
|
},
|
||||||
|
// Regex DoS
|
||||||
|
{
|
||||||
|
regex: /\([^)]*\+\+?[^)]*\)[\*\+]\+?/,
|
||||||
|
severity: 'medium' as const,
|
||||||
|
description: 'Potentially catastrophic regex (ReDoS risk)'
|
||||||
|
},
|
||||||
|
// Weak crypto
|
||||||
|
{
|
||||||
|
regex: /md5\s*\(|sha1\s*\(|createHash\s*\(\s*['"]md5['"]/i,
|
||||||
|
severity: 'high' as const,
|
||||||
|
description: 'Weak cryptographic hash function'
|
||||||
|
},
|
||||||
|
// Process exit in error handler
|
||||||
|
{
|
||||||
|
regex: /process\.exit\s*\(/i,
|
||||||
|
severity: 'medium' as const,
|
||||||
|
description: 'Process termination in error path'
|
||||||
|
},
|
||||||
|
// Unhandled promise rejection pattern
|
||||||
|
{
|
||||||
|
regex: /new\s+Promise\s*\([^)]*\)\s*\{[^}]*\}(?!\s*catch)/i,
|
||||||
|
severity: 'medium' as const,
|
||||||
|
description: 'Promise without catch or error handler'
|
||||||
|
},
|
||||||
|
// Arbitrary command execution
|
||||||
|
{
|
||||||
|
regex: /exec\s*\(|execSync\s*\(|spawn\s*\(\s*[^)]*(?:\+|\$\{|req\.|request\.)/i,
|
||||||
|
severity: 'critical' as const,
|
||||||
|
description: 'Command execution with user input — RCE risk'
|
||||||
|
},
|
||||||
|
// Disabled SSL verification
|
||||||
|
{
|
||||||
|
regex: /rejectUnauthorized\s*:\s*false|NODE_TLS_REJECT_UNAUTHORIZED\s*=\s*['"]0['"]/i,
|
||||||
|
severity: 'critical' as const,
|
||||||
|
description: 'SSL certificate verification disabled'
|
||||||
|
},
|
||||||
|
// Open redirect
|
||||||
|
{
|
||||||
|
regex: /res\.redirect\s*\(\s*[^)]*(?:\+|req\.|request\.|\$\{)/i,
|
||||||
|
severity: 'high' as const,
|
||||||
|
description: 'Open redirect via user input'
|
||||||
|
},
|
||||||
|
// Information disclosure
|
||||||
|
{
|
||||||
|
regex: /res\.json\s*\(\s*\{[^}]*error[^}]*\}\s*\)/i,
|
||||||
|
severity: 'medium' as const,
|
||||||
|
description: 'Verbose error response may leak sensitive information'
|
||||||
|
},
|
||||||
|
// Race condition in file operations
|
||||||
|
{
|
||||||
|
regex: /access\s*\([^)]+\)\s*\{[^}]*readFile|writeFile/i,
|
||||||
|
severity: 'medium' as const,
|
||||||
|
description: 'Time-of-check time-of-use (TOCTOU) race condition'
|
||||||
|
},
|
||||||
|
// Missing authorization check
|
||||||
|
{
|
||||||
|
regex: /app\.(get|post|put|delete|patch)\s*\([^)]+\)\s*,\s*\(?\s*req\s*,\s*res\s*\)?\s*=>/i,
|
||||||
|
severity: 'medium' as const,
|
||||||
|
description: 'Route handler without visible authorization middleware'
|
||||||
|
},
|
||||||
|
// Mass assignment
|
||||||
|
{
|
||||||
|
regex: /Object\.assign\s*\(\s*[^,]+,\s*req\.(body|query|params)/i,
|
||||||
|
severity: 'high' as const,
|
||||||
|
description: 'Mass assignment from user input'
|
||||||
|
},
|
||||||
|
// Timing attack vulnerability
|
||||||
|
{
|
||||||
|
regex: /===\s*['"][^'"]+['"]/i,
|
||||||
|
severity: 'low' as const,
|
||||||
|
description: 'String comparison may be vulnerable to timing attacks'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export function detectPatterns(content: string): DetectionResult {
|
||||||
|
const patterns: PatternMatch[] = [];
|
||||||
|
const lines = content.split('\n');
|
||||||
|
|
||||||
|
let aiScore = 0;
|
||||||
|
let securityScore = 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < lines.length; i++) {
|
||||||
|
const line = lines[i];
|
||||||
|
const lineNum = i + 1;
|
||||||
|
|
||||||
|
for (const pattern of AI_PATTERNS) {
|
||||||
|
const regex = new RegExp(pattern.regex.source, 'i');
|
||||||
|
if (regex.test(line)) {
|
||||||
|
patterns.push({
|
||||||
|
pattern: pattern.regex.source,
|
||||||
|
severity: pattern.severity,
|
||||||
|
description: pattern.description,
|
||||||
|
line: lineNum
|
||||||
|
});
|
||||||
|
|
||||||
|
if (['low', 'medium'].includes(pattern.severity)) {
|
||||||
|
aiScore += 0.05;
|
||||||
|
} else {
|
||||||
|
securityScore += 0.15;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize scores
|
||||||
|
aiScore = Math.min(aiScore, 1.0);
|
||||||
|
securityScore = Math.min(securityScore, 1.0);
|
||||||
|
|
||||||
|
// Combined confidence: weighted toward security issues
|
||||||
|
const confidence = (aiScore * 0.3) + (securityScore * 0.7);
|
||||||
|
|
||||||
|
return {
|
||||||
|
confidence: Math.min(confidence, 1.0),
|
||||||
|
patterns
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getSeverityCounts(patterns: PatternMatch[]): Record<string, number> {
|
||||||
|
const counts: Record<string, number> = { low: 0, medium: 0, high: 0, critical: 0 };
|
||||||
|
for (const p of patterns) {
|
||||||
|
counts[p.severity] = (counts[p.severity] || 0) + 1;
|
||||||
|
}
|
||||||
|
return counts;
|
||||||
|
}
|
||||||
+168
@@ -0,0 +1,168 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
import { analyzeDirectory, analyzeFileContent, AnalysisSummary } from './analyzer.js';
|
||||||
|
import { generateConsoleReport, generateJSONReport, generateMarkdownReport } from './report.js';
|
||||||
|
|
||||||
|
interface CliOptions {
|
||||||
|
target: string;
|
||||||
|
format: 'console' | 'json' | 'markdown';
|
||||||
|
output?: string;
|
||||||
|
include?: string[];
|
||||||
|
exclude?: string[];
|
||||||
|
threshold?: number;
|
||||||
|
help: boolean;
|
||||||
|
version: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseArgs(): CliOptions {
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
const options: CliOptions = {
|
||||||
|
target: '.',
|
||||||
|
format: 'console',
|
||||||
|
help: false,
|
||||||
|
version: false
|
||||||
|
};
|
||||||
|
|
||||||
|
for (let i = 0; i < args.length; i++) {
|
||||||
|
const arg = args[i];
|
||||||
|
|
||||||
|
if (arg === '--help' || arg === '-h') {
|
||||||
|
options.help = true;
|
||||||
|
} else if (arg === '--version' || arg === '-v') {
|
||||||
|
options.version = true;
|
||||||
|
} else if (arg === '--format' || arg === '-f') {
|
||||||
|
const format = args[++i];
|
||||||
|
if (format === 'json' || format === 'markdown' || format === 'console') {
|
||||||
|
options.format = format;
|
||||||
|
}
|
||||||
|
} else if (arg === '--output' || arg === '-o') {
|
||||||
|
options.output = args[++i];
|
||||||
|
} else if (arg === '--include') {
|
||||||
|
options.include = args[++i].split(',').map((s: string) => s.trim().startsWith('.') ? s.trim() : '.' + s.trim());
|
||||||
|
} else if (arg === '--exclude') {
|
||||||
|
options.exclude = args[++i].split(',').map((s: string) => s.trim());
|
||||||
|
} else if (arg === '--threshold') {
|
||||||
|
options.threshold = parseInt(args[++i], 10);
|
||||||
|
} else if (!arg.startsWith('-')) {
|
||||||
|
options.target = arg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
function showHelp(): void {
|
||||||
|
console.log(`
|
||||||
|
ai-code-trust — Audit and assess trust in AI-generated code
|
||||||
|
|
||||||
|
USAGE:
|
||||||
|
ai-code-trust [target] [options]
|
||||||
|
|
||||||
|
OPTIONS:
|
||||||
|
-h, --help Show this help message
|
||||||
|
-v, --version Show version
|
||||||
|
-f, --format <type> Output format: console (default), json, markdown
|
||||||
|
-o, --output <file> Write output to file instead of stdout
|
||||||
|
--include <exts> Comma-separated extensions to scan (default: common code types)
|
||||||
|
--exclude <dirs> Comma-separated directories to exclude
|
||||||
|
--threshold <score> Minimum trust score threshold (0-100)
|
||||||
|
|
||||||
|
EXAMPLES:
|
||||||
|
ai-code-trust ./src
|
||||||
|
ai-code-trust . --format json --output report.json
|
||||||
|
ai-code-trust ./src --include .ts,.js --exclude node_modules,dist
|
||||||
|
ai-code-trust ./src --threshold 60
|
||||||
|
|
||||||
|
DESCRIPTION:
|
||||||
|
Scans code for AI-generation patterns and security vulnerabilities,
|
||||||
|
assigns trust scores (0-100), and generates actionable reports.
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function showVersion(): void {
|
||||||
|
console.log('ai-code-trust v1.0.0');
|
||||||
|
}
|
||||||
|
|
||||||
|
function main(): void {
|
||||||
|
const options = parseArgs();
|
||||||
|
|
||||||
|
if (options.help) {
|
||||||
|
showHelp();
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.version) {
|
||||||
|
showVersion();
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetPath = path.resolve(options.target);
|
||||||
|
|
||||||
|
if (!fs.existsSync(targetPath)) {
|
||||||
|
console.error(`Error: Target path does not exist: ${targetPath}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let summary: AnalysisSummary;
|
||||||
|
|
||||||
|
if (fs.statSync(targetPath).isFile()) {
|
||||||
|
const fileResult = analyzeFileContent(targetPath);
|
||||||
|
if (!fileResult) {
|
||||||
|
console.error('Error: Could not analyze file');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
summary = {
|
||||||
|
totalFiles: 1,
|
||||||
|
scannedFiles: 1,
|
||||||
|
totalLines: fileResult.lines,
|
||||||
|
issuesFound: fileResult.detection.patterns.length,
|
||||||
|
criticalIssues: fileResult.detection.patterns.filter(p => p.severity === 'critical').length,
|
||||||
|
highIssues: fileResult.detection.patterns.filter(p => p.severity === 'high').length,
|
||||||
|
mediumIssues: fileResult.detection.patterns.filter(p => p.severity === 'medium').length,
|
||||||
|
lowIssues: fileResult.detection.patterns.filter(p => p.severity === 'low').length,
|
||||||
|
averageTrustScore: fileResult.trustScore,
|
||||||
|
filesWithIssues: fileResult.detection.patterns.length > 0 ? 1 : 0,
|
||||||
|
cleanFiles: fileResult.detection.patterns.length > 0 ? 0 : 1,
|
||||||
|
riskLevel: fileResult.trustScore < 40 ? 'critical' : fileResult.trustScore < 60 ? 'high' : fileResult.trustScore < 80 ? 'medium' : 'low',
|
||||||
|
fileResults: [fileResult]
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
const config: { include?: string[]; exclude?: string[]; minScore?: number } = {};
|
||||||
|
if (options.include) config.include = options.include;
|
||||||
|
if (options.exclude) config.exclude = options.exclude;
|
||||||
|
if (options.threshold !== undefined) config.minScore = options.threshold;
|
||||||
|
|
||||||
|
summary = analyzeDirectory(targetPath, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
let output: string;
|
||||||
|
|
||||||
|
switch (options.format) {
|
||||||
|
case 'json':
|
||||||
|
output = generateJSONReport(summary);
|
||||||
|
break;
|
||||||
|
case 'markdown':
|
||||||
|
output = generateMarkdownReport(summary);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
output = generateConsoleReport(summary);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.output) {
|
||||||
|
fs.writeFileSync(path.resolve(options.output), output, 'utf-8');
|
||||||
|
console.log(`Report written to: ${options.output}`);
|
||||||
|
} else {
|
||||||
|
console.log(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exit with non-zero if critical issues found
|
||||||
|
if (summary.criticalIssues > 0) {
|
||||||
|
process.exit(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
+233
@@ -0,0 +1,233 @@
|
|||||||
|
import { AnalysisSummary, FileResult } from './analyzer.js';
|
||||||
|
import { PatternMatch } from './detectors.js';
|
||||||
|
|
||||||
|
export function generateConsoleReport(summary: AnalysisSummary): string {
|
||||||
|
const lines: string[] = [];
|
||||||
|
|
||||||
|
lines.push('═'.repeat(60));
|
||||||
|
lines.push(' AI CODE TRUST AUDIT REPORT');
|
||||||
|
lines.push('═'.repeat(60));
|
||||||
|
lines.push('');
|
||||||
|
|
||||||
|
// Risk level with color indicator
|
||||||
|
const riskEmoji = {
|
||||||
|
low: '✅',
|
||||||
|
medium: '⚠️ ',
|
||||||
|
high: '🔴',
|
||||||
|
critical: '☠️ '
|
||||||
|
};
|
||||||
|
|
||||||
|
lines.push(`OVERALL RISK LEVEL: ${riskEmoji[summary.riskLevel]} ${summary.riskLevel.toUpperCase()}`);
|
||||||
|
lines.push(`Average Trust Score: ${summary.averageTrustScore}/100`);
|
||||||
|
lines.push('');
|
||||||
|
lines.push('─'.repeat(60));
|
||||||
|
lines.push('SUMMARY');
|
||||||
|
lines.push('─'.repeat(60));
|
||||||
|
lines.push(` Files scanned: ${summary.scannedFiles} / ${summary.totalFiles}`);
|
||||||
|
lines.push(` Total lines: ${summary.totalLines.toLocaleString()}`);
|
||||||
|
lines.push(` Files with issues: ${summary.filesWithIssues}`);
|
||||||
|
lines.push(` Clean files: ${summary.cleanFiles}`);
|
||||||
|
lines.push(` Issues found: ${summary.issuesFound}`);
|
||||||
|
lines.push('');
|
||||||
|
lines.push('─'.repeat(60));
|
||||||
|
lines.push('ISSUE BREAKDOWN');
|
||||||
|
lines.push('─'.repeat(60));
|
||||||
|
lines.push(` 🔴 Critical: ${summary.criticalIssues}`);
|
||||||
|
lines.push(` 🔴 High: ${summary.highIssues}`);
|
||||||
|
lines.push(` ⚠️ Medium: ${summary.mediumIssues}`);
|
||||||
|
lines.push(` ℹ️ Low: ${summary.lowIssues}`);
|
||||||
|
lines.push('');
|
||||||
|
|
||||||
|
// Top files by risk
|
||||||
|
if (summary.fileResults.length > 0) {
|
||||||
|
lines.push('─'.repeat(60));
|
||||||
|
lines.push('FILES BY TRUST SCORE (lowest first)');
|
||||||
|
lines.push('─'.repeat(60));
|
||||||
|
|
||||||
|
const topFiles = summary.fileResults.slice(0, 20); // show top 20
|
||||||
|
for (const file of topFiles) {
|
||||||
|
const scoreEmoji = file.trustScore >= 80 ? '✅' : file.trustScore >= 60 ? '⚠️ ' : file.trustScore >= 40 ? '🔴' : '☠️ ';
|
||||||
|
lines.push(` ${scoreEmoji} ${file.trustScore.toString().padStart(3)} ${file.file} (${file.lines} lines, ${file.detection.patterns.length} issues)`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (summary.fileResults.length > 20) {
|
||||||
|
lines.push(` ... and ${summary.fileResults.length - 20} more files`);
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push('');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detailed issue report for critical and high
|
||||||
|
const criticalHighFiles = summary.fileResults.filter(
|
||||||
|
f => f.detection.patterns.some(p => p.severity === 'critical' || p.severity === 'high')
|
||||||
|
);
|
||||||
|
|
||||||
|
if (criticalHighFiles.length > 0) {
|
||||||
|
lines.push('─'.repeat(60));
|
||||||
|
lines.push('CRITICAL & HIGH SEVERITY DETAILS');
|
||||||
|
lines.push('─'.repeat(60));
|
||||||
|
|
||||||
|
for (const file of criticalHighFiles) {
|
||||||
|
const criticalIssues = file.detection.patterns.filter(p => p.severity === 'critical' || p.severity === 'high');
|
||||||
|
if (criticalIssues.length > 0) {
|
||||||
|
lines.push(`\n ${file.file} (trust score: ${file.trustScore})`);
|
||||||
|
for (const issue of criticalIssues) {
|
||||||
|
const severityEmoji = issue.severity === 'critical' ? '☠️ ' : '🔴';
|
||||||
|
lines.push(` ${severityEmoji} [${issue.severity.toUpperCase()}] Line ${issue.line}: ${issue.description}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push('');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recommendations
|
||||||
|
lines.push('─'.repeat(60));
|
||||||
|
lines.push('RECOMMENDATIONS');
|
||||||
|
lines.push('─'.repeat(60));
|
||||||
|
|
||||||
|
if (summary.criticalIssues > 0) {
|
||||||
|
lines.push(' ☠️ CRITICAL: Fix all critical issues immediately before deploying.');
|
||||||
|
}
|
||||||
|
if (summary.highIssues > 0) {
|
||||||
|
lines.push(' 🔴 HIGH: Review and fix high-severity issues before code review.');
|
||||||
|
}
|
||||||
|
if (summary.mediumIssues > 0) {
|
||||||
|
lines.push(' ⚠️ MEDIUM: Address medium issues during your next refactor cycle.');
|
||||||
|
}
|
||||||
|
if (summary.averageTrustScore < 50) {
|
||||||
|
lines.push(' 📋 Consider a full manual code review of this codebase.');
|
||||||
|
} else if (summary.averageTrustScore < 70) {
|
||||||
|
lines.push(' 📋 This codebase shows moderate AI-generation signals. Review recommended.');
|
||||||
|
} else {
|
||||||
|
lines.push(' ✅ This codebase scores well. Continue monitoring for new issues.');
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push('');
|
||||||
|
lines.push('═'.repeat(60));
|
||||||
|
lines.push('Generated by ai-code-trust v1.0.0');
|
||||||
|
lines.push('═'.repeat(60));
|
||||||
|
|
||||||
|
return lines.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateJSONReport(summary: AnalysisSummary): string {
|
||||||
|
const report = {
|
||||||
|
version: '1.0.0',
|
||||||
|
generatedAt: new Date().toISOString(),
|
||||||
|
summary: {
|
||||||
|
riskLevel: summary.riskLevel,
|
||||||
|
averageTrustScore: summary.averageTrustScore,
|
||||||
|
totalFiles: summary.totalFiles,
|
||||||
|
scannedFiles: summary.scannedFiles,
|
||||||
|
totalLines: summary.totalLines,
|
||||||
|
issuesFound: summary.issuesFound,
|
||||||
|
issueBreakdown: {
|
||||||
|
critical: summary.criticalIssues,
|
||||||
|
high: summary.highIssues,
|
||||||
|
medium: summary.mediumIssues,
|
||||||
|
low: summary.lowIssues
|
||||||
|
},
|
||||||
|
filesWithIssues: summary.filesWithIssues,
|
||||||
|
cleanFiles: summary.cleanFiles
|
||||||
|
},
|
||||||
|
files: summary.fileResults.map(f => ({
|
||||||
|
file: f.file,
|
||||||
|
lines: f.lines,
|
||||||
|
trustScore: f.trustScore,
|
||||||
|
issueCount: f.detection.patterns.length,
|
||||||
|
issues: f.detection.patterns.map(p => ({
|
||||||
|
line: p.line,
|
||||||
|
severity: p.severity,
|
||||||
|
description: p.description
|
||||||
|
}))
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
|
||||||
|
return JSON.stringify(report, null, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateMarkdownReport(summary: AnalysisSummary): string {
|
||||||
|
const lines: string[] = [];
|
||||||
|
|
||||||
|
lines.push('# AI Code Trust Audit Report');
|
||||||
|
lines.push('');
|
||||||
|
lines.push(`**Generated:** ${new Date().toISOString()}`);
|
||||||
|
lines.push(`**Risk Level:** ${summary.riskLevel.toUpperCase()}`);
|
||||||
|
lines.push(`**Average Trust Score:** ${summary.averageTrustScore}/100`);
|
||||||
|
lines.push('');
|
||||||
|
|
||||||
|
lines.push('## Summary');
|
||||||
|
lines.push('');
|
||||||
|
lines.push(`| Metric | Value |`);
|
||||||
|
lines.push(`|--------|-------|`);
|
||||||
|
lines.push(`| Files Scanned | ${summary.scannedFiles} / ${summary.totalFiles} |`);
|
||||||
|
lines.push(`| Total Lines | ${summary.totalLines.toLocaleString()} |`);
|
||||||
|
lines.push(`| Issues Found | ${summary.issuesFound} |`);
|
||||||
|
lines.push(`| Critical | ${summary.criticalIssues} |`);
|
||||||
|
lines.push(`| High | ${summary.highIssues} |`);
|
||||||
|
lines.push(`| Medium | ${summary.mediumIssues} |`);
|
||||||
|
lines.push(`| Low | ${summary.lowIssues} |`);
|
||||||
|
lines.push(`| Clean Files | ${summary.cleanFiles} |`);
|
||||||
|
lines.push('');
|
||||||
|
|
||||||
|
if (summary.fileResults.length > 0) {
|
||||||
|
lines.push('## Files by Trust Score');
|
||||||
|
lines.push('');
|
||||||
|
lines.push(`| Score | File | Lines | Issues |`);
|
||||||
|
lines.push(`|-------|------|-------|--------|`);
|
||||||
|
|
||||||
|
for (const file of summary.fileResults) {
|
||||||
|
lines.push(`| ${file.trustScore} | ${file.file} | ${file.lines} | ${file.detection.patterns.length} |`);
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push('');
|
||||||
|
}
|
||||||
|
|
||||||
|
const criticalHighFiles = summary.fileResults.filter(
|
||||||
|
f => f.detection.patterns.some(p => p.severity === 'critical' || p.severity === 'high')
|
||||||
|
);
|
||||||
|
|
||||||
|
if (criticalHighFiles.length > 0) {
|
||||||
|
lines.push('## Critical & High Severity Issues');
|
||||||
|
lines.push('');
|
||||||
|
|
||||||
|
for (const file of criticalHighFiles) {
|
||||||
|
const issues = file.detection.patterns.filter(p => p.severity === 'critical' || p.severity === 'high');
|
||||||
|
if (issues.length > 0) {
|
||||||
|
lines.push(`### ${file.file}`);
|
||||||
|
lines.push('');
|
||||||
|
for (const issue of issues) {
|
||||||
|
lines.push(`- **${issue.severity.toUpperCase()}** (Line ${issue.line}): ${issue.description}`);
|
||||||
|
}
|
||||||
|
lines.push('');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push('## Recommendations');
|
||||||
|
lines.push('');
|
||||||
|
|
||||||
|
if (summary.criticalIssues > 0) {
|
||||||
|
lines.push('- **CRITICAL**: Fix all critical issues immediately before deploying.');
|
||||||
|
}
|
||||||
|
if (summary.highIssues > 0) {
|
||||||
|
lines.push('- **HIGH**: Review and fix high-severity issues before code review.');
|
||||||
|
}
|
||||||
|
if (summary.mediumIssues > 0) {
|
||||||
|
lines.push('- **MEDIUM**: Address medium issues during your next refactor cycle.');
|
||||||
|
}
|
||||||
|
if (summary.averageTrustScore < 50) {
|
||||||
|
lines.push('- Consider a full manual code review of this codebase.');
|
||||||
|
} else if (summary.averageTrustScore < 70) {
|
||||||
|
lines.push('- This codebase shows moderate AI-generation signals. Review recommended.');
|
||||||
|
} else {
|
||||||
|
lines.push('- This codebase scores well. Continue monitoring for new issues.');
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push('');
|
||||||
|
lines.push('---');
|
||||||
|
lines.push('*Generated by ai-code-trust v1.0.0*');
|
||||||
|
|
||||||
|
return lines.join('\n');
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "Node16",
|
||||||
|
"moduleResolution": "Node16",
|
||||||
|
"outDir": "./dist",
|
||||||
|
"rootDir": "./src",
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"resolveJsonModule": true
|
||||||
|
},
|
||||||
|
"include": ["src/**/*"],
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user