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

> Learn how Claude-Mem uses <private> tag stripping at the hook layer to give you full control over data privacy, preventing sensitive content from ever reaching storage.

- Repository: [Alex Newman/claude-mem](https://github.com/thedotmack/claude-mem)
- Tags: security
- Published: 2026-02-16

---

**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`](https://github.com/thedotmack/claude-mem/blob/main/src/utils/tag-stripping.ts)

The core privacy logic resides in [`src/utils/tag-stripping.ts`](https://github.com/thedotmack/claude-mem/blob/main/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:

```typescript
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

```typescript
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

```typescript
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

```typescript
// 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`](https://github.com/thedotmack/claude-mem/blob/main/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.