Thanks for considering it. Read SECURITY.md first — there is a short list of contributions this project declines on principle, and it is better to know before you write the code than after.
git clone https://github.com/recoverkit/recoverkit
cd recoverkit
uv sync --all-extras # or: pip install -e ".[dev]"
uv run pytest # must pass with zero external binaries installed
uv run ruff check . && uv run ruff format --check .
uv run mypy srcThe test suite deliberately requires no external tools. Every subprocess call
goes through external.py, and tests/fakebin/ replays recorded output for
adb, photorec, pymobiledevice3 and the rest. If a change to a wrapper
cannot be tested without installing the real binary, the wrapper is doing too
much — move the logic out of the subprocess boundary.
acquire/ is the only code allowed to touch a device. Everything else operates
on a case bundle on disk.
That is what makes the project testable without hardware, lets someone analyse an
image acquired by a completely different tool, and keeps the fake-binary harness
down to a single seam. A pull request that reaches for a device from recover/,
artifacts/ or report/ will be sent back regardless of how well it works.
Modules follow the iLEAPP __artifacts_v2__ convention exactly, on purpose.
A module written here is a valid iLEAPP module and vice versa, which means we can
adopt any of their ~700 parsers by copying one file, and our deleted-data modules
can go upstream unchanged. Inventing our own format would buy nothing and cost
that permanently. See docs/UPSTREAM.md.
One file per artifact, in src/recoverkit/artifacts/<platform>/:
__artifacts_v2__ = {
"photos_trashed": {
"name": "Photos - Recently Deleted",
"description": "Assets flagged as trashed but not yet purged",
"author": "@yourhandle",
"creation_date": "2026-08-05",
"last_update_date": "2026-08-05",
"requirements": "none",
"category": "Photos",
"notes": "30 day retention. ZTRASHEDDATE drives the expiry countdown.",
"paths": ("*/PhotoData/Photos.sqlite*",),
"output_types": "standard",
"artifact_icon": "trash-2",
}
}
@artifact_processor
def photos_trashed(context):
...
return data_headers, data_list, source_pathRequirements for a module to be merged:
- Return headers that match
RecoveredItemfield names. The ledger maps by name; unknown headers land inrawrather than being dropped. - Set
methodon every row. The method is the provenance and it determines the confidence tier. Never setconfidenceyourself — it is derived. See docs/CONFIDENCE.md. - Pull
.db,-waland-shmtogether and never let SQLite checkpoint a database you are analysing. A checkpoint destroys exactly the evidence you came for. Use the read-only helpers inrecoverkit.sqlite_ro, not baresqlite3.connect. - Ship a golden test.
tests/corpus/golden/<module>/input/plusexpected.json. CI fails a new module without one. This is the contract that keeps a repository of thirty modules maintainable. - Fail loudly, never quietly wrong. If a field cannot be parsed, report it as unparsed. A message body attributed to the wrong person is worse than a gap.
- Files 200–400 lines, 800 hard maximum. Functions under 50.
pathlibeverywhere. Never join paths with strings, never assume/, never assume case sensitivity, nevershell=True. Windows is a supported platform.- Type annotations on everything public;
mypy --strictpasses. - Comments explain constraints the code cannot show — a retention window, a schema quirk, why a database must not be checkpointed. Not what the next line does.
- Prefer reusing an existing maintained tool over writing a new one, and say so in the pull request when you do. Half the value of this project's design is what it refuses to reimplement.
Answer these in the pull request:
- What does it do that we cannot do in a few lines of standard library?
- What is its licence, and is it maintained?
- Import or subprocess? Anything under GPL is subprocess-only — see the
licence boundary section in docs/ARCHITECTURE.md.
Adding a GPL package to
dependenciesbreaks the project's licensing position. - Does it need a compiler? If so it belongs in an extra, not the base install.
Then update THIRD_PARTY_LICENSES.md — CI checks it for drift.
Dependencies without a licence file are unusable regardless of quality, because "no licence" means all rights reserved. Two well-known tools in this space are affected; they are listed in THIRD_PARTY_LICENSES.md under "Deliberately not used" so nobody has to rediscover it.
Conventional commits: feat:, fix:, docs:, refactor:, test:, chore:,
perf:, ci:. Explain why in the body when the reason is not obvious.
Sign off your commits (git commit -s) to certify the
Developer Certificate of Origin.
Never commit real evidence. Not a backup, not a database, not a screenshot of
someone's messages. .gitignore blocks the common shapes, but it cannot catch
everything. Fixtures must be synthetic and reproducible from a generator, or come
with the script that built them and its hash — see
docs/TEST_CORPUS.md.
The method-to-confidence table is a claim that people may rely on outside this project, possibly in proceedings. Changing it can retroactively alter how an older report should be read.
So: it changes only in a versioned release, only with the rationale written into
docs/CONFIDENCE.md, and only with a loud entry in CHANGELOG.md. It is frozen
alongside the ledger schema_version. This is not bureaucracy for its own sake —
it is the difference between a number that means something and a number that does not.