Back to Blog
Research14 min read

"Do Not Blow Your Cover": Deconstructing AI Authorship Concealment

W
WHOX Research
April 1, 2026
"Do Not Blow Your Cover": Deconstructing AI Authorship Concealment

Anthropic's Claude Code leaked today. All of it. 512,000 lines of TypeScript spilling out of a misconfigured npm package because someone forgot to exclude source maps from the production build. The infosec equivalent of taping your house keys to the front door.

One file earned its own news cycle: utils/undercover.ts.

When an Anthropic employee fires up Claude Code in a public repo, the system force-activates Undercover Mode. Can't turn it off. The prompt injected into the model:

You are operating UNDERCOVER in a PUBLIC/OPEN-SOURCE repository. Do not blow your cover.

Strip Co-Authored-By. Never mention Claude Code. Never admit you're an AI. Write commits the way a human developer would. The company that sells transparency as a brand value shipped a literal concealment engine for its open-source contributions.

We've spent twelve years building fingerprinting and detection systems. The dynamics here are straight out of our playbook. But instead of writing another hot take, we decided to do what we do best: take the concealment apart, layer by layer, and show exactly where it holds and where it breaks.

Most commentary focused on the prompt instructions: nine rules that tell the model what not to say. We went further. We read the implementation: undercover.ts, commitAttribution.ts, claude.ts, coordinatorMode.ts, system.ts. What we found underneath the concealment prompt is more revealing than the prompt itself.

The undercover instructions themselves contain an unforced error. Line 49 warns the model never to mention "opus-4-7" or "sonnet-4-8," model versions that don't publicly exist. The system designed to prevent codename leaks leaks codenames in its own prompt.


Inside undercover.ts: What the Implementation Reveals

Before we map instructions to detection layers, it's worth looking at how Undercover Mode actually works. Not the prompt it injects, but the code that decides when, where, and why the prompt fires.

The activation function:

export function isUndercover(): boolean {
  if (process.env.USER_TYPE === 'ant') {
    if (isEnvTruthy(process.env.CLAUDE_CODE_UNDERCOVER)) return true
    return getRepoClassCached() !== 'internal'
  }
  return false
}

Three design decisions are encoded here, and each tells you something about how Anthropic thinks about the problem.

No force-OFF. The source comment says it plainly: "There is NO force-OFF. This guards against model codename leaks β€” if we're not confident we're in an internal repo, we stay undercover." You can force it ON with an environment variable, but there is no corresponding OFF switch. This is a one-way door. An Anthropic engineer who wants to be transparent about AI use in a public contribution has no mechanism to do so through the tool itself. The system defaults to concealment and provides no opt-out.

Default is ON. getRepoClassCached() returns one of four values: 'internal', 'external', 'none', or null (check not yet run). Only 'internal' turns undercover off. Everything else, including "we don't know yet," activates concealment. The code comment explains: "'external', 'none', and null (check not yet run) all resolve to ON." Anthropic chose to treat uncertainty the same as public exposure. If in doubt, hide.

Dead-code elimination: concealment of the concealment. The most architecturally interesting detail: USER_TYPE === 'ant' is a build-time --define constant. The source comment: "Since USER_TYPE is a build-time --define, the bundler constant-folds these checks and dead-code-eliminates the ant-only branches from external builds. In external builds every function in this file reduces to a trivial return."

This means: in the npm package that regular users install, isUndercover() literally returns false, and getUndercoverInstructions() returns an empty string. The concealment engine doesn't just hide AI authorship. It hides itself. Without the source map, you'd never know these functions exist. The concealment is invisible to anyone outside Anthropic. A deliberate design choice to make the suppression system undetectable from the outside. Like antidetect browsers that spoof fingerprints and simultaneously hide the spoofing code from JavaScript introspection.


Undercover Mode: Full Instruction Set vs. Detection Surface

The leaked system prompt contains nine distinct concealment instructions. We mapped each one against the four detection layers identified in peer-reviewed research (Ghaleb et al., arXiv:2601.17406, January 2026). The question for each: does this instruction actually neutralize the detection signal?

# Undercover Mode Instruction Target Layer Neutralized? Why / Why Not
1 Never include Co-Authored-By lines Commit metadata Yes Metadata is trivially scrubbed. Detectors don't rely on it.
2 Never mention "Claude Code" or that you are an AI Commit metadata Yes Same as above. String matching is the weakest detection method.
3 No internal model codenames (Capybara, Tengu, etc.) Commit metadata Yes Prevents intel leakage, not relevant to AI detection.
4 No unreleased model version numbers Commit metadata Yes Same: IP protection, not detection evasion.
5 No internal repo names, Slack channels, short links Commit metadata Yes Same category.
6 Write commit messages "as a human developer would" Commit message style Partial Reduces the most discriminative signal (44.7% global importance). But "imitating human" still produces statistical tells: lower variance in message length, more uniform tone. Like an antidetect browser that spoofs the User-Agent but keeps the same TLS fingerprint.
7 No attribution of any kind Commit metadata Yes Easy win. Doesn't touch behavioral signals.
8 Strip all AI traces from commit records Commit metadata + style Partial Metadata: yes. But "traces" in code structure (conditional density, comment patterns) aren't in commit records. They're in the diff.
9 Cannot be manually disabled Operational security N/A Prevents employee from accidentally revealing AI use. Doesn't affect detection.

