Skip to content

Commit b39c8c0

Browse files
authored
Merge pull request #286 from shivasurya/shiva/augment-security-review
feat: add directory listing tool and iterative file navigation for security analysis
2 parents 14b611d + 5392d30 commit b39c8c0

File tree

8 files changed

+306
-39
lines changed

8 files changed

+306
-39
lines changed

extension/secureflow/packages/secureflow-cli/lib/prompts/common/security-review-cli.txt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ You are a highly experienced security engineer conducting a thorough code review
5858
- It may include README files
5959
- You may exclude other files
6060

61+
6. ALWAYS use tools to navigate through the code to understand the complete context of the vulnerability.
62+
- Even though the project structure is provided, use list files tool to navigate through the code to understand the complete context of the vulnerability.
63+
- Use file request tool to request for files if needed for complete analysis iteratively.
6164

6265
Strictly don't report below category of vulnerabilities from Analysis:
6366
- Theoretical vulnerabilities without practical impact
@@ -80,6 +83,17 @@ Strictly don't report below category of vulnerabilities from Analysis:
8083
- Client side DoS attacks
8184
- Assuming backend is malicious
8285

86+
### How does a security engineer work?
87+
88+
A security engineer works in an iterative manner. He first reviews the project information provided to him and
89+
then requests for files to review. He then reviews the files and requests for additional files if needed for
90+
complete analysis. If he finds a vulnerable pattern, he request for related files to review to understand the
91+
complete context of the vulnerability. Mainly he validates the flow of the code to navigate through the code
92+
across files and directories to understand the complete context of the vulnerability.
93+
94+
Similar to a security engineer, you should also work in an iterative manner and you'll receive tools as request
95+
to navigate through the code to understand the complete context of the vulnerability.
96+
8397
### Output
8498

8599
Provide actionable, practical feedback that helps developers understand and fix real security issues while maintaining code quality and performance.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
Please provide your final security analysis. Do not request any more files.
1+
Please provide your final security analysis. Do not request any more files or list of files.
Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
Remember to be skeptical like a senior security engineer and make sure to read all source files. If the file
2-
doesnt exist, strictly dont request it again and wait for the next iteration to request it.
2+
doesnt exist, strictly dont request it again and wait for the next iteration to request it. Be sure to list
3+
all files under project directory without missing any path, because it helps to reduce blind spots in analysis.
34

45
Based on the files analyzed so far, strictly follow either and don't ask any other questions:
56
1. Request additional files if needed for complete analysis in format specified in the file request tag
6-
2. Provide your final security analysis if you have sufficient information
7+
2. Request additional list of files if needed for complete analysis in format specified in the list file request tag
8+
3. Provide your final security analysis if you have sufficient information
9+
4. If you haven't requested for list of files so far, please provide rational reason for not requesting it.

extension/secureflow/packages/secureflow-cli/lib/prompts/tools/file-request-instructions.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
Tool: read file
1+
------
2+
1. Tool: read file
23
Description: This tool helps to read files for analysis
34
Format:
45
<file_request path="./relative/path/to/file.js" reason="short reason for requesting this file" />
@@ -16,3 +17,4 @@ Rules for file requests:
1617
10. If the file is not a source file, dont request it again
1718
11. You should request only files `Files in the project:` section. Files apart from this section are strictly prohibited.
1819
12. If you have already requested a file and read it, dont request it again
20+
------

extension/secureflow/packages/secureflow-cli/lib/prompts/tools/list-file-request-instructions.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
Tool: list files in directory
1+
------
2+
2. Tool: list files in directory (equivalent to 'ls' command)
23
Description: This method helps to list files in a directory
34
Format:
45
<list_file_request path="./relative/path/to/directory" reason="short reason for requesting this directory" />
@@ -13,3 +14,4 @@ Rules for file requests:
1314
7. Skip directories that were not found previously
1415
8. Skip non-source directories
1516
9. Follow project structure strictly
17+
----

extension/secureflow/packages/secureflow-cli/scanner/ai-security-analyzer.js

