How do you find shadow AI coding agents like Claude Code, Cursor, and Codex on developer machines?

Shadow AI in engineering teams is usually an approved coding agent under a personal account or with unreviewed MCP servers.

18 min read
In short: Shadow AI on developer machines is usually a coding agent such as Claude Code, Cursor, Codex, or Copilot running on a personal plan or with MCP servers nobody reviewed. EDR, TLS-inspecting gateways, browser DLP, and the identity provider each miss part of it. Config files in the home directory name accounts and MCP servers, and three evidence sources – endpoint connections, hook events, and the Claude Compliance API – each prove something different. Respond with fast approval, tenant controls, managed settings, hooks with MCP and model allowlists, and endpoint provider blocking for unapproved services.

What does shadow AI look like on a developer's laptop?

Shadow AI in an engineering team is usually a coding agent running under an account, a configuration, or a tool that the organization didn't approve and can't see. The tool is often one you already pay for, such as Claude Code, Cursor, OpenAI Codex, or GitHub Copilot, signed in to a personal plan or loaded with MCP servers that nobody reviewed. Finding it takes several kinds of evidence, because each one proves something different. For the methods that cover the rest of the organization, see how to detect shadow AI.

Consider a backend engineer whose team bought 20 Claude Enterprise seats for 23 people. She's one of the three without a seat, and she has a migration due Friday. She already pays for a personal Max plan, so she installs Claude Code on her work laptop and signs in with it. Then she connects the agent to the staging database with claude mcp add, which writes a stdio server entry into ~/.claude.json under the path of the repository she's working in.

From the security team's side, very little of this is visible. The session doesn't authenticate to your Claude organization, so it doesn't fetch your server-managed settings or appear in the Claude Compliance API. If hooks reach her laptop only through server-managed settings, they don't load either. The database credentials sit in a file in her home directory, and the MCP server talks to Postgres from a local process. The only thing that leaves the laptop and identifies the tool is TLS traffic from the claude binary to api.anthropic.com, which is exactly what an approved session sends too.

Why do the usual controls miss CLI coding agents?

Each control in a typical stack was built for a different kind of AI use, and a coding agent in a terminal falls between them. The following sections cover the four controls that teams usually expect to catch it.

Endpoint detection and response

Endpoint detection and response (EDR) sees processes and their network connections. The native Claude Code installer ships a single compiled executable built on the Bun runtime, and Claude Code's npm package also points its claude command at a compiled executable. The @openai/codex npm package starts through a codex.js script that runs under node, and the GitHub Copilot CLI package starts through npm-loader.js. Cursor's CLI installs as a command named agent. A process tree with node, a generic agent, and a single binary of more than 300 MB is normal for a developer, and EDR detection content is written to find malicious behavior, not policy violations.

Some EDR products add a separate inventory for this. Microsoft Defender for Endpoint's local AI agent discovery lists agents and their configured MCP servers on onboarded Windows and macOS devices (reviewed on November 3, 2026). Microsoft's own documentation notes that an inventory entry isn't proof of active use. An inventory also doesn't say which account the agent signed in with, which is the difference between the approved tool and the shadow one in the scenario.

Security service edge and CASB with TLS inspection

A security service edge (SSE) or cloud access security broker (CASB) that decrypts TLS can read requests, and for Claude and ChatGPT it can insert the tenant restriction headers that refuse personal accounts. That works only when the client accepts the inspecting proxy's certificate, and developer tools vary.

Claude Code trusts the operating system's certificate store by default on the native installer, and on npm installs with Node 22.15 or later. On older Node versions, it trusts only its bundled Mozilla set plus whatever NODE_EXTRA_CA_CERTS points to. Cursor's network configuration guide recommends disabling SSL inspection for .cursor.sh, cursor.com, and related domains, and Cursor falls back to HTTP/1.1 when Zscaler and similar proxies break its HTTP/2 streaming.

When TLS inspection breaks a tool, developers fix it themselves. Some set NODE_TLS_REJECT_UNAUTHORIZED=0. Some tether to a phone. Some get a bypass rule for the vendor's domains. A network appliance in the office also sees nothing from a laptop on a hotel network or a personal hotspot, unless its client runs on the device.

Browser extensions and browser DLP

Browser security extensions inspect what users type into AI sites. A coding agent doesn't run in a browser tab, so these tools see nothing of the session. The one moment that touches the browser is sign-in: Claude Code's login opens a claude.com page that redirects to claude.ai. That visit shows that someone signed in, not which account or what the agent did afterward.

Identity provider

Your identity provider (IdP) records sign-ins to the tenants it federates. A personal Max plan, a personal ChatGPT account used by Codex, or a free Copilot plan signs in with a personal email and never reaches your IdP.

