lex-audit 0.1.5 → 0.1.6
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 +10 -0
- data/lex-audit.gemspec +0 -1
- data/lib/legion/extensions/audit/errors.rb +13 -0
- data/lib/legion/extensions/audit/helpers/verified_write.rb +111 -0
- data/lib/legion/extensions/audit/version.rb +1 -1
- data/lib/legion/extensions/audit.rb +2 -0
- metadata +3 -15
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 531cdf796a189cb155d128d61af3fd72128d859d780ca21cfe40f50336f08ce4
|
|
4
|
+
data.tar.gz: 9bfbf2fa44d6ccb8c864f5b348b0d8bbc9f5f237dafefb65e12752111a077f54
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4b760a57e8ecd4462d38541552e486034b0fa0c39320a078a38e1d60cea940cc9ae3f5214dc661ba169a5b412ef0f8ad64fb044ef939bc48794aded44a548df1
|
|
7
|
+
data.tar.gz: 1ed526ca7d9fad3b2ff541e0fc791ecb50bfc3ca37452339ac699b730065dbb091ff9b19a6ce11c5dd85cb9b8e2e721219758dce2140988476c75653098e7d32
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.1.6] - 2026-03-31
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- `Helpers::VerifiedWrite` module with `verified_write` and `verified_edit` methods
|
|
9
|
+
- Post-write SHA-256 verification catches silent write failures (disk full, permission, NFS stale)
|
|
10
|
+
- Stale edit detection catches external file modifications between read and write
|
|
11
|
+
- `WriteVerificationError` and `StaleEditError` error classes in `errors.rb`
|
|
12
|
+
- Audit trail recording via `Legion::Data::Model::AuditLog` when legion-data is available (best-effort, never breaks the write)
|
|
13
|
+
- Shared `spec/support/audit_log_db.rb` eliminates DB constant conflicts when all specs run together
|
|
14
|
+
|
|
5
15
|
## [0.1.5] - 2026-03-30
|
|
6
16
|
|
|
7
17
|
### Changed
|
data/lex-audit.gemspec
CHANGED
|
@@ -36,7 +36,6 @@ Gem::Specification.new do |spec|
|
|
|
36
36
|
spec.add_dependency 'legion-settings', '>= 1.3.14'
|
|
37
37
|
spec.add_dependency 'legion-transport', '>= 1.3.9'
|
|
38
38
|
|
|
39
|
-
spec.add_development_dependency 'rake'
|
|
40
39
|
spec.add_development_dependency 'rspec'
|
|
41
40
|
spec.add_development_dependency 'rubocop'
|
|
42
41
|
spec.add_development_dependency 'rubocop-rspec'
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Audit
|
|
6
|
+
# Raised when post-write SHA-256 verification fails.
|
|
7
|
+
class WriteVerificationError < StandardError; end
|
|
8
|
+
|
|
9
|
+
# Raised when the file on disk has been modified since the caller read it.
|
|
10
|
+
class StaleEditError < StandardError; end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'digest'
|
|
4
|
+
require 'legion/extensions/audit/errors'
|
|
5
|
+
|
|
6
|
+
module Legion
|
|
7
|
+
module Extensions
|
|
8
|
+
module Audit
|
|
9
|
+
module Helpers
|
|
10
|
+
# Combines file write/edit operations with post-write SHA-256 verification
|
|
11
|
+
# and optional audit trail recording via AuditRecord.
|
|
12
|
+
#
|
|
13
|
+
# Include this module in any class or extension that modifies files and
|
|
14
|
+
# needs tamper-evident confirmation that the write succeeded.
|
|
15
|
+
module VerifiedWrite
|
|
16
|
+
include Legion::Extensions::Helpers::Lex if defined?(Legion::Extensions::Helpers::Lex)
|
|
17
|
+
|
|
18
|
+
# Write +content+ to +path+, then re-read and compare SHA-256 digests.
|
|
19
|
+
#
|
|
20
|
+
# @param path [String] absolute or relative filesystem path
|
|
21
|
+
# @param content [String] content to write
|
|
22
|
+
# @param agent_id [String, nil] identity recorded in the audit trail
|
|
23
|
+
# @param chain_id [String] audit chain identifier
|
|
24
|
+
#
|
|
25
|
+
# @return [Hash] { path:, before_hash:, after_hash:, verified: true }
|
|
26
|
+
# @raise [WriteVerificationError] when re-read digest does not match written digest
|
|
27
|
+
def verified_write(path, content, agent_id: nil, chain_id: 'file_edits')
|
|
28
|
+
before_hash = ::File.exist?(path) ? sha256_file(path) : nil
|
|
29
|
+
expected = sha256_string(content)
|
|
30
|
+
|
|
31
|
+
::File.write(path, content)
|
|
32
|
+
|
|
33
|
+
actual = sha256_file(path)
|
|
34
|
+
unless actual == expected
|
|
35
|
+
raise WriteVerificationError,
|
|
36
|
+
"write verification failed for #{path}: expected #{expected}, got #{actual}"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
record_audit(
|
|
40
|
+
path: path,
|
|
41
|
+
action: 'verified_write',
|
|
42
|
+
agent_id: agent_id,
|
|
43
|
+
chain_id: chain_id,
|
|
44
|
+
before_hash: before_hash,
|
|
45
|
+
after_hash: actual
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
{ path: path, before_hash: before_hash, after_hash: actual, verified: true }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Apply a string-replacement edit to +path+, with a staleness check before writing
|
|
52
|
+
# and SHA-256 verification after writing.
|
|
53
|
+
#
|
|
54
|
+
# @param path [String] absolute or relative filesystem path
|
|
55
|
+
# @param old_content [String] expected current file content (used for staleness check)
|
|
56
|
+
# @param new_content [String] desired file content after edit
|
|
57
|
+
# @param agent_id [String, nil] identity recorded in the audit trail
|
|
58
|
+
# @param chain_id [String] audit chain identifier
|
|
59
|
+
#
|
|
60
|
+
# @return [Hash] { path:, before_hash:, after_hash:, verified: true }
|
|
61
|
+
# @raise [StaleEditError] when the file has been modified since +old_content+ was read
|
|
62
|
+
# @raise [WriteVerificationError] when re-read digest does not match written digest
|
|
63
|
+
def verified_edit(path, old_content, new_content, agent_id: nil, chain_id: 'file_edits')
|
|
64
|
+
before_hash = sha256_string(old_content)
|
|
65
|
+
on_disk_hash = sha256_file(path)
|
|
66
|
+
|
|
67
|
+
unless on_disk_hash == before_hash
|
|
68
|
+
raise StaleEditError,
|
|
69
|
+
"stale edit detected for #{path}: disk content has changed since old_content was read"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
verified_write(path, new_content, agent_id: agent_id, chain_id: chain_id)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
def sha256_string(str)
|
|
78
|
+
::Digest::SHA256.hexdigest(str)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def sha256_file(path)
|
|
82
|
+
::Digest::SHA256.file(path).hexdigest
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def record_audit(path:, action:, agent_id:, chain_id:, before_hash:, after_hash:)
|
|
86
|
+
return unless defined?(Legion::Data::Model::AuditLog)
|
|
87
|
+
|
|
88
|
+
Legion::Data::Model::AuditLog.create(
|
|
89
|
+
event_type: 'file_operation',
|
|
90
|
+
principal_id: agent_id || 'system',
|
|
91
|
+
principal_type: 'system',
|
|
92
|
+
action: action,
|
|
93
|
+
resource: path,
|
|
94
|
+
source: chain_id,
|
|
95
|
+
node: 'local',
|
|
96
|
+
status: 'success',
|
|
97
|
+
detail: ::JSON.generate({ before_hash: before_hash, after_hash: after_hash }),
|
|
98
|
+
record_hash: ::Digest::SHA256.hexdigest("#{before_hash}|#{after_hash}|#{path}"),
|
|
99
|
+
prev_hash: before_hash || ('0' * 64),
|
|
100
|
+
created_at: ::Time.now.utc
|
|
101
|
+
)
|
|
102
|
+
rescue StandardError => e
|
|
103
|
+
# audit recording is best-effort; never let it break the write
|
|
104
|
+
log.warn("[lex-audit] verified_write audit record failed: #{e.message}")
|
|
105
|
+
nil
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lex-audit
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Esity
|
|
@@ -107,20 +107,6 @@ dependencies:
|
|
|
107
107
|
- - ">="
|
|
108
108
|
- !ruby/object:Gem::Version
|
|
109
109
|
version: 1.3.9
|
|
110
|
-
- !ruby/object:Gem::Dependency
|
|
111
|
-
name: rake
|
|
112
|
-
requirement: !ruby/object:Gem::Requirement
|
|
113
|
-
requirements:
|
|
114
|
-
- - ">="
|
|
115
|
-
- !ruby/object:Gem::Version
|
|
116
|
-
version: '0'
|
|
117
|
-
type: :development
|
|
118
|
-
prerelease: false
|
|
119
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
120
|
-
requirements:
|
|
121
|
-
- - ">="
|
|
122
|
-
- !ruby/object:Gem::Version
|
|
123
|
-
version: '0'
|
|
124
110
|
- !ruby/object:Gem::Dependency
|
|
125
111
|
name: rspec
|
|
126
112
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -213,6 +199,8 @@ files:
|
|
|
213
199
|
- lex-audit.gemspec
|
|
214
200
|
- lib/legion/extensions/audit.rb
|
|
215
201
|
- lib/legion/extensions/audit/actors/audit_writer.rb
|
|
202
|
+
- lib/legion/extensions/audit/errors.rb
|
|
203
|
+
- lib/legion/extensions/audit/helpers/verified_write.rb
|
|
216
204
|
- lib/legion/extensions/audit/runners/approval_queue.rb
|
|
217
205
|
- lib/legion/extensions/audit/runners/audit.rb
|
|
218
206
|
- lib/legion/extensions/audit/transport/exchanges/audit.rb
|