Create private evidence directories safely on fsGroup volumes

Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc
This commit is contained in:
tegwick 2026-09-11 12:41:04 +02:00
parent bda9381f07
commit 1a12223574
5 changed files with 57 additions and 2 deletions

View file

@ -45,6 +45,11 @@ actual Flex Auth, Approval Engine and Audit Core with synthetic identity/custody
writer, backup/inspection commands and eight review-only Kubernetes objects.
Network-isolated container restart/restore preserves unresolved submissions.
The 2026-09-11 fsGroup startup correction handles newly created private
directories on Kubernetes volumes; unsafe existing directories still refuse.
Its full local suite passes 335 tests with 39 optional checks skipped. The
earlier local image must be rebuilt with this correction before publication.
Remaining: native policy package/caller/assignment admission, registered human
login and deployed binding, independent production audit custody, image
publication/cutover, platform backup/restore and operator recovery admission.

View file

@ -56,7 +56,9 @@ Kubernetes projected ConfigMaps are root-owned and symlinked. The container
entrypoint reads at most 16 KiB from `/configuration/runtime.json`, requires
`evidence_db=/data/private/review.sqlite`, and copies the configuration into
owned ephemeral storage at `/run/informed-decision/private/runtime.json` (0600).
It creates `/data/private` as the process user with mode 0700 and refuses unsafe
It creates `/data/private` as the process user with mode 0700, clearing the
inherited setgid bit only on directories it just created on fsGroup volumes,
and refuses unsafe
existing ownership/modes. It does not silently chmod or take over existing data.
The evidence database remains 0600. Credential callbacks read rotating projected
files directly; bearer tokens are never copied into SQLite or the config snapshot.

View file

@ -23,7 +23,20 @@ def private_directory(path):
path = Path(path)
if not path.is_absolute():
raise ValueError("absolute private directory required")
path.mkdir(mode=0o700, exist_ok=True)
try:
path.mkdir(mode=0o700)
except FileExistsError:
pass
else:
# Kubernetes fsGroup volumes make newly created children inherit setgid.
# Normalize only this new owned directory; never take over existing data.
fd = os.open(path, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW)
try:
info = os.fstat(fd)
if info.st_uid == os.getuid() and stat.S_IMODE(info.st_mode) == 0o2700:
os.fchmod(fd, 0o700)
finally:
os.close(fd)
info = path.lstat()
if (not stat.S_ISDIR(info.st_mode) or info.st_uid != os.getuid()
or stat.S_IMODE(info.st_mode) != 0o700):

View file

@ -101,3 +101,20 @@ def test_listener_exposure_requires_explicit_supported_setting(monkeypatch,host)
assert not observed
else:
web.main();assert observed[0]['host']==(host or '127.0.0.1')
def test_fsgroup_volume_creates_private_store_without_inherited_setgid(tmp_path):
volume=tmp_path/'volume';volume.mkdir();volume.chmod(0o2770)
directory=private_directory(volume/'private')
assert stat.S_IMODE(directory.stat().st_mode)==0o700
store=Store(directory/'review.sqlite')
assert store.outbox()==[]
assert private_directory(directory)==directory
@pytest.mark.parametrize('mode',[0o2700,0o2770])
def test_existing_setgid_directory_is_refused_without_chmod(tmp_path,mode):
directory=tmp_path/'private';directory.mkdir();directory.chmod(mode)
with pytest.raises(ValueError,match='unsafe ownership or mode'):
private_directory(directory)
assert stat.S_IMODE(directory.stat().st_mode)==mode

View file

@ -667,6 +667,24 @@ publication, owner service/namespace and exact peer admission, registration and
real human/deployed binding, platform backup/restore and product acceptance.
No cluster apply, native secret read, factory attempt or paid call occurred.
2026-09-11 — **native probe exposed fsGroup startup incompatibility.**
The native synthetic sender Job failed during private-store bootstrap, before
any audit HTTP request. A filesystem regression reproduced the same refusal:
Kubernetes fsGroup-style setgid volume roots cause `mkdir(0700)` to create a
`2700` child, which the evidence store correctly refuses. `private_directory`
now clears inherited setgid only on a directory this invocation just created,
through a non-symlink directory descriptor. Unsafe existing directories keep
their mode and remain refused. The private evidence contract stays 0700/0600.
The new regression failed before the correction and passes after it; existing
2700/2770 directories remain unchanged and refused. The available full suite
passes 335 tests, with 39 optional checks skipped in this environment. The native
sender probe will consume this exact helper from pinned source before image
rebuild/publication. The previously built local image is superseded by this
source correction and must not be published as the final service candidate.
T08 stays progress for native acceptance and the existing deployment/human
binding/recovery gates; no service cutover or human disposition occurred.
## Known risks
- **T02 is a hard gate.** Writing the blueprint before the layer ruling risks