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 +4 -4
- data/CHANGELOG.md +30 -0
- data/README.md +24 -11
- data/lib/leakferret/binary.rb +45 -131
- data/lib/leakferret/version.rb +5 -4
- metadata +13 -7
- data/ext/leakferret/extconf.rb +0 -38
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ef6c03214e9031af408f981d0da88fdc2d13a6bbb436162a172133cea932b87d
|
|
4
|
+
data.tar.gz: 51b3f462cab5b37de16c15c885650dfc8cec83fb9422b1b963cc9eb1de6de72b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
binary
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
|
data/lib/leakferret/binary.rb
CHANGED
|
@@ -7,45 +7,28 @@ require_relative 'platform'
|
|
|
7
7
|
require_relative 'error'
|
|
8
8
|
|
|
9
9
|
module Leakferret
|
|
10
|
-
# Resolves
|
|
10
|
+
# Resolves the native `leakferret` binary that ships inside the gem.
|
|
11
11
|
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
# `gem
|
|
16
|
-
#
|
|
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
|
-
#
|
|
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
|
|
40
|
-
#
|
|
41
|
-
#
|
|
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
|
|
48
|
-
#
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
154
|
-
dest.to_s
|
|
57
|
+
raise BinaryNotFoundError, no_binary_message
|
|
155
58
|
end
|
|
156
59
|
|
|
157
|
-
#
|
|
158
|
-
# automatic download
|
|
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
|
-
# @
|
|
161
|
-
|
|
162
|
-
|
|
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
|
|
74
|
+
No prebuilt leakferret binary ships for this platform (#{plat}).
|
|
165
75
|
|
|
166
|
-
|
|
167
|
-
|
|
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
|
-
|
|
170
|
-
|
|
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
|
-
|
|
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
|
data/lib/leakferret/version.rb
CHANGED
|
@@ -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.
|
|
6
|
+
VERSION = '0.2.0'
|
|
7
7
|
|
|
8
|
-
# The native binary release this gem
|
|
9
|
-
#
|
|
10
|
-
#
|
|
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.
|
|
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-
|
|
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.
|
|
18
|
-
|
|
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
|
data/ext/leakferret/extconf.rb
DELETED
|
@@ -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
|