Where do coding agents leave traces on a device?

Coding agents keep credentials and configuration in the home directory and read project-level files from the repository. These files name accounts and MCP servers directly. The following table lists the locations from each vendor's documentation, reviewed on November 3, 2026.

AgentCredentialsUser MCP configProject MCP config
Claude Code

macOS Keychain, or ~/.claude/.credentials.json on Linux and Windows

~/.claude.json (user and local scopes)

.mcp.json at the project root

OpenAI Codex

~/.codex/auth.json or the OS credential store

[mcp_servers.NAME] tables in ~/.codex/config.toml

.codex/config.toml, trusted projects only

CursorManaged by the editor and CLI~/.cursor/mcp.json.cursor/mcp.json
GitHub Copilot CLIManaged by the CLI~/.copilot/mcp-config.json

.mcp.json or .github/mcp.json

Copilot in VS CodeGitHub sign-in in VS Code

User-profile mcp.json, or ~/.copilot/mcp-config.json

.vscode/mcp.json

Anthropic's settings documentation describes Claude Code's ~/.claude.json as the file that holds account details and MCP server configurations. On a machine signed in through claude.ai, it carries an oauthAccount object with emailAddress and organizationUuid fields. Comparing that UUID with your Claude organization's tells an approved session from a personal one. The file's structure isn't a documented interface, so re-test any script that reads it after Claude Code updates.

The following commands collect this evidence on macOS or Linux. Run them as the developer, or through your MDM's script runner in the developer's context, because every path is in the user's home directory:

Terminal window
# Which account and organization Claude Code is signed in to
jq '.oauthAccount | {emailAddress, organizationUuid}' ~/.claude.json
# Claude Code MCP servers: user scope, then local scope per project
jq '.mcpServers // {} | keys' ~/.claude.json
jq '.projects // {} | map_values(.mcpServers // {} | keys)' ~/.claude.json
# Codex MCP servers
grep -E '^\[mcp_servers\.' ~/.codex/config.toml
# Cursor and Copilot CLI MCP servers
jq '.mcpServers | keys' ~/.cursor/mcp.json ~/.copilot/mcp-config.json
# Project-level MCP files in checked-out repositories
find ~/src ~/code -maxdepth 5 \( -name .mcp.json \
-o -path '*/.cursor/mcp.json' -o -path '*/.vscode/mcp.json' \
-o -path '*/.github/mcp.json' -o -path '*/.codex/config.toml' \) 2>/dev/null

Replace ~/src ~/code with the directories where your developers keep repositories.

Read configuration files; don't run the agents' own listing commands in an inventory script. claude mcp list checks each server's health, and for a stdio server that means starting it. We confirmed this on Claude Code 2.1.317 by adding a test server that writes a file when it starts: the file appeared after claude mcp list. On the engineer's laptop, that would start the staging database server.

What does each source of evidence prove?

A file inventory is a snapshot. To see what agents do over time, you need continuous evidence, and for a coding agent there are three sources. The gaps between them are where shadow use shows up.

An endpoint connection doesn't need the agent's cooperation, so it sees the personal-plan session in the scenario. It can't say which account the app used, because personal and Enterprise sessions both connect to api.anthropic.com, and it doesn't prove that a prompt was sent: Claude Code also contacts downloads.claude.ai for update checks and sends operational telemetry, according to its network requirements. One exception is useful: GitHub routes Copilot traffic by plan, and personal plans use *.individual.githubcopilot.com, so for Copilot the hostname does identify a personal plan.

A hook event is the only source that includes the command, the file path, or the MCP tool name, and the only one that can refuse the action. A session that never loaded your configuration produces none.

A Compliance API record is authoritative for your Claude organization, with activity and, on Enterprise, transcripts. According to the Arcjet Observe documentation, it doesn't cover personal Claude accounts, Claude Code authenticated with a Console API key, or Claude Code through Amazon Bedrock, Google Vertex AI, or Microsoft Foundry.

EvidenceProvesDoesn't proveSees a personal-plan session
Endpoint connectionThis app on this device reached this provider, for this personWhich account was used, or that the connection carried a promptYes, as a connection
Hook event

This agent ran this tool with these arguments, and the policy decided allow or deny

Anything in a session where your hooks didn't loadOnly if the hooks are installed on the device
Compliance APIYour Claude organization had this session and activityAnything outside your organization or its covered auth methodsNo