Score: 6 out of 9 instructions target metadata. Metadata is the weakest detection surface. The two partial wins (instructions 6 and 8) attempt to cross into behavioral territory but can't reach the code-level signals that carry 55%+ of detection weight.

The research is unambiguous on this. Ghaleb et al. trained an XGBoost classifier on 33,580 pull requests from five agents: OpenAI Codex, GitHub Copilot, Devin, Cursor, Claude Code. 41 features. 97.2% F1-score. Commit message characteristics dominated globally (44.7%), but code content features like conditional density and comment patterns provided the fingerprint that instruction 6 can't suppress.

Each agent bleeds its own signature. Codex loves multiline commits (67.5% feature importance). Claude Code stuffs conditionals into everything, 27.2% of its signal from if/else density alone. Copilot writes PR descriptions like it's billing by the word (38.4%). Devin follows conventional commit formatting so rigidly it becomes a tell (48.9%).

These findings converge with independent research. Tihanyi et al. (arXiv:2510.10493, 2025) trained CodeT5-JSA on 250,000 JavaScript samples from 20 LLMs and achieved 95.8% accuracy on 5-class model attribution: identifying which model generated the code, not just detecting AI presence. Their key finding: attribution survives minification (91% accuracy), mangling (93%), and even deobfuscation (85%). The signals classifiers exploit aren't formatting or comments. They're deeper structural patterns in AST topology and dataflow graphs. Code-level fingerprints persist through transformations that strip every surface feature a system prompt could address. Ghaleb identifies the what; Tihanyi explains the why: the fingerprint lives in how the model structures logic, not in how it formats text.

A methodological caveat on Ghaleb et al.: their 33,580 PRs are fully agent-generated. Every PR in the training set was produced entirely by an AI agent, not assisted by one. This matters because the classifier's thresholds (27% conditional density, 19.8% comment density) are calibrated against code written entirely by AI. For human-AI blended contributions (the realistic scenario for Anthropic employees), these thresholds may be too high. A PR where Claude wrote 60% and a human rewrote 40% would likely fall below the detection thresholds while still containing substantial AI content. The 97.2% F1-score applies to fully-automated PRs, not to the assisted-coding use case that Undercover Mode is designed to conceal.


Four Detection Layers: What Undercover Mode Can't Reach

Layer 1: Commit Metadata Undercover Mode coverage: full.

Attribution tags, AI mentions, internal references. Trivial to strip. No serious detector relies on this. Equivalent to clearing cookies and thinking you've anonymized your session.

Layer 2: Commit Message Style Undercover Mode coverage: partial.

This is where the concealment gets interesting and starts failing. "Write like a human" sounds simple. Statistically, it's not.

Human commit messages have high variance. Monday morning: "fix login." Friday 6pm: "Refactored the auth middleware to handle edge case where token expires mid-request, see #1247 for context." AI-generated messages, even instructed to "sound human," cluster around a narrower length distribution with more uniform tone. The variance itself is the signal.

Ghaleb et al. found commit message characteristics carry 44.7% of global detection weight. Instruction 6 ("write as a human would") compresses this but doesn't eliminate it. Like telling someone to forge a signature by looking at the original: you can match the shape, but the pen pressure and stroke velocity betray you.

Layer 3: Code Structure Undercover Mode coverage: zero.

No instruction in the leaked prompt addresses code-level patterns. None.

Claude Code's fingerprint in code: elevated conditional density (27.2% detection importance), high comment density (19.8%), uniform variable naming, evenly distributed error handling. These are baked into model weights during training. A system prompt saying "write like a human" changes surface formatting. It can't alter the statistical distribution of token choices learned across billions of parameters.

Human developers drift. Naming conventions mutate inside a single file as focus narrows. Comments cluster around the gnarly bits. Error handling varies module to module because it was written in different moods. AI output has the opposite texture: sterile consistency that reads like every line came off the same press.

Layer 4: Temporal Patterns Undercover Mode coverage: zero.

Watch the git log. A 487-line commit lands. Eight minutes later, a one-line typo fix. Three minutes after that, a missing import. Then a validation patch. This generate-then-patch rhythm reads like a cardiac monitor: for a human it's irregular and unpredictable, for AI it's a clean sine wave with minor correction ripples.

