Skip to content

Commit e579821

Browse files
committed
new gitignore functionality
1 parent 0eb6f57 commit e579821

File tree

4 files changed

+168
-96
lines changed

4 files changed

+168
-96
lines changed

.vibesafeignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Ignore specific test files for self-scan
2+
test-data/config.js

instructions.md

Lines changed: 51 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
**Problem:** Developers ship code quickly but often miss basic security checks (secrets, stale deps, known CVEs).
66
**Solution:** A zero‑config CLI that scans a repo for secrets, outdated packages, and CVEs, then generates an AI‑powered risk report.
7-
**MVP Goal:** Enable any developer to run `vibesafe scan` and get a readable security summary—including file paths and line numbers—in under 60s.
7+
**MVP Goal:** Enable any developer to run `vibesafe scan` and get a readable security summary—including file paths and line numbers—in under 60 s.
88

99
## 2. Personas & Use Cases
1010

@@ -26,119 +26,102 @@
2626
- Automatic patching (`--fix`)
2727
- Remote‑repo scanning
2828
- Real‑time IDE plugins
29-
- Telemetry collection (opt‑in only)
29+
- Telemetry collection (opt‑in only)
3030
- TODO: Proactively check `.gitignore` for `.env` exclusion patterns
3131

3232
## 4. Success Metrics
3333

34-
1. **Performance:** Full scan < 60 s on a 100MB repo
35-
2. **Coverage:** Detects ≥5 unique issues in standard test repos
36-
3. **Adoption:**10 installs in first week (npm/pip downloads)
34+
1. **Performance:** Full scan < 60 s on a 100 MB repo
35+
2. **Coverage:** Detects ≥ 5 unique issues in standard test repos
36+
3. **Adoption:** 10 installs in first week (npm/pip downloads)
3737
4. **Reliability:** CI exit code behavior consistent (HIGH → non-zero)
3838

3939
## 5. Phases & Atomic Tasks
4040

41-
### Phase1: Setup & CI Integration
41+
### Phase 1: Setup & CI Integration
4242
1. **Repo scaffold**
4343
- [x] `mkdir vibesafe && cd vibesafe`
4444
- [x] Initialize Git + add `.gitignore`, `LICENSE`, `README.md`
45-
- [x] Choose language: TypeScript (commander.js) ~~_or_ Python (argparse)~~
45+
- [x] Choose language: TypeScript (commander.js)
4646
- [x] Add basic `vibesafe scan` command stub
4747
2. **CI hook**
4848
- [x] Write a GitHub Actions workflow that runs `vibesafe scan --high-only`
4949
- [x] Ensure exit code propagates
5050

51-
### Phase2: Secrets Scanner
51+
### Phase 2: Secrets Scanner
5252
1. **Regex & entropy engine**
53-
- [x] Define regex patterns for `.env`, AWS, JWT, SSH keys
54-
- [x] Integrate an entropy checker (e.g., Shannon entropy > threshold)
53+
- [x] Define regex patterns for `.env`, AWS, JWT, SSH keys
54+
- [x] Integrate an entropy checker (e.g., Shannon entropy > threshold)
5555
2. **File traversal**
56-
- [x] Walk directory tree, skip default excludes (`node_modules`, `dist`, lockfiles, tsconfig.json, README.md)
57-
- [x] Honor `.vibesafeignore` entries
56+
- [x] Walk directory tree, skip default excludes (`node_modules`, `dist`, lockfiles, tsconfig.json, README.md)
57+
- [x] Honor `.vibesafeignore` entries
5858
3. **Scoring & output**
59-
- [x] Assign Low/Med/High severity based on pattern + entropy
59+
- [x] Assign Low/Med/High severity based on pattern + entropy
6060
- [x] Emit JSON record per finding including `file`, `line`, `pattern`, and `severity`
61-
- [x] Added 'Info' severity for secrets in `.env` files (reduces noise)
61+
- [x] Added 'Info' severity for secrets in `.env` files (reduces noise)
6262

