affaan-m/ECC · error · ContractError

secure receipt validation requires O_NOFOLLOW

Error message

secure receipt validation requires O_NOFOLLOW

What it means

_sha256 refuses to hash receipt files unless the platform's os module supports O_NOFOLLOW, the open() flag that prevents following symlinks. This is a TOCTOU/security guard: hashing a symlinked path could be redirected to attacker-controlled content between check and hash. On platforms without O_NOFOLLOW (older/non-POSIX systems, notably Windows historically), secure validation is unavailable and the library fails closed.

Solutions

  1. Run receipt validation on Linux/macOS where os.O_NOFOLLOW exists
  2. Upgrade the OS or Python build so os.O_NOFOLLOW is available
  3. If the platform is fixed and security is not required, use a non-secure hash path instead of validate_artifact_receipt's secure mode
  4. Add a capability check in your tooling before invoking secure validation: hasattr(os, 'O_NOFOLLOW')

Example fix

// before (script ran on Windows and raised)
validate_artifact_receipt(bundle_root)  # requires O_NOFOLLOW
// after
import os
if hasattr(os, 'O_NOFOLLOW'):
    validate_artifact_receipt(bundle_root)
else:
    print('secure validation unsupported on this platform; use a POSIX host')
Defensive patterns

Strategy: fallback

Validate before calling

import os
secure_ok = hasattr(os, 'O_NOFOLLOW')

Try / catch

try:
    validate_artifact_receipt(root)
except ContractError as e:
    if 'O_NOFOLLOW' in str(e):
        print('Secure validation unsupported on this platform; run on Linux/macOS')
    else:
        raise

Prevention

When it happens

Trigger: Calling validate_artifact_receipt on a system whose os.open lacks O_NOFOLLOW — typically Windows or very old Python/OS combos — while secure receipt validation is requested.

Common situations: Running the script on Windows or in an environment (older WSL setups, unusual embedded Python builds) where O_NOFOLLOW is not exposed; running with a Python version/platform combination that predates the flag.

Understand the failure class

Background: "unsupported platform" / "not supported on this platform" errors: what they mean and how to fix them — this error's family across 47 libraries.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/66fb69856ddfdad5. Report an issue: GitHub.

Appendix: source

Thrown at skills/taste-application/scripts/tasteforge/contract.py:72

    _validate_numeric_evidence(probe, label=label)
    if probe.get("duration") != source_duration:
        raise ContractError(f"{label} probe duration is not bound to source duration")
    for field in ("sample_times", "scene_changes"):
        values = probe.get(field, [])
        if not isinstance(values, list):
            raise ContractError(f"{label} has invalid {field}")
        for value in values:
            _validate_media_time(value, source_duration, label=f"{label} {field}")
    samples = probe.get("style_samples", [])
    if not isinstance(samples, list) or any(not isinstance(sample, dict) for sample in samples):
        raise ContractError(f"{label} has invalid style evidence")
    for sample in samples:
        _validate_media_time(sample.get("time"), source_duration, label=f"{label} style evidence")


def _sha256(path: Path) -> str:
    if not hasattr(os, "O_NOFOLLOW"):
        raise ContractError("secure receipt validation requires O_NOFOLLOW")
    descriptor = os.open(path, os.O_RDONLY | os.O_NOFOLLOW)
    digest = hashlib.sha256()
    try:
        metadata = os.fstat(descriptor)
        if not stat.S_ISREG(metadata.st_mode):
            raise ContractError(f"receipt source is not a regular file: {path}")
        while True:
            chunk = os.read(descriptor, 1024 * 1024)
            if not chunk:
                break
            digest.update(chunk)
    finally:
        os.close(descriptor)
    return digest.hexdigest()


def _semantic_signature(spec: dict[str, Any]) -> str:
    signature = spec.get("signature", {})

View on GitHub (pinned to 8321021c54)