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 +4 -4
- data/CHANGELOG.md +22 -0
- data/lib/ez_logs_agent/capturers/database_capturer.rb +65 -10
- data/lib/ez_logs_agent/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b2d2badd73cb46e12e78d54928f6b2cec5f4a39a93f22f6ecbef5b4767ed200a
|
|
4
|
+
data.tar.gz: c654639d447550d6c2e59a99c072a60126b8502cf749a5be65988f6bc80649ae
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
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
|
-
#
|
|
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
|
|
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]
|
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
|
+
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-
|
|
10
|
+
date: 2026-05-17 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: request_store
|