Skip to content

No address-taking metamorphic or opcode scan exists, so no verdict is bound to an account #83

Description

@thedavidmeister

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/*'.

Metadata

Metadata

Labels

adversarialAdversarial mutation-test findingauditAudit finding

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions