leakferret 0.1.14 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 98c62cebff3d1211eeca1d965e121374d702afce5ecd10f9780894a832149d94
4
- data.tar.gz: 256fc704331ca3665340f2a91ed67536a08378e0d78d83ce07b945818123233d
3
+ metadata.gz: ef6c03214e9031af408f981d0da88fdc2d13a6bbb436162a172133cea932b87d
4
+ data.tar.gz: 51b3f462cab5b37de16c15c885650dfc8cec83fb9422b1b963cc9eb1de6de72b
5
5
  SHA512:
6
- metadata.gz: faf71368d72c20cfe03dd6e33400e87d2ddae92a9fa299edf7f025fc935b5f1c89fadedaf0a05518c31bbde1094fdc41730fcd0d59ff73d3466d6e6c0f570921
7
- data.tar.gz: c5b6e8eeed8d89a9f91cc0bb6c5f125b0cbc06e498347ce65035d1d7f36f1a40906efe151794a571974671e3a2e8e25975947aae42931893742b18b634a0daf0
6
+ metadata.gz: 0fefef7848e0fb314988b865daf8cfd542f29069473b330c9e20ddcbea3d26114dce8516f9db665574b132f1f1a103c76971e42876b51c12c26a529560c2bdfe
7
+ data.tar.gz: 434b508ffc8a671af5071db882e915681003cebd2746fd2055d48ac46e777fe29280ac9f371916e1ba6398eb412cd8cd61ff21ec157c1df56f6a549c39e17fcc
data/CHANGELOG.md ADDED
@@ -0,0 +1,30 @@
1
+ # Changelog
2
+
3
+ All notable changes to the `leakferret` gem are documented here. The gem version
4
+ can move independently of the native binary version it targets.
5
+
6
+ ## [0.2.0] - 2026-06-04
7
+
8
+ Minor bump (not patch): this removes the binary-download fallback, so a
9
+ previously-supported install path is gone on platforms without a prebuilt gem
10
+ (notably arm64-Windows). Breaking change under the 0.x convention.
11
+
12
+ ### Changed
13
+ - Ship **precompiled platform gems** (x86_64-linux, x86_64-darwin, arm64-darwin,
14
+ x64-mingw-ucrt) with the native binary bundled inside the gem. A normal
15
+ `gem install` gets the binary through RubyGems itself: no download, no
16
+ network, no Rust toolchain. Audit it with `gem unpack leakferret`. Mirrors how
17
+ `nokogiri` and `sorbet-static` ship.
18
+ - **Removed the binary-download flow entirely.** Dropped the install-time
19
+ `extconf.rb` pre-fetch and the runtime download + SHA256 logic. The gem now
20
+ has no fetch-and-run code to audit at all.
21
+ - On a platform without a prebuilt binary (e.g. aarch64-linux, Alpine/musl), the
22
+ source gem still installs (so `bundle install` resolves) and raises a clear
23
+ message pointing to `cargo install leakferret-cli` or `LEAKFERRET_BIN`. It
24
+ never downloads.
25
+ - Released gems now carry GitHub build provenance (SLSA attestation).
26
+
27
+ ### Removed
28
+ - `ext/leakferret/extconf.rb` and the runtime binary downloader.
29
+
30
+ Targets the leakferret `0.1.9` binary (unchanged).
data/README.md CHANGED
@@ -16,14 +16,17 @@
16
16
 
