rohitsar567 Claude Opus 4.7 (1M context) commited on
Commit
5876e7b
Β·
1 Parent(s): a519e2c

feat(audit): Tier 1 repo-integrity checks + selftest fixtures

Browse files

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Files changed (2) hide show
  1. audit/selftest_fixtures.py +96 -0
  2. audit/tier1_repo.py +83 -1
audit/selftest_fixtures.py CHANGED
@@ -1 +1,97 @@
 
 
 
 
 
 
 
 
 
 
1
  FIXTURES: dict = {}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Selftest fixtures: each yields a context where the matching check FAILs.
2
+
3
+ Every fixture creates an obviously-temporary broken state under REPO, yields,
4
+ then fully restores in a `finally` so the repo is not left dirty.
5
+ """
6
+ from __future__ import annotations
7
+ import contextlib
8
+ import os
9
+ from audit.core import REPO, sh
10
+
11
  FIXTURES: dict = {}
12
+
13
+
14
+ @contextlib.contextmanager
15
+ def _f_t1_1():
16
+ """Track a symlink (mode 120000) so T1.1 FAILs."""
17
+ link = REPO / "_audit_selftest_symlink"
18
+ if link.exists() or link.is_symlink():
19
+ link.unlink()
20
+ os.symlink("README.md", link)
21
+ sh(["git", "add", "-f", "_audit_selftest_symlink"])
22
+ try:
23
+ yield
24
+ finally:
25
+ sh(["git", "rm", "--cached", "-q", "_audit_selftest_symlink"])
26
+ if link.exists() or link.is_symlink():
27
+ link.unlink()
28
+
29
+
30
+ @contextlib.contextmanager
31
+ def _f_t1_2():
32
+ """Track a >512KB file with an extension not covered by any LFS glob."""
33
+ big = REPO / "_audit_selftest_big.dat"
34
+ big.write_bytes(b"A" * (768 * 1024))
35
+ sh(["git", "add", "-f", "_audit_selftest_big.dat"])
36
+ try:
37
+ yield
38
+ finally:
39
+ sh(["git", "rm", "--cached", "-q", "_audit_selftest_big.dat"])
40
+ if big.exists():
41
+ big.unlink()
42
+
43
+
44
+ @contextlib.contextmanager
45
+ def _f_t1_3():
46
+ """Track a file containing a key-shaped string so T1.3 FAILs."""
47
+ secret = REPO / "_audit_selftest_secret.txt"
48
+ secret.write_text("token = hf_" + "a1B2c3D4e5F6g7H8i9J0kLmNoP\n", encoding="utf-8")
49
+ sh(["git", "add", "-f", "_audit_selftest_secret.txt"])
50
+ try:
51
+ yield
52
+ finally:
53
+ sh(["git", "rm", "--cached", "-q", "_audit_selftest_secret.txt"])
54
+ if secret.exists():
55
+ secret.unlink()
56
+
57
+
58
+ @contextlib.contextmanager
59
+ def _f_t1_4():
60
+ """Remove the slash-less `rag/corpus` line from .gitignore so T1.4 FAILs."""
61
+ gi = REPO / ".gitignore"
62
+ original = gi.read_text(encoding="utf-8")
63
+ patched = "\n".join(
64
+ ln for ln in original.split("\n") if ln != "rag/corpus"
65
+ )
66
+ gi.write_text(patched, encoding="utf-8")
67
+ try:
68
+ yield
69
+ finally:
70
+ gi.write_text(original, encoding="utf-8")
71
+
72
+
73
+ @contextlib.contextmanager
74
+ def _f_t1_5():
75
+ """Track a path containing .DS_Store so T1.5 FAILs."""
76
+ d = REPO / "_audit_selftest_dir"
77
+ d.mkdir(exist_ok=True)
78
+ junk = d / ".DS_Store"
79
+ junk.write_bytes(b"\x00junk\x00")
80
+ sh(["git", "add", "-f", "_audit_selftest_dir/.DS_Store"])
81
+ try:
82
+ yield
83
+ finally:
84
+ sh(["git", "rm", "--cached", "-q", "_audit_selftest_dir/.DS_Store"])
85
+ if junk.exists():
86
+ junk.unlink()
87
+ if d.exists():
88
+ d.rmdir()
89
+
90
+
91
+ FIXTURES.update({
92
+ "T1.1": _f_t1_1,
93
+ "T1.2": _f_t1_2,
94
+ "T1.3": _f_t1_3,
95
+ "T1.4": _f_t1_4,
96
+ "T1.5": _f_t1_5,
97
+ })
audit/tier1_repo.py CHANGED
@@ -1 +1,83 @@
1
- # filled in a later task
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Tier 1 β€” repo integrity (pre-commit, fast)."""
2
+ from __future__ import annotations
3
+ import re
4
+ from audit.core import register, Result, Status, REPO, git, sh
5
+
6
+
7
+ @register("T1.1", "static", "no tracked symlinks")
8
+ def t1_1() -> Result:
9
+ out = git("ls-files", "-s")
10
+ syms = [ln.split("\t", 1)[1] for ln in out.splitlines() if ln.startswith("120000")]
11
+ if syms:
12
+ return Result("T1.1", Status.FAIL, f"tracked symlinks: {syms}",
13
+ "git rm --cached <path>; add to .gitignore (no trailing slash)")
14
+ return Result("T1.1", Status.PASS, "no tracked symlinks")
15
+
16
+
17
+ @register("T1.2", "static", "LFS coverage for binary/large files")
18
+ def t1_2() -> Result:
19
+ ga = (REPO / ".gitattributes").read_text(encoding="utf-8", errors="replace")
20
+ lfs_globs = [ln.split()[0] for ln in ga.splitlines()
21
+ if "filter=lfs" in ln and ln.strip() and not ln.startswith("#")]
22
+ lfs_files = set(sh(["git", "lfs", "ls-files", "-n"]).stdout.split())
23
+ bad = []
24
+ for ln in git("ls-files", "-s").splitlines():
25
+ parts = ln.split()
26
+ mode, blob, path = parts[0], parts[1], ln.split("\t", 1)[1] if "\t" in ln else parts[3]
27
+ if mode == "120000":
28
+ continue
29
+ szout = sh(["git", "cat-file", "-s", blob]).stdout.strip()
30
+ is_lfs = path in lfs_files
31
+ big = szout.isdigit() and int(szout) > 512 * 1024
32
+ if big and not is_lfs:
33
+ bad.append(f"{path} ({int(szout)//1024} KB) not LFS")
34
+ if bad:
35
+ return Result("T1.2", Status.FAIL, "; ".join(bad[:8]),
36
+ "add a filter=lfs rule to .gitattributes; git rm --cached + re-add the files")
37
+ return Result("T1.2", Status.PASS, f"{len(lfs_files)} LFS files; no oversized non-LFS blobs")
38
+
39
+
40
+ @register("T1.3", "static", "no real secrets tracked")
41
+ def t1_3() -> Result:
42
+ KEYISH = re.compile(r"(hf_[A-Za-z0-9]{20,}|sk-[A-Za-z0-9]{20,}|AIza[A-Za-z0-9_\-]{20,}|xox[bp]-[A-Za-z0-9-]{20,})")
43
+ suspects = []
44
+ for path in git("ls-files").splitlines():
45
+ base = path.rsplit("/", 1)[-1]
46
+ if base == ".env" or (base.startswith(".env") and not base.endswith((".example", ".sample"))):
47
+ suspects.append(f"{path} (real dotenv tracked)")
48
+ continue
49
+ if path.endswith((".png", ".jpg", ".jpeg", ".pdf", ".duckdb", ".bin", ".ico", ".woff", ".woff2", ".ttf")):
50
+ continue
51
+ try:
52
+ txt = (REPO / path).read_text(encoding="utf-8", errors="ignore")
53
+ except Exception:
54
+ continue
55
+ if KEYISH.search(txt) and "example" not in path and "ADR-010" not in path:
56
+ suspects.append(f"{path} (key-shaped string)")
57
+ if suspects:
58
+ return Result("T1.3", Status.FAIL, "; ".join(suspects[:8]),
59
+ "remove from index + history; rotate the key; gitignore the file")
60
+ return Result("T1.3", Status.PASS, "no real .env / key material tracked")
61
+
62
+
63
+ @register("T1.4", "static", ".gitignore robust for file AND dir")
64
+ def t1_4() -> Result:
65
+ intents = ["tools/.pdf_text_cache", "rag/corpus", "rag/extracted", "rag/vectors"]
66
+ gi = (REPO / ".gitignore").read_text(encoding="utf-8", errors="replace").splitlines()
67
+ missing = [it for it in intents if it not in gi]
68
+ if missing:
69
+ return Result("T1.4", Status.FAIL, f"only dir-form (or absent) ignore for: {missing}",
70
+ "add a slash-less line per intent so a symlink/file of that name is also ignored")
71
+ return Result("T1.4", Status.PASS, "file+dir ignore intents present")
72
+
73
+
74
+ @register("T1.5", "static", "no junk/build artifacts tracked")
75
+ def t1_5() -> Result:
76
+ JUNK = ("tools/.pdf_text_cache/", ".pytest_cache/", ".DS_Store",
77
+ "frontend/out/", "frontend/.next/", "node_modules/", ".tsbuildinfo")
78
+ tracked = git("ls-files").splitlines()
79
+ hits = [p for p in tracked if any(j in p for j in JUNK)]
80
+ if hits:
81
+ return Result("T1.5", Status.FAIL, f"{len(hits)} junk paths e.g. {hits[:5]}",
82
+ "git rm -r --cached <path> and gitignore it")
83
+ return Result("T1.5", Status.PASS, "no caches/build artifacts tracked")