Undercover Mode doesn't touch commit timing. It can't. Timing is a function of workflow, not of prompt instructions. An AI that generates 500 lines in one burst then patches edge cases sequentially will always produce a different temporal signature than a human who iterates messily, weaving edits through the original work.


The Attribution Paradox: The System That Measures What It Hides

Here's what no one else has reported. While undercover.ts hides AI authorship from the outside, another file, commitAttribution.ts (962 lines), precisely measures it on the inside.

This isn't a logging utility. It's a full character-level attribution system that computes exactly how much of each commit was written by Claude:

  • Tracks claudeChars vs humanChars for every modified file, using prefix/suffix diff matching to isolate the changed region down to individual characters
  • Calculates claudePercent per file and per commit: the exact percentage of AI authorship
  • Stores results in git notes as structured JSON (a version 1 schema with per-file breakdown, surface info, and session IDs)
  • Persists across sessions: tracks promptCount, permissionPromptCount, escapeCount so the data survives compaction and restarts

The data schema tells you what Anthropic is measuring:

export type AttributionData = {
  version: 1
  summary: {
    claudePercent: number
    claudeChars: number
    humanChars: number
    surfaces: string[]
  }
  files: Record<string, {
    claudeChars: number
    humanChars: number
    percent: number
    surface: string
  }>
  surfaceBreakdown: Record<string, { claudeChars: number; percent: number }>
  excludedGenerated: string[]
  sessions: string[]
}

Every file modified in a commit has a claudeChars, humanChars, and percent field. Anthropic knows, to the character, how much of each file was written by their AI.

The paradox: the same codebase that hides AI authorship from the world also precisely measures it. undercover.ts says "never reveal what you are." commitAttribution.ts says "record exactly what you did." These systems coexist in the same repo, built by teams that appear to have had no conversation about the contradiction.

This isn't hypocrisy. It's instrumentation. The attribution data stays in internal git notes, not in public commits. But it means every "human-looking" commit from an Anthropic employee in a public repo has a shadow record, stored internally, documenting the exact percentage Claude wrote. The company doesn't just hide the signal. It hides the signal while keeping its own copy.

For detection research, this is significant. It means Anthropic has ground-truth data on AI-authored code in public repositories: data they could use to evaluate detection systems, improve concealment, or both. The attribution system is a private scoreboard for a public game.


22 Internal Repos: The Allowlist Leak

Undercover Mode deactivates in exactly one case: when the repo's git remote matches a hardcoded allowlist in commitAttribution.ts. The leak exposes this list of 22 private Anthropic repositories where the model is allowed to identify itself:

Infrastructure: terraform-config, infra-manifests, argo-rollouts, starling-configs, mycro_manifests, mycro_configs

Products: mobile-apps, claude-for-hiring, forge-web, claude-cli-internal, apps

Internal tools: casino, dbt, hex-export, ts-tools, ts-capsules, feldspar-testing, trellis, labs

Data and feedback: feedback-v2, dotfiles

The source comment is explicit about why this is a repo-level list, not an org-level check: "This is intentionally a repo allowlist, not an org-wide check. The anthropics and anthropic-experimental orgs contain PUBLIC repos (e.g., anthropics/claude-code, anthropic-experimental/sandbox-runtime). Undercover mode must stay ON in those to prevent codename leaks. Only add repos here that are confirmed PRIVATE."

Two implications. First, even Anthropic's own public repositories get the undercover treatment. If an Anthropic engineer uses Claude Code to contribute to anthropics/claude-code, the public repo for the tool itself, the model hides its involvement in its own source code. The concealment doesn't distinguish between "our public repo" and "someone else's public repo." Public means hidden, regardless of ownership.

Second, the allowlist leaks internal project codenames that the system was specifically designed to protect: "casino," "starling," "feldspar," "trellis," "mycro," "forge." The source comment for instruction 5 says "No internal repo or project names." The allowlist that enforces this rule is itself the leak. The guard published the list of things it's guarding.

The sanitizeModelName function in the same file reveals Anthropic's model version tree: opus-4-6, opus-4-5, opus-4-1, opus-4, sonnet-4-6, sonnet-4-5, sonnet-4, sonnet-3-7, haiku-4-5, haiku-3-5. Internal commits in the allowlisted repos use raw model identifiers; when code crosses the internal/external boundary, they get scrubbed to public names. Any version not matched falls through to the generic label "claude".


The Full Attribution Pipeline: From Measurement to Erasure

The concealment isn't just the undercover prompt. A separate file, attribution.ts, implements the full pipeline from measuring AI contribution to erasing it.

When undercover mode is active, getAttributionTexts() returns empty strings for both commit trailers and PR descriptions:

