ADR-0071: Independent worker ELF dependency inventory

Status: accepted
Date: 2026-08-03
Decision owners: NAUX architecture and x86-64 artifact boundary

Context

ADR-0070 proves that the exact caller-reviewed worker bytes reach execveat(AT_EMPTY_PATH) through an immutable descriptor. A dynamically linked ELF can still name an interpreter and shared libraries that the kernel/loader resolve outside that sealed artifact. Those undeclared runtime identities are dependency debt, not sovereign closure.

Decision drivers

  • Decode the exact sealed ADR-0070 bytes without readelf, libelf, the host loader, LLVM, or the historical NAUX standalone parser.
  • Fail closed on unsupported ELF class, endianness, machine, file type, program-header shape, segment type, bounds, overlap, W+X load, executable stack, malformed dynamic table, search-path tag, or string ambiguity.
  • Inventory every program header and dynamic entry, plus the exact PT_INTERP, DT_NEEDED, DT_FLAGS, and DT_FLAGS_1 facts.
  • Bind the inventory to the reviewed artifact expectation and ADR-0070 policy root, and require the sealed artifact again for independent verification.
  • Keep dependency resolution/admission, execution, sandbox, standalone, timing, claim, and global encoder-policy authority closed.

Decision

Introduce a proof-only Linux x86-64 ELF64 decoder. It accepts only PIE ET_DYN, little-endian ELF64 for EM_X86_64, exact canonical header widths, bounded program and section-header tables, a supported segment allowlist, non-overlapping ordered PT_LOAD spans, one non-executable stack, one interpreter, one dynamic segment, and one RELRO segment.

The decoder maps DT_STRTAB through exactly one file-backed load segment, parses a bounded NUL-terminated string table, rejects embedded search paths, and records every ordered dynamic entry and every unique DT_NEEDED name. Evidence is artifact-specific. Verification re-reads the opaque sealed ADR-0070 artifact, recomputes the entire inventory, and compares it exactly; an evidence seal alone cannot replace source replay.

Frozen limits

  • Linux x86-64 ELF64 little-endian PIE only;
  • at most 64 program headers and 4,096 section headers;
  • at most 16 load segments and 4,096 dynamic entries;
  • at most 64 dependencies;
  • at most 1 MiB dynamic string table and 256 bytes per interpreter/dependency name;
  • no W+X load, executable stack, DT_RPATH, DT_RUNPATH, audit, filter, or auxiliary loader policy;
  • proof-only inventory; global target encoder policy remains 1.4.0.

Consequences

  • NAUX can state exactly which external loader names the reviewed worker asks the host to resolve instead of treating “the binary hash” as dependency closure.
  • A clean inventory still does not attest the interpreter or libraries found at launch time. Those objects and the loader’s resolution semantics remain future authority work.
  • Static/freestanding artifacts may require a separate profile rather than weakening this deliberately dynamic-worker contract.
  • Dependency inventory and structural hardening facts are not performance evidence.

Acceptance gates

  1. The exact reviewed ADR-0070 worker decodes and independently replays with a nonempty absolute interpreter and bounded unique dependencies.
  2. ELF identity, table width/count/range, segment type/flag/alignment/overlap, W+X, executable-stack, interpreter, dynamic table/string map/name, search path, and cap mutations fail closed.
  3. Receipt/evidence and locally resealed inventory mutations fail when replayed against the unchanged sealed artifact.
  4. Production imports no native emitter, historical raw/process/standalone/ measurement module, external ELF parser, or process launcher.
  5. Focused, workspace, strict lint, governance, refinement, format, shell, and static authority gates pass before acceptance.

Accepted evidence

The independent inventory policy is accepted under root 1a6c96c8b47a2001d9969488785c6bb2b846c378280b0498f2c16b0f14fd3bbf. The root binds schema/policy 1.0.0, all ELF/table/string/dependency limits, and the accepted ADR-0070 sealed-artifact policy root.

For the reviewed acceptance worker, independent replay reconstructs 12 program headers, four ordered load segments, 29 dynamic entries, the absolute interpreter /lib64/ld-linux-x86-64.so.2, and three ordered direct dependency names: libgcc_s.so.1, libc.so.6, and ld-linux-x86-64.so.2. DT_FLAGS is 0x8 (BIND_NOW) and DT_FLAGS_1 is 0x08000001 (NOW | PIE). The decoder rejects W+X loads, executable stacks, embedded loader search policy, malformed or ambiguous strings/mappings, noncanonical DT_NULL, truncation, and locally resealed evidence mutation.

Focused positive and structural-mutation tests pass 2/2. The workspace unit suite passes 408 tests with zero failures and six explicit release-only ignores; the native/process/standalone parity tail, strict Clippy, format, perf-gates, and independent ADR-0069/0070 boundaries pass. The artifact and inventory evidence hashes remain deployment-specific; only the policy root is global.

This accepts declaration inventory, not dependency admission or closure. The interpreter and shared objects selected by the host loader are still unresolved external identities.