security: recover Forgejo scanner custody and prove restart acceptance

This commit is contained in:
codex 2026-10-13 15:07:30 +02:00
parent e570433060
commit 8f770e1eea
39 changed files with 1984 additions and 12 deletions

View file

@ -563,6 +563,74 @@
"executed_digest_sha256": "9c8ea3b7fbe16a997359432af7bcf09f429881fd23cc567188c21129e80ccf8d",
"verified_at": "2026-10-12T17:32:28.543884+00:00",
"verified_outcome": "Additive independent scanner installed on 22 exact repositories. Each actual SSH clean and synthetic-secret probe produced the expected scanner result under the temporary veto; probe refs and quarantined commits absent. Veto and staging removed, original hook inventory preserved, health passes. Normal clean acceptance remains separate."
},
{
"proposal_id": "FORGE6-TEMPLATE-LIFECYCLE-001",
"disposition": "unscored",
"outcome": "failed",
"original_revision": 1,
"original_digest_sha256": "7eddea55359b25c5ad307ed8ccd392dfadfe167d65cbe34b02f1955350bbcb36",
"proposal_ref": "docs/changes/CUST-WP-0080/forge-template-proposal.json",
"authorization_ref": "canon/standards/build-mode-change-authorization_v0.1.md",
"reason": "Standing founder blue-lane authorization after exact source/isolation/server-dry-run proof; not an exact-revision human approval, owner assent, autopilot grant or credential-isolation proof.",
"target": "railiance01 namespace forgejo: Deployment forgejo-gitea and immutable ConfigMap forgejo-secret-template-b1d7cf8cd13f",
"expected_result": "Same image and artifact hashes, Recreate replacement Ready, template selected in actual Forgejo global Git configuration, regular/executable inherited hook in temporary native Git repository, existing 22 hook inventories unchanged, health passing. No new production repository, identity, credential or publication.",
"verification_ref": "docs/evidence/2026-10-13-netkingdom-security/forge-template-activation.json",
"dry_run_ref": "docs/evidence/2026-10-13-netkingdom-security/forge-template-dry-run.json",
"rollback": "Same executor --rollback with original proposal. Removes only exact added mounts and volume, replaces added init setting with /usr/share/git-core/templates, waits for healthy replacement and verifies original 22 hooks/binaries. Keeps immutable ConfigMap inert and any already inherited repository hooks. If rollback used, reconcile owner source to explicit default selection before another Helm deploy. Automatic recovery only when current deployment template exactly matches the attempted change; unknown concurrent changes require abort-owner review.",
"abort_owner": "railiance-forge; executing custodian-codex; release source railiance-apps",
"cost_estimate_eur": 0,
"risk_estimate": "No new resource requests, spend commitments, RBAC or credential scope. Existing Recreate deployment incurs a brief forge interruption; abort after 240-second readiness budget, restore default template under conditional rollback with its own 240-second verification budget. Unknown state stops automatic recovery. New repository writes can refuse if scanner missing, overloaded or input unsupported; existing 22 profiles unchanged. Recorded node CPU requests 3460m/4000m and memory 9472Mi (59%).",
"prepared_at": "2026-10-13T11:56:51.534869+00:00",
"promotion_evidence": false,
"refinement_or_rescue": true,
"executed_revision": 1,
"executed_digest_sha256": "7eddea55359b25c5ad307ed8ccd392dfadfe167d65cbe34b02f1955350bbcb36",
"verified_at": "2026-10-13T12:02:44.468580+00:00",
"verified_outcome": "Subsequent ordinary railiance-forge push refused with scanner-file-refused after the restart. Initial hash/executable/malformed-input verification was insufficient. Custody-mode diagnosis and recovery in progress.",
"initial_verification": {
"at": "2026-10-13T12:02:44.468580+00:00",
"outcome": "Exact additive patch reached generation 12 Ready on unchanged image; configured native Git init inherits a regular executable scanner hook and malformed input refuses. Original 22 hooks/binaries preserved, health passing, temporary repository removed. No production API repository creation or global existing-repository coverage inferred.",
"limit": "Did not exercise ordinary source acceptance after the actual Kubernetes restart."
},
"adverse_outcome_observed_at": "2026-10-13T12:11:00.052645+00:00",
"recovery_revisions": [
{
"revision": 2,
"digest_sha256": "7530423d7528b63434d896dfe28e5288d4e332167db817efa58c2255defe28d3",
"proposal_ref": "docs/changes/CUST-WP-0080/forge-custody-repair-proposal.json",
"disposition": "unscored-standing-blue-authorization",
"outcome": "modes-restored; ordinary queued source push accepted via statehub sync",
"preflight_ref": "docs/evidence/2026-10-13-netkingdom-security/forge-custody-preflight.json",
"receipt_ref": "docs/evidence/2026-10-13-netkingdom-security/forge-custody-repair.json"
},
{
"revision": 3,
"digest_sha256": "b9e8d82331ac2a7fc4b12b5524d486809d03b82f52dc96b31e8ae5e73b93c37f",
"proposal_ref": "docs/changes/CUST-WP-0080/forge-custody-rollout-proposal.json",
"disposition": "unscored-standing-blue-authorization",
"outcome": "first rollout verified; second restart preparation refused JSON-pointer local/server mismatch without mutation",
"source_commit": "railiance-apps 50b66c4",
"preflight_ref": "docs/evidence/2026-10-13-netkingdom-security/forge-custody-rollout-dry-run.json",
"receipt_ref": "docs/evidence/2026-10-13-netkingdom-security/forge-custody-rollout.json"
},
{
"revision": 4,
"digest_sha256": "f6060bf7a5a604781a04b853775f72ba7ada5cc05c89a1da0df4fd480779004b",
"proposal_ref": "docs/changes/CUST-WP-0080/forge-custody-restart-proposal.json",
"disposition": "unscored-standing-blue-authorization",
"outcome": "verified second replacement generation14; guard completed; modes755 and clean/secret/quarantine/recovery checks pass",
"receipt_ref": "docs/evidence/2026-10-13-netkingdom-security/forge-custody-restart.json"
}
],
"recovery_outcome": "Recovered with refinement: revisions2-4 restore755 and persist startup custody; two replacement pods passed native receive probes. Original trial remains failed, not an unchanged success. Authenticated SSH source push2c11c36 accepted after final restart and remote head verified.",
"recovery_summary_ref": "docs/evidence/2026-10-13-netkingdom-security/forge-custody-recovery-summary.json",
"proposal_storage": {
"format": "json-checksum-map-entries-v1",
"restore_helper": "docs/changes/CUST-WP-0080/restore_forge_proposal.py",
"original_digest_semantics": "SHA256 of exactly reconstructed executed JSON bytes; not the archive envelope",
"triage_ref": "docs/evidence/2026-10-13-netkingdom-security/forge-custody-metadata-triage.json"
}
}
]
}

View file

@ -0,0 +1,320 @@
#!/usr/bin/env python3
"""Exact, additive Forgejo template deployment with value-free receipts.
Default mode is read-only observation plus Kubernetes server dry-runs. No Secret
objects or Helm values are requested. Reuses existing immutable scanner artifacts.
"""
import argparse
import copy
import datetime
import hashlib
import importlib.util
import json
from pathlib import Path
import re
import subprocess
import time
HERE = Path(__file__).parent
NAME = 'forgejo-secret-template-b1d7cf8cd13f'
VOLUME = 'secret-scan-template'
SETTING = 'GITEA__GIT_0X2E_CONFIG__INIT_0X2E_TEMPLATEDIR'
TEMPLATE = '/opt/forge-secret-template'
DEFAULT = '/usr/share/git-core/templates'
MOUNT = {'name': VOLUME, 'mountPath': TEMPLATE + '/hooks/pre-receive.d/10-custodian-secret-scan',
'subPath': 'hook', 'readOnly': True}
VOL = {'name': VOLUME, 'configMap': {'name': NAME, 'defaultMode': 365}}
def digest(value):
return hashlib.sha256(json.dumps(value, sort_keys=True, separators=(',', ':')).encode()).hexdigest()
def module(name, path):
spec = importlib.util.spec_from_file_location(name, path)
obj = importlib.util.module_from_spec(spec)
spec.loader.exec_module(obj)
return obj
native = module('template_native', HERE / 'verify_receive_native.py')
activation = module('template_activation', HERE / 'activate_receive_hooks.py')
def deployment():
return json.loads(native.command(['-n', 'forgejo', 'get', 'deployment', 'forgejo-gitea', '-o', 'json']))
def target_pod(d, original):
pods = json.loads(native.command(['-n', 'forgejo', 'get', 'pods', '-l', 'app.kubernetes.io/instance=forgejo', '-o', 'json']))['items']
candidates = [p for p in pods if not p['metadata'].get('deletionTimestamp') and
any(c['name'] == 'gitea' for c in p['spec']['containers']) and
p.get('status', {}).get('phase') == 'Running' and
any(c['type'] == 'Ready' and c['status'] == 'True' for c in p.get('status', {}).get('conditions', []))]
if len(candidates) != 1:
raise ValueError('no-unique-ready-pod')
p = candidates[0]
image = next(c['image'] for c in p['spec']['containers'] if c['name'] == 'gitea')
image_id = next(c['imageID'] for c in p['status']['containerStatuses'] if c['name'] == 'gitea')
if image != original['image'] or image_id != original['image_id']:
raise ValueError('image-changed')
if d['status'].get('observedGeneration', 0) < d['metadata']['generation'] or d['status'].get('availableReplicas') != 1:
raise ValueError('deployment-not-ready')
return dict(original, pod=p['metadata']['name'], pod_uid=p['metadata']['uid'])
def patch_for(d, rollback=False):
"""Build a conditional patch; refuse conflicts and preserve all other fields."""
spec = d['spec']['template']['spec']
patch = [{'op': 'test', 'path': '/metadata/uid', 'value': d['metadata']['uid']},
{'op': 'test', 'path': '/metadata/resourceVersion', 'value': d['metadata']['resourceVersion']}]
volumes = spec['volumes']
found = [i for i, v in enumerate(volumes) if v['name'] == VOLUME]
if rollback:
if len(found) != 1 or volumes[found[0]] != VOL:
raise ValueError('rollback-volume-changed')
patch.append({'op': 'remove', 'path': '/spec/template/spec/volumes/' + str(found[0])})
else:
if found:
raise ValueError('template-volume-preexists')
patch.append({'op': 'add', 'path': '/spec/template/spec/volumes/-', 'value': VOL})
configured = 0
for kind in ['initContainers', 'containers']:
for index, c in enumerate(spec[kind]):
base = '/spec/template/spec/' + kind + '/' + str(index)
mounts = c['volumeMounts']
matches = [i for i, v in enumerate(mounts) if v['name'] == VOLUME or v['mountPath'] == MOUNT['mountPath']]
if rollback:
if len(matches) != 1 or mounts[matches[0]] != MOUNT:
raise ValueError('rollback-mount-changed')
patch.append({'op': 'remove', 'path': base + '/volumeMounts/' + str(matches[0])})
else:
if matches:
raise ValueError('template-mount-preexists')
patch.append({'op': 'add', 'path': base + '/volumeMounts/-', 'value': MOUNT})
if kind == 'initContainers' and c['name'] == 'init-app-ini':
env = c['env']
found = [i for i, e in enumerate(env) if e['name'] == SETTING]
if rollback:
if len(found) != 1 or env[found[0]] != {'name': SETTING, 'value': TEMPLATE}:
raise ValueError('rollback-setting-changed')
patch.append({'op': 'replace', 'path': base + '/env/' + str(found[0]),
'value': {'name': SETTING, 'value': DEFAULT}})
else:
if found:
raise ValueError('template-setting-preexists')
patch.append({'op': 'add', 'path': base + '/env/-', 'value': {'name': SETTING, 'value': TEMPLATE}})
configured += 1
if configured != 1:
raise ValueError('unexpected-configuration-container')
return patch
def apply_locally(d, patch):
result = copy.deepcopy(d)
for op in patch:
parts = op['path'].strip('/').split('/')
obj = result
for part in parts[:-1]:
obj = obj[int(part)] if isinstance(obj, list) else obj[part]
last = parts[-1]
key = int(last) if isinstance(obj, list) and last != '-' else last
if op['op'] == 'test':
if obj[key] != op['value']:
raise ValueError('precondition-changed')
elif op['op'] == 'remove':
del obj[key]
elif op['op'] == 'replace':
obj[key] = copy.deepcopy(op['value'])
elif op['op'] == 'add':
if last == '-':
obj.append(copy.deepcopy(op['value']))
else:
obj[key] = copy.deepcopy(op['value'])
return result
def patch_remote(patch, dry_run=False):
args = ['-n', 'forgejo', 'patch', 'deployment', 'forgejo-gitea', '--type=json', '--patch-file=/dev/stdin', '-o', 'json']
if dry_run:
args.append('--dry-run=server')
return json.loads(native.command(args, payload=json.dumps(patch).encode()))
def selected_template(pod):
raw = native.execute(pod, 'sh', '-c',
'set +e; value=$(git config --file /data/home/.gitconfig --get-all init.templateDir 2>/dev/null); code=$?; '
'case "$code" in 0) printf "%s" "$value";; 1) printf "ABSENT";; *) exit 2;; esac')
value = raw.decode().strip()
if value not in ('ABSENT', TEMPLATE, DEFAULT):
raise ValueError('unknown-template-selection')
return value
def check_artifacts(pod):
activation.inspect(native, pod, applied=True)
for item in pod['artifacts'][:2]:
path = pod['version_directory'] + '/' + item['name']
observed = native.execute(pod, 'sha256sum', path).decode().split()[0]
if observed != item['sha256']:
raise ValueError('installed-artifact-changed')
native.execute(pod, 'test', '-x', path)
native.execute(pod, 'wget', '-qO', '/dev/null', 'http://127.0.0.1:3000/api/healthz')
def wait_ready(original, selection, timeout=240):
end = time.monotonic() + timeout
while time.monotonic() < end:
try:
d = deployment()
pod = target_pod(d, original)
if d['status'].get('updatedReplicas') != 1 or selected_template(pod) != selection:
raise ValueError('replacement-not-ready')
check_artifacts(pod)
return d, pod
except (ValueError, KeyError, subprocess.SubprocessError):
time.sleep(3)
raise ValueError('rollout-not-verified')
def verify_inheritance(pod, proposal):
directory = '/tmp/custodian-template-' + proposal['proposal_id'].lower()
created = False
try:
native.execute(pod, 'sh', '-c', 'umask 077; test ! -e "$1" && mkdir "$1" && printf "%s" "$2" > "$1/.owner"',
'sh', directory, proposal['proposal_id'])
created = True
native.execute(pod, 'sh', '-c', 'test -f "$1" && test ! -L "$1" && test -x "$1"', 'sh', MOUNT['mountPath'])
native.execute(pod, 'git', '-c', 'include.path=/data/home/.gitconfig', 'init', '--bare', '-q', directory + '/probe.git')
hook = directory + '/probe.git/hooks/pre-receive.d/10-custodian-secret-scan'
native.execute(pod, 'sh', '-c', 'test -f "$1" && test ! -L "$1" && test -x "$1"', 'sh', hook)
observed = native.execute(pod, 'sha256sum', hook).decode().split()[0]
if observed != pod['hook_sha256']:
raise ValueError('inherited-hook-differs')
raw = native.execute(pod, 'sh', '-c',
'cd "$1"; set +e; output=$(printf "malformed\\n" | hooks/pre-receive.d/10-custodian-secret-scan 2>/dev/null); '
'code=$?; test "$code" = 2 || exit 1; printf "%s" "$output"', 'sh', directory + '/probe.git')
if json.loads(raw)['status'] != 'unverified':
raise ValueError('inherited-hook-not-executable')
return {'mounted_hook_regular': True, 'inherited_hook_regular_executable': True,
'hook_sha256': observed, 'inherited_hook_malformed_input_refused': True,
'scope': 'Native Git init with actual Forgejo global configuration in an owned temporary bare repository; not production API repository creation.'}
finally:
if created:
native.execute(pod, 'sh', '-c', 'test "$(cat "$1/.owner")" = "$2" && rm -rf -- "$1" && test ! -e "$1"',
'sh', directory, proposal['proposal_id'])
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--proposal', type=Path, required=True)
parser.add_argument('--receipt', type=Path, required=True)
mode = parser.add_mutually_exclusive_group()
mode.add_argument('--apply', action='store_true')
mode.add_argument('--rollback', action='store_true')
args = parser.parse_args()
proposal = json.loads(args.proposal.read_text())
if not re.fullmatch(r'[A-Z0-9-]{1,100}', proposal['proposal_id']):
raise ValueError('unsafe-proposal-id')
for filename, expected in proposal['executor_hashes'].items():
if hashlib.sha256((HERE / filename).read_bytes()).hexdigest() != expected:
raise ValueError('executor-revision-changed')
if not args.rollback:
for filename, expected in proposal['owner_source_hashes'].items():
if hashlib.sha256(Path(filename).read_bytes()).hexdigest() != expected:
raise ValueError('owner-source-revision-changed')
isolated = json.loads(Path(proposal['isolated_receipt']).read_text())
if isolated.get('result') != 'verified' or not isolated.get('isolated_container_and_volumes_removed'):
raise ValueError('isolated-acceptance-incomplete')
original = json.loads((HERE / 'receive-hook-proposal.json').read_text())
nodes = json.loads(native.command(['get', 'nodes', '-o', 'json']))['items']
if len(nodes) != 1 or not any(a['address'] == '92.205.62.239' for a in nodes[0]['status']['addresses']):
raise ValueError('host-changed')
apps = json.loads(native.command(['-n', 'argocd', 'get', 'applications', '-o', 'json']))['items']
if any(a['spec'].get('destination', {}).get('namespace') == 'forgejo' for a in apps):
raise ValueError('gitops-owner-now-present')
d = deployment()
if d['metadata']['uid'] != proposal['deployment_uid'] or d['spec']['strategy']['type'] != 'Recreate' or d['spec']['replicas'] != 1:
raise ValueError('deployment-target-changed')
if not args.rollback and digest(d['spec']['template']) != proposal['original_template_sha256']:
raise ValueError('deployment-template-changed')
pod = target_pod(d, original)
check_artifacts(pod)
selection = selected_template(pod)
if selection != (TEMPLATE if args.rollback else 'ABSENT'):
raise ValueError('template-selection-changed')
cm = proposal['configmap']
if (cm.get('apiVersion') != 'v1' or cm.get('kind') != 'ConfigMap' or set(cm.get('data', {})) != {'hook'}
or cm['metadata']['name'] != NAME or cm['metadata']['namespace'] != 'forgejo' or not cm.get('immutable')):
raise ValueError('invalid-configmap-target')
if hashlib.sha256(cm['data']['hook'].encode()).hexdigest() != original['hook_sha256']:
raise ValueError('template-hook-changed')
found = native.command(['-n', 'forgejo', 'get', 'configmap', NAME, '--ignore-not-found', '-o', 'json'])
if found.strip():
existing = json.loads(found)
if existing.get('data') != cm['data'] or existing.get('immutable') is not True:
raise ValueError('template-configmap-conflict')
elif not args.rollback:
native.command(['create', '--dry-run=server', '-f', '-', '-o', 'name'], payload=json.dumps(cm).encode())
patch = patch_for(d, rollback=args.rollback)
simulated = apply_locally(d, patch)
dry = patch_remote(patch, dry_run=True)
if dry['spec']['template'] != simulated['spec']['template']:
raise ValueError('server-dry-run-unexpected-change')
receipt = {'proposal_id': proposal['proposal_id'], 'proposal_sha256': hashlib.sha256(args.proposal.read_bytes()).hexdigest(),
'observed_at': datetime.datetime.now(datetime.timezone.utc).isoformat(), 'result': 'dry-run-verified',
'old_pod_uid': pod['pod_uid'], 'old_generation': d['metadata']['generation'],
'server_dry_run_exact_match': True, 'patch': patch,
'original_template_sha256': digest(d['spec']['template']),
'resulting_template_sha256': digest(simulated['spec']['template']),
'no_secret_objects_read': True, 'mode': 'rollback' if args.rollback else 'apply' if args.apply else 'preflight'}
args.receipt.write_text(json.dumps(receipt, indent=2) + '\n')
if not args.apply and not args.rollback:
print('Exact additive patch and ConfigMap server dry-runs passed; no live mutation.')
return
submitted = False
try:
if not found.strip() and not args.rollback:
native.command(['create', '-f', '-', '-o', 'name'], payload=json.dumps(cm).encode())
receipt['configmap_created'] = True
submitted = True
receipt['patch_submission_started'] = True
changed = patch_remote(patch)
receipt['applied_generation'] = changed['metadata']['generation']
expected = DEFAULT if args.rollback else TEMPLATE
ready, newpod = wait_ready(original, expected)
if selected_template(newpod) != expected:
raise ValueError('runtime-selection-not-adopted')
if not args.rollback:
receipt['inheritance'] = verify_inheritance(newpod, proposal)
receipt.update(result='verified', new_pod_uid=newpod['pod_uid'], generation=ready['metadata']['generation'],
selected_template=expected, existing_22_profiles_preserved=True,
binaries_unchanged=True, health_after=True)
except (ValueError, KeyError, subprocess.SubprocessError):
receipt['result'] = 'unverified'
if submitted and not args.rollback:
try:
current = deployment()
observed_digest = digest(current['spec']['template'])
if observed_digest == receipt['original_template_sha256']:
receipt['rollback'] = 'patch was not applied; original deployment preserved; any created ConfigMap remains inert'
elif observed_digest == receipt['resulting_template_sha256']:
recovery = patch_for(current, rollback=True)
patch_remote(recovery)
_, recovered = wait_ready(original, DEFAULT)
receipt['rollback'] = 'default template selection restored; original 22 hooks preserved; ConfigMap retained inert'
else:
raise ValueError('concurrent-template-change-needs-owner-review')
except (ValueError, KeyError, subprocess.SubprocessError):
receipt['rollback'] = 'unverified; abort owner must inspect exact deployment and receipt'
raise
finally:
args.receipt.write_text(json.dumps(receipt, indent=2) + '\n')
print('Template ' + receipt['mode'] + ' verified; existing profiles and service health preserved.')
if __name__ == '__main__':
try:
main()
except (ValueError, OSError, KeyError, subprocess.SubprocessError):
raise SystemExit('Template operation refused or incomplete; inspect value-free receipt. Raw diagnostics suppressed.')