63-
### Phase3: Dependency & CVE Scanner
63+
### Phase 3: Dependency & CVE Scanner
6464
1. **Detect package manager**
6565
- [x] Inspect files: `package.json`, `yarn.lock`, `requirements.txt`
6666
2. **Parse deps**
67-
- [x] Extract name + version pairs
67+
- [x] Extract name + version pairs
6868
3. **CVE lookup**
6969
- [x] Call OSV.dev or NVD API with each dep
7070
- [x] Capture CVE IDs, severity, published date
7171
4. **Threshold filtering**
72-
- [x] Mark HIGH if any dep ≥7.0 severity
72+
- [x] Mark HIGH if any dep ≥ 7.0 severity
7373

74-
### Phase4: AI Risk Report
74+
### Phase 4: AI Risk Report
7575
1. **Markdown skeleton**
76-
- [x] Build template:
77-
```md
78-
# VibeSafe Report
79-
80-
## Summary
81-
- Total Issues: 5 (2 High, 2 Medium, 1 Low)
82-
83-
## Details
84-
| File | Location | Issue | Severity | CVE/Pattern |
85-
| ------------------ | ---------- | ---------------- | -------- | ------------- |
86-
| `.env` | line 10 | AWS Key exposed | High | — |
87-
| `config/app.js` | line 45 | JWT secret | Medium | — |
88-
| `package.json` | line 23 | lodash 4.17 | Medium | CVE-2024-123 |
89-
| `requirements.txt` | line 12 | Django 2.2 | High | CVE-2023-456 |
90-
| `src/utils.ts` | line 80 | Hardcoded token | Low | — |
91-
92-
## Fix Suggestions
93-
1. Remove AWS keys from code; use environment variables and a secrets vault.
94-
2. Rotate JWT secret and move to env vars.
95-
3. Upgrade `lodash` to ≥ 4.17.21.
96-
4. Update Django to ≥ 3.2.
97-
5. Replace hardcoded tokens with secure storage.
98-
```
76+
- [x] Build template
9977
2. **LLM integration**
100-
- [x] Send JSON findings + skeleton to GPT‑4o-mini
78+
- [x] Send JSON findings + skeleton to GPT‑4omini
10179
- [x] Parse human‑readable summary & per‑issue suggestions
10280
- [x] Merge into final MD
10381

104-
### Phase5: CLI UX & Packaging
82+
### Phase 5: CLI UX & Packaging
10583
1. **Terminal polish**
106-
- [x] Colorize severities (e.g., red for High)
107-
- [x] Add progress spinner during scans
84+
- [x] Colorize severities (e.g., red for High)
85+
- [x] Add progress spinner during scans
10886
2. **Flags & outputs**
109-
- [x] `--output <file.json>`
110-
- [x] `--report <file.md>`
111-
- [x] `--high-only` filter
87+
- [x] `--output <file.json>`
88+
- [x] `--report <file.md>`
89+
- [x] `--high-only` filter
11290
3. **Distribution**
113-
- [x] Set up npm `bin` entry_point
114-
- [x] Test on macOS
115-
116-
## 6. Timeline & Ownership
117-
118-
| Week | Focus | Owner |
119-
| ------ | ------------------------------ | ------------ |
120-
| Week 1 | Phase 1 scaffold + CI | @you |
121-
| Week 2 | Phase 2 secrets scanner | @security |
122-
| Week 3 | Phase 3 dep & CVE scanner | @sec‑lead |
123-
| Week 4 | Phase 4 AI report & polish | @AI‑engineer |
124-
| Week 5 | Phase 5 packaging & QA | @release |
125-
126-
## 7. Risks & Mitigations
91+
- [x] Set up npm `bin` entry_point
92+
- [x] Test on macOS
93+
94+
### Phase 6: Additional Common Checks
95+
1. **Insecure Default Configurations**
96+
- [ ] Scan config files (JSON/YAML) for flags like `DEBUG=true`, `devMode`, or permissive CORS (`*` origins)
97+
2. **Unvalidated File Uploads**
98+
- [ ] Detect code handling file uploads (e.g., multer, busboy) without size/type restrictions
99+
3. **Exposed Debug/Admin Endpoints**
100+
- [ ] Search for routes named `/debug`, `/admin`, `/console`
101+
- [ ] Flag those without authentication or middleware checks
102+
4. **Lack of Rate‑Limiting**
103+
- [ ] Identify HTTP handlers or clients missing rate‑limiter middleware (e.g., express-rate-limit)
104+
- [ ] Flag missing throttle/retry settings in HTTP client code
105+
5. **Insufficient Logging & Error Sanitization**
106+
- [ ] Find logging of full error objects or stack traces (e.g., `console.error(err)`)
107+
- [ ] Detect logging of PII or sensitive data in plain text
108+
109+
## 6. Risks & Mitigations
127110

