Retain verified composed Forgejo source and private-input CLI proof

This commit is contained in:
codex 2026-10-18 01:38:52 +02:00
parent 33a7815e7b
commit af81f354ad
9 changed files with 4890 additions and 0 deletions

View file

@ -0,0 +1,7 @@
{
"returncode": 0,
"seconds": 9.48,
"binary_sha256": "ed6183ad80ef0e0877a14bcdecae0158b9c014a8376148889cf8af222c9f1e85",
"real_credentials": false,
"workspace": "/tmp/custodian-forgejo-successor-attempt2/cli-work-attempt2"
}

View file

@ -0,0 +1,13 @@
{
"passed": true,
"subprocesses": 48,
"real_backend_cli_and_sqlite": true,
"network": "disabled by bwrap",
"secret_inputs": "synthetic only",
"values_never_in_argv_env_output": true,
"actual_browser_login": false,
"actual_readonly_file_bind": true,
"replacement_preserves_other_config": true,
"unsafe_SQL_logging_refused": true,
"actual_error_and_slow_hooks_no_leak": true
}

View file

@ -0,0 +1,8 @@
{
"status": "verified",
"paths": 35,
"patch_sha256": "9bfc7384e81dc33fd2499dd0e13aa4367b2a61c0281eca500854aaf08498a15c",
"fuzz": 0,
"all_postimages_exact": true,
"native_changes": false
}

View file

