Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .github/workflows/no-local-home-paths.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: No absolute local home paths

'on':
pull_request:
push:
branches: [main]
workflow_dispatch:

permissions:
contents: read

jobs:
no-local-home-paths:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
persist-credentials: false
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97
with:
python-version: '3.11.16'
- name: Negative and positive controls
run: |
python -B -S -m unittest discover -s tests -p 'test_check_no_local_home_paths.py' -v
python -B -O -S -m unittest discover -s tests -p 'test_check_no_local_home_paths.py' -v
- name: Tracked tree scan
run: python -B -S tools/check_no_local_home_paths.py .
- run: git diff --exit-code
143 changes: 143 additions & 0 deletions docs/custody/LOCAL_PATH_MAP.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
{
"schema": "local-path-map/v1",
"scientific_effect": "NONE",
"pinned_head": "6e502c1f8a5632eb90670fd9fc0d52e421c015a2",
"purpose": "Owner-side mapping for absolute local home paths retained in hash-pinned historical evidence. Additive only: no pinned file is edited, so no recorded hash changes.",
"prefixes": [
{
"placeholder": "<OWNER_LOCAL_HOME>",
"description": "macOS per-user home directory of the repository owner (the prefix up to and including the user name, no trailing slash)",
"sha256_of_prefix_utf8": "f42cc646e946c6157467d73f3541203ab2746476c0b6dda88a5baa6358256ef5",
"bytes": 15
}
],
"container_paths": {
"pattern": "/root/<workdir>",
"status": "N/A: agent-sandbox scratch directories, not a personal home path; not flagged"
},
"ci_runner_paths": {
"pattern": "/home/runner",
"status": "N/A: GitHub Actions runner paths; not flagged"
},
"files": [
{
"path": "frontiers/lifetime_moment_boundary_20260930/IDENTITY_AND_CONTROL_OBSERVATIONS.json",
"lines": [
4,
10,
16,
78,
84,
90
],
"hits": 6,
"git_blob": "a2aff201bda75aa74b8101d225182ca7731872e3",
"sha256": "84164b9ce29f7cc0d0b31a9cd53a870e0ad2c9bd7bb7de271cff877ba96754e5",
"bytes": 8495,
"disposition": "box-only path remap prepared (obs_paths.patch); not applied here — owner decision"
},
{
"path": "reviews/radial_moment_corollaries_20260930/IDENTITY_AND_CONTROL_OBSERVATIONS.json",
"lines": [
4,
10,
16,
78,
84,
90
],
"hits": 6,
"git_blob": "a2aff201bda75aa74b8101d225182ca7731872e3",
"sha256": "84164b9ce29f7cc0d0b31a9cd53a870e0ad2c9bd7bb7de271cff877ba96754e5",
"bytes": 8495,
"disposition": "box-only path remap prepared (obs_paths.patch); not applied here — owner decision"
},
{
"path": "frontiers/moving_remote_collision_20261003/AUTHOR_CONTROL_RUNS.json",
"lines": [
6
],
"hits": 1,
"git_blob": "aeb927abcf637243228d5b3158d7f802763b1928",
"sha256": "ee15e53018b87d160a0824c684b0eac8b0b598bd24bd3bf49298a1d3edb79bfe",
"bytes": 3214,
"disposition": "needs-owner-mapping"
},
{
"path": "frontiers/moving_remote_collision_20261003/review/ENGINEERING_RUNS.json",
"lines": [
14,
17,
25,
32,
43,
47,
55,
63
],
"hits": 8,
"git_blob": "9bd98bc9df21d39c696335bf949ef3d945ca7d88",
"sha256": "ac99011d56f5fdd0674a798bace78de7560c160849c4bce9460b4f047ffbe5fe",
"bytes": 8801,
"disposition": "historical-evidence+mapping"
},
{
"path": "frontiers/planar_soft_layer_chain_20261003/C102/CHECKER_COMMENT.md",
"lines": [
510,
515,
530
],
"hits": 3,
"git_blob": "7b81b8a5b09b8469441222b1fbf37ea28da60fa5",
"sha256": "68e847b5e74fa734923a31cc0fe6c452c7e183223dbc24d08f0872504402655e",
"bytes": 28098,
"disposition": "historical-evidence"
},
{
"path": "frontiers/planar_soft_layer_chain_20261003/C124/AUTHOR_CONTROLS_COMMENT.md",
"lines": [
344
],
"hits": 1,
"git_blob": "447c317461e10d088672c04ad4fb412543e8c031",
"sha256": "c8387191a6b11b2e9a1f67e7f22a5ad58b667cc11d29b90fded1c0b33e459dc4",
"bytes": 23018,
"disposition": "historical-evidence"
},
{
"path": "frontiers/planar_soft_layer_chain_20261003/C124/CONTROLS.md",
"lines": [
61
],
"hits": 1,
"git_blob": "7b5c77b4d359d93c023aeb4a4c8b77831084806f",
"sha256": "400a043d664f2ad894bbc669c38203d9809a14743522c031cc86bdbef923e399",
"bytes": 10113,
"disposition": "needs-owner-mapping"
},
{
"path": "frontiers/planar_soft_layer_chain_20261003/C124/REVIEW.md",
"lines": [
139
],
"hits": 1,
"git_blob": "5cd4095e16bad897641254e6f2b2089f8d8e7db0",
"sha256": "19c405c68a93c3f5506629f0ebf1ffa78d5c25ad92f17fc4fd04ef0066deb6cf",
"bytes": 22542,
"disposition": "historical-evidence"
},
{
"path": "frontiers/qs_d3_soft_layer_chain_20261005/A3_2/REVIEW_C135_Q4.md",
"lines": [
140
],
"hits": 1,
"git_blob": "d00fec057c73e1ba3a63efce7688832c40d4f647",
"sha256": "0724c5b9fd84c689bd52be84c67b0f0ad047065b5cf57b1c9ec8de19f92c5daa",
"bytes": 20565,
"disposition": "historical-evidence"
}
],
"rule": "tools/check_no_local_home_paths.py fails on any absolute local home path in a tracked text file, except a file listed here whose git_blob is unchanged."
}
72 changes: 72 additions & 0 deletions tests/test_check_no_local_home_paths.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import json, pathlib, subprocess, sys, tempfile, unittest