128111
- **API rate limits (OSV/NVD):** cache results locally; implement exponential back‑off
129112
- **False positives (secrets):** tune regex & entropy thresholds; allow exclusions
130113
- **LLM costs:** only call on `--report` mode; support a dry‑run without AI
131114

132-
## 8. In Cursor
115+
## 7. In Cursor
133116

134117
- **Check progress:**
135-
> “What is the current status of Phase 2: Secrets Scanner?”
118+
> “What is the current status of Phase 6: Additional Common Checks?”
136119
- **Mark tasks done:**
137-
> “Mark Phase 3.3 (CVE lookup) as complete.”
120+
> “Mark Insecure Default Configurations check as complete.”
138121
139122
---
140123

141124
**Next Steps:**
142-
1. Review personas & success metrics.
143-
2. Assign owners & adjust timeline as needed.
144-
3. Kick off Week 1!
125+
1. Tackle Phase 6 atomic tasks in order.
126+
2. Validate each check against representative repos.
127+
3. Prepare to expand into “Most Dangerous” vulnerability scans once Phase 6 is done.

src/index.ts

Lines changed: 54 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import 'dotenv/config';
66
import { Command } from 'commander';
77
import { scanFileForSecrets, SecretFinding } from './scanners/secrets';
88
import { detectPackageManagers, parseDependencies, lookupCves, DependencyInfo, DependencyFinding, FindingSeverity } from './scanners/dependencies';
9-
import { getFilesToScan } from './utils/fileTraversal';
9+
import { getFilesToScan, checkGitignoreStatus, GitignoreWarning } from './utils/fileTraversal';
1010
import { generateMarkdownReport } from './reporting/markdown';
1111
import path from 'path';
1212
import fs from 'fs';
@@ -52,6 +52,10 @@ program.command('scan')
5252
console.log(`Markdown report will be written to: ${options.report}`);
5353
}
5454

55+
// --- Moved: Check .gitignore Status ---
56+
// We will call checkGitignoreStatus later, just declare the variable here
57+
let gitignoreWarnings: GitignoreWarning[] = [];
58+
5559
// --- Findings Aggregation ---
5660
let allSecretFindings: SecretFinding[] = [];
5761
let allDependencyFindings: DependencyFinding[] = [];
@@ -102,6 +106,9 @@ program.command('scan')
102106
? allDependencyFindings.filter(dep => (dep.maxSeverity === 'High' || dep.maxSeverity === 'Critical')) // Exclude errors when highOnly
103107
: allDependencyFindings.filter(dep => dep.vulnerabilities.length > 0 || dep.error);
104108

