vaultkit 1.2.2 → 1.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 992511b9fff48dc39ffa343776721e9c24077c00cfdbe97b9f98e0f1454f4093
4
- data.tar.gz: bcc02c8d21f7d0e66d1b30fd16ef2080aedf8271ac7159d775f05bf1e6db15d1
3
+ metadata.gz: 17014258d6102f66c5d3479d541fbc2540f5f4b988c87c46c0bf8a7d17340043
4
+ data.tar.gz: 83c4694cc2efd7bc579c2fbc29bf2f556fd22539ffae1a23f52ce741b55f126e
5
5
  SHA512:
6
- metadata.gz: dd8c2496c757661ade482f3ac01bf68ab1d073903db14dec88d9a4d4d40721beb9ea36e8942deb4f9296b3f6e6f49014d1724b8bbfb3d67d351ed886024629bf
7
- data.tar.gz: 2881996c7f00bb58a92a62d00dea1408ab2ea42c0e0d3acc2fb0f6fc782ac27aa6eac943b458ddcd5dee416debb8714b0f285a7f6954db9d2c9e00290f2206a7
6
+ metadata.gz: 452fad9cc3ed91570302342f4e319c26cf62c3446bb008f225719f01487731de5f953046ce67b8dbe0b33e6cad6406fa36d1b6e35cb1092a12bb7826e8999fa0
7
+ data.tar.gz: c6ec74cacd3cc839ff3160413c86513f5f3a0d8a036f6a6bb938bfc56bc473215583dd9e63414c05c83710ba1c2d6d9a8c70dccd57fde6760e9f125058f6d34e
data/README.md CHANGED
@@ -43,7 +43,6 @@ The VaultKit CLI provides command-line access to VaultKit for:
43
43
  | **Datasource Admin** | Register and manage database connections (admin only) |
44
44
  | **Schema Discovery** | Scan datasources and detect schema drift |
45
45
  | **Policy Deployment** | Build, validate, and deploy policy bundles |
46
- | **Audit Queries** | Search and export audit logs |
47
46
 
48
47
  ### Mental Model
49
48
 
@@ -266,13 +265,17 @@ vkit request --aql '{
266
265
  }'
267
266
 
268
267
  # Output:
269
- # ✓ Request granted immediately
270
- # Grant ID: gr_abc123xyz
271
- # TTL: 4 hours
272
- # Use: vkit fetch --grant gr_abc123xyz
268
+ # ✅ ACCESS GRANTED
269
+ # Grant Ref: g_public_reports_3f9a
270
+ # Request ID: 8d1c0a2e-… (look it up under Activity in the console)
271
+ # Expires: 2024-01-15 18:00:00 UTC
272
+ # Masked Fields: none
273
+ #
274
+ # To execute and retrieve the data, run:
275
+ # vkit fetch --grant g_public_reports_3f9a
273
276
 
274
277
  # 2. Fetch data
275
- vkit fetch --grant gr_abc123xyz --format table
278
+ vkit fetch --grant g_public_reports_3f9a --format table
276
279
  ```
277
280
 
278
281
  ### Workflow 2: Approval Required
@@ -286,17 +289,17 @@ vkit request --aql '{
286
289
  }'
287
290
 
288
291
  # Output:
289
- # ⏳ Approval required
290
- # Request ID: req_20240115_xyz789
291
- # Approver: finance_manager
292
+ # ⏳ QUEUED for approval
293
+ # Request ID: 3b7e19c4-… (look it up under Activity in the console)
294
+ # Approver roles: finance_manager
292
295
  # Reason: Financial data requires manager approval
293
- # Status: PENDING
296
+ #
297
+ # Once it's approved, run the same request again to get the data.
294
298
 
295
299
  # 2. Check status
296
300
  vkit requests list --state pending
297
301
 
298
- # 3. After approval, fetch data
299
- vkit fetch --grant gr_approved_abc123
302
+ # 3. After approval, run the same request again; it returns the rows
300
303
  ```
301
304
 
302
305
  ### Workflow 3: Denied Access
@@ -309,11 +312,9 @@ vkit request --aql '{
309
312
  }'
310
313
 
311
314
  # Output:
312
- # ❌ Request denied
315
+ # ❌ DENIED
313
316
  # Reason: Direct payment card data access forbidden
