Secret scanning for AI agents
Use ov scan --format agent when a coding agent needs to check for exposed
credentials. This mode returns structured detection results with opaque file
references. Source snippets, filenames, credential previews and raw error text
are excluded from its JSON report.
ov scan --format agent .Scanning needs no OV account. It runs the built-in detectors locally and does not verify credentials or store them. The CLI’s existing background update check still applies when run in a terminal.
Read the result
Section titled “Read the result”| Exit | status |
What the agent should do |
|---|---|---|
0 |
clean |
Report no findings within the declared scope. Check coverage before concluding the intended files were scanned. |
1 |
findings |
Explain the findings and guide local review and remediation. |
2 |
incomplete |
Explain why coverage is incomplete; retain any findings and resolve the missing scope before retrying. |
2 |
error |
Correct the invocation and retry. |
Require schema_version: 1, valid JSON and a matching exit/status pair. Missing,
malformed or unfamiliar output is a failed check. CLI syntax errors and process
termination may occur before a report is produced. A clean scan is detection
evidence for its scope; it does not certify that a project contains no secrets.
The version 1 report fields are:
policy:agent-local-v1.scope:working_tree,file,path_list,staged_index, ornonefor invocation failures.coverage: scanned and skipped file counts, excluded directory count, scanned bytes, and anincomplete_reasonsarray.limits: per-file bytes, total bytes, entries and finding limits.findings: an array, including when empty. Each finding hasid,file_id,detector,severity, numericlineandcolumn,verification: not_checked, and aremediationidentifier (openai_keyorcredential).next_steps: fixed workflow identifiers, not repository-authored instructions.error_code: empty for scans; a fixed code for invocation failures.
Finding and file references are local to one report. They are not persistent identities or handles for editing files. Do not use the numbers to correlate separate scans. To see locations, the user runs the companion format in their own terminal:
ov scan --format review .Review uses the same policy and shows quoted paths and finding positions without source snippets. It includes repository-controlled filenames, so keep this output local instead of feeding it to the model.
Know the scan scope
Section titled “Know the scan scope”Agent and review formats use a fixed policy. Repository configuration, baselines,
ignore files and inline suppression comments do not configure this mode.
The built-in detectors’ general heuristics still apply, including their limits
and false positives. Hidden directories such as .claude, .cursor and
.codex are included.
These exact directory names are excluded below the selected root:
.git, .gitnexus, .worktrees, node_modules, vendor, dist, bin,
.next, .astro, .venv, venv, and __pycache__.
Their contents are outside the reported scope. A skipped directory counts once;
the scanner does not count its unseen children. NUL-containing binary files
are also outside a directory scan’s text scope and count as skipped files.
The limits are 10 MiB per file, 100 MiB of file reads, 10,000 entries, 1,000
reported findings and 64 nested directory levels. A read can consume one
extra byte to detect a limit. These limits bound work, not the detector’s peak
memory. Resource exhaustion, read failures, cancellation, changed files, and
symlinks or special files encountered in scope produce incomplete.
Explicitly targeting an excluded binary file also produces incomplete.
The selected root or selected file’s parent resolves normally, including OS directory aliases. Stable symlinks below that boundary are not followed. Use a quiescent checkout: the scan is not an atomic snapshot and does not defend against arbitrary concurrent same-user filesystem mutation.
You can restrict the mode to a list of working-tree files:
set -o pipefailgit diff --name-only -z --diff-filter=ACMR | ov scan --format agent --include-only-paths-from-stdinRun this in Bash from the repository root. Require the path-producing command
to succeed too: pipefail prevents a failed git diff followed by an empty
scan from being accepted as a successful pipeline. Paths must be root-relative. NUL-delimited
names are preserved exactly; newline-delimited input also supports CRLF.
The input cap is 1 MiB and 10,000 nonempty paths. Missing paths, paths outside
the root, directories and explicitly excluded paths are not silently accepted
as clean. An empty list is a valid empty scope with files_scanned: 0.
Only --format, --staged, and --include-only-paths-from-stdin are supported with this
policy. Other explicit options—including --verify, --fix, --fail-on,
--history, configuration and baselines—return unsupported_option before
scanning. Findings at any severity cause exit 1 when the scan is complete.
Scan staged content
Section titled “Scan staged content”ov scan --format agent --staged# The user reviews locations in their own terminal:ov scan --format review --staged--staged scans the full staged blobs changed relative to HEAD. On a branch
without a first commit, it scans staged additions. Unchanged tracked files,
untracked files, intent-to-add entries and unstaged edits are outside this scope.
Deletions have no staged content to scan. Renamed destinations are scanned as
additions. This is a full-blob scan, not an added-lines-only scan.
The optional positional directory selects a repository; running from a
subdirectory still scans changes across that repository. Linked worktrees use
their own index. Inherited GIT_DIR, GIT_INDEX_FILE and injected Git config
do not select another index. Do not combine --staged with stdin paths.
The flag requires agent or review format; legacy formats retain their existing
working-tree and history behavior.
The JSON shape and agent-local-v1 detection policy are unchanged, with the new
scope value staged_index. Consumers that enumerate scopes must recognize it
before accepting the result. Blob IDs are retained internally during a scan;
they are not included in the report. Local review positions refer to staged
content and repository-root-relative paths, so they may differ from editor
positions in the working tree. After removing a credential locally, stage the
removal and rescan staged content too.
Conflicts, symlinks, submodules, missing objects, excluded staged paths or binary
blobs, and exhausted limits produce incomplete/2. Findings gathered before a
failure are retained. Empty and deletion-only scopes report zero scanned files.
The existing file, byte, entry and finding limits apply; Git enumeration also
has a 4 MiB metadata cap and staged scanning has a two-minute deadline.
Cancellation is checked between detector calls; this is not a hard CPU sandbox.
Additional reason codes are git_error, git_metadata_limit and
unmerged_index; expiration reports cancelled.
The scanner sets GIT_NO_LAZY_FETCH=1 and blocks network protocols independently,
including on older Git versions. It also disables external diff/textconv,
replacement objects and fsmonitor. It invokes Git without shell interpolation
or repository hooks. Missing partial-clone objects must be fetched separately
by the user before retrying. Use a quiescent repository: blob IDs pin the
enumerated bytes, but concurrent changes during enumeration are not an atomic
snapshot guarantee.
Guided example: replace an exposed OpenAI API key
Section titled “Guided example: replace an exposed OpenAI API key”The goal is to give the application a replacement credential while keeping the credential out of the agent’s conversation. The user handles credential entry; the agent handles explanations, secret names and the scan report.
-
Find the exposure. The agent runs
ov scan --format agent .and explains anopenai-keyfinding. The user runsov scan --format review .locally to locate it. Do not paste the file, key or review output into chat. -
Replace and revoke at the provider. In the provider console, create a replacement with the needed permissions and revoke the exposed key. Coordinate application cutover if necessary; until revoked, the old key remains exposed. The scanner does not perform or attest to this step.
-
Store the replacement directly in OV. Sign in with
ov auth loginif needed. Choose the intended app and environment, then run this yourself:Terminal window ov secret set OPENAI_API_KEY --app my-app --env developmentReplace the example app/environment names with your existing OV context. Enter the replacement at OV’s hidden local value prompt and unlock locally. Do not put the value in a command argument,
.envfile, or AI chat. The existing OV storage path encrypts the value client-side. -
Remove the literal locally. Replace hardcoded credentials or plaintext agent configuration with your application’s supported runtime secret interface. Keep only the name
OPENAI_API_KEYin shared configuration. Use your framework/SDK’s existing environment support with OV injection. The agent should not open the credential-bearing file to perform this step. -
Check the application through OV. For an existing Python application, the user can run its normal health check or test command this way:
Terminal window ov run --app my-app --env development --secrets OPENAI_API_KEY -- python app.pyUse your application’s actual command in place of
python app.py. For an agent-driven check, prefer the connected MCPvault_check_contextandvault_runtools, which provide execution metadata without returning child stdout or stderr to the model. Treat a successful process exit as that command’s evidence, not proof of overall application health. -
Rescan the same scope. Run
ov scan --format agent .again. Require a complete report and review its findings. If the credential was committed, coordinate a separate local history review and cleanup; the working-tree result says nothing about historical copies.
Track the evidence separately: provider revocation, OV storage, plaintext
removal, application operation, and the final scan. ov scan --fix stores
credentials interactively and prints guidance; it does not establish all five.
For a citizen developer, the same flow is: locate the exposed connection, replace its credential at the service, store the replacement in OV, connect the tool through its supported secret interface, then test and rescan. If a tool only accepts pasted plaintext credentials, OV injection needs a supported adapter before this workflow can cover it.