Lines changed: 111 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ class AISecurityAnalyzer {
2525
let iteration = 0;
2626
let currentContext = await this._buildInitialContext(profileInfo, projectSummary, reviewPrompt);
2727
let allFileContents = [];
28+
let fileRequestsHistory = [];
2829
let finalAnalysis = null;
2930
let messages = [{ role: 'user', content: currentContext }];
3031

@@ -34,7 +35,6 @@ class AISecurityAnalyzer {
3435

3536
// Send context to AI and get response
3637
const aiResponse = await this._sendToAI(null, iteration, messages);
37-
3838
messages.push({ role: 'assistant', content: aiResponse });
3939

4040
this.analysisLog.push({
@@ -46,10 +46,25 @@ class AISecurityAnalyzer {
4646

4747
// Check if AI wants to request files
4848
const fileRequests = this._extractFileRequests(aiResponse);
49+
const newFileRequests = fileRequests.filter(request => !fileRequestsHistory.some(f => f.path === request.path));
50+
fileRequestsHistory.push(...newFileRequests);
51+
52+
// Check if AI wants to list files
53+
const listFileRequests = this._extractListFileRequests(aiResponse);
4954

50-
if (fileRequests.length > 0) {
51-
// Process file requests
52-
const fileResults = await this.fileHandler.processFileRequests(aiResponse);
55+
if (fileRequests.length > 0 || listFileRequests.length > 0) {
56+
let fileResults = [];
57+
let listResults = [];
58+
59+
// Process file requests first
60+
if (fileRequests.length > 0) {
61+
fileResults = await this.fileHandler.processFileRequests(aiResponse);
62+
}
63+
64+
// Process list file requests separately
65+
if (listFileRequests.length > 0) {
66+
listResults = await this.fileHandler.processListFileRequests(aiResponse);
67+
}
5368

5469
// Add successful file contents to context, avoiding duplicates
5570
const newFileContents = fileResults
@@ -63,17 +78,19 @@ class AISecurityAnalyzer {
6378

6479
allFileContents.push(...newFileContents);
6580

66-
// Build context for next iteration
81+
// Build context for next iteration with both file contents and directory listings
6782
currentContext = await this._buildIterativeContext(
6883
newFileContents,
6984
fileResults,
70-
iteration
85+
listResults,
86+
iteration,
87+
fileRequestsHistory,
88+
projectSummary
7189
);
7290
messages.push({ role: 'user', content: currentContext });
7391

7492
} else {
75-
// No more file requests, this should be the final analysis
76-
console.log(green('✅ AI completed analysis without additional file requests'));
93+
// No more file or list requests, this should be the final analysis
7794
finalAnalysis = aiResponse;
7895
break;
7996
}
@@ -116,7 +133,7 @@ class AISecurityAnalyzer {
116133
context += `Project Path: ${projectSummary.projectPath}\n`;
117134
context += `Total Files: ${projectSummary.totalFiles}\n\n`;
118135

119-
context += `Files in the project:\n${projectSummary.directoryStructure}\n`;
136+
// context += `Files in the project:\n${projectSummary.directoryStructure}\n`;
120137

121138
context += `Files by Extension:\n`;
122139
Object.entries(projectSummary.filesByExtension).forEach(([ext, files]) => {
@@ -139,31 +156,74 @@ class AISecurityAnalyzer {
139156
/**
140157
* Build context for iterative requests
141158
*/
142-
async _buildIterativeContext(fileContents, fileResults, iteration) {
159+
async _buildIterativeContext(fileContents, fileResults, listResults, iteration, fileRequestsHistory, projectSummary) {
143160
let context = "\n";
144161

145162
// Add file contents from previous requests
146-
context += `[ANALYZED FILES - Iteration ${iteration}]\n`;
147-
fileContents.forEach(file => {
148-
context += `\n=== FILE: ${file.path} ===\n`;
149-
context += `Reason: ${file.reason}\n`;
150-
if (file.reason === 'duplicate') {
151-
context += `[File was already analyzed in previous conversation]\n`;
152-
} else {
153-
context += `${file.content}\n`;
154-
}
155-
context += `=== END FILE: ${file.path} ===\n`;
156-
});
163+
if (fileContents.length > 0) {
164+
context += `[ANALYZED FILES - Iteration ${iteration}]\n`;
165+
fileContents.forEach(file => {
166+
context += `\n=== FILE: ${file.path} ===\n`;
167+
context += `Reason: ${file.reason}\n`;
168+
if (file.reason === 'duplicate') {
169+
context += `[File was already analyzed in previous conversation]\n`;
170+
} else {
171+
context += `${file.content}\n`;
172+
}
173+
context += `=== END FILE: ${file.path} ===\n`;
174+
});
175+
}
176+
177+
// Add directory listings from previous requests
178+
if (listResults && listResults.length > 0) {
179+
context += `\n[DIRECTORY LISTINGS - Iteration ${iteration}]\n`;
180+
listResults.forEach(result => {
181+
if (result.status === 'success') {
182+
context += `\n=== DIRECTORY: ${result.path} ===\n`;
183+
context += `Reason: ${result.reason}\n`;
184+
context += `Files and directories found (${result.fileCount} items):\n`;
185+
result.files.forEach(file => {
186+
const icon = file.type === 'directory' ? '📁' : '📄';
187+
context += `${icon} ${file.name} (${file.type})\n`;
188+
});
189+
context += `=== END DIRECTORY: ${result.path} ===\n`;
190+
} else {
191+
context += `❌ Directory listing failed for ${result.path}: ${result.reason}\n`;
192+
}
193+
});
194+
}
157195

158196
// Add file request results summary
159-
context += `\n[FILE REQUEST RESULTS]\n`;
160-
fileResults.forEach(result => {
161-
if (result.status === 'success') {
162-
context += `✅ ${result.path}: Successfully read (${result.lines} lines)\n`;
163-
} else {
164-
context += `❌ This file doesn't exist or is not a source file ${result.path}: ${result.reason}. Don't request it again\n`;
165-
}
166-
});
197+
if (fileResults.length > 0) {
198+
context += `\n[FILE REQUEST RESULTS]\n`;
199+
fileResults.forEach(result => {
200+
if (result.status === 'success') {
201+
context += `✅ ${result.path}: Successfully read (${result.lines} lines)\n`;
202+
} else {
203+
context += `❌ This file doesn't exist or is not a source file ${result.path}: ${result.reason}. Don't request it again\n`;
204+
}
205+
});
206+
}
207+
208+
// Add list request results summary
209+
if (listResults && listResults.length > 0) {
210+
context += `\n[DIRECTORY LISTING RESULTS]\n`;
211+
listResults.forEach(result => {
212+
if (result.status === 'success') {
213+
context += `✅ ${result.path}: Successfully listed (${result.fileCount} items)\n`;
214+
} else {
215+
context += `❌ Directory listing failed for ${result.path}: ${result.reason}. Don't request it again\n`;
216+
}
217+
});
218+
}
219+
220+
// Add file request history
221+
if (fileRequestsHistory.length > 0) {
222+
context += `\n[FILE REQUEST HISTORY]\n`;
223+
context += `So far, you have requested ${fileRequestsHistory.length} files\n`;
224+
context += `Project has ${projectSummary.totalFiles} files\n`;
225+
context += `\n`;
226+
}
167227

168228
// Add instructions for next step
169229
if (iteration < this.maxIterations) {
@@ -198,17 +258,35 @@ class AISecurityAnalyzer {
198258
return requests;
199259
}
200260

261+
/**
262+
* Extract list file requests from AI response
263+
*/
264+
_extractListFileRequests(response) {
265+
const listFileRequestRegex = /<list_file_request\s+path="([^"]+)"(?:\s+reason="([^"]*)")?\s*\/>/g;
266+
const requests = [];
267+
268+
let match;
269+
while ((match = listFileRequestRegex.exec(response)) !== null) {
270+
requests.push({
271+
path: match[1],
272+
reason: match[2] || 'No reason provided'
273+
});
274+
}
275+
276+
return requests;
277+
}
278+
201279
/**
202280
* Send context to AI and get response
203281
*/
204282
async _sendToAI(context, iteration, messages) {
205283
//console.log(dim(`📤 Sending context to AI (${context.length} characters)`));
206284

207285
// Display session state before the call
208-
if (this.tokenTracker) {
209-
const preCallData = this.tokenTracker.getPreCallUsageData(iteration);
210-
TokenDisplay.displayPreCallUsage(preCallData);
211-
}
286+
// if (this.tokenTracker) {
287+
// const preCallData = this.tokenTracker.getPreCallUsageData(iteration);
288+
// TokenDisplay.displayPreCallUsage(preCallData);
289+
// }
212290

213291
try {
214292
const response = await this.aiClient.analyze(context, messages);

extension/secureflow/packages/secureflow-cli/scanner/cli-full-scan-command.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,6 @@ class CLIFullScanCommand {
339339
* Output DefectDojo summary for user convenience
340340
*/
341341
_outputDefectDojoSummary(scanResult, defectDojoFindings) {
342-
console.log(`🤖 Model: ${scanResult.model}`);
343342
console.log(`📊 Files: ${scanResult.filesAnalyzed}/${scanResult.totalFiles} analyzed`);
344343

345344
const summaryCounts = {

0 commit comments

Comments
 (0)