Record relative paths, file sizes and SHA-256 digests so a later copy can be compared with the original.
Walk a trusted local folder in deterministic path order and hash each regular file in small chunks. Symlink files and directories are reported and skipped. File size and modification time are checked before and after reading, while file and byte limits bound the workload. The output is a JSON integrity inventory.
Before you start
Python 3.10 or later; standard library only.
A trusted, stable local folder that is not a symlink.
Up to 10,000 files, 64 MiB per file and 256 MiB total; skipped links are bounded too.
Build and check
Choose a folder and stop changes while taking the inventory.
Write manifest.json outside the scanned folder so it is not included in a future inventory.
Repeat for a later copy and compare relative paths, byte sizes and SHA-256 values.
Limits to understand
An integrity inventory is not a backup, signature or proof of authenticity. It does not restore files.
Symlinks are skipped; permissions, empty directories and extended attributes are not recorded.
The stable-folder assumption matters. This is not a locked filesystem snapshot or protection against an adversary changing paths while they are read.
INSPECT THE SCRIPT
Inspect, copy and run the documented checks.
This browser preview builds code locally. It does not contact an API or run the code.
Default example ready.
Python example
# SPDX-License-Identifier: MIT
# Copyright (c) 2026 OpenViST contributors
"""Record relative paths, sizes and SHA-256 hashes for a trusted local folder."""
import argparse
import hashlib
import json
import os
from pathlib import Path
import sys
MAX_FILES = 10000
MAX_FILE_BYTES = 64 * 1024 * 1024
MAX_TOTAL_BYTES = 256 * 1024 * 1024
def manifest(folder):
root = Path(folder)
if root.is_symlink() or not root.is_dir():
raise ValueError("input must be a real folder, not a symlink")
root = root.resolve()
paths, skipped = [], []
def cannot_walk(error):
raise error
for directory, dirs, files in os.walk(root, followlinks=False, onerror=cannot_walk):
base = Path(directory)
dirs.sort()
for name in list(dirs):
path = base / name
if path.is_symlink():
skipped.append(path.relative_to(root).as_posix())
dirs.remove(name)
for name in sorted(files):
path = base / name
if path.is_symlink():
skipped.append(path.relative_to(root).as_posix())
elif path.is_file():
paths.append(path)
else:
raise ValueError("non-regular file: " + path.relative_to(root).as_posix())
if len(paths) > MAX_FILES or len(skipped) > MAX_FILES:
raise ValueError("folder exceeds the file or skipped-link limit")
records, total = [], 0
for path in sorted(paths, key=lambda p: p.relative_to(root).as_posix()):
flags = os.O_RDONLY | getattr(os, "O_NOFOLLOW", 0)
with os.fdopen(os.open(path, flags), "rb") as stream:
before = os.fstat(stream.fileno())
if before.st_size > MAX_FILE_BYTES or total + before.st_size > MAX_TOTAL_BYTES:
raise ValueError("folder exceeds the byte limits")
digest, size = hashlib.sha256(), 0
while True:
block = stream.read(65536)
if not block:
break
size += len(block)
if size > MAX_FILE_BYTES or total + size > MAX_TOTAL_BYTES:
raise ValueError("file grew beyond the byte limits")
digest.update(block)
after = os.fstat(stream.fileno())
if size != before.st_size or after.st_size != before.st_size or after.st_mtime_ns != before.st_mtime_ns:
raise ValueError("a file changed while being read")
total += size
records.append({"path": path.relative_to(root).as_posix(), "bytes": size, "sha256": digest.hexdigest()})
return {"files": records, "total_bytes": total, "skipped_symlinks": sorted(skipped)}
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("folder")
args = parser.parse_args()
try:
print(json.dumps(manifest(args.folder), ensure_ascii=False, indent=2))
except (OSError, ValueError) as error:
print(f"error: {error}", file=sys.stderr)
return 2
return 0
if __name__ == "__main__":
sys.exit(main())
What to verify after running
Compare the digest of a known small file and an empty file.
Confirm deterministic ordering, nested relative paths and skipped symlinks.
Missing folders and count or byte-limit violations must return a nonzero exit status.
Request or format preview
Illustrative preview. No provider response, price or balance is fetched.
Code license: MIT. Keep the license notice with your copy.
Example version 1.0.0 · Documentation reviewed 2026-10-06.
Executed locally with deterministic fixtures and CLI checks on Python 3.12.14. The three scripts pass 22 tests covering valid output, invalid input and size limits. No network or provider call was made.
Related browser tools
Inspect related data locally in your browser. These tools have their own input rules and limits; they do not execute this recipe.