109+
// --- NOW Check Gitignore Status ---
110+
gitignoreWarnings = checkGitignoreStatus(rootDir);
111+
105112
// --- Output Generation ---
106113
const reportData = {
107114
secretFindings: reportSecretFindings, // Report only standard secrets
@@ -137,6 +144,16 @@ program.command('scan')
137144

138145
// Print to console ONLY if neither JSON nor Markdown output was specified
139146
if (!options.output && !options.report) {
147+
148+
// Print Configuration Warnings FIRST (after scans, before results)
149+
if (gitignoreWarnings.length > 0) {
150+
console.log(chalk.yellow.bold('\n⚠️ Configuration Warnings:')); // Added emoji
151+
gitignoreWarnings.forEach(warning => {
152+
const emoji = warning.type === 'MISSING' ? '❓' : '❗'; // Different emojis
153+
console.log(chalk.yellow(` ${emoji} ${warning.message}`));
154+
});
155+
}
156+
140157
// Handle Info findings first
141158
if (infoSecretFindings.length > 0) {
142159
console.log(chalk.blue.bold('\nInfo:'));
@@ -147,34 +164,43 @@ program.command('scan')
147164
});
148165
}
149166

150-
// Print standard secrets to console
151-
if (reportSecretFindings.length > 0) {
152-
console.log(chalk.bold('\nPotential Secrets Found:'));
153-
reportSecretFindings.forEach(finding => {
154-
console.log(` - [${colorSeverity(finding.severity)}] ${finding.type} in ${chalk.cyan(finding.file)}:${chalk.yellow(String(finding.line))}`);
155-
});
156-
} else if (allSecretFindings.length === 0) {
157-
// Only print "no secrets" if no standard *or* info secrets were found
158-
console.log('No potential secrets found in scanned files.');
159-
}
167+
// Check if any standard findings exist
168+
const hasStandardSecrets = reportSecretFindings.length > 0;
169+
const hasDependencyIssues = reportDependencyFindings.length > 0;
170+
171+
if (hasStandardSecrets || hasDependencyIssues) {
172+
// Print standard secrets to console if found
173+
if (hasStandardSecrets) {
174+
console.log(chalk.bold('\nPotential Secrets Found:'));
175+
reportSecretFindings.forEach(finding => {
176+
console.log(` - [${colorSeverity(finding.severity)}] ${finding.type} in ${chalk.cyan(finding.file)}:${chalk.yellow(String(finding.line))}`);
177+
});
178+
}
160179

161-
// Print dependency findings to console
162-
if (reportDependencyFindings.length > 0) {
163-
console.log(chalk.bold('\nDependencies with Issues Found:'));
164-
reportDependencyFindings.sort((a, b) => severityToSortOrder(b.maxSeverity) - severityToSortOrder(a.maxSeverity));
165-
166-
reportDependencyFindings.forEach(dep => {
167-
if (dep.error) {
168-
console.log(` - [${chalk.red.bold('ERROR')}] ${chalk.magenta(dep.name)}@${chalk.gray(dep.version)}: (${dep.error})`);
169-
} else if (dep.vulnerabilities.length > 0) {
170-
const cveIds = dep.vulnerabilities.map(v => v.id).slice(0,3).join(', ');
171-
const moreCvEs = dep.vulnerabilities.length > 3 ? '...' : '';
172-
// Apply color to severity and dependency name/version
173-
console.log(` - [${colorSeverity(dep.maxSeverity)}] ${chalk.magenta(dep.name)}@${chalk.gray(dep.version)}: ${dep.vulnerabilities.length} vulnerabilities (${chalk.dim(cveIds)}${moreCvEs})`);
174-
}
175-
});
176-
} else if (dependencyInfoList.length > 0 && !options.highOnly) {
177-
console.log('\nNo vulnerabilities found matching criteria in scanned dependencies.');
180+
// Print dependency findings to console if found
181+
if (hasDependencyIssues) {
182+
console.log(chalk.bold('\nDependencies with Issues Found:'));
183+
reportDependencyFindings.sort((a, b) => severityToSortOrder(b.maxSeverity) - severityToSortOrder(a.maxSeverity));
184+
reportDependencyFindings.forEach(dep => {
185+
if (dep.error) {
186+
console.log(` - [${chalk.red.bold('ERROR')}] ${chalk.magenta(dep.name)}@${chalk.gray(dep.version)}: (${dep.error})`);
187+
} else if (dep.vulnerabilities.length > 0) {
188+
const cveIds = dep.vulnerabilities.map(v => v.id).slice(0,3).join(', ');
189+
const moreCvEs = dep.vulnerabilities.length > 3 ? '...' : '';
190+
// Apply color to severity and dependency name/version
191+
console.log(` - [${colorSeverity(dep.maxSeverity)}] ${chalk.magenta(dep.name)}@${chalk.gray(dep.version)}: ${dep.vulnerabilities.length} vulnerabilities (${chalk.dim(cveIds)}${moreCvEs})`);
192+
}
193+
});
194+
}
195+
} else {
196+
// All Clear! Print positive message.
197+
// Check if we actually scanned for dependencies before saying no vulns found
198+
const scannedDeps = dependencyInfoList.length > 0;
199+
console.log(chalk.green.bold('\n✅ No issues found! Keep up the good vibes! 😎'));
200+
// Optionally add context:
201+
if (!scannedDeps) {
202+
console.log(chalk.gray(' (Dependency vulnerability scan skipped as no supported package manager was detected)'));
203+
}
178204
}
179205
}
180206

