Skip to content

The published package is never compiled as published, and ships a file whose import it excludes #76

Closed

Description

@thedavidmeister

Audit scope: whole-repo, commit 7aa85a4

Dimension 6 (hazard surface) · severity medium

Where

  • .soldeerignore:1-20
  • script/Build.sol:8
  • .github/workflows/package-release.yaml:1-11

Problem

Three reviewers found this independently, from three different artifacts:
the published 0.1.11 zip (twice) and a vendored 0.1.6 at
/home/gildlab/code/rain-deploy-15a/dependencies/rain-sol-codegen-0.1.6.

.soldeerignore is a second, hand-maintained definition of "what this library
is", disjoint from the source graph forge build compiles. Nothing checks the
two agree, so the published artifact can be broken or incomplete with a green
main.

.soldeerignore excludes /test but not /script or src/generated.
script/Build.sol:8 imports {CodeGennable} from "../test/concrete/CodeGennable.sol". The published zip contains
script/Build.sol and src/generated/CodeGennable.sol, and no test/. The
shipped script/Build.sol therefore cannot compile in any consumer. No CI step
ever builds the filtered tree, so this has been true across releases with no red.

Nothing breaks today because foundry only compiles dependency files something
imports — but the repo's only worked example of the library, the thing a
consumer copies, cannot be compiled from the package.

The latent import is the symptom; the shape is what lands next. Add
src/lib/LibNewThing.sol imported by LibCodeGen.sol, and add any
.soldeerignore pattern that happens to match it (the file already mixes
anchored /audit with unanchored .github, so pattern edits are easy to get
wrong) — the package publishes without it, every consumer's forge build breaks
on the next soldeer install, and this repo's own CI stays green because the
local tree is complete.

Proposed fix

Two halves:

  1. Compile the package as published, in CI, on every push — a job that unpacks
    what .soldeerignore would ship into a scratch project and builds it, failing
    on any unresolved import.
  2. Remove the inconsistency now: either move CodeGennable to
    src/concrete/CodeGennable.sol (its generated counterpart
    src/generated/CodeGennable.sol already ships) and update the imports in
    script/Build.sol, test/lib/LibFs.buildFileForContract.t.sol:8 and
    test/lib/LibCodeGen.bytecodeHashConstantString.t.sol:8 (both test/src/lib/
    after Move every .t.sol into the test/src/lib mirror tree #56) — or add
    /script to .soldeerignore, which drops the example from the package
    altogether.

Activity

  1. thedavidmeister commented on Aug 17, 2026

    @thedavidmeister
    ContributorAuthor

    Closing: one clause is void, the other is not this repo's to land.

    "ships a file whose import it excludes" — void. #138 deleted
    script/Build.sol and src/generated/CodeGennable.sol under the ruling that the
    worked example leaves this repo. Verified against the real artifact rather than
    the repo tree: rain-sol-codegen~0.1.34 installed from the Soldeer registry
    contains exactly eleven files —

    LICENSE  LICENSES/LicenseRef-DCL-1.0.txt  README.md  REUSE.toml
    src/interface/{IIntegrity,IOpcode,IParser,ISubParser}ToolingV1.sol
    src/lib/{LibCodeGen,LibFs,LibHexString}.sol
    

    no script/, no src/generated/, no test/. Every import under src/ resolves
    inside src/ or to forge-std-1.16.2; nothing shipped points into test/.
    Copied to a scratch consumer with the three build-config files .soldeerignore
    excludes, forge soldeer install && forge build gives
    Compiling 8 files with Solc 0.8.25 / Compiler run successful!, exit 0.

    PR #129, the repo-side attempt at this clause, was closed unmerged: its own diff
    was empty against post-#138 main, and landing it would have pushed a test-only
    fixture into the published package.

    "never compiled as published" — upstream, and needs no wiring here.
    rainlanguage/rainix#316 adds
    uses: rainlanguage/rainix/.github/actions/soldeer-package-build@main inside the
    existing static job of rainix-sol-static.yaml, with rainix-sha as its only
    input — no consumer inputs, no opt-in. This repo's .github/workflows/ is two
    files with zero steps of their own, both calling rainix reusables, so the gate
    starts running on every push here the moment #316 merges. A local job would be
    this repo's first inline CI and would fork shared CI into a consumer.

    Same rule as #89 and #82: the thing that owns a fact is where it gets fixed.
    Shared CI lives in rainix's reusables, not re-implemented per consumer.

    One gap neither covers, noted rather than filed. #316 catches a
    .soldeerignore exclusion that something shipped imports. Nothing anywhere
    makes a positive statement of what the library's public surface is, so an
    accidental omission — a file that should ship and does not — stays invisible.
    That is a third concern, not in this issue's scope, and is worth its own issue if
    it matters.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

auditAudit findingmediumAudit findingpass6Audit finding

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions