vaultkit 1.4.0 → 1.5.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: e2eb23b9fcb727367f905e2d62f8b9de36e23cf9a00f54e067f92d6caff9b282
4
- data.tar.gz: 8d125c6b810953abd5c0976fe18b2add1c65781431e9a3b2e147d6253fe968bb
3
+ metadata.gz: 5c88e22778a9f5cd606b8b8965c90c65ee9988085900491cc59e752df4fa5284
4
+ data.tar.gz: 0c85e8c597f0c845505a6a92002097b3a27d58dc2654cb41beb520bc9099a0c8
5
5
  SHA512:
6
- metadata.gz: f1780712e8c30407ad5b3319f0df166ba391deff62d79b5e89176867ca4a2d2e8e65b2ec8a4769f28ccd73133bb3e72a87bdf2de3c7ca37ecb79d54ed26ddfba
7
- data.tar.gz: b6891fd707489ac9ad584459e1a89dcc9eefaa8eb1a73e19b5b93666bceb34c4cbd84246a1b67eede91453c9da94010f875611b35d09e4441dd11695ca93cdf1
6
+ metadata.gz: 023b4d4d98941064f67cbd41ddf335c4d2de718b32d68bece7d26db1bcb68e587bb9dcc1b8304300636917864e6dc7ee808057db0dba431c87a1a0102421f0c3
7
+ data.tar.gz: 88cc76038999e9fb6c2bcd5e09130b20c1dfd0407fac44ba33c0ebaf26d7a8c721c7bdd9657ad535980b4ff12ad06b600d4bc978e849c1e6dfa2fdd399576916
@@ -43,11 +43,43 @@ module Vkit
43
43
  puts
44
44
 
45
45
  puts "⚠️ Store this token securely. It cannot be retrieved again."
46
+
47
+ warn_if_tenant_scoped(org) unless token["tenant"]
46
48
  end
47
49
  end
48
50
 
49
51
  private
50
52
 
53
+ WARNING_NAMES = 5
54
+
55
+ # A token with no tenant can't read a tenant-scoped dataset at all:
56
+ # VaultKit answers it as if the dataset didn't exist. Say so when the
57
+ # org has any, so the operator isn't left guessing why.
58
+ def warn_if_tenant_scoped(org)
59
+ names = tenant_scoped_datasets(org)
60
+ return if names.empty?
61
+
62
+ shown = names.first(WARNING_NAMES).join(", ")
63
+ shown += ", and #{names.size - WARNING_NAMES} more" if names.size > WARNING_NAMES
64
+ count = names.size == 1 ? "1 dataset is" : "#{names.size} datasets are"
65
+
66
+ puts
67
+ puts "⚠️ This token has no tenant, and #{count} tenant-scoped in #{org}: #{shown}."
68
+ puts " VaultKit answers its requests for them as if they didn't exist (\"Dataset not found\")."
69
+ puts " To read them, create a token for one tenant with --tenant."
70
+ end
71
+
72
+ # Names of datasets with a tenant column, from the registry export.
73
+ # Nothing if the export can't be read: the token is already issued.
74
+ def tenant_scoped_datasets(org)
75
+ registry = authenticated_client.get("/api/v1/orgs/#{org}/registries/export")
76
+ return [] unless registry.is_a?(Hash)
77
+
78
+ registry.select { |_name, ds| ds.is_a?(Hash) && !ds["tenant_column"].to_s.empty? }.keys.sort
79
+ rescue StandardError
80
+ []
81
+ end
82
+
51
83
  def build_body(options)