@ -0,0 +1,200 @@
{
"schema": "custodian.forgejo-composed-source.v1",
"status": "source-tested; actual issuer claim interoperability still incomplete",
"archive_sha256": "1ddd8c926ca07d40436897971458ace7dcf1d3496a0813ab9e0d869edef2365c",
"forgejo_version": "11.0.3",
"producer_source_commit": "ca0cb75729a63c9288b7333382d0fa3eb1e8c9a3",
"patch_sha256": "9bfc7384e81dc33fd2499dd0e13aa4367b2a61c0281eca500854aaf08498a15c",
"files": [
{
"path": "cmd/admin_auth_oauth.go",
"before_sha256": "d5faee30ffbb9a560256c92a0e05e652b79e95e016448cbac7dbb6ad0fdad937",
"after_sha256": "d718d817c46f6393488a9c5a894b7535640cd0ec1a1ae3f72f8411a2bbec3b38"
},
{
"path": "cmd/admin_auth_oauth_secret_linux.go",
"before_sha256": null,
"after_sha256": "6741961f1df67c1afbf34c796e549425f0e04a0d0678c6eedb05f66d4754c64b"
},
{
"path": "cmd/admin_auth_oauth_secret_linux_test.go",
"before_sha256": null,
"after_sha256": "b28c2369dfab9bf2ca48e707e01b4394a36c8be5490cfe5bb658017bb626c4b2"
},
{
"path": "cmd/admin_auth_oauth_secret_other.go",
"before_sha256": null,
"after_sha256": "54056c989a4757e73db0252130e7c16bf16f5ec8eaaca2468f019ba2ef5c532d"
},
{
"path": "go.mod",
"before_sha256": "652162a4a95c6d1c349d2a8c0b514def5abd837f001137b77b7b3f4a0f162d00",
"after_sha256": "130138c4c3ecc29fa0d7f1e2eef0589ddf3b06071f3cc0448aa7b2326847653a"
},
{
"path": "models/auth/source_operation.go",
"before_sha256": null,
"after_sha256": "ef1b07ef6cf454015d00cde8588835f5118d829da91eb0303d37184859345a31"
},
{
"path": "models/auth/source_operation_internal_test.go",
"before_sha256": null,
"after_sha256": "af0f424d3a1fd2b29793b64baa1fa07284fee7ea0b7721c503db4efac0d871cb"
},
{
"path": "models/auth/source_operation_process_test.go",
"before_sha256": null,
"after_sha256": "40d33b11a01cb659f0bf6f8b545c4d79910e8c082a789060bcbc9cadeee965bd"
},
{
"path": "models/auth/source_operation_test.go",
"before_sha256": null,
"after_sha256": "77d311bca42f48faf3fbdc15c53582e3d8a6d9108a72ed7bb6172e5b88951428"
},
{
"path": "models/db/context.go",
"before_sha256": "10774e272206c7a8d6ebfb106b37d535de77f7e96ba57657b4dad960639f8519",
"after_sha256": "d4c4acfcb8987071aecf5cd957b3821c8d2619a2696398d37c2970f2527686f7"
},
{
"path": "models/db/engine.go",
"before_sha256": "774cedc74e1a8d9ae6f9e5f106c6cbb66f12c11b42e99ef07a7affd03269c3d2",
"after_sha256": "43683804d0c5bc6ead1dba0344c69dd196825cf3d5eb502af8d932a7b8207b96"
},
{
"path": "models/db/sensitive_sql.go",
"before_sha256": null,
"after_sha256": "06a298d0d6e628e6920384d4ddeef449e40c090841ebaa3f6d175725a45ae584"
},
{
"path": "models/db/sensitive_sql_logging_test.go",
"before_sha256": null,
"after_sha256": "d924b35d30686473caf29601cf51cc363ba68158cb4ef0981c9e2fc7b03c04b7"
},
{
"path": "models/db/sensitive_sql_test.go",
"before_sha256": null,
"after_sha256": "de6353c955047c3311747e4777b9f9b2195e9efb19160b971dd758dfc94cf31b"
},
{
"path": "routers/web/admin/auths.go",
"before_sha256": "eeed6999e4655993b55873bb51efb4fcd946532255b9473d16e086e04a1ec911",
"after_sha256": "3498229e06fb9ff52396bcb5e68c0cc2d2ad24e6494533d8717d7c833f3e2923"
},
{
"path": "routers/web/auth/oauth.go",
"before_sha256": "6f454b3550b150178a8597a9437adb0c626556f34c15a13778c15d675b05659b",
"after_sha256": "58bff778e0dc17a521706289bf45c3e0d76a759a0874f76c379b3d1ddb419b17"
},
{
"path": "services/auth/source/oauth2/access_engine.go",
"before_sha256": null,
"after_sha256": "851dc8712c883ec2f3135406cdce58d7adf733c0f79501e2594e41646faf28a2"
},
{
"path": "services/auth/source/oauth2/access_engine_azp_test.go",
"before_sha256": null,
"after_sha256": "5f94f43ade9a79b6b7ef529c80115a8c32acfece425dc8ed3f24e6ad839fc0ce"
},
{
"path": "services/auth/source/oauth2/access_engine_callback_test.go",
"before_sha256": null,
"after_sha256": "e2fcbfcc2f1077996c947c5c6fd5150aaaa11ba0e77e05f07055a355978398be"
},
{
"path": "services/auth/source/oauth2/access_engine_test.go",
"before_sha256": null,
"after_sha256": "0552719648e821243c5c6f887359a724c1440cd077e435f15a444bd3c99da888"
},
{
"path": "services/auth/source/oauth2/providers_openid.go",
"before_sha256": "6ddca7fb4ab8b16e6b096481cfb40ca5abdf3a327f38c895b95b3846cc7c2248",
"after_sha256": "5dd9b906cce434c774968d57fcc790cb678147345fea4d2e58477ec6eed70bcf"
},
{
"path": "services/auth/source/oauth2/required_claim_values.go",
"before_sha256": null,
"after_sha256": "29f16d07464bbe12e3f65616b6c1eb60561d04cd8ff89ecbfb56f93dbeaf1cda"
},
{
"path": "services/auth/source/oauth2/required_claim_values_test.go",
"before_sha256": null,
"after_sha256": "54a8bd97dc51c524a9adf0c6aefb9a19fa152d234b3bb929bf1a90932662d3d0"
},
{
"path": "services/auth/source/oauth2/source.go",
"before_sha256": "a6ee5146c07d8efb1837ef91f1ab5f3139f69a1ff66f814d5e9c6053bf9b0d8b",
"after_sha256": "44e99883c2ec479427b3f72c73353fe33936eed2e5c962b2fa40803576aa5642"
},
{
"path": "services/auth/source/oauth2/source_callout.go",
"before_sha256": "c30c418182d4cb98b1e448f621c93322accfbbf92d6a59e6df1ae45a7aa8fc7b",
"after_sha256": "6abba69df97afc62467f0328eaa5a6383cfb4900202a081dec4b84a21b37349c"
},
{
"path": "third_party/access-engine-pep/LICENSE",
"before_sha256": null,
"after_sha256": "ff413f361a3b3bf44cf50f110fbcc4837064d57eefbcef89df2b43997c6262b6"
},
{
"path": "third_party/access-engine-pep/go.mod",
"before_sha256": null,
"after_sha256": "fea700810e47c1ba5638b86a168f8531c083a6e1222ca80bd2a8947b2ea6f2c3"
},
{
"path": "third_party/access-engine-pep/internal/sign/sign.go",
"before_sha256": null,
"after_sha256": "0df9515922e90775ebbfac798361dcafb20da8a4213796b44e246b8a92046cde"
},
{
"path": "third_party/access-engine-pep/pkg/api/canonical.go",
"before_sha256": null,
"after_sha256": "a122a2e8cd524333b13c5939c7a3685e41efbe47497a64e0c7297c92b7f10d19"
},
{
"path": "third_party/access-engine-pep/pkg/api/caring.go",
"before_sha256": null,
"after_sha256": "1907156f0f889e7b9e9c14970cf7a86a20d00478a496efaf92c7231f27fec5e3"
},
{
"path": "third_party/access-engine-pep/pkg/api/resource_manifest.go",
"before_sha256": null,
"after_sha256": "f0fa4080287d389ed48c2b1d6af96cc62e57ef72c902333f677f964420be133f"
},
{
"path": "third_party/access-engine-pep/pkg/decisionpep/client.go",
"before_sha256": null,
"after_sha256": "0d57f6ddaca5c3f81ce426c0e8b14ba14ea5962096d3be5909aa456eef19eeba"
},
{
"path": "third_party/access-engine-pep/pkg/decisionpep/client_test.go",
"before_sha256": null,
"after_sha256": "1a6be3485f580f95a1f1406fb76e525f2c4bf02fe04a424cfe0095bb728e3f62"
},
{
"path": "third_party/access-engine-pep/producer-source.json",
"before_sha256": null,
"after_sha256": "c1d83e3d79f9860ffaa61ec19a6b3415e914d3058b11b07cdd05a7d1182cdceb"
},
{
"path": "third_party/access-engine-pep/testsupport/signfixture/sign.go",
"before_sha256": null,
"after_sha256": "fe4aa86f444dc20145aefbaca35b24f046264b2c5c8b4e3b057a4496b77a4d29"
}
],
"tests": {
"producer_race_passes_including_subtests": 494,
"producer_existing_skips": 3,
"consumer_passes_including_subtests": 76,
"consumer_skips": 0,
"actual_cli_subprocesses": 48
},
"original_bad_context_snapshot": "empty; recovered from frozen patch and verified exact pre/postimage hashes",
"known_open_work": [
"KeyCape emits assurance object; current candidate expects a string claim. Implement actual source format and authentication-time freshness.",
"Actual ongoing HTTP/API/Git/SSH and current operation/inverse identities.",
"Release-wide writer exclusion, authenticated sessions/evidence/recovery and private journal custody/retention.",
"Full frontend/container/release rebuild and independently admitted native trust/rollout/consumer acceptance."
],
"native_gates_closed": 0
}

