TL;DR: grepai helps Claude Code find the right code by understanding its meaning, not just matching keywords, reducing token usage while making security audits faster and more accurate

Benefits of Semantic Code Search
Reduce token usage: Semantic search retrieves only the most relevant code, minimizing context size and lowering token consumption
Improve vulnerability detection: Finds security-relevant code by meaning, not just exact keyword matches
Increase audit accuracy: Provides Claude Code with focused context for faster, more precise security analysis
Flow

Prerequisites
Requirement | Description |
|---|---|
Ollama | Local embedding provider — install from ollama.com/download |
Embedding model |
|
grepai | CLI from github.com/yoanbernabeu/grepai |
Command: Send below command to terminal
# Ollama — install, start, and pull the embedding model
brew install ollama # macOS — see the docs above for Linux/Windows
ollama serve & # leave running; verify with: curl http://localhost:11434/api/tags
ollama pull nomic-embed-text
ollama list # confirm nomic-embed-text is present
# grepai — install via Homebrew (macOS) or the install script (Linux/macOS)
brew install yoanbernabeu/tap/grepai
# brew upgrade grepai # to upgrade later
# curl -sSL https://raw.githubusercontent.com/yoanbernabeu/grepai/main/install.sh | sh # Linux/macOS alternativeStep 1: Initialize grepai on DVAPI
Initialization creates the project configuration that defines how grepai generates embeddings and stores its semantic index.
Clone DVAPI and initialize grepai:
Command: Send below command to terminal
git clone https://github.com/payatu/DVAPI.git
cd DVAPI
grepai init --provider ollama --backend gob --yes--yes skips interactive prompts for provider and backend. Drop it to be prompted instead.
Step 2: Start continuous semantic indexing
grepai watches the repository and updates the index automatically as files change, unlike tools that require manual re-indexing.
Command: Send below command to terminal
grepai watchInitial indexing takes a few minutes on large repositories. DVAPI's Express + EJS codebase is small: seconds to complete.

