Unit
src/interface/IExtrospectV1.sol + src/lib/LibExtrospectMetamorphic.sol + src/lib/LibExtrospectBytecode.sol scanners.
Intent oracle
IExtrospectV1 mixes both shapes: checkCBORTrimmedBytecodeHash(address, bytes32) and checkNoSolidityCBORMetadata(address) read account.code themselves; checkNotMetamorphic(bytes), scanMetamorphicRisk(bytes), scanEVMOpcodesPresentInBytecode(bytes) and scanEVMOpcodesReachableInBytecode(bytes) do not. LibExtrospectMetamorphic's title claims "Detection and guarding against metamorphic contract risk", and README.md still documents the removed scanEVMOpcodesReachableInAccount / scanEVMOpcodesPresentInAccount. The Protofire audit refers to scanEVMOpcodesReachableInAccount throughout.
Violated property
There is no API by which a caller obtains a metamorphic or opcode verdict bound to an address. Every caller must fetch .code themselves, so the step tying "these bytes are clean" to "this address is safe" sits outside the library, unaudited and untested. Extrospect.checkNotMetamorphic(bytes) proves nothing about any deployed account — the caller supplies the bytes — and passing a truncated or unrelated byte string yields a clean verdict with no error.
Combined with the empty-bytes issue, Extrospect.checkNotMetamorphic(hex"") is a passing external call that asserts nothing about anything.
Verified repro
Not executed as a defect — structural. Read src/interface/IExtrospectV1.sol against src/lib/LibExtrospectMetamorphic.sol: two address-taking checks, four bytes-taking checks, no address-taking metamorphic or opcode entry point. Confirmed at 28017b6.
Triage
Severity: low-medium (design / hazard surface, not a wrong answer). The asymmetry is easy to misuse and the docs actively describe the missing half as if it shipped, which is what turns it from a scope choice into a hazard. Fix is additive. See the separate stale-docs issue for the README/audit references to the removed account-taking functions.
Provenance
Adversarial mutation-test campaign on rainlanguage/rain.extrospection @ 28017b6, triage pass.
Repros in this issue were re-executed by the triage agent against a fresh clone at 28017b6
(nix develop -c forge test, baseline 304 passed / 0 failed) unless the issue says otherwise.
Triage repro sources: /home/gildlab/artifacts/2026-08-18-extrospection-amt/triage/repro-tests/
(TriageBytecode.t.sol, TriageProxy.t.sol, TriageEtch.t.sol) — drop into test/triage/ of a
clone at 28017b6 and run nix develop -c forge test --match-path 'test/triage/*'.
Unit
src/interface/IExtrospectV1.sol+src/lib/LibExtrospectMetamorphic.sol+src/lib/LibExtrospectBytecode.solscanners.Intent oracle
IExtrospectV1mixes both shapes:checkCBORTrimmedBytecodeHash(address, bytes32)andcheckNoSolidityCBORMetadata(address)readaccount.codethemselves;checkNotMetamorphic(bytes),scanMetamorphicRisk(bytes),scanEVMOpcodesPresentInBytecode(bytes)andscanEVMOpcodesReachableInBytecode(bytes)do not.LibExtrospectMetamorphic's title claims "Detection and guarding against metamorphic contract risk", andREADME.mdstill documents the removedscanEVMOpcodesReachableInAccount/scanEVMOpcodesPresentInAccount. The Protofire audit refers toscanEVMOpcodesReachableInAccountthroughout.Violated property
There is no API by which a caller obtains a metamorphic or opcode verdict bound to an address. Every caller must fetch
.codethemselves, so the step tying "these bytes are clean" to "this address is safe" sits outside the library, unaudited and untested.Extrospect.checkNotMetamorphic(bytes)proves nothing about any deployed account — the caller supplies the bytes — and passing a truncated or unrelated byte string yields a clean verdict with no error.Combined with the empty-bytes issue,
Extrospect.checkNotMetamorphic(hex"")is a passing external call that asserts nothing about anything.Verified repro
Not executed as a defect — structural. Read
src/interface/IExtrospectV1.solagainstsrc/lib/LibExtrospectMetamorphic.sol: two address-taking checks, four bytes-taking checks, no address-taking metamorphic or opcode entry point. Confirmed at28017b6.Triage
Severity: low-medium (design / hazard surface, not a wrong answer). The asymmetry is easy to misuse and the docs actively describe the missing half as if it shipped, which is what turns it from a scope choice into a hazard. Fix is additive. See the separate stale-docs issue for the README/audit references to the removed account-taking functions.
Provenance
Adversarial mutation-test campaign on
rainlanguage/rain.extrospection@28017b6, triage pass.Repros in this issue were re-executed by the triage agent against a fresh clone at
28017b6(
nix develop -c forge test, baseline 304 passed / 0 failed) unless the issue says otherwise.Triage repro sources:
/home/gildlab/artifacts/2026-08-18-extrospection-amt/triage/repro-tests/(
TriageBytecode.t.sol,TriageProxy.t.sol,TriageEtch.t.sol) — drop intotest/triage/of aclone at
28017b6and runnix develop -c forge test --match-path 'test/triage/*'.