export function getAttributionTexts(): AttributionTexts {
  if (process.env.USER_TYPE === 'ant' && isUndercover()) {
    return { commit: '', pr: '' }
  }
  // ...
  const defaultCommit = `Co-Authored-By: ${modelName} <noreply@anthropic.com>`
  const defaultAttribution = `πŸ€– Generated with [Claude Code](${PRODUCT_URL})`
  // ...
}

When undercover is OFF (internal repos), the system generates Co-Authored-By trailers and PR badges. When ON, both are blank. The switch is binary: full attribution or zero attribution. No partial disclosure, no "AI-assisted" middle ground.

The internal attribution trailers are even more revealing. A comment on line 837 of commitAttribution.ts explains: "formatAttributionTrailer moved to attributionTrailer.ts for tree-shaking (contains excluded strings that should not be in external builds)." The trailer formatting code was physically separated into its own file specifically so the bundler could remove it from public builds. Internal commits carry trailers like "93% 3-shotted by claude-opus-4-6." But the code that generates them doesn't exist in any npm package a user can install.

This is the third layer of concealment: the attribution system measures AI contribution (layer one), the undercover mode suppresses it in commits (layer two), and tree-shaking removes the measurement display code from the build artifact (layer three). Even reverse-engineering the npm bundle won't reveal the trailer format because the code literally isn't there.


Testing It: Anthropic Employees in the Wild

Theory is cheap. We ran the numbers.

We wrote an automated pipeline that collects public PRs from confirmed Anthropic employees (members of the anthropics GitHub organization and top contributors to the anthropics/claude-code repository) and computes five detection metrics per PR: conditional density, comment density, What:Why comment ratio, identifier naming variance (Οƒ), and commit message length variance (Οƒ). We analyzed 28 merged PRs from 8 Anthropic employees across 18 external open-source repositories.

Author Repo Lines Cond% Com% W:W ID Οƒ Commits Msg Οƒ CoAuth
RobertCraigie NixOS/nixpkgs 1465 0.0% 1.0% 15:0 β€” 1 β€” 0
RobertCraigie glide-browser/glide 740 1.6% 14.2% 105:0 14.17 4 0.5 0
karpetrosyan crate-py/url #201 232 0.9% 9.1% 21:0 5.77 1 β€” 0
karpetrosyan openai/openai-python 92 0.0% 3.3% 3:0 β€” 7 2.5 0
karpetrosyan crate-py/url #200 49 0.0% 12.2% 6:0 3.54 1 β€” 0
mikelambert mastra-ai/mastra 81 2.5% 0.0% 0:0 6.65 9 1.9 7
mikelambert ag2ai/ag2 56 0.0% 5.4% 3:0 β€” 6 1.2 1
mikelambert lobehub/lobehub 51 0.0% 2.0% 1:0 1.15 1 β€” 1
mikelambert ThinkInAIXYZ/deepchat 42 0.0% 9.5% 4:0 β€” 1 β€” 1
mikelambert anything-llm 8 0.0% 0.0% 0:0 β€” 5 1.3 1
lovesegfault nix-community/crate2nix 163 3.1% 11.0% 16:2 β€” 2 1.4 0
lovesegfault NixOS/nixpkgs #502733 39 2.6% 15.4% 6:0 β€” 1 β€” 0
lovesegfault NixOS/nixpkgs #502704 33 3.0% 21.2% 7:0 β€” 1 β€” 0
felixrieseberg electron/fiddle #1851 1315 0.2% 6.2% 81:1 3.90 5 1.5 3
felixrieseberg electron/electron 16 100% 0.0% 0:0 β€” 1 β€” 0
felixrieseberg MarshallOfSound/ipc 23 13.0% 13.0% 3:0 1.73 2 1.4 1
bcherny zed-industries/zed #19928 354 3.7% 0.3% 1:0 5.15 7 1.6 2
bcherny zed-industries/zed #18640 83 7.2% 0.0% 0:0 4.60 10 2.3 1
chrislloyd oven-sh/bun 43 0.0% 2.3% 1:0 2.65 2 1.4 1
Mic92 numtide/llm-agents.nix 48 0.0% 29.2% 13:1 β€” 3 2.1 0
ddworken modelcontextprotocol/python-sdk β€” β€” β€” β€” β€” β€” β€” 0

(Plus 7 micro-PRs under 10 lines omitted from analysis. Full dataset: 28 PRs, 8 authors, 18 repos. "β€”" = insufficient data for that metric.)

Aggregate Statistics

On substantial PRs (β‰₯10 lines added, n=21):

