OpenViSTAI · Agents · Blockchain · Crypto
Menu
OPENVIST RECIPES

Create a SHA-256 manifest for a local folder

Python

Record relative paths, file sizes and SHA-256 digests so a later copy can be compared with the original.

Python code excerpt: Create a SHA-256 manifest for a local folder. Version 1.0.0. Read the full recipe for code, sources and recorded test scope.

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

  1. Choose a folder and stop changes while taking the inventory.
  2. Run: python hash_manifest.py folder > manifest.json
  3. Write manifest.json outside the scanned folder so it is not included in a future inventory.
  4. 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.

Change a setting to inspect the preview.

Official sources and related profiles

    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.