View file

@ -0,0 +1,121 @@
#!/usr/bin/env python3
"""Conditional startup-guard rollout, with real installed-scanner receive probes."""
import argparse
import datetime
import hashlib
import importlib.util
import json
from pathlib import Path
import time
HERE = Path(__file__).parent
s = importlib.util.spec_from_file_location('template', HERE / 'activate_forge_template.py')
t = importlib.util.module_from_spec(s)
s.loader.exec_module(t)
def native_probe(pod, suffix):
base = '/tmp/custodian-installed-custody-' + suffix
created = False
try:
t.native.execute(pod, 'sh', '-c', 'umask 077; test ! -e "$1" && mkdir "$1" && printf "%s" custody-probe > "$1/.owner"', 'sh', base)
created = True
raw = t.native.execute(pod, 'sh', '-s', '--', base, pod['existing_dispatcher'],
payload=(HERE / 'probe_forge_installed.sh').read_bytes(), timeout=90)
return json.loads(raw)
finally:
if created:
t.native.execute(pod, 'sh', '-c', 'test "$(cat "$1/.owner")" = custody-probe && rm -rf -- "$1" && test ! -e "$1"', 'sh', base)
def patch_for(d, candidate, rollback=False):
current = d['spec']['template']['spec']['initContainers']
matches = [i for i,c in enumerate(current) if c['name'] == candidate['name']]
patch = [{'op':'test', 'path':'/metadata/uid', 'value':d['metadata']['uid']},
{'op':'test', 'path':'/metadata/resourceVersion', 'value':d['metadata']['resourceVersion']}]
if rollback:
if matches != [0] or current[0] != candidate:
raise ValueError('rollback-candidate-differs')
patch.append({'op':'remove', 'path':'/spec/template/spec/initContainers/0'})
else:
if matches:
raise ValueError('candidate-already-present')
# apply_locally's JSON array add support appends only; replace exact guarded list.
patch.append({'op':'replace', 'path':'/spec/template/spec/initContainers', 'value':[candidate,*current]})
return patch
def verify(pod, suffix):
t.check_artifacts(pod)
stats = []
for item in pod['artifacts'][:2]:
stat = t.native.execute(pod, 'stat', '-c', '%a %u %g', pod['version_directory']+'/'+item['name']).decode().strip()
if stat != '755 1000 1000':
raise ValueError('custody-mode-wrong')
stats.append({'name':item['name'], 'stat':stat})
return {'artifacts':stats, 'receive_probe':native_probe(pod, suffix)}
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--proposal',type=Path,required=True)
parser.add_argument('--receipt',type=Path,required=True)
group=parser.add_mutually_exclusive_group()
group.add_argument('--apply',action='store_true')
group.add_argument('--rollback',action='store_true')
group.add_argument('--restart-proof',action='store_true')
args=parser.parse_args()
p=json.loads(args.proposal.read_text())
for file,expected in p['executor_hashes'].items():
if hashlib.sha256((HERE/file).read_bytes()).hexdigest()!=expected:
raise ValueError('executor-changed')
original=json.loads((HERE/'receive-hook-proposal.json').read_text())
c=json.loads((HERE/'forge-custody-init.json').read_text())
d=t.deployment()
expected=p['patched_template_digest'] if args.rollback or args.restart_proof else p['original_template_digest']
if d['metadata']['uid']!=p['deployment_uid'] or t.digest(d['spec']['template'])!=expected:
raise ValueError('deployment-changed')
pod=t.target_pod(d,original)
if args.restart_proof:
patch=[{'op':'test','path':'/metadata/resourceVersion','value':d['metadata']['resourceVersion']},
{'op':'add','path':'/spec/template/metadata/annotations/custodian.coulomb.social~1custody-proof','value':p['restart_marker']}]
else:
patch=patch_for(d,c,args.rollback)
local=t.apply_locally(d,patch)
dry=t.patch_remote(patch,dry_run=True)
if t.digest(local['spec']['template'])!=t.digest(dry['spec']['template']):
raise ValueError('dry-run-differs')
receipt={'proposal_id':p['proposal_id'],'revision':p['revision'],
'proposal_sha256':hashlib.sha256(args.proposal.read_bytes()).hexdigest(),
'at':datetime.datetime.now(datetime.timezone.utc).isoformat(),
'mode':'rollback' if args.rollback else 'restart-proof' if args.restart_proof else 'apply' if args.apply else 'dry-run',
'dry_run_verified':True,'before_pod_uid':pod['pod_uid'],'result':'prepared'}
args.receipt.write_text(json.dumps(receipt,indent=2)+'\n')
if not (args.apply or args.rollback or args.restart_proof):
receipt['before_probe']=verify(pod,'before')
receipt['result']='dry-run-verified'
else:
applied=t.patch_remote(patch)
receipt.update(result='rollout-started', generation=applied['metadata']['generation'])
args.receipt.write_text(json.dumps(receipt,indent=2)+'\n')
after,new=t.wait_ready(original,t.TEMPLATE)
if new['pod_uid']==pod['pod_uid'] or t.digest(after['spec']['template'])!=t.digest(local['spec']['template']):
raise ValueError('replacement-not-exact')
receipt['after_pod_uid']=new['pod_uid']
if args.rollback:
# Removing the init guard permits fsGroup to widen modes again; repair explicitly.
t.native.execute(new,'sh','-s','--','apply',payload=(HERE/'forge-custody-guard.sh').read_bytes())
else:
raw=json.loads(t.native.command(['-n','forgejo','get','pod',new['pod'],'-o','json']))
status=next(x for x in raw['status']['initContainerStatuses'] if x['name']==c['name'])
if status['state']['terminated']['exitCode']!=0:
raise ValueError('guard-not-completed')
receipt['guard_completed']=True
receipt['after_probe']=verify(new,'after-'+str(after['metadata']['generation']))
receipt['result']='verified'
args.receipt.write_text(json.dumps(receipt,indent=2)+'\n')
print(json.dumps(receipt))
if __name__=='__main__':
main()

View file

@ -0,0 +1,22 @@
#!/bin/sh
set +vx
set -eu
test "$(id -u)" = 1000
test -d /data && test ! -L /data
test -d /data/security && test ! -L /data/security
test -d /data/security/receive-secret-scan && test ! -L /data/security/receive-secret-scan
test -d /data/security/receive-secret-scan/20261012-57a0642b6251 && test ! -L /data/security/receive-secret-scan/20261012-57a0642b6251
test -f /data/security/receive-secret-scan/20261012-57a0642b6251/receive-secret-scan && test ! -L /data/security/receive-secret-scan/20261012-57a0642b6251/receive-secret-scan
test "$(stat -c %u /data/security/receive-secret-scan/20261012-57a0642b6251/receive-secret-scan)" = 1000
test "$(sha256sum /data/security/receive-secret-scan/20261012-57a0642b6251/receive-secret-scan | cut -d " " -f 1)" = 57a0642b625105440fe146fceb4d244fd2dd08a75beb316831675643e6cc27ec
case "$(stat -c %a /data/security/receive-secret-scan/20261012-57a0642b6251/receive-secret-scan)" in 755|775) ;; *) exit 21;; esac
test -f /data/security/receive-secret-scan/20261012-57a0642b6251/gitleaks && test ! -L /data/security/receive-secret-scan/20261012-57a0642b6251/gitleaks
test "$(stat -c %u /data/security/receive-secret-scan/20261012-57a0642b6251/gitleaks)" = 1000
test "$(sha256sum /data/security/receive-secret-scan/20261012-57a0642b6251/gitleaks | cut -d " " -f 1)" = 88f91962aa2f93ac6ab281d553b9e125f5197bbbce38f9f2437f7299c32e5509
case "$(stat -c %a /data/security/receive-secret-scan/20261012-57a0642b6251/gitleaks)" in 755|775) ;; *) exit 21;; esac
if [ "${1:-check}" = apply ]; then
chmod 755 /data/security/receive-secret-scan/20261012-57a0642b6251/receive-secret-scan
test "$(stat -c %a /data/security/receive-secret-scan/20261012-57a0642b6251/receive-secret-scan)" = 755
chmod 755 /data/security/receive-secret-scan/20261012-57a0642b6251/gitleaks
test "$(stat -c %a /data/security/receive-secret-scan/20261012-57a0642b6251/gitleaks)" = 755
fi

View file

