Dashboardinstitutional-transition-labSession 0XzZzxSQLaAG

Session 0XzZzxSQLaAG

Full ID: 0XzZzxSQLaAGJA7bv
Messages: 24
Distillations: 1
Time range: 2026-09-09 05:38:13 — 2026-09-09 05:58:34

Conversation (24 messages)

Perform a fresh, minimal, read-only security audit of only actual decompressed-byte enforcement for issue #4 in /home/byk/Code/institutional-transition-lab. This replaces a resource-accounting reviewer that returned empty twice and is disjoint from declared-size arithmetic. Never edit files or inspect governance records/codings/outcomes/reports/dates/detectors/source adjudication.

Scope only: member stream read amount and loop behavior; actual per-member and aggregate decompressed-byte accounting; short/partial reads; streams producing more or fewer bytes than declared; compression-bomb termination; cleanup/error behavior. Do not inspect central declared-size arithmetic, CRC/required-member identity/content equality, archive names/path/mode/encryption/count, regular-file reads, JSON/pins/hashes, or CLI outputs.

Review only relevant ZIP stream code/constants in src/institution_lab/governance_adjudication.py and narrow tests. Run small read-only probes. Return substantive evidence, never empty. Findings first with exact file:line references and PASS/CONCERN/MUST-FIX/BLOCKED labels; deterministic regression for every defect. End exactly MERGE or DO-NOT-MERGE.

gateway · 2026-09-09 05:38:13

I’ll trace only ZIP member streaming and actual decompressed-byte counters, then run focused read-only tests and synthetic stream probes. I will not inspect the excluded metadata, governance, provenance, or CLI paths.

gpt-5.6-sol · 2026-09-09 05:38:13 · ~42 tokens
result
Found 15 matches
/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 106: 		payload = source.read(max_bytes + 1)

  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 194: 			for member_name, durable_bytes in members.items():

  Line 199: 				if info.file_size > MAX_ARCHIVE_MEMBER_BYTES:

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

  Line 203: 				if len(member_bytes) > MAX_ARCHIVE_MEMBER_BYTES:

  Line 205: 				if member_bytes != durable_bytes:

  Line 233: ...
gateway · 2026-09-09 05:38:26
result
/home/byk/Code/institutional-transition-lab/tests/test_governance_adjudication.py
gateway · 2026-09-09 05:38:40
result
Found 1 matches
/home/byk/Code/institutional-transition-lab/tests/test_governance_adjudication.py:
  Line 979: def test_committed_partial_fragment_validates_but_cannot_be_the_full_envelope() -> None:
