ez_logs_agent 0.1.4 → 0.1.5

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: 3edfe639cbf53d13c7b902ae2795687878c6898affaac8cd1b2d4610d9e357ae
4
- data.tar.gz: ceef7660ed54a8693e8090d03acae9bdc524b4c6993c810f2fda5507a0e691e3
3
+ metadata.gz: b2d2badd73cb46e12e78d54928f6b2cec5f4a39a93f22f6ecbef5b4767ed200a
4
+ data.tar.gz: c654639d447550d6c2e59a99c072a60126b8502cf749a5be65988f6bc80649ae
5
5
  SHA512:
6
- metadata.gz: db1c93abdea22ac379936498fee9c9d873f676741e704267898890ad83324dd0f2b26cc0e0c12a0713f6e8ed4b1e0f7535baab8650b17b088c666a45947a18b8
7
- data.tar.gz: 50d06b7f6035e01032f4d6066ed9477304851d7273ad25c352719c66be5e6f1ba0f641aad8e8710df754f4bed6b8e205524ae424037af16756af7f9f8297778b
6
+ metadata.gz: 703b6dba6216a3f34db5ce0038fda0e19e0a2cd0aa6de07cea95bcb974bdb3856488dc92601376ac7070d18920032b8e8ea941c998d2dc7835e903fa14d72c94
7
+ data.tar.gz: 4c44195f6539f48933c7949b936f988738657d44728dc6406cf43b270958b05a107f5cec5ac6b8de3b7265c5ff2dbbf07fe276c99dea1f1f1662a1f61eabd05a
data/CHANGELOG.md CHANGED
@@ -2,6 +2,28 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [0.1.5] — 2026-05-17 — security release
6
+
7
+ ### Security
8
+ - `DatabaseCapturer` no longer captures columns the host app declared
9
+ `encrypts :foo` on. Rails 7+ decrypts attributes in memory before
10
+ `saved_changes` fires, so without this guard the plaintext of every
11
+ encrypted column was landing on the wire and in the EZLogs UI on
12
+ every create / update. The new policy is declarative: at capture
13
+ time we read `record.class.encrypted_attributes` (Rails 7+) and drop
14
+ every name in that set, regardless of column name. If the host app
15
+ encrypted it, we never capture it. Upgrade is strongly recommended
16
+ for any deployment whose models use `encrypts`. Customers running
17
+ 0.1.4 or earlier should also scrub historical events for the
18
+ affected column names — the data leaked in the past will stay in
19
+ the event store until masked.
20
+ - `SENSITIVE_PATTERNS` (the secondary name-based denylist) now also
21
+ matches `private_key`, `public_key`, `signing_key`, `pem`, `cipher`,
22
+ `nonce`, `salt`, `digest`, `signature`, `hmac`. Belt-and-suspenders
23
+ for columns that carry sensitive material but weren't declared
24
+ `encrypts` (legacy code, manual hashing, externally-generated
25
+ material).
26
+
5
27
  ## [0.1.4] — 2026-05-17
6
28
 
7
29
  ### Fixed
@@ -60,7 +60,16 @@ module EzLogsAgent
60
60
  # Previously we filtered them out, but this loses important context.
61
61
  # FOREIGN_KEY_PATTERN = /_id\z/ # Removed January 2026
62
62
 
63
- # Patterns for sensitive data to ignore
63
+ # Patterns for sensitive data to ignore.
64
+ #
65
+ # The first source of truth is `record.class.encrypted_attributes`
66
+ # (Rails 7+ `encrypts :foo` declaration) — see encrypted_attribute?.
67
+ # If the host app encrypted it, we never capture it.
68
+ #
69
+ # This list is the secondary defense: column names that frequently
70
+ # carry sensitive material even when the host app didn't declare
71
+ # `encrypts` (legacy code, manual hashing, externally-generated
72
+ # material). Matching is substring + case-insensitive.
64
73
  SENSITIVE_PATTERNS = %w[
65
74
  password
66
75
  token
@@ -70,6 +79,16 @@ module EzLogsAgent
70
79
  ssn
71
80
  social_security
72
81
  encrypted
82
+ private_key
83
+ public_key
84
+ signing_key
85
+ pem
86
+ cipher
87
+ nonce
88
+ salt
89
+ digest
90
+ signature
91
+ hmac
73
92
  ].freeze
74
93
 
75
94
 
@@ -276,8 +295,9 @@ module EzLogsAgent
276
295
  changes = model.saved_changes
277
296
  return nil if changes.nil? || changes.empty?
278
297
 