@ -0,0 +1,43 @@
{
"name": "verify-secret-scanner",
"image": "code.forgejo.org/forgejo/forgejo@sha256:691657c7e64af7730a2e885e6d022cf16ae60724931c5392bd61838afadcaeba",
"imagePullPolicy": "IfNotPresent",
"command": [
"sh",
"-c",
"#!/bin/sh\nset +vx\nset -eu\ntest \"$(id -u)\" = 1000\ntest -d /data && test ! -L /data\ntest -d /data/security && test ! -L /data/security\ntest -d /data/security/receive-secret-scan && test ! -L /data/security/receive-secret-scan\ntest -d /data/security/receive-secret-scan/20261012-57a0642b6251 && test ! -L /data/security/receive-secret-scan/20261012-57a0642b6251\ntest -f /data/security/receive-secret-scan/20261012-57a0642b6251/receive-secret-scan && test ! -L /data/security/receive-secret-scan/20261012-57a0642b6251/receive-secret-scan\ntest \"$(stat -c %u /data/security/receive-secret-scan/20261012-57a0642b6251/receive-secret-scan)\" = 1000\ntest \"$(sha256sum /data/security/receive-secret-scan/20261012-57a0642b6251/receive-secret-scan | cut -d \" \" -f 1)\" = 57a0642b625105440fe146fceb4d244fd2dd08a75beb316831675643e6cc27ec\ncase \"$(stat -c %a /data/security/receive-secret-scan/20261012-57a0642b6251/receive-secret-scan)\" in 755|775) ;; *) exit 21;; esac\ntest -f /data/security/receive-secret-scan/20261012-57a0642b6251/gitleaks && test ! -L /data/security/receive-secret-scan/20261012-57a0642b6251/gitleaks\ntest \"$(stat -c %u /data/security/receive-secret-scan/20261012-57a0642b6251/gitleaks)\" = 1000\ntest \"$(sha256sum /data/security/receive-secret-scan/20261012-57a0642b6251/gitleaks | cut -d \" \" -f 1)\" = 88f91962aa2f93ac6ab281d553b9e125f5197bbbce38f9f2437f7299c32e5509\ncase \"$(stat -c %a /data/security/receive-secret-scan/20261012-57a0642b6251/gitleaks)\" in 755|775) ;; *) exit 21;; esac\nif [ \"${1:-check}\" = apply ]; then\n chmod 755 /data/security/receive-secret-scan/20261012-57a0642b6251/receive-secret-scan\n test \"$(stat -c %a /data/security/receive-secret-scan/20261012-57a0642b6251/receive-secret-scan)\" = 755\n chmod 755 /data/security/receive-secret-scan/20261012-57a0642b6251/gitleaks\n test \"$(stat -c %a /data/security/receive-secret-scan/20261012-57a0642b6251/gitleaks)\" = 755\nfi\n",
"sh",
"apply"
],
"securityContext": {
"runAsUser": 1000,
"runAsGroup": 1000,
"runAsNonRoot": true,
"allowPrivilegeEscalation": false,
"readOnlyRootFilesystem": true,
"capabilities": {
"drop": [
"ALL"
]
}
},
"resources": {
"requests": {
"cpu": "10m",
"memory": "32Mi"
},
"limits": {
"cpu": "100m",
"memory": "64Mi"
}
},
"volumeMounts": [
{
"name": "data",
"mountPath": "/data/security",
"subPath": "security"
}
],
"terminationMessagePath": "/dev/termination-log",
"terminationMessagePolicy": "File"
}

View file

@ -0,0 +1,19 @@
{
"proposal_id": "FORGE6-TEMPLATE-LIFECYCLE-001",
"revision": 2,
"original_revision": 1,
"original_digest_sha256": "7eddea55359b25c5ad307ed8ccd392dfadfe167d65cbe34b02f1955350bbcb36",
"kind": "failed-trial-recovery",
"pod_uid": "f818d3f5-5c3d-4a2a-8e78-50e10b10d2ce",
"executor_sha256": "1cf0f846fd4967e6a3a8b2a3844a7736a053c34fa3a9c54d16ea7ba3c18765ad",
"guard_sha256": "75fdd0722a8749d5038f9e575c547fe39f41f599c40344230dbfdfde489c0f1c",
"diff_ref": "forge-custody-guard.sh: fixed two admitted, regular, owned, hash-pinned files mode 0775 to 0755; check mode has no mutations",
"authorization_ref": "canon/standards/build-mode-change-authorization_v0.1.md",
"rollback": "No content or owner change. Exact inverse chmod 0775 only if restoring the failed pre-repair state is required by abort owner; that deliberately re-blocks pushes. Preferred safe recovery is retain hash-verified 0755 and stop on any artifact mismatch. No hook disabling. Durable restart protection follows separately.",
"abort_owner": "railiance-forge; executor custodian-codex",
"receipt_path": "docs/evidence/2026-10-13-netkingdom-security/forge-custody-repair.json",
"expected_result": "Both exact artifacts 0755, then ordinary SSH source push succeeds; restart durability not yet claimed.",
"cost_estimate_eur": 0,
"risk_estimate": "Only removes group write from two existing executable files. No rollout, content, account, policy scope, or spend change. Any mismatch aborts before chmod. Concurrent artifact replacement not expected during this single-agent repair.",
"promotion_evidence": false
}

View file

@ -0,0 +1,33 @@
{
"proposal_id": "FORGE6-TEMPLATE-LIFECYCLE-001",
"revision": 4,
"original_revision": 1,
"original_digest_sha256": "7eddea55359b25c5ad307ed8ccd392dfadfe167d65cbe34b02f1955350bbcb36",
"deployment_uid": "e8eff0aa-64bb-4b44-9a80-a66612986355",
"original_template_digest": "b10384c9564531bff2ca756854b998fdc520f0a25f6acf195f024f675fc3b3e6",
"executor_hashes": {
"deploy_forge_custody.py": "5125b29ac20ab2b0a9bfcf686c2bb2e6e9c143f716bf119035960b345db497b9",
"probe_forge_installed.sh": "66b571a33822cbd58a4956aef3b2ac484d07ab887c296cd2c9ece354117e9440",
"forge-custody-init.json": "cccb9999785fe0151a973d9a8b554975daf17c91432ed3e8735e432f3cd80e8f",
"forge-custody-guard.sh": "75fdd0722a8749d5038f9e575c547fe39f41f599c40344230dbfdfde489c0f1c",
"activate_forge_template.py": "ef2b38f655d5520d69dcd69cdb4997eaad4b50773f32c1d798d16331b204c789",
"verify_receive_native.py": "c9120122c6f6f0b7c41fe4fa975e5a2a340e776885d852b861b8c13b7664adcd",
"activate_receive_hooks.py": "9b01ee383e16463981112226e19156b0de59af2d03dd71b9403e144e280a9fb3",
"restart_forge_custody.py": "104ed6ce5f88739f64b6f6f62e2869252408a390b6780117a499dcc9be0496f2"
},
"authorization_ref": "canon/standards/build-mode-change-authorization_v0.1.md",
"diff_ref": "Only add owned custody-proof pod-template annotation; no config or image change.",
"rollback": "Marker may remain inert. To undo guard, conditionally remove only custody-proof annotation matching this marker, wait for replacement, then use revision3 --rollback. No workload spec reversion from a stale snapshot.",
"abort_owner": "railiance-forge; executor custodian-codex; source railiance-apps",
"receipt_path": "docs/evidence/2026-10-13-netkingdom-security/forge-custody-restart.json",
"expected_result": "New pod runs verified guard to completion; same exact image/artifacts and 22 hooks; modes755; inherited native receive clean, secret refusal, quarantine and recovery pass. Repeat with another replacement pod.",
"restart_marker": "FORGE6-TEMPLATE-LIFECYCLE-001-r4",
"cost_estimate_eur": 0,
"risk_estimate": "Recreate briefly interrupts forge. Existing init maximum100m/128Mi exceeds new10m/32Mi so no increased scheduling reservation; node86percent requests. Bound240s readiness. Mismatched artifacts deliberately block startup; rollback restores prior deployment plus verified755 repair. Mount only existing security subtree, no credentials/RBAC/policy/image changes. No spend.",
"promotion_evidence": false,
"preparation_refinement": {
"previous_revision": 3,
"failure": "Second restart dry-run refused because original local patch helper did not decode JSON Pointer ~1. No second restart occurred. New executor replaces the annotations map while preserving existing fields; server equality remains required."
},
"annotation_key": "custodian.coulomb.social/custody-proof"
}

View file

@ -0,0 +1,28 @@
{
"proposal_id": "FORGE6-TEMPLATE-LIFECYCLE-001",
"revision": 3,
"original_revision": 1,
"original_digest_sha256": "7eddea55359b25c5ad307ed8ccd392dfadfe167d65cbe34b02f1955350bbcb36",
"deployment_uid": "e8eff0aa-64bb-4b44-9a80-a66612986355",
"original_template_digest": "73f6c1ef484c60f93285d61e6df6651b4f1268fafb3e470f35f450bce4f34aba",
"patched_template_digest": "b27e5658ee3d6bcc1933c5d0f0a0acd88c16c54865096c0aae73d689e1066242",
"executor_hashes": {
"deploy_forge_custody.py": "5125b29ac20ab2b0a9bfcf686c2bb2e6e9c143f716bf119035960b345db497b9",
"probe_forge_installed.sh": "66b571a33822cbd58a4956aef3b2ac484d07ab887c296cd2c9ece354117e9440",
"forge-custody-init.json": "833c2a5428999a8f2d0cf8319ca69bf1bf8c8017809bfe73450cdf9d31d5a8d4",
"forge-custody-guard.sh": "75fdd0722a8749d5038f9e575c547fe39f41f599c40344230dbfdfde489c0f1c",
"activate_forge_template.py": "ef2b38f655d5520d69dcd69cdb4997eaad4b50773f32c1d798d16331b204c789",
"verify_receive_native.py": "c9120122c6f6f0b7c41fe4fa975e5a2a340e776885d852b861b8c13b7664adcd",
"activate_receive_hooks.py": "9b01ee383e16463981112226e19156b0de59af2d03dd71b9403e144e280a9fb3"
},
"authorization_ref": "canon/standards/build-mode-change-authorization_v0.1.md",
"diff_ref": "forge-custody-init.json plus owner helm/forgejo-values.yaml; additive first init container, no other changes",
"rollback": "Same executor --rollback under exact patched-template digest; removes only this init container, waits for Ready then verifies hashes and restores 0755 via the exact guard. After restart-proof remove only owned custody-proof annotation before rollback. Reconcile source by removing preExtraInitContainers before future Helm apply. If rollout cannot become Ready, abort owner may remove exact added init container after inspecting unchanged template; no hook bypass.",
"abort_owner": "railiance-forge; executor custodian-codex; source railiance-apps",
"receipt_path": "docs/evidence/2026-10-13-netkingdom-security/forge-custody-rollout.json",
"expected_result": "New pod runs verified guard to completion; same exact image/artifacts and 22 hooks; modes755; inherited native receive clean, secret refusal, quarantine and recovery pass. Repeat with another replacement pod.",
"restart_marker": "FORGE6-TEMPLATE-LIFECYCLE-001-r3",
"cost_estimate_eur": 0,
"risk_estimate": "Recreate briefly interrupts forge. Existing init maximum100m/128Mi exceeds new10m/32Mi so no increased scheduling reservation; node86percent requests. Bound240s readiness. Mismatched artifacts deliberately block startup; rollback restores prior deployment plus verified755 repair. Mount only existing security subtree, no credentials/RBAC/policy/image changes. No spend.",
"promotion_evidence": false
}

View file

@ -0,0 +1,32 @@
{
"proposal_id": "FORGE6-TEMPLATE-LIFECYCLE-001",
"revision": 3,
"original_revision": 1,
"original_digest_sha256": "7eddea55359b25c5ad307ed8ccd392dfadfe167d65cbe34b02f1955350bbcb36",
"deployment_uid": "e8eff0aa-64bb-4b44-9a80-a66612986355",
"original_template_digest": "73f6c1ef484c60f93285d61e6df6651b4f1268fafb3e470f35f450bce4f34aba",
"patched_template_digest": "b10384c9564531bff2ca756854b998fdc520f0a25f6acf195f024f675fc3b3e6",
"executor_hashes": {
"deploy_forge_custody.py": "5125b29ac20ab2b0a9bfcf686c2bb2e6e9c143f716bf119035960b345db497b9",
"probe_forge_installed.sh": "66b571a33822cbd58a4956aef3b2ac484d07ab887c296cd2c9ece354117e9440",
"forge-custody-init.json": "cccb9999785fe0151a973d9a8b554975daf17c91432ed3e8735e432f3cd80e8f",
"forge-custody-guard.sh": "75fdd0722a8749d5038f9e575c547fe39f41f599c40344230dbfdfde489c0f1c",
"activate_forge_template.py": "ef2b38f655d5520d69dcd69cdb4997eaad4b50773f32c1d798d16331b204c789",
"verify_receive_native.py": "c9120122c6f6f0b7c41fe4fa975e5a2a340e776885d852b861b8c13b7664adcd",
"activate_receive_hooks.py": "9b01ee383e16463981112226e19156b0de59af2d03dd71b9403e144e280a9fb3"
},
"authorization_ref": "canon/standards/build-mode-change-authorization_v0.1.md",
"diff_ref": "forge-custody-init.json plus owner helm/forgejo-values.yaml; additive first init container, no other changes",
"rollback": "Same executor --rollback under exact patched-template digest; removes only this init container, waits for Ready then verifies hashes and restores 0755 via the exact guard. After restart-proof remove only owned custody-proof annotation before rollback. Reconcile source by removing preExtraInitContainers before future Helm apply. If rollout cannot become Ready, abort owner may remove exact added init container after inspecting unchanged template; no hook bypass.",
"abort_owner": "railiance-forge; executor custodian-codex; source railiance-apps",
"receipt_path": "docs/evidence/2026-10-13-netkingdom-security/forge-custody-rollout.json",
"expected_result": "New pod runs verified guard to completion; same exact image/artifacts and 22 hooks; modes755; inherited native receive clean, secret refusal, quarantine and recovery pass. Repeat with another replacement pod.",
"restart_marker": "FORGE6-TEMPLATE-LIFECYCLE-001-r3",
"cost_estimate_eur": 0,
"risk_estimate": "Recreate briefly interrupts forge. Existing init maximum100m/128Mi exceeds new10m/32Mi so no increased scheduling reservation; node86percent requests. Bound240s readiness. Mismatched artifacts deliberately block startup; rollback restores prior deployment plus verified755 repair. Mount only existing security subtree, no credentials/RBAC/policy/image changes. No spend.",
"promotion_evidence": false,
"preparation_refinement": {
"original_candidate_ref": "forge-custody-rollout-pre-defaults.json",
"reason": "Server dry-run defaulted terminationMessagePath/Policy; candidate now declares exact defaults. No live change occurred."
}
}

View file

