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 +4 -4
- data/README.md +20 -3
- data/lib/localvault/cli/guard.rb +3 -2
- data/lib/localvault/cli.rb +5 -3
- data/lib/localvault/guard.rb +45 -1
- data/lib/localvault/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f52091ddaad3306c188919a18acc4cae01aff5d0582e3ca07426b6ab3dede07e
|
|
4
|
+
data.tar.gz: 4db7f05666bbb3644b436561e82bcbd51b2b8402618885469ec0534abed60302
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
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 |
|
data/lib/localvault/cli/guard.rb
CHANGED
|
@@ -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
|
-
|
|
68
|
-
|
|
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 " \
|
data/lib/localvault/cli.rb
CHANGED
|
@@ -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,
|
|
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!
|
data/lib/localvault/guard.rb
CHANGED
|
@@ -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))
|
data/lib/localvault/version.rb
CHANGED