leakferret 0.1.3
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 +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +123 -0
- data/exe/leakferret +8 -0
- data/ext/leakferret/extconf.rb +38 -0
- data/lib/leakferret/binary.rb +120 -0
- data/lib/leakferret/client.rb +47 -0
- data/lib/leakferret/error.rb +15 -0
- data/lib/leakferret/platform.rb +40 -0
- data/lib/leakferret/version.rb +11 -0
- data/lib/leakferret.rb +48 -0
- metadata +64 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 77f46588fbab40b45095c4654047ef78ab41203036e77e3df34e6c0af9396777
|
|
4
|
+
data.tar.gz: '079c0db268161032b79eac1e49fee61f785aa334a242680f95d7c8a7d260d8f7'
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 89f676f8d255335e83d820a5c962a723cc6434039b56180e2e0bdddcb017bc2d03d765139a00fe5827e6c1b82125a351617bd0cec8a225d9332e76f18231c339
|
|
7
|
+
data.tar.gz: 151b37249dbf4a8d743bc142d8dfb0815a16e5a7833be451dfa0640316026d21fa36e5215785552e683693594f4ed1bc614145401235edccb29fcc8a4aa62736
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Maria Khan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="assets/logo.png" alt="leakferret" width="380">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
# leakferret (Ruby gem)
|
|
6
|
+
|
|
7
|
+
> MCP-native secret scanner — verified findings, agent-applied rewrites.
|
|
8
|
+
|
|
9
|
+
Ruby gem wrapper around the native [`leakferret`](https://github.com/leakferrethq/leakferret)
|
|
10
|
+
binary. This gem ships no scanning logic of its own: it installs a tiny Ruby
|
|
11
|
+
shim plus a small executable, and downloads the prebuilt, statically-linked
|
|
12
|
+
binary (written in Rust) from GitHub Releases once per platform at install
|
|
13
|
+
time. All the work — scan, classify, verify, rewrite — happens in that single
|
|
14
|
+
binary.
|
|
15
|
+
|
|
16
|
+
This is the same packaging pattern used by `ruff`, `biome`, and `esbuild`:
|
|
17
|
+
distributing the toolchain to build a Rust engine on every machine is
|
|
18
|
+
unfriendly, so we ship the compiled engine instead.
|
|
19
|
+
|
|
20
|
+
## What leakferret does
|
|
21
|
+
|
|
22
|
+
leakferret finds hardcoded secrets and API keys in your code and helps you
|
|
23
|
+
remove them, in five stations:
|
|
24
|
+
|
|
25
|
+
1. **Scan** — regex pre-filter over files; respects `.gitignore` and also reads
|
|
26
|
+
dotfiles like `.env`.
|
|
27
|
+
2. **Catalog** — a signed database of known-public example credentials (Stripe
|
|
28
|
+
test keys, `AKIAIOSFODNN7EXAMPLE`, jwt.io samples) so documented examples are
|
|
29
|
+
marked `FIXTURE` instead of false-alarming.
|
|
30
|
+
3. **Classify** — a `REAL` / `FIXTURE` / `UNKNOWN` verdict, from offline
|
|
31
|
+
heuristics or by asking the host editor/agent language model (no extra API
|
|
32
|
+
key, no cost).
|
|
33
|
+
4. **Verify** — a real but harmless API call to the provider (AWS SigV4,
|
|
34
|
+
GitHub, GitLab, Stripe, OpenAI, Anthropic, Slack, Twilio, SendGrid, Mailgun,
|
|
35
|
+
Datadog, Heroku, npm, PyPI, DigitalOcean) to confirm a key is live, plus a
|
|
36
|
+
trufflehog fallback.
|
|
37
|
+
5. **Rewrite** — swap a hardcoded literal for an environment-variable lookup
|
|
38
|
+
(`ENV.fetch` in Ruby, `process.env` in JS, `os.environ` in Python), add a
|
|
39
|
+
`.env.example` line, and print secret-manager seed commands.
|
|
40
|
+
|
|
41
|
+
**Privacy invariant:** the full secret value never leaves your machine. Only a
|
|
42
|
+
redacted first-4-plus-last-4 preview (e.g. `AKIA...4XYZ`) is ever written to a
|
|
43
|
+
report, log, network message, or model prompt. Verification calls go straight
|
|
44
|
+
from your machine to the provider — leakferret has no servers.
|
|
45
|
+
|
|
46
|
+
## Install
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
gem install leakferret
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
This downloads `leakferret-{version}-{platform}.tar.gz` from GitHub Releases and
|
|
53
|
+
unpacks the binary into `lib/leakferret/bin/`.
|
|
54
|
+
|
|
55
|
+
Add it to a `Gemfile` for project-local use:
|
|
56
|
+
|
|
57
|
+
```ruby
|
|
58
|
+
gem 'leakferret'
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Requires Ruby >= 3.1.
|
|
62
|
+
|
|
63
|
+
## CLI
|
|
64
|
+
|
|
65
|
+
The gem installs a `leakferret` executable that simply `exec`s the binary, so
|
|
66
|
+
every subcommand and flag works exactly as upstream:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
leakferret scan .
|
|
70
|
+
leakferret verify . --only-verified
|
|
71
|
+
leakferret rewrite . --apply --backend doppler
|
|
72
|
+
leakferret baseline init
|
|
73
|
+
leakferret catalog info
|
|
74
|
+
leakferret mcp # MCP server on stdio
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`leakferret scan --git` walks commit history. Output formats are `pretty`
|
|
78
|
+
(colored terminal), `json`, and `sarif` (for GitHub Code Scanning).
|
|
79
|
+
|
|
80
|
+
## Ruby API
|
|
81
|
+
|
|
82
|
+
```ruby
|
|
83
|
+
require 'leakferret'
|
|
84
|
+
|
|
85
|
+
# Regex pre-filter only.
|
|
86
|
+
findings = Leakferret.scan('.')
|
|
87
|
+
|
|
88
|
+
# + provider-verified (live HTTP to GitHub / Stripe / AWS / ...).
|
|
89
|
+
findings = Leakferret.verify('.', mode: 'only-verified')
|
|
90
|
+
|
|
91
|
+
# + propose rewrites for REAL findings.
|
|
92
|
+
findings = Leakferret.rewrite('.', backend: 'doppler')
|
|
93
|
+
|
|
94
|
+
# Apply rewrites in place.
|
|
95
|
+
Leakferret.rewrite('.', apply: true)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Each `Finding` is a hash with `path`, `line`, `column`, `pattern`, `severity`,
|
|
99
|
+
`verdict`, `match_redacted`, `confidence`, `verification`, and `fingerprint`.
|
|
100
|
+
|
|
101
|
+
## Using a local binary
|
|
102
|
+
|
|
103
|
+
Every leakferret wrapper honors the `LEAKFERRET_BIN` environment variable. Point
|
|
104
|
+
it at a binary on disk and the wrapper runs that instead of the downloaded copy:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
export LEAKFERRET_BIN=/opt/leakferret/leakferret
|
|
108
|
+
leakferret scan .
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
For air-gapped or offline installs, set `LEAKFERRET_SKIP_DOWNLOAD=1` to skip the
|
|
112
|
+
release download and position the binary yourself.
|
|
113
|
+
|
|
114
|
+
## License
|
|
115
|
+
|
|
116
|
+
MIT for this gem and the bundled binary. The fixture catalog **data** is
|
|
117
|
+
CC-BY-SA-4.0 — see [`leakferret-catalog`](https://github.com/leakferrethq/leakferret-catalog).
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
Part of [leakferret](https://github.com/leakferrethq/leakferret) ·
|
|
122
|
+
[leakferret.com](https://leakferret.com) ·
|
|
123
|
+
maintained by Maria Khan <missusk@protonmail.com>.
|
data/exe/leakferret
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
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
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'pathname'
|
|
4
|
+
|
|
5
|
+
require_relative 'version'
|
|
6
|
+
require_relative 'platform'
|
|
7
|
+
require_relative 'error'
|
|
8
|
+
|
|
9
|
+
module Leakferret
|
|
10
|
+
# Resolves (and, if needed, downloads) the native `leakferret` binary.
|
|
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.
|
|
17
|
+
module Binary
|
|
18
|
+
# A binary vendored inside the gem, if one was shipped (normally empty).
|
|
19
|
+
BUNDLED_DIR = Pathname.new(__dir__).join('bin').freeze
|
|
20
|
+
|
|
21
|
+
module_function
|
|
22
|
+
|
|
23
|
+
# Absolute path to the native binary, downloading it on first use if
|
|
24
|
+
# necessary. Resolution order:
|
|
25
|
+
# 1. LEAKFERRET_BIN — explicit override
|
|
26
|
+
# 2. lib/leakferret/bin/ — a binary vendored in the gem
|
|
27
|
+
# 3. the per-version cache — fetched on a prior run or at install
|
|
28
|
+
# 4. download into the cache now
|
|
29
|
+
def path
|
|
30
|
+
override = ENV['LEAKFERRET_BIN']
|
|
31
|
+
unless override.nil? || override.empty?
|
|
32
|
+
unless File.file?(override)
|
|
33
|
+
raise BinaryNotFoundError, "LEAKFERRET_BIN points to a missing file: #{override}"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
return override
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
bundled = BUNDLED_DIR.join(Platform.binary_name)
|
|
40
|
+
return bundled.to_s if bundled.file?
|
|
41
|
+
|
|
42
|
+
return cache_path.to_s if cache_path.file?
|
|
43
|
+
|
|
44
|
+
ensure!
|
|
45
|
+
raise BinaryNotFoundError, install_instructions(cache_path) unless cache_path.file?
|
|
46
|
+
|
|
47
|
+
cache_path.to_s
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# User-writable cache directory, namespaced by the binary version so a
|
|
51
|
+
# gem upgrade fetches a fresh binary instead of reusing a stale one.
|
|
52
|
+
def cache_dir
|
|
53
|
+
base =
|
|
54
|
+
if Platform.windows?
|
|
55
|
+
ENV['LOCALAPPDATA'] || File.join(Dir.home, 'AppData', 'Local')
|
|
56
|
+
else
|
|
57
|
+
ENV['XDG_CACHE_HOME'] || File.join(Dir.home, '.cache')
|
|
58
|
+
end
|
|
59
|
+
Pathname.new(base).join('leakferret', BINARY_VERSION)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def cache_path
|
|
63
|
+
cache_dir.join(Platform.binary_name)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def download_url
|
|
67
|
+
'https://github.com/leakferrethq/leakferret/releases/download/' \
|
|
68
|
+
"v#{BINARY_VERSION}/leakferret-#{BINARY_VERSION}-#{Platform.triple}.tar.gz"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Download and unpack the binary into the cache. Idempotent: a no-op when
|
|
72
|
+
# the binary is already cached. Returns the path; raises on failure.
|
|
73
|
+
def ensure!
|
|
74
|
+
dest = cache_path
|
|
75
|
+
return dest.to_s if dest.file?
|
|
76
|
+
|
|
77
|
+
require 'fileutils'
|
|
78
|
+
require 'open-uri'
|
|
79
|
+
require 'zlib'
|
|
80
|
+
require 'rubygems/package'
|
|
81
|
+
|
|
82
|
+
FileUtils.mkdir_p(dest.dirname)
|
|
83
|
+
# Stream download -> gunzip -> untar in pure Ruby (no external `tar`,
|
|
84
|
+
# which on Windows mis-reads `C:\` as a remote host). The archive nests
|
|
85
|
+
# everything under leakferret-<version>-<triple>/, so match by basename.
|
|
86
|
+
found = false
|
|
87
|
+
URI.open(download_url) do |io| # rubocop:disable Security/Open
|
|
88
|
+
Zlib::GzipReader.wrap(io) do |gz|
|
|
89
|
+
Gem::Package::TarReader.new(gz) do |tar|
|
|
90
|
+
tar.each do |entry|
|
|
91
|
+
next unless entry.file?
|
|
92
|
+
next unless File.basename(entry.full_name) == Platform.binary_name
|
|
93
|
+
|
|
94
|
+
File.binwrite(dest, entry.read)
|
|
95
|
+
found = true
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
raise BinaryNotFoundError, "binary not found inside #{download_url}" unless found
|
|
101
|
+
|
|
102
|
+
FileUtils.chmod(0o755, dest) unless Platform.windows?
|
|
103
|
+
dest.to_s
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def install_instructions(candidate)
|
|
107
|
+
<<~MSG
|
|
108
|
+
leakferret native binary not found, and the automatic download failed.
|
|
109
|
+
|
|
110
|
+
Expected it at:
|
|
111
|
+
#{candidate}
|
|
112
|
+
|
|
113
|
+
Download the binary for your platform from:
|
|
114
|
+
https://github.com/leakferrethq/leakferret/releases
|
|
115
|
+
|
|
116
|
+
then either place it at the path above or point LEAKFERRET_BIN at it.
|
|
117
|
+
MSG
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'open3'
|
|
5
|
+
|
|
6
|
+
module Leakferret
|
|
7
|
+
# Thin shell-out wrapper. Each public method invokes the binary with
|
|
8
|
+
# `--format json` and parses the resulting array.
|
|
9
|
+
class Client
|
|
10
|
+
def scan(path, exclude: [], only: nil, show_fixtures: false)
|
|
11
|
+
run(['scan', path, '--format', 'json'] + format_flags(exclude:, only:, show_fixtures:))
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def verify(path, mode: 'best-effort', timeout: 10, **opts)
|
|
15
|
+
run(['verify', path, '--format', 'json', '--verify-mode', mode,
|
|
16
|
+
'--verifier-timeout-secs', timeout.to_s] + format_flags(**opts))
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def rewrite(path, apply: false, backend: 'env', **opts)
|
|
20
|
+
args = ['rewrite', path, '--format', 'json', '--backend', backend]
|
|
21
|
+
args << '--apply' if apply
|
|
22
|
+
run(args + format_flags(**opts))
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def format_flags(exclude: [], only: nil, show_fixtures: false)
|
|
28
|
+
flags = []
|
|
29
|
+
Array(exclude).each { |g| flags.push('--exclude', g) }
|
|
30
|
+
Array(only).each { |p| flags.push('--only', p) }
|
|
31
|
+
flags << '--show-fixtures' if show_fixtures
|
|
32
|
+
flags
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def run(args)
|
|
36
|
+
out, err, status = Open3.capture3(Binary.path, *args)
|
|
37
|
+
unless [0, 1].include?(status.exitstatus)
|
|
38
|
+
raise BinaryInvocationError.new(
|
|
39
|
+
"leakferret exited with #{status.exitstatus}",
|
|
40
|
+
exit_status: status.exitstatus,
|
|
41
|
+
stderr: err,
|
|
42
|
+
)
|
|
43
|
+
end
|
|
44
|
+
JSON.parse(out.strip.empty? ? '[]' : out)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Leakferret
|
|
4
|
+
class Error < StandardError; end
|
|
5
|
+
class BinaryNotFoundError < Error; end
|
|
6
|
+
class BinaryInvocationError < Error
|
|
7
|
+
attr_reader :exit_status, :stderr
|
|
8
|
+
|
|
9
|
+
def initialize(message, exit_status:, stderr:)
|
|
10
|
+
super(message)
|
|
11
|
+
@exit_status = exit_status
|
|
12
|
+
@stderr = stderr
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rbconfig'
|
|
4
|
+
|
|
5
|
+
require_relative 'error'
|
|
6
|
+
|
|
7
|
+
module Leakferret
|
|
8
|
+
module Platform
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
def triple
|
|
12
|
+
cpu = case RbConfig::CONFIG['host_cpu']
|
|
13
|
+
when /x86_64|amd64|x64/ then 'x86_64'
|
|
14
|
+
when /aarch64|arm64/ then 'aarch64'
|
|
15
|
+
else
|
|
16
|
+
raise Error, "unsupported CPU: #{RbConfig::CONFIG['host_cpu']}"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
case RbConfig::CONFIG['host_os']
|
|
20
|
+
when /mswin|mingw|cygwin/ then "#{cpu}-pc-windows-msvc"
|
|
21
|
+
when /darwin/ then "#{cpu}-apple-darwin"
|
|
22
|
+
when /linux/
|
|
23
|
+
# No aarch64-linux release asset yet (v0.1.0 ships x86_64 only).
|
|
24
|
+
raise Error, 'aarch64-linux has no prebuilt binary yet; build from source' if cpu == 'aarch64'
|
|
25
|
+
|
|
26
|
+
"#{cpu}-unknown-linux-gnu"
|
|
27
|
+
else
|
|
28
|
+
raise Error, "unsupported OS: #{RbConfig::CONFIG['host_os']}"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def binary_name
|
|
33
|
+
windows? ? 'leakferret.exe' : 'leakferret'
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def windows?
|
|
37
|
+
RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Leakferret
|
|
4
|
+
# The gem's own version.
|
|
5
|
+
VERSION = '0.1.3'
|
|
6
|
+
|
|
7
|
+
# The native binary release this gem downloads. Tracks the leakferret
|
|
8
|
+
# core release, which may move independently of the gem's own version
|
|
9
|
+
# (e.g. a gem-only bugfix).
|
|
10
|
+
BINARY_VERSION = '0.1.1'
|
|
11
|
+
end
|
data/lib/leakferret.rb
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'open3'
|
|
5
|
+
|
|
6
|
+
require 'leakferret/version'
|
|
7
|
+
require 'leakferret/error'
|
|
8
|
+
require 'leakferret/platform'
|
|
9
|
+
require 'leakferret/binary'
|
|
10
|
+
require 'leakferret/client'
|
|
11
|
+
|
|
12
|
+
# Ruby wrapper around the native `leakferret` binary.
|
|
13
|
+
#
|
|
14
|
+
# The binary is downloaded once per platform at gem install time
|
|
15
|
+
# (`ext/leakferret/extconf.rb`) into `lib/leakferret/bin/`. Subsequent
|
|
16
|
+
# calls shell out to it and parse the JSON output.
|
|
17
|
+
module Leakferret
|
|
18
|
+
class << self
|
|
19
|
+
# Scan a directory; returns an array of finding hashes.
|
|
20
|
+
def scan(path = '.', **opts)
|
|
21
|
+
Client.new.scan(path, **opts)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Scan + verify + classify; returns findings with verification +
|
|
25
|
+
# verdict filled in.
|
|
26
|
+
def verify(path = '.', **opts)
|
|
27
|
+
Client.new.verify(path, **opts)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Scan + classify + propose rewrites for REAL findings. Use
|
|
31
|
+
# apply: true to write the rewrites in place.
|
|
32
|
+
def rewrite(path = '.', apply: false, **opts)
|
|
33
|
+
Client.new.rewrite(path, apply: apply, **opts)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Path to the bundled binary. Useful for tooling integration.
|
|
37
|
+
def binary_path
|
|
38
|
+
Binary.path
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Version reported by the bundled binary (Rust) — may differ from
|
|
42
|
+
# the gem version during pre-release.
|
|
43
|
+
def binary_version
|
|
44
|
+
out, _err, _status = Open3.capture3(binary_path, '--version')
|
|
45
|
+
out.strip
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: leakferret
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.3
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Maria Khan
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
12
|
+
description: |
|
|
13
|
+
Context-aware secret scanning for Ruby projects. A thin wrapper around the
|
|
14
|
+
native leakferret binary (written in Rust): it finds hardcoded secrets,
|
|
15
|
+
confirms which ones are actually live by calling the provider, and rewrites
|
|
16
|
+
them to read from environment variables instead. The platform binary is
|
|
17
|
+
downloaded automatically on first use, so no Rust toolchain is required.
|
|
18
|
+
|
|
19
|
+
The API exposes Leakferret.scan, Leakferret.verify, and Leakferret.rewrite
|
|
20
|
+
(each returning Finding objects), plus a `leakferret` command-line tool.
|
|
21
|
+
email:
|
|
22
|
+
- missusk@protonmail.com
|
|
23
|
+
executables:
|
|
24
|
+
- leakferret
|
|
25
|
+
extensions:
|
|
26
|
+
- ext/leakferret/extconf.rb
|
|
27
|
+
extra_rdoc_files: []
|
|
28
|
+
files:
|
|
29
|
+
- LICENSE.txt
|
|
30
|
+
- README.md
|
|
31
|
+
- exe/leakferret
|
|
32
|
+
- ext/leakferret/extconf.rb
|
|
33
|
+
- lib/leakferret.rb
|
|
34
|
+
- lib/leakferret/binary.rb
|
|
35
|
+
- lib/leakferret/client.rb
|
|
36
|
+
- lib/leakferret/error.rb
|
|
37
|
+
- lib/leakferret/platform.rb
|
|
38
|
+
- lib/leakferret/version.rb
|
|
39
|
+
homepage: https://github.com/leakferrethq/leakferret-ruby
|
|
40
|
+
licenses:
|
|
41
|
+
- MIT
|
|
42
|
+
metadata:
|
|
43
|
+
homepage_uri: https://github.com/leakferrethq/leakferret-ruby
|
|
44
|
+
source_code_uri: https://github.com/leakferrethq/leakferret-ruby
|
|
45
|
+
changelog_uri: https://github.com/leakferrethq/leakferret-ruby/blob/main/CHANGELOG.md
|
|
46
|
+
rubygems_mfa_required: 'true'
|
|
47
|
+
rdoc_options: []
|
|
48
|
+
require_paths:
|
|
49
|
+
- lib
|
|
50
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ">="
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: 3.1.0
|
|
55
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
56
|
+
requirements:
|
|
57
|
+
- - ">="
|
|
58
|
+
- !ruby/object:Gem::Version
|
|
59
|
+
version: '0'
|
|
60
|
+
requirements: []
|
|
61
|
+
rubygems_version: 3.6.9
|
|
62
|
+
specification_version: 4
|
|
63
|
+
summary: Context-aware secret detection (Ruby wrapper for the leakferret binary).
|
|
64
|
+
test_files: []
|