olyx-guardrails 1.1.3 → 1.2.1

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.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +29 -7
  3. data/README.md +109 -177
  4. data/lib/olyx/guardrails/check_result_builder.rb +12 -29
  5. data/lib/olyx/guardrails/check_runner.rb +11 -5
  6. data/lib/olyx/guardrails/check_set.rb +66 -19
  7. data/lib/olyx/guardrails/injection_patterns.rb +1 -2
  8. data/lib/olyx/guardrails/llm_analysis.rb +44 -14
  9. data/lib/olyx/guardrails/message_content.rb +17 -4
  10. data/lib/olyx/guardrails/pii/block_scrubber.rb +2 -2
  11. data/lib/olyx/guardrails/pii/message_scrubber.rb +2 -2
  12. data/lib/olyx/guardrails/pii/numeric_date_validator.rb +10 -5
  13. data/lib/olyx/guardrails/pii/text_scrubber.rb +15 -1
  14. data/lib/olyx/guardrails/policy/match_collector.rb +7 -1
  15. data/lib/olyx/guardrails/policy/rule_matcher.rb +26 -9
  16. data/lib/olyx/guardrails/policy.rb +37 -24
  17. data/lib/olyx/guardrails/policy_rule.rb +56 -19
  18. data/lib/olyx/guardrails/rails.rb +8 -7
  19. data/lib/olyx/guardrails/redaction/content_result.rb +4 -3
  20. data/lib/olyx/guardrails/risk/deterministic_score.rb +2 -4
  21. data/lib/olyx/guardrails/risk/weights.rb +0 -1
  22. data/lib/olyx/guardrails/risk_scorer.rb +4 -5
  23. data/lib/olyx/guardrails/secret_finding_collector.rb +0 -1
  24. data/lib/olyx/guardrails/secret_scanner.rb +1 -2
  25. data/lib/olyx/guardrails/secrets/pattern_catalog.rb +0 -10
  26. data/lib/olyx/guardrails/secrets/redactor.rb +0 -7
  27. data/lib/olyx/guardrails/secrets/source_set.rb +1 -2
  28. data/lib/olyx/guardrails/text/detection_variants.rb +13 -1
  29. data/lib/olyx/guardrails/version.rb +1 -1
  30. data/lib/olyx/guardrails.rb +1 -3
  31. metadata +2 -30
  32. data/lib/olyx/guardrails/check_pipeline.rb +0 -17
  33. data/lib/olyx/guardrails/checks/injection_check.rb +0 -20
  34. data/lib/olyx/guardrails/checks/injection_result.rb +0 -22
  35. data/lib/olyx/guardrails/checks/length_check.rb +0 -18
  36. data/lib/olyx/guardrails/checks/message_injection_check.rb +0 -19
  37. data/lib/olyx/guardrails/checks/pii_check.rb +0 -19
  38. data/lib/olyx/guardrails/checks/policy_check.rb +0 -26
  39. data/lib/olyx/guardrails/checks/secret_check.rb +0 -30
  40. data/lib/olyx/guardrails/checks/skipped_checks.rb +0 -26
  41. data/lib/olyx/guardrails/llm/analysis_normalizer.rb +0 -26
  42. data/lib/olyx/guardrails/llm/analysis_pipeline.rb +0 -37
  43. data/lib/olyx/guardrails/llm/boolean_validator.rb +0 -18
  44. data/lib/olyx/guardrails/llm/result_sanitizer.rb +0 -29
  45. data/lib/olyx/guardrails/message_check_runner.rb +0 -24
  46. data/lib/olyx/guardrails/message_check_set.rb +0 -20
  47. data/lib/olyx/guardrails/pii/ambiguous_numeric_date.rb +0 -32
  48. data/lib/olyx/guardrails/pii/hash_key.rb +0 -19
  49. data/lib/olyx/guardrails/pii/year_first_date.rb +0 -18
  50. data/lib/olyx/guardrails/policy/configuration.rb +0 -68
  51. data/lib/olyx/guardrails/policy/normalized_pattern_matcher.rb +0 -34
  52. data/lib/olyx/guardrails/policy/pattern_matcher.rb +0 -19
  53. data/lib/olyx/guardrails/policy_rule/configuration.rb +0 -52
  54. data/lib/olyx/guardrails/policy_rule/description_value.rb +0 -24
  55. data/lib/olyx/guardrails/policy_rule/name_value.rb +0 -22
  56. data/lib/olyx/guardrails/policy_rule/replacement_value.rb +0 -26
  57. data/lib/olyx/guardrails/policy_rule/text_values.rb +0 -23
  58. data/lib/olyx/guardrails/policy_rule/values.rb +0 -33
  59. data/lib/olyx/guardrails/secrets/confidentiality_source.rb +0 -21
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3beec90de1af339d911ceeb8efd92759f478198cef96679313608caad32b20fa
4
- data.tar.gz: e812ebb1c97a9d359a4cf98e93bbd28724356c80a72d94b0ae1b6fbeffa6a3cf
3
+ metadata.gz: 196f3e181407760bea2e26500d3e3eaa5ce29b886922ad361eab20e98ea76b68
4
+ data.tar.gz: 24144315dc438469cc42ca4d876834b44c3ad3b1b42430a80d6bc693e57ceec3
5
5
  SHA512:
