bundle-safe-update 1.0.14
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 +7 -0
- data/CLAUDE.md +4 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +145 -0
- data/LICENSE.txt +21 -0
- data/README.md +314 -0
- data/Rakefile +12 -0
- data/bin/console +11 -0
- data/bin/install-hooks +42 -0
- data/bin/setup +8 -0
- data/bundle-safe-update.gemspec +30 -0
- data/exe/bundle-safe-update +6 -0
- data/lib/bundle_safe_update/audit_checker.rb +86 -0
- data/lib/bundle_safe_update/cli/options.rb +52 -0
- data/lib/bundle_safe_update/cli/output.rb +155 -0
- data/lib/bundle_safe_update/cli.rb +140 -0
- data/lib/bundle_safe_update/color_output.rb +41 -0
- data/lib/bundle_safe_update/config.rb +142 -0
- data/lib/bundle_safe_update/gem_checker.rb +109 -0
- data/lib/bundle_safe_update/lockfile_parser.rb +72 -0
- data/lib/bundle_safe_update/outdated_checker.rb +61 -0
- data/lib/bundle_safe_update/risk_cache.rb +82 -0
- data/lib/bundle_safe_update/risk_checker.rb +154 -0
- data/lib/bundle_safe_update/rubygems_api.rb +98 -0
- data/lib/bundle_safe_update/version.rb +5 -0
- data/lib/bundle_safe_update.rb +16 -0
- metadata +70 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 92790321156e4545484b954c3f7f0b011172948695a3b7bc7ca74ca0917a3e80
|
|
4
|
+
data.tar.gz: 2616e43947f8e60e1a5ef20b261724edd0fa63637e31db95a5ba37efa8182700
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 3cb9bdd4de67e98f238653e60d78552d23e454f3e75fcc654954fd1f65bd328e54b7d9376beead9cb9b2135e4eedc9b70fce2401776929f7715482b34ecc97ed
|
|
7
|
+
data.tar.gz: 4c6ecef10d7c4cdb566d502a52d097b2e99d5d60119f13e752aabe7634acf0235f915969a522152ea9de3db48cb57877b9aacb022604e3ea0405ce0d31285b64
|
data/CLAUDE.md
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
bundle-safe-update (1.0.14)
|
|
5
|
+
|
|
6
|
+
GEM
|
|
7
|
+
remote: https://rubygems.org/
|
|
8
|
+
specs:
|
|
9
|
+
addressable (2.8.8)
|
|
10
|
+
public_suffix (>= 2.0.2, < 8.0)
|
|
11
|
+
ast (2.4.3)
|
|
12
|
+
bigdecimal (3.3.1)
|
|
13
|
+
crack (1.0.1)
|
|
14
|
+
bigdecimal
|
|
15
|
+
rexml
|
|
16
|
+
date (3.5.1)
|
|
17
|
+
diff-lcs (1.6.2)
|
|
18
|
+
erb (6.0.0)
|
|
19
|
+
hashdiff (1.2.1)
|
|
20
|
+
io-console (0.8.1)
|
|
21
|
+
irb (1.15.3)
|
|
22
|
+
pp (>= 0.6.0)
|
|
23
|
+
rdoc (>= 4.0.0)
|
|
24
|
+
reline (>= 0.4.2)
|
|
25
|
+
json (2.18.0)
|
|
26
|
+
language_server-protocol (3.17.0.5)
|
|
27
|
+
lint_roller (1.1.0)
|
|
28
|
+
parallel (1.27.0)
|
|
29
|
+
parser (3.3.10.0)
|
|
30
|
+
ast (~> 2.4.1)
|
|
31
|
+
racc
|
|
32
|
+
pp (0.6.3)
|
|
33
|
+
prettyprint
|
|
34
|
+
prettyprint (0.2.0)
|
|
35
|
+
prism (1.6.0)
|
|
36
|
+
psych (5.3.0)
|
|
37
|
+
date
|
|
38
|
+
stringio
|
|
39
|
+
public_suffix (7.0.0)
|
|
40
|
+
racc (1.8.1)
|
|
41
|
+
rainbow (3.1.1)
|
|
42
|
+
rake (13.3.1)
|
|
43
|
+
rdoc (6.17.0)
|
|
44
|
+
erb
|
|
45
|
+
psych (>= 4.0.0)
|
|
46
|
+
tsort
|
|
47
|
+
regexp_parser (2.11.3)
|
|
48
|
+
reline (0.6.3)
|
|
49
|
+
io-console (~> 0.5)
|
|
50
|
+
rexml (3.4.4)
|
|
51
|
+
rspec (3.13.2)
|
|
52
|
+
rspec-core (~> 3.13.0)
|
|
53
|
+
rspec-expectations (~> 3.13.0)
|
|
54
|
+
rspec-mocks (~> 3.13.0)
|
|
55
|
+
rspec-core (3.13.6)
|
|
56
|
+
rspec-support (~> 3.13.0)
|
|
57
|
+
rspec-expectations (3.13.5)
|
|
58
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
59
|
+
rspec-support (~> 3.13.0)
|
|
60
|
+
rspec-mocks (3.13.7)
|
|
61
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
62
|
+
rspec-support (~> 3.13.0)
|
|
63
|
+
rspec-support (3.13.6)
|
|
64
|
+
rubocop (1.81.7)
|
|
65
|
+
json (~> 2.3)
|
|
66
|
+
language_server-protocol (~> 3.17.0.2)
|
|
67
|
+
lint_roller (~> 1.1.0)
|
|
68
|
+
parallel (~> 1.10)
|
|
69
|
+
parser (>= 3.3.0.2)
|
|
70
|
+
rainbow (>= 2.2.2, < 4.0)
|
|
71
|
+
regexp_parser (>= 2.9.3, < 3.0)
|
|
72
|
+
rubocop-ast (>= 1.47.1, < 2.0)
|
|
73
|
+
ruby-progressbar (~> 1.7)
|
|
74
|
+
unicode-display_width (>= 2.4.0, < 4.0)
|
|
75
|
+
rubocop-ast (1.48.0)
|
|
76
|
+
parser (>= 3.3.7.2)
|
|
77
|
+
prism (~> 1.4)
|
|
78
|
+
ruby-progressbar (1.13.0)
|
|
79
|
+
stringio (3.1.9)
|
|
80
|
+
tsort (0.2.0)
|
|
81
|
+
unicode-display_width (3.2.0)
|
|
82
|
+
unicode-emoji (~> 4.1)
|
|
83
|
+
unicode-emoji (4.1.0)
|
|
84
|
+
webmock (3.26.1)
|
|
85
|
+
addressable (>= 2.8.0)
|
|
86
|
+
crack (>= 0.3.2)
|
|
87
|
+
hashdiff (>= 0.4.0, < 2.0.0)
|
|
88
|
+
|
|
89
|
+
PLATFORMS
|
|
90
|
+
arm64-darwin-23
|
|
91
|
+
ruby
|
|
92
|
+
|
|
93
|
+
DEPENDENCIES
|
|
94
|
+
bundle-safe-update!
|
|
95
|
+
irb
|
|
96
|
+
rake (~> 13.0)
|
|
97
|
+
rspec (~> 3.0)
|
|
98
|
+
rubocop (~> 1.21)
|
|
99
|
+
webmock (~> 3.0)
|
|
100
|
+
|
|
101
|
+
CHECKSUMS
|
|
102
|
+
addressable (2.8.8) sha256=7c13b8f9536cf6364c03b9d417c19986019e28f7c00ac8132da4eb0fe393b057
|
|
103
|
+
ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383
|
|
104
|
+
bigdecimal (3.3.1) sha256=eaa01e228be54c4f9f53bf3cc34fe3d5e845c31963e7fcc5bedb05a4e7d52218
|
|
105
|
+
bundle-safe-update (1.0.14)
|
|
106
|
+
crack (1.0.1) sha256=ff4a10390cd31d66440b7524eb1841874db86201d5b70032028553130b6d4c7e
|
|
107
|
+
date (3.5.1) sha256=750d06384d7b9c15d562c76291407d89e368dda4d4fff957eb94962d325a0dc0
|
|
108
|
+
diff-lcs (1.6.2) sha256=9ae0d2cba7d4df3075fe8cd8602a8604993efc0dfa934cff568969efb1909962
|
|
109
|
+
erb (6.0.0) sha256=2730893f9d8c9733f16cab315a4e4b71c1afa9cabc1a1e7ad1403feba8f52579
|
|
110
|
+
hashdiff (1.2.1) sha256=9c079dbc513dfc8833ab59c0c2d8f230fa28499cc5efb4b8dd276cf931457cd1
|
|
111
|
+
io-console (0.8.1) sha256=1e15440a6b2f67b6ea496df7c474ed62c860ad11237f29b3bd187f054b925fcb
|
|
112
|
+
irb (1.15.3) sha256=4349edff1efa7ff7bfd34cb9df74a133a588ba88c2718098b3b4468b81184aaa
|
|
113
|
+
json (2.18.0) sha256=b10506aee4183f5cf49e0efc48073d7b75843ce3782c68dbeb763351c08fd505
|
|
114
|
+
language_server-protocol (3.17.0.5) sha256=fd1e39a51a28bf3eec959379985a72e296e9f9acfce46f6a79d31ca8760803cc
|
|
115
|
+
lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87
|
|
116
|
+
parallel (1.27.0) sha256=4ac151e1806b755fb4e2dc2332cbf0e54f2e24ba821ff2d3dcf86bf6dc4ae130
|
|
117
|
+
parser (3.3.10.0) sha256=ce3587fa5cc55a88c4ba5b2b37621b3329aadf5728f9eafa36bbd121462aabd6
|
|
118
|
+
pp (0.6.3) sha256=2951d514450b93ccfeb1df7d021cae0da16e0a7f95ee1e2273719669d0ab9df6
|
|
119
|
+
prettyprint (0.2.0) sha256=2bc9e15581a94742064a3cc8b0fb9d45aae3d03a1baa6ef80922627a0766f193
|
|
120
|
+
prism (1.6.0) sha256=bfc0281a81718c4872346bc858dc84abd3a60cae78336c65ad35c8fbff641c6b
|
|
121
|
+
psych (5.3.0) sha256=8976a41ae29ea38c88356e862629345290347e3bfe27caf654f7c5a920e95eeb
|
|
122
|
+
public_suffix (7.0.0) sha256=f7090b5beb0e56f9f10d79eed4d5fbe551b3b425da65877e075dad47a6a1b095
|
|
123
|
+
racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f
|
|
124
|
+
rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a
|
|
125
|
+
rake (13.3.1) sha256=8c9e89d09f66a26a01264e7e3480ec0607f0c497a861ef16063604b1b08eb19c
|
|
126
|
+
rdoc (6.17.0) sha256=0f50d4e568fc98195f9bb155a9e8dff6c7feabfb515fb22ef6df1d12ad5a02b7
|
|
127
|
+
regexp_parser (2.11.3) sha256=ca13f381a173b7a93450e53459075c9b76a10433caadcb2f1180f2c741fc55a4
|
|
128
|
+
reline (0.6.3) sha256=1198b04973565b36ec0f11542ab3f5cfeeec34823f4e54cebde90968092b1835
|
|
129
|
+
rexml (3.4.4) sha256=19e0a2c3425dfbf2d4fc1189747bdb2f849b6c5e74180401b15734bc97b5d142
|
|
130
|
+
rspec (3.13.2) sha256=206284a08ad798e61f86d7ca3e376718d52c0bc944626b2349266f239f820587
|
|
131
|
+
rspec-core (3.13.6) sha256=a8823c6411667b60a8bca135364351dda34cd55e44ff94c4be4633b37d828b2d
|
|
132
|
+
rspec-expectations (3.13.5) sha256=33a4d3a1d95060aea4c94e9f237030a8f9eae5615e9bd85718fe3a09e4b58836
|
|
133
|
+
rspec-mocks (3.13.7) sha256=0979034e64b1d7a838aaaddf12bf065ea4dc40ef3d4c39f01f93ae2c66c62b1c
|
|
134
|
+
rspec-support (3.13.6) sha256=2e8de3702427eab064c9352fe74488cc12a1bfae887ad8b91cba480ec9f8afb2
|
|
135
|
+
rubocop (1.81.7) sha256=6fb5cc298c731691e2a414fe0041a13eb1beed7bab23aec131da1bcc527af094
|
|
136
|
+
rubocop-ast (1.48.0) sha256=22df9bbf3f7a6eccde0fad54e68547ae1e2a704bf8719e7c83813a99c05d2e76
|
|
137
|
+
ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33
|
|
138
|
+
stringio (3.1.9) sha256=c111af13d3a73eab96a3bc2655ecf93788d13d28cb8e25c1dcbff89ace885121
|
|
139
|
+
tsort (0.2.0) sha256=9650a793f6859a43b6641671278f79cfead60ac714148aabe4e3f0060480089f
|
|
140
|
+
unicode-display_width (3.2.0) sha256=0cdd96b5681a5949cdbc2c55e7b420facae74c4aaf9a9815eee1087cb1853c42
|
|
141
|
+
unicode-emoji (4.1.0) sha256=4997d2d5df1ed4252f4830a9b6e86f932e2013fbff2182a9ce9ccabda4f325a5
|
|
142
|
+
webmock (3.26.1) sha256=4f696fb57c90a827c20aadb2d4f9058bbff10f7f043bd0d4c3f58791143b1cd7
|
|
143
|
+
|
|
144
|
+
BUNDLED WITH
|
|
145
|
+
4.0.0.beta1
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Denis Sablic
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
# bundle-safe-update
|
|
2
|
+
|
|
3
|
+
A CLI tool that enforces a minimum release age for Ruby gems during updates, preventing installation of gem versions that are "too new" (e.g., less than 14 days old). This helps protect against supply chain attacks by ensuring gems have had time for community review.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
gem install bundle-safe-update
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or add to your Gemfile:
|
|
12
|
+
|
|
13
|
+
```ruby
|
|
14
|
+
gem 'bundle-safe-update', group: :development
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
Run in your project directory:
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
bundle-safe-update [options] [gem1 gem2 ...]
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Check all outdated gems:
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
bundle-safe-update
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Check and update specific gems:
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
bundle-safe-update rails sidekiq
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### CLI Options
|
|
38
|
+
|
|
39
|
+
| Option | Description |
|
|
40
|
+
|--------|-------------|
|
|
41
|
+
| `--config PATH` | Path to config file |
|
|
42
|
+
| `--cooldown DAYS` | Minimum age in days (overrides config) |
|
|
43
|
+
| `--update` | Update gems that pass the cooldown check |
|
|
44
|
+
| `--warn-only` | Report violations but exit with success |
|
|
45
|
+
| `--no-audit` | Skip vulnerability audit |
|
|
46
|
+
| `--no-risk` | Skip risk signal checking |
|
|
47
|
+
| `--refresh-cache` | Refresh owner cache without warnings |
|
|
48
|
+
| `--json` | Output in JSON format for CI systems |
|
|
49
|
+
| `--verbose` | Enable verbose output |
|
|
50
|
+
| `--dry-run` | Show configuration without checking |
|
|
51
|
+
| `-v, --version` | Show version |
|
|
52
|
+
| `-h, --help` | Show help |
|
|
53
|
+
|
|
54
|
+
### Example Output
|
|
55
|
+
|
|
56
|
+
Human-readable output:
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
Checking gem versions...
|
|
60
|
+
OK: rails (7.1.3.2) - satisfies minimum age (42 days)
|
|
61
|
+
BLOCKED: nokogiri (1.16.4) - published 3 days ago (< 14 required)
|
|
62
|
+
|
|
63
|
+
1 gem(s) violate minimum release age
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
JSON output (`--json`):
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
{
|
|
70
|
+
"ok": false,
|
|
71
|
+
"cooldown_days": 14,
|
|
72
|
+
"checked": 2,
|
|
73
|
+
"blocked": [
|
|
74
|
+
{ "name": "nokogiri", "version": "1.16.4", "age_days": 3 }
|
|
75
|
+
]
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Updating Safe Gems
|
|
80
|
+
|
|
81
|
+
By default, `bundle-safe-update` only checks gems and reports results. Use `--update` to automatically update gems that pass the cooldown check:
|
|
82
|
+
|
|
83
|
+
```sh
|
|
84
|
+
bundle-safe-update --update
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Example output:
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
OK: rails (7.1.3.2) - satisfies minimum age
|
|
91
|
+
BLOCKED: nokogiri (1.16.4) - published 3 days ago (< 14 required)
|
|
92
|
+
|
|
93
|
+
1 gem(s) violate minimum release age
|
|
94
|
+
|
|
95
|
+
Updating 1 gem(s): rails
|
|
96
|
+
Running: bundle update rails
|
|
97
|
+
Bundle updated successfully.
|
|
98
|
+
|
|
99
|
+
Skipped 1 blocked gem(s): nokogiri
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Updating Specific Gems
|
|
103
|
+
|
|
104
|
+
To check and update specific gems, pass their names as arguments:
|
|
105
|
+
|
|
106
|
+
```sh
|
|
107
|
+
bundle-safe-update --update rails sidekiq
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Only the specified gems are checked and updated if they pass the cooldown check. Without `--update`, specific gems are checked but not updated:
|
|
111
|
+
|
|
112
|
+
```sh
|
|
113
|
+
bundle-safe-update rails sidekiq
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Vulnerability Auditing
|
|
117
|
+
|
|
118
|
+
By default, bundle-safe-update runs `bundle audit` to check for known security vulnerabilities. This requires the `bundler-audit` gem to be installed:
|
|
119
|
+
|
|
120
|
+
```sh
|
|
121
|
+
gem install bundler-audit
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
If `bundler-audit` is not installed, a warning is displayed but the check continues. The audit database is automatically updated before each check.
|
|
125
|
+
|
|
126
|
+
Example output with vulnerabilities:
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
OK: rails (7.1.3.2) - satisfies minimum age
|
|
130
|
+
|
|
131
|
+
Checking for vulnerabilities...
|
|
132
|
+
VULNERABLE: actionpack (CVE-2024-1234) - Possible XSS vulnerability
|
|
133
|
+
Solution: upgrade to >= 7.0.8.1
|
|
134
|
+
|
|
135
|
+
1 vulnerability(ies) found
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
To skip the audit check, use `--no-audit` or set `audit: false` in config.
|
|
139
|
+
|
|
140
|
+
### Risk Intelligence
|
|
141
|
+
|
|
142
|
+
Bundle-safe-update analyzes gems for risk signals that may indicate supply chain threats:
|
|
143
|
+
|
|
144
|
+
| Signal | Description | Default Threshold |
|
|
145
|
+
|--------|-------------|-------------------|
|
|
146
|
+
| Low downloads | Gems with very few total downloads | < 1,000 |
|
|
147
|
+
| Stale gem | Gems not updated recently | > 3 years |
|
|
148
|
+
| New owner | Gems with recent ownership changes | Ownership changed since last run |
|
|
149
|
+
| Version jump | Major version bumps | Any major bump |
|
|
150
|
+
|
|
151
|
+
Example output with risk warnings:
|
|
152
|
+
|
|
153
|
+
```
|
|
154
|
+
OK: rails (7.1.3.2) - satisfies minimum age
|
|
155
|
+
|
|
156
|
+
Risk signals:
|
|
157
|
+
WARNING: tiny-lib (2.0.0) - low downloads (847 total)
|
|
158
|
+
WARNING: old-parser (1.5.0) - stale gem (last release 4.2 years ago)
|
|
159
|
+
BLOCKED: some-gem (5.0.0) - major version jump (was 2.3.1)
|
|
160
|
+
|
|
161
|
+
1 gem(s) blocked by risk signals
|
|
162
|
+
2 risk warning(s)
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Each signal can be set to `warn` (default), `block`, or `off`:
|
|
166
|
+
|
|
167
|
+
```yaml
|
|
168
|
+
risk_signals:
|
|
169
|
+
low_downloads:
|
|
170
|
+
mode: warn # off | warn | block
|
|
171
|
+
threshold: 1000 # minimum total downloads
|
|
172
|
+
|
|
173
|
+
stale_gem:
|
|
174
|
+
mode: warn
|
|
175
|
+
threshold_years: 3 # years since last release
|
|
176
|
+
|
|
177
|
+
new_owner:
|
|
178
|
+
mode: block # block on ownership changes
|
|
179
|
+
threshold_days: 90 # (reserved for future use)
|
|
180
|
+
|
|
181
|
+
version_jump:
|
|
182
|
+
mode: warn
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Owner changes are detected by caching gem owners locally (`.bundle/bundle-safe-update-cache.yml`). On first run, no warnings are generated - owners are just cached. Subsequent runs detect changes.
|
|
186
|
+
|
|
187
|
+
Use `--refresh-cache` to rebuild the cache without triggering warnings (useful after intentional ownership changes). Use `--no-risk` to skip risk checking entirely.
|
|
188
|
+
|
|
189
|
+
## Configuration
|
|
190
|
+
|
|
191
|
+
Create `.bundle-safe-update.yml` in your project root or home directory:
|
|
192
|
+
|
|
193
|
+
```yaml
|
|
194
|
+
# Minimum age in days for gem versions (default: 14)
|
|
195
|
+
cooldown_days: 14
|
|
196
|
+
|
|
197
|
+
# Gems to ignore completely (e.g., internal gems)
|
|
198
|
+
ignore_gems:
|
|
199
|
+
- rails
|
|
200
|
+
- sidekiq
|
|
201
|
+
|
|
202
|
+
# Prefixes to ignore (e.g., company gems)
|
|
203
|
+
ignore_prefixes:
|
|
204
|
+
- mycompany-
|
|
205
|
+
- internal-
|
|
206
|
+
|
|
207
|
+
# Trust gems from specific sources (skip cooldown check)
|
|
208
|
+
# Useful for private gem servers where gems are already vetted
|
|
209
|
+
trusted_sources:
|
|
210
|
+
- gems.mycompany.com
|
|
211
|
+
- gemserver.internal.example.com
|
|
212
|
+
|
|
213
|
+
# Trust gems by RubyGems owner/publisher (skip cooldown check)
|
|
214
|
+
# Useful for well-known publishers like AWS, Google, etc.
|
|
215
|
+
trusted_owners:
|
|
216
|
+
- awscloud # AWS SDK gems
|
|
217
|
+
|
|
218
|
+
# Automatically update gems that pass the cooldown check (default: false)
|
|
219
|
+
update: false
|
|
220
|
+
|
|
221
|
+
# Run vulnerability audit with bundler-audit (default: true)
|
|
222
|
+
audit: true
|
|
223
|
+
|
|
224
|
+
# Report violations but always exit with success (default: false)
|
|
225
|
+
warn_only: false
|
|
226
|
+
|
|
227
|
+
# Enable verbose output
|
|
228
|
+
verbose: false
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Trusted Sources
|
|
232
|
+
|
|
233
|
+
Gems from trusted sources skip the cooldown check entirely. The source is determined by parsing `Gemfile.lock`. This is useful for:
|
|
234
|
+
|
|
235
|
+
- Private gem servers (Cloudsmith, Gemfury, self-hosted)
|
|
236
|
+
- Internal gems that are already vetted by your organization
|
|
237
|
+
|
|
238
|
+
Example output for trusted gems:
|
|
239
|
+
```
|
|
240
|
+
OK: mycompany-auth (1.2.0) - trusted source
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Trusted Owners
|
|
244
|
+
|
|
245
|
+
Gems owned by trusted RubyGems users skip the cooldown check. The owner is fetched from the RubyGems API. This is useful for:
|
|
246
|
+
|
|
247
|
+
- Well-known publishers (AWS, Google, Rails core team, etc.)
|
|
248
|
+
- Organizations with strong security practices
|
|
249
|
+
|
|
250
|
+
Example output for trusted owner gems:
|
|
251
|
+
```
|
|
252
|
+
OK: aws-sdk-s3 (1.180.0) - trusted owner
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
To find a gem's owner, visit `https://rubygems.org/gems/{gem_name}` and look at the "Owners" section, or use:
|
|
256
|
+
```sh
|
|
257
|
+
curl https://rubygems.org/api/v1/gems/{gem_name}/owners.json
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Config Resolution Order
|
|
261
|
+
|
|
262
|
+
1. CLI flags (highest priority)
|
|
263
|
+
2. Project `.bundle-safe-update.yml`
|
|
264
|
+
3. Home directory `~/.bundle-safe-update.yml`
|
|
265
|
+
4. Built-in defaults
|
|
266
|
+
|
|
267
|
+
## Exit Codes
|
|
268
|
+
|
|
269
|
+
| Code | Meaning |
|
|
270
|
+
|------|---------|
|
|
271
|
+
| 0 | All checks passed |
|
|
272
|
+
| 1 | Blocked by cooldown, risk signals, or vulnerabilities |
|
|
273
|
+
| 2 | Unexpected error |
|
|
274
|
+
|
|
275
|
+
## CI Integration
|
|
276
|
+
|
|
277
|
+
### AWS CodeBuild
|
|
278
|
+
|
|
279
|
+
```yaml
|
|
280
|
+
version: 0.2
|
|
281
|
+
phases:
|
|
282
|
+
install:
|
|
283
|
+
commands:
|
|
284
|
+
- gem install bundle-safe-update
|
|
285
|
+
build:
|
|
286
|
+
commands:
|
|
287
|
+
- bundle-safe-update --json
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### GitHub Actions
|
|
291
|
+
|
|
292
|
+
```yaml
|
|
293
|
+
- name: Check gem versions
|
|
294
|
+
run: |
|
|
295
|
+
gem install bundle-safe-update
|
|
296
|
+
bundle-safe-update --json
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
## Development
|
|
300
|
+
|
|
301
|
+
```sh
|
|
302
|
+
# Install dependencies
|
|
303
|
+
bundle install
|
|
304
|
+
|
|
305
|
+
# Run tests
|
|
306
|
+
bundle exec rspec
|
|
307
|
+
|
|
308
|
+
# Run linter
|
|
309
|
+
bundle exec rubocop
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
## License
|
|
313
|
+
|
|
314
|
+
MIT License. See [LICENSE.txt](LICENSE.txt).
|
data/Rakefile
ADDED
data/bin/console
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'bundler/setup'
|
|
5
|
+
require 'bundle_safe_update'
|
|
6
|
+
|
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
|
9
|
+
|
|
10
|
+
require 'irb'
|
|
11
|
+
IRB.start(__FILE__)
|
data/bin/install-hooks
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Install git hooks for bundle-safe-update development
|
|
3
|
+
|
|
4
|
+
set -e
|
|
5
|
+
|
|
6
|
+
HOOKS_DIR=".git/hooks"
|
|
7
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
8
|
+
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
|
9
|
+
|
|
10
|
+
if [ ! -d "$PROJECT_DIR/.git" ]; then
|
|
11
|
+
echo "Error: Not a git repository"
|
|
12
|
+
exit 1
|
|
13
|
+
fi
|
|
14
|
+
|
|
15
|
+
# Create post-commit hook
|
|
16
|
+
cat > "$HOOKS_DIR/post-commit" << 'EOF'
|
|
17
|
+
#!/bin/bash
|
|
18
|
+
# Auto-tag when version.rb changes
|
|
19
|
+
|
|
20
|
+
VERSION_FILE="lib/bundle_safe_update/version.rb"
|
|
21
|
+
|
|
22
|
+
# Check if version.rb was in the commit
|
|
23
|
+
if git diff-tree --no-commit-id --name-only -r HEAD | grep -q "$VERSION_FILE"; then
|
|
24
|
+
VERSION=$(grep -o "VERSION = '[^']*'" "$VERSION_FILE" | cut -d"'" -f2)
|
|
25
|
+
TAG="v$VERSION"
|
|
26
|
+
|
|
27
|
+
if [ -z "$VERSION" ]; then
|
|
28
|
+
echo "Warning: Could not extract version from $VERSION_FILE"
|
|
29
|
+
exit 0
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
if ! git tag | grep -q "^$TAG$"; then
|
|
33
|
+
git tag "$TAG"
|
|
34
|
+
git push origin "$TAG"
|
|
35
|
+
echo "Tagged and pushed $TAG"
|
|
36
|
+
fi
|
|
37
|
+
fi
|
|
38
|
+
EOF
|
|
39
|
+
|
|
40
|
+
chmod +x "$HOOKS_DIR/post-commit"
|
|
41
|
+
|
|
42
|
+
echo "Installed post-commit hook"
|
data/bin/setup
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'lib/bundle_safe_update/version'
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = 'bundle-safe-update'
|
|
7
|
+
spec.version = BundleSafeUpdate::VERSION
|
|
8
|
+
spec.authors = ['Denis Sablic']
|
|
9
|
+
spec.email = ['denis.sablic@gmail.com']
|
|
10
|
+
|
|
11
|
+
spec.summary = 'Enforce minimum release age for Ruby gems during updates'
|
|
12
|
+
spec.description = 'A CLI tool that prevents installation of gem versions ' \
|
|
13
|
+
'that are too new (e.g., <14 days old), helping protect ' \
|
|
14
|
+
'against supply chain attacks.'
|
|
15
|
+
spec.homepage = 'https://github.com/dsablic/bundle-safe-update'
|
|
16
|
+
spec.license = 'MIT'
|
|
17
|
+
spec.required_ruby_version = '>= 3.2.0'
|
|
18
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
|
19
|
+
spec.metadata['source_code_uri'] = 'https://github.com/dsablic/bundle-safe-update'
|
|
20
|
+
spec.metadata['changelog_uri'] = 'https://github.com/dsablic/bundle-safe-update/releases'
|
|
21
|
+
|
|
22
|
+
spec.files = Dir.chdir(__dir__) do
|
|
23
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
|
24
|
+
f.start_with?('spec/', '.git', '.rspec', '.rubocop', 'TODO.md')
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
spec.bindir = 'exe'
|
|
28
|
+
spec.executables = ['bundle-safe-update']
|
|
29
|
+
spec.require_paths = ['lib']
|
|
30
|
+
end
|