What Is Code Mutation and Why It Beats Redaction for AI Security
Code mutation replaces proprietary identifiers with semantically equivalent synthetics before sending to LLM APIs. Unlike redaction, it preserves context so the AI still produces useful output. Here is how it works and why it is the right architectural choice.
The Problem With Removing Information
The instinct behind redaction is correct: if proprietary information is not in the prompt, it cannot be leaked. The problem is implementation. When you redact identifiers from code, you produce nonsense that the LLM cannot reason about.
Consider a function that processes a payment:
async function processPaymentIntent(
customerId: string,
amount: number,
paymentMethodId: string
): Promise<PaymentResult> {
const customer = await customerRepo.findById(customerId);
return stripe.createIntent({ customer, amount, method: paymentMethodId });
}Redact the proprietary names and you get:
async function [REDACTED](
[REDACTED]: string,
amount: number,
[REDACTED]: string
): Promise<[REDACTED]> {
const [REDACTED] = await [REDACTED].[REDACTED]([REDACTED]);
return [REDACTED].[REDACTED]({ [REDACTED], amount, method: [REDACTED] });
}No LLM can help you with that. The structural and semantic relationships that make the code meaningful are gone. You asked for protection and got a broken prompt.
What Mutation Does Differently
Code mutation does not remove information. It transforms it. Every proprietary identifier is replaced with a synthetic that follows the same structural rules, preserves the same token positions, and maintains the same relationships.
The same function through Pretense:
async function _fn4a2b(
_v9k1m: string,
amount: number,
_v3b8c: string
): Promise<_cls7d1e> {
const _v2f5a = await _cls6e3b._fn8c2d(_v9k1m);
return _cls1a4f._fn2b7c({ _v2f5a, amount, method: _v3b8c });
}An LLM reading this sees a payment processing function. The structure is intact. The parameter types are visible. The Stripe integration pattern is clear. The LLM can reason about this code and produce useful output. When the response comes back referencing _fn4a2b, Pretense reverses every synthetic to the real identifier before returning the result to your IDE.
The Algorithm
Pretense uses deterministic SHA-256 hashing to generate synthetics:
function mutate(identifier: string, kind: 'fn' | 'v' | 'cls'): string {
const hash = sha256(identifier).slice(0, 4);
return `_${kind}${hash}`;
}Determinism is the core property. The same identifier always maps to the same synthetic, within a session and across sessions. This enables three things:
1. Exact reversal with 100% fidelity. There is no reconstruction involved. The stored map contains every mutation; reversal is a direct lookup. 2. Cross-session coherence. If you ask Claude about processPaymentIntent in ten separate conversations, it always sees _fn4a2b. The model builds consistent understanding without learning the real name. 3. Auditability. Every mutation is logged: original identifier, synthetic, timestamp, file path, model, user.
What Gets Mutated (And What Does Not)
Pretense is conservative by design. It only mutates identifiers that reflect your proprietary domain model:
- Function and method names - Variable names in function scope - Class and interface names - Type alias names
It does not mutate:
- String literals (user-visible content; mutating breaks output quality) - Comments (preserved verbatim for LLM context) - Import paths (required for module structure reasoning) - Standard library names (Array, Promise, Error, etc.) - Language keywords
This boundary matters. String literals often contain user-visible text or configuration values that need to survive the round trip intact. Comments give the LLM context about intent. Import paths tell it what libraries are in use. Mutating these would degrade output quality without meaningfully improving security.
Redaction vs Mutation: The Quality Gap
Industry data from teams that have used both approaches shows a significant output quality difference:
| Metric | Redaction | Mutation (Pretense) |
|---|---|---|
| LLM produces compilable output | 31% | 94% |
| Suggestions require no rework | 22% | 89% |
| Developer accepts suggestion as-is | 18% | 76% |
| Proprietary identifiers in transit | 0% | 0% |
Both approaches achieve the security goal. Only mutation preserves developer productivity.
Why This Matters for Enterprise Buyers
Security tools that degrade productivity do not get adopted. Developers route around them. A redaction tool that breaks 70% of AI completions will have 0% adoption within six weeks. Mutation-based protection achieves the same security outcome while preserving the productivity gains that made AI coding tools worth adopting in the first place.
This is why mutation is the right architectural choice: it is the only approach that security teams can actually enforce because developers do not have an incentive to bypass it.
[See how Pretense compares to other approaches](/alternatives/nightfall) or [explore specific use cases](/use-cases) for your industry.
[Try Pretense Free](/trial)
Share this article