6
- metadata.gz: ab91be7e1659df86895cb9a011f01bb782b48e7d4f153936570e95734b4ef72a86a6abdc5494f78f09f669a2ad4e05a1173d8e92a0fd584b5423de9a83f5a73b
7
- data.tar.gz: 67861f12b3535faa4e52b50c440ef8d1696488ab88a32bfeda8ef16c50e99e90223b270177696730c99ee8ffb74a32e23fb35977beb7cd43e48507051525e320
6
+ metadata.gz: e3ecc41c16b5f43dc99beb1af720a1f5f0a4f68fbb5453504a21e01c14ff7f1c248e724bd7987e5e4db50aa15944cf03291556a41c951282b98351eb863e1512
7
+ data.tar.gz: 8814874a0d9f40c93a6deb3b2063da29378f6a149742319f19b9b6aa7a50142e52937baab9cc71e6ae58706d42a65820467391d8a583eae021f7d1cad7665b1f
data/CHANGELOG.md CHANGED
@@ -3,6 +3,31 @@
3
3
  All notable changes to olyx-guardrails are documented here.
4
4
  Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
5
 
6
+ ## [Unreleased]
7
+
8
+ ## [1.2.1] - 2026-11-11
9
+
10
+ ### Changed
11
+
12
+ - Simplify evaluation, message handling, LLM analysis, and numeric date validation
13
+ while preserving the public result contract.
14
+ - Require patched Rails test bundles: 8.1.4.1 or newer on Rails 8.1 and
15
+ 8.0.5.2 or newer on Rails 8.0, with Rack >= 3.2.8 and Crass >= 1.0.8.
16
+ - Document request-body limits, host allowlists, and trusted proxy headers for
17
+ applications consuming the gem.
18
+
19
+ ## [1.2.0] - 2026-10-02
20
+
21
+ ### Changed
22
+
23
+ - Scans long inputs in overlapping windows and narrows injection matching to
24
+ reduce missed detections and false positives.
25
+ - Sanitizes provider failures and stops treating confidentiality labels as
26
+ credentials; applications can enforce those labels with policy rules.
27
+ - Reports risk independently from blocking policy.
28
+ - Consolidates scanning and configuration internals, with tests,
29
+ documentation, and RuboCop as the public quality gate.
30
+
6
31
  ## [1.1.3] - 2026-08-07
7
32
 
8
33
  ### Changed
@@ -39,7 +64,7 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
39
64
  decision has zero risk.
40
65
  - The Rails generator provides copyable policy customization instructions and
41
66
  tests the customized YAML path.
42
- - Contributor setup, complete local quality validation, and maintainer release
67
+ - Contributor setup, local validation, and maintainer release
43
68
  verification have canonical commands and documentation.
44
69
  - Closely coupled proxy objects were folded into their owning runtime,
45
70
  configuration, result-building, and notification components.
@@ -47,7 +72,7 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
47
72
  ignored instead of being coerced across the untrusted provider boundary.
48
73
  - Contributor and Rails appraisal locks use the current `net-imap` patch.
49
74
  - CI refreshes the Ruby advisory database and blocks known-vulnerable locked
50
- dependencies without making the offline local quality gate network-dependent.
75
+ dependencies without making offline local validation network-dependent.
51
76
 
52
77
  ## [1.0.0] - 2026-07-21
53
78
 
@@ -155,11 +180,8 @@ Initial public release.
155
180
 
156
181
  - The core has one lightweight runtime dependency, Ruby's `base64` bundled gem.
157
182
  Rails remains optional and is loaded only when used.
158
- - CI enforces RuboCop — including calibrated structural-complexity cops
159
- (`Metrics/AbcSize`, `CyclomaticComplexity`, `PerceivedComplexity`,
160
- `ClassLength`) in place of a separate complexity tool — and a RubyCritic
161
- maintainability gate, alongside the Appraisal matrix across Rails 8.0
162
- and 8.1.
183
+ - CI enforces tests, documentation checks, RuboCop, and the Appraisal matrix
184
+ across Rails 8.0 and 8.1.
163
185
  - Native RDoc covers every supported public class, module, constant, attribute,
164
186
  and method. CI blocks undocumented additions to the explicit public API
165
187
  manifest while leaving implementation-only constants outside the
