Narrow scanner preflight failure to repository-hook inventory
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 2s
Reject generated-only commits / generated-commit-guard (push) Successful in 12s

This commit is contained in:
codex 2026-11-02 01:45:13 +01:00
parent a2e57251ac
commit 6696366f27
8 changed files with 214 additions and 5 deletions

View file

@ -19,3 +19,5 @@ The staged scanner in repo-manager and live receiver have not been altered by th
## Current execution outcome, 2026-11-02
The founder ran the exact revision-2 launcher. It failed during preflight with a suppressed Kubernetes command error, before staging or native mutation (`native_mutation: false`, no events). The exact failed read is not yet identified. Failure evidence is preserved in `attempt-r2-preflight/`; the reviewed packet is unchanged. A separate read-only diagnostic labels each check and outputs fixed failure categories; three tests passed, including raw-diagnostic suppression and receipt preservation. Founder invocation: `bash /tmp/forge-scanner-diagnose.sh`. Do not retry the old apply while this failure is unresolved; its existing receipt prevents an unreviewed retry.
The first diagnostic was returned: node/deployment/pod identity, readiness, old public hook and artifact guard all passed; existing repository hook inventory failed with remote exit1. Its selected receipt is preserved in `attempt-r2-preflight/diagnostic-receipt.json`. The pending request is now `bash /tmp/forge-scanner-inventory-diagnose.sh` to identify the specific filesystem condition without reading out hook contents. Five diagnostic tests pass, including real disposable filesystem cases.

View file

@ -0,0 +1,56 @@
{
"read_only": true,
"replayed_packet_sha256": "d22b1f555af96c203b7d68efd25e80815ce18ca4e3bf0dd3ab96b6f699d50237",
"steps": [
{
"step": "node-identity-read",
"returncode": 0,
"category": "passed"
},
{
"step": "deployment-read",
"returncode": 0,
"category": "passed"
},
{
"step": "replicaset-read",
"returncode": 0,
"category": "passed"
},
{
"step": "pod-list-read",
"returncode": 0,
"category": "passed"
},
{
"step": "deployment-read",
"returncode": 0,
"category": "passed"
},
{
"step": "initial-pod-uid-read",
"returncode": 0,
"category": "passed"
},
{
"step": "old-public-hook-read",
"returncode": 0,
"category": "passed"
},
{
"step": "installed-artifact-guard",
"returncode": 0,
"category": "passed"
},
{
"step": "existing-repository-hook-inventory",
"returncode": 1,
"category": "remote-check-nonzero",
"remote_exit_code": 1
}
],
"native_mutation": false,
"original_failed_receipt_preserved": true,
"result": "read-only-preflight-refused",
"reason": "read-only-step-failed:existing-repository-hook-inventory"
}

View file

@ -0,0 +1,91 @@
"""Founder-run read-only explanation of inventory conditions; no hook contents."""
from pathlib import Path
import importlib.util
import json
import re
import subprocess
HERE = Path(__file__).resolve().parent
ORIGINAL = HERE.parent / 'native-r2'
EXPECTED = 'd22b1f555af96c203b7d68efd25e80815ce18ca4e3bf0dd3ab96b6f699d50237'
CONDITIONS = {'directory-missing', 'symlink', 'hook-symlink-present', 'symlink-query-failed',
'dispatcher-missing', 'dispatcher-not-executable', 'scanner-fragment-missing',
'hook-metadata-command-failed', 'repository-limit', 'no-repositories', 'wrong-uid'}
SCRIPT = r'''set +vx
set -u
export PATH=/usr/bin:/bin
issue() { printf 'ISSUE\t%s\t%s\n' "$1" "$2"; }
check_directory() {
test -d "$1" || issue directory-missing "$1"
test ! -L "$1" || issue symlink "$1"
}
test "$(id -u)" = 1000 || issue wrong-uid /data
for parent in /data /data/git /data/git/gitea-repositories; do check_directory "$parent"; done
count=0
for repo in /data/git/gitea-repositories/*/*.git; do
test -d "$repo" || continue
count=$((count+1))
if test "$count" -gt 500; then issue repository-limit /data/git/gitea-repositories; break; fi
for parent in "${repo%/*}" "$repo" "$repo/hooks" "$repo/hooks/pre-receive.d"; do check_directory "$parent"; done
links=$(find "$repo/hooks" -type l -print -quit 2>/dev/null)
code=$?
if test "$code" != 0; then issue symlink-query-failed "$repo/hooks"
elif test -n "$links"; then issue hook-symlink-present "$repo/hooks"; fi
test -f "$repo/hooks/pre-receive" || issue dispatcher-missing "$repo/hooks/pre-receive"
test -x "$repo/hooks/pre-receive" || issue dispatcher-not-executable "$repo/hooks/pre-receive"
test -f "$repo/hooks/pre-receive.d/10-custodian-secret-scan" || issue scanner-fragment-missing "$repo/hooks/pre-receive.d/10-custodian-secret-scan"
if ! find "$repo/hooks" -type f ! -name '*.sample' -exec sh -c '
for file do
stat -c %a "$file" >/dev/null || exit 1
stat -c %u "$file" >/dev/null || exit 1
stat -c %g "$file" >/dev/null || exit 1
sha256sum "$file" >/dev/null || exit 1
done
' sh {} + >/dev/null 2>&1; then issue hook-metadata-command-failed "$repo/hooks"; fi
done
test "$count" -gt 0 || issue no-repositories /data/git/gitea-repositories
printf 'COUNT\t%s\n' "$count"
'''
def parse(raw):
issues = []; count = None
for line in raw.decode().splitlines():
parts = line.split('\t')
if len(parts) == 2 and parts[0] == 'COUNT' and parts[1].isdigit():
if count is not None: raise RuntimeError('duplicate-counter')
count = int(parts[1]); continue
if len(parts) != 3 or parts[0] != 'ISSUE' or parts[1] not in CONDITIONS:
raise RuntimeError('unexpected-diagnostic-output; raw withheld')
path = parts[2]
if not re.fullmatch(r'/data(?:/[a-zA-Z0-9_.-]+)*', path) or '..' in path.split('/'):
path = 'unsafe-path-withheld'
issues.append({'condition': parts[1], 'path': path})
if count is None: raise RuntimeError('missing-counter')
return {'read_only': True, 'repositories_examined': count, 'issues': issues, 'native_mutation': False}
def main():
spec = importlib.util.spec_from_file_location('reviewed_r2_inventory', ORIGINAL / 'rollout.py')
m = importlib.util.module_from_spec(spec); spec.loader.exec_module(m)
if m.verify_packet() != EXPECTED: raise RuntimeError('reviewed-packet-drift')
c = json.loads((ORIGINAL / 'inputs.json').read_bytes())
def k(*args):
q = subprocess.run(['kubectl', *args], capture_output=True, timeout=90)
if q.returncode: raise RuntimeError('read-only-command-failed; raw diagnostics withheld')
return q.stdout
for resource, name, expected in [('deployment', c['deployment_name'], c['deployment_uid']), ('pod', c['pod'], c['pod_uid'])]:
if k('-n', 'forgejo', 'get', resource, name, '-o', 'jsonpath={.metadata.uid}').decode() != expected:
raise RuntimeError('target-identity-drift')
result = parse(k('-n', 'forgejo', 'exec', '-i', c['pod'], '-c', c['container'], '--', 'sh', '-c', SCRIPT))
result['replayed_packet_sha256'] = EXPECTED
out = Path('/tmp/forge-scanner-inventory-diagnostic-receipt.json')
out.write_text(json.dumps(result, indent=2) + '\n')
print(json.dumps(result, indent=2)); print('receipt:', str(out))
if __name__ == '__main__':
try: main()
except Exception as exc:
print(json.dumps({'result': 'read-only-diagnostic-refused', 'reason': str(exc) if isinstance(exc, RuntimeError) else type(exc).__name__}))
raise SystemExit(1)

