teak-attr_encrypted 0.1.1 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1a3df230e2f241f656f7f25cf32f726dab34da3428c5c7d863022e34900ee892
4
- data.tar.gz: df129ea74fef65d0a01c10579fe9e3be64e43ba5cf191bfae8859d06dcf76e20
3
+ metadata.gz: 5b8497e264b6044541cba0bad8679afb3a4d49344947ae4493bca833f063fff1
4
+ data.tar.gz: e141a21ae14066c3d65fe51083d2c00ac46423434a2f8c3ab28783f5d5e578f7
5
5
  SHA512:
6
- metadata.gz: 286500d3573ca70ca514a559adf01a1f9ec16a3f0034319935550f490110c039ad2325cc32d20b1df573f29cb140392522f7e892b163d10feb44b27bf22e9bb4
7
- data.tar.gz: dda4068e8b804b311fd62caa6ad6e0504a166d9fa1a251d04fddd1fa36e323424359ddbd37f0689dee6a0d41d795985d136ac888030d97f0c6a037d7d3da9024
6
+ metadata.gz: 81136834d78fdc45c21c0384b41330a3aab5bd2f2cefbbf8953a0e707944794b412def9fe55597257bdbad2ee4d45d1d02f7a684c4afb5bca40cd2eed3dcd5bf
7
+ data.tar.gz: 5ca2ab3c1dea9c09c0e0c114be7e4f5377ecd53e76d3833885a544692c72f02900d19f11a2de24fc7a3c801fa4f17c91279412bb117dbe5523946149d22825b7
@@ -0,0 +1,156 @@
1
+ version: 2.1
2
+
3
+ parameters:
4
+ scheduled_run:
5
+ type: boolean
6
+ default: false
7
+
8
+ references:
9
+ AUDIT_ALERT_RECIPIENTS: &AUDIT_ALERT_RECIPIENTS
10
+ "@jfrisby"
11
+ RUBY_IMAGE: &RUBY_IMAGE
12
+ cimg/ruby:3.2.2
13
+ CACHE_KEY: &BUNDLE_CACHE_KEY
14
+ v1-teak-attr_encrypted-{{ checksum "Gemfile.lock" }}
15
+ # ONLY_DEVELOP: &ONLY_DEVELOP
16
+ # filters:
17
+ # branches:
18
+ # only:
19
+ # - develop
20
+ # ONLY_MAIN: &ONLY_MAIN
21
+ # filters:
22
+ # branches:
23
+ # only:
24
+ # - main
25
+ # BRANCHES_ONLY: &BRANCHES_ONLY
26
+ # filters:
27
+ # branches:
28
+ # ignore:
29
+ # - main
30
+ # - develop
31
+
32
+ orbs:
33
+ slack: circleci/slack@6.1 # https://github.com/CircleCI-Public/slack-orb/releases
34
+
35
+ jobs:
36
+ refresh_ruby_deps:
37
+ docker:
38
+ - image: *RUBY_IMAGE
39
+ environment:
40
+ RACK_ENV: test
41
+ working_directory: ~/teak-attr_encrypted-build
42
+ steps:
43
+ - checkout
44
+ - restore_cache:
45
+ key: *BUNDLE_CACHE_KEY
46
+ - run: bundle config set --local path 'vendor/bundle'
47
+ - run: bundle install --jobs 3 --retry 3
48
+ - save_cache:
49
+ key: *BUNDLE_CACHE_KEY
50
+ paths:
51
+ - ~/teak-attr_encrypted-build/vendor/bundle
52
+ - ~/teak-attr_encrypted-build/.bundle
53
+
54
+ rspec:
55
+ docker:
56
+ - image: *RUBY_IMAGE
57
+ parallelism: 4
58
+ working_directory: ~/teak-attr_encrypted-build
59
+ steps:
60
+ - checkout
61
+ - restore_cache:
62
+ key: *BUNDLE_CACHE_KEY
63
+ - run: mkdir ~/rspec
64
+ - run: gem list
65
+ - run: bundle list
66
+ - run:
67
+ command: |
68
+ circleci tests glob "spec/**/*_spec.rb" | circleci tests run --command="xargs bundle exec rspec --format progress --format RspecJunitFormatter -o ~/rspec/rspec.xml" --verbose --split-by=timings
69
+ - store_test_results:
70
+ path: ~/rspec
71
+ - store_artifacts:
72
+ path: ~/rspec/rspec.xml
73
+ - run: mv ./coverage /tmp/coverage_${CIRCLE_NODE_INDEX}
74
+ - persist_to_workspace:
75
+ root: /tmp
76
+ paths:
77
+ - "coverage_*"
78
+
79
+ report_coverage:
80
+ docker:
81
+ - image: *RUBY_IMAGE
82
+ working_directory: ~/teak-attr_encrypted-build
83
+ steps:
84
+ - checkout
85
+ - restore_cache:
86
+ key: *BUNDLE_CACHE_KEY
87
+ - attach_workspace:
88
+ at: /tmp/coverage
89
+ - run: bundle exec rake report:coverage
90
+
91
+ lint_rubocop:
92
+ docker:
93
+ - image: *RUBY_IMAGE
94
+ working_directory: ~/teak-attr_encrypted-build
95
+ steps:
96
+ - checkout
97
+ - restore_cache:
98
+ key: *BUNDLE_CACHE_KEY
99
+ - run: bundle exec rake lint:rubocop
100
+
101
+ report_rubocop:
102
+ docker:
103
+ - image: *RUBY_IMAGE
104
+ working_directory: ~/teak-attr_encrypted-build
105
+ steps:
106
+ - checkout
107
+ - restore_cache:
108
+ key: *BUNDLE_CACHE_KEY
109
+ - run: bundle exec rake report:rubocop
110
+
111
+ lint_bundler_audit:
112
+ docker:
113
+ - image: *RUBY_IMAGE
114
+ working_directory: ~/teak-attr_encrypted-build
115
+ steps:
116
+ - checkout
117
+ - restore_cache:
118
+ key: *BUNDLE_CACHE_KEY
119
+ - run: bundle exec rake lint:bundler_audit
120
+ - slack/notify:
121
+ event: fail
122
+ template: basic_fail_1
123
+ mentions: *AUDIT_ALERT_RECIPIENTS
124
+
125
+ workflows:
126
+ build:
127
+ when:
128
+ not: << pipeline.parameters.scheduled_run >>
129
+ jobs:
130
+ - refresh_ruby_deps
131
+ - rspec:
132
+ requires:
133
+ - refresh_ruby_deps
134
+ - report_coverage:
135
+ requires:
136
+ - rspec
137
+ context:
138
+ - DataDog
139
+ - lint_rubocop:
140
+ requires:
141
+ - refresh_ruby_deps
142
+ - report_rubocop:
143
+ requires:
144
+ - refresh_ruby_deps
145
+ context:
146
+ - DataDog
147
+
148
+ period_dependency_audits:
149
+ when: << pipeline.parameters.scheduled_run >>
150
+ jobs:
151
+ - refresh_ruby_deps
152
+ - lint_bundler_audit:
153
+ context:
154
+ - Slack
155
+ requires:
156
+ - refresh_ruby_deps
data/.gitignore CHANGED
@@ -9,3 +9,5 @@
9
9
 