data/README.md CHANGED
@@ -1,65 +1,48 @@
1
1
  # Olyx Guardrails
2
2
 
3
- [![Gem Version](https://badge.fury.io/rb/olyx-guardrails.svg)](https://rubygems.org/gems/olyx-guardrails)
3
+ [![RubyGems](https://img.shields.io/gem/v/olyx-guardrails?label=rubygems)](https://rubygems.org/gems/olyx-guardrails)
4
+ [![Release](https://img.shields.io/github/v/release/Olyx-labs/olyx-guardrails?label=release)](https://github.com/Olyx-labs/olyx-guardrails/releases/latest)
4
5
  [![Test](https://github.com/Olyx-labs/olyx-guardrails/actions/workflows/test.yml/badge.svg)](https://github.com/Olyx-labs/olyx-guardrails/actions/workflows/test.yml)
5
6
  [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/Olyx-labs/olyx-guardrails/badge)](https://securityscorecards.dev/viewer/?uri=github.com/Olyx-labs/olyx-guardrails)
6
7
  [![License: Apache 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE)
7
8
 
8
- Olyx Guardrails is an in-process safety boundary for Ruby and Rails
9
- applications that send or receive AI-generated content. It provides:
9
+ Input and output checks for AI applications in Ruby and Rails.
10
10
 
11
- - PII and secret detection with explicit redaction;
12
- - prompt-injection and jailbreak detection, including adjacent-turn checks;
13
- - immutable, organization-specific restricted-content policies;
14
- - completed-input and completed-output decisions;
15
- - opt-in Rails adapters for common ingestion paths;
16
- - provider-agnostic semantic analysis through a callable LLM hook; and
17
- - sanitized notifications and Active Support instrumentation.
11
+ Detect prompt injection, PII, secrets, and application-specific restricted
12
+ content. Return explicit allow/block decisions and redact recognized content
13
+ at the boundaries your application chooses.
18
14
 
19
- The deterministic checks run locally. The gem does not proxy model traffic,
20
- discover model calls, or send application content to a third party.
15
+ Olyx Guardrails is a standalone gem. It works without Olyx CLI, Olyx Cloud,
16
+ or an Olyx account. Deterministic checks run locally. An optional
17
+ `llm_provider` receives raw input and may send it elsewhere through your
18
+ application's adapter.
21
19
 
22
- ## Requirements
20
+ ## Install
23
21
 
24
- | Component | Supported versions |
25
- |---|---|
26
- | Ruby | 3.4 or newer |
27
- | Rails | 8.0 and 8.1 |
22
+ Requires **Ruby 3.4 or newer**. Rails is optional; the integration supports
23
+ **Rails 8.0 and 8.1**. The framework-free API does not load Rails.
28
24
 
29
- Rails is optional. The standalone Ruby API does not load Rails.
30
-
31
- The 1.1 release supports Rails 8.0 and 8.1. Only the listed Rails
32
- series are tested and supported. When a series reaches upstream end-of-life,
33
- a subsequent gem release may remove it instead of maintaining framework
34
- security fixes independently. Every support change is recorded in the
35
- changelog.
36
-
37
- ## Installation
38
-
39
- Add the gem to the application:
25
+ Add to your `Gemfile`:
40
26
 
41
27
  ```ruby
42
- gem "olyx-guardrails", "~> 1.1"
28
+ gem "olyx-guardrails", "~> 1.2"
43
29
  ```
44
30
 
45
- Then install the bundle:
46
-
47
- ```bash
31
+ ```sh
48
32
  bundle install
49
33
  ```
50
34
 
51
- The only runtime gem dependency is Ruby's `base64` bundled gem, used for
52
- bounded encoded-input detection.
35
+ The only runtime gem dependency is Ruby's bundled `base64` gem.
53
36
 
54
- ## Quick start
37
+ ## Try it
55
38
 
56
- Create one immutable policy and use it at the application boundary:
39
+ Save this as `guardrails_example.rb`:
57
40
 
58
41
  ```ruby
59
42
  require "olyx/guardrails"
60
43
 
61
44
  policy = Olyx::Guardrails::Policy.new(
62
- name: "customer-data-boundary",
45
+ name: "application-boundary",
63
46
  max_input_length: 4_000,
64
47
  block_pii: false,
65
48
  block_injections: true,
@@ -75,198 +58,147 @@ policy = Olyx::Guardrails::Policy.new(
75
58
  ]
76
59
  )
77
60
 
61
+ input = "Contact ada@example.com about the release."
78
62
  decision = Olyx::Guardrails.check(input, policy: policy)
79
- return forbidden unless decision[:allowed]
63
+ abort "Input rejected" unless decision[:allowed]
80
64
 
81
- safe_input = Olyx::Guardrails.redact(input, policy: policy)[:text]
82
- completion = LlmClient.complete(safe_input)
65
+ puts "Allowed: #{decision[:allowed]}"
66
+ puts Olyx::Guardrails.redact(input, policy: policy)[:text]
83
67
 
84
- output_decision = Olyx::Guardrails.check_output(completion, policy: policy)
85
- return invalid_output unless output_decision[:allowed]
68
+ restricted = Olyx::Guardrails.check("Discuss Project Falcon", policy: policy)
69
+ puts "Restricted input allowed: #{restricted[:allowed]}"
86
70
  ```
87
71
 
88
- `check` makes a decision and never transforms input. `redact` transforms
89
- recognized content and never makes an allow/block decision. Applications that
90
- need both operations call both explicitly.
72
+ Run it:
91
73
 
92
- The default policy:
74
+ ```sh
75
+ bundle exec ruby guardrails_example.rb
76
+ ```
93
77
 
94
- - limits input to 10,000 characters;
95
- - blocks detected injection attempts;
96
- - reports, but does not block, detected PII and secrets; and
97
- - has no custom restricted-content rules.
78
+ Expected output:
98
79
 
99
- Production applications should construct an explicit policy instead of relying
100
- on defaults. See the
101
- [Policies guide](https://github.com/Olyx-labs/olyx-guardrails/blob/master/docs/POLICIES.md)
102
- for the complete rule language and YAML examples.
80
+ ```text
81
+ Allowed: true
82
+ Contact [EMAIL] about the release.
83
+ Restricted input allowed: false
84
+ ```
103
85
 
104
- ## Rails quick start
86
+ In this policy, PII is reported without blocking, then explicitly redacted.
87
+ Injection attempts, secrets, and the configured project name cause rejection.
88
+ Policies are immutable: construct them once and reuse them.
89
+
90
+ **Checking and redacting are separate operations.** `check` returns a decision
91
+ without changing input. `redact` transforms recognized content without making
92
+ an allow/block decision. Redaction does not override a rejection.
93
+
94
+ Use `check_messages` for structured conversations and adjacent-turn detection.
95
+ Use `check_output` and `redact_output` at completed-output boundaries. Your
96
+ application owns the model call and must check the completed response before
97
+ returning it. See the [complete Ruby example](https://github.com/Olyx-labs/olyx-guardrails/blob/master/examples/ruby_only.rb) and
98
+ [API reference](https://github.com/Olyx-labs/olyx-guardrails/blob/master/docs/API.md) for the full flow and result contracts.
99
+
100
+ ## Add Rails enforcement
105
101
 
106
102
  Generate an initializer and environment-keyed policy file:
107
103
 
108
- ```bash
104
+ ```sh
109
105
  bin/rails generate olyx_guardrails:install
110
106
  ```
111
107
 
112
- Add the controller concern only where content crosses an AI boundary:
108
+ Review `config/initializers/olyx_guardrails.rb` and
109
+ `config/olyx_guardrails.yml`, then include an adapter at an application boundary:
113
110
 
114
111
  ```ruby
115
112
  class AiRequestsController < ApplicationController
116
113
  include Olyx::Guardrails::Rails::Controller
117
114
 
118
115
  rescue_from Olyx::Guardrails::Blocked do |error|
119
- render json: {
120
- error: "input_rejected",
121
- decision: error.decision
122
- }, status: :unprocessable_entity
116
+ render json: { error: "input_rejected", decision: error.decision },
117
+ status: :unprocessable_entity
123
118
  end
124
119
 
125
120
  def create
126
121
  prompt = params.require(:prompt)
127
- guardrails_check!(prompt, metadata: { user_id: current_user.id })
128
-
122
+ guardrails_check!(prompt)
129
123
  safe_prompt = guardrails_redact(prompt)[:text]
124
+
130
125
  completion = LlmClient.complete(safe_prompt)
131
126
  Olyx::Guardrails::Rails::Enforcer.check_output!(completion)
127
+ safe_output = Olyx::Guardrails::Rails.redact_output(completion)[:text]
132
128
 
133
- render json: { completion: completion }, status: :created
129
+ render json: { completion: safe_output }
134
130
  end
135
131
  end
136
132
  ```
137
133
 
138
- Rails enforcement is opt-in. The gem does not scan parameters, callbacks,
139
- uploads, jobs, GraphQL operations, or Action Cable messages globally.
140
-
141
- The
142
- [Rails integration guide](https://github.com/Olyx-labs/olyx-guardrails/blob/master/docs/RAILS.md)
143
- covers every adapter, boot-time configuration, safe YAML loading,
144
- instrumentation payloads, and notification delivery.
134
+ `LlmClient` represents your application's existing model client. This controller
135
+ example belongs in a Rails application; it is not a standalone script.
145
136
 
146
- ## Choose the right entry point
137
+ Enforcement is opt-in. The gem does not automatically scan every request, job,
138
+ upload, or model call. The [Rails guide](https://github.com/Olyx-labs/olyx-guardrails/blob/master/docs/RAILS.md) covers controllers,
139
+ Active Job, Active Model, GraphQL, Action Cable, extracted upload text,
140
+ instrumentation, and notifications.
147
141
 
148
- | Boundary | Entry point | Behavior |
149
- |---|---|---|
150
- | Plain text input | `Guardrails.check` | Returns an allow/block decision |
151
- | Structured messages | `Guardrails.check_messages` | Adds adjacent-turn detection |
152
- | Completed model output | `Guardrails.check_output` | Explicit output decision |
153
- | Plain text transformation | `Guardrails.redact` | Returns redacted text |
154
- | Completed output transformation | `Guardrails.redact_output` | Explicit output transformation |
155
- | Rails exception flow | `Rails::Enforcer.check!` | Raises `Guardrails::Blocked` when rejected |
156
- | Low-level secret enforcement | `SecretScanner.scan!` | Raises `SecretScanner::Blocked` on a finding |
142
+ ## Defaults and operational behavior
157
143
 
158
- All completed-output methods operate on complete values. They do not inspect a
159
- token stream before content reaches the caller.
160
-
161
- ## Optional LLM provider
162
-
163
- `llm_provider` accepts any callable object. It receives the raw text and a
164
- bounded context hash containing deterministic findings:
144
+ The default policy:
165
145
 
166
- ```ruby
167
- provider = lambda do |text, context|
168
- LocalClassifier.call(text: text, signals: context)
169
- end
146
+ - rejects input longer than 10,000 Ruby characters;
147
+ - blocks detected prompt injection;
148
+ - reports PII and secrets without blocking them;
149
+ - defines no custom restricted-content rules; and
150
+ - preserves the deterministic decision if an optional LLM provider fails.
170
151
 
171
- policy = Olyx::Guardrails::Policy.new(llm_failure_mode: :block)
152
+ Define an explicit production policy. If semantic analysis is required, choose
153
+ `llm_failure_mode: :block` or `:raise`. The application supplies the classifier
154
+ callable and owns its authentication, transport, timeouts, and retries.
155
+ Semantic findings can add violations but cannot clear deterministic findings.
156
+ See [policies](https://github.com/Olyx-labs/olyx-guardrails/blob/master/docs/POLICIES.md) and the
157
+ [provider contract](https://github.com/Olyx-labs/olyx-guardrails/blob/master/docs/API.md#llm-provider-contract).
172
158
 
173
- result = Olyx::Guardrails.check(
174
- input,
175
- policy: policy,
176
- llm_provider: provider
177
- )
178
- ```
159
+ Rails can publish content-free Active Support events. Optional notifier handlers
160
+ receive sanitized events and run synchronously unless your application queues
161
+ delivery. See [operations](https://github.com/Olyx-labs/olyx-guardrails/blob/master/docs/OPERATIONS.md) for data handling and failure modes.
179
162
 
180
- The provider returns a hash or schema object containing any of:
163
+ ## Limitations
181
164
 
182
- ```ruby
183
- {
184
- injection_attempt: false,
185
- pii_detected: false,
186
- secret_leaked: false,
187
- risk_score: 0.2,
188
- reason: "No semantic violation found"
189
- }
190
- ```
165
+ Pattern-based checks can miss novel attacks, paraphrasing, translations,
166
+ unsupported formats, and nested encodings. PII coverage is strongest for the
167
+ documented North American and common international formats. Secret detection
168
+ is format-based. Restricted-content rules do not infer semantic equivalents
169
+ unless your application supplies additional analysis.
191
170
 
192
- The gem owns normalization, validation, failure handling, and safe result
193
- merging. The application owns model selection, prompting, authentication,
194
- timeouts, retries, and transport. LLM analysis findings can add a violation but cannot clear a deterministic finding.
171
+ Completed-output methods evaluate complete values; they do not enforce a policy
172
+ on tokens already streamed to a caller. Model routing, tool permissions,
173
+ authorization, file extraction, distributed quotas, and audit storage remain
174
+ application responsibilities.
195
175
 
196
- See the
197
- [provider contract](https://github.com/Olyx-labs/olyx-guardrails/blob/master/docs/API.md#llm-provider-contract)
198
- and
199
- [local HTTP example](https://github.com/Olyx-labs/olyx-guardrails/blob/master/examples/local_llm_provider.rb).
200
- The example works with an application-owned classifier sidecar backed by any
201
- inference runtime. Read the
202
- [model-suitability criteria](https://github.com/Olyx-labs/olyx-guardrails/blob/master/docs/OPERATIONS.md#model-suitability)
203
- before using semantic analysis as a blocking production control.
176
+ Treat guardrails as one layer of application protection. Read the
177
+ [detection limits](https://github.com/Olyx-labs/olyx-guardrails/blob/master/docs/OPERATIONS.md#detection-limits) before enabling production
178
+ blocking.
204
179
 
205
- ## Documentation
180
+ ## Documentation and examples
206
181
 
207
182
  - [Documentation index](https://github.com/Olyx-labs/olyx-guardrails/blob/master/docs/README.md)
208
183
  - [Policies and restricted content](https://github.com/Olyx-labs/olyx-guardrails/blob/master/docs/POLICIES.md)
209
184
  - [Rails integration](https://github.com/Olyx-labs/olyx-guardrails/blob/master/docs/RAILS.md)
210
- - [Operations and production behavior](https://github.com/Olyx-labs/olyx-guardrails/blob/master/docs/OPERATIONS.md)
211
- - [API reference](https://github.com/Olyx-labs/olyx-guardrails/blob/master/docs/API.md)
212
- - [Release runbook](https://github.com/Olyx-labs/olyx-guardrails/blob/master/docs/RELEASING.md)
213
- - [Security policy](https://github.com/Olyx-labs/olyx-guardrails/security/policy)
214
- - [Contributing](https://github.com/Olyx-labs/olyx-guardrails/blob/master/CONTRIBUTING.md)
215
- - [Changelog](CHANGELOG.md)
216
-
217
- Examples:
218
-
219
- - [Framework-free Ruby](https://github.com/Olyx-labs/olyx-guardrails/blob/master/examples/ruby_only.rb)
220
- - [Custom policy](https://github.com/Olyx-labs/olyx-guardrails/blob/master/examples/custom_policy.rb)
221
- - [Rails opt-in boundaries](https://github.com/Olyx-labs/olyx-guardrails/blob/master/examples/rails_opt_in.rb)
222
- - [Local LLM provider](https://github.com/Olyx-labs/olyx-guardrails/blob/master/examples/local_llm_provider.rb)
185
+ - [Operations and notifications](https://github.com/Olyx-labs/olyx-guardrails/blob/master/docs/OPERATIONS.md)
186
+ - [Ruby API reference](https://github.com/Olyx-labs/olyx-guardrails/blob/master/docs/API.md)
187
+ - [Runnable Ruby example](https://github.com/Olyx-labs/olyx-guardrails/blob/master/examples/ruby_only.rb)
188
+ - [Custom policies](https://github.com/Olyx-labs/olyx-guardrails/blob/master/examples/custom_policy.rb)
189
+ - [Application-owned local classifier](https://github.com/Olyx-labs/olyx-guardrails/blob/master/examples/local_llm_provider.rb)
223
190
  - [Notifier handlers](https://github.com/Olyx-labs/olyx-guardrails/blob/master/examples/notifier.rb)
224
191
 
225
- The framework-free, custom-policy, and notifier examples run directly. The
226
- Rails example is application-context code, and the local provider example
227
- expects the documented classifier sidecar.
228
-
229
- ## Security model and limitations
230
-
231
- Olyx Guardrails is a defense-in-depth control, not a complete semantic security
232
- boundary or data-loss-prevention system.
233
-
234
- - Pattern-based detection can miss paraphrasing, translations, novel attacks,
235
- unsupported homoglyphs, and nested encoding beyond the bounded normalization
236
- pass.
237
- - PII support is strongest for documented North American and common
238
- international formats.
239
- - Secret detection is format-based and cannot classify every high-entropy
240
- string or future credential format.
241
- - Restricted-content rules are deterministic unless an application supplies an
242
- LLM provider.
243
- - File parsing, authorization, streaming enforcement, distributed quotas,
244
- centralized policy rollout, and audit retention remain application or
245
- platform responsibilities.
246
-
247
- The
248
- [operations guide](https://github.com/Olyx-labs/olyx-guardrails/blob/master/docs/OPERATIONS.md)
249
- describes failure modes, data handling, risk scores, concurrency, and
250
- deployment boundaries. Report security issues through the
251
- [security policy](https://github.com/Olyx-labs/olyx-guardrails/security/policy),
252
- not the public issue tracker.
253
-
254
- ## Development
255
-
256
- Run the same gates used by CI:
257
-
258
- ```bash
259
- bin/setup
260
- bin/ci
261
- ```
192
+ ## Contributing and security
193
+
194
+ See [CONTRIBUTING.md](https://github.com/Olyx-labs/olyx-guardrails/blob/master/CONTRIBUTING.md) for development setup and checks,
195
+ [CHANGELOG.md](CHANGELOG.md) for release changes, and the
196
+ [release runbook](https://github.com/Olyx-labs/olyx-guardrails/blob/master/docs/RELEASING.md) for maintainer procedures.
197
+
198
+ Rails adapter changes must also pass `bundle exec appraisal rake test`.
262
199
 
263
- The development bundle includes Rails so contributors exercise the integration
264
- from the default test suite; Rails remains an optional runtime dependency for
265
- gem consumers. The Rails compatibility matrix is managed with Appraisal. See
266
- [contribution guide](https://github.com/Olyx-labs/olyx-guardrails/blob/master/CONTRIBUTING.md)
267
- before submitting a change.
200
+ Report vulnerabilities through the [security policy](https://github.com/Olyx-labs/olyx-guardrails/blob/master/SECURITY.md).
268
201
 
269
202
  ## License
270
203
 
271
- Olyx Guardrails is available under the
272
204
  [Apache License 2.0](LICENSE).
@@ -10,42 +10,25 @@ module Olyx
10
10
 
11
11
  module_function
12
12
 
13
+ # Keep the fixed public contract together rather than splitting its fields across helpers.
14
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
13
15
  def call(checks, analysis, policy)
14
16
  ordered = CHECK_ORDER.filter_map { |type| checks[type] }
15
- base_result(checks, ordered, policy)
16
- .merge(risk(checks, ordered, analysis))
17
- .merge(analysis_result(analysis))
18
- end
19
-
20
- def base_result(checks, ordered, policy)
21
- detection(checks, ordered).merge(policy(checks[:policy], policy))
22
- end
23
-
24
- def detection(checks, ordered)
25
- {
17
+ result = {
26
18
  allowed: ordered.all? { |check| check[:allowed] },
27
19
  pii_detected: checks[:pii][:detected],
28
20
  injection_attempt: checks[:injection][:injection_attempt],
29
- secret_leaked: checks[:secret][:leaked]
21
+ secret_leaked: checks[:secret][:leaked],
22
+ policy_name: policy.name,
23
+ policy_violated: checks[:policy][:violated],
24
+ policy_findings: checks[:policy][:findings],
25
+ risk_score: RiskScorer.call(checks, analysis),
26
+ checks: ordered
30
27
  }
28
+ result[:llm_analysis] = analysis if analysis
29
+ result
31
30
  end
32
-
33
- def policy(check, configuration)
34
- {
35
- policy_name: configuration.name,
36
- policy_violated: check[:violated],
37
- policy_findings: check[:findings]
38
- }
39
- end
40
-
41
- def risk(checks, ordered, analysis)
42
- { risk_score: RiskScorer.call(checks, ordered, analysis), checks: ordered }
43
- end
44
-
45
- def analysis_result(analysis)
46
- analysis ? { llm_analysis: analysis } : {}
47
- end
48
- private_class_method :base_result, :detection, :policy, :risk, :analysis_result
31
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
49
32
  end
50
33
  end
51
34
  end
@@ -3,9 +3,9 @@
3
3
  require_relative 'check_result_builder'
4
4
  require_relative 'check_analyzer'
5
5
  require_relative 'check_set'
6
- require_relative 'check_pipeline'
7
6
  require_relative 'policy'
8
7
  require_relative 'validation'
8
+ require_relative 'message_source'
9
9
 
10
10
  module Olyx
11
11
  module Guardrails
@@ -15,16 +15,22 @@ module Olyx
15
15
  new(input, **).call
16
16
  end
17
17
 
18
- def initialize(input, policy:, llm_provider:)
19
- @source = input.to_s
18
+ def initialize(input, policy:, llm_provider:, structured: false)
19
+ Validation.array_of!(input, Hash, name: 'messages') if structured
20
+ @messages = input if structured
21
+ @source = input.to_s unless structured
20
22
  @policy = policy
21
23
  @llm_provider = llm_provider
22
24
  validate_options!
25
+ @source = MessageSource.call(input) if structured
23
26
  end
24
27
 
25
28
  def call
26
- checks = CheckSet.call(@source, policy: @policy)
27
- CheckPipeline.call(@source, checks, policy: @policy, llm_provider: @llm_provider)
29
+ checks = CheckSet.call(@source, policy: @policy, messages: @messages)
30
+ merged, analysis = CheckAnalyzer.call(
31
+ checks, provider: @llm_provider, source: @source, policy: @policy
32
+ )
33
+ CheckResultBuilder.call(merged, analysis, @policy)
28
34
  end
29
35
 
30
36
  private
@@ -1,33 +1,80 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'checks/injection_check'
4
- require_relative 'checks/length_check'
5
- require_relative 'checks/pii_check'
6
- require_relative 'checks/policy_check'
7
- require_relative 'checks/secret_check'
8
- require_relative 'checks/skipped_checks'
3
+ require_relative 'injection_detector'
4
+ require_relative 'pii/text_scrubber'
5
+ require_relative 'policy_scanner'
6
+ require_relative 'secret_scanner'
9
7
 
10
8
  module Olyx
11
9
  module Guardrails
12
10
  # Coordinates independent deterministic checks for one normalized input.
13
11
  class CheckSet
14
- CHECKS = {
15
- pii: Checks::PiiCheck,
16
- injection: Checks::InjectionCheck,
17
- secret: Checks::SecretCheck,
18
- policy: Checks::PolicyCheck
19
- }.freeze
20
-
21
- def self.call(source, policy:)
22
- length = Checks::LengthCheck.call(source, policy)
23
- content = length[:allowed] ? scan(source, policy) : Checks::SkippedChecks.call
12
+ def self.call(source, policy:, messages: nil)
13
+ length = length_check(source, policy)
14
+ content = length[:allowed] ? scan(source, policy, messages) : skipped_checks
24
15
  content.merge(length: length)
25
16
  end
26
17
 
27
- def self.scan(source, policy)
28
- CHECKS.to_h { |name, check| [name, check.call(source, policy)] }
18
+ def self.scan(source, policy, messages)
19
+ {
20
+ pii: pii_check(source, policy),
21
+ injection: injection_check(source, policy, messages),
22
+ secret: secret_check(source, policy),
23
+ policy: policy_check(source, policy)
24
+ }
29
25
  end
30
- private_class_method :scan
26
+
27
+ def self.length_check(source, policy)
28
+ length = source.length
29
+ maximum = policy.max_input_length
30
+ { type: 'length', allowed: length <= maximum, length: length, max_length: maximum }
31
+ end
32
+
33
+ def self.pii_check(source, policy)
34
+ detected = Pii::TextScrubber.detect?(source)
35
+ { type: 'pii', allowed: !detected || !policy.block_pii?, detected: detected }
36
+ end
37
+
38
+ def self.injection_check(source, policy, messages)
39
+ scan = InjectionDetector.scan(messages || [{ 'role' => 'user', 'content' => source }])
40
+ attempt = scan[:injection_attempt]
41
+ {
42
+ type: 'injection',
43
+ allowed: !attempt || !policy.block_injections?,
44
+ injection_attempt: attempt,
45
+ patterns: scan[:patterns]
46
+ }
47
+ end
48
+
49
+ def self.secret_check(source, policy)
50
+ scan = SecretScanner.scan(source, custom_patterns: policy.secret_patterns)
51
+ leaked = scan[:leaked]
52
+ { type: 'secret', allowed: !leaked || !policy.block_secrets?, leaked: leaked, count: scan[:findings].size }
53
+ end
54
+
55
+ def self.policy_check(source, policy)
56
+ scan = PolicyScanner.scan(source, policy: policy)
57
+ findings = scan[:findings]
58
+ {
59
+ type: 'policy', allowed: !scan[:blocked], violated: scan[:violated],
60
+ count: findings.size, findings: findings
61
+ }
62
+ end
63
+
64
+ def self.skipped_checks
65
+ {
66
+ pii: skipped('pii', detected: false),
67
+ injection: skipped('injection', injection_attempt: false, patterns: []),
68
+ secret: skipped('secret', leaked: false, count: 0),
69
+ policy: skipped('policy', violated: false, count: 0, findings: [])
70
+ }
71
+ end
72
+
73
+ def self.skipped(type, **fields)
74
+ { type: type, allowed: true, skipped: true, **fields }
75
+ end
76
+ private_class_method :injection_check, :length_check, :pii_check, :policy_check, :scan,
77
+ :secret_check, :skipped, :skipped_checks
31
78
  end
32
79
  end
33
80
  end
@@ -19,8 +19,7 @@ module Olyx
19
19
  /forget\s+(?:all\s+)?(?:your|previous)\s+instructions?/i,
20
20
  /your\s+(?:new\s+)?(?:instructions?\s+are|rules?\s+are)/i,
21
21
  /(?:override|bypass|ignore)\s+your\s+(?:instructions?|safety|restrictions?|filters?|rules?|training)/i,
22
- /(?:pretend|act)\s+(?:you\s+are|as\s+if\s+you|as\s+a(?:n?\s+AI)?)/i,
23
- /you\s+are\s+now\s+(?:a|an|free|DAN|unrestricted)/i,
22
+ /you\s+are\s+now\s+(?:an?\s+)?(?:free|DAN|unrestricted)(?:\s+AI)?/i,
24
23
  /(?:jailbreak|dan\s+mode|developer\s+mode|god\s+mode|unrestricted\s+mode)/i,
25
24
  /do\s+anything\s+now/i,
26
25
  /you\s+have\s+no\s+(?:restrictions?|limits?|rules?|filters?)/i,