View file

@ -0,0 +1,33 @@
"""Reconstruct this frozen consumer directly from verified public archives."""
from pathlib import Path
import argparse
import hashlib
import json
import subprocess
import tarfile
HERE = Path(__file__).resolve().parent
parser = argparse.ArgumentParser()
parser.add_argument('--inputs', type=Path, required=True)
parser.add_argument('--workspace', type=Path, required=True)
args = parser.parse_args()
work = args.workspace.resolve()
assert work.is_relative_to('/tmp') and not work.exists()
manifest = json.loads((HERE / 'composed-source-inventory.json').read_text())
patch = (HERE / 'composed-forgejo-source.patch').read_bytes()
assert hashlib.sha256(patch).hexdigest() == manifest['patch_sha256']
archives = [('forgejo-v11.0.3.tar.gz', manifest['archive_sha256']),
('go1.24.3.tar.gz', '3333f6ea53afa971e9078895eaa4ac7204a8c6b5c68c10e6bc9a33e8e391bdd8')]
for name, digest in archives:
assert hashlib.sha256((args.inputs / name).read_bytes()).hexdigest() == digest
work.mkdir()
for name, _ in archives:
with tarfile.open(args.inputs / name) as tar:
tar.extractall(work, filter='data')
(work / 'forgejo').rename(work / 'source')
run = subprocess.run(['patch', '--fuzz=0', '-p1'], input=patch, cwd=work / 'source', capture_output=True)
assert run.returncode == 0, 'Pinned composite patch refused; workspace retained'
for row in manifest['files']:
assert hashlib.sha256((work / 'source' / row['path']).read_bytes()).hexdigest() == row['after_sha256'], row['path']
(work / 'home').mkdir()
print(json.dumps({'status': 'prepared', 'workspace': str(work), 'paths_verified': len(manifest['files']), 'native_changes': False}))

