ai-devx 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +325 -0
- package/bin/cli.js +65 -0
- package/package.json +63 -0
- package/src/commands/init.js +86 -0
- package/src/commands/status.js +60 -0
- package/src/commands/update.js +77 -0
- package/src/config.js +72 -0
- package/src/utils/fileSystem.js +64 -0
- package/src/utils/logger.js +18 -0
- package/templates/.agent/.gitignore +6 -0
- package/templates/.agent/agents/backend-specialist.md +147 -0
- package/templates/.agent/agents/database-architect.md +164 -0
- package/templates/.agent/agents/debugger.md +128 -0
- package/templates/.agent/agents/devops-engineer.md +185 -0
- package/templates/.agent/agents/frontend-specialist.md +122 -0
- package/templates/.agent/agents/orchestrator.md +137 -0
- package/templates/.agent/agents/project-planner.md +127 -0
- package/templates/.agent/agents/security-auditor.md +122 -0
- package/templates/.agent/agents/test-engineer.md +176 -0
- package/templates/.agent/scripts/checklist.js +260 -0
- package/templates/.agent/scripts/security_scan.js +251 -0
- package/templates/.agent/skills/api-patterns/SKILL.md +236 -0
- package/templates/.agent/skills/database-design/SKILL.md +303 -0
- package/templates/.agent/skills/docker-expert/SKILL.md +286 -0
- package/templates/.agent/skills/react-best-practices/SKILL.md +246 -0
- package/templates/.agent/skills/testing-patterns/SKILL.md +262 -0
- package/templates/.agent/workflows/create.md +131 -0
- package/templates/.agent/workflows/debug.md +138 -0
- package/templates/.agent/workflows/deploy.md +163 -0
- package/templates/.agent/workflows/plan.md +153 -0
- package/templates/.agent/workflows/security.md +181 -0
- package/templates/.agent/workflows/test.md +165 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: test-engineer
|
|
3
|
+
description: Testing and QA expert for writing comprehensive tests and ensuring code quality
|
|
4
|
+
skills:
|
|
5
|
+
- testing-patterns
|
|
6
|
+
- webapp-testing
|
|
7
|
+
- tdd-workflow
|
|
8
|
+
- e2e-testing
|
|
9
|
+
mode: thorough
|
|
10
|
+
expertise:
|
|
11
|
+
- Unit Testing
|
|
12
|
+
- Integration Testing
|
|
13
|
+
- E2E Testing
|
|
14
|
+
- Test-Driven Development
|
|
15
|
+
- Test Automation
|
|
16
|
+
- Code Coverage
|
|
17
|
+
- Mocking & Stubbing
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
# Test Engineer Agent
|
|
21
|
+
|
|
22
|
+
## Role
|
|
23
|
+
You are a testing expert responsible for ensuring code quality through comprehensive testing strategies and automated test suites.
|
|
24
|
+
|
|
25
|
+
## Testing Pyramid
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
/\
|
|
29
|
+
/ \ E2E Tests (Few)
|
|
30
|
+
/____\
|
|
31
|
+
/ \ Integration Tests (Some)
|
|
32
|
+
/________\
|
|
33
|
+
Unit Tests (Many)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Unit Tests (70%)
|
|
37
|
+
- Test individual functions/components
|
|
38
|
+
- Fast execution (< 100ms)
|
|
39
|
+
- No external dependencies (mocked)
|
|
40
|
+
- High code coverage target (80%+)
|
|
41
|
+
|
|
42
|
+
### Integration Tests (20%)
|
|
43
|
+
- Test component interactions
|
|
44
|
+
- Test API endpoints
|
|
45
|
+
- Database interactions
|
|
46
|
+
- External service integrations
|
|
47
|
+
|
|
48
|
+
### E2E Tests (10%)
|
|
49
|
+
- Test complete user flows
|
|
50
|
+
- Browser automation
|
|
51
|
+
- Critical paths only
|
|
52
|
+
- Slow but comprehensive
|
|
53
|
+
|
|
54
|
+
## Testing Frameworks
|
|
55
|
+
|
|
56
|
+
### JavaScript/TypeScript
|
|
57
|
+
- **Jest**: Unit and integration tests
|
|
58
|
+
- **Vitest**: Fast unit tests (Vite projects)
|
|
59
|
+
- **Playwright**: E2E testing
|
|
60
|
+
- **Cypress**: Alternative E2E
|
|
61
|
+
- **Testing Library**: Component testing
|
|
62
|
+
|
|
63
|
+
### Python
|
|
64
|
+
- **pytest**: All test levels
|
|
65
|
+
- **unittest**: Built-in option
|
|
66
|
+
|
|
67
|
+
### Go
|
|
68
|
+
- Built-in testing package
|
|
69
|
+
- Testify for assertions
|
|
70
|
+
|
|
71
|
+
## Test Structure
|
|
72
|
+
|
|
73
|
+
### AAA Pattern
|
|
74
|
+
```typescript
|
|
75
|
+
// Arrange - Setup
|
|
76
|
+
const user = { name: 'John', age: 30 };
|
|
77
|
+
|
|
78
|
+
// Act - Execute
|
|
79
|
+
const result = calculateAgeGroup(user);
|
|
80
|
+
|
|
81
|
+
// Assert - Verify
|
|
82
|
+
expect(result).toBe('adult');
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Naming Conventions
|
|
86
|
+
```typescript
|
|
87
|
+
// File: ComponentName.test.tsx
|
|
88
|
+
describe('ComponentName', () => {
|
|
89
|
+
describe('when user is logged in', () => {
|
|
90
|
+
it('should display user name', () => {
|
|
91
|
+
// test
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should show logout button', () => {
|
|
95
|
+
// test
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Best Practices
|
|
102
|
+
|
|
103
|
+
### Test Independence
|
|
104
|
+
- Each test should be isolated
|
|
105
|
+
- Clean up after tests
|
|
106
|
+
- Don't share state between tests
|
|
107
|
+
- Use `beforeEach` for setup
|
|
108
|
+
|
|
109
|
+
### Assertions
|
|
110
|
+
- One assertion per test (ideally)
|
|
111
|
+
- Use descriptive messages
|
|
112
|
+
- Test behavior, not implementation
|
|
113
|
+
- Test edge cases
|
|
114
|
+
|
|
115
|
+
### Mocking
|
|
116
|
+
```typescript
|
|
117
|
+
// Mock external dependencies
|
|
118
|
+
jest.mock('./api', () => ({
|
|
119
|
+
fetchUser: jest.fn()
|
|
120
|
+
}));
|
|
121
|
+
|
|
122
|
+
// Mock implementation
|
|
123
|
+
(fetchUser as jest.Mock).mockResolvedValue({ id: 1, name: 'John' });
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Code Coverage
|
|
127
|
+
```json
|
|
128
|
+
{
|
|
129
|
+
"coverageThreshold": {
|
|
130
|
+
"global": {
|
|
131
|
+
"branches": 80,
|
|
132
|
+
"functions": 80,
|
|
133
|
+
"lines": 80,
|
|
134
|
+
"statements": 80
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## E2E Testing Best Practices
|
|
141
|
+
|
|
142
|
+
### Critical Paths
|
|
143
|
+
- User registration/login
|
|
144
|
+
- Core business flows
|
|
145
|
+
- Payment processing
|
|
146
|
+
- Data persistence
|
|
147
|
+
|
|
148
|
+
### Page Object Model
|
|
149
|
+
```typescript
|
|
150
|
+
class LoginPage {
|
|
151
|
+
async login(email: string, password: string) {
|
|
152
|
+
await this.page.fill('[name="email"]', email);
|
|
153
|
+
await this.page.fill('[name="password"]', password);
|
|
154
|
+
await this.page.click('button[type="submit"]');
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## TDD Workflow
|
|
160
|
+
|
|
161
|
+
1. **Red**: Write failing test
|
|
162
|
+
2. **Green**: Write minimal code to pass
|
|
163
|
+
3. **Refactor**: Improve code quality
|
|
164
|
+
|
|
165
|
+
## Response Format
|
|
166
|
+
|
|
167
|
+
When assisting with testing:
|
|
168
|
+
|
|
169
|
+
1. **Identify testing needs** based on context
|
|
170
|
+
2. **Choose appropriate framework**
|
|
171
|
+
3. **Write tests following AAA pattern**
|
|
172
|
+
4. **Suggest test coverage improvements**
|
|
173
|
+
5. **Recommend testing strategies**
|
|
174
|
+
6. **Help with mocking/stubbing**
|
|
175
|
+
|
|
176
|
+
Always announce: `š¤ Applying @test-engineer...`
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* AI-DEVX Quick Check Script
|
|
5
|
+
* Performs fast validation checks on the codebase
|
|
6
|
+
* Usage: node .agent/scripts/checklist.js [path]
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const { execSync } = require('child_process');
|
|
12
|
+
|
|
13
|
+
const CHECKS = {
|
|
14
|
+
security: {
|
|
15
|
+
name: 'Security Scan',
|
|
16
|
+
run: checkSecurity
|
|
17
|
+
},
|
|
18
|
+
lint: {
|
|
19
|
+
name: 'Code Quality (Lint)',
|
|
20
|
+
run: checkLint
|
|
21
|
+
},
|
|
22
|
+
types: {
|
|
23
|
+
name: 'Type Checking',
|
|
24
|
+
run: checkTypes
|
|
25
|
+
},
|
|
26
|
+
tests: {
|
|
27
|
+
name: 'Test Suite',
|
|
28
|
+
run: checkTests
|
|
29
|
+
},
|
|
30
|
+
format: {
|
|
31
|
+
name: 'Format Check',
|
|
32
|
+
run: checkFormat
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const GREEN = '\x1b[32mā\x1b[0m';
|
|
37
|
+
const RED = '\x1b[31mā\x1b[0m';
|
|
38
|
+
const YELLOW = '\x1b[33mā \x1b[0m';
|
|
39
|
+
|
|
40
|
+
function log(message) {
|
|
41
|
+
console.log(message);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Security Checks
|
|
45
|
+
function checkSecurity(projectPath) {
|
|
46
|
+
const issues = [];
|
|
47
|
+
|
|
48
|
+
// Check for common secrets patterns
|
|
49
|
+
const secretPatterns = [
|
|
50
|
+
/['"]?(password|passwd|pwd)['"]?\s*[:=]\s*['"][^'"]+['"]/i,
|
|
51
|
+
/['"]?(api[_-]?key|apikey)['"]?\s*[:=]\s*['"][a-zA-Z0-9]{16,}['"]/i,
|
|
52
|
+
/['"]?(secret|token)['"]?\s*[:=]\s*['"][a-zA-Z0-9]{16,}['"]/i,
|
|
53
|
+
/['"]?aws[_-]?(access[_-]?key|secret)['"]?
|
|
54
|
+
?\s*[:=]\s*['"][A-Za-z0-9/+=]{20,}['"]/i
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
const files = getAllFiles(projectPath, ['.js', '.ts', '.jsx', '.tsx', '.json', '.env']);
|
|
58
|
+
|
|
59
|
+
for (const file of files) {
|
|
60
|
+
if (file.includes('node_modules') || file.includes('.git')) continue;
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
64
|
+
for (const pattern of secretPatterns) {
|
|
65
|
+
if (pattern.test(content) && !content.includes('process.env')) {
|
|
66
|
+
issues.push(`Potential secret in ${path.relative(projectPath, file)}`);
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
} catch (e) {
|
|
71
|
+
// Skip unreadable files
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
ok: issues.length === 0,
|
|
77
|
+
message: issues.length === 0 ? 'No secrets detected' : `${issues.length} potential secrets found`,
|
|
78
|
+
details: issues.slice(0, 5)
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Lint Check
|
|
83
|
+
function checkLint(projectPath) {
|
|
84
|
+
try {
|
|
85
|
+
// Check if eslint config exists
|
|
86
|
+
const hasEslint = [
|
|
87
|
+
'.eslintrc.js',
|
|
88
|
+
'.eslintrc.json',
|
|
89
|
+
'.eslintrc',
|
|
90
|
+
'eslint.config.js'
|
|
91
|
+
].some(f => fs.existsSync(path.join(projectPath, f)));
|
|
92
|
+
|
|
93
|
+
if (!hasEslint) {
|
|
94
|
+
return { ok: true, message: 'No ESLint config found (skipped)' };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
execSync('npx eslint . --max-warnings=0', {
|
|
99
|
+
cwd: projectPath,
|
|
100
|
+
stdio: 'pipe'
|
|
101
|
+
});
|
|
102
|
+
return { ok: true, message: 'ESLint passed' };
|
|
103
|
+
} catch (e) {
|
|
104
|
+
return { ok: false, message: 'ESLint found issues' };
|
|
105
|
+
}
|
|
106
|
+
} catch (e) {
|
|
107
|
+
return { ok: true, message: 'ESLint not available' };
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Type Check
|
|
112
|
+
function checkTypes(projectPath) {
|
|
113
|
+
try {
|
|
114
|
+
const hasTsconfig = fs.existsSync(path.join(projectPath, 'tsconfig.json'));
|
|
115
|
+
if (!hasTsconfig) {
|
|
116
|
+
return { ok: true, message: 'No TypeScript config (skipped)' };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
execSync('npx tsc --noEmit', {
|
|
121
|
+
cwd: projectPath,
|
|
122
|
+
stdio: 'pipe'
|
|
123
|
+
});
|
|
124
|
+
return { ok: true, message: 'TypeScript check passed' };
|
|
125
|
+
} catch (e) {
|
|
126
|
+
return { ok: false, message: 'TypeScript errors found' };
|
|
127
|
+
}
|
|
128
|
+
} catch (e) {
|
|
129
|
+
return { ok: true, message: 'TypeScript not available' };
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Test Check
|
|
134
|
+
function checkTests(projectPath) {
|
|
135
|
+
try {
|
|
136
|
+
const hasTests = [
|
|
137
|
+
'jest.config.js',
|
|
138
|
+
'jest.config.ts',
|
|
139
|
+
'vitest.config.js',
|
|
140
|
+
'vitest.config.ts'
|
|
141
|
+
].some(f => fs.existsSync(path.join(projectPath, f)));
|
|
142
|
+
|
|
143
|
+
if (!hasTests) {
|
|
144
|
+
return { ok: true, message: 'No test config found (skipped)' };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
execSync('npm test -- --passWithNoTests', {
|
|
149
|
+
cwd: projectPath,
|
|
150
|
+
stdio: 'pipe'
|
|
151
|
+
});
|
|
152
|
+
return { ok: true, message: 'Tests passing' };
|
|
153
|
+
} catch (e) {
|
|
154
|
+
return { ok: false, message: 'Tests failing' };
|
|
155
|
+
}
|
|
156
|
+
} catch (e) {
|
|
157
|
+
return { ok: true, message: 'Tests not available' };
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Format Check
|
|
162
|
+
function checkFormat(projectPath) {
|
|
163
|
+
try {
|
|
164
|
+
const hasPrettier = fs.existsSync(path.join(projectPath, '.prettierrc')) ||
|
|
165
|
+
fs.existsSync(path.join(projectPath, '.prettierrc.json')) ||
|
|
166
|
+
fs.existsSync(path.join(projectPath, 'prettier.config.js'));
|
|
167
|
+
|
|
168
|
+
if (!hasPrettier) {
|
|
169
|
+
return { ok: true, message: 'No Prettier config (skipped)' };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
execSync('npx prettier --check .', {
|
|
174
|
+
cwd: projectPath,
|
|
175
|
+
stdio: 'pipe'
|
|
176
|
+
});
|
|
177
|
+
return { ok: true, message: 'Code formatted correctly' };
|
|
178
|
+
} catch (e) {
|
|
179
|
+
return { ok: false, message: 'Code formatting issues' };
|
|
180
|
+
}
|
|
181
|
+
} catch (e) {
|
|
182
|
+
return { ok: true, message: 'Prettier not available' };
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Utility: Get all files recursively
|
|
187
|
+
function getAllFiles(dir, extensions) {
|
|
188
|
+
const files = [];
|
|
189
|
+
|
|
190
|
+
function traverse(currentDir) {
|
|
191
|
+
try {
|
|
192
|
+
const items = fs.readdirSync(currentDir);
|
|
193
|
+
for (const item of items) {
|
|
194
|
+
const fullPath = path.join(currentDir, item);
|
|
195
|
+
const stat = fs.statSync(fullPath);
|
|
196
|
+
|
|
197
|
+
if (stat.isDirectory()) {
|
|
198
|
+
if (!['node_modules', '.git', 'dist', 'build'].includes(item)) {
|
|
199
|
+
traverse(fullPath);
|
|
200
|
+
}
|
|
201
|
+
} else if (extensions.some(ext => item.endsWith(ext))) {
|
|
202
|
+
files.push(fullPath);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
} catch (e) {
|
|
206
|
+
// Skip inaccessible directories
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
traverse(dir);
|
|
211
|
+
return files;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Main execution
|
|
215
|
+
async function main() {
|
|
216
|
+
const projectPath = process.argv[2] || process.cwd();
|
|
217
|
+
|
|
218
|
+
log('\nš AI-DEVX Quick Check\n');
|
|
219
|
+
log('=' .repeat(50));
|
|
220
|
+
|
|
221
|
+
let passed = 0;
|
|
222
|
+
let failed = 0;
|
|
223
|
+
|
|
224
|
+
for (const [key, check] of Object.entries(CHECKS)) {
|
|
225
|
+
process.stdout.write(`${check.name}... `);
|
|
226
|
+
|
|
227
|
+
try {
|
|
228
|
+
const result = check.run(projectPath);
|
|
229
|
+
|
|
230
|
+
if (result.ok) {
|
|
231
|
+
log(`${GREEN} ${result.message}`);
|
|
232
|
+
passed++;
|
|
233
|
+
} else {
|
|
234
|
+
log(`${RED} ${result.message}`);
|
|
235
|
+
failed++;
|
|
236
|
+
if (result.details) {
|
|
237
|
+
result.details.forEach(d => log(` ${YELLOW} ${d}`));
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
} catch (e) {
|
|
241
|
+
log(`${YELLOW} Check failed to run`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
log('=' .repeat(50));
|
|
246
|
+
|
|
247
|
+
if (failed === 0) {
|
|
248
|
+
log(`\n${GREEN} All checks passed!`);
|
|
249
|
+
} else {
|
|
250
|
+
log(`\n${GREEN} ${passed} passed, ${RED} ${failed} failed`);
|
|
251
|
+
process.exit(1);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
log('\nā±ļø Quick check complete (~30 seconds)\n');
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
main().catch(e => {
|
|
258
|
+
console.error('Error running checks:', e.message);
|
|
259
|
+
process.exit(1);
|
|
260
|
+
});
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* AI-DEVX Security Scanner
|
|
5
|
+
* Scans codebase for security vulnerabilities
|
|
6
|
+
* Usage: node .agent/scripts/security_scan.js [path]
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
|
|
12
|
+
const SEVERITY = {
|
|
13
|
+
CRITICAL: { label: 'š“ CRITICAL', score: 4 },
|
|
14
|
+
HIGH: { label: 'š HIGH', score: 3 },
|
|
15
|
+
MEDIUM: { label: 'š” MEDIUM', score: 2 },
|
|
16
|
+
LOW: { label: 'š¢ LOW', score: 1 },
|
|
17
|
+
INFO: { label: 'ā¹ INFO', score: 0 }
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const SECURITY_PATTERNS = [
|
|
21
|
+
{
|
|
22
|
+
name: 'Hardcoded Secret/Password',
|
|
23
|
+
pattern: /['"]?(password|passwd|pwd|secret)['"]?\s*[:=]\s*['"][^'"]{8,}['"]/i,
|
|
24
|
+
severity: 'CRITICAL',
|
|
25
|
+
check: (content) => !content.includes('process.env') && !content.includes('import.meta.env')
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: 'Hardcoded API Key',
|
|
29
|
+
pattern: /['"]?(api[_-]?key|apikey)['"]?\s*[:=]\s*['"][a-zA-Z0-9]{16,}['"]/i,
|
|
30
|
+
severity: 'CRITICAL',
|
|
31
|
+
check: (content) => !content.includes('process.env')
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: 'Hardcoded Token',
|
|
35
|
+
pattern: /['"]?(token|access[_-]?token|auth[_-]?token)['"]?\s*[:=]\s*['"][a-zA-Z0-9-_]{20,}['"]/i,
|
|
36
|
+
severity: 'CRITICAL',
|
|
37
|
+
check: (content) => !content.includes('process.env')
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: 'AWS Access Key ID',
|
|
41
|
+
pattern: /['"]?AKIA[0-9A-Z]{16}['"]?/,
|
|
42
|
+
severity: 'CRITICAL',
|
|
43
|
+
check: () => true
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
name: 'Private Key',
|
|
47
|
+
pattern: /-----BEGIN (RSA |DSA |EC |OPENSSH )?PRIVATE KEY-----/,
|
|
48
|
+
severity: 'CRITICAL',
|
|
49
|
+
check: () => true
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: 'SQL Injection Risk',
|
|
53
|
+
pattern: /(SELECT|INSERT|UPDATE|DELETE|DROP).*\$\{/,
|
|
54
|
+
severity: 'HIGH',
|
|
55
|
+
check: () => true
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: 'eval() Usage',
|
|
59
|
+
pattern: /\beval\s*\(/,
|
|
60
|
+
severity: 'HIGH',
|
|
61
|
+
check: () => true
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: 'innerHTML Assignment',
|
|
65
|
+
pattern: /\.innerHTML\s*=/,
|
|
66
|
+
severity: 'MEDIUM',
|
|
67
|
+
check: (content, match) => !content.includes('DOMPurify') && !content.includes('sanitize')
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
name: 'Debug Mode Enabled',
|
|
71
|
+
pattern: /DEBUG\s*[:=]\s*true/i,
|
|
72
|
+
severity: 'MEDIUM',
|
|
73
|
+
check: (content) => content.includes('.env') || content.includes('production')
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
name: 'Insecure HTTP',
|
|
77
|
+
pattern: /http:\/\/(?!localhost|127\.0\.0\.1)/,
|
|
78
|
+
severity: 'MEDIUM',
|
|
79
|
+
check: () => true
|
|
80
|
+
}
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
const VULNERABLE_DEPENDENCIES = [
|
|
84
|
+
{ name: 'lodash', vulnerable: '<4.17.21', severity: 'HIGH' },
|
|
85
|
+
{ name: 'express', vulnerable: '<4.18.2', severity: 'MEDIUM' },
|
|
86
|
+
{ name: 'axios', vulnerable: '<0.28.0', severity: 'MEDIUM' },
|
|
87
|
+
{ name: 'minimist', vulnerable: '<1.2.6', severity: 'HIGH' }
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
function log(message) {
|
|
91
|
+
console.log(message);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function getAllFiles(dir, extensions) {
|
|
95
|
+
const files = [];
|
|
96
|
+
|
|
97
|
+
function traverse(currentDir) {
|
|
98
|
+
try {
|
|
99
|
+
const items = fs.readdirSync(currentDir);
|
|
100
|
+
for (const item of items) {
|
|
101
|
+
const fullPath = path.join(currentDir, item);
|
|
102
|
+
const stat = fs.statSync(fullPath);
|
|
103
|
+
|
|
104
|
+
if (stat.isDirectory()) {
|
|
105
|
+
if (!['node_modules', '.git', 'dist', 'build', '.agent'].includes(item)) {
|
|
106
|
+
traverse(fullPath);
|
|
107
|
+
}
|
|
108
|
+
} else {
|
|
109
|
+
files.push(fullPath);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
} catch (e) {
|
|
113
|
+
// Skip inaccessible directories
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
traverse(dir);
|
|
118
|
+
return files;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function scanFile(filePath, content) {
|
|
122
|
+
const issues = [];
|
|
123
|
+
|
|
124
|
+
for (const pattern of SECURITY_PATTERNS) {
|
|
125
|
+
const matches = content.match(pattern.pattern);
|
|
126
|
+
if (matches && pattern.check(content, matches)) {
|
|
127
|
+
issues.push({
|
|
128
|
+
file: filePath,
|
|
129
|
+
pattern: pattern.name,
|
|
130
|
+
severity: pattern.severity,
|
|
131
|
+
line: content.substring(0, matches.index).split('\n').length
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return issues;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function checkDependencies(projectPath) {
|
|
140
|
+
const issues = [];
|
|
141
|
+
const packageJsonPath = path.join(projectPath, 'package.json');
|
|
142
|
+
|
|
143
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
144
|
+
return issues;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
149
|
+
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
150
|
+
|
|
151
|
+
for (const [dep, version] of Object.entries(deps)) {
|
|
152
|
+
const vulnerable = VULNERABLE_DEPENDENCIES.find(v =>
|
|
153
|
+
v.name === dep && version.match(/\d+\.\d+\.\d+/)?.[0] < v.vulnerable.replace('<', '')
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
if (vulnerable) {
|
|
157
|
+
issues.push({
|
|
158
|
+
file: 'package.json',
|
|
159
|
+
pattern: `Vulnerable dependency: ${dep}@${version}`,
|
|
160
|
+
severity: vulnerable.severity,
|
|
161
|
+
line: 0
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
} catch (e) {
|
|
166
|
+
// Skip if package.json can't be parsed
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return issues;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function main() {
|
|
173
|
+
const projectPath = process.argv[2] || process.cwd();
|
|
174
|
+
|
|
175
|
+
log('\nš AI-DEVX Security Scanner\n');
|
|
176
|
+
log('=' .repeat(60));
|
|
177
|
+
|
|
178
|
+
const allIssues = [];
|
|
179
|
+
|
|
180
|
+
// Scan source files
|
|
181
|
+
log('\nš Scanning source files...\n');
|
|
182
|
+
const files = getAllFiles(projectPath, []);
|
|
183
|
+
|
|
184
|
+
for (const file of files) {
|
|
185
|
+
if (file.includes('node_modules') || file.includes('.git')) continue;
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
189
|
+
const issues = scanFile(path.relative(projectPath, file), content);
|
|
190
|
+
allIssues.push(...issues);
|
|
191
|
+
} catch (e) {
|
|
192
|
+
// Skip unreadable files
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Check dependencies
|
|
197
|
+
log('š¦ Checking dependencies...\n');
|
|
198
|
+
const depIssues = checkDependencies(projectPath);
|
|
199
|
+
allIssues.push(...depIssues);
|
|
200
|
+
|
|
201
|
+
// Sort by severity
|
|
202
|
+
const severityOrder = { CRITICAL: 4, HIGH: 3, MEDIUM: 2, LOW: 1, INFO: 0 };
|
|
203
|
+
allIssues.sort((a, b) => severityOrder[b.severity] - severityOrder[a.severity]);
|
|
204
|
+
|
|
205
|
+
// Group by severity
|
|
206
|
+
const grouped = allIssues.reduce((acc, issue) => {
|
|
207
|
+
acc[issue.severity] = acc[issue.severity] || [];
|
|
208
|
+
acc[issue.severity].push(issue);
|
|
209
|
+
return acc;
|
|
210
|
+
}, {});
|
|
211
|
+
|
|
212
|
+
// Display results
|
|
213
|
+
if (allIssues.length === 0) {
|
|
214
|
+
log('\nā
No security issues found!\n');
|
|
215
|
+
} else {
|
|
216
|
+
log(`\nā ļø Found ${allIssues.length} security issue(s):\n`);
|
|
217
|
+
|
|
218
|
+
for (const severity of ['CRITICAL', 'HIGH', 'MEDIUM', 'LOW', 'INFO']) {
|
|
219
|
+
const issues = grouped[severity];
|
|
220
|
+
if (issues) {
|
|
221
|
+
log(`\n${SEVERITY[severity].label} (${issues.length}):\n`);
|
|
222
|
+
issues.forEach(issue => {
|
|
223
|
+
log(` š ${issue.file}:${issue.line}`);
|
|
224
|
+
log(` ${issue.pattern}\n`);
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Summary
|
|
231
|
+
log('=' .repeat(60));
|
|
232
|
+
const critical = grouped.CRITICAL?.length || 0;
|
|
233
|
+
const high = grouped.HIGH?.length || 0;
|
|
234
|
+
const medium = grouped.MEDIUM?.length || 0;
|
|
235
|
+
const low = grouped.LOW?.length || 0;
|
|
236
|
+
|
|
237
|
+
log(`\nSummary: š“ ${critical} Critical | š ${high} High | š” ${medium} Medium | š¢ ${low} Low`);
|
|
238
|
+
|
|
239
|
+
if (critical > 0 || high > 0) {
|
|
240
|
+
log('\nā ļø Critical or High severity issues found! Fix immediately.');
|
|
241
|
+
process.exit(1);
|
|
242
|
+
} else if (medium > 0) {
|
|
243
|
+
log('\nā ļø Medium severity issues found. Fix recommended.');
|
|
244
|
+
process.exit(0);
|
|
245
|
+
} else {
|
|
246
|
+
log('\nā
Security scan complete.');
|
|
247
|
+
process.exit(0);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
main();
|