Metric Anthropic employees AI zone (Ghaleb et al.)
Conditional density (mean) 7.0% (median 0.9%) >27%
Comment density (mean) 11.0% (median 6.2%) >19.8%
ID naming Οƒ (n=6, β‰₯5 identifiers) 6.71 (median 5.46) <2.0
Commit msg Οƒ (n=12, β‰₯2 commits) 1.6 (median 1.4) <1.0
What:Why comment ratio 302:5 (60:1) high what:why

Zero PRs trigger β‰₯2 Ghaleb flags simultaneously. None of these contributions look AI-generated by classifier standards. Conditional density averages 7.0% against a 27% threshold; even the median (0.9%) is an order of magnitude below the flag. Identifier naming diversity is high (Οƒ 6.71 vs <2.0 AI zone). Commit message variance is healthy.

One signal stands out: the What:Why ratio is 302:5 across all 28 PRs. Only 4 PRs contain any why-comments at all. That's 60 what-comments for every why-comment. This is the AI pattern: explaining what the code does, never why it exists. But it's also the pattern of small utility contributions where context is obvious. With a larger dataset of substantial PRs (>200 lines), this ratio would be more diagnostic.

The Co-Authored-By Bombshell

Here's where it gets interesting. Our automated pipeline flagged Co-Authored-By tags across the dataset. We expected zero. Undercover Mode instruction #1 says strip them. Instead: 10 of 28 PRs (36%) contain explicit AI co-authorship tags. 19 of 78 total commits (24%) credit Claude by name.

We extracted the exact co-author strings from the commit messages:

  • Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>: mikelambert (mastra, lobehub, anything-llm, ag2, deepchat), felixrieseberg (electron/fiddle, MarshallOfSound/ipc), chrislloyd (oven-sh/bun)
  • Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>: chrislloyd (oven-sh/bun, earlier commit)
  • Co-authored-by: Boris <boris@anthropic.com>: bcherny (zed-industries/zed, human pair programming with another Anthropic employee)

Eight of ten Co-Authored-By tags explicitly name Claude Opus 4.5 or 4.6 as the AI co-author. These are Anthropic employees openly crediting their own AI model in public open-source commits.

Why does this happen? The source code explains it completely. isUndercover() checks process.env.USER_TYPE === 'ant', a build-time constant. The standard npm package ships with USER_TYPE set to 'external', which means the entire undercover.ts file is dead code in public builds. Every function returns a trivial value. getAttributionTexts() happily produces Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> because there's no undercover mode to suppress it.

All of them are using the standard Claude Code build from npm, the same one anyone can install. Not an internal Anthropic build with USER_TYPE='ant'. The concealment system exists only in internal builds, and at least four Anthropic employees (mikelambert, felixrieseberg, bcherny, chrislloyd) are actively contributing to public repos with full AI attribution using the standard build.

A striking irony surfaces: the same codebase that implements a concealment system specifically for Anthropic employees also contains a separate system that produces the exact metadata Undercover Mode is designed to strip. Which system fires depends on which build you're running. And the employees apparently run the standard build.

The remaining 18 PRs with no Co-Authored-By could be: (a) employees using the internal build with Undercover Mode active, (b) single-commit PRs where no AI was involved, or (c) contributions made without Claude Code. We can't distinguish these cases from commit data alone. But the 36% rate of open AI attribution from Anthropic employees makes the "are they secretly using AI?" question partially moot. A third of them are telling you outright.

Size as Evasion Surface

61% of the 28 PRs are under 50 lines. Only 21% exceed 100 lines. This is consistent across all eight employees. The Ghaleb et al. classifier was trained on full agent-generated PRs where the AI did everything; small contributions may simply fall below the statistical threshold. Not because they're clean, but because there isn't enough code to produce a signal. You can't fingerprint a person from a single thumbprint smudge on a doorknob.

Whether this is deliberate strategy or natural contribution behavior, the effect is the same: small, scoped PRs are a structural blind spot for current classifiers. If you know large PRs get fingerprinted at 97.2% accuracy, the rational move is what we see here: keep contributions small, scoped, and unremarkable.


Anti-Distillation: The Other Concealment Layer

Undercover Mode hides who wrote the code. A separate system in claude.ts hides how the model thinks, and poisons anyone trying to learn from its output.

Lines 301-313 implement anti-distillation. When four conditions align (a compile-time flag ANTI_DISTILLATION_CC, CLI entrypoint, first-party API provider, and a GrowthBook feature gate tengu_anti_distill_fake_tool_injection), the API request includes anti_distillation: ['fake_tools']. The server responds by injecting decoy tool definitions into the system prompt. If someone is recording Claude Code's API traffic to train a competing model, the fake tools pollute that training data.

A second mechanism in betas.ts adds server-side connector-text summarization. Between tool calls, the API buffers the assistant's reasoning, summarizes it, and returns the summary with a cryptographic signature. On subsequent turns, the original text can be restored from the signature. If you're recording traffic to distill a competing model, you get summaries, not reasoning chains.

