localvault 1.9.0 → 1.9.2

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: f49d7a925b997780d6d03a2b071117e20da0827d24331d28c9cd5043a70b0056
4
- data.tar.gz: 27b2be6fa5bb90d3df68eec4a27fcba483a175b574afd96f7aeba7dd0e175412
3
+ metadata.gz: f52091ddaad3306c188919a18acc4cae01aff5d0582e3ca07426b6ab3dede07e
4
+ data.tar.gz: 4db7f05666bbb3644b436561e82bcbd51b2b8402618885469ec0534abed60302
5
5
  SHA512:
6
- metadata.gz: 7863c066d383db0a2ad4a98ae9665fe1fafc04de61b1fdbfec590d4af0ea04e47f31f1635a4c63d5886f0f71780a5f9f0ebd143348af23d404415991487dd214
7
- data.tar.gz: b61cb7b5096ad2250560a1462f87ecfd81e538ed823971c46fe4820be3bb50443574055e40d0e080c7f4e5f7b7ce93d2d2f9cceedd03ea4f39a9a13141f35c44
6
+ metadata.gz: 76a6c6007b74d7287673345f0e11b08cc4dca663381b04e6e23e1917d84c92a45f0f9f809be73d028492617b905a66968b0c98791ab0a2772980b94f0e9bcf58
7
+ data.tar.gz: 10751a27f1d8bdce859045e678af61a968a18fe65679dc015a60056502ede9fb4de414439f234ca550da3721f0bd63aaa3e3b93895676b653036befd29d0a348
data/README.md CHANGED
@@ -16,12 +16,29 @@ Part of [InventList Tools](https://inventlist.com/tools/localvault) — free, op
16
16
  brew install inventlist/tap/localvault
17
17
  ```
18
18
 
19
+ ### Install script (no Homebrew)
20
+
21
+ ```bash
22
+ curl -sSL https://raw.githubusercontent.com/inventlist/localvault/main/install.sh | sh
23
+ ```
24
+
25
+ Installs into an isolated prefix (`~/.localvault/runtime`) and writes a wrapper
26
+ to your PATH that pins its own Ruby. It never adds a version-manager shim and
27
+ never touches your project gems, so `localvault` keeps working when you switch
28
+ Ruby versions. Override with `LOCALVAULT_BIN_DIR`, `LOCALVAULT_PREFIX`,
29
+ `LOCALVAULT_VERSION`, or `LOCALVAULT_RUBY`.
30
+
19
31
  ### RubyGems
20
32
 
21
33
  ```bash
22
34
  gem install localvault
23
35
  ```
24
36
 
37
+ A plain `gem install` under asdf/rbenv creates a shim that can shadow your
38
+ Homebrew build and silently pin you to an old version. If `localvault version`
39
+ disagrees with what you installed, run `localvault doctor` — it lists every
40
+ copy on your PATH.
41
+
25
42
  **Requires libsodium:**
26
43
 
27
44
  ```bash
@@ -71,11 +88,11 @@ localvault exec -- rails server
71
88
  | `init [NAME]` | Create a vault (Argon2id key derivation) |
72
89
  | `set KEY --stdin` | Store a secret from stdin without argv/history exposure |
73
90
  | `set KEY [VALUE]` | Store a secret (positional value kept for compatibility) |
74
- | `set --group GROUP KEY --stdin` | Store a secret in a named group from stdin |
91
+ | `set --group GROUP KEY --stdin` | Store a secret in a named group from stdin (`-g`) |
75
92
  | `get KEY` | Retrieve a secret (raw, pipeable) |
76
93
  | `show` | Display all secrets in a table (masked by default) |
77
- | `show --reveal` | Display with values visible |
78
- | `show --group` | Group by dot-notation prefix (one table per project) |
94
+ | `show --reveal` | Display with values visible (`-r`) |
95
+ | `show --group` | Group by dot-notation prefix, one table per project (`-g`) |
79
96
  | `show --group QUERY` | Show one exact or uniquely matching group |
80
97
  | `groups [QUERY]` | List/search group names and key counts without values |
81
98
  | `list` | List key names only |
@@ -64,8 +64,9 @@ module LocalVault
64
64
  # missing PATH binary never breaks the user's sessions — but it also
65
65
  # silently guards nothing, which deserves a loud note at install time.
66
66
  def warn_if_path_binary_lacks_guard
67
- help_output = `localvault help 2>/dev/null`
68
- return if help_output.include?("guard")
67
+ # Probe the PATH binary itself: `guard status` exits 0 only where the
68
+ # command exists. Parsing curated help text is not a version check.
69
+ return if system("localvault guard status", out: File::NULL, err: File::NULL)
69
70
 
70
71
  $stderr.puts "Note: the `localvault` on your PATH does not support `guard hook` " \
71
72
  "(old version or different install). The hook fails open and guards " \
@@ -174,6 +174,8 @@ module LocalVault
174
174
  shell.say " localvault mcp Start MCP server (stdio)"
175
175
  shell.say " localvault mcp --check Check setup and active-vault readiness"
176
176
  shell.say " Agents: list names, then use localvault_build_exec for safe injection"
177
+ shell.say " localvault guard install Block secrets in agent commands (Claude Code hooks)"
178
+ shell.say " localvault guard status Show guard hook installation state"
177
179
  shell.say ""
178
180
  shell.say "LEGACY SHARING (pre-v1.2 direct share, still works as fallback)"
179
181
  shell.say " localvault keygen Generate X25519 keypair (same as `keys generate`)"
@@ -241,7 +243,7 @@ module LocalVault
241
243
  Use `localvault show -p platepose -v vault` to view a single project.
242
244
  Use `localvault import` to bulk-load from a .env, .json, or .yml file.
243
245
  DESC
244
- method_option :group, type: :string, desc: "Store KEY and VALUE inside this named group"
246
+ method_option :group, aliases: "-g", type: :string, desc: "Store KEY and VALUE inside this named group"
245
247
  method_option :stdin, type: :boolean, default: false, desc: "Read VALUE from stdin instead of argv"
246
248
  def set(key, value = nil)
247
249
  validate_secret_value_source!(value)
@@ -524,8 +526,8 @@ module LocalVault
524
526
  \x05 localvault show -p platepose -v intellectaco # one project only
525
527
  \x05 localvault show -p platepose -v intellectaco --reveal
526
528
  DESC
527
- method_option :group, type: :string, lazy_default: GROUP_ALL_SENTINEL, desc: "Show all groups or one group by name"
528
- method_option :reveal, type: :boolean, default: false, desc: "Show full values instead of masking"
529
+ method_option :group, aliases: "-g", type: :string, lazy_default: GROUP_ALL_SENTINEL, desc: "Show all groups or one group by name"
530
+ method_option :reveal, aliases: "-r", type: :boolean, default: false, desc: "Show full values instead of masking"
529
531
  method_option :project, aliases: "-p", type: :string, desc: "Show only this project group"
530
532
  def show
531
533
  vault = open_vault!
@@ -1,4 +1,5 @@
1
1
  require "json"
2
+ require "yaml"
2
3
  require "digest"
3
4
  require_relative "store"
4
5
  require_relative "session_cache"
@@ -15,6 +16,44 @@ module LocalVault
15
16
  # check gets uninstalled.
16
17
  module Guard
17
18
  MIN_VALUE_LENGTH = 8
19
+
20
+ # Keys the operator has explicitly declared non-secret, from
21
+ # ~/.localvault/config.yml:
22
+ #
23
+ # guard_ignore:
24
+ # - CLOUDFLARE_ASSETS.r2_bucket
25
+ #
26
+ # WHY EXPLICIT, AND NOT INFERRED FROM THE KEY NAME.
27
+ # The first attempt exempted keys whose name *looked* public — anything
28
+ # ending in bucket / region / _id — with a veto list of credential-ish
29
+ # words. An audit killed it in one line: a value stored under `AWS.bucket`
30
+ # would be exempt regardless of what it actually contained, so the fix
31
+ # traded a false positive for a false NEGATIVE in a security tool. The veto
32
+ # list was also unbounded (pem, jwk, seed, mnemonic, salt, otp, bearer,
33
+ # sas, …) and every omission is a silent bypass.
34
+ #
35
+ # A key's name cannot describe its value. Only the operator can say "this
36
+ # one is public", so only the operator may — by naming the exact full key,
37
+ # in a file they control, which is greppable and auditable.
38
+ #
39
+ # The problem being solved is still real: a public identifier stored beside
40
+ # real secrets (a bucket name that happens to be a substring of a project
41
+ # path) denies every command mentioning that path, and a guard that cries
42
+ # wolf gets uninstalled, which protects nothing. The answer is an explicit
43
+ # allowlist, not a clever one.
44
+ def self.ignored_keys(config_path = default_config_path)
45
+ return [] unless File.exist?(config_path)
46
+
47
+ raw = YAML.safe_load(File.read(config_path)) || {}
48
+ Array(raw["guard_ignore"]).map(&:to_s)
49
+ rescue StandardError
50
+ [] # an unreadable config must never widen the exemption
51
+ end
52
+
53
+ def self.default_config_path
54
+ File.join(Dir.home, ".localvault", "config.yml")
55
+ end
56
+
18
57
  HOOK_ENTRYPOINT = "localvault guard hook".freeze
19
58
  # The installed command must fail open on machines where the binary is
20
59
  # old, missing, or broken — otherwise every Bash call errors for users
@@ -55,12 +94,17 @@ module LocalVault
55
94
  end
56
95
 
57
96
  # @return [Array<Match>] stored secret values appearing in +text+
58
- def self.scan(text, secrets = unlocked_secrets)
97
+ def self.scan(text, secrets = unlocked_secrets, ignore: ignored_keys)
59
98
  return [] if text.nil? || text.empty?
60
99
 
100
+ ignored = Array(ignore).map(&:to_s)
101
+
61
102
  secrets.filter_map do |entry|
62
103
  value = entry[:value]
63
104
  next if value.nil? || value.length < MIN_VALUE_LENGTH
105
+ # Exact full-key match only. No prefixes, no suffix inference — an
106
+ # operator opting one key out must never silently opt out its siblings.
107
+ next if ignored.include?(entry[:key].to_s)
64
108
  next unless text.include?(value)
65
109
 
66
110
  Match.new(vault: entry[:vault], key: entry[:key], fingerprint: fingerprint(value))
@@ -1,3 +1,3 @@
1
1
  module LocalVault
2
- VERSION = "1.9.0"
2
+ VERSION = "1.9.2"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: localvault
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.9.0
4
+ version: 1.9.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nauman Tariq