leakferret 0.1.9 → 0.1.10

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: 353c45016e727caee84d2f288e7c3c5cb9d2f0222f7e572ebb1fec054f46848d
4
- data.tar.gz: a3d5593e00f2b3b2fcbe76b5035c627494a958bfa264cd809f05e428d39e1c8a
3
+ metadata.gz: e2273979e57b9e1e415bc9dc13be44e17382d0fb4dfde43b71e3e21d496f379b
4
+ data.tar.gz: 73bf40a88cdb03f4fa1d8cdb1ec852db6348d45439a88dad0a61501ff519977c
5
5
  SHA512:
6
- metadata.gz: f2be117cdf827d36ce613506e948ec585aaebd59cabf15a06fd2ad6c6999e636c2fd021fbc7f7cf8efe093bbe48a1324f750ded8a74fd6387a42ddd7b2a77816
7
- data.tar.gz: d17ec7a2a5e672af4eb9594ce10390a5e32b932ec4b63a1e811901979f8bf11b3dd83a527ab8bd1088b6af0e49bc280e97e8e28324bafcea0117170687ca62a1
6
+ metadata.gz: 60465cdf6da291268f439dfb9864e15a54460eb93b9b974b4071e38399cc2eed78b1b5a506dad18cd63a6ab069819841dcaca9bd6cc6f0a20388fc3b654cc8de
7
+ data.tar.gz: 3e2de8a23b90a1fae5e5bb01d312e6495a3274908eb8c18c9169edb8e24fbc4a2b1e493e45976e25e8082dc0418777d1e724022d1ef1065c8f38170555098781
data/.yardopts ADDED
@@ -0,0 +1,8 @@
1
+ --markup markdown
2
+ --readme README.md
3
+ --title "leakferret (Ruby)"
4
+ --no-private
5
+ --charset utf-8
6
+ lib/**/*.rb
7
+ -
8
+ LICENSE.txt
data/README.md CHANGED
@@ -6,6 +6,9 @@
6
6
 
7
7
  > MCP-native secret scanner — verified findings, agent-applied rewrites.
8
8
 