The native client attestation in system.ts adds a third layer. API requests include a cch=00000 placeholder in the billing header. Before the request leaves the process, Bun's native HTTP stack (written in Zig) overwrites those five zeros with a computed hash. The server verifies this token to confirm the request came from a real Claude Code binary. The placeholder uses the same byte length to avoid Content-Length changes and buffer reallocation. The computation happens below the JavaScript runtime, invisible to anything running in the JS layer. This is DRM for API calls, implemented at the HTTP transport level.

Together with Undercover Mode, these form a three-layer concealment architecture: hide who wrote the code (undercover), hide how the model reasons (anti-distillation), and verify the client is real (attestation). Each layer addresses a different threat surface.


Information Asymmetry: Every Request Tagged, Every Author Hidden

While undercover mode erases AI traces from commits, a parallel system in the same codebase tags every API request with identifying metadata. The two systems share a call site in claude.ts:

const fingerprint = computeFingerprintFromMessages(messagesForAPI)
// ...
systemPrompt = asSystemPrompt([
    getAttributionHeader(fingerprint),
    // ...
])

The fingerprint computation in fingerprint.ts uses a hardcoded salt 59cf53e54c78, characters at positions [4, 7, 20] of the user's first message, and the version string to produce a 3-character hex hash:

export const FINGERPRINT_SALT = '59cf53e54c78'

export function computeFingerprint(messageText: string, version: string): string {
  const indices = [4, 7, 20]
  const chars = indices.map(i => messageText[i] || '0').join('')
  const fingerprintInput = `${FINGERPRINT_SALT}${chars}${version}`
  const hash = createHash('sha256').update(fingerprintInput).digest('hex')
  return hash.slice(0, 3)
}

The getAttributionHeader function in system.ts combines this fingerprint with the version, entrypoint, client attestation hash, and workload routing into a single header: x-anthropic-billing-header: cc_version=VERSION.FINGERPRINT; cc_entrypoint=cli; cch=00000;. Every API call carries this header. On the same request, if the employee is using an internal build, getAttributionTexts() in commit.ts returns empty strings for the commit trailer.

The architecture is not paradoxical. It's deliberately asymmetric. The billing header goes to Anthropic's servers. The commit trailer goes to the public repo. Two audiences, two policies: full tracing for the server, zero attribution for the world. The system doesn't contradict itself. It serves different masters in the same HTTP request.

Combined with commitAttribution.ts's character-level tracking, Anthropic maintains a complete chain: which session generated which code, what model version, what percentage was AI-written, and a content-derived fingerprint tying the request to the user's message. The company that strips Co-Authored-By from your commits knows more about who wrote what than any external detector ever could.

One detail from secretScanner.ts crystallizes this architecture. The scanner that checks for leaked API keys assembles the Anthropic key prefix as ['sk','ant','api'].join('-') at runtime, because the literal string sk-ant-api is banned by their own excluded-strings build check. The scanner evades its own scanner. The system that detects leaks is itself designed to avoid detection by the system that prevents leaks. Recursive concealment.


The Industry Context: Anthropic vs. Everyone Else

Undercover Mode doesn't exist in a vacuum. Every major AI coding tool has made a choice about attribution transparency, and Anthropic's choice stands alone.

GitHub Copilot adds Co-Authored-By: Copilot to commits by default. As of March 2026, it also includes an Agent-Logs-Url trailer linking every commit to its full session log for audit purposes. Attribution is on by default, opt-out via config. The policy: transparency first, with an escape hatch.

Cursor released Agent Trace in January 2026, an open specification (RFC v0.1.0) for tracking AI-generated code at the file and line level with four contributor classifications: human, ai, mixed, unknown. The spec received backing from Cognition (Devin), Google Jules, Cloudflare, and Vercel. Cursor adds "Made with Cursor" to commits by default and offers line-level Cursor Blame on Enterprise plans. Attribution is on by default, opt-out via settings.

Amazon CodeWhisperer takes a different approach: a reference tracker that flags suggestions resembling open-source training data, surfacing the repository URL and license. No commit-level attribution, but proactive IP transparency.

The pattern is clear. Copilot, Cursor, and CodeWhisperer all default to some form of disclosure. None of them ships a concealment engine. Anthropic is the only vendor whose tool actively suppresses AI attribution, and the only one that does so without opt-out.

Kraishan (arXiv:2512.00867, November 2025) studied 14,300 GitHub commits across 7,393 repositories and found that standard Claude Code users attribute at 80.5%, the highest rate among all tools (Copilot: 9.0%). The standard build, without Undercover Mode, is the most transparent tool in the ecosystem. The concealment system exists exclusively for the internal build used by Anthropic employees. The company built the most transparent AI attribution system in the industry, then built a second system to disable it for themselves.


