DEV Community

Sam Rivera
Sam Rivera

Posted on

Build a Local Health Export Redaction Validator CLI

The small-tool goal is simple: inspect a JSON health export locally and fail closed if it contains fields the recipient did not request. This is not a medical-data parser, and it should not upload anything. It is a narrow preflight check between “export created” and “file shared.”

The trigger is OpenAI’s July 23, 2026 product announcement. OpenAI says Health in ChatGPT is rolling out to eligible US logged-in users age 18+ on web and iOS, with supported connections to medical records and Apple Health. Its dashboard can cover labs, medications, activity, sleep, and other health information. The company says connected data and relevant conversations are not used for foundation-model training or ad targeting. Read those claims in OpenAI’s announcement; this tutorial does not independently verify them.

Define the export contract

Assume an operator has already produced newline-delimited JSON. The validator accepts only a pseudonymous subject, observation type, day, and value. Real exports will differ, so adapting the allowlist is mandatory.

{"subject":"local-17","type":"steps","day":"2026-07-20","value":4200}
{"subject":"local-17","type":"sleep_minutes","day":"2026-07-20","value":395}
Enter fullscreen mode Exit fullscreen mode

Save this unexecuted example as validate_export.py:

#!/usr/bin/env python3
import argparse, json, sys
from pathlib import Path

ALLOWED = {"subject", "type", "day", "value"}
FORBIDDEN = {"name", "email", "address", "phone", "notes"}

def check(path: Path):
    errors = []
    for number, raw in enumerate(path.read_text().splitlines(), 1):
        try:
            row = json.loads(raw)
        except json.JSONDecodeError as exc:
            errors.append(f"line {number}: invalid JSON ({exc.msg})")
            continue
        keys = set(row) if isinstance(row, dict) else set()
        extra = keys - ALLOWED
        found = keys & FORBIDDEN
        if not isinstance(row, dict): errors.append(f"line {number}: object required")
        if extra: errors.append(f"line {number}: unexpected={sorted(extra)}")
        if found: errors.append(f"line {number}: forbidden={sorted(found)}")
    return errors

p = argparse.ArgumentParser()
p.add_argument("file", type=Path)
args = p.parse_args()
problems = check(args.file)
print("REJECT" if problems else "ACCEPT")
for problem in problems: print(problem, file=sys.stderr)
raise SystemExit(bool(problems))
Enter fullscreen mode Exit fullscreen mode

The intended command is python3 validate_export.py export.ndjson. This code has not been executed here, so no passing output is claimed. A useful failure fixture adds "notes":"call after lunch"; the intended result is a nonzero exit with both unexpected and forbidden messages.

Why allowlisting beats a redaction dictionary

A denylist asks whether known sensitive keys appear. An allowlist asks whether every disclosed field was intentionally approved. That catches novel metadata such as source device, original record identifier, timezone, or an accidentally nested object. The FORBIDDEN set above only improves the message; extra does the actual safety work.

Before sharing, add three cheap checks:

  1. Review ten random lines and the total line count.
  2. Confirm the destination and deletion date out of band.
  3. Generate a new file from allowed fields rather than editing the original in place.

The clean exit is to stop if the schema is unknown. Do not “fix” a rejection by adding every observed key to ALLOWED. Ask what concrete task requires each field, and create a separate profile for a genuinely different purpose.

Boundaries

This utility validates top-level names, not semantics. It cannot detect identifying values hidden inside allowed strings, linkage attacks, malformed dates, units, duplicate rows, re-identification, or safe handling by the recipient. It does not connect to ChatGPT, Apple Health, or a medical-record system. It makes no claim about those products’ export formats, APIs, security, compliance, or deletion behavior.

OpenAI describes its health experience as support rather than a replacement for medical care, and not diagnosis or treatment. A local schema check has an even smaller role: it can reduce accidental disclosure, but it cannot determine what health information is accurate, necessary, or clinically meaningful.

AI assistance disclosure: This article was drafted with AI assistance and reviewed against the cited primary source.

Top comments (0)