> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/Gentleman-Programming/engram/llms.txt
> Use this file to discover all available pages before exploring further.

# Privacy & Data Redaction

> Protect sensitive information with privacy tags stripped at multiple layers

Engram provides **defense-in-depth** privacy protection by stripping sensitive content at **two layers**: the plugin layer (before data leaves the process) and the store layer (before any database write).

## Privacy Tags

Wrap sensitive content in `<private>...</private>` tags to redact it from memory:

```markdown theme={null}
Set up API with <private>sk-abc123</private> key
```

Becomes:

```markdown theme={null}
Set up API with [REDACTED] key
```

<Warning>
  **The sensitive content is permanently removed.** Once stripped, it cannot be recovered from Engram's database.
</Warning>

## What Gets Redacted

Any content inside `<private>` tags is replaced with `[REDACTED]`:

* API keys and secrets
* Passwords and tokens
* Personal identifiable information (PII)
* Email addresses, phone numbers
* Database connection strings
* Any sensitive configuration values

<CodeGroup>
  ```markdown Before theme={null}
  Added JWT secret <private>super-secret-key-12345</private> to .env file

  Configured database: <private>postgres://user:pass@localhost/db</private>

  User reported issue via <private>john.doe@example.com</private>
  ```

  ```markdown After theme={null}
  Added JWT secret [REDACTED] to .env file

  Configured database: [REDACTED]

  User reported issue via [REDACTED]
  ```
</CodeGroup>

## Two-Layer Defense

Engram strips privacy tags at **two independent layers** to ensure sensitive data never reaches the database:

### Layer 1: Plugin Layer (TypeScript)

For agents using Engram plugins (OpenCode, Claude Code), privacy tags are stripped **before** data is sent to the HTTP API.

**OpenCode Plugin** (`engram.ts`):

```typescript theme={null}
function stripPrivateTags(content: string): string {
  return content.replace(/<private>.*?<\/private>/gis, '[REDACTED]');
}
```

**When it runs**:

* Before sending observations to `POST /observations`
* Before sending prompts to `POST /prompts`
* Before any HTTP request leaves the plugin

**Why?** Defense in depth. Even if the store layer fails, sensitive data never leaves the process.

### Layer 2: Store Layer (Go)

The SQLite store layer strips privacy tags **before any database write**, regardless of how the data arrives (HTTP API, MCP stdio, CLI).

**Implementation** (`internal/store/store.go`):

```go theme={null}
var privateTagRegex = regexp.MustCompile(`(?is)<private>.*?</private>`)

func stripPrivateTags(s string) string {
    result := privateTagRegex.ReplaceAllString(s, "[REDACTED]")
    result = strings.TrimSpace(result)
    return result
}
```

**When it runs**:

* Inside `AddObservation()` — strips `title` and `content` before INSERT
* Inside `UpdateObservation()` — strips `title` and `content` before UPDATE
* Inside `AddPrompt()` — strips `content` before INSERT
* Inside passive capture — strips learning items before save

**Why?** Guarantees that the database **never** contains sensitive data, even if called directly via API or CLI without a plugin.

## Pattern Details

### Regex Pattern

```go theme={null}
(?is)<private>.*?</private>
```

Flags:

* `(?i)` — Case-insensitive (matches `<PRIVATE>`, `<Private>`, etc.)
* `(?s)` — Dot matches newlines (supports multiline content)
* `.*?` — Non-greedy match (stops at first `</private>`)

### Supported Variations

All of these work:

```markdown theme={null}
<private>secret</private>
<PRIVATE>secret</PRIVATE>
<Private>secret</Private>
<private>
  multiline
  secret
</private>
```

### Nested Tags

The regex is **non-greedy**, so nested tags are handled correctly:

```markdown theme={null}
Outer <private>secret1</private> and <private>secret2</private> content
```

Becomes:

```markdown theme={null}
Outer [REDACTED] and [REDACTED] content
```

## Usage Examples

### Protecting API Keys

```markdown theme={null}
**What**: Added Stripe integration
**Why**: Process payments for premium features
**Where**: src/payments/stripe.ts
**Learned**: Test mode key is <private>sk_test_abc123</private>, live key is <private>sk_live_xyz789</private>
```

Stored as:

```markdown theme={null}
**What**: Added Stripe integration
**Why**: Process payments for premium features
**Where**: src/payments/stripe.ts
**Learned**: Test mode key is [REDACTED], live key is [REDACTED]
```

### Protecting Connection Strings

```markdown theme={null}
**What**: Set up production database
**Why**: Deploy to AWS RDS
**Where**: .env.production
**Learned**: Connection string format is <private>postgresql://admin:password123@prod-db.aws.com:5432/myapp</private>
```

