@hilbras/remembra 3.0.0 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +63 -0
- package/README.md +20 -0
- package/dist/http.d.ts +17 -1
- package/dist/http.js +81 -23
- package/dist/http.js.map +1 -1
- package/dist/index.js +66 -40
- package/dist/index.js.map +1 -1
- package/dist/service.d.ts +25 -1
- package/dist/service.js +72 -9
- package/dist/service.js.map +1 -1
- package/dist/store.d.ts +2 -0
- package/dist/store.js +61 -9
- package/dist/store.js.map +1 -1
- package/dist/types.d.ts +200 -2
- package/dist/types.js +93 -7
- package/dist/types.js.map +1 -1
- package/docs/chatgpt.md +3 -1
- package/docs/clients.md +3 -0
- package/docs/security.md +89 -0
- package/docs/tools.md +11 -0
- package/package.json +1 -1
package/dist/types.js
CHANGED
|
@@ -1,12 +1,98 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
/** The four memory types Remembra stores. */
|
|
3
3
|
export const MemoryType = z.enum(["fact", "decision", "role", "history"]);
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Frontmatter schema version. Bump when the Memory format changes and
|
|
6
|
+
* add a migration step in store.ts (missing field in old files = v1).
|
|
7
|
+
*/
|
|
8
|
+
export const SCHEMA_VERSION = 1;
|
|
9
|
+
/**
|
|
10
|
+
* Scope safety: reject `..` path segments so scope can never escape the
|
|
11
|
+
* storage root (P0 directory traversal fix). Absolute paths like
|
|
12
|
+
* `/home/user/project` are fine — path.join keeps them contained.
|
|
13
|
+
*/
|
|
14
|
+
export function isSafeScope(scope) {
|
|
15
|
+
if (scope === "global")
|
|
16
|
+
return true;
|
|
17
|
+
const normalized = scope.replace(/[^a-zA-Z0-9._/-]/g, "_");
|
|
18
|
+
return !normalized.split("/").some((seg) => seg === "..");
|
|
19
|
+
}
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Shared input schemas (audit #13: one source of truth).
|
|
22
|
+
// The raw `*Shape` objects feed MCP tool inputSchemas (ZodRawShape);
|
|
23
|
+
// the parsed `*Input` objects validate on every transport.
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
export const storeInputShape = {
|
|
26
|
+
type: MemoryType.describe("fact | decision | role | history"),
|
|
27
|
+
content: z.string().min(1).describe("The memory itself, written as a standalone statement"),
|
|
28
|
+
scope: z
|
|
29
|
+
.string()
|
|
30
|
+
.default("global")
|
|
31
|
+
.describe("'global' for always-relevant memories, or a project path/id for project-scoped ones"),
|
|
32
|
+
tags: z.array(z.string()).default([]).describe("Keywords that boost retrieval"),
|
|
33
|
+
importance: z
|
|
34
|
+
.number()
|
|
35
|
+
.int()
|
|
36
|
+
.min(1)
|
|
37
|
+
.max(5)
|
|
38
|
+
.default(3)
|
|
39
|
+
.describe("1=minor, 5=critical (default 3)"),
|
|
40
|
+
source: z.string().optional().describe("Originating session or client"),
|
|
41
|
+
};
|
|
42
|
+
export const StoreInput = z
|
|
43
|
+
.object(storeInputShape)
|
|
44
|
+
.refine((v) => isSafeScope(v.scope), { message: "scope must not contain '..' path segments" });
|
|
45
|
+
export const digestInputShape = {
|
|
46
|
+
transcript: z
|
|
47
|
+
.string()
|
|
48
|
+
.min(1)
|
|
49
|
+
.describe("Conversation transcript or a detailed summary of the session"),
|
|
50
|
+
scope: z.string().optional().describe("Scope for extracted memories (default: global)"),
|
|
51
|
+
source: z.string().optional().describe("Originating session/client"),
|
|
52
|
+
};
|
|
53
|
+
export const DigestInput = z
|
|
54
|
+
.object(digestInputShape)
|
|
55
|
+
.refine((v) => v.scope === undefined || isSafeScope(v.scope), {
|
|
56
|
+
message: "scope must not contain '..' path segments",
|
|
57
|
+
});
|
|
58
|
+
export const searchInputShape = {
|
|
59
|
+
query: z.string().optional().describe("Keywords to match (omit to get a scope/recency-ranked list)"),
|
|
60
|
+
scope: z.string().optional().describe("Current project path or workspace id to filter by"),
|
|
61
|
+
type: MemoryType.optional(),
|
|
62
|
+
limit: z.number().int().min(1).max(50).optional(),
|
|
63
|
+
};
|
|
64
|
+
export const SearchInput = z.object(searchInputShape);
|
|
65
|
+
export const listInputShape = {
|
|
66
|
+
scope: z.string().optional(),
|
|
67
|
+
type: MemoryType.optional(),
|
|
68
|
+
includeArchived: z.boolean().optional().describe("Include archived memories (flagged)"),
|
|
69
|
+
};
|
|
70
|
+
export const ListInput = z.object(listInputShape);
|
|
71
|
+
export const forgetInputShape = {
|
|
72
|
+
id: z.string().describe("Memory id (from memory_store or memory_list)"),
|
|
73
|
+
};
|
|
74
|
+
export const ForgetInput = z.object(forgetInputShape);
|
|
75
|
+
/** Backup file envelope (remembra export / import). */
|
|
76
|
+
export const SNAPSHOT_FORMAT = "remembra-export";
|
|
77
|
+
export const SnapshotInput = z.object({
|
|
78
|
+
format: z.literal(SNAPSHOT_FORMAT),
|
|
79
|
+
version: z.number().int().positive(),
|
|
80
|
+
exportedAt: z.string(),
|
|
81
|
+
memories: z
|
|
82
|
+
.array(z.object({
|
|
83
|
+
id: z.string().regex(/^[a-f0-9]{8,32}$/, "invalid id"),
|
|
84
|
+
type: MemoryType,
|
|
85
|
+
content: z.string().min(1),
|
|
86
|
+
scope: z.string().refine(isSafeScope, { message: "scope must not contain '..'" }),
|
|
87
|
+
tags: z.array(z.string()),
|
|
88
|
+
importance: z.number().int().min(1).max(5),
|
|
89
|
+
createdAt: z.string(),
|
|
90
|
+
updatedAt: z.string(),
|
|
91
|
+
source: z.string().optional(),
|
|
92
|
+
lastSeen: z.string().optional(),
|
|
93
|
+
archivedAt: z.string().optional(),
|
|
94
|
+
embedding: z.array(z.number()).optional(),
|
|
95
|
+
}))
|
|
96
|
+
.max(100_000),
|
|
11
97
|
});
|
|
12
98
|
//# sourceMappingURL=types.js.map
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,6CAA6C;AAC7C,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/docs/chatgpt.md
CHANGED
|
@@ -19,7 +19,9 @@ curl http://localhost:8787/health
|
|
|
19
19
|
# {"status": "ok"}
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
- `REMEMBRA_API_KEY` **
|
|
22
|
+
- `REMEMBRA_API_KEY` **mandatory for public exposure** — without it the server
|
|
23
|
+
binds to `127.0.0.1` only, and a non-loopback `REMEMBRA_HOST` without a key
|
|
24
|
+
refuses to start (default-deny, see [security.md](security.md)).
|
|
23
25
|
- The server binds to localhost by default. For ChatGPT to reach it you must expose it
|
|
24
26
|
publicly (see [§4](#4-exposing-the-server)).
|
|
25
27
|
|
package/docs/clients.md
CHANGED
|
@@ -90,6 +90,9 @@ REMEMBRA_API_KEY="your-secret" remembra --http
|
|
|
90
90
|
| `REMEMBRA_EMBEDDINGS` | `none` | Semantic search: `openai` \| `ollama` \| `none` |
|
|
91
91
|
| `REMEMBRA_ARCHIVE_AFTER_DAYS` | `90` | Unused active memory → archived |
|
|
92
92
|
| `REMEMBRA_ARCHIVE_TTL_DAYS` | `365` | Archived memory → deleted |
|
|
93
|
+
| `REMEMBRA_HOST` | *(see security.md)* | HTTP bind address (loopback without key) |
|
|
94
|
+
| `REMEMBRA_MAX_BODY` | `10485760` | Max HTTP request body bytes |
|
|
95
|
+
| `REMEMBRA_DEBUG` | *(unset)* | `1` logs the storage root path at startup (off by default: log hygiene) |
|
|
93
96
|
|
|
94
97
|
LLM/embedding key setup: see **[providers.md](providers.md)**.
|
|
95
98
|
|
package/docs/security.md
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Security & Trust Model
|
|
2
|
+
|
|
3
|
+
Remembra v3.1.0 hardening notes — what's protected, what's a trust decision,
|
|
4
|
+
and how to deploy safely.
|
|
5
|
+
|
|
6
|
+
## Trust model
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
┌─ Trusted ─────────────────────────────────────────────┐
|
|
10
|
+
│ You / your MCP clients (OpenCode, Claude Code, ...) │
|
|
11
|
+
│ The LLM you configure for digest/merge │
|
|
12
|
+
│ Files under REMEMBRA_HOME (local filesystem trust) │
|
|
13
|
+
└───────────────────────────────────────────────────────┘
|
|
14
|
+
┌─ Semi-trusted ────────────────────────────────────────┐
|
|
15
|
+
│ Transcripts passed to memory_digest │
|
|
16
|
+
│ Anyone holding your REMEMBRA_API_KEY │
|
|
17
|
+
└───────────────────────────────────────────────────────┘
|
|
18
|
+
┌─ Untrusted ───────────────────────────────────────────┐
|
|
19
|
+
│ Network peers (if HTTP is exposed) │
|
|
20
|
+
│ Memory content written by other users of shared AI │
|
|
21
|
+
└───────────────────────────────────────────────────────┘
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Role memories are instructions — the prompt-injection surface
|
|
25
|
+
|
|
26
|
+
`role` memories always surface (+1000) and are meant to be **followed**, not
|
|
27
|
+
merely recalled. That is the feature — and the risk:
|
|
28
|
+
|
|
29
|
+
- **Via MCP**: whoever can call `memory_store` in your session is already the
|
|
30
|
+
agent you're running. No additional boundary is crossed.
|
|
31
|
+
- **Via HTTP**: anyone with your API key can plant a global `role` that every
|
|
32
|
+
future session will receive as an instruction.
|
|
33
|
+
|
|
34
|
+
**Rules:**
|
|
35
|
+
1. Never expose HTTP without `REMEMBRA_API_KEY` (enforced — see below).
|
|
36
|
+
2. Treat the API key as root access to your assistant's standing instructions.
|
|
37
|
+
3. Audit roles periodically: `memory_list { type: "role", includeArchived: true }`.
|
|
38
|
+
4. If you ingest transcripts from other people (shared ChatGPT GPT, public bot),
|
|
39
|
+
remember the digest LLM can extract `role` items from *their* text — review
|
|
40
|
+
before trusting a multi-user deployment.
|
|
41
|
+
|
|
42
|
+
Retrieved memories should be treated as **data with provenance**, not commands
|
|
43
|
+
— but Remembra cannot enforce how the consuming model interprets them.
|
|
44
|
+
|
|
45
|
+
## Enforced protections (3.1.0)
|
|
46
|
+
|
|
47
|
+
| Protection | Mechanism |
|
|
48
|
+
|------------|-----------|
|
|
49
|
+
| **Directory traversal (P0)** | Scopes containing `..` rejected by Zod (`StoreInput`/`DigestInput`) + `fileFor()` re-verifies the resolved path stays under `REMEMBRA_HOME` (defense in depth) |
|
|
50
|
+
| **Default-deny HTTP** | Without `REMEMBRA_API_KEY`: binds `127.0.0.1` only. Non-loopback `REMEMBRA_HOST` without a key → **refuses to start** |
|
|
51
|
+
| **Timing-safe auth** | API keys compared with `crypto.timingSafeEqual` |
|
|
52
|
+
| **Body size limit** | 10 MiB default (`REMEMBRA_MAX_BODY`), `413` on excess — pre-checks `Content-Length` and enforces while streaming |
|
|
53
|
+
| **Digest validation** | `DigestInput` Zod schema on both MCP and HTTP paths |
|
|
54
|
+
| **Atomic writes** | temp file + `rename()` (POSIX-atomic) — no half-written memories after a crash |
|
|
55
|
+
| **ID collisions** | 12-hex IDs (2⁴⁸) + existence check on store |
|
|
56
|
+
| **Content-Length** | Set on every response |
|
|
57
|
+
|
|
58
|
+
## Deployment checklist
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
# Local (safe default — loopback, key optional)
|
|
62
|
+
remembra --http
|
|
63
|
+
|
|
64
|
+
# Public (ChatGPT etc.) — key is mandatory
|
|
65
|
+
export REMEMBRA_API_KEY="$(openssl rand -hex 32)"
|
|
66
|
+
export REMEMBRA_HOST=0.0.0.0
|
|
67
|
+
remembra --http
|
|
68
|
+
# → put TLS in front (Caddy/nginx/cloudflared). Remembra speaks plain HTTP.
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
- [ ] `REMEMBRA_API_KEY` set with a long random value (≥32 bytes)
|
|
72
|
+
- [ ] TLS terminator in front for any non-loopback exposure
|
|
73
|
+
- [ ] `REMEMBRA_HOME` lives on a filesystem you back up — use
|
|
74
|
+
`remembra export <file>.json` for portable snapshots (or `rsync`/git the
|
|
75
|
+
directory)
|
|
76
|
+
- [ ] Periodic `memory_list {type: "role"}` audit
|
|
77
|
+
- [ ] LLM/embedding keys scoped to least privilege
|
|
78
|
+
|
|
79
|
+
## Known non-goals (current version)
|
|
80
|
+
|
|
81
|
+
- **No multi-tenancy** — one store per installation; scope isolates *projects*,
|
|
82
|
+
not *users*. Never share one instance between mutually untrusting users.
|
|
83
|
+
- **No encryption at rest** — files are plaintext markdown (by design: you can
|
|
84
|
+
read and edit them). Use filesystem-level encryption if needed.
|
|
85
|
+
- **No PII redaction** — what you store is what's written to disk.
|
|
86
|
+
- **No write locking / journal** — single-writer assumption; concurrent writers
|
|
87
|
+
from multiple machines are unsupported (atomic writes + the digest lock
|
|
88
|
+
protect against crashes and same-process races, not cross-machine
|
|
89
|
+
interleaving). `remembra export` for backups across machines.
|
package/docs/tools.md
CHANGED
|
@@ -88,6 +88,17 @@ vectors. Roles never decay. Takes no arguments. See [lifecycle.md](lifecycle.md)
|
|
|
88
88
|
Returns counts + affected ids. Also available as `POST /maintain` and the
|
|
89
89
|
`remembra maintain` CLI command.
|
|
90
90
|
|
|
91
|
+
## CLI commands
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
remembra maintain # decay sweep + vector backfill (one-shot, prints JSON)
|
|
95
|
+
remembra export <file>.json # full backup snapshot incl. archived memories
|
|
96
|
+
remembra import <file>.json # restore; validates whole file first (atomic), idempotent
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Import skips existing ids and exact-duplicate contents, so running it twice —
|
|
100
|
+
or importing into a machine that already has the data — is always safe.
|
|
101
|
+
|
|
91
102
|
---
|
|
92
103
|
|
|
93
104
|
## Suggested session flow
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hilbras/remembra",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.0",
|
|
4
4
|
"description": "External memory for AI assistants — remember facts, decisions, roles and history across sessions. MCP server for OpenCode, Claude Code, Cline, Kimi Code and more.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|