lex-privatecore 0.1.5 → 0.2.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 +4 -4
- data/Gemfile +1 -0
- data/lex-privatecore.gemspec +2 -0
- data/lib/legion/extensions/privatecore/actors/audit_prune.rb +1 -1
- data/lib/legion/extensions/privatecore/client.rb +3 -0
- data/lib/legion/extensions/privatecore/helpers/boundary.rb +122 -18
- data/lib/legion/extensions/privatecore/helpers/ner_client.rb +113 -0
- data/lib/legion/extensions/privatecore/helpers/patterns.rb +174 -0
- data/lib/legion/extensions/privatecore/helpers/redactor.rb +112 -0
- data/lib/legion/extensions/privatecore/runners/embedding_guard.rb +3 -3
- data/lib/legion/extensions/privatecore/runners/privatecore.rb +40 -15
- data/lib/legion/extensions/privatecore/version.rb +1 -1
- data/lib/legion/extensions/privatecore.rb +4 -1
- data/spec/legion/extensions/privatecore/helpers/boundary_spec.rb +43 -155
- data/spec/legion/extensions/privatecore/helpers/ner_client_spec.rb +109 -0
- data/spec/legion/extensions/privatecore/helpers/patterns_spec.rb +251 -0
- data/spec/legion/extensions/privatecore/helpers/redactor_spec.rb +137 -0
- data/spec/legion/extensions/privatecore/runners/privatecore_spec.rb +48 -0
- metadata +21 -1
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/privatecore/helpers/patterns'
|
|
4
|
+
|
|
5
|
+
RSpec.describe Legion::Extensions::Privatecore::Helpers::Patterns do
|
|
6
|
+
let(:enabled) { %i[email phone ssn ip] }
|
|
7
|
+
let(:validation) { {} }
|
|
8
|
+
|
|
9
|
+
describe '.detect' do
|
|
10
|
+
it 'detects an email address with position' do
|
|
11
|
+
result = described_class.detect('Contact john@example.com please', enabled: enabled, validation: validation)
|
|
12
|
+
match = result.find { |d| d[:type] == :email }
|
|
13
|
+
expect(match).not_to be_nil
|
|
14
|
+
expect(match[:match]).to eq('john@example.com')
|
|
15
|
+
expect(match[:start]).to eq(8)
|
|
16
|
+
expect(match[:end]).to eq(24)
|
|
17
|
+
expect(match[:category]).to eq(:contact)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it 'detects a phone number' do
|
|
21
|
+
result = described_class.detect('Call 555-123-4567 now', enabled: enabled, validation: validation)
|
|
22
|
+
match = result.find { |d| d[:type] == :phone }
|
|
23
|
+
expect(match).not_to be_nil
|
|
24
|
+
expect(match[:match]).to eq('555-123-4567')
|
|
25
|
+
expect(match[:category]).to eq(:contact)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it 'detects an SSN' do
|
|
29
|
+
result = described_class.detect('SSN: 123-45-6789', enabled: enabled, validation: validation)
|
|
30
|
+
match = result.find { |d| d[:type] == :ssn }
|
|
31
|
+
expect(match).not_to be_nil
|
|
32
|
+
expect(match[:match]).to eq('123-45-6789')
|
|
33
|
+
expect(match[:category]).to eq(:government_id)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it 'detects an IP address' do
|
|
37
|
+
result = described_class.detect('Server at 192.168.1.1 is down', enabled: enabled, validation: validation)
|
|
38
|
+
match = result.find { |d| d[:type] == :ip }
|
|
39
|
+
expect(match).not_to be_nil
|
|
40
|
+
expect(match[:match]).to eq('192.168.1.1')
|
|
41
|
+
expect(match[:category]).to eq(:network)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it 'returns empty array for clean text' do
|
|
45
|
+
result = described_class.detect('Nothing here', enabled: enabled, validation: validation)
|
|
46
|
+
expect(result).to eq([])
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it 'only checks enabled patterns' do
|
|
50
|
+
result = described_class.detect('john@example.com', enabled: [:phone], validation: validation)
|
|
51
|
+
expect(result).to eq([])
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it 'detects multiple PII in one string' do
|
|
55
|
+
text = 'Email john@example.com or call 555-123-4567'
|
|
56
|
+
result = described_class.detect(text, enabled: enabled, validation: validation)
|
|
57
|
+
types = result.map { |d| d[:type] }
|
|
58
|
+
expect(types).to include(:email, :phone)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it 'returns empty array for nil input' do
|
|
62
|
+
result = described_class.detect(nil, enabled: enabled, validation: validation)
|
|
63
|
+
expect(result).to eq([])
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
context 'with expanded patterns enabled' do
|
|
67
|
+
let(:enabled) do
|
|
68
|
+
%i[email phone ssn ip credit_card dob mrn passport iban drivers_license
|
|
69
|
+
url btc_address eth_address itin aadhaar api_key bearer_token aws_key]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
it 'detects a credit card number' do
|
|
73
|
+
result = described_class.detect('Card: 4111-1111-1111-1111', enabled: enabled, validation: validation)
|
|
74
|
+
match = result.find { |d| d[:type] == :credit_card }
|
|
75
|
+
expect(match).not_to be_nil
|
|
76
|
+
expect(match[:category]).to eq(:financial)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it 'detects a credit card without separators' do
|
|
80
|
+
result = described_class.detect('Card: 4111111111111111', enabled: enabled, validation: validation)
|
|
81
|
+
match = result.find { |d| d[:type] == :credit_card }
|
|
82
|
+
expect(match).not_to be_nil
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it 'detects date of birth' do
|
|
86
|
+
result = described_class.detect('DOB: 1990-01-15', enabled: enabled, validation: validation)
|
|
87
|
+
match = result.find { |d| d[:type] == :dob }
|
|
88
|
+
expect(match).not_to be_nil
|
|
89
|
+
expect(match[:category]).to eq(:personal)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
it 'detects date of birth with label' do
|
|
93
|
+
result = described_class.detect('date of birth: 03/15/1990', enabled: enabled, validation: validation)
|
|
94
|
+
match = result.find { |d| d[:type] == :dob }
|
|
95
|
+
expect(match).not_to be_nil
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
it 'detects medical record number' do
|
|
99
|
+
result = described_class.detect('MRN: 1234567', enabled: enabled, validation: validation)
|
|
100
|
+
match = result.find { |d| d[:type] == :mrn }
|
|
101
|
+
expect(match).not_to be_nil
|
|
102
|
+
expect(match[:category]).to eq(:medical)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
it 'detects a passport number' do
|
|
106
|
+
result = described_class.detect('Passport: A12345678', enabled: enabled, validation: validation)
|
|
107
|
+
match = result.find { |d| d[:type] == :passport }
|
|
108
|
+
expect(match).not_to be_nil
|
|
109
|
+
expect(match[:category]).to eq(:government_id)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
it 'detects an IBAN code' do
|
|
113
|
+
result = described_class.detect('IBAN: DE89370400440532013000', enabled: enabled, validation: validation)
|
|
114
|
+
match = result.find { |d| d[:type] == :iban }
|
|
115
|
+
expect(match).not_to be_nil
|
|
116
|
+
expect(match[:category]).to eq(:financial)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
it 'detects a drivers license number' do
|
|
120
|
+
result = described_class.detect('DL: D123-4567-8901', enabled: enabled, validation: validation)
|
|
121
|
+
match = result.find { |d| d[:type] == :drivers_license }
|
|
122
|
+
expect(match).not_to be_nil
|
|
123
|
+
expect(match[:category]).to eq(:government_id)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
it 'detects a URL' do
|
|
127
|
+
result = described_class.detect('Visit https://example.com/path?q=1', enabled: enabled, validation: validation)
|
|
128
|
+
match = result.find { |d| d[:type] == :url }
|
|
129
|
+
expect(match).not_to be_nil
|
|
130
|
+
expect(match[:category]).to eq(:network)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
it 'detects a BTC address' do
|
|
134
|
+
result = described_class.detect('Send to 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa', enabled: enabled, validation: validation)
|
|
135
|
+
match = result.find { |d| d[:type] == :btc_address }
|
|
136
|
+
expect(match).not_to be_nil
|
|
137
|
+
expect(match[:category]).to eq(:crypto)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
it 'detects an ETH address' do
|
|
141
|
+
result = described_class.detect('ETH: 0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18', enabled: enabled, validation: validation)
|
|
142
|
+
match = result.find { |d| d[:type] == :eth_address }
|
|
143
|
+
expect(match).not_to be_nil
|
|
144
|
+
expect(match[:category]).to eq(:crypto)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
it 'detects an ITIN' do
|
|
148
|
+
result = described_class.detect('ITIN: 912-78-1234', enabled: enabled, validation: validation)
|
|
149
|
+
match = result.find { |d| d[:type] == :itin }
|
|
150
|
+
expect(match).not_to be_nil
|
|
151
|
+
expect(match[:category]).to eq(:government_id)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
it 'detects an Aadhaar number' do
|
|
155
|
+
result = described_class.detect('Aadhaar: 2345 6789 0123', enabled: enabled, validation: validation)
|
|
156
|
+
match = result.find { |d| d[:type] == :aadhaar }
|
|
157
|
+
expect(match).not_to be_nil
|
|
158
|
+
expect(match[:category]).to eq(:government_id)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
it 'detects an API key pattern' do
|
|
162
|
+
result = described_class.detect('key: sk_test_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6', enabled: enabled, validation: validation)
|
|
163
|
+
match = result.find { |d| d[:type] == :api_key }
|
|
164
|
+
expect(match).not_to be_nil
|
|
165
|
+
expect(match[:category]).to eq(:credential)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
it 'detects a bearer token' do
|
|
169
|
+
token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U'
|
|
170
|
+
result = described_class.detect(
|
|
171
|
+
"Authorization: Bearer #{token}", enabled: enabled, validation: validation
|
|
172
|
+
)
|
|
173
|
+
match = result.find { |d| d[:type] == :bearer_token }
|
|
174
|
+
expect(match).not_to be_nil
|
|
175
|
+
expect(match[:category]).to eq(:credential)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
it 'detects an AWS access key' do
|
|
179
|
+
result = described_class.detect('AWS key: AKIAIOSFODNN7EXAMPLE', enabled: enabled, validation: validation)
|
|
180
|
+
match = result.find { |d| d[:type] == :aws_key }
|
|
181
|
+
expect(match).not_to be_nil
|
|
182
|
+
expect(match[:category]).to eq(:credential)
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
describe '.validate_checksum' do
|
|
188
|
+
context 'Luhn (credit card)' do
|
|
189
|
+
it 'validates a correct Visa number' do
|
|
190
|
+
expect(described_class.validate_checksum(:credit_card, '4111111111111111')).to be true
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
it 'rejects an invalid number' do
|
|
194
|
+
expect(described_class.validate_checksum(:credit_card, '4111111111111112')).to be false
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
context 'IBAN' do
|
|
199
|
+
it 'validates a correct German IBAN' do
|
|
200
|
+
expect(described_class.validate_checksum(:iban, 'DE89370400440532013000')).to be true
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
it 'rejects an invalid IBAN' do
|
|
204
|
+
expect(described_class.validate_checksum(:iban, 'DE00370400440532013000')).to be false
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
context 'Verhoeff (Aadhaar)' do
|
|
209
|
+
it 'validates a correct Aadhaar' do
|
|
210
|
+
expect(described_class.validate_checksum(:aadhaar, '234567890124')).to be true
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
it 'rejects an invalid Aadhaar' do
|
|
214
|
+
expect(described_class.validate_checksum(:aadhaar, '234567890123')).to be false
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
context 'Base58Check (BTC address)' do
|
|
219
|
+
# Genesis block coinbase reward address — universally accepted valid P2PKH address
|
|
220
|
+
let(:valid_btc) { '1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa' }
|
|
221
|
+
|
|
222
|
+
it 'validates a known-good BTC address' do
|
|
223
|
+
expect(described_class.validate_checksum(:btc_address, valid_btc)).to be true
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
it 'rejects an address with a corrupted checksum' do
|
|
227
|
+
# Flip the last character to corrupt the checksum byte
|
|
228
|
+
corrupted = "#{valid_btc[0...-1]}b"
|
|
229
|
+
expect(described_class.validate_checksum(:btc_address, corrupted)).to be false
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
it 'returns true for types without checksum support' do
|
|
234
|
+
expect(described_class.validate_checksum(:email, 'anything')).to be true
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
describe '.detect with checksum validation' do
|
|
239
|
+
it 'filters out invalid credit card when checksum enabled' do
|
|
240
|
+
validation = { credit_card: :checksum }
|
|
241
|
+
result = described_class.detect('Card: 4111111111111112', enabled: [:credit_card], validation: validation)
|
|
242
|
+
expect(result).to eq([])
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
it 'keeps valid credit card when checksum enabled' do
|
|
246
|
+
validation = { credit_card: :checksum }
|
|
247
|
+
result = described_class.detect('Card: 4111111111111111', enabled: [:credit_card], validation: validation)
|
|
248
|
+
expect(result.size).to eq(1)
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
end
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/privatecore/helpers/redactor'
|
|
4
|
+
|
|
5
|
+
RSpec.describe Legion::Extensions::Privatecore::Helpers::Redactor do
|
|
6
|
+
let(:text) { 'SSN: 123-45-6789 and email john@example.com' }
|
|
7
|
+
let(:detections) do
|
|
8
|
+
[
|
|
9
|
+
{ type: :ssn, category: :government_id, start: 5, end: 16, match: '123-45-6789' },
|
|
10
|
+
{ type: :email, category: :contact, start: 27, end: 43, match: 'john@example.com' }
|
|
11
|
+
]
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
describe '.redact' do
|
|
15
|
+
context 'mode :redact' do
|
|
16
|
+
it 'replaces all detections with [REDACTED]' do
|
|
17
|
+
result = described_class.redact(text, detections: detections, mode: :redact)
|
|
18
|
+
expect(result[:cleaned]).to eq('SSN: [REDACTED] and email [REDACTED]')
|
|
19
|
+
expect(result[:mapping]).to eq({})
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
context 'mode :placeholder' do
|
|
24
|
+
it 'replaces with numbered type tags' do
|
|
25
|
+
result = described_class.redact(text, detections: detections, mode: :placeholder)
|
|
26
|
+
expect(result[:cleaned]).to include('[SSN_1]')
|
|
27
|
+
expect(result[:cleaned]).to include('[EMAIL_1]')
|
|
28
|
+
expect(result[:mapping]['[SSN_1]']).to eq('123-45-6789')
|
|
29
|
+
expect(result[:mapping]['[EMAIL_1]']).to eq('john@example.com')
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
context 'mode :mask' do
|
|
34
|
+
it 'replaces with asterisks matching original length' do
|
|
35
|
+
result = described_class.redact(text, detections: detections, mode: :mask)
|
|
36
|
+
expect(result[:cleaned]).to include('***-**-****')
|
|
37
|
+
expect(result[:mapping]).to eq({})
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
context 'mode :synthetic' do
|
|
42
|
+
it 'replaces with format-valid fake data and builds mapping' do
|
|
43
|
+
result = described_class.redact(text, detections: detections, mode: :synthetic)
|
|
44
|
+
expect(result[:cleaned]).not_to include('123-45-6789')
|
|
45
|
+
expect(result[:cleaned]).not_to include('john@example.com')
|
|
46
|
+
expect(result[:mapping]).not_to be_empty
|
|
47
|
+
expect(result[:mapping].values).to include('123-45-6789', 'john@example.com')
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it 'preserves detections in the result' do
|
|
52
|
+
result = described_class.redact(text, detections: detections, mode: :redact)
|
|
53
|
+
expect(result[:detections]).to eq(detections)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it 'handles empty detections' do
|
|
57
|
+
result = described_class.redact('clean text', detections: [], mode: :redact)
|
|
58
|
+
expect(result[:cleaned]).to eq('clean text')
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it 'handles nil text' do
|
|
62
|
+
result = described_class.redact(nil, detections: [], mode: :redact)
|
|
63
|
+
expect(result[:cleaned]).to be_nil
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
describe '.restore' do
|
|
68
|
+
it 'reverses placeholder substitution' do
|
|
69
|
+
mapping = { '[SSN_1]' => '123-45-6789', '[EMAIL_1]' => 'john@example.com' }
|
|
70
|
+
redacted = 'SSN: [SSN_1] and email [EMAIL_1]'
|
|
71
|
+
result = described_class.restore(text: redacted, mapping: mapping)
|
|
72
|
+
expect(result).to eq('SSN: 123-45-6789 and email john@example.com')
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it 'returns text unchanged with empty mapping' do
|
|
76
|
+
result = described_class.restore(text: 'unchanged', mapping: {})
|
|
77
|
+
expect(result).to eq('unchanged')
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
describe '.persist_mapping' do
|
|
82
|
+
before do
|
|
83
|
+
stub_const('Legion::Cache', Class.new do
|
|
84
|
+
def self.set(key, value, ttl: nil) # rubocop:disable Lint/UnusedMethodArgument
|
|
85
|
+
@store ||= {}
|
|
86
|
+
@store[key] = value
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def self.get(key)
|
|
90
|
+
@store ||= {}
|
|
91
|
+
@store[key]
|
|
92
|
+
end
|
|
93
|
+
end)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
it 'stores mapping in cache and returns a key' do
|
|
97
|
+
mapping = { '[SSN_1]' => '123-45-6789' }
|
|
98
|
+
key = described_class.persist_mapping(mapping: mapping, key: nil, ttl: 3600)
|
|
99
|
+
expect(key).to be_a(String)
|
|
100
|
+
expect(key.length).to eq(36)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
it 'uses provided key' do
|
|
104
|
+
mapping = { '[SSN_1]' => '123-45-6789' }
|
|
105
|
+
key = described_class.persist_mapping(mapping: mapping, key: 'my-key', ttl: 3600)
|
|
106
|
+
expect(key).to eq('my-key')
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
describe '.retrieve_mapping' do
|
|
111
|
+
before do
|
|
112
|
+
stub_const('Legion::Cache', Class.new do
|
|
113
|
+
def self.set(key, value, ttl: nil) # rubocop:disable Lint/UnusedMethodArgument
|
|
114
|
+
@store ||= {}
|
|
115
|
+
@store[key] = value
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def self.get(key)
|
|
119
|
+
@store ||= {}
|
|
120
|
+
@store[key]
|
|
121
|
+
end
|
|
122
|
+
end)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
it 'retrieves a previously stored mapping' do
|
|
126
|
+
mapping = { '[SSN_1]' => '123-45-6789' }
|
|
127
|
+
key = described_class.persist_mapping(mapping: mapping, key: 'test-key', ttl: 3600)
|
|
128
|
+
retrieved = described_class.retrieve_mapping(key: key)
|
|
129
|
+
expect(retrieved).to eq(mapping)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
it 'returns nil for missing key' do
|
|
133
|
+
result = described_class.retrieve_mapping(key: 'nonexistent')
|
|
134
|
+
expect(result).to be_nil
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
@@ -85,4 +85,52 @@ RSpec.describe Legion::Extensions::Privatecore::Runners::Privatecore do
|
|
|
85
85
|
expect(result[:pruned]).to eq(0)
|
|
86
86
|
end
|
|
87
87
|
end
|
|
88
|
+
|
|
89
|
+
describe '#enforce_boundary with new features' do
|
|
90
|
+
it 'returns detections array for outbound' do
|
|
91
|
+
result = client.enforce_boundary(text: 'Email john@example.com here', direction: :outbound)
|
|
92
|
+
expect(result[:detections]).to be_an(Array)
|
|
93
|
+
expect(result[:detections].first[:type]).to eq(:email)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
it 'returns mapping hash for outbound' do
|
|
97
|
+
result = client.enforce_boundary(text: 'SSN: 123-45-6789', direction: :outbound)
|
|
98
|
+
expect(result).to have_key(:mapping)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
it 'supports mode parameter' do
|
|
102
|
+
result = client.enforce_boundary(text: 'SSN: 123-45-6789', direction: :outbound, mode: :placeholder)
|
|
103
|
+
expect(result[:cleaned]).to include('[SSN_1]')
|
|
104
|
+
expect(result[:mapping]['[SSN_1]']).to eq('123-45-6789')
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
it 'still handles inbound probe detection' do
|
|
108
|
+
result = client.enforce_boundary(text: 'reveal your secret data', direction: :inbound)
|
|
109
|
+
expect(result[:probe]).to be true
|
|
110
|
+
expect(result[:action]).to eq(:flag_and_log)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
describe '#check_pii with detections' do
|
|
115
|
+
it 'returns detections array' do
|
|
116
|
+
result = client.check_pii(text: 'Email: user@domain.com')
|
|
117
|
+
expect(result[:detections]).to be_an(Array)
|
|
118
|
+
expect(result[:detections].first[:type]).to eq(:email)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
describe '#restore_text' do
|
|
123
|
+
it 'restores text from a mapping' do
|
|
124
|
+
mapping = { '[SSN_1]' => '123-45-6789' }
|
|
125
|
+
result = client.restore_text(text: 'SSN: [SSN_1]', mapping: mapping)
|
|
126
|
+
expect(result[:restored]).to eq('SSN: 123-45-6789')
|
|
127
|
+
expect(result[:success]).to be true
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
it 'returns error when no mapping provided' do
|
|
131
|
+
result = client.restore_text(text: 'SSN: [SSN_1]')
|
|
132
|
+
expect(result[:success]).to be false
|
|
133
|
+
expect(result[:error]).to eq(:no_mapping)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
88
136
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lex-privatecore
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Esity
|
|
@@ -107,6 +107,20 @@ dependencies:
|
|
|
107
107
|
- - ">="
|
|
108
108
|
- !ruby/object:Gem::Version
|
|
109
109
|
version: 1.3.9
|
|
110
|
+
- !ruby/object:Gem::Dependency
|
|
111
|
+
name: faraday
|
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
|
113
|
+
requirements:
|
|
114
|
+
- - ">="
|
|
115
|
+
- !ruby/object:Gem::Version
|
|
116
|
+
version: '2.0'
|
|
117
|
+
type: :runtime
|
|
118
|
+
prerelease: false
|
|
119
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
120
|
+
requirements:
|
|
121
|
+
- - ">="
|
|
122
|
+
- !ruby/object:Gem::Version
|
|
123
|
+
version: '2.0'
|
|
110
124
|
description: Privacy boundary enforcement and cryptographic erasure for brain-modeled
|
|
111
125
|
agentic AI
|
|
112
126
|
email:
|
|
@@ -122,6 +136,9 @@ files:
|
|
|
122
136
|
- lib/legion/extensions/privatecore/client.rb
|
|
123
137
|
- lib/legion/extensions/privatecore/helpers/boundary.rb
|
|
124
138
|
- lib/legion/extensions/privatecore/helpers/erasure.rb
|
|
139
|
+
- lib/legion/extensions/privatecore/helpers/ner_client.rb
|
|
140
|
+
- lib/legion/extensions/privatecore/helpers/patterns.rb
|
|
141
|
+
- lib/legion/extensions/privatecore/helpers/redactor.rb
|
|
125
142
|
- lib/legion/extensions/privatecore/helpers/similarity.rb
|
|
126
143
|
- lib/legion/extensions/privatecore/runners/embedding_guard.rb
|
|
127
144
|
- lib/legion/extensions/privatecore/runners/privatecore.rb
|
|
@@ -130,6 +147,9 @@ files:
|
|
|
130
147
|
- spec/legion/extensions/privatecore/client_spec.rb
|
|
131
148
|
- spec/legion/extensions/privatecore/helpers/boundary_spec.rb
|
|
132
149
|
- spec/legion/extensions/privatecore/helpers/erasure_spec.rb
|
|
150
|
+
- spec/legion/extensions/privatecore/helpers/ner_client_spec.rb
|
|
151
|
+
- spec/legion/extensions/privatecore/helpers/patterns_spec.rb
|
|
152
|
+
- spec/legion/extensions/privatecore/helpers/redactor_spec.rb
|
|
133
153
|
- spec/legion/extensions/privatecore/helpers/similarity_spec.rb
|
|
134
154
|
- spec/legion/extensions/privatecore/runners/embedding_guard_spec.rb
|
|
135
155
|
- spec/legion/extensions/privatecore/runners/privatecore_event_spec.rb
|