Terminal showing grepai watch running and grepai status output
Semantic search with grepai: IDOR security code audit
This demo uses the grepai CLI only. Claude Code isn't involved yet. The goal is exploring an unfamiliar codebase by meaning rather than exact text.
Situation
DVAPI needs a security review. The goal is to check for an IDOR vulnerability. Can a user change a user ID, ticket ID, or username to access another user's data?
Task
Trace how a user-controlled ID moves from the HTTP request to the database.
Check if the code verifies that the user owns the resource before returning the data.
Step by step
1. Find where a user-supplied identifier looks up a resource
Nothing is known about DVAPI's code layout yet. Start with intent, not filename. Scope the query to application behavior, not documentation:
Command: Send below command to terminal
grepai search "endpoint handler that fetches another user's saved note by username without checking the requester's identity"
Relationship graph showing the initial code discovery process
(Example output):
─── Result (score: 0.5622) ───
File: src/controllers/controllers.js:92-145
101 │ exports.addNote = (req, res, next) => {
102 │ const { note } = req.body;
...
123 │ exports.getNote = (req, res, next) => {
124 │ async function getUserSecretNote() {
125 │ try {
126 │ const user = await User.findOne({ username: req.query.username });
127 │ console.log('User found:', user);
128 │ return res.json({ status: "success", note: user.secretNote });
...One matched chunk. Two functions worth comparing: addNote writes a note, getNote reads one. Neither "note" nor "username" had to appear in the query. grepai matched intent and landed on the exact pair needed for an IDOR audit.
2. Find the request entry point
/api/getNote is the target, but HTTP method and its validation are unknown. A second search answers this directly instead of opening files:
Command: Send below command to terminal
grepai search "express route definitions and middleware chain for getNote endpoint"
Relationship graph mapping the HTTP request path to the handler
(Example output):
─── Result 1 (score: 0.6156) ───
File: src/routes/routes.js:1-32
11 │ router.get('/api/getNote', auth.verifyToken, controller.getNote);
The route is authenticated: auth.verifyToken runs first. But authentication only proves the requester is someone, not whether controller.getNote checks that the someone matches the username being requested.
3. Read the handler to find the source

Relationship graph tracing where user-controlled data first enters the application
exports.getNote = (req, res, next) => {
async function getUserSecretNote() {
const user = await User.findOne({ username: req.query.username });
return res.json({ status: "success", note: user.secretNote });
}
getUserSecretNote();
}The source is visible: req.query.username, set by the caller, feeds directly into the database lookup. verifyToken already attached the authenticated identity as req.userId and req.user, but the handler never reads either one. That's the shape of the bug. The next four commands confirm it with the call graph instead of trusting the read.
4. Sink-to-source: trace backward from the database read
getUserSecretNote is a named function, unlike the exports.x = (req, res) => {} handler that wraps it. That makes it traceable:
Command: Send below command to terminal
grepai trace callers "getUserSecretNote"
Relationship graph with validated call relationships
(Example output):
Symbol: getUserSecretNote (function)
File: src/controllers/controllers.js:124
Callers (2):
------------------------------------------------------------
1. getUserSecretNote
Defined: src/controllers/controllers.js:124
Calls at: src/controllers/controllers.js:124
Context: async function getUserSecretNote() {
2. updateUserSecretNote
Defined: src/controllers/controllers.js:106
Calls at: src/controllers/controllers.js:134
Context: getUserSecretNote();
The
Contextline correctly identifies the actual invocation at line 134:getUserSecretNote();(insideexports.getNote)The attributed symbol name,
updateUserSecretNote, is incorrect.exports.getNoteis an arrow-assigned handler, which grepai's regex-based tracer does not recognize as a function scope
5. Source-to-sink: trace forward from the same function
Command: Send below command to terminal
grepai trace callees "getUserSecretNote"
Relationship graph illustrating the complete read-path data flow
(Example output):
Symbol: getUserSecretNote (function)
File: src/controllers/controllers.js:124
Callees (6):
------------------------------------------------------------
1. getUserSecretNote
Defined: src/controllers/controllers.js:124
Called at: src/controllers/controllers.js:124
2. findOne
Called at: src/controllers/controllers.js:126
3. log
Called at: src/controllers/controllers.js:127
4. json
Called at: src/controllers/controllers.js:128
The full path is now shown in both directions. It starts with req.query.username (the input), goes through User.findOne (the database lookup), and ends at res.json, which sends secretNote back to the user.
6. Contrast with the correctly-scoped counterpart
updateUserSecretNote (the function that writes a note) sits directly above getUserSecretNote. Tracing its callees checks whether the write path made the same mistake:
Command: Send below command to terminal
grepai trace callees "updateUserSecretNote"Real output (trimmed to the write path):
Symbol: updateUserSecretNote (function)
File: src/controllers/controllers.js:106
Callees (15):
------------------------------------------------------------
2. findOneAndUpdate
Called at: src/controllers/controllers.js:108
...
7. updateUserSecretNote
Defined: src/controllers/controllers.js:106
Called at: src/controllers/controllers.js:120
Context: updateUserSecretNote(req.userId);The write path uses req.userId from verifyToken, not the request body. findOneAndUpdate only updates the logged-in user's document. Unlike the read path, the write path uses a trusted user ID.

Relationship graph comparing the application's read and write execution paths
Verify
exports.getNote and getUserSecretNote never use req.user or req.userId. No code checks if the logged-in user owns the requested username before returning secretNote. This finding comes from reading the code only. No requests were sent to a running DVAPI instance.
Result
Found an IDOR vulnerability through code review only
Traced the full flow from
req.query.usernametores.jsonFound that the read path does not check ownership before returning
secretNoteCompared it with the safe write path, which uses
req.userIdgrepai found the vulnerable endpoint and its route from a simple search
trace callers,trace callees, andtrace graphshowed the full call flow without manually searching every file
Integrate Claude Code + grepai
Claude Code and grepai work together for an AI-assisted security code audit. Claude uses grepai to understand the code, find security issues, and save the findings to a report
Connect grepai to Claude Code
Command: Send below command to terminal
touch CLAUDE.md # Only if not exists
grepai agent-setup --with-subagentThis finds Claude Code's CLAUDE.md file and adds instructions to use grepai search and grepai trace instead of grep for code search. It also creates a deep-explore agent with grepAI access, so Claude can answer security questions automatically.
Review 1: Authentication
Prompt: Send below prompt to Claude Code
Review the authentication implementation in this repository for security issues.
Use grepai search and grepai trace to:
- Locate every authentication-related file and function.
- Trace the complete login execution path.
- Identify where passwords are checked and where the JWT is created.
Check for:
- Missing password hashing
- Hardcoded secrets
- Missing account lockout
- JWT implementation issues
For every finding, report the affected file, the execution path, the impact, and a fix.
Save the complete findings as Markdown to findings/authentication-review.md.
Expected grepai usage: grepai search "user authentication and login" locates auth.js, then direct read of auth.login/auth.register. Both are exports.x = handlers, so grepai trace returns No symbol found. Claude Code falls back to reading the file, consistent with the fallback behavior grepai agent-setup configures.
Review 2: Broken access control
Prompt: Send below prompt to Claude Code
Perform an authorization review of this repository.
Use grepai search and grepai trace to:
- Locate the authorization/authentication middleware.
- Find every caller of that middleware.
- Trace which routes it protects and which routes bypass it.
Check for:
- Missing authorization on sensitive routes
- IDOR (e.g. user lookups by parameter)
- Inconsistent enforcement between similar endpoints
Provide the complete execution path for every finding.
Save the complete findings as Markdown to findings/access-control-review.md.
Expected grepai usage: grepai search "route authorization middleware" finds verifyToken in auth.js, then direct read of routes.js to enumerate routes. verifyToken is another exports.x = handler, so grepai trace callers returns No symbol found (same limitation noted earlier).
Review 3: SSRF
Prompt: Send below prompt to Claude Code
Review this repository for server-side request forgery (SSRF).
Use grepai search and grepai trace to:
- Locate every place the code makes an outbound HTTP request.
- Trace how the destination URL is built.
- Determine whether user input can influence the destination.
Check for:
- SSRF
- Missing URL allowlists
- Missing protocol or hostname validation
Explain the complete execution path for every finding.
Save the complete findings as Markdown to findings/ssrf-review.md.
Expected grepai usage: grepai search "server side request forgery unvalidated url" ranks documentation above handlers (as shown earlier), then grepai trace callers "updateUserSecretNote" reaches addNoteWithLink through the one traceable inner function.
Daily review loop
A typical session:
Command: Send below command to terminal
grepai watch --background # keep the index current
claude # open Claude Code in the repositoryDescribe the review target in plain language (e.g. "review the authentication implementation for security issues").
Claude Code calls
grepai searchandgrepai traceto pull semantic context instead of reading files at random.Findings are written to Markdown under
findings/, versioned in git, available as source material for the next Claude Code session without re-running.Confirm every finding by reading the source directly before treating it as real. grepai narrows down where to look. It doesn't replace reading the code.
Trade-offs
This setup is static only. No request hits a running instance. Findings are candidates needing manual source confirmation, not confirmed live issues.
Semantic search returns close matches by meaning. A query can return related code that's not an exact match. False positives are expected.
The embedding step needs local compute through Ollama. DVAPI-sized repositories index in seconds. Much larger monorepos take proportionally longer.
Further Reading
grepai — the CLI used in this guide
DVAPI — the vulnerable API used for both demos
Ollama — local embedding provider
Claude Code documentation — subagents, MCP, and CLAUDE.md configuration
Ready to apply AI to your Security Engineering?
Subscribe to Secengai Newsletter for weekly actionable content on AI for security engineers.
This content reflects personal views, experiments, and use cases in AI and security engineering. It does not represent any employer's positions, policies, or practices.

