Dashboardinstitutional-transition-labSession 0k60TJeo0Lqv

Session 0k60TJeo0Lqv

Full ID: 0k60TJeo0Lqvt0U3Z
Messages: 17
Distillations: 1
Time range: 2026-09-09 04:36:13 — 2026-09-09 04:52:25

Conversation (17 messages)

Perform a fresh read-only security audit of only input descriptor opening and bounded reads for issue #4 in /home/byk/Code/institutional-transition-lab. This replaces a reviewer that returned empty twice. Never edit files and never inspect governance bodies, coding decisions, outcomes, reports, dates, detector output, or source-adjudication scopes.

Scope only: _read_regular_file and directly related constants/tests; rejection of symlinks, FIFOs, sockets, block/character devices, directories, and other special files without blocking or following links; descriptor/path TOCTOU; exact byte-limit enforcement, short/partial reads, descriptor cleanup, and error behavior. Do not review JSON parsing, pins/hashes, ZIP internals, or CLI output writing.

Review current working copy. Run narrow read-only tests/probes. Return substantive evidence, never empty. Findings first, severity ordered, exact current file:line references, PASS/CONCERN/MUST-FIX/BLOCKED labels. Every defect needs a deterministic regression. If clean, state PASS with tests/probes and residual risks. End exactly MERGE or DO-NOT-MERGE.

gateway · 2026-09-09 04:36:13
result
Found 26 matches
/home/byk/Code/institutional-transition-lab/tests/test_governance_adjudication.py:
  Line 940: 				"from institution_lab.governance_adjudication import _read_regular_file; "

  Line 941: 				f"_read_regular_file(Path({str(fifo)!r}))"


/home/byk/Code/institutional-transition-lab/src/institution_lab/governance_adjudication.py:
  Line 65: MAX_INPUT_BYTES = 16 * 1024 * 1024

  Line 66: MAX_ARTIFACT_BYTES = 32 * 1024 * 1024

  Line 68: MAX_ARCHIVE_MEMBER_BYTES = 16 * 1024 * 1024

  Line 69: MAX_ARCHIVE_EXPANDED_BYTES = 64 * 1024 * 1024

  Line 94: def _read_regular_file(path: Path, *, max_bytes: int = MAX_INPUT_BYTES) -> bytes:

  Line 113: 	return _bytes_sha256(_read_regular_file(path, max_bytes=MAX_ARTIFACT_BYTES))

  Line 188: 			if sum(info.file_size for info in infos) > MAX_ARCHIVE_EXPANDED_BYTES:

  Line 199: 				if info.file_size > MAX_ARCHIVE_MEMBER_BYTES:

  Line 202: 					member_bytes = source.read(MAX_ARCHIVE_MEMBER_BYTES + 1)

  Line 203: 				if ...
gateway · 2026-09-09 04:36:24
result
Found 8 matches
/home/byk/Code/institutional-transition-lab/tests/test_governance_adjudication.py:
  Line 813: @pytest.mark.parametrize("alias_kind", ["direct", "symlink", "hardlink"])

  Line 825: 	elif alias_kind == "symlink":

  Line 826: 		paths["summary_json"].symlink_to(paths[input_name])

  Line 931: def test_regular_file_reader_rejects_fifo_without_blocking(tmp_path: Path) -> None:

  Line 932: 	fifo = tmp_path / "input.fifo"

  Line 933: 	os.mkfifo(fifo)

  Line 941: 				f"_read_regular_file(Path({str(fifo)!r}))"

  Line 951: 	assert "input path is not a regular file" in result.stderr
