mitre-settingslogic 3.0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: cabca9894cc72e6bf4207679d3847b539f15ba2c99cbd30165271b3988568b86
4
+ data.tar.gz: 1dead53a7b4c408796cb7439bf15fce7a3cef28e868a755ec595d759def19252
5
+ SHA512:
6
+ metadata.gz: 70ed779744d55559bbc1db46ec27700b6406f93c88e82e157de222b30b1264361e702d1c55d9012d234f94de2f3e541f13606d36b30893530b96f76efd2cb030
7
+ data.tar.gz: f920eadd8d2e1191209d1bab0497b845978a690a0e1b03da0f4ef29189221dad2c2638a1522023bb6e4cf6e25fe4727849c533b287f1f57e9a65fa420f28e8fe
data/CHANGELOG.md ADDED
@@ -0,0 +1,67 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [3.0.0] - 2025-01-11
9
+
10
+ ### 🔒 Security (BREAKING CHANGES)
11
+
12
+ - **Critical**: Replace `YAML.unsafe_load` with `YAML.safe_load` to prevent arbitrary code execution
13
+ - Default permitted YAML classes: `Symbol, Date, Time, DateTime, BigDecimal`
14
+ - Replace vulnerable `open-uri` with `Net::HTTP` for URL loading
15
+ - Add protocol validation to block dangerous URI schemes (file://, ftp://, etc.)
16
+
17
+ ### ✨ Features
18
+
19
+ - Add Ruby 3.x compatibility (3.0, 3.1, 3.2, 3.3, 3.4)
20
+ - Add Rails 7.x and 8.x compatibility
21
+ - Add Psych 4 support with YAML alias handling
22
+ - Add configurable permitted classes via `Settingslogic.yaml_permitted_classes`
23
+ - Add migration path with deprecated `Settingslogic.use_yaml_unsafe_load` flag
24
+ - Add helpful error messages with migration instructions
25
+
26
+ ### 🐛 Fixes
27
+
28
+ - Fix RSpec Array#flatten issues with `to_ary` method
29
+ - Fix deprecated `has_key?` usage (now `key?`)
30
+ - Fix eval security with proper `__FILE__` and `__LINE__` tracking
31
+ - Fix Ruby 3.4 compatibility with explicit bigdecimal dependency
32
+ - Fix CI issues with Ruby 2.7 + Rails 6.1 zeitwerk conflict
33
+
34
+ ### 📦 Infrastructure
35
+
36
+ - Add comprehensive test suite (94.63% coverage)
37
+ - Add RuboCop linting with rubocop-rspec and rubocop-performance
38
+ - Add GitHub Actions CI for all Ruby/Rails combinations
39
+ - Add automated release tooling with version management
40
+ - Add security testing suite (19 security-specific tests)
41
+
42
+ ### 📚 Documentation
43
+
44
+ - Add comprehensive README with migration guide
45
+ - Add SECURITY.md with vulnerability reporting process
46
+ - Add ROADMAP.md for future development plans
47
+ - Add CONTRIBUTING.md for contribution guidelines
48
+ - Update all documentation for v3.0.0
49
+
50
+ ### ⚠️ Breaking Changes
51
+
52
+ - YAML files can no longer instantiate arbitrary Ruby objects by default
53
+ - To allow custom classes: `Settingslogic.yaml_permitted_classes += [MyClass]`
54
+ - Temporary opt-out available: `Settingslogic.use_yaml_unsafe_load = true` (deprecated)
55
+
56
+ ### 📝 Notes
57
+
58
+ This is a major security release addressing CVE-2022-32224-like vulnerabilities. All users should upgrade and review their YAML files for compatibility with safe_load restrictions.
59
+
60
+ ## [2.0.9] - 2012-10-19
61
+
62
+ Last release of the original gem by Ben Johnson (binarylogic).
63
+
64
+ ---
65
+
66
+ Maintained by MITRE Corporation
67
+ Primary maintainer: Aaron Lippold <lippold@gmail.com>
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,119 @@
1
+ # Contributing to Settingslogic
2
+
3
+ Thank you for your interest in contributing to the MITRE fork of settingslogic! This fork provides Ruby 3.x and Rails 7.x+ compatibility for the classic settingslogic gem.
4
+
5
+ ## 🚀 Quick Start
6
+
7
+ ```bash
8
+ # Fork and clone
9
+ git clone https://github.com/mitre/settingslogic.git
10
+ cd settingslogic
11
+
12
+ # Install dependencies
13
+ bundle install
14
+
15
+ # Run tests
16
+ bundle exec rspec
17
+ ```
18
+
19
+ ## 🛠️ Development Setup
20
+
21
+ ### Prerequisites
22
+
23
+ - Ruby 2.7+ (we test against 2.7, 3.0, 3.1, 3.2, 3.3, and 3.4)
24
+ - Bundler
25
+
26
+ ### Running Tests
27
+
28
+ ```bash
29
+ # Run all tests
30
+ bundle exec rspec
31
+
32
+ # Run tests with specific Ruby version
33
+ rbenv local 3.2.0 # or rvm use 3.2.0
34
+ bundle exec rspec
35
+
36
+ # Run linting
37
+ bundle exec rubocop
38
+ ```
39
+
40
+ ## 📝 Making Changes
41
+
42
+ 1. **Fork the repository** on GitHub
43
+ 2. **Create a feature branch** from `master`
44
+ ```bash
45
+ git checkout -b feature/my-new-feature
46
+ ```
47
+ 3. **Make your changes**
48
+ - Add tests for any new functionality
49
+ - Ensure all tests pass
50
+ - Follow existing code style
51
+ 4. **Commit your changes**
52
+ ```bash
53
+ git commit -m "Add new feature"
54
+ ```
55
+ 5. **Push to your fork**
56
+ ```bash
57
+ git push origin feature/my-new-feature
58
+ ```
59
+ 6. **Create a Pull Request** on GitHub
60
+
61
+ ## 🧪 Testing Requirements
62
+
63
+ All changes must:
64
+ - Include tests for new functionality
65
+ - Pass all existing tests
66
+ - Work with Ruby 2.7 through 3.4
67
+ - Maintain backwards compatibility where possible
68
+
69
+ ### Testing YAML Aliases
70
+
71
+ Since the main purpose of this fork is Psych 4 compatibility, ensure YAML aliases work:
72
+
73
+ ```ruby
74
+ # test.yml
75
+ defaults: &defaults
76
+ host: localhost
77
+ port: 3000
78
+
79
+ development:
80
+ <<: *defaults
81
+ database: dev_db
82
+ ```
83
+
84
+ ## 🎯 Focus Areas
85
+
86
+ We're particularly interested in:
87
+ - Ruby 3.x compatibility improvements
88
+ - Rails 7.x and 8.x compatibility
89
+ - Performance improvements
90
+ - Security enhancements
91
+ - Better error messages
92
+
93
+ ## 📋 Code Style
94
+
95
+ - Use frozen string literals
96
+ - Follow Ruby community conventions
97
+ - Run `bundle exec rubocop` before committing
98
+ - Keep methods small and focused
99
+ - Document complex logic
100
+
101
+ ## 🐛 Reporting Issues
102
+
103
+ When reporting issues, please include:
104
+ - Ruby version
105
+ - Rails version (if applicable)
106
+ - Minimal reproduction example
107
+ - Full error message and stack trace
108
+
109
+ ## 📄 License
110
+
111
+ By contributing, you agree that your contributions will be licensed under the MIT License.
112
+
113
+ ## 📧 Contact
114
+
115
+ For questions about contributing, please open an issue on GitHub or contact the maintainers at lippold@gmail.com.
116
+
117
+ ## 🙏 Acknowledgments
118
+
119
+ Thanks to Ben Johnson for creating the original settingslogic gem, and to all the fork maintainers who have kept it alive for the Ruby community.
data/LICENSE.md ADDED
@@ -0,0 +1,16 @@
1
+ ---
2
+ title: License
3
+ description: Apache 2.0 license for the cyber-trackr-live project
4
+ layout: doc
5
+ sidebar: true
6
+ ---
7
+
8
+ Licensed under the apache-2.0 license, except as noted below.
9
+
10
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
11
+
12
+ * Redistributions of source code must retain the above copyright/ digital rights legend, this list of conditions and the following Notice.
13
+
14
+ * Redistributions in binary form must reproduce the above copyright copyright/ digital rights legend, this list of conditions and the following Notice in the documentation and/or other materials provided with the distribution.
15
+
16
+ * Neither the name of The MITRE Corporation nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
data/README.md ADDED
@@ -0,0 +1,204 @@
1
+ # Settingslogic - MITRE Fork
2
+
3
+ [![CI](https://github.com/mitre/settingslogic/actions/workflows/ci.yml/badge.svg)](https://github.com/mitre/settingslogic/actions/workflows/ci.yml)
4
+ [![Gem Version](https://badge.fury.io/rb/settingslogic.svg)](http://badge.fury.io/rb/settingslogic)
5
+
6
+ A simple and straightforward settings solution that uses an ERB enabled YAML file and a singleton design pattern. This is a MITRE-maintained fork with Ruby 3.x and Rails 7.x+ compatibility.
7
+
8
+ ## 🎯 Why This Fork?
9
+
10
+ The original settingslogic gem hasn't been updated since 2012 but is still widely used. This fork provides:
11
+
12
+ - ✅ **Ruby 3.x compatibility** - Full support for Ruby 3.0, 3.1, 3.2, 3.3, and 3.4
13
+ - ✅ **Psych 4 support** - Handles YAML aliases correctly with Ruby 3.1+
14
+ - ✅ **Rails 7.x/8.x compatibility** - Works with modern Rails versions
15
+ - ✅ **Security updates** - Modern security practices and dependency updates
16
+ - ✅ **Maintained** - Active maintenance and support
17
+
18
+ ## 📦 Installation
19
+
20
+ Add this to your Gemfile:
21
+
22
+ ```ruby
23
+ # Use the MITRE fork for Ruby 3.x compatibility
24
+ gem 'settingslogic', github: 'mitre/settingslogic', branch: 'master'
25
+ ```
26
+
27
+ Or if we publish to RubyGems:
28
+
29
+ ```ruby
30
+ gem 'mitre-settingslogic'
31
+ ```
32
+
33
+ ## 🚀 Quick Start
34
+
35
+ ### 1. Define your settings class
36
+
37
+ ```ruby
38
+ # app/models/settings.rb (for Rails)
39
+ # or anywhere in your Ruby project
40
+ class Settings < Settingslogic
41
+ source "#{Rails.root}/config/application.yml"
42
+ namespace Rails.env
43
+ end
44
+ ```
45
+
46
+ ### 2. Create your YAML configuration
47
+
48
+ ```yaml
49
+ # config/application.yml
50
+ defaults: &defaults
51
+ host: localhost
52
+ port: 3000
53
+ ssl: false
54
+
55
+ development:
56
+ <<: *defaults
57
+ database: myapp_development
58
+
59
+ production:
60
+ <<: *defaults
61
+ host: production.example.com
62
+ port: 443
63
+ ssl: true
64
+ database: myapp_production
65
+ ```
66
+
67
+ ### 3. Use your settings
68
+
69
+ ```ruby
70
+ Settings.host # => "localhost" in development, "production.example.com" in production
71
+ Settings.port # => 3000 in development, 443 in production
72
+ Settings.ssl? # => false in development, true in production
73
+ Settings.database # => "myapp_development" in development
74
+
75
+ # Nested settings
76
+ Settings.smtp.address # => Access nested configuration
77
+ Settings['smtp']['address'] # => Hash-style access also works
78
+ ```
79
+
80
+ ## 🔧 Advanced Features
81
+
82
+ ### Dynamic Settings
83
+
84
+ ```ruby
85
+ # Settings with ERB
86
+ production:
87
+ secret_key: <%= ENV['SECRET_KEY_BASE'] %>
88
+ redis_url: <%= ENV['REDIS_URL'] || 'redis://localhost:6379' %>
89
+ ```
90
+
91
+ ### Multiple Configuration Files
92
+
93
+ ```ruby
94
+ class DatabaseSettings < Settingslogic
95
+ source "#{Rails.root}/config/database_settings.yml"
96
+ namespace Rails.env
97
+ end
98
+
99
+ class FeatureFlags < Settingslogic
100
+ source "#{Rails.root}/config/features.yml"
101
+ namespace Rails.env
102
+ end
103
+ ```
104
+
105
+ ### Suppress Errors
106
+
107
+ ```ruby
108
+ class Settings < Settingslogic
109
+ source "#{Rails.root}/config/application.yml"
110
+ namespace Rails.env
111
+ suppress_errors true # Returns nil instead of raising errors for missing keys
112
+ end
113
+ ```
114
+
115
+ ### Dynamic Access
116
+
117
+ ```ruby
118
+ # Access nested keys with dot notation
119
+ Settings.get('database.pool.size') # => 5
120
+ Settings.get('redis.cache.ttl') # => 3600
121
+ ```
122
+
123
+ ## 🏗️ What's Fixed in This Fork
124
+
125
+ ### Psych 4 / Ruby 3.1+ Compatibility
126
+
127
+ The main issue with the original gem is that Ruby 3.1+ ships with Psych 4, which disables YAML aliases by default. This fork handles that correctly:
128
+
129
+ ```ruby
130
+ # This YAML with aliases now works correctly in Ruby 3.1+
131
+ defaults: &defaults
132
+ timeout: 30
133
+ retries: 3
134
+
135
+ production:
136
+ <<: *defaults # This alias expansion works!
137
+ timeout: 60
138
+ ```
139
+
140
+ ### Other Improvements
141
+
142
+ - ✅ Fixed deprecated `has_key?` → `key?`
143
+ - ✅ Added `to_ary` method for RSpec compatibility
144
+ - ✅ Improved `symbolize_keys` for nested hashes
145
+ - ✅ Added `stringify_keys` for Rails compatibility
146
+ - ✅ Better error messages
147
+ - ✅ Security improvements for eval usage
148
+ - ✅ Frozen string literals
149
+ - ✅ Modern Ruby idioms
150
+
151
+ ## 🧪 Compatibility
152
+
153
+ Tested and working with:
154
+
155
+ - **Ruby:** 2.7, 3.0, 3.1, 3.2, 3.3, 3.4
156
+ - **Rails:** 5.2, 6.0, 6.1, 7.0, 7.1, 8.0
157
+ - **Psych:** 3.x and 4.x
158
+
159
+ ## 🔒 Security
160
+
161
+ ### YAML Safe Loading (v3.0.0+)
162
+ - **Default behavior**: Uses `YAML.safe_load` to prevent arbitrary code execution
163
+ - **Permitted classes**: `Symbol, Date, Time, DateTime, BigDecimal`
164
+ - **Custom classes**: Add via `Settingslogic.yaml_permitted_classes += [MyClass]`
165
+ - **Migration path**: Temporary opt-out with `Settingslogic.use_yaml_unsafe_load = true` (deprecated, will be removed in v4.0.0)
166
+
167
+ ### Other Security Features
168
+ - URL loading uses `Net::HTTP` instead of vulnerable `open-uri`
169
+ - All eval usage includes proper `__FILE__` and `__LINE__` tracking
170
+ - No arbitrary code execution vulnerabilities in default configuration
171
+
172
+ ## 🤝 Contributing
173
+
174
+ We welcome contributions! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details.
175
+
176
+ ## 📄 License
177
+
178
+ This project is licensed under the MIT License - see [LICENSE.md](LICENSE.md) for details.
179
+
180
+ ## 🙏 Acknowledgments
181
+
182
+ - Original gem created by [Ben Johnson](https://github.com/binarylogic) (binarylogic)
183
+ - Community maintainers at [settingslogic/settingslogic](https://github.com/settingslogic/settingslogic)
184
+ - Key fixes incorporated from:
185
+ - [minorun99/settingslogic](https://github.com/minorun99/settingslogic) - Ruby 3.2 compatibility
186
+ - [etozzato/settingslogic](https://github.com/etozzato/settingslogic) - Psych 4 safe_load implementation
187
+ - [bigcommerce/settingslogic](https://github.com/bigcommerce/settingslogic) - Various compatibility fixes
188
+ - [tvw/settingslogic](https://github.com/tvw/settingslogic) - Ruby 3 support
189
+ - And many others who kept this gem alive through their forks and PRs
190
+ - Special thanks to all who submitted PRs to the original repo, especially PR #86 for Psych 4 compatibility
191
+
192
+ ## 📚 Documentation
193
+
194
+ - [Original Documentation](http://rdoc.info/github/binarylogic/settingslogic)
195
+ - [MITRE Fork Issues](https://github.com/mitre/settingslogic/issues)
196
+ - [MITRE Fork Repository](https://github.com/mitre/settingslogic)
197
+
198
+ ## 🏢 About MITRE
199
+
200
+ This fork is maintained by [MITRE Corporation](https://www.mitre.org/) to support our Ruby applications, particularly [Vulcan](https://github.com/mitre/vulcan) and other security tools.
201
+
202
+ ---
203
+
204
+ **Maintained with ❤️ by the MITRE Security Automation Framework Team**
data/ROADMAP.md ADDED
@@ -0,0 +1,39 @@
1
+ # Settingslogic Roadmap
2
+
3
+ ## Version 3.0.0 (Current Release)
4
+ - ✅ Ruby 3.x compatibility through Ruby 3.4+
5
+ - ✅ Rails 7.x and 8.x compatibility
6
+ - ✅ Security: Replaced `YAML.unsafe_load` with `YAML.safe_load`
7
+ - ✅ Configurable permitted classes via `Settingslogic.yaml_permitted_classes`
8
+ - ✅ Migration path with deprecated `use_yaml_unsafe_load` flag
9
+ - ✅ 94%+ test coverage with reorganized specs
10
+
11
+ ## Version 3.x (Maintenance)
12
+ - Rename master branch to main (v3.0.1 or v3.1)
13
+ - Test gem autopublishing workflow
14
+ - Bug fixes as needed
15
+ - Maintain compatibility with new Ruby/Rails releases
16
+ - No new features planned - focus on stability
17
+
18
+ ## Version 4.0.0 (Future - Aligned with Vulcan Rewrite)
19
+ ### Breaking Changes
20
+ - Drop Ruby 2.7 support (already EOL as of March 2023)
21
+ - Remove deprecated `use_yaml_unsafe_load` flag
22
+ - Minimum Ruby version: 3.0+ (or higher based on adoption)
23
+
24
+ ### Goals
25
+ - Simplify codebase by removing legacy compatibility code
26
+ - Maintain as a simple, focused settings gem
27
+ - Ensure compatibility with Ruby 3.4+ for the long term
28
+
29
+ ## Maintenance Philosophy
30
+ This is a MITRE-maintained fork created specifically for Vulcan and other MITRE projects. We recognize that many in the Ruby community are moving to other configuration solutions, but settingslogic remains valuable for existing projects.
31
+
32
+ Our focus:
33
+ - Security and stability over features
34
+ - Maintaining compatibility for existing users
35
+ - Clear migration paths when changes are needed
36
+ - No unnecessary complexity
37
+
38
+ ## Note
39
+ Given the gem's maturity and decreasing usage in new projects, we don't anticipate significant feature development. This fork exists primarily to ensure MITRE projects using settingslogic can safely upgrade to modern Ruby and Rails versions.
data/SECURITY.md ADDED
@@ -0,0 +1,122 @@
1
+ # Security Policy
2
+
3
+ ## Reporting Security Issues
4
+
5
+ The MITRE team takes security seriously. If you discover a security vulnerability in the settingslogic fork, please report it responsibly.
6
+
7
+ ### Contact Information
8
+
9
+ - **Email**: [saf-security@mitre.org](mailto:saf-security@mitre.org)
10
+ - **GitHub**: Use the [Security tab](https://github.com/mitre/settingslogic/security) to report vulnerabilities privately
11
+ - **Direct Contact**: lippold@gmail.com for urgent issues
12
+
13
+ ### What to Include
14
+
15
+ When reporting security issues, please provide:
16
+
17
+ 1. **Description** of the vulnerability
18
+ 2. **Steps to reproduce** the issue
19
+ 3. **Affected versions** (Ruby version, settingslogic version)
20
+ 4. **Potential impact** assessment
21
+ 5. **Suggested fix** (if you have one)
22
+
23
+ ### Response Timeline
24
+
25
+ - **Acknowledgment**: Within 48 hours
26
+ - **Initial Assessment**: Within 1 week
27
+ - **Fix Timeline**: Depends on severity
28
+ - Critical: Within 1 week
29
+ - High: Within 2 weeks
30
+ - Medium: Within 1 month
31
+ - Low: Next release
32
+
33
+ ## Security Considerations
34
+
35
+ ### YAML Loading (v3.0.0+)
36
+
37
+ This gem uses YAML for configuration files with secure defaults:
38
+
39
+ - **Default**: Uses `YAML.safe_load` to prevent arbitrary code execution
40
+ - **Permitted classes**: `Symbol, Date, Time, DateTime, BigDecimal`
41
+ - **Customizable**: Add your own classes via `Settingslogic.yaml_permitted_classes`
42
+ - **YAML aliases**: Fully supported with `aliases: true` parameter
43
+
44
+ **⚠️ Important**: Configuration files should be:
45
+ - Stored in secure locations
46
+ - Not user-uploadable
47
+ - Protected with appropriate file permissions
48
+
49
+ ### ERB Processing
50
+
51
+ Configuration files support ERB templates. This means:
52
+ - Environment variables can be embedded
53
+ - Ruby code can be executed during configuration loading
54
+
55
+ **Best Practices**:
56
+ ```yaml
57
+ # Good - using environment variables
58
+ production:
59
+ secret_key: <%= ENV['SECRET_KEY_BASE'] %>
60
+
61
+ # Avoid - executing arbitrary code
62
+ production:
63
+ value: <%= `whoami` %> # Don't do this!
64
+ ```
65
+
66
+ ### File Access
67
+
68
+ The gem can load configuration from:
69
+ - Local files (recommended)
70
+ - HTTP/HTTPS URLs (use with caution)
71
+
72
+ For production environments, always use local files with proper permissions.
73
+
74
+ ## Supported Versions
75
+
76
+ | Version | Ruby Versions | Supported |
77
+ | ------- | ------------- | ------------------ |
78
+ | 3.0.x | 2.7 - 3.4 | :white_check_mark: |
79
+ | 2.0.x | 1.9 - 2.6 | :x: |
80
+
81
+ ## Security Enhancements in v3.0.0
82
+
83
+ ### Critical Security Fixes
84
+ - **YAML deserialization security**: Replaced `YAML.unsafe_load` with `YAML.safe_load` to prevent arbitrary object instantiation (addresses CVE-2022-32224 pattern)
85
+ - **Removed open-uri vulnerability**: Replaced `open-uri` with `Net::HTTP` to prevent SSRF attacks
86
+ - **Protocol validation**: Explicitly blocks dangerous protocols (file://, ftp://, gopher://, etc.)
87
+ - **Safe YAML loading**: Default permitted classes: Symbol, Date, Time, DateTime, BigDecimal
88
+ - **Input sanitization**: Dynamic method names validated with `/^\w+$/` to prevent code injection
89
+
90
+ ### Security Features
91
+ - **No automatic redirects**: HTTP client doesn't follow redirects automatically
92
+ - **Controlled deserialization**: Prevents arbitrary object instantiation in YAML
93
+ - **Proper error handling**: Security errors fail safely without exposing internals
94
+ - **Comprehensive testing**: 18 security-specific test cases added
95
+
96
+ ### Known Security Issues
97
+
98
+ #### Original Gem (2.0.9)
99
+ - **CVE-2020-8287 (related)**: Uses vulnerable `open-uri` for URL loading
100
+ - No Psych 4 support (Ruby 3.1+ compatibility issues)
101
+ - Uses deprecated `YAML.load` without restrictions
102
+ - No security updates since 2012
103
+
104
+ #### This Fork (3.0.0+)
105
+ - All known security issues have been addressed
106
+ - Regular security audits via bundler-audit
107
+ - Active maintenance by MITRE SAF team
108
+ - Comprehensive security test coverage (94.63%)
109
+
110
+ ## Disclosure Policy
111
+
112
+ We follow responsible disclosure:
113
+
114
+ 1. Security issues are fixed in private
115
+ 2. Patches are released with security advisories
116
+ 3. Credit is given to reporters (unless they prefer anonymity)
117
+
118
+ ## Additional Resources
119
+
120
+ - [MITRE CVE Database](https://cve.mitre.org/)
121
+ - [Ruby Security Advisories](https://www.ruby-lang.org/en/security/)
122
+ - [Rails Security Guide](https://guides.rubyonrails.org/security.html)
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Settingslogic < Hash
4
+ VERSION = '3.0.0'
5
+ end
@@ -0,0 +1,360 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+ require 'erb'
5
+ require 'date'
6
+ require 'bigdecimal'
7
+
8
+ # A simple settings solution using a YAML file. See README for more information.
9
+ class Settingslogic < Hash
10
+ class MissingSetting < StandardError; end
11
+
12
+ class << self
13
+ def name # :nodoc:
14
+ superclass != Hash && instance.key?('name') ? instance.name : super
15
+ end
16
+
17
+ # Configure additional permitted classes for YAML deserialization
18
+ # Default: [Symbol, Date, Time, DateTime, BigDecimal]
19
+ # Example: Settingslogic.yaml_permitted_classes += [MyCustomClass]
20
+ def yaml_permitted_classes
21
+ @yaml_permitted_classes ||= [Symbol, Date, Time, DateTime, BigDecimal]
22
+ end
23
+
24
+ attr_writer :yaml_permitted_classes
25
+
26
+ # DEPRECATED: Temporarily allow unsafe YAML loading for backwards compatibility
27
+ # This option will be removed in v4.0.0
28
+ # WARNING: This enables arbitrary code execution vulnerabilities!
29
+ def use_yaml_unsafe_load=(value)
30
+ if value
31
+ warn '[DEPRECATION] Settingslogic.use_yaml_unsafe_load is deprecated and will be removed in v4.0.0. ' \
32
+ 'Please migrate to using Settingslogic.yaml_permitted_classes instead.'
33
+ end
34
+ @use_yaml_unsafe_load = value
35
+ end
36
+
37
+ def use_yaml_unsafe_load
38
+ @use_yaml_unsafe_load ||= false
39
+ end
40
+
41
+ # Enables Settings.get('nested.key.name') for dynamic access
42
+ def get(key)
43
+ parts = key.split('.')
44
+ curs = self
45
+ while (p = parts.shift)
46
+ curs = curs.send(p)
47
+ end
48
+ curs
49
+ end
50
+
51
+ def source(value = nil)
52
+ @source ||= value
53
+ end
54
+
55
+ def namespace(value = nil)
56
+ @namespace ||= value
57
+ end
58
+
59
+ def suppress_errors(value = nil)
60
+ @suppress_errors ||= value
61
+ end
62
+
63
+ def [](key)
64
+ instance.fetch(key.to_s, nil)
65
+ end
66
+
67
+ def []=(key, val)
68
+ # Setting[:key][:key2] = 'value' for dynamic settings
69
+ val = new(val, source) if val.is_a? Hash
70
+ instance.store(key.to_s, val)
71
+ instance.create_accessor_for(key, val)
72
+ end
73
+
74
+ def load!
75
+ instance
76
+ true
77
+ end
78
+
79
+ def reload!
80
+ @instance = nil
81
+ load!
82
+ end
83
+
84
+ private
85
+
86
+ def instance
87
+ return @instance if @instance
88
+
89
+ @instance = new
90
+ create_accessors!
91
+ @instance
92
+ end
93
+
94
+ def method_missing(name, *args, &block)
95
+ instance.send(name, *args, &block)
96
+ end
97
+
98
+ # It would be great to DRY this up somehow, someday, but it's difficult because
99
+ # of the singleton pattern. Basically this proxies Setting.foo to Setting.instance.foo
100
+ def create_accessors!
101
+ instance.each_key do |key|
102
+ create_accessor_for(key)
103
+ end
104
+ end
105
+
106
+ def create_accessor_for(key)
107
+ return unless /^\w+$/.match?(key.to_s) # could have "some-setting:" which blows up eval
108
+
109
+ instance_eval "def #{key}; instance.send(:#{key}); end", __FILE__, __LINE__
110
+ end
111
+ end
112
+
113
+ # Initializes a new settings object. You can initialize an object in any of the following ways:
114
+ #
115
+ # Settings.new(:application) # will look for config/application.yml
116
+ # Settings.new("application.yaml") # will look for application.yaml
117
+ # Settings.new("/var/configs/application.yml") # will look for /var/configs/application.yml
118
+ # Settings.new(:config1 => 1, :config2 => 2)
119
+ #
120
+ # Basically if you pass a symbol it will look for that file in the configs directory of your rails app,
121
+ # if you are using this in rails. If you pass a string it should be an absolute path to your settings file.
122
+ # Then you can pass a hash, and it just allows you to access the hash via methods.
123
+ def initialize(hash_or_file = self.class.source, section = nil)
124
+ # puts "new! #{hash_or_file}"
125
+ case hash_or_file
126
+ when nil
127
+ raise Errno::ENOENT, 'No file specified as Settingslogic source'
128
+ when Hash
129
+ replace hash_or_file
130
+ else
131
+ file_contents = read_file(hash_or_file)
132
+ hash = file_contents.empty? ? {} : parse_yaml_content(file_contents)
133
+ if self.class.namespace
134
+ hash = hash[self.class.namespace] or
135
+ return missing_key("Missing setting '#{self.class.namespace}' in #{hash_or_file}")
136
+ end
137
+
138
+ replace hash
139
+ end
140
+ @section = section || self.class.source # so end of error says "in application.yml"
141
+ create_accessors!
142
+ end
143
+
144
+ # Called for dynamically-defined keys, and also the first key deferenced at the top-level, if load! is not used.
145
+ # Otherwise, create_accessors! (called by new) will have created actual methods for each key.
146
+ def method_missing(name, *_args)
147
+ key = name.to_s
148
+ return missing_key("Missing setting '#{key}' in #{@section}") unless key? key
149
+
150
+ value = fetch(key)
151
+ create_accessor_for(key)
152
+ value.is_a?(Hash) ? self.class.new(value, "'#{key}' section in #{@section}") : value
153
+ end
154
+
155
+ def [](key)
156
+ fetch(key.to_s, nil)
157
+ end
158
+
159
+ def []=(key, val)
160
+ # Setting[:key][:key2] = 'value' for dynamic settings
161
+ val = self.class.new(val, @section) if val.is_a? Hash
162
+ store(key.to_s, val)
163
+ create_accessor_for(key, val)
164
+ end
165
+
166
+ # Returns an instance of a Hash object
167
+ def to_hash
168
+ to_h
169
+ end
170
+
171
+ # Prevents Array#flatten from trying to expand Settings objects
172
+ # This fixes RSpec issues when Settings objects are included in arrays
173
+ def to_ary
174
+ nil
175
+ end
176
+
177
+ # This handles naming collisions with Sinatra/Vlad/Capistrano. Since these use a set()
178
+ # helper that defines methods in Object, ANY method_missing ANYWHERE picks up the Vlad/Sinatra
179
+ # settings! So settings.deploy_to title actually calls Object.deploy_to (from set :deploy_to, "host"),
180
+ # rather than the app_yml['deploy_to'] hash. Jeezus.
181
+ def create_accessors!
182
+ each do |key, _val|
183
+ create_accessor_for(key)
184
+ end
185
+ end
186
+
187
+ # Use instance_eval/class_eval because they're actually more efficient than define_method{}
188
+ # http://stackoverflow.com/questions/185947/ruby-definemethod-vs-def
189
+ # http://bmorearty.wordpress.com/2009/01/09/fun-with-rubys-instance_eval-and-class_eval/
190
+ def create_accessor_for(key, val = nil)
191
+ return unless /^\w+$/.match?(key.to_s) # could have "some-setting:" which blows up eval
192
+
193
+ instance_variable_set("@#{key}", val)
194
+ self.class.class_eval <<-ENDEVAL, __FILE__, __LINE__ + 1
195
+ def #{key}
196
+ return @#{key} if @#{key}
197
+ return missing_key("Missing setting '#{key}' in #{@section}") unless key? '#{key}'
198
+ value = fetch('#{key}')
199
+ @#{key} = if value.is_a?(Hash)
200
+ self.class.new(value, "'#{key}' section in #{@section}")
201
+ elsif value.is_a?(Array) && value.all?{|v| v.is_a? Hash}
202
+ value.map{|v| self.class.new(v)}
203
+ else
204
+ value
205
+ end
206
+ end
207
+ ENDEVAL
208
+ end
209
+
210
+ # Convert all keys to symbols recursively
211
+ def symbolize_keys
212
+ each_with_object({}) do |(key, value), memo|
213
+ k = begin
214
+ key.to_sym
215
+ rescue StandardError
216
+ key
217
+ end
218
+ # Access the value properly through the accessor method
219
+ v = respond_to?(key) ? send(key) : value
220
+ # Recursively symbolize nested hashes
221
+ memo[k] = if v.is_a?(self.class)
222
+ v.symbolize_keys
223
+ elsif v.respond_to?(:symbolize_keys)
224
+ v.symbolize_keys
225
+ else
226
+ v
227
+ end
228
+ end
229
+ end
230
+
231
+ # Convert all keys to strings recursively (Rails compatibility)
232
+ def stringify_keys
233
+ each_with_object({}) do |(key, value), memo|
234
+ k = key.to_s
235
+ v = begin
236
+ send(key)
237
+ rescue StandardError
238
+ value
239
+ end
240
+ memo[k] = v.respond_to?(:stringify_keys) ? v.stringify_keys : v
241
+ end
242
+ end
243
+
244
+ # Deep merge settings (useful for overrides)
245
+ def deep_merge(other_hash)
246
+ self.class.new(deep_merge_hash(to_hash, other_hash))
247
+ end
248
+
249
+ # Deep merge in place
250
+ def deep_merge!(other_hash)
251
+ replace(deep_merge_hash(to_hash, other_hash))
252
+ end
253
+
254
+ private
255
+
256
+ # Helper for deep merging
257
+ def deep_merge_hash(hash, other_hash)
258
+ hash.merge(other_hash) do |_key, old_val, new_val|
259
+ if old_val.is_a?(Hash) && new_val.is_a?(Hash)
260
+ deep_merge_hash(old_val, new_val)
261
+ else
262
+ new_val
263
+ end
264
+ end
265
+ end
266
+
267
+ def missing_key(msg)
268
+ return nil if self.class.suppress_errors
269
+
270
+ raise MissingSetting, msg
271
+ end
272
+
273
+ # Parse YAML content with Psych 4 / Ruby 3.1+ compatibility
274
+ # Handles YAML aliases which are disabled by default in Psych 4
275
+ def parse_yaml_content(file_content)
276
+ erb_result = ERB.new(file_content).result
277
+
278
+ # Check if unsafe loading is enabled (deprecated)
279
+ if self.class.use_yaml_unsafe_load
280
+ # Use the old unsafe behavior (security risk!)
281
+ if YAML.respond_to?(:unsafe_load)
282
+ # unsafe_load doesn't take aliases parameter, it allows them by default
283
+ YAML.unsafe_load(erb_result).to_hash
284
+ else
285
+ # Fallback to regular load for older Ruby versions
286
+ YAML.load(erb_result).to_hash # rubocop:disable Security/YAMLLoad
287
+ end
288
+ else
289
+ # Use safe_load for security (recommended)
290
+ permitted_classes = self.class.yaml_permitted_classes
291
+
292
+ begin
293
+ if YAML.respond_to?(:safe_load)
294
+ # Try with modern safe_load signature (Ruby 2.6+)
295
+ YAML.safe_load(erb_result, permitted_classes: permitted_classes, aliases: true).to_hash
296
+ else
297
+ # Fallback for older Ruby versions
298
+ YAML.safe_load(erb_result, permitted_classes, [], true).to_hash
299
+ end
300
+ rescue ArgumentError => e
301
+ # Handle older safe_load signature (Ruby 2.5 and earlier)
302
+ raise e unless e.message.include?('unknown keyword') || e.message.include?('wrong number of arguments')
303
+
304
+ # Old signature: safe_load(yaml, whitelist_classes, whitelist_symbols, aliases)
305
+ YAML.safe_load(erb_result, permitted_classes, [], true).to_hash
306
+ end
307
+ end
308
+ rescue Psych::DisallowedClass => e
309
+ # Extract class name from error message
310
+ class_name = e.message[/Tried to load unspecified class: (.+)/, 1] || e.message
311
+
312
+ # Provide helpful error message with migration instructions
313
+ raise MissingSetting, "YAML file contains disallowed class: #{class_name}\n\n" \
314
+ "To fix this, you have two options:\n" \
315
+ "1. Add the class to permitted classes:\n " \
316
+ "Settingslogic.yaml_permitted_classes += [#{class_name}]\n" \
317
+ "2. (NOT RECOMMENDED) Temporarily use unsafe loading:\n " \
318
+ "Settingslogic.use_yaml_unsafe_load = true\n\n" \
319
+ "Current permitted classes: #{self.class.yaml_permitted_classes.inspect}"
320
+ rescue Psych::BadAlias => e
321
+ # This shouldn't happen with aliases: true, but handle it just in case
322
+ raise MissingSetting, "YAML file contains aliases but they could not be processed. " \
323
+ "Error: #{e.message}"
324
+ end
325
+
326
+ # Read file contents handling both local files and URIs
327
+ # Uses Net::HTTP for security instead of open-uri
328
+ def read_file(source)
329
+ source_str = source.to_s
330
+
331
+ # Check for dangerous protocols first
332
+ if source_str.match?(%r{\A(file|ftp|gopher|ldap|dict|tftp|sftp)://}i)
333
+ raise ArgumentError, "Invalid URL protocol: #{source}"
334
+ end
335
+
336
+ if source_str.match?(%r{\Ahttps?://}i)
337
+ # For HTTP/HTTPS URLs, use Net::HTTP which is more secure
338
+ require 'net/http'
339
+ require 'uri'
340
+
341
+ uri = URI.parse(source_str)
342
+
343
+ # Security: validate the URI
344
+ raise ArgumentError, "Invalid URL: #{source}" unless uri.is_a?(URI::HTTP)
345
+
346
+ # Use Net::HTTP with proper error handling
347
+ response = Net::HTTP.get_response(uri)
348
+
349
+ case response
350
+ when Net::HTTPSuccess
351
+ response.body
352
+ else
353
+ raise "Failed to fetch #{source}: #{response.code} #{response.message}"
354
+ end
355
+ else
356
+ # For local files, use File.read which is more efficient
357
+ File.read(source_str)
358
+ end
359
+ end
360
+ end
metadata ADDED
@@ -0,0 +1,172 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mitre-settingslogic
3
+ version: !ruby/object:Gem::Version
4
+ version: 3.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Ben Johnson
8
+ - MITRE SAF Team
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2025-08-11 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bigdecimal
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '3.1'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '3.1'
28
+ - !ruby/object:Gem::Dependency
29
+ name: bundler-audit
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: '0.9'
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: '0.9'
42
+ - !ruby/object:Gem::Dependency
43
+ name: rake
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: '13.2'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: '13.2'
56
+ - !ruby/object:Gem::Dependency
57
+ name: rspec
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - "~>"
61
+ - !ruby/object:Gem::Version
62
+ version: '3.13'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: '3.13'
70
+ - !ruby/object:Gem::Dependency
71
+ name: rubocop
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - "~>"
75
+ - !ruby/object:Gem::Version
76
+ version: '1.65'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - "~>"
82
+ - !ruby/object:Gem::Version
83
+ version: '1.65'
84
+ - !ruby/object:Gem::Dependency
85
+ name: rubocop-performance
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - "~>"
89
+ - !ruby/object:Gem::Version
90
+ version: '1.21'
91
+ type: :development
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - "~>"
96
+ - !ruby/object:Gem::Version
97
+ version: '1.21'
98
+ - !ruby/object:Gem::Dependency
99
+ name: rubocop-rspec
100
+ requirement: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - "~>"
103
+ - !ruby/object:Gem::Version
104
+ version: '3.0'
105
+ type: :development
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - "~>"
110
+ - !ruby/object:Gem::Version
111
+ version: '3.0'
112
+ - !ruby/object:Gem::Dependency
113
+ name: simplecov
114
+ requirement: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - "~>"
117
+ - !ruby/object:Gem::Version
118
+ version: '0.22'
119
+ type: :development
120
+ prerelease: false
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - "~>"
124
+ - !ruby/object:Gem::Version
125
+ version: '0.22'
126
+ description: A simple and straightforward settings solution that uses an ERB enabled
127
+ YAML file and a singleton design pattern. This is a MITRE-maintained fork with Ruby
128
+ 3.x and Rails 7.x compatibility.
129
+ email:
130
+ - saf@mitre.org
131
+ executables: []
132
+ extensions: []
133
+ extra_rdoc_files: []
134
+ files:
135
+ - CHANGELOG.md
136
+ - CONTRIBUTING.md
137
+ - LICENSE.md
138
+ - README.md
139
+ - ROADMAP.md
140
+ - SECURITY.md
141
+ - lib/settingslogic.rb
142
+ - lib/settingslogic/version.rb
143
+ homepage: https://github.com/mitre/settingslogic
144
+ licenses:
145
+ - MIT
146
+ metadata:
147
+ homepage_uri: https://github.com/mitre/settingslogic
148
+ source_code_uri: https://github.com/mitre/settingslogic
149
+ changelog_uri: https://github.com/mitre/settingslogic/blob/main/CHANGELOG.md
150
+ bug_tracker_uri: https://github.com/mitre/settingslogic/issues
151
+ documentation_uri: https://www.rubydoc.info/gems/settingslogic
152
+ rubygems_mfa_required: 'true'
153
+ post_install_message:
154
+ rdoc_options: []
155
+ require_paths:
156
+ - lib
157
+ required_ruby_version: !ruby/object:Gem::Requirement
158
+ requirements:
159
+ - - ">="
160
+ - !ruby/object:Gem::Version
161
+ version: 2.7.0
162
+ required_rubygems_version: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ requirements: []
168
+ rubygems_version: 3.3.27
169
+ signing_key:
170
+ specification_version: 4
171
+ summary: A simple settings solution using YAML and a singleton pattern
172
+ test_files: []