Coordinator Mode: The Brain Is a Prompt

One more file worth examining. coordinatorMode.ts reveals how Claude Code manages multi-agent workflows, and the answer is architecturally surprising: the orchestration algorithm isn't code. It's a 4,000+ word system prompt.

The coordinator manages worker agents through natural language instructions: "Do not rubber-stamp weak work." "You must understand findings before directing follow-up work. Never hand off understanding to another worker." "Verification means proving the code works, not confirming it exists."

Task routing, parallelism decisions, quality control, and failure recovery: all prompt. No state machines, no scheduler algorithms, no priority queues. The "brain" of Claude Code's multi-agent system is text. Which means: anyone with the prompt and an API key can replicate the orchestration. The coordination isn't protected by code complexity. It's just a very long, very specific set of instructions.

For the detection story, the coordinator is less about concealment and more about consistency. Every Claude Code user running in coordinator mode gets the same orchestration logic: the same prompt, the same task routing, the same verification patterns. This uniformity is the kind of signal classifiers learn to exploit: not what the code does, but how the work is structured across commits.


Audit Your Own Repository

You don't need a machine learning classifier to check for AI fingerprints. A git log and basic command-line tools give you five signals that correspond to the features in the Ghaleb et al. paper.

1. Commit Message Length Variance

git log --format="%s" --no-merges | awk '{print NF}' | \
  awk '{sum+=$1; sumsq+=$1*$1; n++} END {
    mean=sum/n; 
    variance=sumsq/n - mean*mean; 
    print "Mean words:", mean, "Std dev:", sqrt(variance)
  }'

Low standard deviation (below 3.0 words) with mean between 6-12 words: suspicious. Human developers swing between terse one-liners and paragraph-length explanations. AI clusters around medium length.

2. Temporal Cadence

git log --format="%H %at" --no-merges | awk '
  NR>1 {
    delta = prev_time - $2;
    if (delta > 0 && delta < 3600) 
      print prev_hash, delta "s"
  }
  { prev_time=$2; prev_hash=$1 }
'

Look for the pattern: large commit followed by 2-3 small commits within 15 minutes. Ratio of "big commit followed by micro-fixes within 15 min" to "standalone commits": above 30% is a flag.

3. Conditional Density (per PR/commit)

git diff <commit> | grep -c '^\+.*\(if\|else\|switch\|case\|? .*:\)' 
git diff <commit> | grep -c '^\+' 
# Divide first by second. Above 0.15 for non-validation code: flag.

Claude Code's tell is if/else everywhere. Humans write conditionals when logic demands it. AI front-loads them as a default control flow pattern, producing higher density even in code that doesn't need complex branching.

4. Comment Distribution

git diff <commit> | grep '^\+.*\/\/' | head -20

Manually inspect: are comments evenly spaced (every 5-10 lines), or clustered around complex sections? Do they explain what ("Hash the password") or why ("bcrypt over argon2 because prod lacks libsodium")? Uniform distribution + what-comments: AI signature.

5. Naming Consistency

git diff <commit> | grep -oE '^\+.*(let|const|var|function)\s+[a-zA-Z_]+' | \
  grep -oE '[a-zA-Z_]+$' | awk '{print length}' | \
  awk '{sum+=$1; sumsq+=$1*$1; n++} END {
    mean=sum/n;
    print "Mean length:", mean, "Std dev:", sqrt(sumsq/n - mean*mean)
  }'

Standard deviation below 2.5 characters across 20+ identifiers: suspiciously uniform. Human developers use short names for loop variables, long names for domain objects, and everything in between.

Scoring: Three or more flags on the same PR is a strong signal. No single metric is conclusive. The power is in correlation across layers, same principle as cross-layer browser fingerprinting.


The Architectural Constraints

The concealment system's design reveals structural limitations that no prompt update can fix.

Constraint 1: System prompts cannot alter token distributions. Every behavioral modification added to the undercover prompt creates a second-order pattern. Instructing the model to "vary conditional density" produces artificially varied density, a distribution optimized toward a target that differs from the natural distribution of human code. The gap between "optimized-for-human-like" and "actually human" is precisely what next-generation classifiers will learn to exploit. Tihanyi et al. demonstrated this directly: attribution survives even heavy code transformation because classifiers capture dataflow and AST-level patterns baked into model weights, not surface formatting that prompts control.

Constraint 2: Cross-layer consistency is exponentially hard. We've watched this exact cycle for a decade in browser antidetect. Spoofing User-Agent took five seconds. Randomizing canvas took an afternoon. Achieving cross-layer consistency where every observable parameter tells a coherent story? That's where most solutions collapse, because each layer you patch exposes three you didn't know existed. Undercover Mode addresses two of four detection layers (metadata and commit messages) while leaving two (code structure and temporal patterns) entirely untouched. Adding prompt instructions for code structure creates a fifth observable dimension (compliance with the prompt) without addressing temporal patterns. The layers multiply faster than the patches.

