Skip to content

Commit 301c968

Browse files
Make AI suggestions opt-in via --ai-suggestions flag.
Markdown reports are deterministic by default; OpenAI is only called when explicitly requested. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent d5a7d13 commit 301c968

3 files changed

Lines changed: 33 additions & 30 deletions

File tree

README.md

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ VibeSafe helps developers quickly check their projects for common security issue
4040
Console, JSON (`--output`), or Markdown reports (`--report`).
4141

4242
- 🧠 **AI-Powered Fix Suggestions (Optional)**
43-
Add an OpenAI API key for smart recommendations in Markdown reports.
43+
Pass `--ai-suggestions` with `--report` and an OpenAI API key for smart recommendations in Markdown reports.
4444

4545
- 🎯 **Focus on Critical Issues**
4646
Use `--high-only` to trim noise.
@@ -79,7 +79,7 @@ vibesafe scan -o scan-results.json
7979

8080
**Generate Markdown Report:**
8181

82-
To generate a Markdown report, use the `-r` or `--report` flag. You can optionally provide a filename. If no filename is given, it defaults to `VIBESAFE-REPORT.md` in the scanned directory.
82+
To generate a deterministic Markdown report (no LLM calls), use the `-r` or `--report` flag. You can optionally provide a filename. If no filename is given, it defaults to `VIBESAFE-REPORT.md` in the scanned directory.
8383

8484
*With a specific filename:*
8585
```bash
@@ -93,28 +93,28 @@ vibesafe scan -r
9393
vibesafe scan --report
9494
```
9595

96-
*Using a local llm host for report (the llm host must support OpenAI API)
97-
```bash
98-
# example with ollama at local host with default ollama port
99-
vibesafe scan --url http://127.0.0.1:11434 --model gemma3:27b-it-q8_0
100-
```
101-
102-
if --url flag is not specified the report will be done by OpenAI (you will need an OpenAI API Key, see below)
103-
104-
**Generate AI Report from OpenAI (Requires API Key):**
96+
**Generate AI Suggestions in Report (Opt-in, Requires API Key):**
10597

106-
To generate fix suggestions in the Markdown report, you need an OpenAI API key.
98+
AI fix suggestions are **not** included by default. Pass `--ai-suggestions` along with `--report` to enable them.
10799

108100
1. Create a `.env` file in the root of the directory where you run `vibesafe` (or in the project root if running locally during development).
109101
2. Add your key to the `.env` file:
110102
```
111103
OPENAI_API_KEY=sk-YourActualOpenAIKeyHere
112104
```
113-
3. Run the scan with the report flag:
105+
3. Run the scan with both flags:
114106
```bash
115-
vibesafe scan -r ai-report.md
107+
vibesafe scan -r ai-report.md --ai-suggestions
116108
```
117109

110+
*Using a local LLM host (must support OpenAI API):*
111+
```bash
112+
# example with ollama at local host with default ollama port
113+
vibesafe scan -r report.md --ai-suggestions --url http://127.0.0.1:11434 --model gemma3:27b-it-q8_0
114+
```
115+
116+
If `--url` is not specified, AI suggestions use the OpenAI API.
117+
118118
**Show Only High/Critical Issues:**
119119