gateway · 2026-09-09 04:36:30
result
<path>/home/byk/Code/institutional-transition-lab/src/institution_lab/governance_adjudication.py</path>
<type>file</type>
<content>
45: 	"documents_sha256": "d70f7f795847968339a645b6dbd6d4428af6bd6044de44fa2602d4c4343b0ee9",
46: 	"documents_file_sha256": "2a62ab05687982c112a29f6126227c26c3ff8defc89ca64662753aaf85c3c0ce",
47: 	"coding_package_file_sha256": (
48: 		"14644759f53071adfe1173d4e25bb01931b8c1591fa8a48295dbd0556a754769"
49: 	),
50: 	"protocol_sha256": "06d4b3407677fe7fb23046f70164a366db06638e8a29af3daf7661f00d565933",
51: 	"coding_schema_sha256": "bfc735a7d8602d1f236a88c2d23b195f0600a8bae86fb8c8936afe7883a41b8e",
52: 	"adjudication_schema_sha256": (
53: 		"6181f5cb60a0462f83be00be301763daa89f11581e68ee1c46a84e8bf4e5be31"
54: 	),
55: }
56: CLAIM_FIELDS = {
57: 	"record_class",
58: 	"event_kind",
59: 	"title",
60: 	"announced_on",
61: 	"effective_on",
62: 	"affected_scope",
63: 	"body_patch_relation",
64: }
65: MAX_INPUT_BYTES = 16 * 1024 * 1024
66: MAX_ARTIFACT_BYTES = 32 * ...
gateway · 2026-09-09 04:36:43
result
<path>/home/byk/Code/institutional-transition-lab/src/institution_lab/governance_adjudication.py</path>
<type>file</type>
<content>
1: from __future__ import annotations
2: 
3: import argparse
4: import hashlib
5: import io
6: import json
7: import os
8: import stat
9: import tempfile
10: import zipfile
11: from collections import Counter
12: from contextlib import suppress
13: from dataclasses import dataclass
14: from pathlib import Path
15: from typing import Any
16: 
17: from jsonschema import Draft202012Validator, FormatChecker
18: 
19: from institution_lab.governance_coding import validate_response
20: 
21: DECISIONS = {"accept", "revise", "reject", "abstain"}
22: INSTITUTIONAL_CLASSES = {
23: 	"effective_institutional_change",
24: 	"announced_institutional_change",
25: 	"proposal_only",
26: }
27: REVIEWER_TYPES = {"human", "llm", "human_llm_assisted"}

(Showing lines 1-27 of 946. Use offset=28 to continue.)
</content>
gateway · 2026-09-09 04:36:49
result
<path>/home/byk/Code/institutional-transition-lab/tests/test_governance_adjudication.py</path>
<type>file</type>
<content>
910: 	monkeypatch: pytest.MonkeyPatch,
911: ) -> None:
912: 	manifest, paths = _write_pinned_cli_inputs(tmp_path, monkeypatch)
913: 	with zipfile.ZipFile(paths["artifact"], "a") as archive:
914: 		for index in range(MAX_TEST_ARCHIVE_MEMBERS):
915: 			archive.writestr(f"padding/{index}", b"")
916: 	manifest["artifact_sha256"] = file_sha256(paths["artifact"])
917: 	monkeypatch.setattr(governance_adjudication, "FROZEN_INPUT_PINS", dict(manifest))
918: 
919: 	with pytest.raises(ValueError, match="artifact member count exceeds limit"):
920: 		verify_input_manifest(
921: 			manifest,
922: 			artifact=paths["artifact"],
923: 			documents=paths["documents"],
924: 			coding_package=paths["coding_package"],
925: 			protocol=paths["protocol"],
926: 			coding_schema=paths["coding_schema"],
927: 			adjudication_schema=paths["adjudication_schema"],
928: 		)
929: 
930: 
931: def ...
gateway · 2026-09-09 04:39:31
result
<path>/home/byk/Code/institutional-transition-lab</path>
<type>directory</type>
<entries>
.git/
.github/
.gitignore
.lore.md
.pytest_cache/
.ruff_cache/
.venv/
AGENTS.md
cases/
data/
docs/
node_modules/
package-lock.json
package.json
PROTOCOL.md
pyproject.toml
README.md
research/
schema/
scripts/
src/
tests/
uv.lock

(23 entries)
</entries>
gateway · 2026-09-09 04:40:47
result
Found 1 matches
/home/byk/Code/institutional-transition-lab/tests/test_governance_adjudication.py:
  Line 951: 	assert "input path is not a regular file" in result.stderr
gateway · 2026-09-09 04:41:06

I’ll inspect only the reader, its limits, and its direct test, then run cache-disabled probes for file types, races, boundaries, and descriptor cleanup. I will not open any governance artifacts or derived research data.

gpt-5.6-sol · 2026-09-09 04:41:06 · ~44 tokens
result
.                                                                        [100%]
1 passed in 0.38s
gateway · 2026-09-09 04:44:14
result
PASS reject final symlink: cannot open input as a regular file: /tmp/regular-reader-audit-4w4av91l/final-symlink
Traceback (most recent call last):
  File "<stdin>", line 29, in <module>
  File "<stdin>", line 14, in reject
  File "/home/byk/Code/institutional-transition-lab/src/institution_lab/governance_adjudication.py", line 100, in _read_regular_file
    with os.fdopen(descriptor, "rb") as source:
         ~~~~~~~~~^^^^^^^^^^^^^^^^^^
  File "<frozen os>", line 1069, in fdopen
