Claude-Mem Private Tag Stripping: How It Implements User-Controlled Privacy

Claude-Mem implements privacy control by stripping <private> tags at the hook layer before data reaches storage, ensuring wrapped content never persists to SQLite or Chroma backends.

Claude-Mem is an open-source memory layer for Claude that gives users granular control over data persistence. The Claude-Mem private tag stripping mechanism allows prompt authors to mark sensitive content as non-persistent by wrapping it in <private>…</private> tags, which the system removes before any storage operations occur.

Dual-Tag Architecture for Privacy Control

Claude-Mem employs two distinct tags that are both stripped at the hook layer, but serve different purposes:

  • <private> – User-generated tags that explicitly mark content the author does not want stored. This gives end-users direct control over what enters the persistent memory.
  • <claude-mem-context> – System-generated tags that prevent recursive injection of auto-added observations when the hook already supplied context.

Both tags are stripped once, at the hook layer, which is the first processing point for incoming requests. This upstream filtering keeps the worker service simple and guarantees that persisted memory never contains private fragments.

Tag-Stripping Implementation in src/utils/tag-stripping.ts

The core privacy logic resides in src/utils/tag-stripping.ts, which exports a set of utilities for safely removing sensitive content.

Safety-First Tag Counting

Before any regex processing occurs, the countTags function tallies <private> and <claude-mem-context> occurrences. If the total exceeds the hard limit of MAX_TAG_COUNT = 100, the system logs a warning via logger.warn to guard against ReDoS (Regular Expression Denial of Service) attacks.

Core Stripping Logic with stripTagsInternal

The actual removal happens in stripTagsInternal, which uses non-greedy regex patterns to ensure each matched tag pair is removed in a single pass:

content
  .replace(/<claude-mem-context>[\s\S]*?<\/claude-mem-context>/g, '')
  .replace(/<private>[\s\S]*?<\/private>/g, '')
  .trim();

The [\s\S]*? pattern matches any character including line breaks, ensuring multi-line private blocks are fully removed.

Public API Methods

The module exposes two primary entry points:

  • stripMemoryTagsFromPrompt – Used for raw user prompts before they enter the processing pipeline.
  • stripMemoryTagsFromJson – Used for JSON-encoded tool inputs and outputs, parsing the string, stripping tags, and re-serializing.

Both methods delegate to stripTagsInternal, ensuring consistent privacy handling across all data paths.

Hook Layer Integration

The actual enforcement of privacy happens in the hook layer files under src/hooks/. Immediately after receiving a request, the hook imports the stripping utilities and sanitizes the payload before forwarding it to the worker service.

This architecture ensures that private content never reaches the SQLite or Chroma storage backends, as the stripping occurs upstream of any persistence logic.

Code Examples

Stripping Private Content from Prompts

import { stripMemoryTagsFromPrompt } from './src/utils/tag-stripping.js';

const userPrompt = `
  Here is my secret: <private>my password is 12345</private>.
  Please summarize the rest.
`;

const cleaned = stripMemoryTagsFromPrompt(userPrompt);
console.log(cleaned);
// Output:
// "Here is my secret: . Please summarize the rest."

Sanitizing JSON Tool Inputs

import { stripMemoryTagsFromJson } from './src/utils/tag-stripping.js';

const toolInput = JSON.stringify({
  query: "Search notes",
  notes: "<private>Do not store this note.</private>"
});

const cleanedJson = stripMemoryTagsFromJson(toolInput);
console.log(cleanedJson);
// Output (string):
// {"query":"Search notes","notes":""}

Hook Implementation Pattern

// In src/hooks/some-hook.ts
import { stripMemoryTagsFromPrompt } from '../utils/tag-stripping.js';

export async function handleRequest(request) {
  const safePrompt = stripMemoryTagsFromPrompt(request.prompt);
  // forward safePrompt to the worker…
}

Summary

  • Claude-Mem private tag stripping provides user-controlled privacy by removing <private> wrapped content before it reaches storage.
  • The system uses a dual-tag architecture (<private> for users, <claude-mem-context> for system) both stripped in src/utils/tag-stripping.ts.
  • Safety guardrails include a hard limit of 100 tags to prevent ReDoS attacks.
  • Hook-layer integration ensures private data never touches SQLite or Chroma backends, occurring upstream of the worker service.
  • Public APIs stripMemoryTagsFromPrompt and stripMemoryTagsFromJson handle both raw text and JSON-encoded tool I/O.

Frequently Asked Questions

What happens if I nest <private> tags inside each other?

The non-greedy regex [\s\S]*? matches the first closing tag it encounters, so nested tags may not strip as expected. The first </private> closes the first <private>, leaving subsequent content exposed. Best practice is to avoid nesting and use separate tag blocks for distinct private sections.

Does the <private> tag stripping work for tool outputs as well as inputs?

Yes, stripMemoryTagsFromJson handles both tool inputs and outputs that are JSON-encoded strings. Whether the private content appears in a tool request or response, the stripping utility parses the JSON, removes tagged sections, and re-serializes the result before any persistence occurs.

What is the maximum number of <private> tags allowed in a single request?

The system enforces a hard limit of MAX_TAG_COUNT = 100 total tags, which includes both <private> and <claude-mem-context> tags combined. If a request exceeds this limit, countTags triggers a logger.warn warning and the system proceeds with caution to prevent ReDoS attacks via catastrophic regex backtracking.

Where does the actual tag stripping occur in the request lifecycle?

Stripping occurs at the hook layer in files under src/hooks/, immediately after request receipt and before forwarding to the worker service. This upstream placement ensures that private content never reaches the SQLite or Chroma storage backends, eliminating any risk of accidental logging or persistence of sensitive data.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →