diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 312b4e241..ec83e605c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,6 +28,16 @@ env: MSPEC_REF: b79c65ed28b00f0869ad82b0e3dceef0d943cd58 jobs: + # The repository holds no programs - no .exe, .dll, .so or other binary that runs - so all + # of it can be read; Util/check-no-executables.rb says why and how it tells them apart. + no-executables: + name: no programs in the repository + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - name: Check every tracked file + run: ruby Util/check-no-executables.rb + build-and-test: name: ${{ matrix.os }} ${{ matrix.tfm }} runs-on: ${{ matrix.os }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f4e9d44e4..207220ee6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,6 +5,8 @@ name: Release # pre-release while the version has a suffix such as -preview1), with # Docs/releases/.md as the notes when there is one; running the workflow by hand, or # a pull request that changes the packaging, only keeps them as workflow artifacts. +# Each published file gets a signed build provenance attestation (actions/attest) in the +# job that built it; `gh attestation verify --repo Largo/ironruby` checks it. # The launch video, rendered from media/launch, is only rendered when asked for by hand - # it takes about 7 minutes of every run otherwise: with render_video it is kept as an # artifact, and with video_for set to a tag it alone is rendered and replaces the video, @@ -50,6 +52,12 @@ jobs: runs-on: ${{ matrix.os }} # macOS is untested (README, Platforms): a failure there does not hold the release back. continue-on-error: ${{ matrix.experimental }} + # id-token, attestations and artifact-metadata: for the build provenance below. + permissions: + contents: read + id-token: write + attestations: write + artifact-metadata: write strategy: fail-fast: false matrix: @@ -151,6 +159,17 @@ jobs: working-directory: dist run: cd ironruby-*-win-x64 && cmd //c ir.cmd Samples/Notepad/test_notepad.rb + # Signed SLSA build provenance for the archive a tag publishes, with GitHub's own + # actions/attest, pinned to the commit of its v4.2.2 tag: `gh attestation verify + # --repo Largo/ironruby` shows that this workflow built it, from which + # commit and in which run. dist/ironruby--.* is the archive alone, not + # the directory it was packed from. + - name: Attest build provenance + if: github.event_name == 'push' && github.ref_type == 'tag' + uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2 + with: + subject-path: dist/ironruby-*-${{ matrix.rid }}.* + - uses: actions/upload-artifact@v7 with: name: ironruby-${{ matrix.rid }} @@ -168,6 +187,11 @@ jobs: continue-on-error: true # A render takes about 7 minutes; a failed one can leave hyperframes waiting. timeout-minutes: 20 + permissions: + contents: read + id-token: write + attestations: write + artifact-metadata: write steps: - uses: actions/checkout@v7 @@ -198,6 +222,14 @@ jobs: $hf render --fps 60 --quality high --output "$GITHUB_WORKSPACE/dist/ironruby-4-launch.mp4" $hf snapshot --frames 12 + # As the archives: the film that video_for puts on a release gets its provenance here, + # in the job that rendered it. + - name: Attest build provenance + if: inputs.video_for != '' + uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2 + with: + subject-path: dist/ironruby-4-launch.mp4 + - uses: actions/upload-artifact@v7 with: name: ironruby-launch-video @@ -220,6 +252,9 @@ jobs: runs-on: ubuntu-latest permissions: contents: write + id-token: write + attestations: write + artifact-metadata: write steps: - uses: actions/checkout@v7 with: @@ -236,6 +271,12 @@ jobs: working-directory: dist run: sha256sum * | tee SHA256SUMS + # The archives were attested where they were built; SHA256SUMS is made here. + - name: Attest SHA256SUMS + uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2 + with: + subject-path: dist/SHA256SUMS + - name: Publish env: GH_TOKEN: ${{ github.token }} @@ -259,13 +300,16 @@ jobs: runs-on: ubuntu-latest permissions: contents: write + id-token: write + attestations: write + artifact-metadata: write steps: - uses: actions/download-artifact@v8 with: name: ironruby-launch-video path: dist - - name: Upload + - name: Checksums working-directory: dist env: GH_TOKEN: ${{ github.token }} @@ -276,4 +320,16 @@ jobs: { grep -v ' ironruby-4-launch.mp4$' SHA256SUMS; echo "$sum"; } > SHA256SUMS.new mv SHA256SUMS.new SHA256SUMS cat SHA256SUMS - gh release upload "$TAG" ironruby-4-launch.mp4 SHA256SUMS --clobber --repo "$GITHUB_REPOSITORY" + + # The video was attested where it was rendered; this SHA256SUMS is made here. + - name: Attest SHA256SUMS + uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2 + with: + subject-path: dist/SHA256SUMS + + - name: Upload + working-directory: dist + env: + GH_TOKEN: ${{ github.token }} + TAG: ${{ inputs.video_for }} + run: gh release upload "$TAG" ironruby-4-launch.mp4 SHA256SUMS --clobber --repo "$GITHUB_REPOSITORY" diff --git a/Build/Key.snk b/Build/Key.snk deleted file mode 100644 index 3557d7b81..000000000 Binary files a/Build/Key.snk and /dev/null differ diff --git a/README.md b/README.md index e88d31260..ba7225134 100644 --- a/README.md +++ b/README.md @@ -521,8 +521,30 @@ The Windows archive also carries the Windows Desktop runtime, for Windows Forms bundles, Microsoft's license and third-party notices. `Util/package-release.sh ` builds such an archive from a checkout, and -`.github/workflows/release.yml` builds all three, renders the [launch video](media/launch) -and publishes them when a `v*` tag is pushed. +`.github/workflows/release.yml` builds all three and publishes them when a `v*` tag is +pushed; the [launch video](media/launch) is rendered and added by hand. + +Releases after 4.0.1 carry a signed [build provenance attestation](https://docs.github.com/en/actions/security-guides/using-artifact-attestations-to-establish-provenance-for-builds) +for each archive and for `SHA256SUMS`, made by GitHub's `actions/attest` in the job that +built the file. It says +which workflow built the file, from which commit, in which run, and that the file is +the one it built. Check one with the GitHub CLI: + +```console +$ gh attestation verify ironruby--linux-x64.tar.gz --repo Largo/ironruby +``` + +What is in an archive comes from these places, none of them a binary in this repository: +IronRuby, the DLR and prism (libprism too) are compiled in the job from source, at the +revisions `release.yml` pins; the NuGet packages the projects name, at the versions they +pin - AngleSharp, AngleSharp.Xml, Microsoft.Data.Sqlite, System.Text.Encoding.CodePages +and SQLitePCLRaw.bundle_e_sqlite3, whose native `e_sqlite3` is SQLite as that package +builds it on Windows and macOS; on Linux, SQLite's own amalgamation source, downloaded +from sqlite.org and checked against the SHA-256 `Util/build-e_sqlite3.sh` pins, which +that script compiles in the job instead (the package's library crashes beside another +SQLite); and the .NET runtime. The repository itself holds no programs - no `.exe`, +`.dll`, `.so` or any other binary that runs - and CI keeps it so with +`Util/check-no-executables.rb`, which looks at what each tracked file is, not at its name. ## Building diff --git a/Src/Samples/Tutorial/app/Tutorials/ironruby_files/TutorialSamples.dll b/Src/Samples/Tutorial/app/Tutorials/ironruby_files/TutorialSamples.dll deleted file mode 100644 index b706b066e..000000000 Binary files a/Src/Samples/Tutorial/app/Tutorials/ironruby_files/TutorialSamples.dll and /dev/null differ diff --git a/Src/Samples/Tutorial/wpf/SplashScreen.dll b/Src/Samples/Tutorial/wpf/SplashScreen.dll deleted file mode 100644 index 0d01bf702..000000000 Binary files a/Src/Samples/Tutorial/wpf/SplashScreen.dll and /dev/null differ diff --git a/Tests/Experiments/BuiltinsAll.txt b/Tests/Experiments/BuiltinsAll.txt index 9be08baf0..8e8843b4d 100644 Binary files a/Tests/Experiments/BuiltinsAll.txt and b/Tests/Experiments/BuiltinsAll.txt differ diff --git a/Tests/Experiments/Loader/Extensions/fcntl.so b/Tests/Experiments/Loader/Extensions/fcntl.so index f0ae0c5c4..e3b8f6319 100644 Binary files a/Tests/Experiments/Loader/Extensions/fcntl.so and b/Tests/Experiments/Loader/Extensions/fcntl.so differ diff --git a/Tests/Experiments/Loader/Extensions2/fcntl.so b/Tests/Experiments/Loader/Extensions2/fcntl.so index f0ae0c5c4..e3b8f6319 100644 Binary files a/Tests/Experiments/Loader/Extensions2/fcntl.so and b/Tests/Experiments/Loader/Extensions2/fcntl.so differ diff --git a/Tests/Experiments/Process/X/A B/x.exe b/Tests/Experiments/Process/X/A B/x.exe deleted file mode 100644 index 876d6594d..000000000 Binary files a/Tests/Experiments/Process/X/A B/x.exe and /dev/null differ diff --git a/Tests/Experiments/Process/X/a.exe b/Tests/Experiments/Process/X/a.exe deleted file mode 100644 index 2eb126e3d..000000000 Binary files a/Tests/Experiments/Process/X/a.exe and /dev/null differ diff --git a/Tests/Experiments/Process/X/attrib.exe b/Tests/Experiments/Process/X/attrib.exe deleted file mode 100644 index 417b103b3..000000000 Binary files a/Tests/Experiments/Process/X/attrib.exe and /dev/null differ diff --git a/Tests/Experiments/Process/X/b.exe b/Tests/Experiments/Process/X/b.exe deleted file mode 100644 index 195dc9245..000000000 Binary files a/Tests/Experiments/Process/X/b.exe and /dev/null differ diff --git a/Tests/Experiments/Process/X/inspect.exe b/Tests/Experiments/Process/X/inspect.exe deleted file mode 100644 index 6021ae409..000000000 Binary files a/Tests/Experiments/Process/X/inspect.exe and /dev/null differ diff --git a/Tests/Experiments/Process/X/notepad.exe b/Tests/Experiments/Process/X/notepad.exe deleted file mode 100644 index 417b103b3..000000000 Binary files a/Tests/Experiments/Process/X/notepad.exe and /dev/null differ diff --git a/Tests/Experiments/Process/X/q.com b/Tests/Experiments/Process/X/q.com deleted file mode 100644 index 417b103b3..000000000 Binary files a/Tests/Experiments/Process/X/q.com and /dev/null differ diff --git a/Tests/Experiments/Process/X/w.exe.exe b/Tests/Experiments/Process/X/w.exe.exe deleted file mode 100644 index 417b103b3..000000000 Binary files a/Tests/Experiments/Process/X/w.exe.exe and /dev/null differ diff --git a/Tests/Experiments/Process/X/w.t.exe b/Tests/Experiments/Process/X/w.t.exe deleted file mode 100644 index 417b103b3..000000000 Binary files a/Tests/Experiments/Process/X/w.t.exe and /dev/null differ diff --git a/Tests/Experiments/Process/X/w.tx.exe b/Tests/Experiments/Process/X/w.tx.exe deleted file mode 100644 index 417b103b3..000000000 Binary files a/Tests/Experiments/Process/X/w.tx.exe and /dev/null differ diff --git a/Tests/Experiments/Process/X/z.txt.exe b/Tests/Experiments/Process/X/z.txt.exe deleted file mode 100644 index 417b103b3..000000000 Binary files a/Tests/Experiments/Process/X/z.txt.exe and /dev/null differ diff --git a/Tests/Experiments/Process/X/z.txtt.exe b/Tests/Experiments/Process/X/z.txtt.exe deleted file mode 100644 index 417b103b3..000000000 Binary files a/Tests/Experiments/Process/X/z.txtt.exe and /dev/null differ diff --git a/Tests/Interop/com/fixtures.generated.dll b/Tests/Interop/com/fixtures.generated.dll deleted file mode 100644 index 0fab2d9c4..000000000 Binary files a/Tests/Interop/com/fixtures.generated.dll and /dev/null differ diff --git a/Tests/Interop/net/A/a.generated.dll b/Tests/Interop/net/A/a.generated.dll deleted file mode 100644 index 2a236129c..000000000 Binary files a/Tests/Interop/net/A/a.generated.dll and /dev/null differ diff --git a/Tests/Interop/net/B/b.generated.dll b/Tests/Interop/net/B/b.generated.dll deleted file mode 100644 index c3f5b3d4f..000000000 Binary files a/Tests/Interop/net/B/b.generated.dll and /dev/null differ diff --git a/Tests/Interop/net/fixtures.generated.dll b/Tests/Interop/net/fixtures.generated.dll deleted file mode 100644 index b362b8900..000000000 Binary files a/Tests/Interop/net/fixtures.generated.dll and /dev/null differ diff --git a/Util/check-no-executables.rb b/Util/check-no-executables.rb new file mode 100644 index 000000000..bdd0dd9ef --- /dev/null +++ b/Util/check-no-executables.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true +# +# Fails when a file in the repository is a program: a Windows PE (.exe, .dll, .NET +# assemblies included), an ELF or Mach-O binary, a Java class or WebAssembly. Everything +# IronRuby ships is compiled from the sources here, the NuGet packages the projects pin and +# the .NET runtime, so the tree itself holds nothing that runs but cannot be read. Images, +# fonts and documents are not programs and are let through. CI runs it on every change: +# +# ruby Util/check-no-executables.rb # every file git tracks +# ruby Util/check-no-executables.rb FILE... # only these +# +# A file is recognised by its first bytes, not by its name, so a program cannot pass by +# being called notes.txt, and an extension-only placeholder such as +# Tests/Experiments/Loader/Extensions/fcntl.so - which is text - is not flagged. + +def kind_of(file, size) + head = file.read(4096) || "".b + return "ELF" if head.start_with?("\x7FELF".b) + return "WebAssembly" if head.start_with?("\0asm".b) + if head.start_with?("MZ".b) && head.bytesize >= 0x40 + # The PE signature is where e_lfanew says, which may be past what was read. + pe = head.byteslice(0x3c, 4).unpack1("V") + if pe + 4 <= size + signature = pe + 4 <= head.bytesize ? head.byteslice(pe, 4) : (file.seek(pe) && file.read(4)) + return "PE (Windows program or .NET assembly)" if signature == "PE\0\0".b + end + end + case head.byteslice(0, 4)&.unpack1("N") + when 0xFEEDFACE, 0xFEEDFACF, 0xCEFAEDFE, 0xCFFAEDFE then "Mach-O" + when 0xCAFEBABE then "Mach-O universal binary or Java class" + when 0xCAFEBABF then "Mach-O universal binary (64-bit offsets)" + end +end + +paths = ARGV.empty? ? `git ls-files -z`.split("\0") : ARGV +abort "check-no-executables: git ls-files failed" if ARGV.empty? && !$?.success? + +found = paths.filter_map do |path| + next unless File.file?(path) + size = File.size(path) + kind = File.open(path, "rb") { |file| kind_of(file, size) } + "#{path}: #{kind}" if kind +end + +if found.empty? + puts "check-no-executables: #{paths.size} files, no programs among them" +else + warn "check-no-executables: #{found.size} program(s) in the repository - build them from source instead:" + found.each { |line| warn " #{line}" } + exit 1 +end diff --git a/Util/numeric-conversion-matrix.rb b/Util/numeric-conversion-matrix.rb index 8171cc167..080347963 100644 Binary files a/Util/numeric-conversion-matrix.rb and b/Util/numeric-conversion-matrix.rb differ