src/utils/fileTraversal.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,67 @@ const DEFAULT_IGNORE_PATTERNS = [
2424

2525
const VIBESAFE_IGNORE_FILE = '.vibesafeignore';
2626

27+
// --- .gitignore Check ---
28+
29+
// Patterns we want to ensure are typically ignored by users
30+
const SENSITIVE_PATTERNS_TO_CHECK = [
31+
'.env',
32+
'.env.*', // Catch .env.local, .env.development etc.
33+
'*.env', // Catch other potential env files
34+
// Add other common sensitive file patterns here if needed later
35+
// e.g., '*.pem', '*.key'?
36+
];
37+
38+
export interface GitignoreWarning {
39+
type: 'MISSING' | 'PATTERN_NOT_IGNORED';
40+
message: string;
41+
pattern?: string; // The pattern that wasn't ignored
42+
}
43+
44+
/**
45+
* Checks if .gitignore exists and if it ignores common sensitive patterns.
46+
* @param rootDir The root directory of the project.
47+
* @returns An array of warning objects.
48+
*/
49+
export function checkGitignoreStatus(rootDir: string): GitignoreWarning[] {
50+
const warnings: GitignoreWarning[] = [];
51+
const gitignorePath = path.join(rootDir, '.gitignore');
52+
53+
if (!fs.existsSync(gitignorePath)) {
54+
warnings.push({
55+
type: 'MISSING',
56+
message: '.gitignore file not found. Recommend creating one and adding sensitive files (like .env*, *.log) to prevent accidental commits.'
57+
});
58+
return warnings; // No point checking patterns if the file doesn't exist
59+
}
60+
61+
try {
62+
const gitignoreContent = fs.readFileSync(gitignorePath, 'utf-8');
63+
const ig = ignore().add(gitignoreContent);
64+
65+
SENSITIVE_PATTERNS_TO_CHECK.forEach(pattern => {
66+
// Use a common example filename that matches the pattern
67+
// Note: This check isn't perfect, complex patterns could behave differently,
68+
// but it covers common cases like direct filenames or *.ext.
69+
const testFileName = pattern.includes('*') ? pattern.replace('*.', 'example.') : pattern;
70+
71+
if (!ig.ignores(testFileName)) {
72+
warnings.push({
73+
type: 'PATTERN_NOT_IGNORED',
74+
message: `Pattern "${pattern}" is not covered by .gitignore. Consider adding it to prevent committing sensitive files.`,
75+
pattern: pattern
76+
});
77+
}
78+
});
79+
80+
} catch (error: any) {
81+
console.warn(`Error reading or parsing .gitignore: ${error.message}`);
82+
// Don't block the scan, just warn about the check failure
83+
}
84+
85+
return warnings;
86+
}
87+
2788
/**
2889
* Reads ignore patterns from .vibesafeignore file if it exists.
2990
* @param rootDir The root directory of the project.

0 commit comments

Comments
 (0)