@ -0,0 +1,145 @@
# Forgejo template inheritance
FORGE-WP-0006-T02, coordinated by CUST-WP-0080 G07. Proposal
`forge-template-proposal.json` records the exact source, executor, target,
verification and recovery for trial `FORGE6-TEMPLATE-LIFECYCLE-001`.
Standing build-mode blue-lane authorization applies; this is not an autopilot
grant or a claim of independent owner approval.
The October 13 execution reached Deployment generation 12 Ready, preserving the
image and all 22 previously installed profiles. The live Forgejo global Git
configuration selects `/opt/forge-secret-template`. Native Git initialization
using that configuration inherited the exact regular, executable scanner hook;
malformed input was refused and the owned temporary repository was removed.
The execution receipt is
`docs/evidence/2026-10-13-netkingdom-security/forge-template-activation.json`.
## Durable source
railiance-apps commit `390a30a` owns the immutable non-secret ConfigMap manifest,
Helm volume/mount/encoded environment setting, and the Make prerequisite that
installs the ConfigMap before a future Helm deployment. A file `subPath` mount
is intentional: Git must copy a regular executable, not a ConfigMap projection
symlink. The hook still invokes the two previously admitted binaries on the
existing data PVC. Backup/restore must preserve or re-admit those exact binaries;
the template alone cannot recreate them. Missing scanner artifacts refuse
pushes, rather than disabling detection.
The live change used a conditional JSON Patch because the running Deployment
contains changes newer than its last Helm revision. Reapplying that old Helm
release was outside scope. The patch added one volume, four file mounts and one
configuration environment variable; resourceVersion/UID tests guard the write.
Images, replica/strategy/resource settings, identities, existing environment
entries and Secret objects were unchanged. Initial-only admin bootstrap and
the existing admin row were verified before the restart. Configuration conversion
uses the existing chart's environment-to-ini process.
## Recovery
Do not replay the apply operation after activation. It refuses the changed
Deployment template. To recover this exact change, after checking the current
target and the source record:
```sh
python3 docs/changes/CUST-WP-0080/activate_forge_template.py \
--proposal docs/changes/CUST-WP-0080/forge-template-proposal.json \
--receipt /tmp/forge-template-rollback.json --rollback
```
Rollback removes only the exact added mounts/volume and sets the added environment
variable to `/usr/share/git-core/templates`. Merely removing the environment
variable would leave the saved application/global-Git setting behind. The
ConfigMap remains inert; original and already inherited repository hooks remain.
Readiness, health, exact original 22 profiles, binary custody and default template
selection must pass again. Reconcile the owner Helm source to that explicit default
before another deployment; the original source otherwise intentionally re-enables
the desired control. Unknown concurrent changes cause refusal and require the
railiance-forge abort owner to review the current target, not overwrite it.
Automatic recovery after an incomplete apply is limited to a Deployment whose
template matches the exact attempted result. An uncertain submission checks
current state before deciding whether recovery is needed. The readiness budget
is 240 seconds per rollout/recovery; failure remains unverified in the receipt.
The original October 12 receive-hook activation retains its old pod binding.
Do not edit that old proposal to pretend it targets the replacement pod.
## Remaining acceptance
This closes configuration persistence and native Git inheritance for future
creation paths that use Forgejo's configured Git initialization. It does not prove
an authenticated production API/browser creation, SSO journey or imported/mirrored
content check. Imports and mirrors use clone/fetch; a receive hook is insufficient
for those paths. The refreshed inventory has 166 repositories: 22 installed
profiles and 144 builtin-only inventories. Existing repository expansion, CI
authority, private finding-to-response, historical/LFS/log/artifact coverage and
the applicable DSM requirements remain open in the existing owner plans.
Validation: 30 isolated exact-image lifecycle/configuration/rollback checks, six
conditional-patch/recovery tests, non-secret Helm rendering and matching server
dry-run. Local test containers were removed. The isolated environment used SQLite;
the deployed proof is scoped to configuration, restart, Git inheritance and the
preserved existing profiles.
## October 13 custody regression and recovery
The original generation-12 receipt proves only its listed checks. Ordinary source
push acceptance subsequently failed: Kubernetes volume processing left the
scanner binaries group-writable (0775), which the scanner correctly refuses.
The trial remains failed; later corrections are recovery revisions of the same
proposal, not new unchanged successes. Do not use the original malformed-input
check as evidence that the scanner can open and scan its admitted executable.
- Revision 2: `forge-custody-repair-proposal.json` and
`repair_forge_custody.py` restore only the two hash-pinned files to 0755.
- Revision 3: `forge-custody-rollout-proposal.json` and
`deploy_forge_custody.py` add the unprivileged first init container. The exact
Helm source is railiance-apps `50b66c4`; only the existing security subtree is
mounted. Kubernetes defaults are declared explicitly. The first startup and
real installed-scanner receive probe passed.
- Revision 4: `forge-custody-restart-proposal.json` and
`restart_forge_custody.py` repeat acceptance on another pod. Use its receipt to
determine completion. The revision-3 `--restart-proof` preparation is withdrawn:
its local JSON Pointer helper did not decode the annotation's slash. Its dry-run
refused before mutation. Original executed files and hashes remain preserved.
The guard checks all artifact hashes and owners before changing either mode. It
refuses unexpected permissions, symlinks and mismatched binaries. Missing or
changed artifacts stop startup for investigation; never disable scanning to make
that check pass. The six isolated custody checks simulate fsGroup mode changes.
Native probes use temporary repositories and the real inherited installed hook;
production builtin authorization is excluded there. A normal source sync push is
required separately for authenticated SSH acceptance.
For rollback after revision 4, first conditionally remove only the owned
`custodian.coulomb.social/custody-proof` annotation whose value is the revision-4
marker. Review the resulting template against revision 3's patched digest; wait
for that replacement to finish. Then revision 3 `--rollback` removes only the
exact guard, waits for readiness, and repairs the exact binaries to 0755 after
remount. The next unguarded restart may widen modes again, so rollback is temporary
recovery and does not establish durable acceptance. Reconcile the owner Helm
source before its next deployment. Any unexpected deployment or artifact change
stops this procedure for the named abort owner. Original template rollback is a
separate operation and must not silently remove the custody repair.
### Exact original proposal storage
`forge-template-proposal.json` and `forge-template-source-render.json` are now
lossless archival envelopes. A public ConfigMap source checksum under a filename
containing `secret` triggered the generic API-key detector. The archive keeps
paths and SHA-256 values in separate fields and retains all original metadata.
No detector ignore, exception or rule change was added. The executed original
proposal digest remains unchanged; it is the digest of the restored bytes, not
of the envelope. `forge-custody-metadata-triage.json` records this classification.
Restore the historical proposal to a new private file before using its original
executor (which remains byte-for-byte unchanged):
```sh
python3 docs/changes/CUST-WP-0080/restore_forge_proposal.py \
docs/changes/CUST-WP-0080/forge-template-proposal.json \
/tmp/forge-template-original.json
```
The helper refuses an existing destination or any reconstruction whose digest
differs. Restoration does not authorize replaying a stale live proposal.

View file

@ -0,0 +1,63 @@
{
"archival_format": "json-checksum-map-entries-v1",
"original_sha256": "7eddea55359b25c5ad307ed8ccd392dfadfe167d65cbe34b02f1955350bbcb36",
"converted_map_field": "owner_source_hashes",
"reason": "Public non-secret ConfigMap source checksum was classified as generic-api-key when its filename was used as a JSON property name. Structured path/sha256 entries preserve exact historical reconstruction without detector exceptions.",
"document": {
"proposal_id": "FORGE6-TEMPLATE-LIFECYCLE-001",
"revision": 1,
"prepared_at": "2026-10-13T11:56:51.534869+00:00",
"deployment_uid": "e8eff0aa-64bb-4b44-9a80-a66612986355",
"original_template_sha256": "18855f7b4c78f79117884684e44550406c72fc7be08c0b8e61ec16cfcb855746",
"original_generation": 11,
"executor_hashes": {
"activate_forge_template.py": "ef2b38f655d5520d69dcd69cdb4997eaad4b50773f32c1d798d16331b204c789",
"verify_receive_native.py": "c9120122c6f6f0b7c41fe4fa975e5a2a340e776885d852b861b8c13b7664adcd",
"activate_receive_hooks.py": "9b01ee383e16463981112226e19156b0de59af2d03dd71b9403e144e280a9fb3",
"receive-hook-proposal.json": "9c8ea3b7fbe16a997359432af7bcf09f429881fd23cc567188c21129e80ccf8d"
},
"owner_source_hashes": [
{
"path": "/home/worsch/railiance-apps/helm/forgejo-values.yaml",
"sha256": "e97aa62fe767f4dc55f2c68c7ae1b69e61e632826a8ba3ccb4ffaca6e302174c"
},
{
"path": "/home/worsch/railiance-apps/Makefile",
"sha256": "6bd20555631eb4c22e0729779d6c24d6888a4e6d555ab1f03083697016d42b15"
},
{
"path": "/home/worsch/railiance-apps/manifests/forgejo-secret-scan-template.yaml",
"sha256": "8fad71aff5a25d8e31289072b9c06c6ec74ac74fedafc54dde7177bfe187c96a"
}
],
"configmap": {
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": {
"name": "forgejo-secret-template-b1d7cf8cd13f",
"namespace": "forgejo",
"labels": {
"app.kubernetes.io/part-of": "forgejo",
"custodian.coulomb.social/workplan": "FORGE-WP-0006"
}
},
"immutable": true,
"data": {
"hook": "#!/bin/sh\nset +vx\nset -eu\nexec /data/security/receive-secret-scan/20261012-57a0642b6251/receive-secret-scan --scanner /data/security/receive-secret-scan/20261012-57a0642b6251/gitleaks\n"
}
},
"isolated_receipt": "docs/evidence/2026-10-13-netkingdom-security/forge-template-isolated.json",
"lane": "blue",
"authorization_ref": "canon/standards/build-mode-change-authorization_v0.1.md",
"diff_ref": "owner source patch; docs/evidence/2026-10-13-netkingdom-security/forge-template-source-render.json; exact additive JSON Patch in server dry-run receipt",
"rollback": "Same executor --rollback with original proposal. Removes only exact added mounts and volume, replaces added init setting with /usr/share/git-core/templates, waits for healthy replacement and verifies original 22 hooks/binaries. Keeps immutable ConfigMap inert and any already inherited repository hooks. If rollback used, reconcile owner source to explicit default selection before another Helm deploy. Automatic recovery only when current deployment template exactly matches the attempted change; unknown concurrent changes require abort-owner review.",
"abort_owner": "railiance-forge; executing custodian-codex; release source railiance-apps",
"receipt_path": "docs/evidence/2026-10-13-netkingdom-security/forge-template-activation.json",
"target": "railiance01 namespace forgejo: Deployment forgejo-gitea and immutable ConfigMap forgejo-secret-template-b1d7cf8cd13f",
"expected_result": "Same image and artifact hashes, Recreate replacement Ready, template selected in actual Forgejo global Git configuration, regular/executable inherited hook in temporary native Git repository, existing 22 hook inventories unchanged, health passing. No new production repository, identity, credential or publication.",
"cost_estimate_eur": 0,
"risk_estimate": "No new resource requests, spend commitments, RBAC or credential scope. Existing Recreate deployment incurs a brief forge interruption; abort after 240-second readiness budget, restore default template under conditional rollback with its own 240-second verification budget. Unknown state stops automatic recovery. New repository writes can refuse if scanner missing, overloaded or input unsupported; existing 22 profiles unchanged. Recorded node CPU requests 3460m/4000m and memory 9472Mi (59%).",
"verification_limit": "Native config and Git inheritance, not authenticated production HTTP/SSO/frontend repository creation. Initial 22 repositories remain sole existing-repository activation scope; other 143, imported/mirrored history, CI and private response remain open.",
"promotion_evidence": false
}
}

View file

@ -0,0 +1,43 @@
#!/bin/sh
# Caller owns a unique temporary directory and removes it by marker afterwards.
set +vx
set -eu
base=$1
dispatcher=$2
cd "$base"
umask 077
export HOME="$base" GIT_CONFIG_NOSYSTEM=1 GIT_CONFIG_GLOBAL=/dev/null GIT_TERMINAL_PROMPT=0
git -c include.path=/data/home/.gitconfig init -q --bare server.git
git init -q -b main client
git -C client config core.hooksPath "$base/no-hooks"
git -C client config user.name 'Custodian isolated receive probe'
git -C client config user.email 'probe@example.test'
test -x server.git/hooks/pre-receive.d/10-custodian-secret-scan
cp "$dispatcher" server.git/hooks/pre-receive
chmod 755 server.git/hooks/pre-receive
# No production database identity: native dispatcher with only the inherited scan hook.
printf 'clean baseline\n' > client/source.txt
git -C client add source.txt
git -C client commit -qm 'isolated clean baseline'
git -C client push "$base/server.git" HEAD:refs/heads/main > clean.out 2>&1
grep -q '"status":"clean"' clean.out
initial=$(git -C client rev-parse HEAD)
ssh-keygen -q -t ed25519 -N '' -f "$base/synthetic-key" >/dev/null 2>&1
cp synthetic-key client/fixture.txt
git -C client add fixture.txt
git -C client commit -qm 'isolated synthetic fixture'
rejected=$(git -C client rev-parse HEAD)
git -C client rm -q fixture.txt
git -C client commit -qm 'remove fixture from final tree'
if git -C client push "$base/server.git" HEAD:refs/heads/main > blocked.out 2>&1; then exit 22; fi
grep -q '"status":"blocked"' blocked.out
if grep -q 'BEGIN OPENSSH PRIVATE KEY' blocked.out; then exit 23; fi
if git -C server.git cat-file -e "$rejected" >/dev/null 2>&1; then exit 24; fi
test "$(git -C server.git rev-parse refs/heads/main)" = "$initial"
git -C client checkout -qb recovery "$initial"
printf 'clean recovery\n' > client/source.txt
git -C client add source.txt
git -C client commit -qm 'isolated clean recovery'
git -C client push "$base/server.git" HEAD:refs/heads/main > recovered.out 2>&1
grep -q '"status":"clean"' recovered.out
printf '{"inherited_installed_hook":true,"clean_push":true,"intermediate_secret_refused":true,"quarantine_discarded":true,"clean_recovery":true,"builtin_authorization_excluded":true}\n'

View file

@ -0,0 +1,68 @@
#!/usr/bin/env python3
"""Guarded repair of the two admitted scanner modes; capture no child diagnostics."""
import argparse
import datetime
import hashlib
import importlib.util
import json
from pathlib import Path
HERE = Path(__file__).parent
spec = importlib.util.spec_from_file_location('template', HERE / 'activate_forge_template.py')
template = importlib.util.module_from_spec(spec)
spec.loader.exec_module(template)
def guard_script(original):
lines = ['#!/bin/sh', 'set +vx', 'set -eu', 'test "$(id -u)" = 1000']
# Validate all files before changing either. Never follow a substituted link.
for parent in ['/data', '/data/security', '/data/security/receive-secret-scan', original['version_directory']]:
lines.append(f'test -d {parent} && test ! -L {parent}')
for item in original['artifacts'][:2]:
path = original['version_directory'] + '/' + item['name']
lines.extend([f'test -f {path} && test ! -L {path}',
f'test "$(stat -c %u {path})" = 1000',
f'test "$(sha256sum {path} | cut -d " " -f 1)" = {item["sha256"]}',
f'case "$(stat -c %a {path})" in 755|775) ;; *) exit 21;; esac'])
lines.append('if [ "${1:-check}" = apply ]; then')
for item in original['artifacts'][:2]:
path = original['version_directory'] + '/' + item['name']
lines.extend([f' chmod 755 {path}', f' test "$(stat -c %a {path})" = 755'])
lines.extend(['fi', ''])
return '\n'.join(lines)
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--proposal', type=Path, required=True)
parser.add_argument('--receipt', type=Path, required=True)
parser.add_argument('--apply', action='store_true')
args = parser.parse_args()
proposal = json.loads(args.proposal.read_text())
original = json.loads((HERE / 'receive-hook-proposal.json').read_text())
script = guard_script(original).encode()
assert hashlib.sha256(Path(__file__).read_bytes()).hexdigest() == proposal['executor_sha256']
assert hashlib.sha256(script).hexdigest() == proposal['guard_sha256']
d = template.deployment()
pod = template.target_pod(d, original)
assert pod['pod_uid'] == proposal['pod_uid']
template.check_artifacts(pod)
mode = 'apply' if args.apply else 'check'
receipt = {'proposal_id': proposal['proposal_id'], 'revision': proposal['revision'],
'proposal_sha256': hashlib.sha256(args.proposal.read_bytes()).hexdigest(),
'pod_uid': pod['pod_uid'], 'mode': mode, 'result': 'unverified',
'at': datetime.datetime.now(datetime.timezone.utc).isoformat()}
args.receipt.write_text(json.dumps(receipt, indent=2) + '\n')
template.native.execute(pod, 'sh', '-s', '--', mode, payload=script)
observed = []
for item in original['artifacts'][:2]:
path = original['version_directory'] + '/' + item['name']
observed.append({'name': item['name'], 'stat': template.native.execute(pod, 'stat', '-c', '%a %u %g', path).decode().strip()})
receipt.update(result='modes-restored' if args.apply else 'preflight-passed', artifacts=observed,
push_acceptance='pending', restart_durability='pending')
args.receipt.write_text(json.dumps(receipt, indent=2) + '\n')
print(json.dumps(receipt))
if __name__ == '__main__':
main()

View file