The useful signal is a mismatch. A device that shows steady connections from claude to api.anthropic.com, with no hook events and no Compliance API sessions for the same person in the same hours, is worth a conversation. The mismatch isn't proof on its own: the hooks may not be installed on that device yet, or the traffic may be update checks. The reverse inference doesn't hold either. A hook event a few seconds after a connection doesn't prove that the two belong to the same request, because one agent opens many connections and several sessions can run at once.

How do you find shadow MCP servers?

A shadow MCP server is one that a developer added without review. It's often the more serious half of the problem, because it gives the agent credentials and reach that the developer granted on the spot.

Most MCP servers on a developer machine use the stdio transport. The agent starts the server as a child process and talks to it over standard input and output, so the link between the agent and the server never touches the network. Whatever the server connects to, such as the staging database, it connects to from its own process, usually node, python, or uv. A connection log shows that process reaching Postgres or an internal API, not that a coding agent used an MCP tool to get there. Network controls, including an endpoint connection view, aren't the right place to discover these servers.

You can see stdio servers in two places. The configuration files in the preceding table list every server, its command, and its arguments, and a file inventory catches servers that haven't run yet. Hook events catch servers that do run: Claude Code names MCP tools mcp__SERVER__TOOL in its hook input, so a PreToolUse event for mcp__staging-db__query tells you which server and which tool, with the arguments. Remote MCP servers that use HTTP are visible on the network too, but the hook event still carries more: the tool name and arguments, not only the host.

To control MCP servers instead of only finding them, use the agent's managed configuration together with a hook policy. Claude Code's managed MCP controls include allowedMcpServers with allowManagedMcpServersOnly: true, and a fixed server set in managed-mcp.json. Match stdio servers with serverCommand and remote servers with serverUrl. Anthropic's documentation warns that a serverName entry isn't a security control, because the name is a label the developer chooses. Claude Code drops a blocked server from /mcp without saying why, so tell developers what you've blocked. A hook policy applies one MCP allowlist across Claude Code, Cursor, Codex, Copilot, and Muse Code. For more information about MCP allowlists, see restrict MCP servers in coding agents, and for servers that behave maliciously once approved, see rogue MCP server detection.

How do you respond to shadow AI without driving developers to workarounds?

The engineer in the scenario had a deadline and no seat on the approved path. If your response is only to block, the next engineer in her position finds a path you don't see: a personal laptop, a browser tab, or an API key in a shell variable. A response plan works when the approved path is faster than the workaround.

  1. Approve fast. Publish which coding agents, tiers, and MCP servers are approved, and give developers a request path that answers in days. Approve enterprise tiers, not tools: Claude Code on a Claude Team or Enterprise seat is a different risk from Claude Code on a personal plan.
  2. Refuse personal accounts with tenant controls. Hostnames can't separate personal and company accounts on claude.ai or chatgpt.com, but each vendor offers a control that can. For Claude Code, deploy forceLoginMethod and forceLoginOrgUUID in managed settings, and Claude Code refuses a claude.ai login to any other organization. For Codex, set forced_chatgpt_workspace_id. Block *.individual.githubcopilot.com for Copilot, and set AllowedTeamId for Cursor. For the network headers and the full set of controls, see block personal AI accounts on work laptops.
  3. Deploy managed settings through MDM. For Claude Code, set allowManagedHooksOnly so that only your hooks run, availableModels to limit model choice, and the MCP keys from the preceding section. Deliver them as files or MDM profiles, not only as server-managed settings: server-managed settings reach a developer only after they authenticate to your organization, which a personal-plan session never does.
  4. Install hooks with an MCP allowlist and a model allowlist. Hooks decide each tool call before it runs, on every agent you approve. An MCP allowlist stops an unreviewed server from being used even when it's configured. Codex and Cursor send no model switch event, so on those agents the policy refuses prompts and tool calls while a disallowed model is selected. For more information, see restrict models in coding agents.
  5. Block the providers you don't approve on the endpoint. For AI services with no approved tier at all, block the connection on the device. Endpoint provider blocking works without decrypting TLS, so it doesn't break approved tools that fail behind an inspecting proxy, and it applies on a hotel network the same as in the office.

Where does Arcjet fit, and where doesn't it?

Arcjet covers the three evidence sources from one console. Its endpoint agent runs on managed macOS and Windows devices and records each connection to an AI service with the app that made it and the person behind the local account. On macOS, it's a Network Extension content filter, with a root launchd service that owns reporting and credentials, so closing the menu bar app or logging out doesn't stop managed reporting. On Windows, it's a LocalSystem service that reads the destination server name from the TLS ClientHello through the Packet Monitor API and correlates kernel Event Tracing for Windows (ETW) events to identify the process and user, with no kernel driver. Neither decrypts TLS, installs a root certificate, or reads prompts, so tools that break under TLS inspection keep working. You deploy it through your MDM, including Jamf.