120120
```bash

src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ program.command('scan')
5353
.option('-o, --output <file>', 'Specify JSON output file path (e.g., report.json)')
5454
.option('-r, --report [file]', 'Specify Markdown report file path (defaults to VIBESAFE-REPORT.md)')
5555
.option('--high-only', 'Only report high severity issues')
56+
.option('--ai-suggestions', 'Include AI-powered fix suggestions in the Markdown report (requires OpenAI API key or compatible LLM host)')
5657
.option('-m, --model <model>', 'Specify OpenAI model to use for suggestions. If not specified the program will use gpt-4.1-nano', 'gpt-4.1-nano')
5758
.option('-u, --url <url>', 'Use the specified url (e.g. http://localhost:11434 for ollama or https://api.openai.com for ChatGPT) for ai suggestions. If not specified the program will call OpenAI API', 'https://api.openai.com')
5859
.action(async (directory, options) => {
@@ -312,7 +313,7 @@ program.command('scan')
312313
infoSecretFindings: infoSecretFindings
313314
};
314315
try {
315-
const markdownContent = await generateMarkdownReport(reportData, options.url, options.model);
316+
const markdownContent = await generateMarkdownReport(reportData, options.url, options.model, !!options.aiSuggestions);
316317
fs.writeFileSync(reportPath, markdownContent);
317318
console.log(chalk.green(`\nMarkdown report generated successfully at ${reportPath}`));
318319
} catch (error: any) {

src/reporting/markdown.ts

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import { RateLimitFinding } from '../scanners/rateLimiting';
77
import { LoggingFinding } from '../scanners/logging';
88
import { HttpClientFinding } from '../scanners/httpClient';
99
import { GitignoreWarning } from '../utils/fileTraversal';
10-
import { generateAISuggestions } from './aiSuggestions';
1110
import path from 'path';
1211
import ora from 'ora';
1312
import chalk from 'chalk';
@@ -53,7 +52,7 @@ function getSeverityInfo(severity: FindingSeverity | SecretFinding['severity'] |
5352
* @param reportData The aggregated findings.
5453
* @returns A Markdown formatted string.
5554
*/
56-
export async function generateMarkdownReport(reportData: ReportData, url: string, model: string = 'gpt-4.1-nano'): Promise<string> {
55+
export async function generateMarkdownReport(reportData: ReportData, url: string, model: string = 'gpt-4.1-nano', aiSuggestions: boolean = false): Promise<string> {
5756
let markdown = `# VibeSafe Security Scan Report ✨🛡️\n\n`;
5857
markdown += `Generated: ${new Date().toISOString()}\n\n`;
5958

@@ -198,19 +197,22 @@ export async function generateMarkdownReport(reportData: ReportData, url: string
198197
}
199198
}
200199

201-
// --- AI Suggestions ---
202-
let apiKey = process.env.OPENAI_API_KEY;
203-
if (! apiKey){ // ollama dont need an API key but this field can't be none
204-
apiKey = 'YOUR_API_KEY_PLACEHOLDER';
205-
}
206-
const spinner = ora(`Generating AI suggestions (using API from ${url}/v1 with model: ${model})... `).start();
207-
try {
208-
const aiSuggestions = await generateAISuggestions(reportData, {baseURL: url + '/v1', apiKey: apiKey}, model);
209-
spinner.succeed('AI suggestions generated.');
210-
markdown += aiSuggestions; // Append the suggestions section
211-
} catch (error: any) {
212-
spinner.fail('AI suggestion generation failed.');
213-
markdown += `\n## AI Suggestions\n\n*Error generating suggestions: ${error.message}*\n`; // Append error message
200+
// --- AI Suggestions (opt-in via --ai-suggestions) ---
201+
if (aiSuggestions) {
202+
const { generateAISuggestions } = await import('./aiSuggestions');
203+
let apiKey = process.env.OPENAI_API_KEY;
204+
if (!apiKey) { // ollama dont need an API key but this field can't be none
205+
apiKey = 'YOUR_API_KEY_PLACEHOLDER';
206+
}
207+
const spinner = ora(`Generating AI suggestions (using API from ${url}/v1 with model: ${model})... `).start();
208+
try {
209+
const suggestions = await generateAISuggestions(reportData, { baseURL: url + '/v1', apiKey }, model);
210+
spinner.succeed('AI suggestions generated.');
211+
markdown += suggestions;
212+
} catch (error: any) {
213+
spinner.fail('AI suggestion generation failed.');
214+
markdown += `\n## AI Suggestions\n\n*Error generating suggestions: ${error.message}*\n`;
215+
}
214216
}
215217

216218
return markdown;

0 commit comments

Comments
 (0)