52
84
  body = {
53
85
  name: options.fetch(:name),
@@ -121,9 +121,15 @@ module Vkit
121
121
  "allow"
122
122
  end
123
123
 
124
+ # One group stays approver_role, as older control planes read it;
125
+ # several become approver_roles with their approval_mode.
124
126
  def self.extract_approval(p)
125
127
  return unless p.dig("action", "require_approval")
126
- { "approver_role" => p.dig("action", "approver_role") }
128
+
129
+ roles = p.dig("action", "approver_roles")
130
+ return { "approver_role" => p.dig("action", "approver_role") } unless roles
131
+
132
+ { "approver_roles" => roles, "approval_mode" => p.dig("action", "approval_mode") || "all" }
127
133
  end
128
134
 
129
135
  def self.extract_masking(p)
@@ -19,7 +19,10 @@ module Vkit
19
19
  SENSITIVITIES = %w[high medium low unknown].freeze
20
20
 
21
21
  MATCH_KEYS = %w[dataset fields context].freeze
22
- FIELD_KEYS = %w[any all contains sensitivity].freeze
22
+ FIELD_KEYS = %w[any all contains sensitivity usage].freeze
23
+ # row: the query reads the field value by value (or MIN/MAX of it);
24
+ # aggregate: it only counts, sums, averages or groups by it.
25
+ USAGES = %w[row aggregate].freeze
23
26
  CONTEXT_KEYS = %w[environment dataset_region requester_role requester_region requester_clearance time].freeze
24
27
  ENVIRONMENTS = %w[production staging development sandbox].freeze
25
28
  REGIONS = %w[EU US CA APAC].freeze
@@ -86,6 +89,9 @@ module Vkit
86
89
  if fields.key?("sensitivity") && !@sensitivities.include?(fields["sensitivity"].to_s)
87
90
  problems << "sensitivity #{fields['sensitivity']} never occurs. Use one of: #{@sensitivities.to_a.join(', ')}"
88
91
  end
92
+ if fields.key?("usage") && !USAGES.include?(fields["usage"].to_s)
93
+ problems << "match.fields.usage #{fields['usage']} isn't one of: #{USAGES.join(', ')}"
94
+ end
89
95
  problems
90
96
  end
91
97
 
@@ -1,8 +1,8 @@
1
1
  __pack_meta:
2
2
  name: ai_safety
3
- version: "2.0.0"
3
+ version: "2.1.0"
4
4
  layer: 30
5
- description: "Agents need approval for personal, financial and unreviewed data in production, and for high-sensitivity data anywhere."
5
+ description: "Agents need approval for personal and unreviewed data in production, for financial data read row by row in production, and for high-sensitivity data anywhere."
6
6
  dependencies:
7
7
  - starter
8
8
  priority_band:
@@ -1,10 +1,10 @@
1
1
  id: ai_require_approval_agents_sensitive_prod
2
- description: Agents need security approval for personal or financial data in production.
2
+ description: Agents need security approval for personal data in production. Financial data is 04_require_approval_agents_financial_prod.
3
3
  priority: 1200
4
4
 
5
5
  match:
6
6
  fields:
7
- contains: [pii, financial]
7
+ contains: [pii]
8
8
 
9
9
  context:
10
10
  environment: production
@@ -13,5 +13,5 @@ context:
13
13
  action:
14
14
  require_approval: true
15
15
  approver_role: security
16
- reason: Agents reading personal or financial data in production need security review.
16
+ reason: Agents reading personal data in production need security review.
17
17
  ttl: 1h
@@ -0,0 +1,23 @@
1
+ id: ai_require_approval_agents_financial_prod
2
+ description: >-
3
+ Agents need a person's review to read financial data row by row in production,
4
+ from security or a finance manager. With financial_compliance installed, the
5
+ finance manager it already asks for meets this too, so one sign-off covers both.
6
+ Counts, sums, averages and grouping need no approval.
7
+ priority: 1250
8
+
9
+ match:
10
+ fields:
11
+ contains: [financial]
12
+ usage: row
13
+
14
+ context:
15
+ environment: production
16
+ requester_role: agent
17
+
18
+ action:
19
+ require_approval: true
20
+ approver_roles: [security, finance_manager]
21
+ approval_mode: any
22
+ reason: Agents reading financial data row by row in production need review by security or a finance manager.
23
+ ttl: 1h
@@ -1,8 +1,8 @@
1
1
  __pack_meta:
2
2
  name: financial_compliance
3
- version: "2.0.0"
3
+ version: "2.1.0"
4
4
  layer: 40
5
- description: "Finance approval for financial data in production, and short-lived grants for financial fields."
5
+ description: "Finance approval for reading financial data row by row in production (totals and averages need none), and short-lived grants for financial fields."
6
6
  dependencies:
7
7
  - starter
8
8
  priority_band:
@@ -1,10 +1,11 @@
1
1
  id: fin_require_approval_financial_prod
2
- description: A finance manager approves requests for financial data in production.
2
+ description: A finance manager approves reading financial data row by row in production. Counts, sums, averages and grouping need no approval; MIN and MAX return a single value, so they do.
3
3
  priority: 2100
4
4
 
5
5
  match:
6
6
  fields:
7
7
  contains: [financial]
8
+ usage: row
8
9
 
9
10
  context:
10
11
  environment: production
@@ -12,5 +13,5 @@ context:
12
13
  action:
13
14
  require_approval: true
14
15
  approver_role: finance_manager
15
- reason: Financial data in production needs finance approval.
16
+ reason: Reading financial data row by row in production needs finance approval.
16
17
  ttl: 30m
@@ -80,7 +80,7 @@ module Vkit
80
80
 
81
81
  when "require_approval"
82
82
  require_reason!(action, prefix)
83
- require_string!(action, "approver_role", prefix)
83
+ validate_approvers!(action, prefix)
84
84
 
85
85
  when "mask"
86
86
  # masking is optional, but if present must be valid
@@ -93,6 +93,59 @@ module Vkit
93
93
  validate_ttl!(action["ttl"], prefix) if action.key?("ttl")
94
94
  end
95
95
 
96
+ APPROVAL_MODES = %w[all any].freeze
97
+
98
+ # One group (approver_role), or several (approver_roles) with
99
+ # approval_mode: all (the default, every group signs off) or any (one
100
+ # of them does).
101
+ def self.validate_approvers!(action, prefix)
102
+ single = action["approver_role"]
103
+ several = action["approver_roles"]
104
+
105
+ if single && several
106
+ raise ValidationError, <<~MSG
107
+ #{prefix}Set approver_role or approver_roles, not both.
108
+ MSG
109
+ end
110
+
111
+ if several.nil?
112
+ require_string!(action, "approver_role", prefix)
113
+ if action.key?("approval_mode")
114
+ raise ValidationError, <<~MSG
115
+ #{prefix}approval_mode only applies to approver_roles (several groups).
116
+ MSG
117
+ end
118
+ return
119
+ end
120
+
121
+ unless several.is_a?(Array) && several.any? && several.all? { |r| r.is_a?(String) && !r.strip.empty? }
122
+ raise ValidationError, <<~MSG
123
+ #{prefix}approver_roles must be a list of approver group names.
124
+
125
+ Example:
126
+
127
+ action:
128
+ require_approval: true
129
+ approver_roles: [security, data_owner]
130
+ approval_mode: any
131
+ MSG
132
+ end
133
+
134
+ if several.uniq.size != several.size
135
+ raise ValidationError, "#{prefix}approver_roles lists a group more than once.\n"
136
+ end
137
+
138
+ mode = action.fetch("approval_mode", "all")
139
+ return if APPROVAL_MODES.include?(mode.to_s)
140
+
141
+ raise ValidationError, <<~MSG
142
+ #{prefix}approval_mode must be all (every group signs off) or any (one of them does).
143
+
144
+ Got:
145
+ approval_mode: #{mode.inspect}
146
+ MSG
147
+ end
148
+
96
149
  def self.require_reason!(action, prefix)
97
150
  reason = action["reason"]
98
151
  return if reason.is_a?(String) && !reason.strip.empty?
@@ -214,7 +214,8 @@
214
214
  "contains": {
215
215
  "type": "array",
216
216
  "items": { "type": "string" }
217
- }
217
+ },
218
+ "usage": { "type": "string", "enum": ["row", "aggregate"] }
218
219
  }