@ -0,0 +1,59 @@
#!/usr/bin/env python3
"""Repeat custody acceptance on a new pod, preserving all existing annotations."""
import argparse
import datetime
import hashlib
import importlib.util
import json
from pathlib import Path
HERE = Path(__file__).parent
s=importlib.util.spec_from_file_location('custody',HERE/'deploy_forge_custody.py')
m=importlib.util.module_from_spec(s)
s.loader.exec_module(m)
t=m.t
def main():
parser=argparse.ArgumentParser(description=__doc__)
parser.add_argument('--proposal',type=Path,required=True)
parser.add_argument('--receipt',type=Path,required=True)
parser.add_argument('--apply',action='store_true')
args=parser.parse_args()
p=json.loads(args.proposal.read_text())
for file,expected in p['executor_hashes'].items():
assert hashlib.sha256((HERE/file).read_bytes()).hexdigest()==expected,'executor-changed'
d=t.deployment()
assert t.digest(d['spec']['template'])==p['original_template_digest'],'deployment-changed'
original=json.loads((HERE/'receive-hook-proposal.json').read_text())
pod=t.target_pod(d,original)
annotations=dict(d['spec']['template']['metadata'].get('annotations',{}))
assert p['annotation_key'] not in annotations,'marker-preexists'
annotations[p['annotation_key']]=p['restart_marker']
patch=[{'op':'test','path':'/metadata/uid','value':p['deployment_uid']},
{'op':'test','path':'/metadata/resourceVersion','value':d['metadata']['resourceVersion']},
{'op':'add','path':'/spec/template/metadata/annotations','value':annotations}]
local=t.apply_locally(d,patch)
dry=t.patch_remote(patch,dry_run=True)
assert t.digest(local['spec']['template'])==t.digest(dry['spec']['template']),'dry-run-differs'
receipt={'proposal_id':p['proposal_id'],'revision':p['revision'],
'proposal_sha256':hashlib.sha256(args.proposal.read_bytes()).hexdigest(),
'at':datetime.datetime.now(datetime.timezone.utc).isoformat(),
'before_pod_uid':pod['pod_uid'],'dry_run_verified':True,'result':'dry-run-verified'}
args.receipt.write_text(json.dumps(receipt,indent=2)+'\n')
if args.apply:
applied=t.patch_remote(patch)
receipt.update(generation=applied['metadata']['generation'],result='rollout-started')
args.receipt.write_text(json.dumps(receipt,indent=2)+'\n')
after,new=t.wait_ready(original,t.TEMPLATE)
assert new['pod_uid']!=pod['pod_uid'],'pod-not-replaced'
assert t.digest(after['spec']['template'])==t.digest(local['spec']['template']),'deployment-changed'
raw=json.loads(t.native.command(['-n','forgejo','get','pod',new['pod'],'-o','json']))
status=next(x for x in raw['status']['initContainerStatuses'] if x['name']=='verify-secret-scanner')
assert status['state']['terminated']['exitCode']==0,'guard-not-completed'
receipt.update(after_pod_uid=new['pod_uid'],guard_completed=True,
after_probe=m.verify(new,'restart-'+str(after['metadata']['generation'])),result='verified')
args.receipt.write_text(json.dumps(receipt,indent=2)+'\n')
print(json.dumps(receipt))
if __name__=='__main__':
main()

View file