View file

@ -0,0 +1,72 @@
"""Run public dependency preparation or isolated actual consumer package tests."""
from pathlib import Path
import argparse
import hashlib
import json
import subprocess
import time
HERE = Path(__file__).resolve().parent
WORK = Path('/tmp/custodian-forgejo-successor-attempt2')
parser = argparse.ArgumentParser()
parser.add_argument('action', choices=['download', 'tidy', 'test', 'build'])
parser.add_argument('--attempt', type=int, default=1)
parser.add_argument('--workspace', type=Path, default=WORK)
args = parser.parse_args()
WORK = args.workspace.resolve()
assert WORK.is_relative_to('/tmp') and WORK.is_dir()
stem = f'consumer-{args.action}-attempt{args.attempt}'
assert not (HERE / (stem + '.json')).exists()
argv = ['bwrap', '--unshare-pid', '--die-with-parent', '--new-session']
if args.action in ['test', 'build']:
argv += ['--unshare-net']
for name in ['/usr', '/lib', '/lib64']:
argv += ['--ro-bind', name, name]
argv += ['--symlink', 'usr/bin', '/bin', '--proc', '/proc', '--dev', '/dev',
'--tmpfs', '/tmp', '--bind', str(WORK), '/work',
'--ro-bind', '/home/worsch/go/pkg/mod/cache/download', '/cached',
'--ro-bind', '/etc/ssl/certs', '/etc/ssl/certs',
'--ro-bind', '/etc/resolv.conf', '/etc/resolv.conf', '--chdir', '/work/source', '--clearenv']
if args.action == 'test':
fixtures = WORK / 'descriptor-fixtures'
fixtures.mkdir(exist_ok=True)
for name, value in [('race', 'A' * 64), ('race-replacement', 'B' * 64)]:
file = fixtures / name
if file.exists():
file.chmod(0o600)
file.write_text(value)
file.chmod(0o400)
argv += ['--dir', '/run', '--ro-bind', str(fixtures), '/run/forgejo-oidc',
'--bind', str(fixtures), '/run/writable-input']
env = {'PATH': '/work/go/bin:/usr/bin:/bin', 'HOME': '/work/home', 'GOPATH': '/work/gopath',
'GOTOOLCHAIN': 'local', 'GOPROXY': 'off' if args.action in ['test', 'build'] else 'file:///cached,https://proxy.golang.org',
'GOSUMDB': 'off' if args.action in ['test', 'build'] else 'sum.golang.org', 'GOVCS': '*:off',
'GOMODCACHE': '/work/modules', 'GOCACHE': '/work/gocache', 'GOMAXPROCS': '4',
'CGO_ENABLED': '1', 'GIT_CONFIG_NOSYSTEM': '1', 'GIT_CONFIG_GLOBAL': '/work/home/test.gitconfig', 'GIT_TERMINAL_PROMPT': '0'}
for key, value in env.items():
argv += ['--setenv', key, value]
command = ['go', 'mod', args.action] if args.action != 'test' else ['go', 'test', '-p', '1', '-json', '-tags', 'sqlite sqlite_unlock_notify',
'./services/auth/source/oauth2', './cmd', './models/auth', './models/db', '-run',
'TestAccessEngine|TestVerifiedApplicationIdentity|TestRequired|TestCurrent|TestPrivateSecret|TestSourceOperation|TestSensitiveSQL', '-count=1']
if args.action == 'build':
command = ['go', 'build', '-p', '4', '-tags', 'sqlite sqlite_unlock_notify', '-o', '/work/forgejo', '.']
argv += ['--'] + command
if (HERE / 'composed-source-inventory.json').exists():
selected = {row['path'] for row in json.loads((HERE / 'composed-source-inventory.json').read_text())['files']}
else:
selected = {row['path'] for row in json.loads((HERE / 'consumer-reconstruction.json').read_text())['paths']}
selected.update(str(item.relative_to(HERE / 'forgejo-source')) for item in (HERE / 'forgejo-source').rglob('*') if item.is_file())
selected.update(str(item.relative_to(WORK / 'source')) for item in (WORK / 'source/third_party/access-engine-pep').rglob('*') if item.is_file())
selected.update(['go.mod', 'go.sum'])
source_pins = {name: hashlib.sha256((WORK / 'source' / name).read_bytes()).hexdigest() for name in sorted(selected)}
started = time.monotonic()
with (HERE / (stem + '.log')).open('xb') as log:
run = subprocess.run(argv, stdout=log, stderr=subprocess.STDOUT, timeout=900)
pins = {name: hashlib.sha256((WORK / 'source' / name).read_bytes()).hexdigest() for name in ['go.mod', 'go.sum']}
result = {'returncode': run.returncode, 'seconds': round(time.monotonic() - started, 2),
'argv': argv, 'workspace': str(WORK), 'module_hashes': pins,
'composed_source_pins': source_pins,
'network': 'disabled' if args.action in ['test', 'build'] else 'public module download only', 'pid_namespace': 'private', 'real_credentials': False}
(HERE / (stem + '.json')).write_text(json.dumps(result, indent=2) + '\n')
print(json.dumps({'action': args.action, 'returncode': run.returncode, 'seconds': result['seconds']}))
raise SystemExit(run.returncode)

