memdebug 0.2.0__tar.gz → 0.4.0__tar.gz
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.
- memdebug-0.4.0/.github/ISSUE_TEMPLATE/bug_report.yml +87 -0
- memdebug-0.4.0/.github/pull_request_template.md +24 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/.gitignore +4 -0
- memdebug-0.4.0/CHANGELOG.md +82 -0
- memdebug-0.4.0/CONTRIBUTING.md +101 -0
- memdebug-0.4.0/PKG-INFO +356 -0
- memdebug-0.4.0/README.md +323 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/ROADMAP.md +19 -14
- {memdebug-0.2.0 → memdebug-0.4.0}/docs/agents.md +3 -2
- memdebug-0.4.0/docs/demo.cast +21 -0
- memdebug-0.4.0/docs/demo.gif +0 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/docs/releasing.md +4 -3
- {memdebug-0.2.0 → memdebug-0.4.0}/docs/threat-model.md +38 -1
- {memdebug-0.2.0 → memdebug-0.4.0}/pyproject.toml +1 -1
- {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/__init__.py +1 -1
- memdebug-0.4.0/src/memdebug/adapters/fileops.py +190 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/adapters/folder.py +5 -0
- memdebug-0.4.0/src/memdebug/adapters/folder_restore.py +275 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/adapters/restore.py +5 -159
- {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/agents.py +3 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/cli.py +103 -12
- {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/monitor.py +54 -7
- {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/paths.py +5 -0
- memdebug-0.4.0/src/memdebug/provenance.py +251 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/rollback_flow.py +12 -3
- {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/selftest.py +253 -63
- {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/viewer/pages.py +112 -4
- {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/viewer/server.py +4 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/viewer/style.py +1 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/tests/test_agents.py +11 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/tests/test_demo.py +9 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/tests/test_docker_source.py +3 -1
- memdebug-0.4.0/tests/test_folder_restore.py +473 -0
- memdebug-0.4.0/tests/test_folder_rollback_cli.py +235 -0
- memdebug-0.4.0/tests/test_provenance.py +245 -0
- memdebug-0.4.0/tests/test_provenance_check.py +197 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/tests/test_release_hygiene.py +2 -1
- {memdebug-0.2.0 → memdebug-0.4.0}/tests/test_restore.py +3 -3
- memdebug-0.4.0/tests/test_selftest_tripwire.py +340 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/tests/test_viewer.py +13 -1
- memdebug-0.4.0/tests/test_viewer_agents.py +81 -0
- memdebug-0.4.0/tests/test_viewer_putback.py +257 -0
- memdebug-0.2.0/CHANGELOG.md +0 -33
- memdebug-0.2.0/CONTRIBUTING.md +0 -57
- memdebug-0.2.0/PKG-INFO +0 -206
- memdebug-0.2.0/README.md +0 -173
- {memdebug-0.2.0 → memdebug-0.4.0}/.gitattributes +0 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/.github/dependabot.yml +0 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/.github/workflows/ci.yml +0 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/.github/workflows/release.yml +0 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/LICENSE +0 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/NOTICE +0 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/SECURITY.md +0 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/__main__.py +0 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/adapters/__init__.py +0 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/adapters/base.py +0 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/adapters/common.py +0 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/adapters/markdown_git.py +0 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/adapters/mem0.py +0 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/adapters/openwebui.py +0 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/backends.py +0 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/demo.py +0 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/describe.py +0 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/diff.py +0 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/docker_source.py +0 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/errors.py +0 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/hints.py +0 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/ledger.py +0 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/models.py +0 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/reconcile.py +0 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/report.py +0 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/stores.py +0 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/sync.py +0 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/textsafe.py +0 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/viewer/__init__.py +0 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/viewer/html.py +0 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/viewer/redline.py +0 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/witness.py +0 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/tests/conftest.py +0 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/tests/payloads.py +0 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/tests/test_cli.py +0 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/tests/test_diff.py +0 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/tests/test_folder_and_openwebui.py +0 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/tests/test_hints.py +0 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/tests/test_ledger.py +0 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/tests/test_ledger_read.py +0 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/tests/test_markdown_git.py +0 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/tests/test_mem0_adapter.py +0 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/tests/test_models.py +0 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/tests/test_platform.py +0 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/tests/test_provenance_openwebui.py +0 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/tests/test_readable_ids_and_honest_wording.py +0 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/tests/test_reconcile.py +0 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/tests/test_report.py +0 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/tests/test_rollback_cli.py +0 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/tests/test_snapshots.py +0 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/tests/test_stores_and_monitor.py +0 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/tests/test_sync.py +0 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/tests/test_textsafe.py +0 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/tests/test_viewer_html.py +0 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/tests/test_viewer_redline.py +0 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/tests/test_viewer_rollback.py +0 -0
- {memdebug-0.2.0 → memdebug-0.4.0}/tests/test_witness.py +0 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
name: Bug report
|
|
2
|
+
description: Something does not work as the documentation says
|
|
3
|
+
labels: ["bug"]
|
|
4
|
+
body:
|
|
5
|
+
- type: markdown
|
|
6
|
+
attributes:
|
|
7
|
+
value: |
|
|
8
|
+
## Please read this first
|
|
9
|
+
|
|
10
|
+
**Never paste memory contents or session transcripts into this issue.** An agent's memory and its conversation logs hold private text,
|
|
11
|
+
and an issue is public and stays public even if you edit or delete it. Describe what the text *did*, or replace it with a made-up
|
|
12
|
+
example that behaves the same way.
|
|
13
|
+
|
|
14
|
+
Also leave out: the ledger (`*.db`), `stores.json`, backup folders, the link printed by `memdebug serve` (it contains a secret),
|
|
15
|
+
API keys, and personal paths (replace them with `<home>`). Be careful with the output of `memdebug check`, `timeline`, `diff` and
|
|
16
|
+
`report`: it can quote the start of changed notes.
|
|
17
|
+
|
|
18
|
+
**A security problem?** Do not open an issue. Report it privately: https://github.com/juraj-jumic/memdebug/security/advisories/new
|
|
19
|
+
(see [SECURITY.md](https://github.com/juraj-jumic/memdebug/blob/main/SECURITY.md)).
|
|
20
|
+
- type: checkboxes
|
|
21
|
+
id: safe
|
|
22
|
+
attributes:
|
|
23
|
+
label: Before you submit
|
|
24
|
+
options:
|
|
25
|
+
- label: I have not included memory contents, session transcripts, a ledger, `stores.json`, a viewer link or personal paths.
|
|
26
|
+
required: true
|
|
27
|
+
- label: This is not a security problem.
|
|
28
|
+
required: true
|
|
29
|
+
- type: textarea
|
|
30
|
+
id: happened
|
|
31
|
+
attributes:
|
|
32
|
+
label: What happened
|
|
33
|
+
description: What you did, and what memdebug did.
|
|
34
|
+
validations:
|
|
35
|
+
required: true
|
|
36
|
+
- type: textarea
|
|
37
|
+
id: expected
|
|
38
|
+
attributes:
|
|
39
|
+
label: What you expected
|
|
40
|
+
validations:
|
|
41
|
+
required: true
|
|
42
|
+
- type: textarea
|
|
43
|
+
id: steps
|
|
44
|
+
attributes:
|
|
45
|
+
label: Steps to reproduce
|
|
46
|
+
description: The commands you ran, ideally against a made-up folder of notes. `memdebug demo` shows what a healthy run looks like.
|
|
47
|
+
- type: input
|
|
48
|
+
id: version
|
|
49
|
+
attributes:
|
|
50
|
+
label: memdebug version
|
|
51
|
+
description: The output of `memdebug --version`. If you use pipx and a virtual environment, make sure it is the copy you actually ran.
|
|
52
|
+
validations:
|
|
53
|
+
required: true
|
|
54
|
+
- type: dropdown
|
|
55
|
+
id: system
|
|
56
|
+
attributes:
|
|
57
|
+
label: Operating system
|
|
58
|
+
options:
|
|
59
|
+
- Windows
|
|
60
|
+
- macOS
|
|
61
|
+
- Linux
|
|
62
|
+
- Other
|
|
63
|
+
validations:
|
|
64
|
+
required: true
|
|
65
|
+
- type: input
|
|
66
|
+
id: tools
|
|
67
|
+
attributes:
|
|
68
|
+
label: Python and git versions
|
|
69
|
+
description: The output of `python --version` and `git --version`.
|
|
70
|
+
- type: dropdown
|
|
71
|
+
id: store
|
|
72
|
+
attributes:
|
|
73
|
+
label: Kind of store involved
|
|
74
|
+
multiple: true
|
|
75
|
+
options:
|
|
76
|
+
- Markdown notes in git
|
|
77
|
+
- Plain folder of markdown notes
|
|
78
|
+
- Open WebUI
|
|
79
|
+
- Mem0
|
|
80
|
+
- The viewer (memdebug serve)
|
|
81
|
+
- Not about a store
|
|
82
|
+
- type: textarea
|
|
83
|
+
id: output
|
|
84
|
+
attributes:
|
|
85
|
+
label: Output or error message
|
|
86
|
+
description: Only text you have read through and are sure holds no memory contents. The output of `memdebug selftest` is useful here, but it names your user folder on Windows, so replace that with `<home>` first.
|
|
87
|
+
render: text
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
## What this changes, and why
|
|
2
|
+
|
|
3
|
+
<!-- One or two sentences. Link the issue if there is one. -->
|
|
4
|
+
|
|
5
|
+
## How it was checked
|
|
6
|
+
|
|
7
|
+
Run these with the repository's own environment (see [CONTRIBUTING.md](https://github.com/juraj-jumic/memdebug/blob/main/CONTRIBUTING.md)):
|
|
8
|
+
|
|
9
|
+
- [ ] `python -m pytest -q -n auto`, `ruff check src tests`, `mypy` and `python -m memdebug selftest` all pass
|
|
10
|
+
- [ ] If this adds or changes a protection: I removed it on purpose and a test failed (CONTRIBUTING, ground rule 5). Otherwise: no protection changed
|
|
11
|
+
- [ ] Hostile input is handled: text read from a store, log or repository is bounded, shown through `safe_text` or the viewer's builder, and any name from the ledger is validated (validators end with `\Z`, never `$`) before it reaches a command or a path
|
|
12
|
+
- [ ] Windows is covered: file names that differ only by case, CRLF, and junctions are considered; a POSIX-only test is marked skipped with a reason
|
|
13
|
+
|
|
14
|
+
## Documents
|
|
15
|
+
|
|
16
|
+
- [ ] `CHANGELOG.md` has a line under `[Unreleased]` for anything a user would notice
|
|
17
|
+
- [ ] Anything the change makes untrue is updated, and a new limit is written into `docs/threat-model.md`
|
|
18
|
+
|
|
19
|
+
## Private data
|
|
20
|
+
|
|
21
|
+
- [ ] The diff and the commit messages contain no memory text, session transcripts, `*.db` files, `stores.json`, `backups/`, `copies/` or personal paths. Test data is made up
|
|
22
|
+
- [ ] I understand the history is public. A GitHub no-reply address is fine as the commit email
|
|
23
|
+
|
|
24
|
+
By contributing you agree that your contribution is licensed under the Apache License 2.0 (see [LICENSE](https://github.com/juraj-jumic/memdebug/blob/main/LICENSE)).
|
|
@@ -26,7 +26,11 @@ htmlcov/
|
|
|
26
26
|
*.partial
|
|
27
27
|
stores.json
|
|
28
28
|
copies/
|
|
29
|
+
backups/
|
|
29
30
|
memdebug-report*
|
|
31
|
+
# Rollback backups hold the notes byte for byte, and agent session logs are whole conversations. If a test ever needs a made-up .jsonl file,
|
|
32
|
+
# add a negation for that one path (!tests/data/example.jsonl) instead of removing the rule.
|
|
33
|
+
*.jsonl
|
|
30
34
|
|
|
31
35
|
# Secrets
|
|
32
36
|
.env
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes. The format follows [Keep a Changelog](https://keepachangelog.com/); versions follow [Semantic Versioning](https://semver.org/)
|
|
4
|
+
(alpha: anything may change before 1.0).
|
|
5
|
+
|
|
6
|
+
## [0.4.0] - 2026-10-09
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
- **Who wrote it.** For a changed note that looks suspicious, or that changed outside git, `memdebug check` and `memdebug watch` now also say which
|
|
10
|
+
logged Claude Code session wrote it, from that agent's session logs ("who wrote it: ..."), or that no logged edit explains it. It returns only a
|
|
11
|
+
session id, a time and a tool name, never a path or any text from a log. It is evidence, not proof: a deleted log looks the same as a note nobody
|
|
12
|
+
wrote, and shell-made changes cannot be matched (they are only counted). See `docs/threat-model.md`.
|
|
13
|
+
- The viewer has an **Agents** page (`memdebug serve`): the known agents that appear to be installed and what memdebug could watch for each, with the command
|
|
14
|
+
to run (`memdebug setup`). It only checks that folders exist, opens no file, and works even while the ledger is busy.
|
|
15
|
+
- Cline is in the agent catalog: `memdebug agents` and `memdebug setup` offer its global rules folder (`~/Documents/Cline/Rules`) once it holds markdown rules.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
- `memdebug selftest`, "rollback is safe" and "hostile repository config cannot run programs": the control (ordinary git, tripwires armed) now runs in
|
|
19
|
+
a separate repository with its own marker files, so nothing it leaves behind can be mistaken for an escape from the protected run, which is still
|
|
20
|
+
asserted to run no tripwire. The tripwires now record what started them (time, arguments, and the parent and grandparent command lines where the
|
|
21
|
+
platform shows them: `/proc` on Linux, `ps` on macOS, nothing on Windows), and a failure prints those records and `git --version`. This follows a
|
|
22
|
+
failure on CI that passed on a re-run and could not be reproduced.
|
|
23
|
+
- The repository's ignore file also excludes `backups/` and `*.jsonl` (session logs), and the release hygiene test checks both rules.
|
|
24
|
+
|
|
25
|
+
### Documentation
|
|
26
|
+
- The README has badges, a recorded demo (`docs/demo.gif`, with the unpaced recording in `docs/demo.cast`), and new "Why this matters", "How it works" and
|
|
27
|
+
"Engineering" sections, checked against the code; claims about snapshots, the ledger and rollback that overstated what the code does were corrected.
|
|
28
|
+
- CONTRIBUTING.md now starts with the private-data rule and covers reporting a security problem, setup, writing tests, changing the viewer and the pull
|
|
29
|
+
request process; its second ground rule names both rollback engines. There is a pull request template and a bug-report form that tells people never
|
|
30
|
+
to paste memory contents or session transcripts.
|
|
31
|
+
- `docs/threat-model.md` has a section on the session-log reader, and `docs/agents.md` has a row for Cline and records Claude Code's per-project memory
|
|
32
|
+
folder as confirmed on Windows 11.
|
|
33
|
+
|
|
34
|
+
## [0.3.0] - 2026-10-07
|
|
35
|
+
|
|
36
|
+
### Added
|
|
37
|
+
- **Rollback for plain folders.** `memdebug rollback store NAME --to s1` restores a watched folder of notes (and, by the same command, git notes) to a
|
|
38
|
+
snapshot: a dry run first, everything it replaces saved first byte for byte in a private folder next to the ledger, every write undone if a later
|
|
39
|
+
step fails, and the rollback recorded in the ledger and itself undoable. A folder is rebuilt from the snapshot's text, so line endings are LF (or CRLF
|
|
40
|
+
if the file it replaces uses CRLF throughout), and text a snapshot could not keep faithfully is skipped, never written.
|
|
41
|
+
- `memdebug selftest` has a new check, "folder rollback is safe", that proves the plain-folder rollback claims on your machine without needing git.
|
|
42
|
+
- `memdebug snapshot store NAME` saves a known-good copy of a watched store to roll back to. It refuses while the ledger holds an outside-history
|
|
43
|
+
change or flagged wording since the store's last snapshot, until you add `--include-changes`.
|
|
44
|
+
|
|
45
|
+
### Changed
|
|
46
|
+
- **The viewer** shows how to put a store back: snapshot pages, the compare page, the page of a change made outside a store's history and the
|
|
47
|
+
overview's "Needs a look" give the `memdebug rollback store` command as text to copy (the viewer itself still only reads). A store name from the
|
|
48
|
+
ledger is only put into a command if it is safe to paste; for an outside change the snapshot offered is always one taken before it. A rollback of a
|
|
49
|
+
plain folder is described as in place with a backup, not as a commit.
|
|
50
|
+
- The file-writing safety code of the git rollback engine (links, junctions, case clashes, atomic writes, undo) now lives in one shared module used by
|
|
51
|
+
both engines. No behaviour change.
|
|
52
|
+
- `memdebug demo` now ends by pointing at `memdebug setup`, the guided way to watch your own agents, instead of an advanced command.
|
|
53
|
+
- Documentation: the README explains installing from PyPI, with a route that works on Windows without pipx.
|
|
54
|
+
|
|
55
|
+
## [0.2.0] - 2026-10-07
|
|
56
|
+
|
|
57
|
+
### Added
|
|
58
|
+
- **Witness:** a second copy of the ledger's head kept elsewhere, so a rewritten or cut-short ledger is noticed (`memdebug witness`, `verify --witness`).
|
|
59
|
+
- **Reports** in Markdown, JSON and SARIF, with CI exit codes (`memdebug report`).
|
|
60
|
+
- **Guided use:** `setup`, `add`, `stores`, `remove`, `check`, `status`, `watch`, `agents`, and a registry of watched stores.
|
|
61
|
+
- **More stores:** plain folders of markdown, Open WebUI's memory (read from a copy of its database, or copied by memdebug itself from a Docker
|
|
62
|
+
container), and single-file memory for agents that keep credentials beside it (Gemini CLI, Codex CLI, Claude Code's global file).
|
|
63
|
+
- **An agent catalog** (Claude Code, OpenClaw, Gemini CLI, Codex CLI, Windsurf, Open WebUI) with locations taken from each agent's documentation.
|
|
64
|
+
- **Hints** ("worth a second look") for instructions to send data, remove confirmation or weaken safeguards, hidden characters and secret-like strings.
|
|
65
|
+
- **A first slice of provenance for Open WebUI:** the app's own `created_by` label and how close a chat was, as evidence only.
|
|
66
|
+
- `memdebug demo`, `memdebug --version`, Apache-2.0 licence, security policy, threat model, roadmap and contributing guide.
|
|
67
|
+
|
|
68
|
+
### Changed
|
|
69
|
+
- The release workflow re-downloads and re-checks the built files in a separate job before it can publish, so a dry run tests the whole hand-over.
|
|
70
|
+
- The viewer says plainly when a store keeps no history, instead of claiming every change went through one.
|
|
71
|
+
- Opaque ids (UUIDs) are shown short, with what the memory says next to them in `check`.
|
|
72
|
+
- Every pattern that validates an identifier now ends with a strict end-of-text anchor (a trailing newline used to slip through some of them).
|
|
73
|
+
|
|
74
|
+
### Fixed
|
|
75
|
+
- The witness reader accepts Windows line endings and a byte-order mark (git's autocrlf and some editors add them).
|
|
76
|
+
- Tests can no longer touch the Docker, home folder or data folder of the machine they run on.
|
|
77
|
+
|
|
78
|
+
## [0.1.0] - 2026-10-05
|
|
79
|
+
|
|
80
|
+
### Added
|
|
81
|
+
- Tamper-evident ledger with snapshots, `verify`, and compare; markdown/git and Mem0 stores; detection of changes that bypass a store's own history.
|
|
82
|
+
- A read-only browser viewer; rollback for markdown/git with backups, undo on failure and a recorded, undoable result; `selftest`.
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Thanks for helping. memdebug is a security tool, so the bar for changes is "what happens if the input is hostile?".
|
|
4
|
+
|
|
5
|
+
## Private data comes first
|
|
6
|
+
|
|
7
|
+
memdebug reads people's agent memory, so this repository must never hold any of it. Never commit, attach or paste (in an issue, a pull
|
|
8
|
+
request, a test or a commit message): real memory text, session transcripts, a ledger or any other `*.db` file, `stores.json`, backup or copy
|
|
9
|
+
folders, the link `memdebug serve` prints (it contains a secret), tokens or keys, or paths that name your user account. Test data is made up.
|
|
10
|
+
The ignore file keeps the common cases out (databases, `stores.json`, copy and backup folders, `*.jsonl` session logs, reports, keys), but it
|
|
11
|
+
is only a safety net: read `git status` and `git diff --cached` before every commit. The history is public, and a pushed commit cannot be
|
|
12
|
+
taken back without rewriting it.
|
|
13
|
+
|
|
14
|
+
## Reporting a security problem
|
|
15
|
+
|
|
16
|
+
Do not open a public issue or pull request for a security problem. Report it privately through GitHub
|
|
17
|
+
(https://github.com/juraj-jumic/memdebug/security/advisories/new); [SECURITY.md](SECURITY.md) says what counts and what to include. Keep private
|
|
18
|
+
text out of the report as well: a made-up example that behaves the same way is enough.
|
|
19
|
+
|
|
20
|
+
## Setup
|
|
21
|
+
|
|
22
|
+
python -m venv .venv
|
|
23
|
+
.venv\Scripts\activate # Windows (PowerShell or cmd); on macOS and Linux: source .venv/bin/activate
|
|
24
|
+
pip install -e ".[dev]"
|
|
25
|
+
python -m pytest -n auto # the whole suite, in parallel; plain `python -m pytest` works too
|
|
26
|
+
python -m memdebug selftest # proves the protections on THIS machine
|
|
27
|
+
ruff check src tests # lint (CI enforces it)
|
|
28
|
+
mypy # types (CI enforces it)
|
|
29
|
+
|
|
30
|
+
You need Python 3.10+ and git 2.31+. The suite starts many git processes, so it is slower on Windows. Run everything through this environment's
|
|
31
|
+
`python -m ...`: a `memdebug` command on your PATH (a pipx install, say) can be an older release than the code you are editing, while
|
|
32
|
+
`python -m memdebug` is always your checkout. Two release-hygiene tests are skipped unless PyYAML is installed (`pip install pyyaml`).
|
|
33
|
+
|
|
34
|
+
## Ground rules
|
|
35
|
+
|
|
36
|
+
1. **Everything read from a memory store or repository is untrusted.** Bound its size, never print it raw (use
|
|
37
|
+
`safe_text`), and never let it become markup (use the viewer's builder, never string-built HTML).
|
|
38
|
+
2. **Adapters only read.** The only code allowed to change a memory store is the two rollback engines, `adapters/restore.py` (git
|
|
39
|
+
notes) and `adapters/folder_restore.py` (plain folders), which share the file-writing code in `adapters/fileops.py`. They have their
|
|
40
|
+
own rules: a dry run first, a plan the person confirms, a backup of anything not already held (in git, or in a private backup folder),
|
|
41
|
+
no rewritten history, undo on failure.
|
|
42
|
+
3. **No shell, fixed argument lists, no programs named by the data.** git runs with a scrubbed environment and the
|
|
43
|
+
repository's risky settings overridden. Do not add a git command without checking it cannot run a hook, filter or
|
|
44
|
+
external helper.
|
|
45
|
+
4. **Explicit text encodings** everywhere (`encoding="utf-8"`); a test enforces this because Windows defaults to a legacy
|
|
46
|
+
code page.
|
|
47
|
+
5. **A protection without a test that fails when it is removed is not a protection.** Break your own code on purpose
|
|
48
|
+
(remove the check) and confirm a test notices. Several tests exist only because that exercise found a gap.
|
|
49
|
+
6. **Windows is a first-class platform.** Avoid POSIX-only assumptions; if a test truly needs POSIX, mark it and say why. Two traps that only show up
|
|
50
|
+
there: file names ignore case ("A.db" and "a.db" are one file, so never name files after values that can differ only by case), and text-mode
|
|
51
|
+
writes turn `\n` into `\r\n` (write bytes when exact content matters).
|
|
52
|
+
7. **Say what a feature does not do.** Add its limits to `docs/threat-model.md`.
|
|
53
|
+
|
|
54
|
+
## Writing tests
|
|
55
|
+
|
|
56
|
+
* Use made-up data only. Every test already gets an empty home folder and no Docker (`isolated_home` and `no_real_docker` in
|
|
57
|
+
`tests/conftest.py`), so never read a real home folder, agent folder, session log or container, and never put a real transcript in a
|
|
58
|
+
fixture. Write a made-up log, as `tests/test_provenance.py` does. A test that sets the home folder sets both `HOME` and `USERPROFILE`.
|
|
59
|
+
* A new protection needs a test that fails when it is removed (ground rule 5). A new validator or route pattern ends with `\Z`, never `$`,
|
|
60
|
+
and is added to `test_no_validator_accepts_a_trailing_line_break` in `tests/test_docker_source.py`, which tries every pattern with a
|
|
61
|
+
trailing line break.
|
|
62
|
+
* Give parametrized tests short `ids=`, and mark a POSIX-only test skipped with a reason (see `posix_only` in `tests/test_provenance.py`).
|
|
63
|
+
|
|
64
|
+
## Changing the viewer
|
|
65
|
+
|
|
66
|
+
The viewer (`memdebug serve`) only looks. It answers GET and HEAD only, sends no JavaScript, and cannot change a store or the ledger (the
|
|
67
|
+
theme switch sets one cookie and nothing else). Pages are built with the escaping builder in `src/memdebug/viewer/html.py`, never by joining
|
|
68
|
+
strings. The only form is the Compare picker, which uses `method="get"` and just navigates; a test fails if any form uses another method.
|
|
69
|
+
Where a person may want to act, show the command as text to paste, as the "put it back" guidance does, and build it only from names that pass
|
|
70
|
+
`valid_name`. A new page needs a route anchored with `\Z`, an entry in the trailing-line-break test, and an entry in `ALL_PAGES` in
|
|
71
|
+
`tests/test_viewer.py`. What is defended, and what is not: [docs/threat-model.md](docs/threat-model.md#the-viewer).
|
|
72
|
+
|
|
73
|
+
## Adding a backend
|
|
74
|
+
|
|
75
|
+
Implement the `MemoryAdapter` protocol in `adapters/base.py` (`list_memories`, `read_history`, `history`), read-only,
|
|
76
|
+
validating every row. Look at `adapters/mem0.py` and `adapters/markdown_git.py` for the expected hardening, and add the
|
|
77
|
+
same kinds of tests (hostile rows, oversized values, odd encodings). A backend without a change history should record
|
|
78
|
+
what it observes instead of raising "outside the history" alarms.
|
|
79
|
+
|
|
80
|
+
## Adding an agent to the catalog
|
|
81
|
+
|
|
82
|
+
See `docs/agents.md`: a documented location (with a link), a test using a fake home folder, and watch only the named files when the
|
|
83
|
+
folder also holds credentials.
|
|
84
|
+
|
|
85
|
+
## Workflows and Dependabot
|
|
86
|
+
|
|
87
|
+
Dependabot proposes updates to the pinned actions in one grouped pull request a week. Let CI run on it, and for changes to the release
|
|
88
|
+
workflow also run its dry run (docs/releasing.md) from the pull request's branch before merging.
|
|
89
|
+
|
|
90
|
+
## Releases
|
|
91
|
+
|
|
92
|
+
See `docs/releasing.md`. A test keeps the version, the changelog, the licence files and the links in the documents in agreement.
|
|
93
|
+
|
|
94
|
+
## Pull requests
|
|
95
|
+
|
|
96
|
+
Keep them focused (one topic), include tests, update the docs that your change makes untrue, and run the whole suite; the pull request
|
|
97
|
+
template lists what to check. Add a line under `[Unreleased]` in `CHANGELOG.md` for anything a user would notice, and write a new limit into
|
|
98
|
+
`docs/threat-model.md`. Do not bump the version or rename the changelog's heading: that is part of a release. The required checks must pass
|
|
99
|
+
before a pull request can merge, and only the maintainer creates release tags (the maintainer can bypass both rules, so they guard against
|
|
100
|
+
mistakes, not against the maintainer). Commits stay public for good, so use a GitHub no-reply address as your commit email if you would
|
|
101
|
+
rather not publish your own. By contributing you agree that your contribution is licensed under the Apache License 2.0 (see LICENSE).
|