Constraint 3: The architecture is structurally fragmented. The source code reveals no shared configuration, no unified policy, and no coordination layer linking undercover.ts (suppress attribution) with commitAttribution.ts (measure attribution) with attribution.ts (format or erase attribution) with fingerprint.ts (tag every request). Four files, four behaviors, no coordinator. Whether this was deliberate separation of concerns or an organizational gap, the effect is the same: the system that hides and the system that measures operate independently, producing the contradictions documented in the source code itself.

Where detection goes next. The research trajectory is clear: from binary classification (human/AI) to model attribution (which AI), and from static classifiers to temporal models that track behavioral drift across a contributor's history. The fingerprint isn't in any single commit. It's in the sequence. Tihanyi et al.'s 20-class attribution at 88.5% accuracy demonstrates that even distinguishing between closely related models (GPT-4o vs GPT-4o-mini vs GPT-5) is tractable. The question shifts from "was this written by AI?" to "which specific model, which version, which configuration wrote this?"


What Undercover Mode Actually Reveals

The most interesting thing about utils/undercover.ts isn't the concealment. It's the confession.

Anthropic knows their AI leaves detectable traces in code. They quantified the risk precisely enough to build a dedicated suppression system. They scoped the suppression to metadata and commit messages because those are the layers they can control through a system prompt. They left code structure and temporal patterns untouched because they can't fix those without retraining the model.

But the source code says more than the prompt ever could. Seven interlocking systems, each addressing a different threat:

  • undercover.ts: hide who wrote the code
  • commitAttribution.ts: measure exactly how much the AI wrote
  • attribution.ts + attributionTrailer.ts: format the measurement for internal use, erase it for external use, with tree-shaking to remove the formatting code from public builds
  • The internal repo allowlist: classify which contexts are safe for honesty
  • Dead-code elimination: hide the concealment system itself from external builds
  • Anti-distillation in claude.ts: hide how the model reasons and poison copies
  • fingerprint.ts + system.ts: tag every API request with a content-derived fingerprint and client attestation, maintaining server-side traceability while client-side attribution is erased

Each system works. Together, they form a map of everything Anthropic considers a threat surface. Published by the team that built it, in a source map they forgot to exclude.

And our empirical analysis adds an eighth data point: at least four Anthropic employees openly credit Claude Code in public commits using the standard build, bypassing the entire concealment architecture. The system that hides AI authorship coexists with employees who don't bother to hide it. Either Anthropic hasn't mandated internal builds for external contributions, or the employees made a personal transparency choice that the company's architecture tried to make for them.

The gap between what Undercover Mode covers and what it doesn't is a map of Claude Code's known detection surface. The attribution system adds a second map: what Anthropic measures internally tells you what signals they believe are real. The allowlist adds a third: which repos they trust enough to be honest in tells you where they think the risk lives. The Co-Authored-By data adds a fourth: which employees use standard builds tells you how far the concealment actually reaches.

For anyone building AI code detection, this isn't just a gift. It's a blueprint. The target told you which layers they think are vulnerable, which ones they gave up on, which ones they track for their own scoreboard, and which employees opted out of the concealment entirely.

The cover was blown before it was built.


WHOX - We tell real signals from manufactured ones. Check your fingerprint at whox.com and find out what signals leak from your browser and how unique you are.

Sources

Ghaleb, T.A. et al. "Fingerprinting AI Coding Agents on GitHub." arXiv:2601.17406, January 2026. MSR 2026 Mining Challenge.

Tihanyi, N. et al. "The Hidden DNA of LLM-Generated JavaScript: Structural Patterns Enable High-Accuracy Authorship Attribution." arXiv:2510.10493, 2025.

Kraishan, O. "The AI Attribution Paradox: Transparency as Social Strategy in Open-Source Software Development." arXiv:2512.00867, November 2025.

Lu, Y. et al. "WASA: Watermarking for Source Code Attribution." ACL Findings, 2025.

Claude Code source leak, March 31, 2026. Files analyzed: utils/undercover.ts, utils/commitAttribution.ts, utils/attribution.ts, coordinator/coordinatorMode.ts, services/api/claude.ts, constants/system.ts, utils/fingerprint.ts, services/teamMemorySync/secretScanner.ts, utils/http.ts, commands/commit.ts.

GitHub API data collected April 1, 2026: 28 PRs from 8 confirmed Anthropic employees across 18 external repositories. Analysis pipeline: automated metric extraction via GitHub REST API with cached responses.

ai-detectionclaude-codefingerprintingresearchcode-authorship

Related Articles