9
+ [![Gem Version](https://img.shields.io/gem/v/leakferret?logo=rubygems&logoColor=white)](https://rubygems.org/gems/leakferret)
10
+ [![MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/leakferrethq/leakferret/blob/master/LICENSE.txt)
11
+
9
12
  <p align="center">
10
13
  <img src="https://raw.githubusercontent.com/leakferrethq/leakferret/master/brand/demo.gif" alt="leakferret finds, verifies, and rewrites a leaked secret" width="760">
11
14
  </p>
@@ -14,6 +14,8 @@ module Leakferret
14
14
  # throwaway temp dir, so anything written relative to the gem during
15
15
  # `gem install` is discarded — the cache path sidesteps that entirely and
16
16
  # also lets a plain `gem install` (no extension) work.
17
+ #
18
+ # @api private
17
19
  module Binary
18
20
  # A binary vendored inside the gem, if one was shipped (normally empty).
19
21
  BUNDLED_DIR = Pathname.new(__dir__).join('bin').freeze
@@ -40,6 +42,10 @@ module Leakferret
40
42
  # 2. lib/leakferret/bin/ — a binary vendored in the gem
41
43
  # 3. the per-version cache — fetched on a prior run or at install
42
44
  # 4. download into the cache now
45
+ #
46
+ # @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
43
49
  def path
44
50
  override = ENV['LEAKFERRET_BIN']
45
51
  unless override.nil? || override.empty?
@@ -63,6 +69,8 @@ module Leakferret
63
69
 
64
70
  # User-writable cache directory, namespaced by the binary version so a
65
71
  # gem upgrade fetches a fresh binary instead of reusing a stale one.
72
+ #
73
+ # @return [Pathname] the per-version cache directory
66
74
  def cache_dir
67
75
  base =
68
76
  if Platform.windows?
@@ -73,17 +81,25 @@ module Leakferret
73
81
  Pathname.new(base).join('leakferret', BINARY_VERSION)
74
82
  end
75
83
 
84
+ # @return [Pathname] the cached binary's full path for this platform
76
85
  def cache_path
77
86
  cache_dir.join(Platform.binary_name)
78
87
  end
79
88
 
89
+ # @return [String] the GitHub release download URL for this platform's tarball
80
90
  def download_url
81
91
  'https://github.com/leakferrethq/leakferret/releases/download/' \
82
92
  "v#{BINARY_VERSION}/leakferret-#{BINARY_VERSION}-#{Platform.triple}.tar.gz"
83
93
  end
84
94
 
85
- # Download and unpack the binary into the cache. Idempotent: a no-op when
86
- # the binary is already cached. Returns the path; raises on failure.
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
87
103
  def ensure!
88
104
  dest = cache_path
89
105
  return dest.to_s if dest.file?
@@ -138,6 +154,11 @@ module Leakferret
138
154
  dest.to_s
139
155
  end
140
156
 
157
+ # Human-readable fallback message shown when the binary is absent and the
158
+ # automatic download failed.
159
+ #
160
+ # @param candidate [Pathname] the cache path the binary was expected at
161
+ # @return [String] multi-line manual-install instructions
141
162
  def install_instructions(candidate)
142
163
  <<~MSG
143
164
  leakferret native binary not found, and the automatic download failed.
@@ -4,18 +4,47 @@ require 'json'
4
4
  require 'open3'
5
5
 
6
6
  module Leakferret
7
- # Thin shell-out wrapper. Each public method invokes the binary with
8
- # `--format json` and parses the resulting array.
7
+ # Thin shell-out wrapper around the native binary. Each public method invokes
8
+ # `leakferret <verb> --format json` and parses the resulting array. You
9
+ # normally call the module-level {Leakferret.scan}, {Leakferret.verify}, and
10
+ # {Leakferret.rewrite} helpers instead of constructing this directly.
9
11
  class Client
12
+ # Run a scan-only pass (regex pre-filter, offline).
13
+ #
14
+ # @param path [String] file or directory to scan
15
+ # @param exclude [Array<String>] glob(s) to skip
16
+ # @param only [Array<String>, String, nil] restrict the scan to these path(s)
17
+ # @param show_fixtures [Boolean] include catalog fixtures in the result
18
+ # @return [Array<Hash>] candidate finding hashes
19
+ # @raise [BinaryInvocationError] on an unexpected exit status
10
20
  def scan(path, exclude: [], only: nil, show_fixtures: false)
11
21
  run(['scan', path, '--format', 'json'] + format_flags(exclude:, only:, show_fixtures:))
12
22
  end
13
23
 
24
+ # Run scan + classify + provider verification.
25
+ #
26
+ # @param path [String] file or directory to scan
27
+ # @param mode [String] verify mode passed to `--verify-mode`
28
+ # @param timeout [Integer] per-verifier timeout in seconds
29
+ # @option opts [Array<String>] :exclude glob(s) to skip
30
+ # @option opts [Array<String>, String] :only restrict the scan to these path(s)
31
+ # @option opts [Boolean] :show_fixtures include catalog fixtures
32
+ # @return [Array<Hash>] findings with verification and verdict filled in
33
+ # @raise [BinaryInvocationError] on an unexpected exit status
14
34
  def verify(path, mode: 'best-effort', timeout: 10, **opts)
15
35
  run(['verify', path, '--format', 'json', '--verify-mode', mode,
16
36
  '--verifier-timeout-secs', timeout.to_s] + format_flags(**opts))
17
37
  end
18
38
 
39
+ # Run scan + classify + rewrite proposal.
40
+ #
41
+ # @param path [String] file or directory to scan
42
+ # @param apply [Boolean] write the rewrites in place when true
43
+ # @param backend [String] rewrite backend (e.g. `env`, `doppler`)
44
+ # @option opts [Array<String>] :exclude glob(s) to skip
45
+ # @option opts [Array<String>, String] :only restrict the scan to these path(s)
46
+ # @return [Array<Hash>] findings, each with a proposed replacement
47
+ # @raise [BinaryInvocationError] on an unexpected exit status
19
48
  def rewrite(path, apply: false, backend: 'env', **opts)
20
49
  args = ['rewrite', path, '--format', 'json', '--backend', backend]
21
50
  args << '--apply' if apply
@@ -24,6 +53,10 @@ module Leakferret
24
53
 
25
54
  private
26
55
 
56
+ # Build the shared `--exclude` / `--only` / `--show-fixtures` flag list.
57
+ #
58
+ # @return [Array<String>] CLI flags
59
+ # @api private
27
60
  def format_flags(exclude: [], only: nil, show_fixtures: false)
28
61
  flags = []
29
62
  Array(exclude).each { |g| flags.push('--exclude', g) }
@@ -32,6 +65,13 @@ module Leakferret
32
65
  flags
33
66
  end
34
67
 
68
+ # Invoke the binary and parse its JSON output. Exit codes 0 (clean) and 1
69
+ # (findings present) are both treated as success; any other status raises.
70
+ #
71
+ # @param args [Array<String>] argv passed to the binary
72
+ # @return [Array<Hash>] the parsed findings (empty array when output is blank)
73
+ # @raise [BinaryInvocationError] if the binary exits with a status other than 0 or 1
74
+ # @api private
35
75
  def run(args)
36
76
  out, err, status = Open3.capture3(Binary.path, *args)
37
77
  unless [0, 1].include?(status.exitstatus)
@@ -1,11 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Leakferret
4
+ # Base class for every error this gem raises. Rescue this to catch them all.
4
5
  class Error < StandardError; end
6
+
7
+ # Raised when the native binary cannot be located or downloaded (no pinned
8
+ # checksum for the platform, a failed download, or a bad `LEAKFERRET_BIN`).
5
9
  class BinaryNotFoundError < Error; end
10
+
11
+ # Raised when the binary runs but exits with an unexpected status (anything
12
+ # other than 0 for a clean run or 1 for findings present).
6
13
  class BinaryInvocationError < Error
7
- attr_reader :exit_status, :stderr
14
+ # @return [Integer] the binary's process exit status
15
+ attr_reader :exit_status
16
+
17
+ # @return [String] captured standard error from the failed invocation
18
+ attr_reader :stderr
8
19
 
20
+ # @param message [String] human-readable error message
21
+ # @param exit_status [Integer] the binary's exit status
22
+ # @param stderr [String] captured standard error
9
23
  def initialize(message, exit_status:, stderr:)
10
24
  super(message)
11
25
  @exit_status = exit_status
@@ -5,9 +5,18 @@ require 'rbconfig'
5
5
  require_relative 'error'
6
6
 
7
7
  module Leakferret
8
+ # Host detection for picking the right release asset. Maps the running
9
+ # Ruby's `RbConfig` host to a Rust target triple and binary name.
10
+ #
11
+ # @api private
8
12
  module Platform
9
13
  module_function
10
14
 
15
+ # The Rust target triple for the current host (e.g.
16
+ # `x86_64-unknown-linux-gnu`).
17
+ #
18
+ # @return [String] the target triple
19
+ # @raise [Error] on an unsupported CPU or OS, or aarch64-linux (no asset yet)
11
20
  def triple
12
21
  cpu = case RbConfig::CONFIG['host_cpu']
13
22
  when /x86_64|amd64|x64/ then 'x86_64'
@@ -29,10 +38,13 @@ module Leakferret
29
38
  end
30
39
  end
31
40
 
41
+ # Executable name for the current OS.
42
+ # @return [String] `"leakferret.exe"` on Windows, otherwise `"leakferret"`
32
43
  def binary_name
33
44
  windows? ? 'leakferret.exe' : 'leakferret'
34
45
  end
35
46
 
47
+ # @return [Integer, nil] truthy when the host OS is Windows
36
48
  def windows?
37
49
  RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/
38
50
  end
@@ -1,11 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Leakferret
4
- # The gem's own version.
5
- VERSION = '0.1.9'
4
+ # The gem's own version (what `gem install leakferret` resolves).
5
+ # @return [String]
6
+ VERSION = '0.1.10'
6
7
 
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).
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).
11
+ # @return [String]
10
12
  BINARY_VERSION = '0.1.6'
11
13
  end
data/lib/leakferret.rb CHANGED
@@ -9,37 +9,91 @@ require 'leakferret/platform'
9
9
  require 'leakferret/binary'
10
10
  require 'leakferret/client'
11
11
 
12
- # Ruby wrapper around the native `leakferret` binary.
12
+ # Ruby wrapper around the native `leakferret` secret scanner.
13
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.
14
+ # leakferret finds hardcoded secrets, confirms which ones are actually live by
15
+ # calling the provider, and rewrites them to read from environment variables.
16
+ # This gem is a thin wrapper: the native binary (written in Rust) is downloaded
17
+ # once per platform on first use and cached, then each call shells out to it
18
+ # and parses the JSON it prints. The full secret value never leaves your
19
+ # machine; every finding carries only a redacted `first4...last4` preview.
20
+ #
21
+ # The three top-level methods mirror the CLI verbs and each return an array of
22
+ # finding hashes.
23
+ #
24
+ # @example Scan the working tree
25
+ # Leakferret.scan(".").each do |f|
26
+ # puts "#{f['path']}:#{f['line']} #{f['pattern']} [#{f['verdict']}]"
27
+ # end
28
+ #
29
+ # @example Fail a script on any live secret
30
+ # exit(1) unless Leakferret.verify(".", mode: "only-verified").empty?
31
+ #
32
+ # @see https://leakferret.com
33
+ # @see https://github.com/leakferrethq/leakferret
17
34
  module Leakferret
18
35
  class << self
19
- # Scan a directory; returns an array of finding hashes.
36
+ # Scan a path for candidate secrets. This is the regex pre-filter only (no
37
+ # classification, no verification): the fastest, fully offline pass.
38
+ #
39
+ # @param path [String] file or directory to scan, relative or absolute
40
+ # @option opts [Array<String>] :exclude glob(s) to skip
41
+ # @option opts [Array<String>, String] :only restrict the scan to these path(s)
42
+ # @option opts [Boolean] :show_fixtures (false) include catalog fixtures in the result
43
+ # @return [Array<Hash>] candidate findings, each with `path`, `line`,
44
+ # `pattern`, `verdict`, and `match_redacted` keys
45
+ # @raise [BinaryInvocationError] if the binary exits with an unexpected status
46
+ # @example
47
+ # Leakferret.scan("app/", exclude: ["**/*_test.rb"])
20
48
  def scan(path = '.', **opts)
21
49
  Client.new.scan(path, **opts)
22
50
  end
23
51
 
24
- # Scan + verify + classify; returns findings with verification +
25
- # verdict filled in.
52
+ # Scan, classify, and verify. Real findings are confirmed live with a
53
+ # harmless API call to the provider (AWS, GitHub, Stripe, and others), so
54
+ # this method makes outbound network requests.
55
+ #
56
+ # @param path [String] file or directory to scan
57
+ # @option opts [String] :mode ("best-effort") verify mode: `none`,
58
+ # `best-effort`, `only-verified`, or `ever-verified`
59
+ # @option opts [Integer] :timeout (10) per-verifier timeout in seconds
60
+ # @option opts [Array<String>] :exclude glob(s) to skip
61
+ # @option opts [Array<String>, String] :only restrict the scan to these path(s)
62
+ # @return [Array<Hash>] findings with `verdict` and `verification` filled in
63
+ # @raise [BinaryInvocationError] if the binary exits with an unexpected status
64
+ # @example Only return secrets confirmed live
65
+ # Leakferret.verify(".", mode: "only-verified")
26
66
  def verify(path = '.', **opts)
27
67
  Client.new.verify(path, **opts)
28
68
  end
29
69
 
30
- # Scan + classify + propose rewrites for REAL findings. Use
31
- # apply: true to write the rewrites in place.
70
+ # Scan, classify, and propose environment-variable rewrites for real
71
+ # findings. Pass `apply: true` to write the rewrites to disk in place.
72
+ #
73
+ # @param path [String] file or directory to scan
74
+ # @param apply [Boolean] when true, edit files in place; otherwise only
75
+ # propose the replacements
76
+ # @option opts [String] :backend ("env") rewrite backend, e.g. `env`, `doppler`
77
+ # @return [Array<Hash>] findings, each with a `replacement` proposal attached
78
+ # @raise [BinaryInvocationError] if the binary exits with an unexpected status
79
+ # @example Apply rewrites and seed Doppler
80
+ # Leakferret.rewrite(".", apply: true, backend: "doppler")
32
81
  def rewrite(path = '.', apply: false, **opts)
33
82
  Client.new.rewrite(path, apply: apply, **opts)
34
83
  end
35
84
 
36
- # Path to the bundled binary. Useful for tooling integration.
85
+ # Absolute path to the native binary, downloading it on first use.
86
+ #
87
+ # @return [String] absolute filesystem path to the `leakferret` executable
88
+ # @raise [BinaryNotFoundError] if the binary is missing and cannot be fetched
37
89
  def binary_path
38
90
  Binary.path
39
91
  end
40
92
 
41
- # Version reported by the bundled binary (Rust) — may differ from
42
- # the gem version during pre-release.
93
+ # Version string reported by the bundled native binary. May differ from
94
+ # {VERSION} (the gem's own version) during pre-release; see {BINARY_VERSION}.
95
+ #
96
+ # @return [String] the binary's `--version` output, stripped
43
97
  def binary_version
44
98
  out, _err, _status = Open3.capture3(binary_path, '--version')
45
99
  out.strip
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: leakferret
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.9
4
+ version: 0.1.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Maria Khan
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2026-06-01 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies: []
13
12
  description: |
14
13
  Context-aware secret scanning for Ruby projects. A thin wrapper around the
@@ -27,6 +26,7 @@ extensions:
27
26
  - ext/leakferret/extconf.rb
28
27
  extra_rdoc_files: []
29
28
  files:
29
+ - ".yardopts"
30
30
  - LICENSE.txt
31
31
  - README.md
32
32
  - exe/leakferret
@@ -44,8 +44,8 @@ metadata:
44
44
  homepage_uri: https://github.com/leakferrethq/leakferret-ruby
45
45
  source_code_uri: https://github.com/leakferrethq/leakferret-ruby
46
46
  changelog_uri: https://github.com/leakferrethq/leakferret-ruby/blob/main/CHANGELOG.md
47
+ documentation_uri: https://rubydoc.info/gems/leakferret
47
48
  rubygems_mfa_required: 'true'
48
- post_install_message:
49
49
  rdoc_options: []
50
50
  require_paths:
51
51
  - lib
@@ -60,8 +60,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
60
60
  - !ruby/object:Gem::Version
61
61
  version: '0'
62
62
  requirements: []
63
- rubygems_version: 3.5.22
64
- signing_key:
63
+ rubygems_version: 3.6.9
65
64
  specification_version: 4
66
65
  summary: Context-aware secret detection (Ruby wrapper for the leakferret binary).
67
66
  test_files: []