gateway · 2026-09-09 05:38:57
result
Found 19 matches
/home/byk/Code/institutional-transition-lab/tests/test_governance_adjudication.py:
  Line 8: import zipfile

  Line 52: 		"artifact": tmp_path / "artifact.zip",

  Line 65: 	with zipfile.ZipFile(paths["artifact"], "w") as archive:

  Line 66: 		archive.writestr("documents.json", paths["documents"].read_bytes())

  Line 67: 		archive.writestr(

  Line 687: 	artifact = tmp_path / "artifact.zip"

  Line 703: 	with zipfile.ZipFile(artifact, "w") as archive:

  Line 704: 		archive.writestr("documents.json", documents.read_bytes())

  Line 705: 		archive.writestr("v1.2-adjudication-package.json", coding_package.read_bytes())

  Line 733: 	artifact = tmp_path / "artifact.zip"

  Line 743: 	with zipfile.ZipFile(artifact, "w") as archive:

  Line 744: 		archive.writestr("documents.json", b"different documents")

  Line 745: 		archive.writestr("v1.2-adjudication-package.json", coding_package.read_bytes())

  Line 890: 		zipfile.ZipFile(paths["artifact"], "a") as archive,

  ...
gateway · 2026-09-09 05:39:20
result
<path>/home/byk/Code/institutional-transition-lab/src/institution_lab/governance_adjudication.py</path>
<type>file</type>
<content>
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 * 1024 * 1024
67: MAX_ARCHIVE_MEMBERS = 128
68: MAX_ARCHIVE_MEMBER_BYTES = 16 * 1024 * 1024
69: MAX_ARCHIVE_EXPANDED_BYTES = 64 * 1024 * 1024
70: 
71: 
72: @dataclass(frozen=True)
73: class VerifiedInputBundle:
74: 	pins: dict[str, Any]

(Showing lines 55-74 of 946. Use offset=75 to continue.)
</content>
gateway · 2026-09-09 05:39:24
result
<path>/home/byk/Code/institutional-transition-lab/src/institution_lab/governance_adjudication.py</path>
<type>file</type>
<content>
176: 	for field, value in computed.items():
177: 		if manifest.get(field) != value:
178: 			raise ValueError(f"input manifest mismatch: {field}")
179: 	try:
180: 		with zipfile.ZipFile(io.BytesIO(artifact)) as archive:
181: 			infos = archive.infolist()
182: 			if len(infos) > MAX_ARCHIVE_MEMBERS:
183: 				raise ValueError("artifact member count exceeds limit")
184: 			member_counts = Counter(info.filename for info in infos)
185: 			for member_name, count in member_counts.items():
186: 				if count != 1:
187: 					raise ValueError(f"artifact member is not unique: {member_name}")
188: 			if sum(info.file_size for info in infos) > MAX_ARCHIVE_EXPANDED_BYTES:
189: 				raise ValueError("artifact expanded size exceeds limit")
190: 			members = {
191: 				"documents.json": documents,
192: 				"v1.2-adjudication-package.json": coding_package,
193: 			}
194: ...
gateway · 2026-09-09 05:39:51
result
No files found
gateway · 2026-09-09 05:40:13
result
Found 3 matches
/home/byk/Code/institutional-transition-lab/tests/test_governance_adjudication.py:
  Line 729: def test_input_manifest_rejects_files_not_from_pinned_artifact(

  Line 883: def test_input_manifest_rejects_duplicate_artifact_members(

  Line 908: def test_input_manifest_rejects_excessive_artifact_member_count(
gateway · 2026-09-09 05:40:32
result
Found 9 matches
/home/byk/Code/institutional-transition-lab/src/institution_lab/governance_adjudication.py:
  Line 68: MAX_ARCHIVE_MEMBER_BYTES = 16 * 1024 * 1024

  Line 69: MAX_ARCHIVE_EXPANDED_BYTES = 64 * 1024 * 1024

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

  Line 189: 				raise ValueError("artifact expanded size exceeds limit")

  Line 199: 				if info.file_size > MAX_ARCHIVE_MEMBER_BYTES:

  Line 200: 					raise ValueError(f"artifact member exceeds size limit: {member_name}")

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

  Line 203: 				if len(member_bytes) > MAX_ARCHIVE_MEMBER_BYTES:

  Line 204: 					raise ValueError(f"artifact member exceeds size limit: {member_name}")
gateway · 2026-09-09 05:40:53
result
Found 3 matches
/home/byk/Code/institutional-transition-lab/src/institution_lab/governance_adjudication.py:
  Line 157: def _verify_input_payloads(

  Line 220: def verify_input_manifest(

  Line 835: def _validate_output_paths(*, inputs: list[Path], outputs: list[Path]) -> None:
gateway · 2026-09-09 05:41:16
result
<path>/home/byk/Code/institutional-transition-lab/src/institution_lab/governance_adjudication.py</path>
<type>file</type>
<content>
157: def _verify_input_payloads(
158: 	manifest: Any,
159: 	*,
160: 	artifact: bytes,
161: 	documents: bytes,
162: 	coding_package: bytes,
163: 	protocol: bytes,
164: 	coding_schema: bytes,

(Showing lines 157-164 of 946. Use offset=165 to continue.)
</content>
gateway · 2026-09-09 05:42:34
result
Python 3.12.3
    def read(self, n=-1):
        """Read and return up to n bytes.
        If the argument is omitted, None, or negative, data is read and returned until EOF is reached.
        """
        if self.closed:
            raise ValueError("read from closed file.")
        if n is None or n < 0:
            buf = self._readbuffer[self._offset:]
            self._readbuffer = b''
            self._offset = 0
            while not self._eof:
                buf += self._read1(self.MAX_N)
            return buf

        end = n + self._offset
        if end < len(self._readbuffer):
            buf = self._readbuffer[self._offset:end]
            self._offset = end
            return buf

        n = end - len(self._readbuffer)
        buf = self._readbuffer[self._offset:]
        self._readbuffer = b''
        self._offset = 0
        while n > 0 and not self._eof:
            data = self._read1(n)
            if n < len(data):
                self._readbuffer = data
           ...
gateway · 2026-09-09 05:42:50
result
No files found
gateway · 2026-09-09 05:44:02
result
Found 6 matches
/home/byk/Code/institutional-transition-lab/tests/test_governance_adjudication.py:
  Line 652: def test_input_manifest_rejects_self_consistent_substitute(field: str) -> None:

  Line 683: def test_input_manifest_rejects_changed_file_bytes(

  Line 729: def test_input_manifest_rejects_files_not_from_pinned_artifact(

  Line 870: def test_input_manifest_rejects_non_object() -> None:

  Line 883: def test_input_manifest_rejects_duplicate_artifact_members(

  Line 908: def test_input_manifest_rejects_excessive_artifact_member_count(
gateway · 2026-09-09 05:44:35
result
    def _read2(self, n):
        if self._compress_left <= 0:
            return b''

        n = max(n, self.MIN_READ_SIZE)
        n = min(n, self._compress_left)

        data = self._fileobj.read(n)
        self._compress_left -= len(data)
        if not data:
            raise EOFError

        if self._decrypter is not None:
            data = self._decrypter(data)
        return data

gateway · 2026-09-09 05:49:27
result
class LZMADecompressor:

    def __init__(self):
        self._decomp = None
        self._unconsumed = b''
        self.eof = False

    def decompress(self, data):
        if self._decomp is None:
            self._unconsumed += data
            if len(self._unconsumed) <= 4:
                return b''
            psize, = struct.unpack('<H', self._unconsumed[2:4])
            if len(self._unconsumed) <= 4 + psize:
                return b''

            self._decomp = lzma.LZMADecompressor(lzma.FORMAT_RAW, filters=[
                lzma._decode_filter_properties(lzma.FILTER_LZMA1,
                                               self._unconsumed[4:4 + psize])
            ])
            data = self._unconsumed[4 + psize:]
            del self._unconsumed

        result = self._decomp.decompress(data)
        self.eof = self._decomp.eof
        return result

gateway · 2026-09-09 05:51:33
result
Traceback (most recent call last):
  File "<stdin>", line 7, in <module>
ModuleNotFoundError: No module named 'institution_lab'
gateway · 2026-09-09 05:52:30
result
more-than-declared encoded_expansion=16777217 declared=1 returned=1 next_read=0 peak_traced=47587880
fewer-than-declared encoded_expansion=1 declared=8 returned=1 next_read=0
partial-compressed-reads returned=32768 underlying_partial_reads=46
gateway · 2026-09-09 05:53:15
result
read-error OSError source_closed=True archive_closed=True
gateway · 2026-09-09 05:53:42
result
No files found
gateway · 2026-09-09 05:55:09
result
No files found
gateway · 2026-09-09 05:58:34

Findings

MUST-FIX — returned length does not bound actual decompression

MAX_ARCHIVE_MEMBER_BYTES and MAX_ARCHIVE_EXPANDED_BYTES are defined at src/institution_lab/governance_adjudication.py:68-69, but the stream loop only:

  • calls source.read(MAX_ARCHIVE_MEMBER_BYTES + 1) at :201-202;
  • measures the returned buffer at :203-204;
  • keeps no aggregate counter across iterations at :194 and :201-204.

CPython 3.12.3’s ZipExtFile clips returned data to the declared size. Its BZIP2 and LZMA paths can decompress the entire supplied compressed chunk before clipping. Therefore len(member_bytes) does not measure actual decoder output.

An in-memory BZIP2 probe produced:

encoded_expansion=16777217 declared=1 returned=1 next_read=0 peak_traced=47587880

The true expansion exceeded the 16 MiB member limit, but line 203 observed one byte. The same behavior leaves aggregate decompression unbounded and permits compression-bomb CPU/memory exhaustion before either check runs.

A second probe showed that a stream producing fewer bytes than declared also reaches EOF without a stream error:

encoded_expansion=1 declared=8 returned=1 next_read=0

Deterministic regressions:

  1. Lower limits with monkeypatch; construct BZIP2 and LZMA streams whose expansion is member_limit + 1 but whose declared length matches a short valid prefix. Require rejection based on actual expansion.
  2. Construct two streams individually below the member limit but collectively above a lowered aggregate limit. Require rejection while reading the second stream.
  3. Construct a stream shorter than its declared length. Require an explicit stream-length error.
  4. Keep fixtures small and compressible so these tests fail safely on the current implementation without consuming substantial memory.

PASS — partial compressed reads are completed

For the concrete ZipExtFile returned at src/institution_lab/governance_adjudication.py:201, the one-shot call at ...

gpt-5.6-sol · 2026-09-09 05:58:34 · ~630 tokens

Distillations (1)

Gen 0 2026-09-09 06:00:11 · 1988 tokens

Date: September 9, 2026 * 🔴 (05:38) User stated issue #4 in `/home/byk/Code/institutional-transition-lab` concerns actual decompressed-byte enforcement, is disjoint from declared-size arithmetic, and replaces a resource-accounting review that returned empty twice. * 🔴 [requested-security-audit] (05:38) User requested a fresh, minimal, substantive, read-only security audit limited to actual ZIP …