@ -0,0 +1,41 @@
#!/usr/bin/env python3
"""Restore exact historical JSON bytes from a checksum-metadata archive.
The archive separates file paths from digest fields to avoid interpreting a
public ConfigMap checksum as an API key. No detector exception is introduced.
The original execution digest is verified before writing a new private file.
"""
import argparse
import copy
import hashlib
import json
import os
from pathlib import Path
def restore(archive):
if archive['archival_format'] != 'json-checksum-map-entries-v1':
raise ValueError('unsupported-archive-format')
document = copy.deepcopy(archive['document'])
field = archive['converted_map_field']
entries = document[field]
restored = {entry['path']: entry['sha256'] for entry in entries}
if len(restored) != len(entries):
raise ValueError('duplicate-path')
document[field] = restored
raw = (json.dumps(document, indent=2) + '\n').encode()
if hashlib.sha256(raw).hexdigest() != archive['original_sha256']:
raise ValueError('original-digest-mismatch')
return raw
if __name__ == '__main__':
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('archive', type=Path)
parser.add_argument('output', type=Path)
args = parser.parse_args()
raw = restore(json.loads(args.archive.read_text()))
fd = os.open(args.output, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
with os.fdopen(fd, 'wb') as out:
out.write(raw)
print('Exact historical bytes restored and digest verified.')

View file

@ -0,0 +1,55 @@
#!/usr/bin/env python3
"""Exercise the exact startup command in the pinned image, with synthetic volume state."""
import hashlib
import json
from pathlib import Path
import shutil
import subprocess
import tempfile
HERE = Path(__file__).parent
original = json.loads((HERE / 'receive-hook-proposal.json').read_text())
container = json.loads((HERE / 'forge-custody-init.json').read_text())
checks = {}
with tempfile.TemporaryDirectory(prefix='forge-custody-test-') as tmp:
root = Path(tmp)
version = root / 'receive-secret-scan' / Path(original['version_directory']).name
version.mkdir(parents=True)
files = [version / x['name'] for x in original['artifacts'][:2]]
for x, dest in zip(original['artifacts'], files):
shutil.copyfile('/tmp/custodian-forge-lifecycle/' + x['name'], dest)
assert hashlib.sha256(dest.read_bytes()).hexdigest() == x['sha256']
dest.chmod(0o775)
def run():
return subprocess.run(['docker', 'run', '--rm', '--network', 'none', '--read-only',
'--cap-drop', 'ALL', '--security-opt', 'no-new-privileges', '--user', '1000:1000',
'--mount', f'type=bind,src={root},dst=/data/security', '--entrypoint', 'sh',
container['image'], *container['command'][1:]], capture_output=True, timeout=45).returncode
checks['remount_0775_repaired'] = run() == 0 and all(x.stat().st_mode & 0o777 == 0o755 for x in files)
checks['already_correct_idempotent'] = run() == 0
for x in files:
x.chmod(0o775)
saved = files[1].read_bytes()
files[1].write_bytes(b'corrupt fixture')
checks['wrong_hash_refused_before_any_chmod'] = run() != 0 and all(x.stat().st_mode & 0o777 == 0o775 for x in files)
files[1].write_bytes(saved)
files[1].chmod(0o777)
checks['unexpected_world_write_refused'] = run() != 0 and files[0].stat().st_mode & 0o777 == 0o775
files[1].chmod(0o775)
target = version / 'link-target'
files[1].rename(target)
files[1].symlink_to(target.name)
checks['symlink_refused'] = run() != 0 and target.stat().st_mode & 0o777 == 0o775
files[1].unlink()
target.rename(files[1])
checks['recovery_after_refusals'] = run() == 0 and all(x.stat().st_mode & 0o777 == 0o755 for x in files)
receipt = {'result': 'verified' if all(checks.values()) else 'failed', 'checks': checks,
'scope': 'Exact startup command and pinned Forgejo image; simulated fsGroup remount modes, no production mount.',
'container_sha256': hashlib.sha256((HERE / 'forge-custody-init.json').read_bytes()).hexdigest()}
Path('docs/evidence/2026-10-13-netkingdom-security/forge-custody-isolated.json').write_text(json.dumps(receipt, indent=2) + '\n')
print(json.dumps(receipt))
assert all(checks.values())

View file

@ -0,0 +1,69 @@
import copy
import importlib.util
from pathlib import Path
import unittest
path = Path(__file__).with_name('activate_forge_template.py')
spec = importlib.util.spec_from_file_location('template', path)
t = importlib.util.module_from_spec(spec)
spec.loader.exec_module(t)
def fixture():
def container(name):
return {'name': name, 'image': 'unchanged:fixture', 'resources': {'requests': {'cpu': '100m'}},
'volumeMounts': [{'name': 'data', 'mountPath': '/data'}],
'env': [{'name': 'EXISTING_REFERENCE', 'valueFrom': {'secretKeyRef': {'name': 'existing', 'key': 'reference-only'}}}]}
return {'metadata': {'uid': 'fixed', 'resourceVersion': '17'}, 'spec': {'template': {'metadata': {'labels': {'preserved': 'yes'}},
'spec': {'volumes': [{'name': 'data', 'persistentVolumeClaim': {'claimName': 'existing'}}],
'initContainers': [container('init-directories'), container('init-app-ini'), container('configure-gitea')],
'containers': [container('gitea')]}}}}
class Tests(unittest.TestCase):
def test_rollback_preserves_existing_configuration_and_explicitly_restores_default(self):
before = fixture()
active = t.apply_locally(before, t.patch_for(before))
rolled = t.apply_locally(active, t.patch_for(active, rollback=True))
expected = copy.deepcopy(before)
expected['spec']['template']['spec']['initContainers'][1]['env'].append({'name': t.SETTING, 'value': t.DEFAULT})
self.assertEqual(rolled, expected)
self.assertEqual(before, fixture())
def test_stale_resource_version_cannot_apply(self):
before = fixture()
patch = t.patch_for(before)
before['metadata']['resourceVersion'] = '18'
with self.assertRaisesRegex(ValueError, 'precondition'):
t.apply_locally(before, patch)
def test_existing_setting_not_overwritten(self):
d = fixture()
d['spec']['template']['spec']['initContainers'][1]['env'].append({'name': t.SETTING, 'value': '/unknown'})
with self.assertRaisesRegex(ValueError, 'setting-preexists'):
t.patch_for(d)
def test_conflicting_mount_not_overwritten(self):
d = fixture()
d['spec']['template']['spec']['containers'][0]['volumeMounts'].append({'name': 'other', 'mountPath': t.MOUNT['mountPath']})
with self.assertRaisesRegex(ValueError, 'mount-preexists'):
t.patch_for(d)
def test_rollback_preserves_concurrently_modified_mount(self):
d = fixture()
d = t.apply_locally(d, t.patch_for(d))
d['spec']['template']['spec']['containers'][0]['volumeMounts'][-1]['subPath'] = 'unknown'
original = copy.deepcopy(d)
with self.assertRaisesRegex(ValueError, 'rollback-mount-changed'):
t.patch_for(d, rollback=True)
self.assertEqual(d, original)
def test_missing_configuration_container_refused(self):
d = fixture()
d['spec']['template']['spec']['initContainers'].pop(1)
with self.assertRaisesRegex(ValueError, 'configuration-container'):
t.patch_for(d)
if __name__ == '__main__':
unittest.main(verbosity=2)

View file

@ -0,0 +1,96 @@
#!/usr/bin/env python3
"""Verify the proposed file mount, encoded configuration and rollback in isolation."""
import argparse
import hashlib
import importlib.util
import json
from pathlib import Path
import subprocess
import tempfile
BASE = Path(__file__).with_name('verify_forge_lifecycle.py')
BASE_SHA256 = '88bbf4235086b7ffc40ca4d7df7fafa1d67e2e1b41d2f0055ec82421ed4840cb'
if hashlib.sha256(BASE.read_bytes()).hexdigest() != BASE_SHA256:
raise SystemExit('Lifecycle helper revision changed.')
spec = importlib.util.spec_from_file_location('lifecycle', BASE)
base = importlib.util.module_from_spec(spec)
spec.loader.exec_module(base)
SETTING = 'GITEA__GIT_0X2E_CONFIG__INIT_0X2E_TEMPLATEDIR'
TEMPLATE = '/opt/forge-secret-template'
MOUNT = TEMPLATE + '/hooks/pre-receive.d/10-custodian-secret-scan'
class Probe(base.Probe):
def __init__(self, receipt, hook_file):
super().__init__(receipt)
self.hook_file = hook_file
self.evidence['scope'] = ('Isolated exact-image instance with SQLite and no network. '
'One read-only mount of the public hook fixture simulates a Kubernetes file subPath. '
'No production data, configuration or credentials used; production Kubernetes acceptance remains separate.')
def run(self, argv, *args, **kwargs):
if argv[:2] == ['docker', 'run']:
argv = list(argv)
index = argv.index(base.IMAGE)
argv[index:index] = ['--mount', f'type=bind,src={self.hook_file},dst={MOUNT},readonly']
return super().run(argv, *args, **kwargs)
def set_template(self, value):
self.execute('env', SETTING + '=' + value, 'environment-to-ini', '--config', base.CONFIG,
'--work-path', base.ROOT, '--out', base.CONFIG)
self.run(['docker', 'restart', self.cid])
self.start_web()
selected = self.execute('git', 'config', '--file', base.ROOT + '/git-home/.gitconfig',
'--get', 'init.templateDir').stdout.decode().strip()
self.check('encoded_setting_selected_' + ('default' if value != TEMPLATE else 'template'), selected == value)
def execute(self, *args, **kwargs):
if args[:5] == ('git', 'config', '--file', base.ROOT + '/git-home/.gitconfig', 'init.templateDir'):
self.set_template(TEMPLATE)
result = super().execute('sh', '-c', 'test -f "$1" && test ! -L "$1" && test -x "$1"', 'sh', MOUNT)
self.check('mounted_hook_regular_executable', result.returncode == 0)
return result
return super().execute(*args, **kwargs)
def run_probe(self, artifact_dir):
super().run_probe(artifact_dir)
self.evidence['result'] = 'unverified'
self.phase = 'rollback-default-template'
self.set_template('/usr/share/git-core/templates')
status, _ = self.api('POST', '/api/v1/user/repos', {'name': 'rollback', 'private': True, 'auto_init': True})
self.check('rollback_new_repository_created', status == 201)
absent = self.execute('test', '-e', self.profile('rollback'), allowed=(0, 1))
self.check('rollback_restores_default_creation_behavior', absent.returncode == 1)
for repo in ['coverage', 'templated']:
digest = self.execute('sha256sum', self.profile(repo)).stdout.split()[0].decode()
self.check('rollback_preserves_existing_' + repo + '_hook', digest == hashlib.sha256(self.hook_file.read_bytes()).hexdigest())
self.evidence['result'] = 'verified'
def close(self):
super().close()
self.evidence['base_executor_sha256'] = BASE_SHA256
self.evidence['executor_sha256'] = hashlib.sha256(Path(__file__).read_bytes()).hexdigest()
self.receipt.write_text(json.dumps(self.evidence, indent=2) + '\n')
def main():
p = argparse.ArgumentParser(description=__doc__)
p.add_argument('--artifact-dir', type=Path, required=True)
p.add_argument('--receipt', type=Path, required=True)
args = p.parse_args()
with tempfile.TemporaryDirectory(prefix='forge-template-fixture-') as temporary:
hook = Path(temporary) / 'hook'
hook.write_bytes(Path(__file__).with_name('receive-profile-hook').read_bytes())
hook.chmod(0o555)
probe = Probe(args.receipt, hook)
try:
probe.run_probe(args.artifact_dir)
except (ValueError, OSError, KeyError, subprocess.SubprocessError) as error:
probe.evidence['failure'] = probe.phase + ':' + type(error).__name__
raise SystemExit('Template acceptance incomplete at phase ' + probe.phase)
finally:
probe.close()
if __name__ == '__main__':
main()

View file

@ -841,8 +841,8 @@
"closure_evidence": [],
"assessment": {
"scope": "Reviewed workstation checkouts and 22 deployed Forgejo repositories; separate exact-image isolated lifecycle checks; no estate-wide conformance inferred",
"observed": "Inherited generated-only hook allowed a synthetic private-key commit. New pinned scanner adapter detects that fixture in complete changed staged blobs with real-scanner tests. Actual configured hooks now enforce the pinned scanner on 22 exact workstation roots: clean accepted, disposable private key rejected, generated-only guard retained, unavailable scanner refused. Temporary alternate indexes/object stores preserved user state; unreviewed checkout behavior and routing stayed unchanged. Forge-owned introduced-history adapter now passes 33 source/protocol leaf cases with actual disposable pushes; exact-binary native activation is now verified on 22 Forgejo repositories. Actual SSH clean/detection probes passed under a temporary veto, with absent probe refs/quarantine and veto cleanup; fresh read-only hashes match. Ordinary source pushes to Custodian and railiance-forge succeeded. Separate exact-image isolated HTTP/API/merge, regeneration, Git-template inheritance and restart checks pass (23 assertions), with all earlier incomplete preparation attempts retained.",
"remaining": "The other 143 inventoried repositories and other workstation clones remain uncovered. New-repository template is tested but not deployed; deployed HTTP/SSO/UI and Kubernetes lifecycle acceptance remain open. Import/mirror clone/fetch paths bypass receive-pack; historical, LFS, logs/artifacts/runtime coverage, CI authority and private detection-to-response are unverified.",
"observed": "Inherited generated-only hook allowed a synthetic private-key commit. New pinned scanner adapter detects that fixture in complete changed staged blobs with real-scanner tests. Actual configured hooks now enforce the pinned scanner on 22 exact workstation roots: clean accepted, disposable private key rejected, generated-only guard retained, unavailable scanner refused. Temporary alternate indexes/object stores preserved user state; unreviewed checkout behavior and routing stayed unchanged. Forge-owned introduced-history adapter now passes 33 source/protocol leaf cases with actual disposable pushes; exact-binary native activation is now verified on 22 Forgejo repositories. Actual SSH clean/detection probes passed under a temporary veto, with absent probe refs/quarantine and veto cleanup; fresh read-only hashes match. Ordinary source pushes to Custodian and railiance-forge succeeded. Separate exact-image isolated HTTP/API/merge, regeneration, Git-template inheritance and restart checks pass (23 assertions), with all earlier incomplete preparation attempts retained. Exact template source was deployed on October 13: same-image generation 12 Ready, actual global Git template selection and regular/executable inheritance in a native temporary repository verified, all original 22 profiles/binaries preserved. Thirty isolated mounted-file/configuration/rollback checks and six conditional-patch regressions pass. The first live template rollout subsequently failed ordinary source acceptance: group-writable0775 scanner permissions were refused. Trial retained as failed. Recovery restored0755 and persisted a hash/owner-checked startup guard (railiance-apps50b66c4). Two Kubernetes replacements, generations13/14, completed the guard and passed inherited native clean/secret/quarantine/recovery probes. Six isolated custody checks pass; builtin authorization excluded in temporary native probes. See recovery summary for separate ordinary SSH acceptance.",
"remaining": "Fresh inventory has 166 repositories: 22 installed profiles and 144 builtin-only; other workstation clones remain uncovered. Production authenticated HTTP/SSO/frontend creation remains open. Import/mirror clone/fetch bypass receive-pack; historical, LFS, logs/artifacts/runtime coverage, CI authority and private detection-to-response are unverified.",
"owner_task": "RMGR-WP-0021-T01/T02/T03/T04; CUST-WP-0080-T03; FORGE-WP-0006-T02/T03",
"verification_evidence": [
"secret-detection.json",
@ -851,7 +851,11 @@
"receive-hook-activation.json",
"../2026-10-13-netkingdom-security/receive-profile-recheck.json",
"../2026-10-13-netkingdom-security/receive-clean-push.json",
"../2026-10-13-netkingdom-security/forge-lifecycle-summary.json"
"../2026-10-13-netkingdom-security/forge-lifecycle-summary.json",
"../2026-10-13-netkingdom-security/forge-template-activation.json",
"../2026-10-13-netkingdom-security/forge-template-isolated.json",
"../2026-10-13-netkingdom-security/forge-hook-coverage-refresh.json",
"../2026-10-13-netkingdom-security/forge-custody-recovery-summary.json"
],
"assessed_at": "2026-10-13"
}
@ -870,8 +874,8 @@
"closure_evidence": [],
"assessment": {
"scope": "Reviewed workstation checkouts and 22 deployed Forgejo repositories; separate exact-image isolated lifecycle checks; no estate-wide conformance inferred",
"observed": "Verified local hook wiring in 21 review repositories plus Custodian; source adapter covers changed Git index blobs only. Actual configured hooks now enforce the pinned scanner on 22 exact workstation roots: clean accepted, disposable private key rejected, generated-only guard retained, unavailable scanner refused. Temporary alternate indexes/object stores preserved user state; unreviewed checkout behavior and routing stayed unchanged. Forge-owned introduced-history adapter now passes 33 source/protocol leaf cases with actual disposable pushes; exact-binary native activation is now verified on 22 Forgejo repositories. Actual SSH clean/detection probes passed under a temporary veto, with absent probe refs/quarantine and veto cleanup; fresh read-only hashes match. Ordinary source pushes to Custodian and railiance-forge succeeded. Separate exact-image isolated HTTP/API/merge, regeneration, Git-template inheritance and restart checks pass (23 assertions), with all earlier incomplete preparation attempts retained.",
"remaining": "The other 143 inventoried repositories and other workstation clones remain uncovered. New-repository template is tested but not deployed; deployed HTTP/SSO/UI and Kubernetes lifecycle acceptance remain open. Import/mirror clone/fetch paths bypass receive-pack; historical, LFS, logs/artifacts/runtime coverage, CI authority and private detection-to-response are unverified.",
"observed": "Verified local hook wiring in 21 review repositories plus Custodian; source adapter covers changed Git index blobs only. Actual configured hooks now enforce the pinned scanner on 22 exact workstation roots: clean accepted, disposable private key rejected, generated-only guard retained, unavailable scanner refused. Temporary alternate indexes/object stores preserved user state; unreviewed checkout behavior and routing stayed unchanged. Forge-owned introduced-history adapter now passes 33 source/protocol leaf cases with actual disposable pushes; exact-binary native activation is now verified on 22 Forgejo repositories. Actual SSH clean/detection probes passed under a temporary veto, with absent probe refs/quarantine and veto cleanup; fresh read-only hashes match. Ordinary source pushes to Custodian and railiance-forge succeeded. Separate exact-image isolated HTTP/API/merge, regeneration, Git-template inheritance and restart checks pass (23 assertions), with all earlier incomplete preparation attempts retained. Exact template source was deployed on October 13: same-image generation 12 Ready, actual global Git template selection and regular/executable inheritance in a native temporary repository verified, all original 22 profiles/binaries preserved. Thirty isolated mounted-file/configuration/rollback checks and six conditional-patch regressions pass. The first live template rollout subsequently failed ordinary source acceptance: group-writable0775 scanner permissions were refused. Trial retained as failed. Recovery restored0755 and persisted a hash/owner-checked startup guard (railiance-apps50b66c4). Two Kubernetes replacements, generations13/14, completed the guard and passed inherited native clean/secret/quarantine/recovery probes. Six isolated custody checks pass; builtin authorization excluded in temporary native probes. See recovery summary for separate ordinary SSH acceptance.",
"remaining": "Fresh inventory has 166 repositories: 22 installed profiles and 144 builtin-only; other workstation clones remain uncovered. Production authenticated HTTP/SSO/frontend creation remains open. Import/mirror clone/fetch bypass receive-pack; historical, LFS, logs/artifacts/runtime coverage, CI authority and private detection-to-response are unverified.",
"owner_task": "RMGR-WP-0021-T01/T02/T03/T04; CUST-WP-0080-T03; FORGE-WP-0006-T02/T03",
"verification_evidence": [
"secret-detection.json",
@ -880,7 +884,11 @@
"receive-hook-activation.json",
"../2026-10-13-netkingdom-security/receive-profile-recheck.json",
"../2026-10-13-netkingdom-security/receive-clean-push.json",
"../2026-10-13-netkingdom-security/forge-lifecycle-summary.json"
"../2026-10-13-netkingdom-security/forge-lifecycle-summary.json",
"../2026-10-13-netkingdom-security/forge-template-activation.json",
"../2026-10-13-netkingdom-security/forge-template-isolated.json",
"../2026-10-13-netkingdom-security/forge-hook-coverage-refresh.json",
"../2026-10-13-netkingdom-security/forge-custody-recovery-summary.json"
],
"assessed_at": "2026-10-13"
}
@ -899,8 +907,8 @@
"closure_evidence": [],
"assessment": {
"scope": "Reviewed workstation checkouts and 22 deployed Forgejo repositories; separate exact-image isolated lifecycle checks; no estate-wide conformance inferred",
"observed": "A disposable secret commit succeeds through the inherited hook and is rejected through the candidate scanner hook; git commit --only and partial/alternate-index cases are tested. Actual configured hooks now enforce the pinned scanner on 22 exact workstation roots: clean accepted, disposable private key rejected, generated-only guard retained, unavailable scanner refused. Temporary alternate indexes/object stores preserved user state; unreviewed checkout behavior and routing stayed unchanged. Forge-owned introduced-history adapter now passes 33 source/protocol leaf cases with actual disposable pushes; exact-binary native activation is now verified on 22 Forgejo repositories. Actual SSH clean/detection probes passed under a temporary veto, with absent probe refs/quarantine and veto cleanup; fresh read-only hashes match. Ordinary source pushes to Custodian and railiance-forge succeeded. Separate exact-image isolated HTTP/API/merge, regeneration, Git-template inheritance and restart checks pass (23 assertions), with all earlier incomplete preparation attempts retained.",
"remaining": "The other 143 inventoried repositories and other workstation clones remain uncovered. New-repository template is tested but not deployed; deployed HTTP/SSO/UI and Kubernetes lifecycle acceptance remain open. Import/mirror clone/fetch paths bypass receive-pack; historical, LFS, logs/artifacts/runtime coverage, CI authority and private detection-to-response are unverified.",
"observed": "A disposable secret commit succeeds through the inherited hook and is rejected through the candidate scanner hook; git commit --only and partial/alternate-index cases are tested. Actual configured hooks now enforce the pinned scanner on 22 exact workstation roots: clean accepted, disposable private key rejected, generated-only guard retained, unavailable scanner refused. Temporary alternate indexes/object stores preserved user state; unreviewed checkout behavior and routing stayed unchanged. Forge-owned introduced-history adapter now passes 33 source/protocol leaf cases with actual disposable pushes; exact-binary native activation is now verified on 22 Forgejo repositories. Actual SSH clean/detection probes passed under a temporary veto, with absent probe refs/quarantine and veto cleanup; fresh read-only hashes match. Ordinary source pushes to Custodian and railiance-forge succeeded. Separate exact-image isolated HTTP/API/merge, regeneration, Git-template inheritance and restart checks pass (23 assertions), with all earlier incomplete preparation attempts retained. Exact template source was deployed on October 13: same-image generation 12 Ready, actual global Git template selection and regular/executable inheritance in a native temporary repository verified, all original 22 profiles/binaries preserved. Thirty isolated mounted-file/configuration/rollback checks and six conditional-patch regressions pass. The first live template rollout subsequently failed ordinary source acceptance: group-writable0775 scanner permissions were refused. Trial retained as failed. Recovery restored0755 and persisted a hash/owner-checked startup guard (railiance-apps50b66c4). Two Kubernetes replacements, generations13/14, completed the guard and passed inherited native clean/secret/quarantine/recovery probes. Six isolated custody checks pass; builtin authorization excluded in temporary native probes. See recovery summary for separate ordinary SSH acceptance.",
"remaining": "Fresh inventory has 166 repositories: 22 installed profiles and 144 builtin-only; other workstation clones remain uncovered. Production authenticated HTTP/SSO/frontend creation remains open. Import/mirror clone/fetch bypass receive-pack; historical, LFS, logs/artifacts/runtime coverage, CI authority and private detection-to-response are unverified.",
"owner_task": "RMGR-WP-0021-T01/T02/T03/T04; CUST-WP-0080-T03; FORGE-WP-0006-T02/T03",
"verification_evidence": [
"secret-detection.json",
@ -909,7 +917,11 @@
"receive-hook-activation.json",
"../2026-10-13-netkingdom-security/receive-profile-recheck.json",
"../2026-10-13-netkingdom-security/receive-clean-push.json",
"../2026-10-13-netkingdom-security/forge-lifecycle-summary.json"
"../2026-10-13-netkingdom-security/forge-lifecycle-summary.json",
"../2026-10-13-netkingdom-security/forge-template-activation.json",
"../2026-10-13-netkingdom-security/forge-template-isolated.json",
"../2026-10-13-netkingdom-security/forge-hook-coverage-refresh.json",
"../2026-10-13-netkingdom-security/forge-custody-recovery-summary.json"
],
"assessed_at": "2026-10-13"
}

View file

@ -0,0 +1,13 @@
{
"result": "verified",
"checks": {
"remount_0775_repaired": true,
"already_correct_idempotent": true,
"wrong_hash_refused_before_any_chmod": true,
"unexpected_world_write_refused": true,
"symlink_refused": true,
"recovery_after_refusals": true
},
"scope": "Exact startup command and pinned Forgejo image; simulated fsGroup remount modes, no production mount.",
"container_sha256": "cccb9999785fe0151a973d9a8b554975daf17c91432ed3e8735e432f3cd80e8f"
}

View file

@ -0,0 +1,23 @@
{
"result": "classified-and-represented-as-structured-metadata",
"findings": [
{
"file": "docs/changes/CUST-WP-0080/forge-template-proposal.json",
"classification": "public ConfigMap source checksum false positive",
"rule": "generic-api-key",
"original_sha256": "7eddea55359b25c5ad307ed8ccd392dfadfe167d65cbe34b02f1955350bbcb36",
"exact_original_reconstruction_verified": true
},
{
"file": "docs/evidence/2026-10-13-netkingdom-security/forge-template-source-render.json",
"classification": "public ConfigMap source checksum false positive",
"rule": "generic-api-key",
"original_sha256": "d3658a4dd8ea9c31bed5ce7fc99f2e1a79ac89e59d9e3dcebb3fb517409abd27",
"exact_original_reconstruction_verified": true
}
],
"credential_values_present": false,
"detector_configuration_changed": false,
"detector_exception_added": false,
"original_bytes_retained": true
}

View file

@ -0,0 +1,21 @@
{
"proposal_id": "FORGE6-TEMPLATE-LIFECYCLE-001",
"revision": 2,
"proposal_sha256": "7530423d7528b63434d896dfe28e5288d4e332167db817efa58c2255defe28d3",
"pod_uid": "f818d3f5-5c3d-4a2a-8e78-50e10b10d2ce",
"mode": "check",
"result": "preflight-passed",
"at": "2026-10-13T12:17:21.841158+00:00",
"artifacts": [
{
"name": "receive-secret-scan",
"stat": "775 1000 1000"
},
{
"name": "gitleaks",
"stat": "775 1000 1000"
}
],
"push_acceptance": "pending",
"restart_durability": "pending"
}

View file

@ -0,0 +1,54 @@
{
"proposal_id": "FORGE6-TEMPLATE-LIFECYCLE-001",
"original_outcome": "failed",
"original_receipt_ref": "forge-template-activation.json",
"original_receipt_sha256": "b7f84a6d2840e95d16b3f9ac6cc83d0b5e8c237f082c85f72ba84fa41d46f380",
"failure": "Ordinary source push scanner-file-refused after generation12; artifacts0775, which scanner correctly rejects. Initial malformed-input/hash/health checks did not prove usability.",
"recovery_result": "verified with refinement; original trial remains failed",
"source_commit": "railiance-apps50b66c4e44d78c1fb1e89991439eda14722103b3",
"recovery_receipts": [
"forge-custody-repair.json",
"forge-custody-isolated.json",
"forge-custody-source-render.json",
"forge-custody-rollout-dry-run.json",
"forge-custody-rollout.json",
"forge-custody-restart-dry-run.json",
"forge-custody-restart.json"
],
"retained_preparation_failures": [
"Revision3 initial server dry-run added omitted default termination fields; explicit defaults then matched.",
"Revision3 restart-only dry-run refused local JSON Pointer mismatch; revision4 preserves annotations and matched. No live mutation occurred in either rejected dry-run."
],
"coverage": {
"workstation_checkouts": 22,
"server_installed_profiles": 22,
"server_total_repositories": 166,
"server_builtin_only": 144
},
"not_proven": [
"Production authenticated HTTP/SSO/frontend creation",
"Remaining144repository activation including gitlink compatibility",
"Import/mirror fetch and broader history/LFS/log/artifact coverage",
"CI change authority and private detection-to-response"
],
"at": "2026-10-13T12:29:15.704944+00:00",
"promotion_evidence": false,
"authenticated_ssh_after_final_restart": {
"accepted": true,
"source_commit": "2c11c366ceb6d95b4a2275e56f69a8c52d04072f",
"route": "statehub fix-consistency --repo railiance-forge --no-writeback",
"remote_head_verified": true,
"sync_result": "warn",
"sync_summary": {
"fail": 0,
"automation_error": 0,
"warn": 7,
"info": 0
}
},
"remote_heads": {
"railiance-forge": "2c11c366ceb6d95b4a2275e56f69a8c52d04072f",
"railiance-apps": "50b66c4e44d78c1fb1e89991439eda14722103b3"
},
"verified_at": "2026-10-13T13:02:36.216871+00:00"
}

View file

@ -0,0 +1,26 @@
{
"proposal_id": "FORGE6-TEMPLATE-LIFECYCLE-001",
"revision": 2,
"proposal_sha256": "7530423d7528b63434d896dfe28e5288d4e332167db817efa58c2255defe28d3",
"pod_uid": "f818d3f5-5c3d-4a2a-8e78-50e10b10d2ce",
"mode": "apply",
"result": "modes-restored",
"at": "2026-10-13T12:17:44.858130+00:00",
"artifacts": [
{
"name": "receive-secret-scan",
"stat": "755 1000 1000"
},
{
"name": "gitleaks",
"stat": "755 1000 1000"
}
],
"push_acceptance": {
"accepted": true,
"commit": "ec1d7c591af4823ab42168fe3b6e940c8559392b",
"route": "statehub fix-consistency --repo railiance-forge --no-writeback",
"result": "pushed 1 backlogged commit; enforced hook unchanged"
},
"restart_durability": "pending"
}

View file

@ -0,0 +1,9 @@
{
"proposal_id": "FORGE6-TEMPLATE-LIFECYCLE-001",
"revision": 4,
"proposal_sha256": "f6060bf7a5a604781a04b853775f72ba7ada5cc05c89a1da0df4fd480779004b",
"at": "2026-10-13T12:26:48.093378+00:00",
"before_pod_uid": "5c5b12d3-f029-4288-9803-16f56bebd5e0",
"dry_run_verified": true,
"result": "dry-run-verified"
}

View file

@ -0,0 +1,32 @@
{
"proposal_id": "FORGE6-TEMPLATE-LIFECYCLE-001",
"revision": 4,
"proposal_sha256": "f6060bf7a5a604781a04b853775f72ba7ada5cc05c89a1da0df4fd480779004b",
"at": "2026-10-13T12:27:16.105641+00:00",
"before_pod_uid": "5c5b12d3-f029-4288-9803-16f56bebd5e0",
"dry_run_verified": true,
"result": "verified",
"generation": 14,
"after_pod_uid": "d4728052-b8df-4574-8a3e-74490a04472d",
"guard_completed": true,
"after_probe": {
"artifacts": [
{
"name": "receive-secret-scan",
"stat": "755 1000 1000"
},
{
"name": "gitleaks",
"stat": "755 1000 1000"
}
],
"receive_probe": {
"inherited_installed_hook": true,
"clean_push": true,
"intermediate_secret_refused": true,
"quarantine_discarded": true,
"clean_recovery": true,
"builtin_authorization_excluded": true
}
}
}

View file

@ -0,0 +1,30 @@
{
"proposal_id": "FORGE6-TEMPLATE-LIFECYCLE-001",
"revision": 3,
"proposal_sha256": "b9e8d82331ac2a7fc4b12b5524d486809d03b82f52dc96b31e8ae5e73b93c37f",
"at": "2026-10-13T12:22:45.963893+00:00",
"mode": "dry-run",
"dry_run_verified": true,
"before_pod_uid": "f818d3f5-5c3d-4a2a-8e78-50e10b10d2ce",
"result": "dry-run-verified",
"before_probe": {
"artifacts": [
{
"name": "receive-secret-scan",
"stat": "755 1000 1000"
},
{
"name": "gitleaks",
"stat": "755 1000 1000"
}
],
"receive_probe": {
"inherited_installed_hook": true,
"clean_push": true,
"intermediate_secret_refused": true,
"quarantine_discarded": true,
"clean_recovery": true,
"builtin_authorization_excluded": true
}
}
}

View file

@ -0,0 +1,33 @@
{
"proposal_id": "FORGE6-TEMPLATE-LIFECYCLE-001",
"revision": 3,
"proposal_sha256": "b9e8d82331ac2a7fc4b12b5524d486809d03b82f52dc96b31e8ae5e73b93c37f",
"at": "2026-10-13T12:23:38.883450+00:00",
"mode": "apply",
"dry_run_verified": true,
"before_pod_uid": "f818d3f5-5c3d-4a2a-8e78-50e10b10d2ce",
"result": "verified",
"generation": 13,
"after_pod_uid": "5c5b12d3-f029-4288-9803-16f56bebd5e0",
"guard_completed": true,
"after_probe": {
"artifacts": [
{
"name": "receive-secret-scan",
"stat": "755 1000 1000"
},
{
"name": "gitleaks",
"stat": "755 1000 1000"
}
],
"receive_probe": {
"inherited_installed_hook": true,
"clean_push": true,
"intermediate_secret_refused": true,
"quarantine_discarded": true,
"clean_recovery": true,
"builtin_authorization_excluded": true
}
}
}

View file

@ -0,0 +1,6 @@
{
"result": "verified",
"exact_init_rendered": true,
"rollback_restores_original_template": true,
"values_sha256": "cf87a3dccd666bf8870619755afac4af64fc0bee77b0f0ea54718d6384b70d4f"
}

View file

@ -0,0 +1,15 @@
{
"observed_at": "2026-10-13T12:06:05.310048+00:00",
"pod": "forgejo-gitea-77cdff66d5-5jpjl",
"pod_uid": "f818d3f5-5c3d-4a2a-8e78-50e10b10d2ce",
"repository_count": 166,
"counts": {
"reviewed_profile": 22,
"builtin_only": 144,
"other_hook_inventory": 0,
"shallow": 0,
"partial": 0
},
"private_inventory_sha256": "38476667ce2b74d8701b9d5ce165fcecba12df5cd46149996aadb19765441d46",
"scope": "Read-only exact hook hashes and shallow/partial metadata for all current database repositories. No content scan, authorization or behavior proof for the unactivated repositories. No unknown hook replaced."
}

View file

@ -0,0 +1,19 @@
{
"observed_at": "2026-10-13T12:04:04.828055+00:00",
"host": "railiance01",
"pod": "forgejo-gitea-77cdff66d5-5jpjl",
"pod_uid": "f818d3f5-5c3d-4a2a-8e78-50e10b10d2ce",
"private_inventory_sha256": "b230efc3c3b620d5104a68a9d2238049c548d032e9eaab68411f4e4c5987b618",
"database_repositories": 166,
"filesystem_repositories": 166,
"missing_database_directories": 0,
"unmapped_directories": 0,
"private": 12,
"mirrors": 0,
"forks": 0,
"archived": 0,
"empty": 1,
"wikis": 0,
"protected_branch_rules": 0,
"scope": "Metadata only; full repository identities kept in a mode-0600 local observation, not committed. No repository content or credential values read."
}

View file

@ -0,0 +1,100 @@
{
"proposal_id": "FORGE6-TEMPLATE-LIFECYCLE-001",
"proposal_sha256": "7eddea55359b25c5ad307ed8ccd392dfadfe167d65cbe34b02f1955350bbcb36",
"observed_at": "2026-10-13T12:00:12.863351+00:00",
"result": "verified",
"old_pod_uid": "bc771b51-e9b8-456d-b59c-e7041704389d",
"old_generation": 11,
"server_dry_run_exact_match": true,
"patch": [
{
"op": "test",
"path": "/metadata/uid",
"value": "e8eff0aa-64bb-4b44-9a80-a66612986355"
},
{
"op": "test",
"path": "/metadata/resourceVersion",
"value": "67848734"
},
{
"op": "add",
"path": "/spec/template/spec/volumes/-",
"value": {
"name": "secret-scan-template",
"configMap": {
"name": "forgejo-secret-template-b1d7cf8cd13f",
"defaultMode": 365
}
}
},
{
"op": "add",
"path": "/spec/template/spec/initContainers/0/volumeMounts/-",
"value": {
"name": "secret-scan-template",
"mountPath": "/opt/forge-secret-template/hooks/pre-receive.d/10-custodian-secret-scan",
"subPath": "hook",
"readOnly": true
}
},
{
"op": "add",
"path": "/spec/template/spec/initContainers/1/volumeMounts/-",
"value": {
"name": "secret-scan-template",
"mountPath": "/opt/forge-secret-template/hooks/pre-receive.d/10-custodian-secret-scan",
"subPath": "hook",
"readOnly": true
}
},
{
"op": "add",
"path": "/spec/template/spec/initContainers/1/env/-",
"value": {
"name": "GITEA__GIT_0X2E_CONFIG__INIT_0X2E_TEMPLATEDIR",
"value": "/opt/forge-secret-template"
}
},
{
"op": "add",
"path": "/spec/template/spec/initContainers/2/volumeMounts/-",
"value": {
"name": "secret-scan-template",
"mountPath": "/opt/forge-secret-template/hooks/pre-receive.d/10-custodian-secret-scan",
"subPath": "hook",
"readOnly": true
}
},
{
"op": "add",
"path": "/spec/template/spec/containers/0/volumeMounts/-",
"value": {
"name": "secret-scan-template",
"mountPath": "/opt/forge-secret-template/hooks/pre-receive.d/10-custodian-secret-scan",
"subPath": "hook",
"readOnly": true
}
}
],
"original_template_sha256": "18855f7b4c78f79117884684e44550406c72fc7be08c0b8e61ec16cfcb855746",
"resulting_template_sha256": "73f6c1ef484c60f93285d61e6df6651b4f1268fafb3e470f35f450bce4f34aba",
"no_secret_objects_read": true,
"mode": "apply",
"configmap_created": true,
"patch_submission_started": true,
"applied_generation": 12,
"inheritance": {
"mounted_hook_regular": true,
"inherited_hook_regular_executable": true,
"hook_sha256": "b1d7cf8cd13feafe44e45876b1de3fdd9c88339d8d45791fa2b9f72ca832932b",
"inherited_hook_malformed_input_refused": true,
"scope": "Native Git init with actual Forgejo global configuration in an owned temporary bare repository; not production API repository creation."
},
"new_pod_uid": "f818d3f5-5c3d-4a2a-8e78-50e10b10d2ce",
"generation": 12,
"selected_template": "/opt/forge-secret-template",
"existing_22_profiles_preserved": true,
"binaries_unchanged": true,
"health_after": true
}

View file

@ -0,0 +1,84 @@
{
"proposal_id": "FORGE6-TEMPLATE-LIFECYCLE-001",
"proposal_sha256": "7eddea55359b25c5ad307ed8ccd392dfadfe167d65cbe34b02f1955350bbcb36",
"observed_at": "2026-10-13T11:57:41.978702+00:00",
"result": "dry-run-verified",
"old_pod_uid": "bc771b51-e9b8-456d-b59c-e7041704389d",
"old_generation": 11,
"server_dry_run_exact_match": true,
"patch": [
{
"op": "test",
"path": "/metadata/uid",
"value": "e8eff0aa-64bb-4b44-9a80-a66612986355"
},
{
"op": "test",
"path": "/metadata/resourceVersion",
"value": "67848734"
},
{
"op": "add",
"path": "/spec/template/spec/volumes/-",
"value": {
"name": "secret-scan-template",
"configMap": {
"name": "forgejo-secret-template-b1d7cf8cd13f",
"defaultMode": 365
}
}
},
{
"op": "add",
"path": "/spec/template/spec/initContainers/0/volumeMounts/-",
"value": {
"name": "secret-scan-template",
"mountPath": "/opt/forge-secret-template/hooks/pre-receive.d/10-custodian-secret-scan",
"subPath": "hook",
"readOnly": true
}
},
{
"op": "add",
"path": "/spec/template/spec/initContainers/1/volumeMounts/-",
"value": {
"name": "secret-scan-template",
"mountPath": "/opt/forge-secret-template/hooks/pre-receive.d/10-custodian-secret-scan",
"subPath": "hook",
"readOnly": true
}
},
{
"op": "add",
"path": "/spec/template/spec/initContainers/1/env/-",
"value": {
"name": "GITEA__GIT_0X2E_CONFIG__INIT_0X2E_TEMPLATEDIR",
"value": "/opt/forge-secret-template"
}
},
{
"op": "add",
"path": "/spec/template/spec/initContainers/2/volumeMounts/-",
"value": {
"name": "secret-scan-template",
"mountPath": "/opt/forge-secret-template/hooks/pre-receive.d/10-custodian-secret-scan",
"subPath": "hook",
"readOnly": true
}
},
{
"op": "add",
"path": "/spec/template/spec/containers/0/volumeMounts/-",
"value": {
"name": "secret-scan-template",
"mountPath": "/opt/forge-secret-template/hooks/pre-receive.d/10-custodian-secret-scan",
"subPath": "hook",
"readOnly": true
}
}
],
"original_template_sha256": "18855f7b4c78f79117884684e44550406c72fc7be08c0b8e61ec16cfcb855746",
"resulting_template_sha256": "73f6c1ef484c60f93285d61e6df6651b4f1268fafb3e470f35f450bce4f34aba",
"no_secret_objects_read": true,
"mode": "preflight"
}

View file

@ -0,0 +1,50 @@
{
"observed_at": "2026-10-13T11:48:57.834001+00:00",
"image": "code.forgejo.org/forgejo/forgejo@sha256:691657c7e64af7730a2e885e6d022cf16ae60724931c5392bd61838afadcaeba",
"result": "verified",
"checks": {
"synthetic_identity_authenticated": true,
"native_repository_created": true,
"new_repository_missing_profile_without_template": true,
"http_push_clean": true,
"http_push_blocked": true,
"http_quarantine_discarded": true,
"api_accepted_http_status": 201,
"api_accepted": true,
"api_refused_http_status": 500,
"api_refused": true,
"api_refusal_has_scanner_detection": true,
"rejected_api_content_absent": true,
"feature_branch_created": true,
"feature_content_created": true,
"pull_request_created": true,
"merge_refusal_status": 409,
"merge_message_secret_refused": true,
"merge_refusal_has_scanner_detection": true,
"rejected_merge_not_applied": true,
"clean_merge_accepted": true,
"regeneration_preserves_exact_profile": true,
"encoded_setting_selected_template": true,
"mounted_hook_regular_executable": true,
"native_repository_with_template_created": true,
"new_repository_inherits_exact_profile": true,
"restart_preserves_coverage_profile": true,
"restart_preserves_templated_profile": true,
"inherited_profile_blocks_after_restart": true,
"encoded_setting_selected_default": true,
"rollback_new_repository_created": true,
"rollback_restores_default_creation_behavior": true,
"rollback_preserves_existing_coverage_hook": true,
"rollback_preserves_existing_templated_hook": true
},
"scope": "Isolated exact-image instance with SQLite and no network. One read-only mount of the public hook fixture simulates a Kubernetes file subPath. No production data, configuration or credentials used; production Kubernetes acceptance remains separate.",
"identity_http_status": 200,
"repository_create_http_status": 201,
"repository_create_error_kinds": [
"email"
],
"isolated_container_and_volumes_removed": true,
"last_phase": "rollback-default-template",
"executor_sha256": "2e7b431b5f0f270d8bce5de21fec2d0f8278a26ad9014f1f7a341c47e24ab83b",
"base_executor_sha256": "88bbf4235086b7ffc40ca4d7df7fafa1d67e2e1b41d2f0055ec82421ed4840cb"
}

View file

@ -0,0 +1,36 @@
{
"archival_format": "json-checksum-map-entries-v1",
"original_sha256": "d3658a4dd8ea9c31bed5ce7fc99f2e1a79ac89e59d9e3dcebb3fb517409abd27",
"converted_map_field": "source_hashes",
"reason": "Public non-secret ConfigMap source checksum was classified as generic-api-key when its filename was used as a JSON property name. Structured path/sha256 entries preserve exact historical reconstruction without detector exceptions.",
"document": {
"chart": "gitea-12.5.0",
"chart_sha256": "2d6f35f3d1ff0cfcab0f22142d20a1d6405fa974a79d7a18000904d233131823",
"source_hashes": [
{
"path": "helm/forgejo-values.yaml",
"sha256": "e97aa62fe767f4dc55f2c68c7ae1b69e61e632826a8ba3ccb4ffaca6e302174c"
},
{
"path": "Makefile",
"sha256": "6bd20555631eb4c22e0729779d6c24d6888a4e6d555ab1f03083697016d42b15"
},
{
"path": "manifests/forgejo-secret-scan-template.yaml",
"sha256": "8fad71aff5a25d8e31289072b9c06c6ec74ac74fedafc54dde7177bfe187c96a"
}
],
"rendered_target": "Deployment forgejo/forgejo-gitea",
"file_mounts_checked": [
"gitea",
"init-directories",
"init-app-ini",
"configure-gitea"
],
"encoded_setting": {
"name": "GITEA__GIT_0X2E_CONFIG__INIT_0X2E_TEMPLATEDIR",
"value": "/opt/forge-secret-template"
},
"scope": "Nonsecret owner source render only. No production config/Secret read. Live rollout uses a narrowly scoped patch, not full Helm reapplication."
}
}

View file

@ -0,0 +1,9 @@
{
"observed_at": "2026-10-13T12:08:04.722236+00:00",
"repository_count": 166,
"default_trees_without_gitlinks": 164,
"default_trees_with_gitlinks": 1,
"empty_repositories": 1,
"private_inventory_sha256": "5ea46926a62c5626111a8b2c7e1f6080baaae2af20b5da0c3bdc2a457b9ffe73",
"scope": "Only current default-branch tree modes examined. No source blobs read, no credential scan, no claim about other refs or past history. A gitlink count flags compatibility for the current refusal behavior."
}

View file

@ -15,7 +15,7 @@ No whole-estate SEC-DSM conformance or maturity level is claimed.
| G04 | Credential inventory is incomplete and its five metadata fields do not by themselves establish full SEC-DSM lifecycle coverage. Mason's dated survey found 49 paths; coverage is not current estate-wide discovery. | MASON-WP-0004-T03/T04; RPF-WP-0035; CUST-WP-0080-T03 | Current metadata-only discovery denominator; owners/consumers/scopes; rotation and revocation procedures; unknown entries remain findings |
| G05 | Agent effective authority and credential isolation are unproven. Glas declares no ready profile; the Custodian record grants no autopilot authority. | CUST-WP-0073-T02/T04; GLAS-WP-0012 | Actual admitted agent identity, runtime read-denial and tool-effect restrictions, task expiry/cancel cleanup, no cross-task credential accumulation |
| G06 | Native evidence delivery, source completeness and attribution remain incomplete. Independent archive custody is not source completeness. | AUDIT-WP-0013/0014/0015; RPF-WP-0035-T08; existing FLEX-WP-0031 and NK-WP-0035/0040 residual records | Admitted sender, durable outbox, native archive readback, heartbeat/reconciliation and failure recovery. Residual work requires explicit promotion |
| G07 | The original generated-only hook accepted a disposable private-key commit. The pinned scanner is now enforced by actual configured hooks on 22 exact workstation checkouts; clean, synthetic-secret, generated-only and unavailable-scanner probes pass. The additive forge scanner is installed on the same 22 named repositories and SSH refusal probes passed; ordinary acceptance, other write paths, broader forge/CI coverage and response remain unverified. | RMGR-WP-0021-T01/T02 source and local activation done; T03 forge/CI via FORGE-WP-0006, T04 response open; CUST-WP-0080-T03 coordinates railiance-forge ownership | Deterministic production-repo inventory; actual prevention/detection runs; scanner findings kept private; finding-to-revocation procedure |
| G07 | The original generated-only hook accepted a disposable private-key commit. The pinned scanner is now enforced by actual configured hooks on 22 exact workstation checkouts; clean, synthetic-secret, generated-only and unavailable-scanner probes pass. The additive forge scanner is installed on the same 22 named repositories and SSH refusal probes passed; ordinary SSH acceptance is verified; other deployed write paths, broader forge/CI coverage and response remain unverified. | RMGR-WP-0021-T01/T02 source and local activation done; T03 forge/CI via FORGE-WP-0006, T04 response open; CUST-WP-0080-T03 coordinates railiance-forge ownership | Deterministic production-repo inventory; actual prevention/detection runs; scanner findings kept private; finding-to-revocation procedure |
| G08 | Legacy root export and generation-prefix disclosure reproduced and removed in source. Bootstrap now uses a registered public recipient, explicit temporary-key resume and an encrypted handoff bound to the bundle digest. Other custody callers and native recovery acceptance remain unverified. | NK-WP-0049 T01 repaired; T02 caller reconciliation; T03 native recovery. NK-WP-0045 covers only the non-secret root-grant record; CUST-WP-0073 retains isolation/incident work | Inspect actual bootstrap callers; stop secret output reaching model/logs; owner-reviewed safe recovery procedure and tests |
| G09 | Rotation and revocation closure are incomplete: existing exposure rotation is founder-deferred, npm migration remains open, and five-field descriptions do not prove consumer adoption. | CUST-WP-0073-T05; SECRETS-WP-0006-T06; WARDEN-WP-0037-T03; RPF-WP-0035-T07 | For each relevant credential: new consumer accepted, old credential refused, copies reconciled; explicit DSM exception if still deferred |
| G10 | Build-mode exceptions and close-down checklist do not uniformly contain DSM-EXC requirement, risk, compensating controls and review fields. | CUST-WP-0080-T03; existing security-close-down checklist and October 11 decisions | Requirement-level exception records with real approval references and revalidation; no invented approval or retroactive conformance |
@ -145,7 +145,33 @@ hook omission is reproduced; a Git-template candidate remedies it in isolation.
retain all failed preparation attempts and distinguish these tests from deployed
PostgreSQL/SSO/ingress and Kubernetes acceptance. Import/mirror clone/fetch paths
bypass receive-pack. Remaining repository and response coverage is now also on
close-down checklist row 21. The tested template has not been deployed.
close-down checklist row 21. At that test stage the template had not been deployed; subsequent rollout and recovery are recorded below.
G07 template deployed on October 13: source railiance-apps `390a30a` and exact
FORGE6-TEMPLATE-LIFECYCLE-001 reached generation 12 Ready using a conditional
additive patch. Read-only file mounts avoid copying ConfigMap symlinks into Git
hooks. Actual Forgejo Git configuration now selects the template; native Git init
inherited the exact executable hook and malformed input refused. Original 22
profiles and binaries remain exact. [Deployment receipt](evidence/2026-10-13-netkingdom-security/forge-template-activation.json).
Thirty isolated configuration/lifecycle/rollback checks and six patch regressions
passed; explicit-default rollback is documented in the linked proposal.
[Fresh inventory](evidence/2026-10-13-netkingdom-security/forge-inventory-refresh.json)
now has 166 repositories, with [22 installed and 144 builtin-only hook inventories](evidence/2026-10-13-netkingdom-security/forge-hook-coverage-refresh.json).
No unknown custom hooks were found. Production API/SSO/frontend acceptance,
existing-repository expansion and imported/mirrored content/CI/response coverage
remain open; inherited hooks do not scan fetch or historical content.
G07 custody recovery on October 13: the generation-12 rollout later failed ordinary
source acceptance because scanner permissions were 0775. The earlier health,
hash and malformed-input checks did not prove scanner usability. The exact trial
is retained as failed. Revisions 2–4 restored 0755 and deployed a hash/owner-checked
startup guard in owner source `50b66c4`. Two actual Kubernetes replacements
(generations 13 and 14) completed the guard and passed inherited native clean
acceptance, intermediate-secret refusal, quarantine removal and clean recovery.
Six isolated custody cases also pass. [Recovery evidence](evidence/2026-10-13-netkingdom-security/forge-custody-recovery-summary.json)
supersedes the initial acceptance claim without discarding its original receipt.
The temporary native probes exclude builtin authorization; ordinary source pushes
supply separate SSH evidence. G07 and all wider coverage requirements remain open.
## SEC DSM requirement coverage

View file

@ -27,4 +27,4 @@ Add a row whenever you loosen, shortcut or leave unarmed a control. Remove nothi
| 18 | Ordinary authenticated operator/PDP admission for User Engine application.register | Executed one-use exception USER-CATALOGUE-INFDEC-20261011-R1 under confirmed founder approval b859698b-5d08-4da5-9599-4760c6a799f1 (02:07:09Z). Exact executor b0dbbb97 and transport3e594e0e; packet307c4fc5, grant SHA 72ee846034809f4ae6848eac3a43f2b7390f1df7a67ede50eff7435d1b43f839. Two exact informed-decision catalogue records committed; genuine denial retained, three truthful audits, one consumed ledger and unsent/unclaimed registered event verified. Private grant removed. Ordinary management admission remains unmet; no IAM/human claim/future registration/targeted replay authority. Execution deadline2026-10-12T23:59:59Z does not erase facts. [Outcome](evidence/2026-10-10-security-consolidation/user-catalogue-independent-postapply-verification.json). | Before customer production, supported authenticated operator management with genuine ordinary allow/deny/attribution and unavailable refusal; retire bootstrap use, preserve/reconcile ledger/audits/events. Broader USER-WP-0026-T03/USER-WP-0028-T02 remain live; no human journey completion inferred. | user-engine, access-engine; Custodian tracks | 2026-10-11 |
| 19 | Fresh informed-decision readback during a failed T05 rollout | Exact new trial INFD-WP-0009-T05/zlib85091-r1 approved under founder acceptance b5ce337c-5d7d-44fa-864d-706b54039f7c at2026-10-11T10:40:55Z. Image f558/source312ab97 verified Ready and actual canonical lifecycle HTTP200 at11:20:25Z, with latest database preserved. Proposed bounded30minute manual outage recovery was expressly accepted but never invoked; trial completed. Original proposed packet and pre-execution refusals remain immutable. [Outcome](evidence/2026-10-10-security-consolidation/zlib-native-completed/canonical-provider-verification.json). | Completed2026-10-11. Ordinary fresh-authority gate retained; no standing cached authority, autonomy promotion, other-trial or customer-production reuse. Any future standing recovery mechanism needs independently obtainable authority and separate review. | informed-decision, railiance-platform; Custodian tracks | 2026-10-11 |
| 20 | Unattended origin publication for assembly-economics checkpoints | rein-aharness approach `assembly-checkpoint` pushes to `coulomb/assembly-economics` origin without an attended session, limited to `checkpoints/`, fast-forward only, rebase-once, never force (mirrors the FI brief grant). Founder-approved 2026-10-12: assembly-economics `docs/decisions/2026-10-12-checkpoint-origin-publication.md`. Runs on legacy profile-absent routing until 2026-12-31. | Publication through rein-aharness's governed publish lane with a per-repo, path-scoped grant and a governed execution profile; legacy route removed. | rein-aharness / assembly-economics | 2026-10-12 |
| 21 | Independent repository secret detection and response (CUST-WP-0080 G07) | Scanner active on 22 workstation checkouts and 22 Forgejo repositories; 143 other repositories remain uncovered. New repositories lack the hook by default. Exact-image HTTP/API/merge and regeneration/restart checks pass in isolation; Git-template inheritance is tested but not deployed. Import/mirror fetch, historical/LFS/log/artifact coverage and private finding response remain open. [Evidence](evidence/2026-10-13-netkingdom-security/forge-lifecycle-summary.json). | Complete FORGE-WP-0006 and RMGR-WP-0021: admit the full repository denominator, persist/reconcile hooks across creation and lifecycle, verify deployed write paths and unsupported input handling, and prove private detection-to-response with existing custody owners. Rejection alone does not revoke prior exposure; CUST-WP-0073-T05 remains founder-deferred. | railiance-forge, repo-manager; Custodian coordinates | 2026-10-13 |
| 21 | Independent repository secret detection and response (CUST-WP-0080 G07) | Scanner active on 22 workstation checkouts and 22 Forgejo repositories; 144 of the refreshed 166 repositories remain builtin-only. Template inheritance deployed October 13; its first rollout broke scanner custody permissions and is retained as failed. Recovery adds a pinned startup guard; two Kubernetes replacements (generations 13/14) pass 0755 custody and native clean/secret/quarantine/recovery checks. Thirty isolated configuration/lifecycle/rollback checks pass; authenticated production API/SSO/frontend acceptance remains open. Import/mirror fetch, historical/LFS/log/artifact coverage and private finding response remain open. [Recovery evidence](evidence/2026-10-13-netkingdom-security/forge-custody-recovery-summary.json). | Complete FORGE-WP-0006 and RMGR-WP-0021: admit the full repository denominator, persist/reconcile hooks across creation and lifecycle, verify deployed write paths and unsupported input handling, and prove private detection-to-response with existing custody owners. Rejection alone does not revoke prior exposure; CUST-WP-0073-T05 remains founder-deferred. | railiance-forge, repo-manager; Custodian coordinates | 2026-10-13 |

View file

@ -234,6 +234,55 @@ scanner passes the revised staged source; no suppression or exception was added.
`scanner-triage.json` retains value-free classification and resolution evidence;
this does not close RMGR-WP-0021-T04's credential-response requirement.
2026-10-13 G07 template deployment: source railiance-apps `390a30a` adds the
immutable non-secret ConfigMap, read-only file subPath mounts, encoded Git
configuration and deployment prerequisite. Thirty isolated file-mount/config/
rollback checks and six conditional-patch regressions pass. Exact proposal
FORGE6-TEMPLATE-LIFECYCLE-001 passed the server dry-run and its additive live patch
reached generation 12 Ready on the unchanged image. Actual Forgejo Git config
selects the template; a native temporary Git repository inherited the regular,
executable exact hook and refused malformed input. All 22 original hook inventories
and both binaries remain unchanged; health passes and probe data was removed.
Existing admin identity and initial-only password mode were checked before restart.
No credential, identity, resource request or policy scope was changed. The first
owner-source sync hit the restart window; retry pushed `390a30a` successfully.
Receipt: `docs/evidence/2026-10-13-netkingdom-security/forge-template-activation.json`;
proposal, guarded rollback and source reconciliation instructions are in
`docs/changes/CUST-WP-0080/forge-template-operations.md`.
Fresh metadata now reconciles 166 database repositories and directories (one more
than October 12): all hook inventories are known; 22 have the admitted profile,
144 are builtin-only. No mirrors, forks or wikis are present; no protected-branch
rules are configured. Full identity inventories remain in mode-0600 local files;
committed receipts contain aggregates only. Production API/SSO/frontend creation,
existing-repository expansion, import/mirror content paths, CI and response
acceptance remain open; T03/T04 and the owner plan are not closed by this rollout.
2026-10-13 adverse acceptance and recovery: the initial generation-12 health,
hash and malformed-input checks missed a real restart failure. The next ordinary
Forge owner push refused with `scanner-file-refused`; both installed artifacts
had become group-writable 0775, consistent with Kubernetes fsGroup volume
processing. The original trial remains failed in supervision; its original
proposal and receipt are retained, not rewritten as an unchanged success.
Revision 2 verified exact hashes/ownership and restored only 0755. The queued
Forge owner source push `ec1d7c5` then succeeded through the enforced hook.
Revision 3 adds an exact-image unprivileged startup guard, mounted only to the
existing security subtree, persisted in railiance-apps `50b66c4`. Six isolated
checks cover remount permissions, idempotence, wrong hashes, unsafe modes,
symlinks and recovery. Server dry-run and rendered source match; rollback removes
only that init container and explicitly repairs modes after remount.
Generation 13 completed the guard and passed real inherited native receive
checks: clean acceptance, intermediate-secret refusal, quarantine removal and
clean recovery. Builtin authorization is excluded in the temporary native probe;
the ordinary source push separately exercises authenticated SSH. A second
Kubernetes replacement reached generation 14 and repeated all those checks with
a different pod UID; both startup guards exited zero and both binaries remained
0755. Authenticated SSH source acceptance after that replacement is recorded
separately in the recovery summary.
Evidence: `forge-custody-repair.json`, `forge-custody-rollout.json` and
`forge-custody-restart.json` in the October 13 evidence directory. This resolves
an availability regression without weakening scanner refusal or widening access.
## Verify native closure against every requirement
```task