View file

@ -13,5 +13,19 @@
"diagnostic_sha256": "301919aa82b9e9ae5f1a2d55175d2e988ba4c4ac955598651d7f25ba71ddc21c",
"tests": "3 passed; failure label/redaction, command restrictions and preservation of original receipt",
"promotion_evidence": false,
"score": "unscored; no signed approval or separate approval evidence backfilled"
"score": "unscored; no signed approval or separate approval evidence backfilled",
"followup_diagnostic": {
"receipt_ref": "diagnostic-receipt.json",
"passed_checks": [
"node/deployment/pod identities",
"readiness",
"old public hook",
"installed artifact guard"
],
"failed_check": "existing-repository-hook-inventory",
"category": "remote-check-nonzero",
"remote_exit_code": 1,
"next_check": "inventory_conditions.py; read-only filesystem condition report",
"diagnostic_tests": "5 passed in total, including actual disposable filesystem fixtures"
}
}

View file

@ -0,0 +1,44 @@
"""Exercise actual read-only shell against disposable filesystem fixtures."""
import importlib.util
from pathlib import Path
import subprocess
import tempfile
import unittest
HERE = Path(__file__).resolve().parent
spec = importlib.util.spec_from_file_location('inventory_conditions', HERE / 'inventory_conditions.py')
m = importlib.util.module_from_spec(spec); spec.loader.exec_module(m)
class Tests(unittest.TestCase):
def test_clean_missing_fragment_nonexecutable_dispatcher_and_symlink(self):
with tempfile.TemporaryDirectory() as temp:
data = Path(temp) / 'data'
hooks = data / 'git/gitea-repositories/coulomb/example.git/hooks'
fragments = hooks / 'pre-receive.d'; fragments.mkdir(parents=True)
dispatcher = hooks / 'pre-receive'; dispatcher.write_text('#!/bin/sh\nexit 0\n'); dispatcher.chmod(0o755)
fragment = fragments / '10-custodian-secret-scan'; fragment.write_text('#!/bin/sh\nexit 0\n'); fragment.chmod(0o775)
script = m.SCRIPT.replace('/data', str(data))
def issues():
q = subprocess.run(['sh', '-c', script], capture_output=True, timeout=10)
self.assertEqual(q.returncode, 0)
result = m.parse(q.stdout)
self.assertEqual(result['repositories_examined'], 1)
return {x['condition'] for x in result['issues']} - {'wrong-uid'}
self.assertEqual(issues(), set())
fragment.unlink(); self.assertEqual(issues(), {'scanner-fragment-missing'})
fragment.write_text('#!/bin/sh\nexit 0\n'); fragment.chmod(0o775)
dispatcher.chmod(0o644); self.assertEqual(issues(), {'dispatcher-not-executable'})
dispatcher.chmod(0o755)
original = hooks / 'saved.d'; fragments.rename(original); fragments.symlink_to(original, target_is_directory=True)
self.assertEqual(issues(), {'symlink', 'hook-symlink-present'})
self.assertTrue((original / fragment.name).exists())
def test_parser_rejects_unframed_data_and_hides_unsafe_paths(self):
with self.assertRaisesRegex(RuntimeError, '^unexpected-diagnostic-output; raw withheld$'):
m.parse(b'PRIVATE_SENTINEL\n')
result = m.parse(b'ISSUE\tsymlink\t/elsewhere/PRIVATE_SENTINEL\nCOUNT\t1\n')
self.assertEqual(result['issues'][0]['path'], 'unsafe-path-withheld')
if __name__ == '__main__': unittest.main()