10
10
  # rspec failure tracking
11
11
  .rspec_status
12
+
13
+ .claude/settings.local.json
data/.reek.yml ADDED
@@ -0,0 +1,36 @@
1
+ exclude_paths:
2
+ - bin
3
+ - spec # We might want to reconsider this at some point, but...
4
+ detectors:
5
+ IrresponsibleModule:
6
+ enabled: false # Duplicative vs. RuboCop.
7
+ TooManyStatements:
8
+ enabled: false # Duplicative vs. RuboCop.
9
+ ControlParameter:
10
+ exclude:
11
+ - Teak::AttrEncrypted::DSL::ClassMethods#attr_encrypted
12
+ - Teak::AttrEncrypted::KEKProvider::AwsKMS#initialize
13
+ DuplicateMethodCall:
14
+ exclude:
15
+ - Teak::AttrEncrypted::DSL::ClassMethods#attr_encrypted
16
+ - Teak::AttrEncrypted::DSL::ClassMethods#attr_encrypted
17
+ - Teak::AttrEncrypted::KEKProvider::AES#request_data_key
18
+ - Teak::AttrEncrypted::Testing#context_allowed?
19
+ FeatureEnvy:
20
+ exclude:
21
+ - Teak::AttrEncrypted::Encryptor#decrypt
22
+ - Teak::AttrEncrypted::Encryptor#encrypt
23
+ - Teak::AttrEncrypted::KEKProvider::AES#decrypt_data_key
24
+ - Teak::AttrEncrypted::KEKProvider::AES#request_data_key
25
+ LongParameterList:
26
+ exclude:
27
+ - Teak::AttrEncrypted::DSL::ClassMethods#attr_encrypted
28
+ NilCheck:
29
+ exclude:
30
+ - Teak::AttrEncrypted::DSL::ClassMethods#attr_encrypted
31
+ TooManyConstants:
32
+ exclude:
33
+ - Teak::AttrEncrypted::Encryptor
34
+ UtilityFunction:
35
+ exclude:
36
+ - Teak::AttrEncrypted::Testing::RSpecHelpers#allow_encryption_contexts
data/.rubocop.yml ADDED
@@ -0,0 +1,7 @@
1
+ inherit_gem:
2
+ teak-dev: config/rubocop-base.yml
3
+
4
+ inherit_from: .rubocop_todo.yml
5
+ inherit_mode:
6
+ merge:
7
+ - Exclude
data/.rubocop_todo.yml ADDED
@@ -0,0 +1,268 @@
1
+ # This configuration was generated by
2
+ # `rubocop --auto-gen-config --auto-gen-only-exclude --no-exclude-limit --no-auto-gen-enforced-style`
3
+ # on 2026-03-25 19:14:20 UTC using RuboCop version 1.86.0.
4
+ # The point is for the user to remove these configuration records
5
+ # one by one as the offenses are removed from the code base.
6
+ # Note that changes in the inspected code, or installation of new
7
+ # versions of RuboCop, may require this file to be generated again.
8
+
9
+ # Offense count: 5
10
+ Cop/RequireSpecHelper:
11
+ Exclude:
12
+ - 'spec/teak/attr_encrypted_spec.rb'
13
+ - 'spec/teak/dsl_spec.rb'
14
+ - 'spec/teak/encryptor_spec.rb'
15
+ - 'spec/teak/kek_provider/aes_spec.rb'
16
+ - 'spec/teak/kek_provider/aws_kms_spec.rb'
17
+
18
+ # Offense count: 7
19
+ # This cop supports safe autocorrection (--autocorrect).
20
+ # Configuration parameters: AllowAliasSyntax, AllowedMethods.
21
+ # AllowedMethods: alias_method, public, protected, private
22
+ Layout/EmptyLinesAroundAttributeAccessor:
23
+ Exclude:
24
+ - 'spec/teak/dsl_spec.rb'
25
+
26
+ # Offense count: 1
27
+ # This cop supports safe autocorrection (--autocorrect).
28
+ # Configuration parameters: AllowForAlignment, AllowBeforeTrailingComments, ForceEqualSignAlignment.
29
+ Layout/ExtraSpacing:
30
+ Exclude:
31
+ - 'teak-attr_encrypted.gemspec'
32
+
33
+ # Offense count: 19
34
+ # This cop supports safe autocorrection (--autocorrect).
35
+ # Configuration parameters: AllowMultipleStyles, EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle.
36
+ # SupportedHashRocketStyles: key, separator, table
37
+ # SupportedColonStyles: key, separator, table
38
+ # SupportedLastArgumentHashStyles: always_inspect, always_ignore, ignore_implicit, ignore_explicit
39
+ Layout/HashAlignment:
40
+ Exclude:
41
+ - 'lib/teak/attr_encrypted/encryptor.rb'
42
+ - 'lib/teak/attr_encrypted/kek_provider/aws_kms.rb'
43
+ - 'spec/teak/dsl_spec.rb'
44
+ - 'spec/teak/kek_provider/aes_spec.rb'
45
+ - 'spec/teak/kek_provider/aws_kms_spec.rb'
46
+
47
+ # Offense count: 1
48
+ # This cop supports safe autocorrection (--autocorrect).
49
+ # Configuration parameters: AllowForAlignment, EnforcedStyleForExponentOperator, EnforcedStyleForRationalLiterals.
50
+ # SupportedStylesForExponentOperator: space, no_space
51
+ # SupportedStylesForRationalLiterals: space, no_space
52
+ Layout/SpaceAroundOperators:
53
+ Exclude:
54
+ - 'teak-attr_encrypted.gemspec'
55
+
56
+ # Offense count: 2
57
+ # This cop supports safe autocorrection (--autocorrect).
58
+ # Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces.
59
+ # SupportedStyles: space, no_space, compact
60
+ # SupportedStylesForEmptyBraces: space, no_space
61
+ Layout/SpaceInsideHashLiteralBraces:
62
+ Exclude:
63
+ - 'lib/teak/attr_encrypted/kek_provider/aes.rb'
64
+
65
+ # Offense count: 5
66
+ # Configuration parameters: AllowedMethods.
67
+ # AllowedMethods: enums
68
+ Lint/ConstantDefinitionInBlock:
69
+ Exclude:
70
+ - 'spec/teak/dsl_spec.rb'
71
+
72
+ # Offense count: 1
73
+ # Configuration parameters: AllowComments, AllowNil.
74
+ Lint/SuppressedException:
75
+ Exclude:
76
+ - 'Rakefile'
77
+
78
+ # Offense count: 2
79
+ # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max.
80
+ Metrics/AbcSize:
81
+ Exclude:
82
+ - 'lib/teak/attr_encrypted/dsl.rb'
83
+ - 'lib/teak/attr_encrypted/encryptor.rb'
84
+
85
+ # Offense count: 1
86
+ # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
87
+ Metrics/CyclomaticComplexity:
88
+ Exclude:
89
+ - 'lib/teak/attr_encrypted/dsl.rb'
90
+
91
+ # Offense count: 3
92
+ # Configuration parameters: CountComments, Max, CountAsOne, AllowedMethods, AllowedPatterns.
93
+ Metrics/MethodLength:
94
+ Exclude:
95
+ - 'lib/teak/attr_encrypted/dsl.rb'
96
+ - 'lib/teak/attr_encrypted/encryptor.rb'
97
+
98
+ # Offense count: 1
99
+ # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
100
+ Metrics/PerceivedComplexity:
101
+ Exclude:
102
+ - 'lib/teak/attr_encrypted/dsl.rb'
103
+
104
+ # Offense count: 2
105
+ # This cop supports safe autocorrection (--autocorrect).
106
+ # Configuration parameters: EnforcedStyle.
107
+ # SupportedStyles: be, be_nil
108
+ RSpec/BeNil:
109
+ Exclude:
110
+ - 'spec/teak/attr_encrypted_spec.rb'
111
+ - 'spec/teak/dsl_spec.rb'
112
+
113
+ # Offense count: 6
114
+ # This cop supports safe autocorrection (--autocorrect).
115
+ RSpec/EmptyLineAfterFinalLet:
116
+ Exclude:
117
+ - 'spec/teak/dsl_spec.rb'
118
+ - 'spec/teak/encryptor_spec.rb'
119
+ - 'spec/teak/kek_provider/aes_spec.rb'
120
+ - 'spec/teak/kek_provider/aws_kms_spec.rb'
121
+
122
+ # Offense count: 6
123
+ # This cop supports safe autocorrection (--autocorrect).
124
+ RSpec/LeadingSubject:
125
+ Exclude:
126
+ - 'spec/teak/dsl_spec.rb'
127
+ - 'spec/teak/encryptor_spec.rb'
128
+ - 'spec/teak/kek_provider/aes_spec.rb'
129
+ - 'spec/teak/kek_provider/aws_kms_spec.rb'
130
+
131
+ # Offense count: 5
132
+ RSpec/LeakyConstantDeclaration:
133
+ Exclude:
134
+ - 'spec/teak/dsl_spec.rb'
135
+
136
+ # Offense count: 1
137
+ # This cop supports safe autocorrection (--autocorrect).
138
+ RSpec/ScatteredLet:
139
+ Exclude:
140
+ - 'spec/teak/dsl_spec.rb'
141
+
142
+ # Offense count: 4
143
+ # Configuration parameters: CustomTransform, IgnoreMethods, IgnoreMetadata, InflectorPath, EnforcedInflector.
144
+ # SupportedInflectors: default, active_support
145
+ RSpec/SpecFilePathFormat:
146
+ Exclude:
147
+ - 'spec/teak/dsl_spec.rb'
148
+ - 'spec/teak/encryptor_spec.rb'
149
+ - 'spec/teak/kek_provider/aes_spec.rb'
150
+ - 'spec/teak/kek_provider/aws_kms_spec.rb'
151
+
152
+ # Offense count: 4
153
+ # This cop supports safe autocorrection (--autocorrect).
154
+ # Configuration parameters: EnforcedStyle.
155
+ # SupportedStyles: separated, grouped
156
+ Style/AccessorGrouping:
157
+ Exclude:
158
+ - 'spec/teak/dsl_spec.rb'
159
+
160
+ # Offense count: 6
161
+ # Configuration parameters: AllowedConstants.
162
+ Style/Documentation:
163
+ Exclude:
164
+ - 'lib/teak/attr_encrypted.rb'
165
+ - 'lib/teak/attr_encrypted/dsl.rb'
166
+ - 'lib/teak/attr_encrypted/encryptor.rb'
167
+ - 'lib/teak/attr_encrypted/kek_provider/aws_kms.rb'
168
+ - 'lib/teak/attr_encrypted/kek_provider/base.rb'
169
+
170
+ # Offense count: 1
171
+ # This cop supports safe autocorrection (--autocorrect).
172
+ Style/ExpandPathArguments:
173
+ Exclude:
174
+ - 'teak-attr_encrypted.gemspec'
175
+
176
+ # Offense count: 7
177
+ # This cop supports unsafe autocorrection (--autocorrect-all).
178
+ # Configuration parameters: EnforcedStyle.
179
+ # SupportedStyles: always, always_true, never
180
+ Style/FrozenStringLiteralComment:
181
+ Exclude:
182
+ - 'Gemfile'
183
+ - 'Rakefile'
184
+ - 'bin/console'
185
+ - 'lib/teak/attr_encrypted/version.rb'
186
+ - 'spec/spec_helper.rb'
187
+ - 'spec/teak/attr_encrypted_spec.rb'
188
+ - 'teak-attr_encrypted.gemspec'
189
+
190
+ # Offense count: 1
191
+ # This cop supports safe autocorrection (--autocorrect).
192
+ # Configuration parameters: EnforcedStyle, EnforcedShorthandSyntax, UseHashRocketsWithSymbolValues, PreferHashRocketsForNonAlnumEndingSymbols.
193
+ # SupportedStyles: ruby19, hash_rockets, no_mixed_keys, ruby19_no_mixed_keys
194
+ # SupportedShorthandSyntax: always, never, either, consistent, either_consistent
195
+ Style/HashSyntax:
196
+ Exclude:
197
+ - 'Rakefile'
198
+
199
+ # Offense count: 1
200
+ # This cop supports unsafe autocorrection (--autocorrect-all).
201
+ # Configuration parameters: EnforcedStyle.
202
+ # SupportedStyles: literals, strict
203
+ Style/MutableConstant:
204
+ Exclude:
205
+ - 'lib/teak/attr_encrypted/version.rb'
206
+
207
+ # Offense count: 1
208
+ # This cop supports safe autocorrection (--autocorrect).
209
+ # Configuration parameters: PreferredDelimiters.
210
+ Style/PercentLiteralDelimiters:
211
+ Exclude:
212
+ - 'teak-attr_encrypted.gemspec'
213
+
214
+ # Offense count: 2
215
+ # This cop supports unsafe autocorrection (--autocorrect-all).
216
+ # Configuration parameters: AllowedCompactTypes.
217
+ # SupportedStyles: compact, exploded
218
+ Style/RaiseArgs:
219
+ Exclude:
220
+ - 'lib/teak/attr_encrypted.rb'
221
+ - 'lib/teak/attr_encrypted/encryptor.rb'
222
+
223
+ # Offense count: 1
224
+ # This cop supports safe autocorrection (--autocorrect).
225
+ # Configuration parameters: AllowedMethods.
226
+ # AllowedMethods: infinite?, nonzero?
227
+ Style/RedundantCondition:
228
+ Exclude:
229
+ - 'lib/teak/attr_encrypted.rb'
230
+
231
+ # Offense count: 1
232
+ # This cop supports safe autocorrection (--autocorrect).
233
+ Style/RedundantPercentQ:
234
+ Exclude:
235
+ - 'teak-attr_encrypted.gemspec'
236
+
237
+ # Offense count: 36
238
+ # This cop supports safe autocorrection (--autocorrect).
239
+ # Configuration parameters: EnforcedStyle, ConsistentQuotesInMultiline.
240
+ # SupportedStyles: single_quotes, double_quotes
241
+ Style/StringLiterals:
242
+ Exclude:
243
+ - 'Gemfile'
244
+ - 'Rakefile'
245
+ - 'bin/console'
246
+ - 'lib/teak/attr_encrypted/version.rb'
247
+ - 'spec/spec_helper.rb'
248
+ - 'spec/teak/attr_encrypted_spec.rb'
249
+ - 'spec/teak/kek_provider/aws_kms_spec.rb'
250
+ - 'teak-attr_encrypted.gemspec'
251
+
252
+ # Offense count: 9
253
+ # This cop supports safe autocorrection (--autocorrect).
254
+ # Configuration parameters: EnforcedStyleForMultiline.
255
+ # SupportedStylesForMultiline: comma, consistent_comma, diff_comma, no_comma
256
+ Style/TrailingCommaInHashLiteral:
257
+ Exclude:
258
+ - 'lib/teak/attr_encrypted/encryptor.rb'
259
+ - 'lib/teak/attr_encrypted/kek_provider/aws_kms.rb'
260
+ - 'spec/teak/kek_provider/aws_kms_spec.rb'
261
+
262
+ # Offense count: 1
263
+ # This cop supports safe autocorrection (--autocorrect).
264
+ # Configuration parameters: MinSize, WordRegex.
265
+ # SupportedStyles: percent, brackets
266
+ Style/WordArray:
267
+ Exclude:
268
+ - 'teak-attr_encrypted.gemspec'
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ teak-attr_encrypted
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ ruby-3.2.2
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ ## 0.2.0
2
+
3
+ FEATURES:
4
+ * Add testing tools, to simulate availability / absence of specific encryption contexts.
5
+
1
6
  ## 0.1.1