ROOT = pathlib.Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / 'tools'))
import check_no_local_home_paths as chk

S = '/'
MAC_HOME = S + 'Us' + 'ers' + S + 'synthetic' + 'user'
LIN_HOME = S + 'ho' + 'me' + S + 'synthetic' + 'user'
WIN_HOME = 'C:' + '\\' + 'Us' + 'ers' + '\\' + 'synthetic' + 'user'
RUN_HOME = S + 'ho' + 'me' + S + 'run' + 'ner' + S + 'work'
ROOT_SCRATCH = S + 'root' + S + 'scratch_dir'


def repo(files, allow=None):
d = tempfile.TemporaryDirectory()
r = pathlib.Path(d.name)
subprocess.run(['git', 'init', '-q', str(r)], check=True)
for k, v in files.items():
(r / k).parent.mkdir(parents=True, exist_ok=True)
(r / k).write_text(v, encoding='utf-8')
if allow is not None:
(r / 'docs/custody').mkdir(parents=True, exist_ok=True)
(r / chk.MAP).write_text(json.dumps({'schema': 'local-path-map/v1', 'files': allow}))
subprocess.run(['git', '-C', str(r), 'add', '-A'], check=True)
return d, r


class NegativeControls(unittest.TestCase):
def test_each_synthetic_home_fails(self):
for home in (MAC_HOME, LIN_HOME, WIN_HOME, WIN_HOME.replace('\\', '/')):
d, r = repo({'a.md': 'ran ' + home + '/venv/bin/python\n'})
with d:
self.assertEqual(len(chk.scan(r)), 1, home)

def test_runner_and_root_scratch_pass(self):
d, r = repo({'a.log': RUN_HOME + '\n' + ROOT_SCRATCH + '\n'})
with d:
self.assertEqual(chk.scan(r), [])