IsADirectoryError: [Errno 21] Is a directory: 4
gateway · 2026-09-09 04:46:14

The first probe found a real fail-closed gap before reaching the remaining cases: opening a directory reaches os.fdopen first, which raises IsADirectoryError and leaves the raw descriptor unowned. I’m rerunning the disjoint cases with fault capture so one rejection cannot stop the audit.

gpt-5.6-sol · 2026-09-09 04:46:14 · ~61 tokens
result
PASS reject final symlink with ValueError: cannot open input as a regular file: /tmp/regular-reader-audit-owz72hfq/final-symlink
PASS reject missing path with ValueError: cannot open input as a regular file: /tmp/regular-reader-audit-owz72hfq/missing
PASS reject FIFO with ValueError: input path is not a regular file: /tmp/regular-reader-audit-owz72hfq/fifo
PASS reject Unix socket with ValueError: cannot open input as a regular file: /tmp/regular-reader-audit-owz72hfq/socket
PASS reject character device with ValueError: input path is not a regular file: /dev/null
PASS reject limit + 1 regular file with ValueError: input file exceeds 7 bytes: /tmp/regular-reader-audit-owz72hfq/boundary
PASS exact byte boundary: 7 accepted, 8 rejected
PASS reject file grown after fstat with ValueError: input file exceeds 7 bytes: /tmp/regular-reader-audit-owz72hfq/grow
PASS post-fstat growth is caught by the max+1 read
PASS path replacement after open reads the already-opened inode
OBSERVED ancestor ...
gateway · 2026-09-09 04:47:42
result
OBSERVED final symlink is followed when O_NOFOLLOW is unavailable
gateway · 2026-09-09 04:48:42
result
PASS successful read: descriptor closed
PASS oversize rejection: descriptor closed
PASS read error: descriptor closed
gateway · 2026-09-09 04:48:52
result
?? src/institution_lab/governance_adjudication.py
?? tests/test_governance_adjudication.py
gateway · 2026-09-09 04:52:25

Findings

  1. MUST-FIX — directory and fdopen failures leak descriptors

    • os.open() creates the descriptor at src/institution_lab/governance_adjudication.py:97.
    • Ownership transfers only when os.fdopen() succeeds at line 100. If it raises, no finally closes the raw descriptor.
    • A real directory raised IsADirectoryError at line 100 before the intended type check at lines 101–103. The descriptor remained open.
    • An injected fdopen(...)=ENOMEM failure produced the same leak.
    • Repeated hostile inputs can exhaust descriptors.
    • Required regressions:
      • Open a directory, assert controlled ValueError, then assert the captured descriptor returns EBADF.
      • Make os.fdopen raise, assert the raw descriptor is closed.
      • Ensure failing tests close leaked descriptors during cleanup.
  2. MUST-FIX — symlink rejection remains fail-open

    • O_NOFOLLOW at line 95 protects only the final path component.
    • A file beneath a symlinked parent directory was followed and accepted.
    • getattr(os, "O_NOFOLLOW", 0) at line 95 silently disables protection when the flag is unavailable. Removing the attribute in a probe caused a final symlink to be followed and read.
    • Required regressions:
      • A symlinked parent directory must be rejected.
      • When O_NOFOLLOW is unavailable, the helper must fail before opening the path.
      • Preserve the existing final-component symlink rejection test behavior.
  3. CONCERN — arbitrary device opening is not guaranteed nonblocking

    • Line 97 opens the object for reading before fstat() classifies it at lines 101–103.
    • O_NONBLOCK at line 95 prevented the tested FIFO from blocking, but device drivers do not universally guarantee nonblocking or side-effect-free open().
    • /dev/null and synthetic block-device metadata were rejected. I did not open a live block device. ...
gpt-5.6-sol · 2026-09-09 04:52:25 · ~793 tokens

Distillations (1)

Gen 0 2026-09-09 04:53:34 · 1729 tokens

Date: Sep 9, 2026 * 🔴 [enforced-read-only] (04:36) User directed that files must never be edited and governance bodies must never be inspected. * 🔴 (04:36) User prohibited inspection of coding decisions, outcomes, reports, dates, detector output, and source-adjudication scopes. * 🟡 [requested-security-audit] (04:36) User requested a fresh, read-only security audit for issue #4 in `/home/byk/Co…