2
7
 
3
8
  BUG FIXES:
data/CLAUDE.md ADDED
@@ -0,0 +1,176 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Overview
6
+
7
+ teak-attr_encrypted is a Ruby gem that provides a DSL for transparent envelope encryption/decryption of attributes on any class (primarily ORM models). It uses a Key Encryption Key (KEK) provider pattern where data keys are generated per-encryption and themselves encrypted by the KEK.
8
+
9
+ ## Commands
10
+
11
+ - **Install dependencies:** `bundle install`
12
+ - **Run all tests:** `bundle exec rake spec`
13
+ - **Run a single test file:** `bundle exec rspec spec/teak/encryptor_spec.rb`
14
+ - **Run a single test by line:** `bundle exec rspec spec/teak/encryptor_spec.rb:42`
15
+ - **Interactive console:** `bin/console`
16
+
17
+ ## Architecture
18
+
19
+ The gem implements envelope encryption with a two-tier key hierarchy:
20
+
21
+ - **`Teak::AttrEncrypted`** — Entry module. `include` it in a class to get the `attr_encrypted` DSL. Holds the global `default_kek_provider`.
22
+ - **`DSL`** (`lib/teak/attr_encrypted/dsl.rb`) — Defines `attr_encrypted` class method. For each encrypted attribute, it creates a getter/setter pair that delegates to an `Encryptor` instance. Supports `context:` (symbol, proc, or literal) as additional authenticated data.
23
+ - **`Encryptor`** (`lib/teak/attr_encrypted/encryptor.rb`) — Handles AES-256-GCM encrypt/decrypt. Produces versioned envelopes: a version char prefix + Base64-encoded MessagePack blob containing IV, auth tag, encrypted data key, and ciphertext.
24
+ - **KEK Providers** (`lib/teak/attr_encrypted/kek_provider/`) — Generate and decrypt data keys:
25
+ - `Base` — Abstract base with an `id` accessor.
26
+ - `AES` — Local AES-256-GCM key wrapping. Intended for dev/test only.
27
+ - `AwsKMS` — Production provider using AWS KMS `GenerateDataKey`/`Decrypt` for envelope encryption.
28
+ - **`Testing`** (`lib/teak/attr_encrypted/testing.rb`) — Test helpers that prepend onto `Encryptor` to allow/deny encryption contexts in test scopes. Provides `RSpecHelpers` module.
29
+
30
+ ## Key Details
31
+
32
+ - Ruby 3.2.2 (`.ruby-version`), gemset managed via `.ruby-gemset`
33
+ - Only runtime dependency: `msgpack ~> 1.7`
34
+ - Test dependencies: `rspec`, `simplecov` (branch coverage enabled), `aws-sdk-kms`
35
+ - RSpec configured with `disable_monkey_patching!` and `expect` syntax only
36
+ - Envelope format is versioned (currently version `'1'`) to allow future format changes
37
+
38
+
39
+ ## 📋 Commit Requirements (ALL Work - Features, Docs, Refactoring, Everything)
40
+
41
+ ### Every Commit Must Capture Human-Claude Interaction
42
+
43
+ **The commit template below is MANDATORY for ALL commits** - documentation changes, refactoring, bug fixes, features, CLAUDE.md updates, everything. This captures how humans effectively guide Claude.
44
+
45
+ ### When to Commit
46
+ Only commit when:
47
+ - ✅ Tests pass (if applicable)
48
+ - ✅ Human explicitly requests commit OR
49
+ - ✅ Reached major milestone
50
+
51
+ Never commit:
52
+ - ❌ Without capturing full interaction log
53
+ - ❌ With failing tests or lint errors
54
+ - ❌ "Proactively" without meeting above criteria
55
+
56
+ ### Commit Checklist
57
+ Before ANY commit:
58
+ - [ ] Did I run `rake lint` and fix any issues?
59
+ - [ ] Did I run tests (`rspec`) if applicable?
60
+ - [ ] Did I search for existing patterns before writing code?
61
+ - [ ] Did I document any NEW problem/solution in CLAUDE.md?
62
+ - [ ] Am I solving a genuinely new problem? (Rare!)
63
+ - [ ] **Did I capture VERBATIM human-Claude interactions in commit message?**
64
+
65
+ ### MANDATORY: Output Before EVERY Commit (All Work Types, Including Amends!)
66
+ You MUST output exactly (even for `git commit --amend`):
67
+ ```
68
+ 📝 COMMIT READY CHECK:
69
+ ☑️ Lint clean: [YES/NO/NA - only if code changed]
70
+ ☑️ Tests pass: [YES/NO/NA - only if code changed]
71
+ ☑️ Tested in CLI: [YES/NO/NA - only if CLI code changed]
72
+ ☑️ Documented new patterns: [YES/NO/NA - only if applicable]
73
+ ☑️ ALL prompts since last commit captured: [YES - X prompts captured VERBATIM]
74
+
75
+ [If ANY are NO: "❌ NOT ready - need to: (list required actions)"]
76
+ [If ALL are YES/NA: "✅ Ready to commit with COMPLETE Human-Claude interaction log."]
77
+ ```
78
+
79
+ When ready, commit your work with the human-Claude interaction log.
80
+
81
+ **Note on Amending**: `git commit --amend` still requires the full checklist - code may have changed since original commit.
82
+
83
+ **Universal Commit Message Template** (USE FOR EVERY COMMIT - docs, refactoring, features, EVERYTHING):
84
+ ```bash
85
+ # First, check what you modified:
86
+ git status
87
+ # Then add YOUR specific changes (not everything):
88
+ git add [specific files you changed]
89
+ # For multiple files:
90
+ git add file1.js file2.rb CLAUDE.md
91
+ # Commit with interaction log:
92
+ git commit -m "$(cat <<'EOF'
93
+ Brief description of what was done
94
+
95
+ [Technical changes made]
96
+
97
+ ## Human-Claude Interaction Log
98
+
99
+ ### Human prompts (VERBATIM - include typos, informal language, COMPLETE text):
100
+ **Include EVERY prompt since last commit - even short ones, corrections, clarifications**
101
+ 1. "[Copy-paste ENTIRE first prompt since last commit]"
102
+ → Claude: [What Claude did in response]
103
+
104
+ 2. "[Copy-paste ENTIRE second prompt - including [Request interrupted] if present]"
105
+ → Claude: [How Claude adjusted]
106
+
107
+ [Continue numbering ALL prompts - don't skip any or judge importance]
108
+
109
+ ### Key decisions made:
110
+ - Human guided: [specific guidance provided]
111
+ - Claude discovered: [patterns found]
112
+
113
+ 🤖 Generated with Claude Code
114
+ Co-Authored-By: Claude <noreply@anthropic.com>
115
+ EOF
116
+ )"
117
+ ```
118
+ **Note**: Avoid `git add -A` when multiple Claudes work in parallel - add only YOUR files.
119
+
120
+ ## GitHub Operations and Pull Request Creation
121
+
122
+ ### Team GitHub Usernames
123
+ - Mark → **@blamfantastico**
124
+ - Alex → **@AlexSc**
125
+ - Jon → **@MrJoy**
126
+
127
+ ### Creating Pull Requests with Proper Escaping
128
+
129
+ When creating pull requests using `gh pr create`, **ALWAYS use heredoc syntax** to ensure proper escaping of all special characters in the PR body.
130
+
131
+ **Critical Requirements:**
132
+
133
+ - **ALWAYS use heredoc (`cat <<'EOF'...EOF`)** for the `--body` parameter to ensure proper escaping of all special characters (quotes, backticks, dollar signs, parentheses, brackets, etc.)
134
+ - The single quotes around `'EOF'` prevent variable expansion within the heredoc
135
+ - This pattern safely handles any text content without manual escaping
136
+ - Never attempt to manually escape characters - let the heredoc handle it
137
+
138
+ **Correct PR Creation Example:**
139
+
140
+ ```bash
141
+ # IMPORTANT: Always use heredoc syntax for --body to properly escape special characters
142
+ gh pr create --title "the pr title" --body "$(cat <<'EOF'
143
+ ## Summary
144
+ - Feature implementation with special chars like $, `, ", ', [], ()
145
+ - Bug fixes for edge cases
146
+
147
+ ## Test plan
148
+ - [ ] Unit tests pass
149
+ - [ ] Integration tests complete
150
+ - [ ] Manual testing done
151
+
152
+ ## Notes
153
+ Any text here is safe, including: "quotes", `backticks`, $variables,
154
+ [brackets], (parentheses), and other special characters!
155
+
156
+ 🤖 Generated with [Claude Code](https://claude.ai/code)
157
+ EOF
158
+ )"
159
+ ```
160
+
161
+ **Why Heredocs Solve Escaping Issues:**
162
+
163
+ 1. The heredoc delimiter (`'EOF'`) with single quotes prevents shell expansion
164
+ 2. All content between delimiters is treated as literal text
165
+ 3. No need to escape individual characters
166
+ 4. Handles multi-line content naturally
167
+ 5. Same pattern used successfully for commit messages
168
+
169
+ ### Other GitHub CLI Operations
170
+
171
+ - View PR comments: `gh pr view <number> --comments`
172
+ - Check PR status: `gh pr status`
173
+ - List PRs: `gh pr list`
174
+ - View PR diff: `gh pr diff <number>`
175
+
176
+ **Important:** Never update git config or push to remote unless explicitly requested by the user.
data/Gemfile CHANGED
@@ -5,5 +5,13 @@ gemspec
5
5
 
6
6
  gem "rake", "~> 12.0"
7
7
  gem "rspec", "~> 3.0"
8
+ gem "rspec_junit_formatter", "~> 0.6"
8
9
  gem 'simplecov', '~> 0.22.0'
9
10
  gem "aws-sdk-kms", "~> 1.72"
11
+ gem "nokogiri", "~> 1.19"
12
+
13
+ group :development do
14
+ gem 'bundler-audit', '~> 0.8', require: false
15
+ gem 'reek', '~> 6.1', '>= 6.1.1', require: false
16
+ gem 'teak-dev', '~> 0.1', require: false, source: 'https://repo.fury.io/alexsc/'
17
+ end
data/Gemfile.lock CHANGED
@@ -1,58 +1,179 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- teak-attr_encrypted (0.1.1)
4
+ teak-attr_encrypted (0.2.0)
5
5
  msgpack (~> 1.7)
6
6
 
7
+ GEM
8
+ remote: https://repo.fury.io/alexsc/
9
+ specs:
10
+ teak-dev (0.3.0)
11
+ bundler-audit (~> 0.9.2)
12
+ datadog_api_client (~> 2.43.0)
13
+ rubocop (~> 1.72)
14
+ rubocop-performance (~> 1.23, >= 1.23.1)
15
+ rubocop-rake (~> 0.7.1)
16
+ rubocop-rspec (~> 3.4)
17
+
7
18
  GEM
8
19
  remote: https://rubygems.org/
9
20
  specs:
10
- aws-eventstream (1.2.0)
11
- aws-partitions (1.844.0)
12
- aws-sdk-core (3.186.0)
13
- aws-eventstream (~> 1, >= 1.0.2)
14
- aws-partitions (~> 1, >= 1.651.0)
15
- aws-sigv4 (~> 1.5)
21
+ ast (2.4.3)
22
+ aws-eventstream (1.4.0)
23
+ aws-partitions (1.1230.0)
24
+ aws-sdk-core (3.244.0)
25
+ aws-eventstream (~> 1, >= 1.3.0)
26
+ aws-partitions (~> 1, >= 1.992.0)
27
+ aws-sigv4 (~> 1.9)
28
+ base64
29
+ bigdecimal
16
30
  jmespath (~> 1, >= 1.6.1)
17
- aws-sdk-kms (1.72.0)
18
- aws-sdk-core (~> 3, >= 3.184.0)
19
- aws-sigv4 (~> 1.1)
20
- aws-sigv4 (1.6.1)
31
+ logger
32
+ aws-sdk-kms (1.123.0)
33
+ aws-sdk-core (~> 3, >= 3.244.0)
34
+ aws-sigv4 (~> 1.5)
35
+ aws-sigv4 (1.12.1)
21
36
  aws-eventstream (~> 1, >= 1.0.2)
22
- diff-lcs (1.5.0)
23
- docile (1.4.0)
37
+ base64 (0.3.0)
38
+ bigdecimal (3.3.1)
39
+ bundler-audit (0.9.3)
40
+ bundler (>= 1.2.0)
41
+ thor (~> 1.0)
42
+ concurrent-ruby (1.3.6)
43
+ csv (3.3.5)
44
+ datadog_api_client (2.43.0)
45
+ httparty (~> 0.20, >= 0.20.0)
46
+ zeitwerk (~> 2.6, >= 2.6.0)
47
+ diff-lcs (1.6.2)
48
+ docile (1.4.1)
49
+ dry-configurable (1.3.0)
50
+ dry-core (~> 1.1)
51
+ zeitwerk (~> 2.6)
52
+ dry-core (1.1.0)
53
+ concurrent-ruby (~> 1.0)
54
+ logger
55
+ zeitwerk (~> 2.6)
56
+ dry-inflector (1.2.0)
57
+ dry-initializer (3.2.0)
58
+ dry-logic (1.6.0)
59
+ bigdecimal
60
+ concurrent-ruby (~> 1.0)
61
+ dry-core (~> 1.1)
62
+ zeitwerk (~> 2.6)
63
+ dry-schema (1.14.1)
64
+ concurrent-ruby (~> 1.0)
65
+ dry-configurable (~> 1.0, >= 1.0.1)
66
+ dry-core (~> 1.1)
67
+ dry-initializer (~> 3.2)
68
+ dry-logic (~> 1.5)
69
+ dry-types (~> 1.8)
70
+ zeitwerk (~> 2.6)
71
+ dry-types (1.8.3)
72
+ bigdecimal (~> 3.0)
73
+ concurrent-ruby (~> 1.0)
74
+ dry-core (~> 1.0)
75
+ dry-inflector (~> 1.0)
76
+ dry-logic (~> 1.4)
77
+ zeitwerk (~> 2.6)
78
+ httparty (0.24.2)
79
+ csv
80
+ mini_mime (>= 1.0.0)
81
+ multi_xml (>= 0.5.2)
24
82
  jmespath (1.6.2)
25
- msgpack (1.7.2)
83
+ json (2.19.3)
84
+ language_server-protocol (3.17.0.5)
85
+ lint_roller (1.1.0)
86
+ logger (1.7.0)
87
+ mini_mime (1.1.5)
88
+ mini_portile2 (2.8.9)
89
+ msgpack (1.8.0)
90
+ multi_xml (0.7.1)
91
+ bigdecimal (~> 3.1)
92
+ nokogiri (1.19.2)
93
+ mini_portile2 (~> 2.8.2)
94
+ racc (~> 1.4)
95
+ parallel (1.27.0)
96
+ parser (3.3.10.2)
97
+ ast (~> 2.4.1)
98
+ racc
99
+ prism (1.9.0)
100
+ racc (1.8.1)
101
+ rainbow (3.1.1)
26
102
  rake (12.3.3)
27
- rspec (3.12.0)
28
- rspec-core (~> 3.12.0)
29
- rspec-expectations (~> 3.12.0)
30
- rspec-mocks (~> 3.12.0)
31
- rspec-core (3.12.2)
32
- rspec-support (~> 3.12.0)
33
- rspec-expectations (3.12.3)
103
+ reek (6.5.0)
104
+ dry-schema (~> 1.13)
105
+ logger (~> 1.6)
106
+ parser (~> 3.3.0)
107
+ rainbow (>= 2.0, < 4.0)
108
+ rexml (~> 3.1)
109
+ regexp_parser (2.11.3)
110
+ rexml (3.4.4)
111
+ rspec (3.13.2)
112
+ rspec-core (~> 3.13.0)
113
+ rspec-expectations (~> 3.13.0)
114
+ rspec-mocks (~> 3.13.0)
115
+ rspec-core (3.13.6)
116
+ rspec-support (~> 3.13.0)
117
+ rspec-expectations (3.13.5)
34
118
  diff-lcs (>= 1.2.0, < 2.0)
35
- rspec-support (~> 3.12.0)
36
- rspec-mocks (3.12.6)
119
+ rspec-support (~> 3.13.0)
120
+ rspec-mocks (3.13.8)
37
121
  diff-lcs (>= 1.2.0, < 2.0)
38
- rspec-support (~> 3.12.0)
39
- rspec-support (3.12.1)
122
+ rspec-support (~> 3.13.0)
123
+ rspec-support (3.13.7)
124
+ rspec_junit_formatter (0.6.0)
125
+ rspec-core (>= 2, < 4, != 2.12.0)
126
+ rubocop (1.86.0)
127
+ json (~> 2.3)
128
+ language_server-protocol (~> 3.17.0.2)
129
+ lint_roller (~> 1.1.0)
130
+ parallel (~> 1.10)
131
+ parser (>= 3.3.0.2)
132
+ rainbow (>= 2.2.2, < 4.0)
133
+ regexp_parser (>= 2.9.3, < 3.0)
134
+ rubocop-ast (>= 1.49.0, < 2.0)
135
+ ruby-progressbar (~> 1.7)
136
+ unicode-display_width (>= 2.4.0, < 4.0)
137
+ rubocop-ast (1.49.1)
138
+ parser (>= 3.3.7.2)
139
+ prism (~> 1.7)
140
+ rubocop-performance (1.26.1)
141
+ lint_roller (~> 1.1)
142
+ rubocop (>= 1.75.0, < 2.0)
143
+ rubocop-ast (>= 1.47.1, < 2.0)
144
+ rubocop-rake (0.7.1)
145
+ lint_roller (~> 1.1)
146
+ rubocop (>= 1.72.1)
147
+ rubocop-rspec (3.9.0)
148
+ lint_roller (~> 1.1)
149
+ rubocop (~> 1.81)
150
+ ruby-progressbar (1.13.0)
40
151
  simplecov (0.22.0)
41
152
  docile (~> 1.1)
42
153
  simplecov-html (~> 0.11)
43
154
  simplecov_json_formatter (~> 0.1)
44
- simplecov-html (0.12.3)
155
+ simplecov-html (0.13.2)
45
156
  simplecov_json_formatter (0.1.4)
157
+ thor (1.5.0)
158
+ unicode-display_width (3.2.0)
159
+ unicode-emoji (~> 4.1)
160
+ unicode-emoji (4.2.0)
161
+ zeitwerk (2.6.18)
46
162
 
47
163
  PLATFORMS
48
164
  ruby
49
165
 
50
166
  DEPENDENCIES
51
167
  aws-sdk-kms (~> 1.72)
168
+ bundler-audit (~> 0.8)
169
+ nokogiri (~> 1.19)
52
170
  rake (~> 12.0)
171
+ reek (~> 6.1, >= 6.1.1)
53
172
  rspec (~> 3.0)
173
+ rspec_junit_formatter (~> 0.6)
54
174
  simplecov (~> 0.22.0)
55
175
  teak-attr_encrypted!
176
+ teak-dev (~> 0.1)!
56
177
 
57
178
  BUNDLED WITH
58
- 2.1.4
179
+ 2.5.4
data/Rakefile CHANGED
@@ -1,6 +1,12 @@
1
1
  require "bundler/gem_tasks"
2
2
  require "rspec/core/rake_task"
3
3
 
4
+ begin
5
+ require 'teak/dev/tasks'
6
+ Teak::Dev::Tasks.install(service: 'teak-attr_encrypted')
7
+ rescue LoadError
8
+ end
9
+
4
10
  RSpec::Core::RakeTask.new(:spec)
5
11
 
6
12
  task :default => :spec
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'teak/attr_encrypted'
4
+
5
+ module Teak
6
+ module AttrEncrypted
7
+ # Test helpers for controlling which encryption contexts are available
8
+ # in test scopes. Prepends onto Encryptor to gate encrypt/decrypt calls.
9
+ module Testing
10
+ # Raised when an encryption context is not allowed in the current test scope.
11
+ class ContextNotAllowed < Teak::AttrEncrypted::Error
12
+ def initialize(context)
13
+ super("Encryption context #{context.inspect} is not allowed in the current test scope")
14
+ end
15
+ end
16
+
17
+ THREAD_KEY = :teak_attr_encrypted_testing_config
18
+
19
+ class << self
20
+ def allow_encryption_contexts(*contexts)
21
+ config = context_config
22
+ config[:allowed] = Array(contexts).flatten
23
+ config[:denied] = nil
24
+ end
25
+
26
+ def context_allowed?(context)
27
+ config = context_config
28
+ if config[:allowed]
29
+ config[:allowed].include?(context&.[](:type))
30
+ else
31
+ true
32
+ end
33
+ end
34
+
35
+ def check_context!(context)
36
+ return if context_allowed?(context)
37
+
38
+ raise ContextNotAllowed, context
39
+ end
40
+
41
+ def reset!
42
+ Thread.current[THREAD_KEY] = nil
43
+ end
44
+
45
+ private
46
+
47
+ def context_config
48
+ Thread.current[THREAD_KEY] ||= {}
49
+ end
50
+ end
51
+
52
+ # Prepended onto Encryptor to check context before encrypt/decrypt.
53
+ module EncryptorOverride
54
+ def decrypt(envelope, encryption_context)
55
+ Teak::AttrEncrypted::Testing.check_context!(encryption_context)
56
+ super
57
+ end
58
+
59
+ def encrypt(plaintext, encryption_context)
60
+ Teak::AttrEncrypted::Testing.check_context!(encryption_context)
61
+ super
62
+ end
63
+ end
64
+
65
+ # Include in RSpec to get allow helper methods.
66
+ module RSpecHelpers
67
+ def allow_encryption_contexts(*contexts)
68
+ Teak::AttrEncrypted::Testing.allow_encryption_contexts(*contexts)
69
+ end
70
+ end
71
+
72
+ Teak::AttrEncrypted::Encryptor.prepend(EncryptorOverride)
73
+ end
74
+ end
75
+ end
@@ -1,5 +1,5 @@
1
1
  module Teak
2
2
  module AttrEncrypted
3
- VERSION = "0.1.1"
3
+ VERSION = "0.2.0"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: teak-attr_encrypted
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex Scarborough
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-09-10 00:00:00.000000000 Z
11
+ date: 2026-03-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: msgpack
@@ -31,10 +31,17 @@ executables: []
31
31
  extensions: []
32
32
  extra_rdoc_files: []
33
33
  files:
34
+ - ".circleci/config.yml"
34
35
  - ".gitignore"
36
+ - ".reek.yml"
35
37
  - ".rspec"
38
+ - ".rubocop.yml"
39
+ - ".rubocop_todo.yml"
40
+ - ".ruby-gemset"
41
+ - ".ruby-version"
36
42
  - ".travis.yml"
37
43
  - CHANGELOG.md
44
+ - CLAUDE.md
38
45
  - Gemfile
39
46
  - Gemfile.lock
40
47
  - LICENSE
@@ -49,6 +56,7 @@ files:
49
56
  - lib/teak/attr_encrypted/kek_provider/aes.rb
50
57
  - lib/teak/attr_encrypted/kek_provider/aws_kms.rb
51
58
  - lib/teak/attr_encrypted/kek_provider/base.rb
59
+ - lib/teak/attr_encrypted/testing.rb
52
60
  - lib/teak/attr_encrypted/version.rb
53
61
  - teak-attr_encrypted.gemspec
54
62
  homepage: https://github.com/GoCarrot/teak-attr_encrypted
@@ -74,7 +82,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
74
82
  - !ruby/object:Gem::Version
75
83
  version: '0'
76
84
  requirements: []
77
- rubygems_version: 3.1.6
85
+ rubygems_version: 3.5.4
78
86
  signing_key:
79
87
  specification_version: 4
80
88
  summary: Encrypts attributes on models using a key encryption key and envelopes.