Stored as:

```markdown theme={null}
**What**: Set up production database
**Why**: Deploy to AWS RDS
**Where**: .env.production
**Learned**: Connection string format is [REDACTED]
```

### Protecting User Data

```markdown theme={null}
**What**: Fixed bug in password reset flow
**Why**: User <private>alice@example.com</private> reported they couldn't reset their password
**Where**: src/auth/reset-password.ts
**Learned**: Token expiry was set to 1 hour instead of 24 hours
```

Stored as:

```markdown theme={null}
**What**: Fixed bug in password reset flow
**Why**: User [REDACTED] reported they couldn't reset their password
**Where**: src/auth/reset-password.ts
**Learned**: Token expiry was set to 1 hour instead of 24 hours
```

## When to Use Privacy Tags

<CardGroup cols={2}>
  <Card title="Always Redact" icon="shield-check">
    * API keys, tokens, secrets
    * Passwords, passphrases
    * Database credentials
    * Personal emails, phone numbers
    * Social Security Numbers, credit cards
  </Card>

  <Card title="Consider Redacting" icon="shield-exclamation">
    * Internal URLs with auth tokens
    * Server hostnames or IPs
    * User-specific identifiers
    * Proprietary business logic
    * Sensitive configuration values
  </Card>
</CardGroup>

<Note>
  **Team collaboration consideration**: If you're using [Git Sync](/features/git-sync) to share memories with your team, privacy tags prevent sensitive data from being committed to the repository.

  Always wrap secrets in `<private>` tags before saving to memory.
</Note>

## MCP Tool Integration

When calling MCP tools directly (without a plugin), privacy tags are still stripped at the store layer:

```json theme={null}
{
  "tool": "mem_save",
  "arguments": {
    "title": "Configured OAuth",
    "content": "Client secret: <private>abc123xyz</private>",
    "type": "config"
  }
}
```

The observation is saved with:

```json theme={null}
{
  "title": "Configured OAuth",
  "content": "Client secret: [REDACTED]",
  "type": "config"
}
```

## Verification

You can verify that privacy tags are working by:

### 1. Check the TUI

```bash theme={null}
engram tui
```

Navigate to the observation and verify that sensitive content shows `[REDACTED]`.

### 2. Check the Database Directly

```bash theme={null}
sqlite3 ~/.engram/engram.db
```

```sql theme={null}
SELECT content FROM observations WHERE id = 123;
```

Verify that the content contains `[REDACTED]` instead of the sensitive value.

### 3. Export and Inspect

```bash theme={null}
engram export memories.json
cat memories.json | grep -i private
```

Should return nothing — no `<private>` tags should appear in the export.

## Best Practices

<Steps>
  <Step title="Use privacy tags proactively">
    Don't wait until you see a secret in memory. Wrap it in `<private>` tags **before** saving.
  </Step>

  <Step title="Tag entire values, not just parts">
    ❌ Bad: `API_KEY=abc<private>123</private>xyz`

    ✅ Good: `API_KEY=<private>abc123xyz</private>`
  </Step>

  <Step title="Use privacy tags in prompts too">
    When saving user prompts with `mem_save_prompt`, wrap sensitive content:

    ```markdown theme={null}
    User asked: "Can you configure the API with key <private>sk-123</private>?"
    ```
  </Step>

  <Step title="Review memories before syncing">
    Before `engram sync`, review recent memories in the TUI to ensure no secrets leaked through:

    ```bash theme={null}
    engram tui  # Navigate to Recent Observations
    ```
  </Step>
</Steps>

## Limitations

<Warning>
  **What privacy tags do NOT protect**:

  1. **Secrets in file paths**: If you save `Fixed bug in /home/user/.ssh/id_rsa`, the path is stored as-is. Wrap it: `Fixed bug in <private>/home/user/.ssh/id_rsa</private>`

  2. **Secrets in tool output**: If an agent runs a command that outputs secrets to its context, those won't be automatically redacted unless the agent wraps them in privacy tags before saving.

  3. **Inference from context**: If you write "API key ends in 123", an attacker with access to your memory could narrow down the key. Be mindful of indirect leakage.
</Warning>

## Related

<CardGroup cols={2}>
  <Card title="Git Sync" icon="git-alt" href="/features/git-sync">
    Share memories safely with privacy tags
  </Card>

  <Card title="MCP Tools" icon="wrench" href="/features/mcp-tools">
    Save memories with privacy protection
  </Card>

  <Card title="Export/Import" icon="file-export" href="/cli/commands#export-import">
    Export memories with redacted content
  </Card>

  <Card title="Architecture" icon="sitemap" href="/concepts/architecture">
    Learn about Engram's security layers
  </Card>
</CardGroup>