219
220
  },
220
221
  "context": {
@@ -293,7 +294,14 @@
293
294
  "type": "object",
294
295
  "additionalProperties": false,
295
296
  "properties": {
296
- "approver_role": { "type": "string" }
297
+ "approver_role": { "type": "string" },
298
+ "approver_roles": {
299
+ "type": "array",
300
+ "minItems": 1,
301
+ "uniqueItems": true,
302
+ "items": { "type": "string", "minLength": 1 }
303
+ },
304
+ "approval_mode": { "type": "string", "enum": ["all", "any"] }
297
305
  }
298
306
  },
299
307
 
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.4.0"
4
+ VERSION = "1.5.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.4.0
4
+ version: 1.5.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-11-07 00:00:00.000000000 Z
11
+ date: 2026-11-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -101,6 +101,7 @@ files:
101
101
  - lib/vkit/policy/packs/ai_safety/policies/01_require_approval_agents_sensitive_prod.yaml
102
102
  - lib/vkit/policy/packs/ai_safety/policies/02_require_approval_agents_high_sensitivity.yaml
103
103
  - lib/vkit/policy/packs/ai_safety/policies/03_require_approval_agents_unknown_prod.yaml
104
+ - lib/vkit/policy/packs/ai_safety/policies/04_require_approval_agents_financial_prod.yaml
104
105
  - lib/vkit/policy/packs/financial_compliance/metadata.yaml
105
106
  - lib/vkit/policy/packs/financial_compliance/policies/01_require_approval_financial_prod.yaml
106
107
  - lib/vkit/policy/packs/financial_compliance/policies/02_short_ttl_for_financial_grants.yaml