In the Arcjet Console, Discovery goes from people to devices to AI usage, and a Providers view shows which people, devices, and apps reach each service. Counts are connections, not prompts or tokens. An administrator maps local accounts to people, and shared, system, and unidentified accounts show as Unknown user instead of inheriting the device owner. Endpoint provider policies set Allow or Block per provider for a group of devices, the agent blocks the connection on the device, and each decision appears in Arcjet Activity with the person, device, app, and provider.

Arcjet coding agent hooks enforce one policy across Claude Code, GitHub Copilot, Cursor, OpenAI Codex, and Muse Code, installed through managed settings, MDM, or an organization lock. Starter policies include coding-agent.mcp-allowlist, coding-agent.model-allowlist, coding-agent.egress-allowlist, coding-agent.credential-access, and coding-agent.sensitive-info. The Claude Compliance API and OpenTelemetry export add observe-only records of sessions the hooks don't reach.

Arcjet is the wrong tool, or only part of the answer, in the following cases:

  • Unmanaged personal devices. A developer's personal laptop or phone has no endpoint agent and no managed settings.
  • Cloud-hosted agent sessions. Claude Code on the web, Codex cloud tasks, the Copilot cloud agent, and Cursor's cloud agents run on the vendor's infrastructure, not on the laptop, so the endpoint agent sees none of their traffic. Hooks reach some of these sessions where the vendor loads repository or organization configuration; the per-agent guides for Claude Code, Copilot, Codex, and Cursor say which.
  • Prompt content in a browser tab. Because the endpoint agent doesn't decrypt TLS, it doesn't inspect what someone pastes into claude.ai or chatgpt.com. Browser DLP products such as Harmonic, LayerX, and Prompt Security do this. For a comparison, see Prompt Security vs Arcjet.
  • Account identity from the network. A connection can't say whether api.anthropic.com was reached with a personal or a company account. Vendor tenant controls answer that, and Arcjet records what remains.
  • Local models. A model that runs on the laptop with no network traffic produces no connection to record.

To compare Arcjet with other shadow AI tools, see shadow AI detection tools. To see the endpoint agent on your own devices, book a demo. To start with hooks, follow the per-agent guide for your approved agent and publish coding-agent.mcp-allowlist in dry run, so you learn which servers your developers already use before you refuse any.

Frequently asked questions

What is shadow AI in a software engineering team?

It's AI use that the organization didn't approve and can't see. In engineering teams, it's usually a coding agent such as Claude Code, Cursor, Codex, or Copilot on a work laptop, signed in to a personal plan or connected to MCP servers that nobody reviewed.

How do I detect Claude Code on employee laptops?

Look for the claude binary, the ~/.claude directory, and ~/.claude.json, which lists MCP servers and, for claude.ai sign-ins, the signed-in email and organization UUID. Compare that UUID with your Claude organization's. An endpoint agent shows connections from the app to api.anthropic.com over time, but it can't tell a personal account from a company one.

Can EDR detect unapproved AI coding tools?

Generic EDR telemetry shows node, a Bun-compiled binary, or a command named agent making outbound TLS connections, which is normal developer activity. Some EDR products add an AI agent inventory, such as Microsoft Defender for Endpoint's local AI agent discovery, but an inventory entry doesn't prove use or show which account the agent signed in with.

How do I find MCP servers developers added themselves?

Read the config files: ~/.claude.json and .mcp.json for Claude Code, ~/.cursor/mcp.json for Cursor, ~/.codex/config.toml for Codex, and ~/.copilot/mcp-config.json or .vscode/mcp.json for Copilot. Hook events name each MCP tool call as it runs. Don't run claude mcp list in an inventory script, because it starts each stdio server to check its health.

Does blocking claude.ai or chatgpt.com stop personal accounts?

No. Personal and company accounts use the same domains, so a domain block also blocks the approved tool. Use vendor tenant controls such as forceLoginOrgUUID or the anthropic-allowed-org-ids header for Claude, forced_chatgpt_workspace_id for Codex, plan routing for Copilot, and AllowedTeamId for Cursor.

Does Arcjet read prompts or decrypt TLS?

No. The Arcjet endpoint agent records which app on which device reached which AI provider, and blocks providers you don't approve, without decrypting TLS or installing a root certificate. Hooks see tool calls and their arguments on the agents where they're installed. For prompt content typed into a browser tab, use a browser DLP product.

AI runtime security in your code

Protect your AI agent workflows with Arcjet

Publish the MCP allowlist starter policy in dry run and see which servers your developers' agents already use before you refuse any.