17
17
  Ruby gem wrapper around the native [`leakferret`](https://github.com/leakferrethq/leakferret)
18
18
  binary. This gem ships no scanning logic of its own: it installs a tiny Ruby
19
- shim plus a small executable, and downloads the prebuilt, statically-linked
20
- binary (written in Rust) from GitHub Releases once per platform at install
21
- time. All the work — scan, classify, verify, rewrite — happens in that single
22
- binary.
23
-
24
- This is the same packaging pattern used by `ruff`, `biome`, and `esbuild`:
25
- distributing the toolchain to build a Rust engine on every machine is
26
- unfriendly, so we ship the compiled engine instead.
19
+ shim plus the prebuilt, statically-linked engine (written in Rust). All the
20
+ work scan, classify, verify, rewrite happens in that single binary.
21
+
22
+ You get a **precompiled platform gem** with the binary bundled inside it, the
23
+ same way `nokogiri` and `sorbet-static` ship their native code, for x86_64
24
+ Linux (glibc), macOS (x86_64 and arm64), and x86_64 Windows. The binary travels
25
+ through RubyGems with the gem **no download, no network access, no Rust
26
+ toolchain** and you can audit exactly what you will run with
27
+ `gem unpack leakferret`. The gem has **no fetch-and-run code at all**. On any
28
+ other platform the source gem installs and points you at a one-line fix (build
29
+ from source, or set `LEAKFERRET_BIN`); it never downloads a binary.
27
30
 
28
31
  ## What leakferret does
29
32
 
@@ -57,9 +60,19 @@ from your machine to the provider — leakferret has no servers.
57
60
  gem install leakferret
58
61
  ```
59
62
 
60
- The platform binary (`leakferret-{version}-{platform}.tar.gz` from GitHub
61
- Releases) is downloaded automatically on first use and cached under your home
62
- directory no Rust toolchain required.
63
+ RubyGems and Bundler select the right precompiled gem for your platform
64
+ automatically, so the binary is already there after `gem install` no
65
+ first-run download. Inspect it before you trust it:
66
+
67
+ ```bash
68
+ gem unpack leakferret # the bundled binary is at lib/leakferret/bin/
69
+ ```
70
+
71
+ Released gems carry GitHub build provenance (SLSA), so you can verify each gem
72
+ was built from the tagged source in CI. On a platform without a prebuilt binary
73
+ (e.g. aarch64-linux or Alpine/musl), the source gem still installs — so
74
+ `bundle install` resolves — and tells you to build from source
75
+ (`cargo install leakferret-cli`) or set `LEAKFERRET_BIN`. It never downloads.
63
76
 
64
77
  Add it to a `Gemfile` for project-local use:
65
78
 
@@ -7,45 +7,28 @@ require_relative 'platform'
7
7
  require_relative 'error'
8
8
 
9
9
  module Leakferret
10
- # Resolves (and, if needed, downloads) the native `leakferret` binary.
10
+ # Resolves the native `leakferret` binary that ships inside the gem.
11
11
  #
12
- # The binary is fetched into an absolute, user-writable cache directory
13
- # rather than into the gem's own tree. RubyGems builds extensions in a
14
- # throwaway temp dir, so anything written relative to the gem during
15
- # `gem install` is discarded the cache path sidesteps that entirely and
16
- # also lets a plain `gem install` (no extension) work.
12
+ # Precompiled platform gems bundle the binary at lib/leakferret/bin/. There is
13
+ # deliberately no download path: the gem either carries the binary for your
14
+ # platform or it tells you how to provide one. Nothing here touches the
15
+ # network, so what you `gem unpack` is exactly what runs - the whole gem is
16
+ # auditable, with no fetch-and-execute code to vet.
17
17
  #
18
18
  # @api private
19
19
  module Binary
20
- # A binary vendored inside the gem, if one was shipped (normally empty).
20
+ # Where a precompiled platform gem stages the native binary.
21
21
  BUNDLED_DIR = Pathname.new(__dir__).join('bin').freeze
22
22
 
23
- # SHA256 of each release tarball, pinned to BINARY_VERSION. The download is
24
- # verified against these before the archive is ever unpacked, so a tampered
25
- # or corrupted release asset is rejected instead of being executed. Because
26
- # the digests live in the gem source, auditing the published gem tells you
27
- # exactly which binary bytes it will run. Regenerate on every binary bump
28
- # from the release's `*.tar.gz.sha256` files.
29
- CHECKSUMS = {
30
- 'aarch64-apple-darwin' => '363b1da65bf34d2c89c37aa2e00eaa03d49c8595cc5b8bb752a344dacf21d7da',
31
- 'aarch64-pc-windows-msvc' => 'b009e852af7eb73dc203e9cb343bec80a1727075d6a35be4ff9567191fd57ca9',
32
- 'x86_64-apple-darwin' => '3af3d7f22a8d127053df123033b0b6777701310733d643406754423d6b1d3912',
33
- 'x86_64-pc-windows-msvc' => '3f038f55cfded63ebc2f8c16c1edbdd1ddcd36ae43a872b91f5aaeed93438fc1',
34
- 'x86_64-unknown-linux-gnu' => 'ba28ac5ef5a44d47f162f3c424c1465da5bbd17fb7292f60d3bd3cf3b2d362a4'
35
- }.freeze
36
-
37
23
  module_function
38
24
 
39
- # Absolute path to the native binary, downloading it on first use if
40
- # necessary. Resolution order:
41
- # 1. LEAKFERRET_BIN — explicit override
42
- # 2. lib/leakferret/bin/ — a binary vendored in the gem
43
- # 3. the per-version cache — fetched on a prior run or at install
44
- # 4. download into the cache now
25
+ # Absolute path to the native binary. Resolution order:
26
+ # 1. LEAKFERRET_BIN - explicit override
27
+ # 2. lib/leakferret/bin/<name> - bundled by the precompiled platform gem
45
28
  #
46
29
  # @return [String] absolute path to the executable
47
- # @raise [BinaryNotFoundError] if the override is missing, or the binary is
48
- # absent and cannot be downloaded
30
+ # @raise [BinaryNotFoundError] if the override is missing, or no binary is
31
+ # bundled for this platform
49
32
  def path
50
33
  override = ENV['LEAKFERRET_BIN']
51
34
  unless override.nil? || override.empty?
@@ -57,119 +40,50 @@ module Leakferret
57
40
  end
58
41
 
59
42
  bundled = BUNDLED_DIR.join(Platform.binary_name)
60
- return bundled.to_s if bundled.file?
61
-
62
- return cache_path.to_s if cache_path.file?
63
-
64
- ensure!
65
- raise BinaryNotFoundError, install_instructions(cache_path) unless cache_path.file?
66
-
67
- cache_path.to_s
68
- end
69
-
70
- # User-writable cache directory, namespaced by the binary version so a
71
- # gem upgrade fetches a fresh binary instead of reusing a stale one.
72
- #
73
- # @return [Pathname] the per-version cache directory
74
- def cache_dir
75
- base =
76
- if Platform.windows?
77
- ENV['LOCALAPPDATA'] || File.join(Dir.home, 'AppData', 'Local')
78
- else
79
- ENV['XDG_CACHE_HOME'] || File.join(Dir.home, '.cache')
80
- end
81
- Pathname.new(base).join('leakferret', BINARY_VERSION)
82
- end
83
-
84
- # @return [Pathname] the cached binary's full path for this platform
85
- def cache_path
86
- cache_dir.join(Platform.binary_name)
87
- end
88
-
89
- # @return [String] the GitHub release download URL for this platform's tarball
90
- def download_url
91
- 'https://github.com/leakferrethq/leakferret/releases/download/' \
92
- "v#{BINARY_VERSION}/leakferret-#{BINARY_VERSION}-#{Platform.triple}.tar.gz"
93
- end
94
-
95
- # Download, checksum-verify, and unpack the binary into the cache.
96
- # Idempotent: a no-op when the binary is already cached. The SHA256 is
97
- # checked against the pinned {CHECKSUMS} value before anything is written
98
- # or marked executable, so a tampered or truncated asset is rejected.
99
- #
100
- # @return [String] absolute path to the cached binary
101
- # @raise [BinaryNotFoundError] on an unknown platform, a checksum mismatch,
102
- # or a binary missing from the downloaded archive
103
- def ensure!
104
- dest = cache_path
105
- return dest.to_s if dest.file?
106
-
107
- require 'fileutils'
108
- require 'open-uri'
109
- require 'zlib'
110
- require 'digest'
111
- require 'stringio'
112
- require 'rubygems/package'
113
-
114
- expected = CHECKSUMS[Platform.triple]
115
- if expected.nil?
116
- raise BinaryNotFoundError,
117
- "no pinned checksum for platform #{Platform.triple}; refusing to run an " \
118
- 'unverified binary. Build from source and set LEAKFERRET_BIN instead.'
119
- end
120
-
121
- FileUtils.mkdir_p(dest.dirname)
122
-
123
- # Download the whole tarball, verify its SHA256 against the pinned value,
124
- # and only then unpack. Nothing is written to the cache (let alone marked
125
- # executable) until the bytes match, so a tampered or truncated release
126
- # asset is rejected rather than run.
127
- tarball = URI.open(download_url, &:read) # rubocop:disable Security/Open
128
- actual = Digest::SHA256.hexdigest(tarball)
129
- unless actual.casecmp?(expected)
130
- raise BinaryNotFoundError,
131
- "checksum mismatch for #{download_url}\n" \
132
- " expected #{expected}\n got #{actual}\n" \
133
- 'Refusing to install a binary that does not match the pinned hash.'
134
- end
135
-
136
- # Unpack in pure Ruby (no external `tar`, which on Windows mis-reads `C:\`
137
- # as a remote host). The archive nests everything under
138
- # leakferret-<version>-<triple>/, so match by basename.
139
- found = false
140
- Zlib::GzipReader.wrap(StringIO.new(tarball)) do |gz|
141
- Gem::Package::TarReader.new(gz) do |tar|
142
- tar.each do |entry|
143
- next unless entry.file?
144
- next unless File.basename(entry.full_name) == Platform.binary_name
145
-
146
- File.binwrite(dest, entry.read)
147
- found = true
43
+ if bundled.file?
44
+ # Make sure it is executable in case the mode did not survive packaging
45
+ # or install; the gem dir is usually writable, a read-only one is
46
+ # harmless to skip.
47
+ unless Platform.windows? || bundled.executable?
48
+ begin
49
+ bundled.chmod(0o755)
50
+ rescue StandardError
51
+ # best effort
148
52
  end
149
53
  end
54
+ return bundled.to_s
150
55
  end
151
- raise BinaryNotFoundError, "binary not found inside #{download_url}" unless found
152
56
 
153
- FileUtils.chmod(0o755, dest) unless Platform.windows?
154
- dest.to_s
57
+ raise BinaryNotFoundError, no_binary_message
155
58
  end
156
59
 
157
- # Human-readable fallback message shown when the binary is absent and the
158
- # automatic download failed.
60
+ # Message shown when the source/fallback gem is installed on a platform with
61
+ # no precompiled binary. No automatic download is attempted - the user
62
+ # provides the binary, or builds it.
159
63
  #
160
- # @param candidate [Pathname] the cache path the binary was expected at
161
- # @return [String] multi-line manual-install instructions
162
- def install_instructions(candidate)
64
+ # @return [String] multi-line build-from-source instructions
65
+ def no_binary_message
66
+ plat =
67
+ begin
68
+ Gem::Platform.local.to_s
69
+ rescue StandardError
70
+ RUBY_PLATFORM
71
+ end
72
+
163
73
  <<~MSG
164
- leakferret native binary not found, and the automatic download failed.
74
+ No prebuilt leakferret binary ships for this platform (#{plat}).
165
75
 
166
- Expected it at:
167
- #{candidate}
76
+ leakferret publishes precompiled gems for x86_64 and arm64 macOS,
77
+ x86_64 Linux (glibc), and x86_64 Windows. On any other platform, provide
78
+ the binary yourself - either of these works:
168
79
 
169
- Download the binary for your platform from:
170
- https://github.com/leakferrethq/leakferret/releases
80
+ 1. Build from source and point LEAKFERRET_BIN at it:
81
+ cargo install leakferret-cli
82
+ export LEAKFERRET_BIN="$(command -v leakferret)"
171
83
 
172
- then either place it at the path above or point LEAKFERRET_BIN at it.
84
+ 2. Download a release binary for your platform from
85
+ https://github.com/leakferrethq/leakferret/releases
86
+ and set LEAKFERRET_BIN to its absolute path.
173
87
  MSG
174
88
  end
175
89
  end
@@ -3,11 +3,12 @@
3
3
  module Leakferret
4
4
  # The gem's own version (what `gem install leakferret` resolves).
5
5
  # @return [String]
6
- VERSION = '0.1.14'
6
+ VERSION = '0.2.0'
7
7
 
8
- # The native binary release this gem downloads and runs. Tracks the
9
- # leakferret core release and can move independently of {VERSION}
10
- # (e.g. a gem-only bugfix keeps the same binary).
8
+ # The native binary release this gem bundles and runs. The release workflow
9
+ # stages this version's binary into each precompiled platform gem. Tracks the
10
+ # leakferret core release and can move independently of {VERSION} (e.g. a
11
+ # gem-only bugfix keeps the same binary).
11
12
  # @return [String]
12
13
  BINARY_VERSION = '0.1.9'
13
14
  end
metadata CHANGED
@@ -1,21 +1,28 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: leakferret
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.14
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Maria Khan
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-06-03 00:00:00.000000000 Z
11
+ date: 2026-06-04 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |
14
14
  Context-aware secret scanning for Ruby projects. A thin wrapper around the
15
15
  native leakferret binary (written in Rust): it finds hardcoded secrets,
16
16
  confirms which ones are actually live by calling the provider, and rewrites
17
- them to read from environment variables instead. The platform binary is
18
- downloaded automatically on first use, so no Rust toolchain is required.
17
+ them to read from environment variables instead.
18
+
19
+ Precompiled platform gems bundle the native binary inside the gem, so a
20
+ normal `gem install` ships the binary through RubyGems itself: no download,
21
+ no network access, and no Rust toolchain. You can audit exactly what you are
22
+ about to run with `gem unpack leakferret`. The gem never fetches and runs a
23
+ binary off the internet - there is no download code to vet. On a platform
24
+ without a prebuilt gem, the source gem tells you to build from source
25
+ (`cargo install leakferret-cli`) or point LEAKFERRET_BIN at a binary.
19
26
 
20
27
  The API exposes Leakferret.scan, Leakferret.verify, and Leakferret.rewrite
21
28
  (each returning Finding objects), plus a `leakferret` command-line tool.
@@ -23,15 +30,14 @@ email:
23
30
  - missusk@protonmail.com
24
31
  executables:
25
32
  - leakferret
26
- extensions:
27
- - ext/leakferret/extconf.rb
33
+ extensions: []
28
34
  extra_rdoc_files: []
29
35
  files:
30
36
  - ".yardopts"
37
+ - CHANGELOG.md
31
38
  - LICENSE.txt
32
39
  - README.md
33
40
  - exe/leakferret
34
- - ext/leakferret/extconf.rb
35
41
  - lib/leakferret.rb
36
42
  - lib/leakferret/binary.rb
37
43
  - lib/leakferret/client.rb
@@ -1,38 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Best-effort install-time pre-fetch of the native binary into the
4
- # user-writable cache (see lib/leakferret/binary.rb). This is purely an
5
- # optimisation so the first invocation is instant — the binary is also
6
- # downloaded lazily on first use, so a failure here is harmless. Set
7
- # LEAKFERRET_SKIP_DOWNLOAD to skip the pre-fetch.
8
- #
9
- # We deliberately do NOT compile the Rust source: that would force every
10
- # user to have a Rust toolchain and wait for a long build. The
11
- # download-binary model matches ruff (Python), biome/esbuild (npm).
12
-
13
- require_relative '../../lib/leakferret/version'
14
- require_relative '../../lib/leakferret/platform'
15
- require_relative '../../lib/leakferret/error'
16
- require_relative '../../lib/leakferret/binary'
17
-
18
- # Emit an empty Makefile so rubygems considers the "extension" built.
19
- File.write('Makefile', "all:\n\t@true\ninstall:\n\t@true\nclean:\n\t@true\n")
20
-
21
- def log(msg)
22
- warn "[leakferret/extconf] #{msg}"
23
- end
24
-
25
- if ENV['LEAKFERRET_SKIP_DOWNLOAD']
26
- log 'LEAKFERRET_SKIP_DOWNLOAD set; binary will be downloaded on first use.'
27
- exit 0
28
- end
29
-
30
- begin
31
- path = Leakferret::Binary.ensure!
32
- log "binary ready at #{path}"
33
- rescue StandardError => e
34
- log "pre-fetch failed (#{e.class}: #{e.message}); it will download on first use."
35
- end
36
-
37
- # Always succeed: a failed pre-fetch must not fail the gem install.
38
- exit 0