View file

@ -0,0 +1,32 @@
{
"observed_at": "2026-10-17T23:36:49.758150+00:00",
"producer_source_commit": "ca0cb75729a63c9288b7333382d0fa3eb1e8c9a3",
"producer_source_paths_verified": 24,
"projection": "primary/railiance01",
"producer_full_race": {
"returncode": 0,
"passed_tests_and_subtests": 494,
"existing_skips": 3
},
"consumer_packages": {
"returncode": 0,
"passed_tests_and_subtests": 76,
"skips": 0
},
"composed_cli": {
"returncode": 0,
"subprocesses": 48,
"real_credentials": false
},
"composite_patch": {
"status": "verified",
"paths": 35,
"patch_sha256": "9bfc7384e81dc33fd2499dd0e13aa4367b2a61c0281eca500854aaf08498a15c",
"fuzz": 0,
"all_postimages_exact": true,
"native_changes": false
},
"native_gates_closed": 0,
"formal_owner_assent": false,
"known_open": "Actual KeyCape structured assurance/authentication-time/provenance interoperability; ongoing protected paths; complete owner sessions/exclusion/evidence/custody/native acceptance."
}

View file

@ -0,0 +1,99 @@
import pathlib,subprocess,json,sqlite3,os,shlex,time
w=pathlib.Path('/tmp/custodian-forgejo-successor-attempt2/cli-work-attempt2');s='/tmp/forgejo-private-input-r1';r='/tmp/forgejo-independent-r1'
fixtures=w/'fixtures'; fixtures.mkdir(exist_ok=True)
A='AbCdEf012345678-'*4;B='ZyXwVu_987654321'*4 # synthetic exact64
assert len(A)==64 and len(B)==64
for name,data,mode in [('good',A,0o400),('replacement',B,0o440),('newline',A+'\n',0o400),('short',A[:-1],0o400),('long',A+'A',0o400),('badbyte',A[:-1]+'=',0o400),('world',A,0o444),('writable',A,0o600),('executable',A,0o500),('empty','',0o400),('CLIENT_SECRET','',0o400)]:
p=fixtures/name
if p.exists():p.chmod(0o600)
p.write_text(data);p.chmod(mode)
for name in ['symlink']:
p=fixtures/name
if not p.exists():p.symlink_to('good')
if not (fixtures/'fifo').exists():os.mkfifo(fixtures/'fifo',0o400)
(fixtures/'directory').mkdir(exist_ok=True)
(fixtures/'badparent').mkdir(exist_ok=True);(fixtures/'badparent').chmod(0o777)
if (fixtures/'badparent/value').exists():(fixtures/'badparent/value').chmod(0o600)
(fixtures/'badparent/value').write_text(A);(fixtures/'badparent/value').chmod(0o400)
if not (fixtures/'parentlink').exists():(fixtures/'parentlink').symlink_to('directory',target_is_directory=True)
(w/'runtime').mkdir(exist_ok=True)
dbfile=w/'runtime/test.db'
if dbfile.exists():dbfile.unlink() # isolated synthetic test database only
ini=w/'runtime/app.ini';ini.write_text('''APP_NAME = synthetic-private-input-test
RUN_USER = worsch
WORK_PATH = /tmp/custodian-forgejo-successor-attempt2/cli-work-attempt2/runtime
[security]
INSTALL_LOCK = true
SECRET_KEY = SYNTHETIC-TEST-KEY
[database]
DB_TYPE = sqlite3
SLOW_QUERY_THRESHOLD = 1ns
PATH = /tmp/custodian-forgejo-successor-attempt2/cli-work-attempt2/runtime/test.db
[server]
APP_DATA_PATH = /tmp/custodian-forgejo-successor-attempt2/cli-work-attempt2/runtime/data
OFFLINE_MODE = true
[repository]
ROOT = /tmp/custodian-forgejo-successor-attempt2/cli-work-attempt2/runtime/repos
[log]
MODE = console
LEVEL = Error
''')
env={'PATH':'/usr/bin:/bin','HOME':str(w/'home'),'GIT_CONFIG_NOSYSTEM':'1','GIT_CONFIG_GLOBAL':str(w/'home/test.gitconfig'),'GIT_TERMINAL_PROMPT':'0','USER':'worsch','GITEA_WORK_DIR':str(w/'runtime')}
base=['bwrap','--unshare-pid','--unshare-net','--ro-bind','/usr','/usr','--ro-bind','/lib','/lib','--ro-bind','/lib64','/lib64','--symlink','usr/bin','/bin','--proc','/proc','--dev','/dev','--tmpfs','/tmp','--bind',str(w),str(w),'--dir','/run','--ro-bind',str(fixtures),'/run/forgejo-oidc','--ro-bind',str(fixtures/'good'),'/run/forgejo-oidc/CLIENT_SECRET','--bind',str(fixtures),'/run/writable-input','--chdir',str(w/'runtime'),'--','env','-i']+[k+'='+v for k,v in env.items()]+[str(w/'forgejo'),'--config',str(ini)]
results=[]
def run(args,ok=True,reason=None):
p=subprocess.run(base+args,capture_output=True,text=True,timeout=60); output=p.stdout+p.stderr
assert A not in output and B not in output, 'synthetic input leaked in output'
assert all(A not in arg and B not in arg for arg in base+args), 'synthetic input in argv'
assert all(A not in v and B not in v for v in env.values()), 'synthetic input in env'
results.append({'operation':args,'returncode':p.returncode,'stdout_stderr_secret_absent':True})
(w/'cli-attempts.json').write_text(json.dumps(results,indent=2))
if (p.returncode==0)!=ok:raise AssertionError('CLI unexpected status: '+repr(args)+'\n'+output[-5000:])
if reason:assert reason in output,output[-2000:]
return p
run(['migrate'])
clean_ini=ini.read_text()
ini.write_text(clean_ini.replace('DB_TYPE = sqlite3', 'DB_TYPE = sqlite3\nLOG_SQL = true'))
run(['admin','auth','add-oauth','--name','refused-debug-logging','--provider','github','--secret-file','/run/forgejo-oidc/CLIENT_SECRET'],False,'OAuth configuration requires SQL logging disabled')
ini.write_text(clean_ini)
def rows():
with sqlite3.connect(w/'runtime/test.db') as db:return db.execute('select id,name,cfg from login_source order by id').fetchall()
def cfg():return json.loads(rows()[0][2])
run(['admin','auth','add-oauth','--name','synthetic-oauth','--provider','github','--key','synthetic-public-id','--secret-file','/run/forgejo-oidc/CLIENT_SECRET','--group-claim-name','groups','--admin-group','unchanged-admin','--scopes','read:user,user:email'])
assert len(rows())==1 and cfg()['ClientSecret']==A
id=str(rows()[0][0]); before=cfg()
run(['admin','auth','update-oauth','--id',id,'--icon-url','https://synthetic.invalid/icon'])
assert cfg()['ClientSecret']==A
preserved=cfg()
run(['admin','auth','update-oauth','--id',id,'--secret-file','/run/forgejo-oidc/replacement'])
after=cfg();assert after['ClientSecret']==B
assert {k:v for k,v in after.items() if k!='ClientSecret'}=={k:v for k,v in preserved.items() if k!='ClientSecret'},'secret replacement changed unrelated config'
# Guarded inactive source exercises actual update omission and strict list semantics
# without discovery/network or claiming a browser login.
with sqlite3.connect(w/'runtime/test.db') as db:
guarded=after|{'Provider':'openidConnect','OpenIDConnectAutoDiscoveryURL':'https://synthetic.invalid/.well-known/openid-configuration','RequiredClaimName':'groups','RequiredClaimValues':['forge-admins','forge-coulomb-owners','forge-coulomb-readers']}
db.execute('update login_source set is_active=0,cfg=? where id=?',(json.dumps(guarded),id))
run(['admin','auth','update-oauth','--id',id,'--name','synthetic-guarded'])
assert cfg()['RequiredClaimValues']==guarded['RequiredClaimValues'] and cfg()['ClientSecret']==B
run(['admin','auth','update-oauth','--id',id,'--secret-file','/run/forgejo-oidc/good'])
assert cfg()['ClientSecret']==A and cfg()['RequiredClaimValues']==guarded['RequiredClaimValues']
for path in ['/run/forgejo-oidc/'+n for n in ['newline','short','long','badbyte','world','writable','executable','empty','symlink','fifo','directory','missing','badparent/value','parentlink/value']]+['relative','','/run/forgejo-oidc/../forgejo-oidc/good','/run/writable-input/good']:
before=rows()
for cmd in [['admin','auth','add-oauth','--name','refused','--provider','github'],['admin','auth','update-oauth','--id',id,'--name','refused']]:
run(cmd+['--secret-file',path],False,'secret-file input refused')
assert rows()==before,'failure changed DB'
for action in [['add-oauth','--name','refused'],['update-oauth','--id',id]]:
before=rows();run(['admin','auth']+action+['--secret','','--secret-file','/run/forgejo-oidc/good'],False,'mutually exclusive');assert rows()==before
# Failure to clear/ambiguously replace gate keeps stored application bytes unchanged.
before=rows();run(['admin','auth','update-oauth','--id',id,'--required-claim-values',''],False);assert rows()==before
# Actual XORM error hook under a failing SQLite trigger must not disclose cfg.
before=rows()
with sqlite3.connect(w/'runtime/test.db') as database:
database.execute("create trigger synthetic_private_error before update on login_source begin select raise(abort, 'synthetic-db-failure'); end")
run(['admin','auth','update-oauth','--id',id,'--secret-file','/run/forgejo-oidc/replacement'],False,'OAuth source update failed')
assert rows()==before
with sqlite3.connect(w/'runtime/test.db') as database:database.execute('drop trigger synthetic_private_error')
# Legacy explicit secret remains supported; synthetic legacy string is not private input.
run(['admin','auth','update-oauth','--id',id,'--secret','synthetic-legacy-literal'])
assert cfg()['ClientSecret']=='synthetic-legacy-literal' and cfg()['RequiredClaimValues']==guarded['RequiredClaimValues']
(w/'cli-results.json').write_text(json.dumps({'passed':True,'subprocesses':len(results),'real_backend_cli_and_sqlite':True,'network':'disabled by bwrap','secret_inputs':'synthetic only','values_never_in_argv_env_output':True,'actual_browser_login':False,'actual_readonly_file_bind':True,'replacement_preserves_other_config':True,'unsafe_SQL_logging_refused':True,'actual_error_and_slow_hooks_no_leak':True},indent=2));print('CLI integrations passed:',len(results))