mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-08-02 21:04:08 +00:00
71 '<!-- CONFIRM -->' review markers across 19 files used HTML comment
syntax, which MDX cannot parse. One parse error breaks the whole
production build — this is why the deployed site 404'd on every page
while local dev limped along. All converted to {/* */} (code-fence
contents untouched). Also: remove the legacy source-'/' redirect,
replace the phantom architecture-diagram image with an ASCII diagram
until the real one lands.
Verified locally: mintlify broken-links parses all pages clean (one
known-good /api-reference tab link that 307s at runtime), and /,
/overview, /concepts/architecture, /quickstart, /patterns/*,
/versioning all render 200 with content.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
238 lines
13 KiB
Text
238 lines
13 KiB
Text
---
|
|
title: "Security & Compliance"
|
|
sidebarTitle: "Security"
|
|
description: "Tenant isolation you can enforce with a key, encryption, SOC 2 Type 2, GDPR and HIPAA posture, and how to actually delete a user's data."
|
|
icon: "shield-halved"
|
|
---
|
|
|
|
This page is written for your security review. It covers the isolation model and how to enforce it, encryption, compliance status (SOC 2 Type 2 {/* CONFIRM: publishable SOC 2 Type 2 wording */}, GDPR, HIPAA/BAA), what we do and don't do with your data, and the exact API calls that implement a right-to-erasure request. Where an answer is a document rather than an API call, the [last section](#request-the-reports) tells you how to get it.
|
|
|
|
## The isolation guarantee: scoped API keys
|
|
|
|
Here's the question every reviewer asks, so let's answer it first: *"Suppose I'm a malicious developer — or a compromised client, or a prompt-injected agent. `containerTag` is only a request parameter. What stops me from changing it and reading another user's memories?"*
|
|
|
|
If the caller holds your org-wide API key: nothing. That key can touch every container in your organization — that's what an org key is for, and it should never leave your server. Anything closer to the user gets a **scoped key** instead: a key bound to exactly one [container tag](/concepts/permissioning) at creation time.
|
|
|
|
To mint one, call the scoped-key endpoint from your server with your org key:
|
|
|
|
<CodeGroup>
|
|
|
|
```typescript TypeScript
|
|
// POST /v3/auth/scoped-key — server-side only, never from the client
|
|
const res = await fetch("https://api.supermemory.ai/v3/auth/scoped-key", {
|
|
method: "POST",
|
|
headers: {
|
|
Authorization: `Bearer ${process.env.SUPERMEMORY_API_KEY}`,
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify({
|
|
containerTag: "user_4f8a",
|
|
expiresInDays: 30,
|
|
}),
|
|
});
|
|
const { key } = await res.json();
|
|
// hand `key` to the client session — it can only ever see user_4f8a
|
|
```
|
|
|
|
```python Python
|
|
# POST /v3/auth/scoped-key — server-side only, never from the client
|
|
import os
|
|
import requests
|
|
|
|
res = requests.post(
|
|
"https://api.supermemory.ai/v3/auth/scoped-key",
|
|
headers={"Authorization": f"Bearer {os.environ['SUPERMEMORY_API_KEY']}"},
|
|
json={"containerTag": "user_4f8a", "expiresInDays": 30},
|
|
)
|
|
key = res.json()["key"]
|
|
# hand key to the client session — it can only ever see user_4f8a
|
|
```
|
|
|
|
```bash cURL
|
|
curl -X POST "https://api.supermemory.ai/v3/auth/scoped-key" \
|
|
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"containerTag": "user_4f8a",
|
|
"expiresInDays": 30
|
|
}'
|
|
```
|
|
|
|
</CodeGroup>
|
|
|
|
The boundary is then enforced at the data layer, not in your application code:
|
|
|
|
- A request naming any **other** container tag is rejected with `403 Forbidden`.
|
|
- A request that omits the tag is automatically scoped to the key's own container.
|
|
- The key only reaches memory endpoints (`/v3/documents`, `/v3/search`, `/v4/search`, `/v4/memories`, `/v4/profile`, `/v4/conversations`, `/v3/container-tags`) — no billing, account, or key-management routes.
|
|
- Revocation is immediate: `DELETE /v3/auth/scoped-key/:keyId`, and the key gets `401` from that moment on.
|
|
|
|
So the answer for your review, in print: the attacker can change the parameter, and the API refuses the request. Isolation doesn't depend on your code being correct — it depends on which key the caller holds, and the check runs server-side on every request, not in a client library that could be patched out.
|
|
|
|
The SDK has no helper for key management yet — mint and revoke via the REST endpoint as above. Full parameters (rate-limit overrides, naming, expiry bounds) are in the [authentication reference](/authentication), and the complete multi-tenant design — including the mistakes to avoid — is in [permissioning](/concepts/permissioning).
|
|
|
|
<Note>
|
|
Human access works the same way: organization members can be restricted to specific container tags in the console, with the same 403 behavior applied to people instead of keys.
|
|
</Note>
|
|
|
|
## Encryption in transit and at rest
|
|
|
|
Your data is encrypted in transit and at rest. {/* CONFIRM: encryption specifics — TLS version, at-rest cipher (AES-256?), key management details */}
|
|
|
|
On self-hosted deployments, encryption keys are yours: the enterprise and managed on-prem tiers run inside your infrastructure, so key custody follows your own KMS setup. One gotcha worth knowing before it bites: on self-hosted installs, stored data is encrypted against your configured key — lose or rotate that key incorrectly and existing data becomes unreadable. The setup and recovery notes are in [self-hosting troubleshooting](/self-hosting/troubleshooting).
|
|
|
|
## Compliance status
|
|
|
|
**SOC 2 Type 2.** Supermemory is SOC 2 Type 2 certified. {/* CONFIRM: exact publishable statement — "certified" vs "audited/report available", audit period, auditor */} The report is available under NDA — see [how to request it](#request-the-reports).
|
|
|
|
**GDPR.** Supermemory supports GDPR-compliant deployments: a Data Processing Agreement (DPA) is available {/* CONFIRM: DPA availability + whether self-serve or on request */}, and the right-to-erasure mechanics are a first-class API operation, [documented below](#delete-a-users-data-the-right-to-erasure-path) — not a support ticket.
|
|
|
|
**HIPAA.** A Business Associate Agreement (BAA) is available on the managed cloud only. {/* CONFIRM: BAA availability + cloud-only scope */} The scoping matters, so here's the reasoning: a BAA covers infrastructure *we* operate and audit. On a self-hosted deployment the infrastructure is yours, so there's nothing for us to attest — you inherit your own cloud's compliance posture instead (which is often exactly what a healthcare security team wants; see [deployment tiers](/self-hosting/tiers)).
|
|
|
|
If you're evaluating for a regulated environment, the practical split is: managed cloud when you want our controls and paperwork to cover you, self-hosted when your controls must cover everything.
|
|
|
|
## Your data is not training data
|
|
|
|
We don't train models on your data. {/* CONFIRM: exact scope of no-training commitment — all customers or paid plans only; contractual wording */}
|
|
|
|
Worth understanding *how* that's true mechanically, because it's a common source of skepticism. Supermemory's ingestion pipeline runs a custom fine-tuned memory model — but fine-tuning happened before your data ever arrived. When you ingest content, the model derives memories from it at inference time; nothing about your content updates model weights. The derived state (memories, graph, [profiles](/concepts/user-profiles)) lives inside your container as data, not inside any model — which is also why deleting a container [actually removes what was learned from it](#delete-a-users-data-the-right-to-erasure-path).
|
|
|
|
## Choose where your data lives
|
|
|
|
Data residency comes in four shapes, from most managed to most yours:
|
|
|
|
- **Managed cloud** — the default. {/* CONFIRM: cloud hosting region(s) and whether region pinning / EU residency is available */}
|
|
- **Managed on-prem** — we operate supermemory inside your cloud account; data never leaves your VPC.
|
|
- **Enterprise self-hosted** — you run everything, including on air-gapped infrastructure.
|
|
- **Local binary** — for development and small workloads, entirely on your machine.
|
|
|
|
If "data cannot leave our environment" is a hard requirement (it was the number-one blocker for more than one enterprise we've worked with), the answer isn't a cloud configuration flag — it's picking the right tier. The full matrix, including scale ceilings and what the OSS tier lacks, is in [deployment tiers](/self-hosting/tiers).
|
|
|
|
## Delete a user's data: the right-to-erasure path
|
|
|
|
The container boundary is also the deletion boundary. A GDPR erasure request, an offboarded tenant, and an ended client contract are all the same operation: bulk-delete everything under the container tag.
|
|
|
|
<CodeGroup>
|
|
|
|
```typescript TypeScript
|
|
// DELETE /v3/documents/bulk — removes every document in the container
|
|
// (no SDK helper yet — call the endpoint directly)
|
|
await fetch("https://api.supermemory.ai/v3/documents/bulk", {
|
|
method: "DELETE",
|
|
headers: {
|
|
Authorization: `Bearer ${process.env.SUPERMEMORY_API_KEY}`,
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify({ containerTags: ["user_4f8a"] }),
|
|
});
|
|
```
|
|
|
|
```python Python
|
|
# DELETE /v3/documents/bulk — removes every document in the container
|
|
import os
|
|
import requests
|
|
|
|
requests.delete(
|
|
"https://api.supermemory.ai/v3/documents/bulk",
|
|
headers={"Authorization": f"Bearer {os.environ['SUPERMEMORY_API_KEY']}"},
|
|
json={"containerTags": ["user_4f8a"]},
|
|
)
|
|
```
|
|
|
|
```bash cURL
|
|
curl -X DELETE "https://api.supermemory.ai/v3/documents/bulk" \
|
|
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{ "containerTags": ["user_4f8a"] }'
|
|
```
|
|
|
|
</CodeGroup>
|
|
|
|
This removes the user's documents and the memories derived from them. {/* CONFIRM: derived memories, graph edges, and profile fully cleared by bulk delete; backup/retention window before data is unrecoverable */} If the user held a scoped key, revoke it too: `DELETE /v3/auth/scoped-key/:keyId` takes effect immediately.
|
|
|
|
<Warning>
|
|
Deletion is permanent and there's no recovery — gate this behind your own confirmation flow. Note that deleting documents does **not** restore used quota: you're billed at ingestion, not for storage held. See [usage & billing](/trust/usage-and-billing).
|
|
</Warning>
|
|
|
|
### Know the difference: delete vs forget
|
|
|
|
Supermemory has two removal mechanisms, and for compliance work you must use the right one:
|
|
|
|
- **Delete (v3, document-level) is the erasure path.** `DELETE /v3/documents/:id` (`client.documents.delete(id)` in the SDK) removes one document; `DELETE /v3/documents/bulk` removes everything under a container tag. This is hard removal — use it for right-to-erasure requests.
|
|
- **Forget (v4, memory-level) is a product feature, not erasure.** `DELETE /v4/memories` and `POST /v4/memories/forget-matching` soft-delete individual derived facts: the memory stops appearing in search results but is preserved in the database (`isForgotten: true`), so the system can reason about what it used to believe. That's the right tool for "the user corrected an outdated fact" — and the wrong tool for "the user invoked their legal right to be forgotten."
|
|
|
|
For a single stale fact rather than a whole user, forget is what you want:
|
|
|
|
<CodeGroup>
|
|
|
|
```typescript TypeScript
|
|
// DELETE /v4/memories — soft-delete one memory by id or exact content
|
|
await fetch("https://api.supermemory.ai/v4/memories", {
|
|
method: "DELETE",
|
|
headers: {
|
|
Authorization: `Bearer ${process.env.SUPERMEMORY_API_KEY}`,
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify({
|
|
id: "mem_abc123",
|
|
containerTag: "user_4f8a",
|
|
reason: "outdated information",
|
|
}),
|
|
});
|
|
```
|
|
|
|
```python Python
|
|
# DELETE /v4/memories — soft-delete one memory by id or exact content
|
|
import os
|
|
import requests
|
|
|
|
requests.delete(
|
|
"https://api.supermemory.ai/v4/memories",
|
|
headers={"Authorization": f"Bearer {os.environ['SUPERMEMORY_API_KEY']}"},
|
|
json={
|
|
"id": "mem_abc123",
|
|
"containerTag": "user_4f8a",
|
|
"reason": "outdated information",
|
|
},
|
|
)
|
|
```
|
|
|
|
```bash cURL
|
|
curl -X DELETE "https://api.supermemory.ai/v4/memories" \
|
|
-H "Authorization: Bearer $SUPERMEMORY_API_KEY" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"id": "mem_abc123",
|
|
"containerTag": "user_4f8a",
|
|
"reason": "outdated information"
|
|
}'
|
|
```
|
|
|
|
</CodeGroup>
|
|
|
|
And `forget-matching` handles "forget everything about X" agentically, with a `dryRun` mode to preview the blast radius before committing. Both are covered in depth in [memory operations](/memory-operations).
|
|
|
|
## Request the reports
|
|
|
|
For anything that's a document rather than an API call — the SOC 2 report, the DPA, a BAA, or answers to a security questionnaire — reach the team through your account contact, or at the address on [console.supermemory.ai](https://console.supermemory.ai). {/* CONFIRM: security/report-request contact address (security@supermemory.ai?) */} The SOC 2 report ships under NDA; the DPA and BAA come back countersigned.
|
|
|
|
---
|
|
|
|
That's it — every answer on this page is either an API call you can test or a document you can request.
|
|
|
|
## Where next
|
|
|
|
<Columns cols={2}>
|
|
<Card title="Permissioning & multi-tenancy" href="/concepts/permissioning">
|
|
The complete isolation model — container tags, metadata, scoped keys, and the anti-patterns.
|
|
</Card>
|
|
<Card title="Deployment tiers" href="/self-hosting/tiers">
|
|
Cloud, managed on-prem, enterprise self-hosted, and local — pick by residency requirement.
|
|
</Card>
|
|
<Card title="Authentication" href="/authentication">
|
|
Scoped-key parameters, rate-limit overrides, and revocation.
|
|
</Card>
|
|
<Card title="Usage & billing" href="/trust/usage-and-billing">
|
|
What you're charged for, and why deletion doesn't restore quota.
|
|
</Card>
|
|
</Columns>
|