279
- # Find meaningful changes
280
- meaningful_changes = filter_meaningful_changes(changes)
298
+ # Find meaningful changes (excludes encrypted columns + sensitive
299
+ # name patterns — see meaningful_attribute? / encrypted_attribute?)
300
+ meaningful_changes = filter_meaningful_changes(changes, model)
281
301
  return nil if meaningful_changes.empty?
282
302
 
283
303
  # Build context with all meaningful changes
@@ -305,7 +325,7 @@ module EzLogsAgent
305
325
 
306
326
  # Filter to meaningful, non-nil scalar attributes
307
327
  meaningful_attrs = attributes.select do |attribute, value|
308
- meaningful_attribute?(attribute) &&
328
+ meaningful_attribute?(attribute, model) &&
309
329
  scalar?(value) &&
310
330
  !value.nil?
311
331
  end
@@ -324,20 +344,27 @@ module EzLogsAgent
324
344
  # Filters changes to only meaningful business attributes
325
345
  #
326
346
  # @param changes [Hash] The saved_changes hash
347
+ # @param model [ActiveRecord::Base] The model instance (used to
348
+ # consult `record.class.encrypted_attributes` so columns declared
349
+ # `encrypts :foo` are never captured, regardless of their name).
327
350
  # @return [Array<Array>] Array of [attribute, [from, to]] pairs
328
- def filter_meaningful_changes(changes)
351
+ def filter_meaningful_changes(changes, model)
329
352
  changes.select do |attribute, (from, to)|
330
- meaningful_attribute?(attribute) &&
353
+ meaningful_attribute?(attribute, model) &&
331
354
  scalar_values?(from, to) &&
332
355
  values_actually_changed?(from, to)
333
356
  end.to_a
334
357
  end
335
358
 
336
- # Checks if an attribute is meaningful (not technical/ignored)
359
+ # Checks if an attribute is meaningful (not technical/ignored).
337
360
  #
338
361
  # @param attribute [String] The attribute name
362
+ # @param model [ActiveRecord::Base, nil] The model instance — when
363
+ # supplied, columns declared `encrypts :foo` on the model class
364
+ # are dropped regardless of name. Authoritative drop: if the host
365
+ # app encrypted the column, we never capture it.
339
366
  # @return [Boolean]
340
- def meaningful_attribute?(attribute)
367
+ def meaningful_attribute?(attribute, model = nil)
341
368
  attr_str = attribute.to_s
342
369
 
343
370
  # Skip explicitly ignored attributes
@@ -347,13 +374,41 @@ module EzLogsAgent
347
374
  # relationship changes (e.g., assigned_to_id changing from user A to user B)
348
375
  # Previously filtered via FOREIGN_KEY_PATTERN - removed January 2026
349
376
 
350
- # Skip sensitive data
377
+ # Authoritative: drop anything the host app declared `encrypts` on.
378
+ # Rails decrypts at the attribute layer before saved_changes fires,
379
+ # so without this check the plaintext would land on the wire.
380
+ return false if model && encrypted_attribute?(attr_str, model)
381
+
382
+ # Skip name-pattern-sensitive data (legacy / non-encrypts paths).
351
383
  return false if sensitive_attribute?(attr_str)
352
384
 
353
385
  true
354
386
  end
355
387
 
356
- # Checks if attribute name contains sensitive patterns
388
+ # Checks whether the host app declared `encrypts :<attribute>` on
389
+ # this model's class. Available since Rails 7.0 via
390
+ # ActiveRecord::Encryption::EncryptableRecord#encrypted_attributes.
391
+ #
392
+ # Safe across host Rails versions: returns false if the API isn't
393
+ # present (older Rails, non-AR records).
394
+ #
395
+ # @param attribute [String] The attribute name (already to_s'd)
396
+ # @param model [ActiveRecord::Base] The model instance
397
+ # @return [Boolean]
398
+ def encrypted_attribute?(attribute, model)
399
+ klass = model.class
400
+ return false unless klass.respond_to?(:encrypted_attributes)
401
+
402
+ encrypted = klass.encrypted_attributes
403
+ return false if encrypted.nil? || encrypted.empty?
404
+
405
+ encrypted.map(&:to_s).include?(attribute)
406
+ rescue StandardError
407
+ false
408
+ end
409
+
410
+ # Checks if attribute name contains sensitive patterns.
411
+ # Secondary check — see SENSITIVE_PATTERNS comment.
357
412
  #
358
413
  # @param attribute [String] The attribute name
359
414
  # @return [Boolean]
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module EzLogsAgent
4
- VERSION = "0.1.4"
4
+ VERSION = "0.1.5"
5
5
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ez_logs_agent
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - dezsirazvan
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-05-16 00:00:00.000000000 Z
10
+ date: 2026-05-17 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: request_store