Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/auto-tag.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ jobs:
with:
ruby-version: '3.3'
bundler-cache: true
- name: Configure gem signing key
if: steps.check.outputs.exists == 'false'
run: |
mkdir -p ~/.ssh
echo "${{ secrets.GEM_SIGNING_KEY }}" > ~/.ssh/gem-private_key.pem
chmod 600 ~/.ssh/gem-private_key.pem
- name: Build gem
if: steps.check.outputs.exists == 'false'
run: gem build activeitem.gemspec
Expand Down
14 changes: 14 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ on:
pull_request:
branches: [main]

permissions:
contents: read

jobs:
test:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -43,3 +46,14 @@ jobs:
ruby-version: '3.4'
bundler-cache: true
- run: bundle exec rubocop

security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: ruby/setup-ruby@v1
with:
ruby-version: '3.4'
bundler-cache: true
- run: gem install bundler-audit
- run: bundler-audit check --update
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
/pkg/
/tmp/
*.gem
gem-private_key.pem
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ ActiveRecord-like ORM for AWS DynamoDB.
gem 'activeitem'
```

To install with signature verification:

```bash
gem cert --add <(curl -Ls https://raw.githubusercontent.com/stowzilla/activeitem/master/certs/stowzilla.pem)
gem install activeitem -P MediumSecurity
```

## Configuration

```ruby
Expand Down
5 changes: 4 additions & 1 deletion activeitem.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ Gem::Specification.new do |spec|
spec.metadata['changelog_uri'] = 'https://github.com/stowzilla/activeitem/blob/main/CHANGELOG.md'
spec.metadata['rubygems_mfa_required'] = 'true'

spec.files = Dir['lib/**/*', 'LICENSE.txt', 'README.md', 'CHANGELOG.md']
spec.cert_chain = ['certs/stowzilla.pem']
spec.signing_key = File.expand_path('~/.ssh/gem-private_key.pem') if File.exist?(File.expand_path('~/.ssh/gem-private_key.pem'))

spec.files = Dir['lib/**/*', 'LICENSE.txt', 'README.md', 'CHANGELOG.md', 'certs/*']
spec.require_paths = ['lib']

spec.add_dependency 'activemodel', '>= 7.0'
Expand Down
31 changes: 31 additions & 0 deletions certs/stowzilla.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
-----BEGIN CERTIFICATE-----
MIIFaTCCA1GgAwIBAgIUax444kioC0RHJ7r5iympSbt4GOEwDQYJKoZIhvcNAQEL
BQAwRDESMBAGA1UEAwwJc3Rvd3ppbGxhMRkwFwYKCZImiZPyLGQBGRYJc3Rvd3pp
bGxhMRMwEQYKCZImiZPyLGQBGRYDY29tMB4XDTI2MDUyNjE1MDMzMloXDTI3MDUy
NjE1MDMzMlowRDESMBAGA1UEAwwJc3Rvd3ppbGxhMRkwFwYKCZImiZPyLGQBGRYJ
c3Rvd3ppbGxhMRMwEQYKCZImiZPyLGQBGRYDY29tMIICIjANBgkqhkiG9w0BAQEF
AAOCAg8AMIICCgKCAgEAmLd7XtS7QkSvRl7eHVJwwvUTWG4Rk/uHRGZR09Cb/Hr3
ehCdHQvcVxyotmg3qZh4yyf7jZfiESMlm3iXqPsjm7IyVKHBky0n9WB7xFDZe5fh
gZwXAA5OXA0RGCaqMSnps3WJIzvUZkOZ2dnSH+oF2mK/7i4SUMN1nVMXdzu1bM3b
jwhMbhsYztQSuypugDKV/87RitsHHN+1qDFP6DJRhSgXy9tFzYconRJBgIag/Kyy
PQmAOTPfCoZeMmNllOJsd3F2W2u++bSKIGCJ2JD86lmggLwI+8kT1b9nOD8ULEGu
GcPrqHWShn3lVDXCQHJVU1pGN2DybtCVO88+Sf4UxT61TPKAYj/dMYMyYta1aYfK
u4RtgGkUD7eJYzJ4QSpNJgr+eUQXMirwn7xPRhBIWIZx4QbnAmZRfNpLjoPRiC9k
V6/rjMYAeJU6z2jnOySZGN97cTbTNK7m9QyR5U0U5WQi0IBpB+0xe6c8ihKyPOeC
hsLkZ7VxuO0gqfUDKG9DqX4/+xSqNKO8bwYa+nbBtZPjPWFyHQGQWKXOLLFK/VHk
0+6V6HosjoaRW4M1gVlu+vGWHcpmdPOmfPShkqNgZQkvhhrdg2U/xyFzLrHoRTG3
9JsjpfIGO+PAK3LPNy3YjZJcLnk8rLaaksUt691d1KH0y6e411vKoqxmZ3pHeu8C
AwEAAaNTMFEwHQYDVR0OBBYEFJdJFQTOcHjR+03ZWZ7p9nnCVRBRMB8GA1UdIwQY
MBaAFJdJFQTOcHjR+03ZWZ7p9nnCVRBRMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZI
hvcNAQELBQADggIBAFVHepD8diydFHg62HRWf7lETEoupJC7bUvSamgWi9r5svKK
AhhRErJu5JJIcFsS0tQQNVucbJXbaEVfuQ0jHpIT1TnVQGKALE9nIFt90epjAlzB
IrWWn4CgEWG8ek/81vvJZ7nBBq+BS11/yi2sx/j/eSArY/5ElM6rEnKrG/E7YnZD
eCnF794rc17PdbNmLkjUWK99EA/v0424L7l8RU/2FvgHXa57xuZcY7LqpQIwaKIU
uDBvSASNCzuBe+XXx5eEIpvEpBysDrGtIZ99IkJe+6XtAXnJRefq3fEJCaEzUPky
qoZFv1pK+U+ziCXTVH33+daPpDa+UqUAfhX+qGj7ZyKKP5i3tHi76DtVAqA4rkHE
riNRHVNH6TJnvBA9MPDNGy1AzgccqwKlWNz9l0WoMxkP8xfJbsTlc2AKjNOjx/RB
Xv+HRgTnsHUgDEIFx23dsFsl71ULMii8GrarJf0GfCAYv8WNJWchWMhSaNpZGBPa
+4NJEsWBK1Rof5oTH3SS8FyWdbt1hbQ2XW5BQ94xCtsybxJYlfEkYflIg6ROlCZO
mR0ObwJ14vsQVbJibq0eRHIg8G4yV19pvEdJCli02eLl1+451M63HZAqBNuyJ9Ny
I1fxbbEBAzf7WHfoKdwFMuRZq7hpdLykCA8YQJFlLLFoXT0g41ug9iOKBtGg
-----END CERTIFICATE-----
4 changes: 4 additions & 0 deletions lib/active_item/relation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1346,6 +1346,10 @@ def preload_has_many_counts(records, assoc_name, config)
# Each query is a lightweight indexed count — no data transfer, just a number.
# Uses thread pool for parallel execution when multiple parent IDs exist.
#
# Thread safety: Aws::DynamoDB::Client is thread-safe — it uses internal
# connection pooling (Net::HTTP persistent connections per thread). The Mutex
# here protects only the shared `counts` hash, not the client itself.
#
# @param assoc_class [Class] The associated model class
# @param index_name [String] GSI index name
# @param dynamo_fk [String] The DynamoDB attribute name for the foreign key
Expand Down
7 changes: 7 additions & 0 deletions lib/active_item/validations.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@
module ActiveItem
# ActiveModel validator that checks attribute uniqueness by querying
# DynamoDB, with optional scope and custom condition support.
#
# IMPORTANT LIMITATIONS:
# - TOCTOU race condition: DynamoDB is eventually consistent, so a check-then-write
# cannot guarantee uniqueness under concurrent writes. For strong uniqueness, use
# a DynamoDB conditional put (attribute_not_exists) at the persistence layer instead.
# - DoS vector: without an index, uniqueness checks fall back to table scans. Always
# ensure validated attributes have a GSI to avoid full-table scans on write paths.
class UniquenessValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
return if value.nil? || value.to_s.empty?
Expand Down