mva-hackathon-2026 / scripts /privacy_gate.py
MarxistLeninist's picture
Mirror of Marxist-Leninist/mva-hackathon-2026 incl. genome-wide addendum, dosage model, contraindications
4533472 verified
Raw History Blame Contribute Delete
2.74 kB
#!/usr/bin/env python3
"""Fail closed when likely gated genomic data or secrets enter the public tree."""
from __future__ import annotations
import argparse
import re
import sys
from pathlib import Path
from validate_submission import validate
BANNED_DIRECTORIES = {"data", "raw", "private", "work", "logs", "tmp"}
BANNED_SUFFIXES = {
".vcf", ".bcf", ".tbi", ".csi", ".bam", ".bai", ".cram", ".crai",
".fastq", ".fq", ".gvcf", ".ped", ".fam", ".docx",
}
SECRET_PATTERNS = {
"Hugging Face token": re.compile(r"hf_[A-Za-z0-9]{20,}"),
"GitHub token": re.compile(r"gh[opsu]_[A-Za-z0-9]{20,}"),
"private key": re.compile(r"BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY"),
}
MAX_PUBLIC_FILE_BYTES = 5 * 1024 * 1024
def iter_public_files(root: Path):
for path in sorted(root.rglob("*")):
if not path.is_file() or ".git" in path.parts or "__pycache__" in path.parts:
continue
yield path
def check(root: Path) -> list[str]:
errors: list[str] = []
for path in iter_public_files(root):
relative = path.relative_to(root)
if any(part in BANNED_DIRECTORIES for part in relative.parts[:-1]):
errors.append(f"banned directory: {relative}")
suffixes = "".join(path.suffixes).lower()
if path.suffix.lower() in BANNED_SUFFIXES or any(
suffixes.endswith(item + ".gz") for item in BANNED_SUFFIXES
):
errors.append(f"banned genomic/private extension: {relative}")
if path.stat().st_size > MAX_PUBLIC_FILE_BYTES:
errors.append(f"file exceeds 5 MiB public limit: {relative}")
if relative.as_posix() == "scripts/privacy_gate.py":
continue
try:
text = path.read_text(encoding="utf-8")
except UnicodeDecodeError:
errors.append(f"unexpected binary file: {relative}")
continue
for label, pattern in SECRET_PATTERNS.items():
if pattern.search(text):
errors.append(f"possible {label}: {relative}")
prediction = root / "results" / "MarxistLeninist_bub1b_compound_het.csv"
if prediction.exists():
errors.extend(f"submission: {item}" for item in validate(prediction))
else:
errors.append("missing public Track 1 prediction CSV")
return errors
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("root", nargs="?", default=".", type=Path)
args = parser.parse_args()
errors = check(args.root.resolve())
if errors:
for error in errors:
print(f"ERROR: {error}", file=sys.stderr)
return 1
print("PASS: public-release privacy gate")
return 0
if __name__ == "__main__":
raise SystemExit(main())