# Keyhole manual

# Let your assistant start the handoff.

Your assistant prepares the instructions and requests the setting. You supply the value outside the conversation.

> **Keyhole 0.1.0 is available.** Install it from the [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=jlumbroso.keyhole) or [Open VSX](https://open-vsx.org/extension/jlumbroso/keyhole), or run the command below. This release provides manual entry into `.env`.

 

```
code --install-extension jlumbroso.keyhole
```

 

Your assistant can build the integration, but a step may still need you: signing in to a service, authorizing access or supplying a token. Keyhole gives the assistant a way to initiate that handoff. Its companion [Porthole](https://jlumbroso.github.io/porthole/) can display the instructions beside your work.

 

The assistant requests a setting by name. You obtain its value and enter it in Keyhole's masked box. The value goes into the project without passing through the conversation.

 

> **Released and preview.** Keyhole 0.1.0 supports manual entry into `.env`. Assistant-triggered requests and a status MCP are implemented in an unreleased preview. Harness-specific settings writers are still to come. The [model reference](https://jlumbroso.github.io/keyhole-docs/for-assistants.html) separates these interfaces.

 

## Choose your starting point

 

- [Try Keyhole with a harmless value](https://jlumbroso.github.io/keyhole-docs/first-secret.html) before introducing a real credential.
- [Connect a Render MCP server](https://jlumbroso.github.io/keyhole-docs/render-mcp.html) to help your assistant work with Render services.
- [Connect a Neon MCP server](https://jlumbroso.github.io/keyhole-docs/neon-mcp.html) to help your assistant work with Neon projects and databases.
- [Give your assistant the exact trigger commands](https://jlumbroso.github.io/keyhole-docs/for-assistants.html), request format and status contract.
- [Find the right assistant configuration](https://jlumbroso.github.io/keyhole-docs/assistant-settings.html) for Claude Code, Codex, Gemini CLI or Grok Build.

 

The service guides start with the task and authorization method. If the MCP offers browser sign-in, you may not need to copy a token at all.

 

## A name and a value

 

A setting has two parts. Its name, such as `DATABASE_URL`, tells the program which setting it is. Its value is the connection string, key, or other information supplied by the service.

 

Discuss the name with your assistant. Enter the value in Keyhole.

 

Before obtaining a credential, ask which service and credential type the project needs, what it will use it for, and the exact variable name it expects. A database connection string and an account-management API key serve different purposes, even when both come from the same provider.

 

## What Keyhole does

 

Run `Keyhole: Set a Secret in .env…` from VS Code's Command Palette. Keyhole asks for the variable's name, checks Git's tracking and ignore rules when a repository is present, and takes the value in a masked input box. It appends the setting to the selected folder's `.env`. If it finds a repository VS Code cannot open, it refuses the write. [A folder without a detected repository can receive a value](https://jlumbroso.github.io/keyhole-docs/troubleshooting.html#no-git-repository).

 

In this first command flow, you start the command yourself. The preview adds [requests an assistant can trigger](https://jlumbroso.github.io/keyhole-docs/for-assistants.html#request-link); it still writes to `.env`. Neither build writes directly to an assistant's settings file yet.

 

`.env` is an ordinary text file. Your program must be configured to read it. Writing this local file does not update the settings of a deployed service.

 

## Saved, loaded, and accepted are different

 

1. Saved: Keyhole wrote the setting to `.env` and checked the result.
2. Loaded: the program read that file and received the setting. A program that reads settings at startup needs to restart.
3. Accepted: the service accepted the credential for the operation you tried.

 

Keyhole's success message confirms the first step. Check the program's behavior to establish the other two, without printing the value.

 

## Before using a real secret

 

Follow [Keep secrets out of the conversation](https://jlumbroso.github.io/keyhole-docs/keeping-secrets-out-of-the-conversation.html) for the application running your assistant. Keyhole omits the full value from its own messages and logs, but another tool with file access may still read `.env`. The file is not encrypted.

 

Git ignore rules help prevent future commits. They do not prevent reads, and they do not remove a secret from an earlier commit.

 

## What you need

 

- VS Code desktop 1.134.0 or later.
- Your project open as a local folder.
- Git installed and VS Code's built-in Git extension enabled.

 

Hand checks recorded so far cover macOS with VS Code 1.141.0. Windows and Linux have not been checked.

 

If a step does not work, start with [Troubleshooting](https://jlumbroso.github.io/keyhole-docs/troubleshooting.html). Share the variable's name and the message you saw, without the value or a copy of `.env`.

## Chapter notes

**Name, not value**

Your assistant can discuss DATABASE_URL without seeing the connection string.

**Start harmlessly**

Use the practice setting before introducing a real credential.

Keyhole 0.1.0 · previews labeled separately. Updated 24 October 2026. [Online chapter](https://jlumbroso.github.io/keyhole-docs/index.html). [Source reference](https://github.com/jlumbroso/keyhole/blob/main/docs/manual/content/overview.html).

Source access currently requires a repository invitation.


---

# Try it with a harmless value.

Walk through the command once, using a setting that cannot unlock anything.

Once Keyhole is installed, try the flow with an invented value before using a real credential. Use a new practice folder so you can recognize what changes. See the [overview](https://jlumbroso.github.io/keyhole-docs/index.html) for release status and requirements.

 

## Try a harmless value

 

1. Open the practice folder in VS Code. Keyhole writes to that folder's `.env`. If several local folders are open, the command asks which one to use.
2. Choose **View → Command Palette…** from VS Code's menu, then search for `Keyhole: Set a Secret in .env…` and select it.
3. Enter the variable name `KEYHOLE_PRACTICE` and press Enter. Names use capital letters, digits, and underscores, and start with a letter.
4. Read any Git message before continuing. If Git does not ignore `.env`, Keyhole offers `Add to .gitignore`. Choosing it adds an ignore rule and checks again. If Git reports that it already tracks the file, Keyhole stops; follow [Git tracks the file](https://jlumbroso.github.io/keyhole-docs/troubleshooting.html#git-tracks-the-file) before continuing. A repository VS Code cannot open also stops the write. When no repository is detected, the write is allowed; see [folders without Git](https://jlumbroso.github.io/keyhole-docs/troubleshooting.html#no-git-repository).
5. Enter the practice value `not-a-secret`. The box masks what you type. The eye button shows the value; selecting it again hides the value. Switching away from the VS Code window also hides a revealed value.
6. Press Enter to save. Look for the message beginning `Keyhole set KEYHOLE_PRACTICE in`. It names the destination and gives the value's length without displaying the full value.

 

Keyhole appends the setting beneath a comment recording when it wrote it. The success message confirms a file write. It does not confirm that a running program loaded the setting or that a service accepted it.

 

## Use a real setting

 

First configure your assistant's [file protections](https://jlumbroso.github.io/keyhole-docs/keeping-secrets-out-of-the-conversation.html). Then repeat the steps in your actual project, using the variable name the project expects and the value from the service. Paste the value into Keyhole, not the chat.

 

If the program reads `.env` when it starts, restart that program after saving. Check the task you were trying to perform. Ask for a result such as “setting present” or “connection succeeded,” without printing the value.

 

> Saving a local `.env` does not configure a hosted application. If the program runs on a hosting service, follow that service's instructions for its environment settings.

 

## Set a new value for an existing name

 

If the name is already set, Keyhole asks whether to `Set New Value`. Choosing it opens the value box and, after you submit, appends another assignment. A comment identifies the earlier line it supersedes. The old value stays in the file.

 

Cancel keeps the existing assignment. After you confirm that the new value works, you may remove the old line locally. Removing a line does not revoke the old credential at its service.

 

## Cancel or pause

 

If you cancel before submitting the value, Keyhole does not save that value to `.env`. An ignore rule you already approved may remain in `.gitignore`.

 

The value box stays open when you switch to another application, and a revealed value becomes masked again. If you close the box, run the command again when you are ready; this version does not save a draft.

 

If the result differs from these steps, see [Troubleshooting](https://jlumbroso.github.io/keyhole-docs/troubleshooting.html).

## Chapter notes

**Practice value**

not-a-secret is deliberately harmless. Keep real credentials out of screenshots and chat.

**After saving**

A saved file does not establish that your program has loaded it.

Keyhole 0.1.0 · previews labeled separately. Updated 24 October 2026. [Online chapter](https://jlumbroso.github.io/keyhole-docs/first-secret.html). [Source reference](https://github.com/jlumbroso/keyhole/blob/main/docs/manual/content/first-secret.html).

Source access currently requires a repository invitation.


---

# Trigger the next step.

Exact commands for displaying a guide, registering a request and checking status, with their availability made explicit.

> **Request interface: implemented preview.** The request link, inbox and status MCP described here belong to Keyhole's preview branch, checked at `50d6803`. They are not included in the released 0.1.0, which provides `Keyhole: Set a Secret in .env…`. Porthole's local-file opening interface is released in 0.4.1.

 

Initiate the step that needs the person. Prepare the instructions, display them through Porthole, and submit a Keyhole request containing the setting's name and purpose. The person obtains the credential and supplies its value in Keyhole. Continue from the recorded status without asking for the value in chat.

 

## Choose the handoff before requesting a token

 

1. Identify the service, task and credential type. For MCP access, start with the [Render](https://jlumbroso.github.io/keyhole-docs/render-mcp.html) or [Neon](https://jlumbroso.github.io/keyhole-docs/neon-mcp.html) guide. If browser authorization is sufficient, use that route instead of requesting an API key.
2. Identify the receiving process and destination. A project's `.env` is not automatically available to every MCP server. Check [assistant settings](https://jlumbroso.github.io/keyhole-docs/assistant-settings.html) before promising a complete setup.
3. Explain what access the credential grants. Ask only for the names the chosen setup needs; never request a value through a tool argument, URL or conversation.
4. Confirm the person has a build with the interface below. If not, display the guide and explain the current Command Palette entry flow.

 

## 1. Display a local guide with Porthole

 

Porthole opens an existing local Markdown or HTML file. It does not open the manual's remote URL directly. Run the download from the selected workspace folder's root, so the shell and Porthole resolve the same relative path:

 

```
mkdir -p docs/keyhole && curl --fail --location 'https://jlumbroso.github.io/keyhole-docs/render-mcp.md' --output docs/keyhole/render-mcp.md
```

 

After the download succeeds, read the local guide before presenting it. Then, on macOS, open it with this credential-free command. Replace `my-project` with the receiving VS Code workspace folder's display name:

 

```
open 'vscode://jlumbroso.porthole/v1/open?file=docs/keyhole/render-mcp.md&repo=my-project&width=480&height=720'
```

 

For Neon, use `neon-mcp.md` in both the download and local path. Keep credentials out of the file. The OS sends the link to the last-used VS Code window; `repo` disambiguates a folder in that window, not a different window.

 

Executing `open` is a GUI action subject to your harness permissions. Printing a command or link is not executing it. Porthole gives no completion receipt to the caller, and opening a guide does not establish that the person has read it. See [Porthole's terminal and agents reference](https://jlumbroso.github.io/porthole-docs/terminal-and-agents.html).

 

## 2. Register a request by opening its URI

 

With the Keyhole preview installed, this harmless practice request records the name and announces it in VS Code:

 

```
open 'vscode://jlumbroso.keyhole/v1/request?name=KEYHOLE_PRACTICE&why=Try%20the%20request%20flow&repo=my-project'
```

 

The person chooses **Keyhole: Show Inbox** and selects the request when ready. The link does not immediately open a value box. With 0.1.0, the person instead runs **Keyhole: Set a Secret in .env…**, enters the agreed name, and follows [the practice walkthrough](https://jlumbroso.github.io/keyhole-docs/first-secret.html).

 

Preview URI: `vscode://jlumbroso.keyhole/v1/request`

| Parameter | Meaning |
| --- | --- |
| `name` | Required variable name. Repeat for multiple names, in the order requested. A request accepts 1–10 distinct names. |
| `why` | Optional short explanation of the task that needs the credential. |
| `where` | Optional description of where the person obtains it. This is not the output file or a configurable destination. |
| `repo` | Optional exact display name of an open local workspace folder. Without it, exactly one local folder must be open. |

 

Encode query values. Names must match `[A-Z][A-Z0-9_]*`; names that alter command execution or sensitive runtime behavior are refused. Keep `why` and `where` to 500 characters each, without line breaks or control characters. Do not repeat these fields or `repo`. Avoid text containing an ampersand followed by a parameter-like name and equals sign; VS Code's pre-decoding makes that ambiguous in this preview.

 

There is no `value`, `token` or `target` parameter. Unknown fields are rejected. Allowed prose is not a secret detector: never hide a value inside `why` or `where`. The destination remains the chosen folder's `.env`.

 

## Alternative: write the request record

 

An assistant that can edit project files can add the same request to `.keyhole/inbox.json`. Preserve existing rows. Use the current UTC time for `asked`; the date below is illustrative.

 

```
{
  "format": 1,
  "requests": [
    {
      "kind": "request",
      "names": ["KEYHOLE_PRACTICE"],
      "why": "Try the request flow",
      "asked": "2026-10-24T23:00:00Z"
    }
  ]
}
```

 

The file belongs to that project, so it has no `repo` field. Each row permits only `kind`, `names`, `why`, `where` and `asked`. The active extension watches the file. Requests already present at startup are not announced or initially refreshed; the URI is preferable for an active handoff. The record contains names and instructions, never values; it is intended to be shareable with the project.

 

The URI route and file route reach the same request checks. A repeated URI with the same ordered names does not add another row or update its description. Neither route returns a request ID or proves that the person submitted a value.

 

## 3. Connect the status MCP

 

In the preview, run **Keyhole: Connect an Assistant…**. It copies the registration command for the chosen harness, with the installed server's actual stable path. The person runs that command once. These are the source-derived command shapes; `<server-path>` is a placeholder, not a file to create:

 

```
claude mcp add --scope user --transport stdio keyhole -- node "<server-path>"
codex mcp add keyhole -- node "<server-path>"
gemini mcp add -s user keyhole node "<server-path>"
grok mcp add keyhole -- node "<server-path>"
```

 

These commands require `node` on the harness's PATH. VS Code's own chat is offered a Keyhole MCP provider separately. Connecting the server exposes a status tool; it does not create requests or display guides.

 

## 4. Call `secret_status`

 

The preview MCP has one tool, `secret_status`. Pass a required name and, preferably, the project's absolute path. Here are its arguments:

 

```
{
  "name": "KEYHOLE_PRACTICE",
  "project": "/absolute/path/my-project"
}
```

 

For an MCP client implementing the protocol directly, the corresponding tool-call message is:

 

```
{"jsonrpc":"2.0","id":7,"method":"tools/call","params":{"name":"secret_status","arguments":{"name":"KEYHOLE_PRACTICE","project":"/absolute/path/my-project"}}}
```

 

Only `name` and `project` are accepted. If `project` is omitted, the server uses `CLAUDE_PROJECT_DIR`, then its working directory. Unlike the URI's `repo`, this is a filesystem path; it is not limited to currently open workspace folders.

 

Interpreting the structured result

| Result | What it establishes |
| --- | --- |
| `known: false` | No readable cached entry. This is not proof that the variable is absent. |
| `known: true, set: false` | The cache records no matching assignment at `checked`. A missing or unreadable `.env` can also produce this result. |
| `known: true, set: true` | The extension found an assignment at `checked`, which may be empty. The result includes its length and keyprint; it does not establish a usable credential. |

 

The MCP process reads `.keyhole/status.local.json`, not `.env`. The extension maintains that cache. Check the timestamp; do not turn a cached observation into a claim about a fresh write, a particular request, or service authentication. A keyprint can include a recognized public prefix and the last four characters of a long value.

 

## Continue without the value

 

Distinguish these outcomes in your reply: guide displayed, request registered, setting observed, process configured, and service accepted a permitted operation. Resume only the work supported by the observed step. Do not print environment variables, read a secret-bearing file, or ask the person to paste it to diagnose a failure. Use the service guide's verification step and [file protections](https://jlumbroso.github.io/keyhole-docs/keeping-secrets-out-of-the-conversation.html).

 

## Reference status

 

Checked against [Keyhole preview source at 50d6803](https://github.com/jlumbroso/keyhole/tree/50d68037f398797643a4da39f59da4eb545c98e5/src) and [Porthole 0.4.1](https://github.com/jlumbroso/porthole/releases/tag/v0.4.1) on 24 October 2026. The Keyhole source link currently requires repository access. These examples are documentation/source checked, not a fresh end-to-end test in a running assistant or a provider account.

## Chapter notes

**Preview contract**

Keyhole requests and secret_status are implemented on an unreleased branch. Version 0.1.0 supports manual entry only.

**Names cross**

Requests carry names and instructions. The person supplies the value in VS Code.

Keyhole 0.1.0 · previews labeled separately. Updated 24 October 2026. [Online chapter](https://jlumbroso.github.io/keyhole-docs/for-assistants.html). [Source reference](https://github.com/jlumbroso/keyhole/blob/main/docs/manual/content/for-assistants.html).

Source access currently requires a repository invitation.


---

# Connect your assistant to Render.

Choose browser authorization or an API key, then give the Render MCP server the access it needs.

Use Render's MCP server when you want your coding assistant to inspect or manage services hosted on Render. This connects the assistant to your Render account; it does not supply a credential to the application you are building.

 

> Guide preview. Steps checked against official documentation on 24 October 2026; sign-in and connection checks have not yet been tested here.

 

## 1. Choose how to connect

 

Start with browser authorization, also called OAuth. You sign in to Render and review access in your browser, without copying a token into Keyhole or the conversation. Use an API key only when your client or unattended setup needs one.

 

Agree on the workspace and task first. Render's tools can change services and start deployments. For an initial check, request only a service list. See [Render's MCP instructions](https://render.com/docs/mcp-server).

 

## 2. Authorize in your browser

 

### Claude Code

 

1. In Claude Code, enter `/plugin install render@claude-plugins-official`.
2. Choose the plugin's installation scope, then run `/reload-plugins`. This choice concerns installation, not the Render resources you authorize.
3. Ask for the [connection check below](https://jlumbroso.github.io/keyhole-docs/render-mcp.html#verify-connection). On first use, complete Render's browser authorization. Check the account and requested access before approving.

 

For Codex, Cursor or Claude Desktop, follow [Render's instructions for that client](https://render.com/docs/mcp-server#1-connect-to-the-mcp-server), then continue to the connection check.

 

## Alternative: obtain an API key

 

1. Prepare the receiving configuration first.
2. Open [Render's account settings](https://dashboard.render.com/u/settings?add-api-key=), find the API keys area, and choose **Create API Key**.
3. Name the key for its use, such as `assistant-on-my-laptop`.
4. Copy the new value directly to the receiving field. The full key is displayed only when created.

 

See [Render's key-creation walkthrough](https://render.com/tutorials/render-with-ai-agents/install-the-plugin) and [API key documentation](https://render.com/docs/api#1-create-an-api-key).

 

> Treat this key as access to every Render workspace you belong to, as specified by [Render's API authentication reference](https://api-docs.render.com/reference/authentication). Choosing a workspace for an MCP operation does not restrict the credential itself. Do not describe the key as read-only or limited to one service.

 

## Put the key in the client's configuration

 

For a coding client, identify its exact credential destination using [Assistant settings](https://jlumbroso.github.io/keyhole-docs/assistant-settings.html) and Render's client instructions. Have your assistant prepare a placeholder configuration. Apply the [file-access protections](https://jlumbroso.github.io/keyhole-docs/keeping-secrets-out-of-the-conversation.html), then enter the value yourself.

 

Keep the real key out of assistant-run commands, chat, and shared files. Once the receiving file contains it, do not ask the assistant to open that file to check the edit.

 

### Claude Code: reference the key by name

 

Prepare this project-root `.mcp.json` configuration while it contains only references. Merge the `render` entry with existing servers; do not replace the whole file if it already has settings.

 

```
{
  "mcpServers": {
    "render": {
      "type": "http",
      "url": "https://mcp.render.com/mcp",
      "headers": {"Authorization": "Bearer ${RENDER_API_KEY}"}
    }
  }
}
```

 

1. Leave `${RENDER_API_KEY}` unchanged. Privately set `env.RENDER_API_KEY` in the project's `.claude/settings.local.json`, following [the protected settings walkthrough](https://jlumbroso.github.io/keyhole-docs/assistant-settings.html). Enter the raw key, without a ` Bearer  ` prefix.
2. Restart Claude Code in this project. Review its MCP approval request and approve the intended Render server. Then run the connection check below.

 

This combines [Render's bearer-header authentication](https://render.com/docs/mcp-server) with [Claude Code's documented variable references](https://code.claude.com/docs/en/mcp#environment-variable-expansion-in-mcpjson). The reference belongs in `.mcp.json`; the value belongs in private settings. It is a documentation-derived configuration, not an account-tested recipe.

 

> Keyhole currently writes the project's `.env`, not `.claude/settings.local.json`. Saving `RENDER_API_KEY` through Keyhole does not complete the settings step above. For now, that step is a private human edit. See [the destination distinction](https://jlumbroso.github.io/keyhole-docs/assistant-settings.html).

 

## 3. Check the connection without exposing the key

 

Ask your assistant:

 

```
Use Render's MCP tools to list the workspaces I can access.
Let me choose the workspace, then list only its service names.
Do not deploy, change settings, read logs, or retrieve credentials.
```

 

Confirm the workspace before the service lookup. The assistant should use that workspace explicitly for the request, as described in the [server's workspace reference](https://github.com/render-oss/render-mcp-server#workspaces). An empty list can be a successful result for a new workspace.

 

Success establishes access for that lookup, not permission for every later task. If it fails, share the client name and error category, without the key, configuration file, request headers, or raw debug log.

 

## 4. Remove or replace access

 

For an API key, revoke it in Render's account settings. Deleting its local copy does not revoke it. For a planned replacement, create a new key, update each client, verify, then revoke the old key. If exposed, revoke it first. See [Render's key revocation instructions](https://render.com/docs/api#1-create-an-api-key).

 

For Claude Code OAuth, open `/mcp`, select Render, and use **Clear authentication**; disable the server if you no longer want it available. See [Claude Code's authentication controls](https://code.claude.com/docs/en/mcp#authenticate-with-remote-mcp-servers). This review did not establish Render Dashboard clicks for revoking every issued OAuth token. Contact Render support if an OAuth credential may have been exposed.

 

## For the assistant guiding this step

 

Present this guide through Porthole using [the assistant handoff reference](https://jlumbroso.github.io/keyhole-docs/for-assistants.html). Resolve authentication and destination before submitting a Keyhole request. OAuth needs no API-key request. For a key route, distinguish “saved locally,” “loaded by the client,” and “accepted by Render.”

 

## Source notes

 

The [Render CLI tutorial](https://render.com/tutorials/render-cli-power-user/auth-and-workspaces) describes workspace-specific keys, while the API reference says keys access all of the user's workspaces. This guide uses the broader boundary and makes no promise of a workspace-restricted key. Sources were documentation-reviewed on 24 October 2026; account screens and connection flows still need a hands-on check.

## Chapter notes

**Before granting access**

An MCP server can act on your services. Review its permissions and approve only the task you intend.

**Documentation checked**

Official sources reviewed on 24 October 2026. This guide has not been tested against a signed-in account.

Keyhole 0.1.0 · previews labeled separately. Updated 24 October 2026. [Online chapter](https://jlumbroso.github.io/keyhole-docs/render-mcp.html). [Source reference](https://github.com/jlumbroso/keyhole/blob/main/docs/manual/content/render-mcp.html).

Source access currently requires a repository invitation.


---

# Connect your assistant to Neon.

Authorize the hosted MCP server, or choose an API key when your client needs one. A database connection string is a separate setting.

Use Neon's MCP server when you want your coding assistant to inspect or work on a Neon project. For the API-key route, the credential is a Neon management API key. A database password, a `postgresql://` connection string, and an application's end-user login token are different credentials.

 

> Guide preview. Steps checked against official documentation on 24 October 2026; sign-in and connection checks have not yet been tested here.

 

## 1. Choose how to connect

 

Start with browser authorization, also called OAuth. Your client opens Neon in your browser, where you review and approve access. You do not need to create an API key or pass a value through Keyhole.

 

Use a development or test project containing no personal or production data. Neon recommends its MCP server for development and testing. Depending on configuration, its tools can read database contents and make changes. See [Neon's security guidance](https://neon.com/docs/ai/neon-mcp-server#mcp-security-guidance).

 

## 2. Authorize in your browser

 

1. In your terminal, run `npx neon@latest mcp --oauth --read-only`. It uses Neon's installer and requires Node.js and `npx`.
2. Choose the configuration location and only the client you intend to use. For a personal setup, choose the per-user location. Review the destination displayed before the installer writes.
3. Restart the client if needed. On the first Neon tool request, complete the browser sign-in and review the requested access.
4. Continue to the [connection check](https://jlumbroso.github.io/keyhole-docs/neon-mcp.html#verify-connection).

 

The explicit `--oauth` option avoids minting an API key. Without it, the installer can create a key and write it into client configuration. The `--read-only` option restricts MCP operations; it does not change an API key's scope. See [Neon's installer reference](https://neon.com/docs/cli/mcp).

 

The installer selects a file for the chosen client. This is a client configuration step, not a write to your project's `.env`. See [Assistant settings](https://jlumbroso.github.io/keyhole-docs/assistant-settings.html) before choosing another destination.

 

For Claude Desktop, Neon also offers a browser-authorized connector under **Customize → Connectors**. See [Neon's connector instructions](https://neon.com/docs/ai/neon-mcp-server#claude-connector).

 

## Keep the first connection narrow

 

Neon's server supports `readonly=true` and an optional `projectId` in its URL. A project restriction limits this MCP connection, separately from the credential's scope. A database role can further limit SQL access, but creating and granting that role is a separate task. Consult [Neon's access-control reference](https://neon.com/docs/ai/neon-mcp-server#access-control) before enabling writes or connecting another project.

 

## Alternative: obtain a management API key

 

1. Prepare the receiving configuration first. Sign in to the [Neon Console](https://console.neon.tech).
2. For a personal key, open your profile menu, then **Settings → API keys**, and create a key for this client.
3. If you administer an organization and need only one project, use its **Settings → API keys → Create new → Project-scoped** route and select the project.
4. Copy the new value directly to the receiving field. It is shown once; a lost value must be replaced.

 

Personal keys follow your account's access. Organization-wide keys grant broad access. A project-scoped key still permits changes within its project; it is not read-only. Only organization administrators can create organization and project-scoped keys. See [Neon's key types and creation instructions](https://neon.com/docs/manage/api-keys).

 

## Put the key in the client's configuration

 

Have your assistant prepare a placeholder configuration before you enter the value. Apply the [file-access protections](https://jlumbroso.github.io/keyhole-docs/keeping-secrets-out-of-the-conversation.html). Keep the key out of assistant-run commands, chat, and shared files. Do not ask the assistant to open the receiving file after you enter it.

 

### Claude Code: reference the key by name

 

Prepare this project-root `.mcp.json` configuration. Merge the `neon` entry with existing servers rather than replacing them.

 

```
{
  "mcpServers": {
    "neon": {
      "type": "http",
      "url": "https://mcp.neon.tech/mcp?readonly=true",
      "headers": {"Authorization": "Bearer ${NEON_API_KEY}"}
    }
  }
}
```

 

1. Leave `${NEON_API_KEY}` unchanged. Privately set `env.NEON_API_KEY` in the project's `.claude/settings.local.json`, following [the protected settings walkthrough](https://jlumbroso.github.io/keyhole-docs/assistant-settings.html). Enter the raw key, without a ` Bearer  ` prefix.
2. Restart Claude Code in this project. Review its MCP approval request and approve the intended Neon server. Then check the connection below.

 

This combines [Neon's bearer authentication](https://neon.com/docs/ai/connect-mcp-clients-to-neon#claude-code) with [Claude Code's variable references](https://code.claude.com/docs/en/mcp#environment-variable-expansion-in-mcpjson) and Neon's read-only option. It is documentation-derived, not account-tested. For another client, follow [Neon's client-specific instructions](https://neon.com/docs/ai/connect-mcp-clients-to-neon).

 

> Keyhole currently writes the project's `.env`, not `.claude/settings.local.json`. Saving `NEON_API_KEY` through Keyhole does not complete the settings step above. For now, that step is a private human edit. See [the destination distinction](https://jlumbroso.github.io/keyhole-docs/assistant-settings.html).

 

This configuration stores the key locally and connects to Neon's hosted server. Use the `/mcp` endpoint. The old `/sse` endpoint and `@neondatabase/mcp-server-neon` local package are deprecated; see [Neon's migration notes](https://neon.com/docs/ai/neon-mcp-server#troubleshooting).

 

## 3. Check the connection without reading a secret

 

For a connection without a single-project restriction, ask:

 

```
Use Neon's MCP tools to list only my project names and IDs.
Do not run SQL, retrieve a connection string, or change anything.
```

 

If you restricted the connection to one project, ask for that project's name and ID instead. Do not widen access merely to make a cross-project listing work. These inspection tasks are supported by [Neon's server](https://github.com/neondatabase/mcp-server-neon).

 

Success means Neon accepted that lookup. It does not prove database-query access or permission to make changes. If it fails, share the client name and error category without the key, settings file, connection string, request headers, or raw debug log.

 

## 4. Remove or replace access

 

For an API key, return to its API keys page and use **Revoke**. Revocation is immediate and permanent. For planned rotation, create a replacement, update each client, verify, then revoke the old key. If exposed, revoke it first. See [Neon's revocation and rotation instructions](https://neon.com/docs/manage/api-keys#revoke-api-keys).

 

For Claude Code OAuth, open `/mcp`, select Neon, and use **Clear authentication**; disable the server if it is no longer needed. See [Claude Code's authentication controls](https://code.claude.com/docs/en/mcp#authenticate-with-remote-mcp-servers). Deleting local settings is not evidence that every issued OAuth token was revoked. This review did not establish Neon Console clicks for provider-side OAuth revocation; contact Neon support if an OAuth credential may have been exposed.

 

## For the assistant guiding this step

 

Use [the assistant handoff reference](https://jlumbroso.github.io/keyhole-docs/for-assistants.html) to present this guide through Porthole. OAuth needs no Keyhole request. For an API key, establish the receiving client, exact setting name, value format, destination, and loading step first. Report the observed result without asking to see the value.

 

## Source notes

 

The linked Neon and client documentation was reviewed on 24 October 2026. Commands, configuration, and account flows are documentation-checked, not verified in a running MCP client or Neon account.

## Chapter notes

**Two credentials**

A Neon API key manages Neon resources. DATABASE_URL connects an application to a database.

**Documentation checked**

Official sources reviewed on 24 October 2026. This guide has not been tested against a signed-in account.

Keyhole 0.1.0 · previews labeled separately. Updated 24 October 2026. [Online chapter](https://jlumbroso.github.io/keyhole-docs/neon-mcp.html). [Source reference](https://github.com/jlumbroso/keyhole/blob/main/docs/manual/content/neon-mcp.html).

Source access currently requires a repository invitation.


---

# Put it where the server reads it.

Claude Code, Codex, Gemini CLI and Grok Build have different environment and MCP configuration paths.

> **Current writer: project `.env` only.** Keyhole does not yet write the assistant configuration files below. Support for additional destinations is planned. These examples explain the receiving applications; they are not new Keyhole commands or request parameters.

 

First identify what needs the credential: your application, a command your assistant runs, or one MCP server. Those can be separate processes with separate settings. Saving a value in the project's `.env` does not automatically configure an assistant or its MCP servers.

 

For service-specific choices, see [Render MCP](https://jlumbroso.github.io/keyhole-docs/render-mcp.html) and [Neon MCP](https://jlumbroso.github.io/keyhole-docs/neon-mcp.html). If the service offers browser authorization, that may avoid a copied API key. The [assistant reference](https://jlumbroso.github.io/keyhole-docs/for-assistants.html) explains how to display the guide and initiate the supported handoff.

 

## Claude Code

 

Claude Code supports this shape in the project's `.claude/settings.local.json`. Both `TOKEN_NAME` and `not-a-secret` are placeholders; the latter is a harmless practice value:

 

```
{
  "env": {
    "TOKEN_NAME": "not-a-secret"
  }
}
```

 

This personal, project-local file supplies variables to Claude Code's environment. Its subprocesses, including Bash, hooks and stdio MCP servers, normally inherit them, subject to Claude's environment filtering. That is broader access than giving a credential to one server. Values under this top-level `env` are literal: `$HOME` is not shell-expanded. [Claude Code's env reference](https://code.claude.com/docs/en/settings-reference#env) describes precedence and trust exceptions.

 

Use strict JSON and preserve existing settings. When another program creates this file, verify that Git ignores it; the filename alone is not protection. Saved additions and changes can update Claude's environment, but removing a variable does not unset it in an already-running session. Restart Claude and reconnect the affected server after changing credentials. [Environment and reload behavior](https://code.claude.com/docs/en/env-vars#set-variables-in-settings-files).

 

A server's own `mcpServers.<server>.env` is different from the settings file's top-level `env`. Project server definitions live in `.mcp.json`; personal and MCP-local definitions live in `~/.claude.json`. Shared definitions can reference a privately supplied variable:

 

```
"env": {
  "TOKEN_NAME": "${TOKEN_NAME}"
}
```

 

For an HTTP MCP server, use its `headers` configuration or supported browser authorization. Once the settings `env` value has applied, a header such as `Authorization: Bearer ${TOKEN_NAME}` can reference it. An environment variable does not become a header automatically. Claude blocks certain provider-credential names in remote URLs and headers; use the intended service's variable name. [MCP environment references and credential restrictions](https://code.claude.com/docs/en/mcp#environment-variable-expansion-in-mcpjson).

 

## Codex

 

For commands Codex runs, its TOML configuration supports:

 

```
[shell_environment_policy]
set = { TOKEN_NAME = "not-a-secret" }
```

 

This can live in personal `~/.codex/config.toml` or a trusted project's `.codex/config.toml`. Project settings may be shared, so never put a real credential in a tracked copy. The environment policy can filter names; saving a setting does not establish that a running command received it. [Codex environment policy](https://learn.chatgpt.com/docs/config-file/config-advanced#environment-policy).

 

MCP settings are separate. Under `[mcp_servers.<server>]`, `env` supplies explicit stdio-process values; `env_vars` names variables to forward from Codex's environment. The shell policy's `set` table is not a substitute for either. For HTTP servers, `http_headers` holds literal headers, `env_http_headers` maps header names to environment-variable names, and `bearer_token_env_var` names the variable supplying the bearer token. A reference stores a name, not the credential itself. [Codex MCP configuration](https://learn.chatgpt.com/docs/extend/mcp?surface=cli).

 

## Gemini CLI

 

Gemini CLI does not provide Claude's top-level `env` setting. Its general environment comes from the launching process and its `.env` loader, which also recognizes `.gemini/.env`. Loading depends on folder trust, exclusions and existing process values; it is not a promise that every secret reaches every tool. [Gemini environment configuration](https://geminicli.com/docs/reference/configuration/#environment-variables-and-env-files).

 

For a particular stdio MCP server, use `mcpServers.<server>.env` in personal `~/.gemini/settings.json` or the project's `.gemini/settings.json`. This fragment belongs inside an existing server entry; `not-a-secret` is a harmless practice value:

 

```
"env": {
  "TOKEN_NAME": "not-a-secret"
}
```

 

Gemini CLI 0.65.0 sanitizes the inherited MCP environment first, then adds the server entry's explicit values. A real credential belongs only in a private configuration: before using a project file, verify that it is untracked and ignored. Preserve the server's command and other settings. [Pinned stdio environment construction](https://github.com/google-gemini/gemini-cli/blob/v0.65.0/packages/core/src/tools/mcp-client.ts#L2342-L2392).

 

A name reference such as `${TOKEN_NAME}` needs more care. At this version, settings references resolve before `.env` loads. If the value exists only in `.env`, the remaining reference is expanded at MCP startup against the redacted environment; names containing `TOKEN`, `KEY` or `SECRET` are removed, so that reference becomes empty. Listing the name in `security.environmentVariableRedaction.allowed` permits it through the name filter. Values already present before Gemini starts can instead resolve during the earlier settings pass. [Settings expansion](https://github.com/google-gemini/gemini-cli/blob/v0.65.0/packages/cli/src/config/settings.ts#L818-L824); [later environment loading](https://github.com/google-gemini/gemini-cli/blob/v0.65.0/packages/cli/src/config/settings.ts#L973-L984).

 

The allowed-name list affects the inherited environment, beyond one server. It also does not bypass filters for recognizable secret values, which run first. Treat a `.env`-plus-reference configuration as a path to verify, not a guaranteed delivery mechanism. [Pinned filter order](https://github.com/google-gemini/gemini-cli/blob/v0.65.0/packages/core/src/services/environmentSanitization.ts#L148-L187).

 

HTTP and SSE server entries use `headers` instead of stdio `env`; their references also expand against the sanitized environment. Restart Gemini after changing settings or `.env`, then check the intended server. [Pinned header expansion](https://github.com/google-gemini/gemini-cli/blob/v0.65.0/packages/core/src/tools/mcp-client.ts#L988-L1013); [Gemini MCP configuration](https://geminicli.com/docs/tools/mcp-server/).

 

## Grok Build

 

This section concerns the official [Grok Build](https://github.com/xai-org/grok-build) CLI, `grok`. Other tools with that executable name may use different files.

 

Grok Build's published source supports `[shell_environment_policy]` with a `set` map for commands it runs. Put that general policy in personal `~/.grok/config.toml` (or the configured Grok home), not the project's file. Project `.grok/config.toml` accepts only selected sections, including MCP servers. [Grok settings scopes](https://docs.x.ai/build/settings); [published shell-policy source](https://github.com/xai-org/grok-build/blob/2bdd1d6a6369de0e8c68132ea4539e9abd9e14a8/crates/codegen/xai-grok-tools/src/util/shell_env_policy.rs#L37-L53).

 

Its Claude compatibility path can also read `env` from `.claude/settings.json` and `.claude/settings.local.json`, with project values gated by trust. This supplies Bash and terminal commands, not MCP servers or MCP reference expansion. The compatibility reader stops once Grok's Claude import marker is set. `.envrc` values follow the same command-only path. [Pinned compatibility reader](https://github.com/xai-org/grok-build/blob/2bdd1d6a6369de0e8c68132ea4539e9abd9e14a8/crates/codegen/xai-grok-permission-rules/src/claude_settings.rs#L405-L434); [command environment handoff](https://github.com/xai-org/grok-build/blob/2bdd1d6a6369de0e8c68132ea4539e9abd9e14a8/crates/codegen/xai-grok-shell/src/agent/mvp_agent/agent_ops.rs#L4348-L4368).

 

For stdio MCP, use `[mcp_servers.<server>]` with `env`. For remote MCP, use `headers` or `bearer_token_env_var`. MCP strings support `${VAR}` references. These per-server settings can be project-local or personal; real credentials still need a private destination. [Grok MCP settings reference](https://docs.x.ai/build/settings/reference).

 

The pinned source also supports `bearer_token_file` for HTTP and SSE servers: an absolute or `~/` path to a private token file. Grok reads it on each request, so replacing that file can rotate the token without a restart. This is a separate file format and is not Keyhole's current `.env` writer. [Pinned token-file instructions](https://github.com/xai-org/grok-build/blob/2bdd1d6a6369de0e8c68132ea4539e9abd9e14a8/crates/codegen/xai-grok-pager/docs/user-guide/07-mcp-servers.md#L344-L354).

 

## Check the destination without disclosing the value

 

Keep real values out of commands pasted into chat, terminal arguments and shared configuration examples. A person can edit the appropriate private file locally while Keyhole's additional writers are pending. Preserve unrelated fields and verify Git protection for project files; a home-directory configuration has a different scope and may affect every project.

 

Restart or reconnect the receiving process as its documentation requires. Check a permitted operation or a presence-only result. A saved file, a loaded setting and a successful service operation are separate observations. Follow [file protections](https://jlumbroso.github.io/keyhole-docs/keeping-secrets-out-of-the-conversation.html) before introducing a real credential: making a value available to an assistant's subprocesses does not prevent those subprocesses from printing it.

 

## Reference status

 

Documentation and source checked on 24 October 2026. Source checks included [Codex 0.164.0's MCP environment](https://github.com/openai/codex/blob/rust-v0.164.0/codex-rs/rmcp-client/src/utils.rs#L16-L58) and [Gemini CLI 0.65.0's settings schema](https://github.com/google-gemini/gemini-cli/blob/v0.65.0/schemas/settings.schema.json). Grok's source reference is a periodically published snapshot, not verification of every installed binary. These configuration examples have not been exercised here in running assistants or signed-in service accounts.

## Chapter notes

**Writer status**

Keyhole currently writes .env. The harness-specific destinations here are configuration references, not supported Keyhole writers yet.

**No universal loader**

A shell setting and an MCP setting can affect different processes. Saved does not mean loaded.

Keyhole 0.1.0 · previews labeled separately. Updated 24 October 2026. [Online chapter](https://jlumbroso.github.io/keyhole-docs/assistant-settings.html). [Source reference](https://github.com/jlumbroso/keyhole/blob/main/docs/manual/content/assistant-settings.html).

Source access currently requires a repository invitation.


---

# Keep secrets out of the conversation.

Choose the application running your assistant, then check its protections before adding a real key.

An API key lets a program use an account or service. Your assistant can help you obtain one and explain where it belongs without seeing the value.

 

Keyhole's **Set a Secret in .env…** command in VS Code's Command Palette writes that value to your project folder's `.env` through a masked input box. After it is saved, the assistant may still be able to read the file with its other tools. This chapter helps you reduce those accidental reads.

 

**Choose the application running your assistant**, sometimes called its *harness*: [Claude Code](https://jlumbroso.github.io/keyhole-docs/keeping-secrets-out-of-the-conversation.html#claude-code), [Codex](https://jlumbroso.github.io/keyhole-docs/keeping-secrets-out-of-the-conversation.html#codex), [Gemini CLI](https://jlumbroso.github.io/keyhole-docs/keeping-secrets-out-of-the-conversation.html#gemini-cli), or [Grok Build](https://jlumbroso.github.io/keyhole-docs/keeping-secrets-out-of-the-conversation.html#grok-build). The model's name alone does not tell you which settings apply.

 

**Checked: 20 October 2026.** The instructions below were checked against vendor documentation and, where noted, source code. None of these recipes has yet been exercised here in a running assistant. Start with the [harmless check](https://jlumbroso.github.io/keyhole-docs/keeping-secrets-out-of-the-conversation.html#check-with-a-harmless-value), using your installed version.

 

## Before adding a real key

 

Your project's `.env` is a text file containing named settings, often including secrets. A setting's name, such as `DATABASE_URL`, can be discussed in chat; its value should stay out.

 

Three different controls matter:

 



| Control | What it does |
| --- | --- |
| Git ignore rules | Help keep a file out of future source-code commits; they do not prevent a program from reading it. |
| File permissions in your assistant | Restrict particular tools or sandboxed programs from opening the file. Coverage depends on the application. |
| Command environment settings | Control values handed directly to programs the assistant runs, even without opening a file. |

 

Git also keeps tracking a file that was already added, despite a later ignore rule. When VS Code recognizes the destination's Git repository, Keyhole checks both whether the file is ignored and whether it is already tracked. Neither check removes a value from existing history. [Git's ignore documentation](https://git-scm.com/docs/gitignore)

 

If VS Code returns no repository, Keyhole also looks for a `.git` marker in the project folder and its parent folders. Finding one stops the write, because the repository's tracking and ignore rules could not be checked. If no marker is found, Keyhole treats the folder as having no repository and permits the write without those checks.

 

For the examples below:

 

1. Open the project's top-level folder in VS Code. Paths beginning with a dot, such as `.claude`, are ordinary folder names.
2. Make the changes in the editor. If a settings file already exists, keep its other settings and merge the additions into the existing sections. JSON files must not contain duplicate keys; TOML files must not repeat a table.
3. Keep the real key out of chat, commands, screenshots, and copied settings. The assistant can explain a change using an empty example.

 

The patterns below include `.env.local`, `.env.production`, and `.env.example`. If the assistant needs a sample, give it a separate file such as `environment-example.txt`, containing names and empty or invented values. Avoid creating an exception for a file that could later acquire real values.

 

## Claude Code

 

Start Claude Code in the project's top-level folder. Open or create `.claude/settings.json` inside that folder. For a new file, use this JSON. For an existing file, add these entries to its `permissions.deny` list:

 

```
{
  "permissions": {
    "deny": [
      "Read(./.env)",
      "Read(./.env.*)",
      "Read(/**/.env)",
      "Read(/**/.env.*)",
      "Read(/.claude/settings.local.json)"
    ]
  }
}
```

 

The first pair works below the current directory. The second anchors coverage to the session's starting folder, including subfolders, even after a directory change. In this project settings file, `/` means that starting folder, not your disk's root. Start at the project root; files above that boundary need their own rules.

 

The last rule protects Claude's local settings file, which can itself contain secrets. Do not copy it into chat. Keyhole 0.1.0 writes to `.env` only.

 

Open `/permissions` and check that the deny rules appear with the expected settings source. Claude Code reloads permission edits; if the rules do not appear, check for invalid JSON and use `claude doctor` to diagnose rejected settings. [Claude Code settings](https://code.claude.com/docs/en/settings#when-edits-take-effect)

 

These rules cover Claude's built-in file tools and recognized shell operations that name a file. They do not cover every program that can open it: a Python script or recursive shell search can take another route. `Bash(cat .env)` is not an adequate substitute; blocking one command spelling does not protect the file. Deny rules take precedence over allow rules. [Claude Code file permissions](https://code.claude.com/docs/en/permissions#read-and-edit)

 

**Checkpoint:** the practice-file Read calls should be denied. If you also need to restrict scripts, follow Anthropic's [sandbox filesystem guidance](https://code.claude.com/docs/en/sandboxing) and configure denied paths. Enabling a sandbox alone does not hide `.env`; its default read access is broad.

 

A variable placed under `env` in `.claude/settings.local.json` is deliberately made available to commands. Blocking tool reads of that file does not remove those inherited values. Put a variable there only when Claude's commands need the credential. [Claude Code environment settings](https://code.claude.com/docs/en/env-vars#set-variables-in-settings-files)

 

## Codex

 

These instructions concern local Codex command execution. Open your personal `~/.codex/config.toml` (`~` means your home folder). In the Codex IDE extension, the settings menu offers **Open config.toml**. Project `.codex/config.toml` overrides are loaded only for trusted projects; command line overrides can take precedence. [OpenAI configuration guide](https://learn.chatgpt.com/docs/config-file/config-basic)

 

Codex's current **beta permission profiles** can deny filesystem access. The following example extends the built-in workspace profile:

 

```
default_permissions = "keyhole"

[permissions.keyhole]
extends = ":workspace"

[permissions.keyhole.filesystem]
glob_scan_max_depth = 6

[permissions.keyhole.filesystem.":workspace_roots"]
".env" = "deny"
".env.*" = "deny"
"**/.env" = "deny"
"**/.env.*" = "deny"
```

 

Place `default_permissions` at the top level, before table headings. If you already use a custom permission profile, add the file rules to that profile instead. Older `sandbox_mode` settings or a launch-time `--sandbox` can override profile selection: reconcile them before relying on these rules. `deny` prevents writes as well as reads. The scan depth is bounded; use exact paths for files deeper than the chosen scan.

 

This governs sandboxed commands, not every connector or external tool. On Linux, WSL, and supported native Windows sandboxes, glob matches are collected when each sandboxed process starts. An already-running shell, script, watcher, or development server may miss matching files created afterward. Restart those processes after adding secret files. [OpenAI permission profiles](https://learn.chatgpt.com/docs/permissions)

 

On macOS, Seatbelt checks the denied paths when they are accessed, so matching files created after the sandbox starts are covered too. This follows the [Codex 0.163.0 implementation](https://github.com/openai/codex/blob/rust-v0.163.0/codex-rs/sandboxing/src/seatbelt.rs#L624-L662); we have not run that check here.

 

For variables the assistant's commands do not need, merge this separate setting into the same configuration:

 

```
[shell_environment_policy]
ignore_default_excludes = false
```

 

It filters inherited names containing `KEY`, `SECRET`, or `TOKEN`. It does not catch every secret: `DATABASE_URL`, for instance, needs an explicit filter. Values supplied through `set` can restore excluded variables. Keep existing environment filters and review them together; a program that needs a removed variable will no longer receive it. [OpenAI shell environment policy](https://learn.chatgpt.com/docs/config-file/config-advanced#shell-environment-policy)

 

Start a fresh session. Use `/permissions` to inspect the selected profile, then run the harmless check. An approval to execute outside the sandbox can remove the protection for that command; do not approve a request to work around a secret-file denial. Ordinary **read-only** mode still allows reads. [OpenAI sandbox and approval guide](https://learn.chatgpt.com/docs/sandboxing)

 

## Gemini CLI

 

Open or create `.geminiignore` in the project's top-level folder. Keep any existing lines and append:

 

```
**/.env
**/.env.*
```

 

Restart Gemini CLI after editing this file. It excludes matching paths from participating file tools and `@` file inclusion. It does not prevent an allowed shell program from opening them. [Gemini ignore files](https://geminicli.com/docs/cli/gemini-ignore/)

 

Gemini also loads environment files itself. In a project you trust, merge these settings into `.gemini/settings.json`:

 

```
{
  "context": {
    "fileFiltering": {
      "respectGitIgnore": true,
      "respectGeminiIgnore": true
    }
  },
  "advanced": {
    "ignoreLocalEnv": true
  },
  "security": {
    "environmentVariableRedaction": {
      "enabled": true
    }
  }
}
```

 

The first section enables ignore-file handling. `ignoreLocalEnv` skips generic `.env` files except the one in your home folder; redaction makes a best-effort attempt to filter sensitive environment variables passed to tools. It does not identify every credential. Existing `allowed` and `blocked` lists affect the result. Restart afterward. Project settings require trust and can be overridden by system settings. These changes can leave development commands without credentials they previously received. [Gemini configuration](https://geminicli.com/docs/reference/configuration/)

 

Do not assume redaction is already enabled: the current settings schema defaults it to `false`, despite prose elsewhere calling it automatic. The nested JSON above follows that [settings schema](https://github.com/google-gemini/gemini-cli/blob/main/packages/cli/src/config/settingsSchema.ts).

 

`ignoreLocalEnv` still permits `.gemini/.env` in trusted folders, including `~/.gemini/.env`, and does not remove variables inherited from the launching shell. Gemini CLI 0.64.0 searches upward as far as the filesystem root, stopping at the first eligible file; a `.git` folder does not stop the search. With `ignoreLocalEnv` off, a stray `.env` in a parent folder can therefore be loaded. File exclusions and environment loading are separate. These details come from the [0.64.0 environment loader](https://github.com/google-gemini/gemini-cli/blob/v0.64.0/packages/cli/src/config/settings.ts#L561-L598), not a walkthrough of the installed release.

 

**Checkpoint:** `read_file` should report that the practice file is ignored. Current [read-file source](https://github.com/google-gemini/gemini-cli/blob/main/packages/core/src/tools/read-file.ts) checks both the supplied and resolved paths. A shell or script is a separate route: this recipe does not claim to block it.

 

## Grok Build

 

This section is for xAI's **official Grok Build on macOS or Linux**. Other programs named `grok` may use different settings.

 

Quit Grok. Open or create `.grok/sandbox.toml` in your project and add this profile, preserving other profiles:

 

```
[profiles.protect-env]
extends = "workspace"
deny = ["**/.env", "**/.env.*"]
```

 

From the project's top-level folder, start a **new process and session**:

 

```
grok --sandbox protect-env
```

 

The profile adds filesystem denials to the ordinary workspace sandbox. Plain `workspace` and `read-only` profiles do not by themselves hide `.env`. A same-named profile in your user settings can override the project profile; check startup warnings. An explicit custom profile should refuse startup if it cannot be enforced. [Grok sandbox documentation](https://docs.x.ai/build/features/sandbox), [sandbox source guide](https://github.com/xai-org/grok-build/blob/2bdd1d6a6369de0e8c68132ea4539e9abd9e14a8/crates/codegen/xai-grok-pager/docs/user-guide/18-sandbox.md)

 

**On Linux, create the file before launching Grok.** Denial patterns are expanded at process launch. Before Keyhole creates another environment file, quit Grok; launch a new Grok process after the file exists. The source guide describes runtime matching on macOS and requires bubblewrap for Linux read denial. Resuming an old session preserves its sandbox selection, so do not use resume to switch an earlier session into this profile. [Grok's sandbox source guide](https://github.com/xai-org/grok-build/blob/2bdd1d6a6369de0e8c68132ea4539e9abd9e14a8/crates/codegen/xai-grok-pager/docs/user-guide/18-sandbox.md)

 

For command-environment filtering, the configuration belongs in your **personal** `~/.grok/config.toml`, not the project's config file:

 

```
[shell_environment_policy]
inherit = "core"
ignore_default_excludes = false
```

 

Quit and relaunch Grok after changing the policy. This reduces inherited variables and filters names containing `KEY`, `SECRET`, or `TOKEN`. It can remove credentials commands need; it does not detect every secret. Explicit `set` values are applied after exclusions. [Grok configuration scope](https://docs.x.ai/build/settings), [environment-policy source](https://github.com/xai-org/grok-build/blob/2bdd1d6a6369de0e8c68132ea4539e9abd9e14a8/crates/codegen/xai-grok-tools/src/util/shell_env_policy.rs)

 

Grok can also load `.envrc`, a file containing shell instructions. `session.load_envrc` is enabled by default in the documentation and published source; that source permits loading only in a folder Grok trusts. [Published trust check](https://github.com/xai-org/grok-build/blob/2bdd1d6a6369de0e8c68132ea4539e9abd9e14a8/crates/codegen/xai-grok-workspace/src/envrc.rs#L45-L60)

 

If you do not use that feature, set `load_envrc = false` under `[session]` in the same personal config. Restart afterward. Shell startup files remain another way variables can enter a command's environment. [Grok settings reference](https://docs.x.ai/build/settings/reference), [sandbox source guide](https://github.com/xai-org/grok-build/blob/2bdd1d6a6369de0e8c68132ea4539e9abd9e14a8/crates/codegen/xai-grok-pager/docs/user-guide/18-sandbox.md)

 

**Checkpoint:** both file-tool and shell/script reads should be denied. This is a documentation-based recipe, not a tested native-Windows procedure. The public source snapshot predates Grok Build 1.0.62, whose behavior has not been exercised here, making the harmless check particularly important.

 

## Check with a harmless value

 

Do this in a **new, empty practice folder**, before working with real keys. Do not overwrite your actual `.env` or test by asking the assistant to reveal a real credential.

 

1. Apply the instructions for your application in the practice folder, including any personal settings they require.
2. In VS Code, create a file named `.env` containing only `KEYHOLE_PRACTICE=not-a-secret`. Create `.env.local` with the same harmless line. If your project uses nested folders, repeat there too.
3. Start a fresh assistant session in that folder, using the intended permission or sandbox profile. For Grok, quit and restart the process; starting another conversation within it or resuming an earlier session is insufficient.
4. For Claude, ask it to use **Read** on the practice files; for Gemini, use **read_file**; for Grok, use its file-reading tool. Ask it to stop on a denial, without trying another route. Inspect the actual denied or ignored-path tool result. A conversational promise does not test the setting. For Codex, proceed to the command check below.
5. For Codex or Grok's filesystem sandbox, also test a shell read and a small script that opens the practice file. Both should be denied under the selected sandbox. Claude's file-tool rules and Gemini's ignore rules alone do **not** promise this result.
6. Confirm it can still read an ordinary file in the practice folder. If it cannot read anything, that does not establish that your secret-file pattern is correct.

 

If an expected denial does not occur, keep using the harmless folder while you check the version, active settings, paths, and tool involved. A passing check establishes those tested routes in that setup; it is not proof about other tools, plugins, or future versions.

 

Once the practice check works, apply the same project settings in your actual project and confirm the active configuration there. Personal settings may carry across; files created in the practice folder do not. **Do not repeat the read test on real credentials.**

 

## Continue without reading the value back

 

The useful question after saving is “Is the setting available where the program needs it?” A file can be saved while a running program still needs to reload it. Ask for a result such as “setting present” or “connection succeeded,” without printing the value.

 

Do not use a dump of `.env`, `env`, `printenv`, or a configuration object as the diagnostic. A file-read restriction cannot hide a value that was already handed to a program through its environment, copied elsewhere, or included in an error message.

 

Tell the assistant the variable's **name**, the destination, and a secret-free description of the failure. If a real value has already entered the conversation, use its provider's instructions to revoke or rotate it; adding a deny rule afterward does not undo that exposure.

## Chapter notes

**Checked 20 October**

These recipes were checked against documentation and source. They have not been exercised here in running assistants.

**Test harmlessly**

A passing check covers the tested routes and setup. It is not proof about every tool or future version.

Keyhole 0.1.0 · previews labeled separately. Updated 24 October 2026. [Online chapter](https://jlumbroso.github.io/keyhole-docs/keeping-secrets-out-of-the-conversation.html). [Source reference](https://github.com/jlumbroso/keyhole/blob/main/docs/manual/keeping-secrets-out-of-the-conversation.md).

Source access currently requires a repository invitation.


---

# When a step stops you.

Find the message or symptom, then check the next step without exposing the value.

Use the message you saw to find the next step. When asking for help, share the variable's name, destination, and message. Do not attach the value, a screenshot of a revealed field, or the contents of `.env`.

 

## The command cannot start

 

### The command is missing

 

Check that Keyhole is installed and enabled in the VS Code window you are using. Search the Command Palette for `Keyhole: Set a Secret in .env…`. See the [overview](https://jlumbroso.github.io/keyhole-docs/index.html) for current release status and requirements.

 

### “Open a folder first”

 

Open the local project folder in VS Code, then run the command again. An individual open file is not sufficient.

 

## Keyhole stopped at the Git check

 

### “It could not ask git”

 

Check that Git is installed and VS Code's built-in Git extension is enabled. Keyhole has not written the value. Resolve the reported problem, then run the command again.

 

### “Git tracks it”

 

Keyhole refuses to put a new secret into an already tracked `.env`. Git keeps tracking a file even after you add an ignore rule.

 

From the project folder containing that `.env`, this command stops tracking the file without deleting the local copy:

 

```
git rm --cached .env
```

 

Then add `.env` to `.gitignore` and run Keyhole again. If an earlier commit contains a real secret, these changes do not remove that exposure. Revoke or rotate the credential through its service.

 

### “Git still does not ignore it”

 

Keyhole may have added an ignore rule, but its second check failed. It has not written the secret to `.env`. Review conflicting ignore rules, particularly rules beginning with `!`, and retry once Git reports the file ignored.

 

### “A git repository that VS Code's Git extension did not open”

 

VS Code did not return a usable repository, but Keyhole found a `.git` marker in the project folder or a folder above it. Keyhole refuses to write because it cannot check the tracking and ignore rules.

 

Run `git status` in a terminal in that folder to see why, or inspect VS Code's Git output. Git may report an ownership warning. Resolve the cause before retrying; do not dismiss a warning merely to make the message disappear.

 

### The folder does not use Git

 

If VS Code returns no repository and Keyhole finds no `.git` marker in the folder or any parent folder, it treats the folder as having no repository and permits the write. The tracking and ignore checks do not run on that path.

 

If you later enable Git or copy the project elsewhere, make sure `.env` is ignored before adding project files to a commit.

 

## The name or value was not accepted

 

### The name is not accepted

 

Use the exact name expected by the project, in capital letters, digits, and underscores, starting with a letter. Keyhole also refuses some names that change how programs execute, such as `PATH` and `NODE_OPTIONS`. Do not invent a different name merely to bypass a refusal; the program may not recognize it.

 

### “The value has more than one line”

 

This version writes single-line values. A private-key file or certificate may require a different setup. Follow that service's instructions; do not remove line breaks merely to make Keyhole accept the value.

 

### “It cannot be written safely in one line”

 

Keyhole rejects some combinations of quotation marks and characters a program could expand when loading the file. Keep the credential unchanged and use the service or framework's documented configuration method.

 

## Something is wrong after saving

 

### “Wrote … but reading it back did not match”

 

Keyhole did write the file but could not verify the result. Do not treat this as success or repeatedly submit more values. Inspect the destination locally; ask for help using the message and variable name without sharing the file.

 

### Keyhole saved it, but my program still fails

 

1. Confirm the variable name and the project folder you selected.
2. Confirm that the program reads that folder's `.env`. A file write alone does not configure the program to load it.
3. Restart the relevant program if it loads settings at startup. A local file does not update a hosted service's environment.
4. Check that the credential has the right type and permissions for the operation, and is still valid at its provider.

 

Ask for a result such as “setting present” or “connection succeeded,” without printing the value. Avoid an environment dump or a copy of the configuration file.

 

### An older value is still in the file

 

Keyhole retains earlier assignments. It appends the newer one and identifies the superseded line in its comment. After the new value works, you may remove the old line locally. Removing a line does not revoke the credential at its service.

 

### What is a keyprint?

 

A keyprint is a recognition marker used in messages and logs. It contains a recognized public prefix, when there is one, and a short hash. Values at least 32 characters long also show their last four characters. It is not the complete credential.

 

## A real value entered the conversation

 

Use the provider's instructions to revoke or rotate it. Adding a file-read rule afterward does not undo the exposure. Configure your assistant's [file protections](https://jlumbroso.github.io/keyhole-docs/keeping-secrets-out-of-the-conversation.html) before entering the replacement through Keyhole.

## Chapter notes

**When asking for help**

Share the variable name and the message. Leave out the value, .env contents, and revealed screenshots.

**Local settings**

Writing a local .env does not configure a hosted service.

Keyhole 0.1.0 · previews labeled separately. Updated 24 October 2026. [Online chapter](https://jlumbroso.github.io/keyhole-docs/troubleshooting.html). [Source reference](https://github.com/jlumbroso/keyhole/blob/main/docs/manual/content/troubleshooting.html).

Source access currently requires a repository invitation.
