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

  • 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

nomic-embed-text, pulled through Ollama

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 alternative

Step 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 watch

Initial 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 Context line correctly identifies the actual invocation at line 134: getUserSecretNote(); (inside exports.getNote)

  • The attributed symbol name, updateUserSecretNote, is incorrect.

  • exports.getNote is 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.username to res.json

  • Found that the read path does not check ownership before returning secretNote

  • Compared it with the safe write path, which uses req.userId

  • grepai found the vulnerable endpoint and its route from a simple search

  • trace callers, trace callees, and trace graph showed 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-subagent

This 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 repository
  1. Describe the review target in plain language (e.g. "review the authentication implementation for security issues").

  2. Claude Code calls grepai search and grepai trace to pull semantic context instead of reading files at random.

  3. Findings are written to Markdown under findings/, versioned in git, available as source material for the next Claude Code session without re-running.

  4. 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

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.

Keep Reading