314
- # Policy: pci_dss_restrictions
315
- # Alternative: Use tokenized_card_id field instead
316
- # Audit ID: audit_20240115_denied_123
317
+ # Request ID: 7f29e0aa-… (look it up under Activity in the console)
317
318
  ```
318
319
 
319
320
  ---
@@ -869,60 +870,71 @@ vkit policy show <POLICY_ID>
869
870
 
870
871
  ---
871
872
 
872
- ### Audit Commands
873
+ ### Agent Token Commands (Admin Only)
873
874
 
874
- #### `vkit audit query`
875
+ Agents (an MCP server, a scheduled job, a coding assistant) authenticate with
876
+ an agent token instead of a login. An agent is every token with the same
877
+ `--name`; the console's Agents page groups them that way.
875
878
 
876
- Search audit logs.
879
+ #### `vkit agents tokens create`
877
880
 
878
881
  ```bash
879
- vkit audit query [OPTIONS]
882
+ vkit agents tokens create --name NAME [OPTIONS]
880
883
  ```
881
884
 
882
885
  **Options:**
883
- - `--user` — Filter by user email
884
- - `--action` — Filter by action: `granted`, `denied`, `pending`
885
- - `--dataset` — Filter by dataset name
886
- - `--days` — Look back N days (default: 7)
887
- - `--limit` — Number of results (default: 100)
888
- - `--format` — Output format: `json`, `table`, `csv`
886
+ - `--name` — The agent's name (e.g. `incident-bot`); tokens with the same name are one agent
887
+ - `--label` — What this token is for, shown next to it in the console (e.g. `"PagerDuty runner"`)
888
+ - `--for` — Email of the person the agent acts for. Their Activity shows the agent's requests, so a member can follow what an agent does on their behalf. Must be an active member of your organization. Without it the agent acts for no one, and only admins see its requests.
889
+ - `--expires-in` — Lifetime: `1h`, `24h`, `30d` (default: no expiry)
890
+ - `--role`, `--clearance`, `--region`, `--tenant` — What policies see for this token
891
+
892
+ The token is printed once; store it straight away. The console never shows it.
889
893
 
890
- **Examples:**
891
894
  ```bash
892
- # All queries by a user
893
- vkit audit query --user analyst@company.com --days 30
895
+ vkit agents tokens create --name incident-bot --label "PagerDuty runner" \
896
+ --for sam@acme.test --expires-in 30d
897
+ ```
894
898
 
895
- # Denied access attempts
896
- vkit audit query --action denied --days 7
899
+ `--for` names who the agent acts for. It's separate from who created the
900
+ token (you), which the console shows as "Created by".
897
901
 
898
- # Access to specific dataset
899
- vkit audit query --dataset customers --action granted
902
+ #### `vkit agents tokens list`
900
903
 
901
- # Export to CSV
902
- vkit audit query --user analyst@company.com --format csv --output audit_report.csv
904
+ ```bash
905
+ vkit agents tokens list [--agent NAME] [--format table|json]
903
906
  ```
904
907
 
905
- #### `vkit audit export`
906
-
907
- Export audit logs for compliance reporting.
908
+ #### `vkit agents tokens revoke`
908
909
 
909
910
  ```bash
910
- vkit audit export --start-date <DATE> --end-date <DATE> [OPTIONS]
911
+ vkit agents tokens revoke --token PREFIX_OR_ID --reason "Leaked in CI logs"
911
912
  ```
912
913
 
913
- **Options:**
914
- - `--start-date` — Start date (YYYY-MM-DD)
915
- - `--end-date` — End date (YYYY-MM-DD)
916
- - `--format` — Output format: `json`, `csv` (default: `csv`)
917
- - `--output` — Output file path
914
+ `--reason` is required and recorded in the audit log. The token stops working
915
+ at once: its live grants are revoked, and a request already in flight with it
916
+ gets no rows.
917
+
918
+ `--for` needs a control plane with the console's Agents page; an older one
919
+ ignores it, and the token acts for no one. That control plane also refuses a
920
+ revoke without a reason, so older CLI versions can't revoke against it.
921
+
922
+ ### Audit Log
923
+
924
+ There's no `vkit audit` command. Every request, decision and revoke is in the
925
+ audit log; read it in the console:
926
+
927
+ - **Activity** lists every request: who asked, what was decided and why, and
928
+ what ran. Filter by decision, agent, person, dataset or time, and open one
929
+ by the request ID that `vkit request` prints.
930
+ - **Export CSV** on the Activity page downloads the filtered view.
931
+ - A refused request, for example one made with a revoked agent token, prints
932
+ its code and request ID too:
918
933
 
919
- **Example:**
920
934
  ```bash