def test_allowlisted_unchanged_passes_and_changed_fails(self):
body = 'x ' + MAC_HOME + '/p\n'
d, r = repo({'e.json': body})
with d:
b = chk.blob(body.encode())
(r / 'docs/custody').mkdir(parents=True)
(r / chk.MAP).write_text(json.dumps({'schema': 'local-path-map/v1', 'files': [{'path': 'e.json', 'git_blob': b}]}))
subprocess.run(['git', '-C', str(r), 'add', '-A'], check=True)
self.assertEqual(chk.scan(r), [])
(r / 'e.json').write_text(body + 'y ' + LIN_HOME + '/q\n')
self.assertEqual(len(chk.scan(r)), 2)

def test_cli_exit_code(self):
d, r = repo({'a.txt': MAC_HOME + '\n'})
with d:
p = subprocess.run([sys.executable, str(ROOT / 'tools/check_no_local_home_paths.py'), str(r)], capture_output=True)
self.assertEqual(p.returncode, 1)

def test_own_sources_have_no_literal_home_path(self):
for f in ('tools/check_no_local_home_paths.py', 'tests/test_check_no_local_home_paths.py', '.github/workflows/no-local-home-paths.yml', chk.MAP):
p = ROOT / f
if p.exists():
self.assertFalse(chk.PATTERN.search(p.read_text(encoding='utf-8')), f)


class PositiveControl(unittest.TestCase):
def test_current_tree_passes(self):
self.assertEqual(chk.scan(ROOT), [])


if __name__ == '__main__':
unittest.main()
80 changes: 80 additions & 0 deletions tools/check_no_local_home_paths.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#!/usr/bin/env python3
"""Fail if a tracked text file contains an absolute local home path.

Flagged: macOS per-user homes, Linux /home/<user> (except the GitHub Actions
runner), and Windows C:/Users or C:\\Users homes. Agent-container scratch
paths under /root are not personal homes and are not flagged. Files listed in
docs/custody/LOCAL_PATH_MAP.json are allowed only while their git blob is
unchanged, so any edit to such a file that keeps or adds a home path fails.

Patterns are assembled from fragments so this file never contains a literal
home path itself.
"""
import hashlib, json, pathlib, re, subprocess, sys

SEP = '/'
MAC = SEP + 'Us' + 'ers' + SEP
LINUX = SEP + 'ho' + 'me' + SEP
WIN = 'C:' + '[\\\\/]+' + 'Us' + 'ers' + '[\\\\/]+'
RUNNER = 'run' + 'ner'
NAME = r'[A-Za-z0-9._-]+'
PATTERN = re.compile(
'(?:' + re.escape(MAC) + NAME + ')'
+ '|(?:' + re.escape(LINUX) + '(?!' + RUNNER + r'(?![A-Za-z0-9._-]))' + NAME + ')'
+ '|(?:' + WIN + NAME + ')',
re.IGNORECASE)
MAP = 'docs/custody/LOCAL_PATH_MAP.json'


def blob(data):
return hashlib.sha1(b'blob %d\0' % len(data) + data).hexdigest()


def tracked(root):
out = subprocess.run(['git', '-C', str(root), 'ls-files', '-z'],
check=True, capture_output=True).stdout
return [p for p in out.decode('utf-8', 'surrogateescape').split('\0') if p]


def load_allow(root):
path = pathlib.Path(root) / MAP
if not path.is_file():
return {}
doc = json.loads(path.read_text(encoding='utf-8'))
if doc.get('schema') != 'local-path-map/v1':
raise SystemExit('unexpected schema in ' + MAP)
return {e['path']: e['git_blob'] for e in doc['files']}


def scan(root, files=None):
root = pathlib.Path(root)
allow = load_allow(root)
findings = []
for rel in (files if files is not None else tracked(root)):
p = root / rel
if p.is_symlink() or not p.is_file():
continue
data = p.read_bytes()
if b'\0' in data[:8192]:
continue
if allow.get(rel) == blob(data):
continue
text = data.decode('utf-8', 'replace')
for n, line in enumerate(text.split('\n'), 1):
if PATTERN.search(line):
note = ' (allowlisted file changed)' if rel in allow else ''
findings.append(f'{rel}:{n}: absolute local home path{note}')
return findings


def main(argv):
root = argv[1] if len(argv) > 1 else '.'
findings = scan(root)
for f in findings:
print(f)
print(json.dumps({'findings': len(findings), 'scientific_effect': 'NONE'}))
return 1 if findings else 0


if __name__ == '__main__':
sys.exit(main(sys.argv))
Loading