921
- vkit audit export \
922
- --start-date 2024-01-01 \
923
- --end-date 2024-01-31 \
924
- --format csv \
925
- --output compliance_reports/january_2024.csv
935
+ # API 401: Agent token vkit_agent_f5f5d9 was revoked at 2026-11-04T19:55:11Z: Leaked in CI logs
936
+ # Code: token_revoked
937
+ # Request ID: 7973fcba-… (look it up under Activity in the console)
926
938
  ```
927
939
 
928
940
  ---
@@ -1082,9 +1094,7 @@ docker compose up
1082
1094
 
1083
1095
  **Problem:**
1084
1096
  ```
1085
- Error: Grant has expired
1086
- Grant ID: gr_abc123xyz
1087
- Expired at: 2024-01-15 12:00:00 UTC
1097
+ API 403: Grant expired
1088
1098
  ```
1089
1099
 
1090
1100
  **Solutions:**
@@ -83,7 +83,7 @@ module Vkit
83
83
 
84
84
  when 403
85
85
  if body.is_a?(Hash) && body.key?("error")
86
- raise APIError.new(code, body["error"])
86
+ raise APIError.from_body(code, body)
87
87
  else
88
88
  # Valid domain response (e.g. policy denied)
89
89
  body
@@ -93,21 +93,39 @@ module Vkit
93
93
  # Queued for approval
94
94
  body
95
95
 
96
- when 422
97
- raise APIError.new(code, body["error"] || body)
98
-
99
96
  else
100
- raise APIError.new(code, body["error"] || body)
97
+ raise APIError.from_body(code, body)
101
98
  end
102
99
  end
103
100
  end
104
101
 
102
+ # The control plane's { error, code, request_id }. code says why in a
103
+ # word (token_revoked, token_expired), and request_id finds the request
104
+ # on the console's Activity page.
105
105
  class APIError < StandardError
106
- attr_reader :status
106
+ attr_reader :status, :error, :code, :request_id
107
+
108
+ def self.from_body(status, body)
109
+ return new(status, body) unless body.is_a?(Hash)
110
+
111
+ new(status, body["error"] || body, code: body["code"], request_id: body["request_id"])
112
+ end
107
113
 
108
- def initialize(status, message)
114
+ def initialize(status, error, code: nil, request_id: nil)
109
115
  @status = status
110
- super("API #{status}: #{message}")
116
+ @error = error
117
+ @code = code
118
+ @request_id = request_id
119
+ super(describe)
120
+ end
121
+
122
+ private
123
+
124
+ def describe
125
+ lines = ["API #{status}: #{error}"]
126
+ lines << "Code: #{code}" if code
127
+ lines << "Request ID: #{request_id} (look it up under Activity in the console)" if request_id
128
+ lines.join("\n")
111
129
  end
112
130
  end
113
131
  end
@@ -115,7 +115,7 @@ module Vkit
115
115
 
116
116
  desc "grant:revoke", "Revoke a grant"
117
117
  option :grant, type: :string, required: true
118
- option :reason, type: :string
118
+ option :reason, type: :string, required: true, desc: "Why it's revoked; recorded in the audit log"
119
119
  option :force, type: :boolean, default: false
120
120
  define_method("grant:revoke") do
121
121
  Commands::GrantRevokeCommand.new.call(
@@ -335,9 +335,14 @@ module Vkit
335
335
  option :tenant, type: :string, desc: "Tenant this token acts for on tenant-scoped datasets"
336
336
  option :clearance, type: :string, desc: "Clearance policies see: low, medium or high"
337
337
  option :region, type: :string, desc: "Region policies see: EU, US, CA or APAC"
338
+ option :label, type: :string, desc: "What this token is for, shown in the console (e.g. \"PagerDuty runner\")"
339
+ option :for, type: :string, banner: "EMAIL",
340
+ desc: "Email of the person the agent acts for; they see its requests in Activity"
338
341
  def create
339
342
  Commands::AgentTokensCreateCommand.new.call(
340
343
  name: options[:name],
344
+ label: options[:label],
345
+ for: options[:for],
341
346
  expires_in: options[:expires_in],
342
347
  role: options[:role],
343
348
  tenant: options[:tenant],
@@ -349,10 +354,12 @@ module Vkit
349
354
  # agents tokens revoke
350
355
  desc "revoke", "Revoke an agent token"
351
356
  option :token, required: true, desc: "Token ID or prefix"
357
+ option :reason, type: :string, required: true, desc: "Why it's revoked; recorded in the audit log"
352
358
  option :force, type: :boolean, default: false, desc: "Skip confirmation"
353
359
  def revoke
354
360
  Commands::AgentTokensRevokeCommand.new.call(
355
361
  token: options[:token],
362
+ reason: options[:reason],
356
363
  force: options[:force]
357
364
  )
358
365
  end
@@ -25,6 +25,8 @@ module Vkit
25
25
  puts
26
26
 
27
27
  puts "🧠 Name: #{token["name"]}"
28
+ puts "📝 Label: #{token["label"]}" if token["label"]
29
+ puts "👤 Acts for: #{acts_for(token)}"
28
30
  puts "🎭 Role: #{token["role"]}"
29
31
  puts "🏢 Tenant: #{token["tenant"]}" if token["tenant"]
30
32
  puts "🛡 Clearance: #{token["clearance"]}" if token["clearance"]
@@ -49,6 +51,9 @@ module Vkit
49
51
  def build_body(options)
50
52
  body = {
51
53
  name: options.fetch(:name),
54
+ label: options[:label],
55
+ # Resolved to a member of your organization by the control plane.
56
+ user_email: options[:for],
52
57
  role: options[:role],
53
58
  tenant: options[:tenant],
54
59
  clearance: options[:clearance],
@@ -62,6 +67,13 @@ module Vkit
62
67
  body
63
68
  end
64
69
 
70
+ def acts_for(token)
71
+ person = token["acts_for"]
72
+ return "no one (its requests show only to admins)" unless person
73
+
74
+ person["name"] == person["email"] ? person["email"] : "#{person["name"]} <#{person["email"]}>"
75
+ end
76
+
65
77
  def parse_expires_in(value)
66
78
  number = value.to_i
67
79
 
@@ -42,6 +42,8 @@ module Vkit
42
42
  "ID" => t["id"],
43
43
  "Agent" => t["name"],
44
44
  "Prefix" => t["prefix"],
45
+ "Label" => t["label"] || "-",
46
+ "Acts For" => t.dig("acts_for", "email") || "-",
45
47
  "Expires At" => t["expires_at"] || "never",
46
48
  "Revoked At" => t["revoked_at"] || "-",
47
49
  "Created At" => t["created_at"]
@@ -8,6 +8,9 @@ module Vkit
8
8
  class AgentTokensRevokeCommand < BaseCommand
9
9
  def call(options)
10
10
  token_ref = options.fetch(:token)
11
+ reason = options[:reason].to_s.strip
12
+ # The control plane refuses a revoke without one.
13
+ raise "A reason is required (--reason)" if reason.empty?
11
14
 
12
15
  with_auth do
13
16
  user = credential_store.user
@@ -21,7 +24,7 @@ module Vkit
21
24
 
22
25
  authenticated_client.post(
23
26
  "/api/v1/orgs/#{org}/agent_tokens/#{token["id"]}/revoke",
24
- body: {}
27
+ body: { reason: reason }
25
28
  )
26
29
 
27
30
  puts "🛑 AGENT TOKEN REVOKED"
@@ -29,8 +32,9 @@ module Vkit
29
32
  puts "🧠 Name: #{token["name"]}"
30
33
  puts "🏷 Prefix: #{token["prefix"]}"
31
34
  puts "🆔 ID: #{token["id"]}"
35
+ puts "📝 Reason: #{reason}"
32
36
  puts
33
- puts "✅ The token is no longer valid."
37
+ puts "✅ The token is no longer valid, and its grants are revoked."
34
38
  end
35
39
  end
36
40
 
@@ -61,7 +61,7 @@ module Vkit
61
61
  puts " The grant is issued once every role has approved."
62
62
  else
63
63
  puts "✅ Approved request #{id}"
64
- puts " → Grant Ref: #{res["grant_ref"] || res["grant_id"]}"
64
+ puts " → Grant Ref: #{res["grant_ref"]}"
65
65
  puts " → Expires: #{res["expires_at"]}"
66
66
  end
67
67
  end
@@ -5,6 +5,10 @@ module Vkit
5
5
  module Commands
6
6
  class GrantRevokeCommand < BaseCommand
7
7
  def call(grant_ref:, reason:, force:)
8
+ reason = reason.to_s.strip
9
+ # The control plane refuses a revoke without one.
10
+ raise "A reason is required (--reason)" if reason.empty?
11
+
8
12
  with_auth do
9
13
  user = credential_store.user
10
14
  org = user["organization_slug"]
@@ -14,7 +18,7 @@ module Vkit
14
18
  unless force
15
19
  puts "⚠️ You are about to revoke grant: #{grant_ref}"
16
20
  puts " Organization: #{org}"
17
- puts " Reason: #{reason || 'Grant revoked via CLI'}"
21
+ puts " Reason: #{reason}"
18
22
  print "Continue? (y/N): "
19
23
  answer = STDIN.gets.strip
20
24
  return puts("Aborted.") unless answer.downcase == "y"
@@ -43,9 +43,17 @@ module Vkit
43
43
 
44
44
  private
45
45
 
46
+ # Every response names its request; the console's Activity page
47
+ # finds it by this id.
48
+ def print_request_id(result)
49
+ return if result["request_id"].to_s.empty?
50
+
51
+ puts "Request ID: #{result["request_id"]} (look it up under Activity in the console)"
52
+ end
53
+
46
54
  def print_queued(result)
47
55
  puts "⏳ QUEUED for approval"
48
- puts "Request ID: #{result["request_id"]}"
56
+ print_request_id(result)
49
57
 
50
58
  roles = ApprovalRoles.required_roles(result)
51
59
  puts "Approver roles: #{roles.join(', ')}" if roles.any?
@@ -67,6 +75,7 @@ module Vkit
67
75
  when "denied"
68
76
  puts "❌ DENIED"
69
77
  puts "Reason: #{result["reason"]}"
78
+ print_request_id(result)
70
79
  exit 2
71
80
 
72
81
  # The control plane answers "pending_approval"; "queued" is what
@@ -79,7 +88,8 @@ module Vkit
79
88
  expires_at = Time.parse(result["expires_at"]).getlocal
80
89
 
81
90
  puts "✅ ACCESS GRANTED"
82
- puts "Grant ID: #{result["grant_id"]}"
91
+ puts "Grant Ref: #{result["grant_ref"]}"
92
+ print_request_id(result)
83
93
  puts "Expires: #{expires_at.strftime("%Y-%m-%d %H:%M:%S %Z")}"
84
94
 
85
95
  if (mask = result["masked_fields"]) && mask.any?
@@ -90,7 +100,7 @@ module Vkit
90
100
 
91
101
  puts
92
102
  puts "To execute and retrieve the data, run:"
93
- puts " vkit fetch --grant #{result["grant_ref"] || result["grant_id"]}"
103
+ puts " vkit fetch --grant #{result["grant_ref"]}"
94
104
  exit 0
95
105
 
96
106
  when "ok"
@@ -102,6 +112,8 @@ module Vkit
102
112
  end
103
113
 
104
114
  puts "✅ OK — #{rows.size} rows"
115
+ puts "Grant Ref: #{result["grant_ref"]} (reused)" if result["grant_ref"]
116
+ print_request_id(result)
105
117
 
106
118
  if meta.any?
107
119
  puts "ℹ️ Query Metadata:"
@@ -116,6 +128,7 @@ module Vkit
116
128
  else
117
129
  puts "❓ Unexpected response:"
118
130
  puts result.inspect
131
+ print_request_id(result) if result.is_a?(Hash)
119
132
  exit 1
120
133
  end
121
134
  end
data/lib/vkit/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Vkit
4
- VERSION = "1.2.2"
4
+ VERSION = "1.3.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vaultkit
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.2
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nnamdi Ogundu
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-10-30 00:00:00.000000000 Z
11
+ date: 2026-11-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor