olyx-guardrails 1.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 +7 -0
- data/CHANGELOG.md +117 -0
- data/CODE_OF_CONDUCT.md +28 -0
- data/CONTRIBUTING.md +63 -0
- data/LICENSE +134 -0
- data/README.md +611 -0
- data/SECURITY.md +35 -0
- data/docs/API.md +460 -0
- data/examples/claude_analyzer.rb +53 -0
- data/examples/custom_policy.rb +41 -0
- data/examples/notifier.rb +37 -0
- data/examples/openai_analyzer.rb +45 -0
- data/examples/rails_opt_in.rb +99 -0
- data/examples/ruby_only.rb +53 -0
- data/lib/generators/olyx_guardrails/install_generator.rb +22 -0
- data/lib/generators/olyx_guardrails/templates/initializer.rb.tt +16 -0
- data/lib/generators/olyx_guardrails/templates/policy.yml.tt +41 -0
- data/lib/olyx/guardrails/ai/analysis_normalizer.rb +26 -0
- data/lib/olyx/guardrails/ai/analysis_pipeline.rb +26 -0
- data/lib/olyx/guardrails/ai/boolean_validator.rb +18 -0
- data/lib/olyx/guardrails/ai/flag_finding_merger.rb +16 -0
- data/lib/olyx/guardrails/ai/result_sanitizer.rb +29 -0
- data/lib/olyx/guardrails/ai/secret_finding_merger.rb +19 -0
- data/lib/olyx/guardrails/ai/standard_finding_merger.rb +24 -0
- data/lib/olyx/guardrails/ai_analysis.rb +29 -0
- data/lib/olyx/guardrails/ai_context_builder.rb +32 -0
- data/lib/olyx/guardrails/ai_failure_handler.rb +17 -0
- data/lib/olyx/guardrails/ai_finding_merger.rb +30 -0
- data/lib/olyx/guardrails/check_analyzer.rb +26 -0
- data/lib/olyx/guardrails/check_base_result.rb +32 -0
- data/lib/olyx/guardrails/check_pipeline.rb +17 -0
- data/lib/olyx/guardrails/check_result_builder.rb +24 -0
- data/lib/olyx/guardrails/check_runner.rb +42 -0
- data/lib/olyx/guardrails/check_set.rb +33 -0
- data/lib/olyx/guardrails/checks/injection_check.rb +20 -0
- data/lib/olyx/guardrails/checks/injection_result.rb +22 -0
- data/lib/olyx/guardrails/checks/length_check.rb +18 -0
- data/lib/olyx/guardrails/checks/message_injection_check.rb +19 -0
- data/lib/olyx/guardrails/checks/pii_check.rb +19 -0
- data/lib/olyx/guardrails/checks/policy_check.rb +26 -0
- data/lib/olyx/guardrails/checks/secret_check.rb +30 -0
- data/lib/olyx/guardrails/checks/skipped_checks.rb +26 -0
- data/lib/olyx/guardrails/enum_value.rb +20 -0
- data/lib/olyx/guardrails/errors.rb +22 -0
- data/lib/olyx/guardrails/injection_detector.rb +27 -0
- data/lib/olyx/guardrails/injection_patterns.rb +44 -0
- data/lib/olyx/guardrails/integrations/openai/analyzer_setup.rb +21 -0
- data/lib/olyx/guardrails/integrations/openai/configuration_resolver.rb +28 -0
- data/lib/olyx/guardrails/integrations/openai/dependency_contracts.rb +28 -0
- data/lib/olyx/guardrails/integrations/openai/input_builder.rb +39 -0
- data/lib/olyx/guardrails/integrations/openai/member_reader.rb +21 -0
- data/lib/olyx/guardrails/integrations/openai/model_identifier.rb +28 -0
- data/lib/olyx/guardrails/integrations/openai/refusal_guard.rb +28 -0
- data/lib/olyx/guardrails/integrations/openai/request_builder.rb +36 -0
- data/lib/olyx/guardrails/integrations/openai/request_configuration.rb +25 -0
- data/lib/olyx/guardrails/integrations/openai/request_values.rb +33 -0
- data/lib/olyx/guardrails/integrations/openai/response_contents.rb +22 -0
- data/lib/olyx/guardrails/integrations/openai/response_option_keys.rb +32 -0
- data/lib/olyx/guardrails/integrations/openai/response_options.rb +23 -0
- data/lib/olyx/guardrails/integrations/openai/schema_registry.rb +46 -0
- data/lib/olyx/guardrails/integrations/openai/sdk.rb +26 -0
- data/lib/olyx/guardrails/integrations/openai/signal_summary.rb +40 -0
- data/lib/olyx/guardrails/integrations/openai_analyzer.rb +107 -0
- data/lib/olyx/guardrails/integrations/openai_analyzer_configuration.rb +38 -0
- data/lib/olyx/guardrails/integrations/openai_response_parser.rb +34 -0
- data/lib/olyx/guardrails/message_check_runner.rb +24 -0
- data/lib/olyx/guardrails/message_check_set.rb +20 -0
- data/lib/olyx/guardrails/message_content.rb +27 -0
- data/lib/olyx/guardrails/message_source.rb +16 -0
- data/lib/olyx/guardrails/multi_turn_injection_detector.rb +18 -0
- data/lib/olyx/guardrails/multi_turn_pair_scanner.rb +35 -0
- data/lib/olyx/guardrails/multi_turn_variant_pairs.rb +25 -0
- data/lib/olyx/guardrails/notification/base_event.rb +35 -0
- data/lib/olyx/guardrails/notification/deep_freezer.rb +25 -0
- data/lib/olyx/guardrails/notification/delivery_dispatcher.rb +38 -0
- data/lib/olyx/guardrails/notification/delivery_summary.rb +17 -0
- data/lib/olyx/guardrails/notification/handler.rb +33 -0
- data/lib/olyx/guardrails/notification/handler_collection.rb +25 -0
- data/lib/olyx/guardrails/notification/handler_collection_validator.rb +23 -0
- data/lib/olyx/guardrails/notification/key.rb +19 -0
- data/lib/olyx/guardrails/notification/metadata.rb +34 -0
- data/lib/olyx/guardrails/notification/notifier_setup.rb +19 -0
- data/lib/olyx/guardrails/notification/preview.rb +27 -0
- data/lib/olyx/guardrails/notification/scrubber.rb +22 -0
- data/lib/olyx/guardrails/notification/violation_labels.rb +19 -0
- data/lib/olyx/guardrails/notification_event_builder.rb +41 -0
- data/lib/olyx/guardrails/notification_sanitizer.rb +33 -0
- data/lib/olyx/guardrails/notifier.rb +53 -0
- data/lib/olyx/guardrails/notifier_configuration.rb +30 -0
- data/lib/olyx/guardrails/pii/ambiguous_numeric_date.rb +32 -0
- data/lib/olyx/guardrails/pii/block_scrubber.rb +31 -0
- data/lib/olyx/guardrails/pii/content_scrubber.rb +33 -0
- data/lib/olyx/guardrails/pii/date_validator.rb +25 -0
- data/lib/olyx/guardrails/pii/hash_key.rb +19 -0
- data/lib/olyx/guardrails/pii/iban_remainder.rb +25 -0
- data/lib/olyx/guardrails/pii/iban_validator.rb +34 -0
- data/lib/olyx/guardrails/pii/ipv4_validator.rb +17 -0
- data/lib/olyx/guardrails/pii/ipv6_validator.rb +20 -0
- data/lib/olyx/guardrails/pii/luhn_checksum.rb +29 -0
- data/lib/olyx/guardrails/pii/luhn_validator.rb +21 -0
- data/lib/olyx/guardrails/pii/message_scrubber.rb +31 -0
- data/lib/olyx/guardrails/pii/named_date_validator.rb +28 -0
- data/lib/olyx/guardrails/pii/numeric_date_validator.rb +26 -0
- data/lib/olyx/guardrails/pii/pattern_catalog.rb +41 -0
- data/lib/olyx/guardrails/pii/sin_validator.rb +24 -0
- data/lib/olyx/guardrails/pii/ssn_validator.rb +21 -0
- data/lib/olyx/guardrails/pii/text_scrubber.rb +28 -0
- data/lib/olyx/guardrails/pii/year_first_date.rb +18 -0
- data/lib/olyx/guardrails/pii_scrubber.rb +23 -0
- data/lib/olyx/guardrails/pii_validators.rb +26 -0
- data/lib/olyx/guardrails/policy/ai_failure_mode.rb +15 -0
- data/lib/olyx/guardrails/policy/configuration.rb +62 -0
- data/lib/olyx/guardrails/policy/configuration_hash.rb +20 -0
- data/lib/olyx/guardrails/policy/finding_order.rb +20 -0
- data/lib/olyx/guardrails/policy/finding_presenter.rb +41 -0
- data/lib/olyx/guardrails/policy/match_collector.rb +20 -0
- data/lib/olyx/guardrails/policy/name.rb +23 -0
- data/lib/olyx/guardrails/policy/normalized_pattern_matcher.rb +34 -0
- data/lib/olyx/guardrails/policy/pattern_matcher.rb +19 -0
- data/lib/olyx/guardrails/policy/redaction_result.rb +20 -0
- data/lib/olyx/guardrails/policy/redaction_spans.rb +32 -0
- data/lib/olyx/guardrails/policy/rule_collection.rb +34 -0
- data/lib/olyx/guardrails/policy/rule_matcher.rb +29 -0
- data/lib/olyx/guardrails/policy/rule_normalizer.rb +21 -0
- data/lib/olyx/guardrails/policy/scan_result.rb +21 -0
- data/lib/olyx/guardrails/policy/secret_pattern_collection.rb +22 -0
- data/lib/olyx/guardrails/policy/unmatched_segment_builder.rb +28 -0
- data/lib/olyx/guardrails/policy/unmatched_transformer.rb +20 -0
- data/lib/olyx/guardrails/policy.rb +46 -0
- data/lib/olyx/guardrails/policy_aware_redactor.rb +42 -0
- data/lib/olyx/guardrails/policy_redactor.rb +28 -0
- data/lib/olyx/guardrails/policy_rule/configuration.rb +46 -0
- data/lib/olyx/guardrails/policy_rule/description_value.rb +24 -0
- data/lib/olyx/guardrails/policy_rule/match_mode.rb +15 -0
- data/lib/olyx/guardrails/policy_rule/name_value.rb +22 -0
- data/lib/olyx/guardrails/policy_rule/pattern_compiler.rb +28 -0
- data/lib/olyx/guardrails/policy_rule/regexp_compiler.rb +31 -0
- data/lib/olyx/guardrails/policy_rule/replacement_value.rb +26 -0
- data/lib/olyx/guardrails/policy_rule/term_compiler.rb +33 -0
- data/lib/olyx/guardrails/policy_rule/text_values.rb +23 -0
- data/lib/olyx/guardrails/policy_rule/values.rb +33 -0
- data/lib/olyx/guardrails/policy_rule.rb +30 -0
- data/lib/olyx/guardrails/policy_scanner.rb +44 -0
- data/lib/olyx/guardrails/rails/action_cable.rb +21 -0
- data/lib/olyx/guardrails/rails/active_job_handler.rb +27 -0
- data/lib/olyx/guardrails/rails/active_model_validator.rb +23 -0
- data/lib/olyx/guardrails/rails/check_telemetry.rb +19 -0
- data/lib/olyx/guardrails/rails/configuration.rb +68 -0
- data/lib/olyx/guardrails/rails/configuration_finalizer.rb +30 -0
- data/lib/olyx/guardrails/rails/configuration_registry.rb +41 -0
- data/lib/olyx/guardrails/rails/configuration_values.rb +30 -0
- data/lib/olyx/guardrails/rails/constant_resolver.rb +18 -0
- data/lib/olyx/guardrails/rails/controller.rb +34 -0
- data/lib/olyx/guardrails/rails/controller_metadata.rb +22 -0
- data/lib/olyx/guardrails/rails/decision_service.rb +26 -0
- data/lib/olyx/guardrails/rails/enforcer.rb +33 -0
- data/lib/olyx/guardrails/rails/evaluation_lifecycle.rb +23 -0
- data/lib/olyx/guardrails/rails/evaluation_service.rb +12 -0
- data/lib/olyx/guardrails/rails/filter_parameters.rb +26 -0
- data/lib/olyx/guardrails/rails/graphql.rb +21 -0
- data/lib/olyx/guardrails/rails/ingress.rb +26 -0
- data/lib/olyx/guardrails/rails/input_runtime.rb +25 -0
- data/lib/olyx/guardrails/rails/instrumentation.rb +39 -0
- data/lib/olyx/guardrails/rails/instrumentation_payloads.rb +25 -0
- data/lib/olyx/guardrails/rails/instrumentation_publisher.rb +24 -0
- data/lib/olyx/guardrails/rails/integration_configuration.rb +27 -0
- data/lib/olyx/guardrails/rails/job.rb +37 -0
- data/lib/olyx/guardrails/rails/job_argument.rb +41 -0
- data/lib/olyx/guardrails/rails/job_reference.rb +36 -0
- data/lib/olyx/guardrails/rails/message_evaluation_service.rb +13 -0
- data/lib/olyx/guardrails/rails/message_runtime.rb +20 -0
- data/lib/olyx/guardrails/rails/notification_dispatcher.rb +22 -0
- data/lib/olyx/guardrails/rails/output_runtime.rb +26 -0
- data/lib/olyx/guardrails/rails/path_value.rb +30 -0
- data/lib/olyx/guardrails/rails/policy_configuration.rb +25 -0
- data/lib/olyx/guardrails/rails/policy_document.rb +40 -0
- data/lib/olyx/guardrails/rails/policy_file.rb +29 -0
- data/lib/olyx/guardrails/rails/policy_file_error.rb +25 -0
- data/lib/olyx/guardrails/rails/policy_rule_names.rb +19 -0
- data/lib/olyx/guardrails/rails/policy_yaml.rb +22 -0
- data/lib/olyx/guardrails/rails/queue_name.rb +21 -0
- data/lib/olyx/guardrails/rails/redaction_payload.rb +36 -0
- data/lib/olyx/guardrails/rails/redaction_service.rb +21 -0
- data/lib/olyx/guardrails/rails/result_summary.rb +36 -0
- data/lib/olyx/guardrails/rails/runtime.rb +30 -0
- data/lib/olyx/guardrails/rails/timer.rb +27 -0
- data/lib/olyx/guardrails/rails/upload.rb +34 -0
- data/lib/olyx/guardrails/rails.rb +35 -0
- data/lib/olyx/guardrails/railtie.rb +19 -0
- data/lib/olyx/guardrails/redaction/content_result.rb +32 -0
- data/lib/olyx/guardrails/redaction/input_validator.rb +17 -0
- data/lib/olyx/guardrails/redaction/public_result.rb +23 -0
- data/lib/olyx/guardrails/redaction/unrestricted_text.rb +17 -0
- data/lib/olyx/guardrails/redactor.rb +29 -0
- data/lib/olyx/guardrails/risk/ai_score.rb +19 -0
- data/lib/olyx/guardrails/risk/check_weights.rb +18 -0
- data/lib/olyx/guardrails/risk/deterministic_score.rb +20 -0
- data/lib/olyx/guardrails/risk/weights.rb +18 -0
- data/lib/olyx/guardrails/risk_scorer.rb +28 -0
- data/lib/olyx/guardrails/secret_finding_collector.rb +31 -0
- data/lib/olyx/guardrails/secret_scanner.rb +42 -0
- data/lib/olyx/guardrails/secrets/blocked.rb +17 -0
- data/lib/olyx/guardrails/secrets/catalog_source.rb +28 -0
- data/lib/olyx/guardrails/secrets/cloud_credential_patterns.rb +16 -0
- data/lib/olyx/guardrails/secrets/confidentiality_source.rb +21 -0
- data/lib/olyx/guardrails/secrets/custom_pattern_compiler.rb +23 -0
- data/lib/olyx/guardrails/secrets/custom_pattern_source.rb +30 -0
- data/lib/olyx/guardrails/secrets/finding_order.rb +17 -0
- data/lib/olyx/guardrails/secrets/finding_presenter.rb +34 -0
- data/lib/olyx/guardrails/secrets/generic_credential_patterns.rb +15 -0
- data/lib/olyx/guardrails/secrets/internal_endpoint_source.rb +28 -0
- data/lib/olyx/guardrails/secrets/network_patterns.rb +13 -0
- data/lib/olyx/guardrails/secrets/normalized_source_set.rb +27 -0
- data/lib/olyx/guardrails/secrets/pattern_catalog.rb +37 -0
- data/lib/olyx/guardrails/secrets/private_network_validator.rb +19 -0
- data/lib/olyx/guardrails/secrets/redaction_spans.rb +32 -0
- data/lib/olyx/guardrails/secrets/redactor.rb +27 -0
- data/lib/olyx/guardrails/secrets/regexp_finding_source.rb +21 -0
- data/lib/olyx/guardrails/secrets/source_set.rb +19 -0
- data/lib/olyx/guardrails/single_message_injection_detector.rb +35 -0
- data/lib/olyx/guardrails/supplemental_violation_labels.rb +22 -0
- data/lib/olyx/guardrails/text/base64_decoder.rb +45 -0
- data/lib/olyx/guardrails/text/detection_variants.rb +28 -0
- data/lib/olyx/guardrails/text/html_decoder.rb +18 -0
- data/lib/olyx/guardrails/text/mapped_builder.rb +33 -0
- data/lib/olyx/guardrails/text/mapped_normalization.rb +27 -0
- data/lib/olyx/guardrails/text/normalizer.rb +35 -0
- data/lib/olyx/guardrails/text/unicode_escape_decoder.rb +20 -0
- data/lib/olyx/guardrails/text/url_decoder.rb +20 -0
- data/lib/olyx/guardrails/validation.rb +33 -0
- data/lib/olyx/guardrails/version.rb +8 -0
- data/lib/olyx/guardrails/violation_labels.rb +24 -0
- data/lib/olyx/guardrails.rb +63 -0
- metadata +283 -0
data/README.md
ADDED
|
@@ -0,0 +1,611 @@
|
|
|
1
|
+
# Olyx Guardrails
|
|
2
|
+
|
|
3
|
+
[](https://rubygems.org/gems/olyx-guardrails)
|
|
4
|
+
[](https://github.com/Olyx-labs/olyx-guardrails/actions/workflows/test.yml)
|
|
5
|
+
[](https://securityscorecards.dev/viewer/?uri=github.com/Olyx-labs/olyx-guardrails)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
`olyx-guardrails` is a dependency-light Ruby library for synchronous AI
|
|
9
|
+
boundary safety:
|
|
10
|
+
|
|
11
|
+
- PII and credential redaction
|
|
12
|
+
- Prompt-injection and jailbreak detection
|
|
13
|
+
- Secret and internal-endpoint detection
|
|
14
|
+
- Input-length enforcement
|
|
15
|
+
- Reusable named policies for organization-specific restricted content
|
|
16
|
+
- Structured-message checks and completed-output checks
|
|
17
|
+
- Optional semantic analysis through a caller-supplied AI hook
|
|
18
|
+
- Optional OpenAI Responses API connector with native schema models
|
|
19
|
+
- Vendor-neutral notifications through configurable application handlers
|
|
20
|
+
|
|
21
|
+
The core checks run in-process and have no runtime gem dependencies.
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```ruby
|
|
26
|
+
gem "olyx-guardrails", "~> 1.0"
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
bundle install
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Ruby 3.4 or newer is required. The repository pins the current Ruby 3.4 patch
|
|
34
|
+
release in `.ruby-version` for rbenv users.
|
|
35
|
+
|
|
36
|
+
## Choose an integration path
|
|
37
|
+
|
|
38
|
+
- [`examples/ruby_only.rb`](examples/ruby_only.rb) is a runnable, framework-free
|
|
39
|
+
example with custom policy enforcement, redaction, completed-output checks,
|
|
40
|
+
and vendor-neutral notifications.
|
|
41
|
+
- [`examples/rails_opt_in.rb`](examples/rails_opt_in.rb) shows explicit Rails
|
|
42
|
+
boundaries for controllers, GraphQL, Action Cable, Active Job, Active Model,
|
|
43
|
+
uploads, callbacks, and service objects.
|
|
44
|
+
|
|
45
|
+
The Ruby-only path requires no Rails runtime. The Rails path remains opt-in and
|
|
46
|
+
uses the same core `Policy` and decision contracts.
|
|
47
|
+
|
|
48
|
+
## Rails integration
|
|
49
|
+
|
|
50
|
+
The core gem remains framework-neutral. Rails applications receive an optional
|
|
51
|
+
Railtie, boot-validated configuration, explicit adapters for controllers,
|
|
52
|
+
GraphQL, Action Cable, Active Job, Active Model, uploads, and service objects,
|
|
53
|
+
sanitized Active Support events, filtered AI parameter names, and an Active Job
|
|
54
|
+
notifier adapter. Enforcement remains opt-in: the gem never guesses which
|
|
55
|
+
application values are AI-bound.
|
|
56
|
+
|
|
57
|
+
Install the Rails configuration:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
bin/rails generate olyx_guardrails:install
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
This creates `config/initializers/olyx_guardrails.rb` and an environment-keyed
|
|
64
|
+
`config/olyx_guardrails.yml`. YAML is loaded with `YAML.safe_load`; ERB,
|
|
65
|
+
aliases, arbitrary classes, and symbols are not evaluated. A missing,
|
|
66
|
+
unreadable, unsafe, or invalid configured policy stops application boot instead
|
|
67
|
+
of falling back to a permissive policy.
|
|
68
|
+
|
|
69
|
+
Use the explicit controller concern at the AI boundary:
|
|
70
|
+
|
|
71
|
+
```ruby
|
|
72
|
+
class AiRequestsController < ApplicationController
|
|
73
|
+
include Olyx::Guardrails::Rails::Controller
|
|
74
|
+
|
|
75
|
+
rescue_from Olyx::Guardrails::Blocked do |error|
|
|
76
|
+
render json: { error: "input_rejected", decision: error.decision },
|
|
77
|
+
status: :unprocessable_entity
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def create
|
|
81
|
+
guardrails_check!(
|
|
82
|
+
params.require(:prompt),
|
|
83
|
+
metadata: { user_id: current_user.id }
|
|
84
|
+
)
|
|
85
|
+
safe_prompt = guardrails_redact(params[:prompt])[:text]
|
|
86
|
+
|
|
87
|
+
render json: LlmClient.complete(safe_prompt), status: :created
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
The exception contains a frozen decision summary—policy name, risk score,
|
|
93
|
+
violation labels, and restricted rule identifiers—but never the input or AI
|
|
94
|
+
reason. The application owns the HTTP response because `403`, `422`, and other
|
|
95
|
+
policies have different semantics.
|
|
96
|
+
|
|
97
|
+
Protect the other common Rails ingress paths explicitly:
|
|
98
|
+
|
|
99
|
+
```ruby
|
|
100
|
+
class CompletionResolver
|
|
101
|
+
include Olyx::Guardrails::Rails::GraphQL
|
|
102
|
+
|
|
103
|
+
def resolve(prompt:)
|
|
104
|
+
guardrails_check_graphql!(prompt)
|
|
105
|
+
result = LlmClient.complete(prompt)
|
|
106
|
+
guardrails_check_graphql_output!(result)
|
|
107
|
+
result
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
class PromptChannel < ApplicationCable::Channel
|
|
112
|
+
include Olyx::Guardrails::Rails::ActionCable
|
|
113
|
+
|
|
114
|
+
def receive(data)
|
|
115
|
+
guardrails_check_cable!(data.fetch("prompt"))
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
class CompletionJob < ApplicationJob
|
|
120
|
+
include Olyx::Guardrails::Rails::Job
|
|
121
|
+
guardrails_input_arguments 0, :system_prompt
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
class PromptDraft
|
|
125
|
+
include ActiveModel::Model
|
|
126
|
+
attr_accessor :prompt
|
|
127
|
+
validates :prompt, olyx_guardrails: true
|
|
128
|
+
end
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
For callbacks and service objects, call
|
|
132
|
+
`Olyx::Guardrails::Rails::Enforcer.check!`, `check_messages!`, or
|
|
133
|
+
`check_output!`. For uploads, the application retains ownership of parsing and
|
|
134
|
+
passes a bounded text extractor:
|
|
135
|
+
|
|
136
|
+
```ruby
|
|
137
|
+
Olyx::Guardrails::Rails::Upload.check!(
|
|
138
|
+
params.require(:document),
|
|
139
|
+
extractor: ->(upload) { DocumentText.extract(upload.tempfile) }
|
|
140
|
+
)
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
These adapters close framework routing gaps; they do not parse arbitrary file
|
|
144
|
+
formats, discover model calls, or intercept every callback automatically.
|
|
145
|
+
|
|
146
|
+
Rails checks publish content-free Active Support events:
|
|
147
|
+
|
|
148
|
+
```ruby
|
|
149
|
+
ActiveSupport::Notifications.subscribe("violation.olyx_guardrails") do |event|
|
|
150
|
+
Rails.logger.warn(event.payload.to_json)
|
|
151
|
+
end
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Available events are `check.olyx_guardrails`, `violation.olyx_guardrails`,
|
|
155
|
+
`redact.olyx_guardrails`, and `notification.olyx_guardrails`. They include
|
|
156
|
+
bounded decision or delivery fields and evaluation duration, never raw input,
|
|
157
|
+
redacted output, metadata, or analyzer prose. Subscriber failures are isolated
|
|
158
|
+
from enforcement.
|
|
159
|
+
|
|
160
|
+
Use any Active Job backend for asynchronous notification delivery:
|
|
161
|
+
|
|
162
|
+
```ruby
|
|
163
|
+
Olyx::Guardrails::Rails.configure do |config|
|
|
164
|
+
config.policy_path = Rails.root.join("config/olyx_guardrails.yml")
|
|
165
|
+
config.notifier_handlers = {
|
|
166
|
+
incidents: Olyx::Guardrails::Rails::ActiveJobHandler.new(
|
|
167
|
+
job: "GuardrailIncidentJob",
|
|
168
|
+
queue: :low
|
|
169
|
+
)
|
|
170
|
+
}
|
|
171
|
+
end
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
The String job name is resolved when the event is delivered, which remains
|
|
175
|
+
compatible with Rails code reloading. The job receives the same sanitized,
|
|
176
|
+
immutable vendor-neutral event described under Notifications.
|
|
177
|
+
|
|
178
|
+
## Configure a reusable policy
|
|
179
|
+
|
|
180
|
+
All enforcement settings and organization-specific restrictions live in one
|
|
181
|
+
immutable policy object:
|
|
182
|
+
|
|
183
|
+
```ruby
|
|
184
|
+
require "olyx/guardrails"
|
|
185
|
+
|
|
186
|
+
POLICY = Olyx::Guardrails::Policy.new(
|
|
187
|
+
name: "customer-data-boundary",
|
|
188
|
+
max_input_length: 4_000,
|
|
189
|
+
block_pii: true,
|
|
190
|
+
block_injections: true,
|
|
191
|
+
block_secrets: true,
|
|
192
|
+
ai_failure_mode: :block,
|
|
193
|
+
secret_patterns: ["company-token-[a-z0-9]{24}"],
|
|
194
|
+
rules: [
|
|
195
|
+
{
|
|
196
|
+
name: :confidential_projects,
|
|
197
|
+
description: "Project names that must not enter an AI request",
|
|
198
|
+
patterns: ["project[ -]falcon", /PF-\d{4}/],
|
|
199
|
+
block: true,
|
|
200
|
+
replacement: "[CONFIDENTIAL_PROJECT]"
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
name: :competitor_references,
|
|
204
|
+
terms: ["competitor corp"],
|
|
205
|
+
match: :whole_word,
|
|
206
|
+
block: false
|
|
207
|
+
}
|
|
208
|
+
]
|
|
209
|
+
)
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Use `terms` for literal, case-insensitive restricted text; regex metacharacters
|
|
213
|
+
inside a term are escaped automatically. `match:` controls term interpretation:
|
|
214
|
+
`:substring` (default), `:whole_word`, or `:regexp`. String `patterns` are always
|
|
215
|
+
regular-expression source and compiled case-insensitively, while `Regexp`
|
|
216
|
+
patterns retain their flags. Each rule must define at least one term or pattern.
|
|
217
|
+
A blocking rule makes `check` reject the input; a non-blocking rule records the
|
|
218
|
+
violation for monitoring. `redact` transforms matches from either kind. Rule
|
|
219
|
+
configuration is compiled once when the policy is created, rejects
|
|
220
|
+
empty-matching or invalid expressions, and applies a timeout during matching.
|
|
221
|
+
|
|
222
|
+
`ai_failure_mode:` controls an optional analyzer failure: `:allow` keeps the
|
|
223
|
+
deterministic decision (the default), `:block` adds a failed AI check and rejects
|
|
224
|
+
the request, and `:raise` raises `Olyx::Guardrails::AiAnalyzerError`. Production
|
|
225
|
+
applications that require semantic analysis should choose `:block` or `:raise`
|
|
226
|
+
explicitly.
|
|
227
|
+
|
|
228
|
+
String-keyed configuration loaded from YAML or JSON is supported:
|
|
229
|
+
|
|
230
|
+
```ruby
|
|
231
|
+
policy = Olyx::Guardrails::Policy.from_h(configuration)
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Policy findings are separate from secret findings. They expose the rule name,
|
|
235
|
+
description, block decision, `[REDACTED]` marker, SHA-256 fingerprint, and offsets;
|
|
236
|
+
the matched restricted text is never returned in plaintext.
|
|
237
|
+
Use `secret_patterns` for organization-specific credential formats that should
|
|
238
|
+
remain classified as secrets; use `rules` for business restrictions.
|
|
239
|
+
See [`examples/custom_policy.rb`](examples/custom_policy.rb) for a complete
|
|
240
|
+
decision-and-redaction flow.
|
|
241
|
+
|
|
242
|
+
## Decision and transformation are separate
|
|
243
|
+
|
|
244
|
+
Use `check` to make an allow/block decision:
|
|
245
|
+
|
|
246
|
+
```ruby
|
|
247
|
+
require "olyx/guardrails"
|
|
248
|
+
|
|
249
|
+
result = Olyx::Guardrails.check(
|
|
250
|
+
input,
|
|
251
|
+
policy: POLICY
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
return forbidden unless result[:allowed]
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Use `redact` when transformed text is required:
|
|
258
|
+
|
|
259
|
+
```ruby
|
|
260
|
+
redaction = Olyx::Guardrails.redact(input, policy: POLICY)
|
|
261
|
+
|
|
262
|
+
safe_input = redaction[:text]
|
|
263
|
+
redaction[:redacted] # true when text changed
|
|
264
|
+
redaction[:pii_detected] # true when regex-detected PII was removed
|
|
265
|
+
redaction[:secret_leaked] # true when a secret was removed
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
`check` never claims to transform input, and `redact` never makes an
|
|
269
|
+
allow/block decision.
|
|
270
|
+
|
|
271
|
+
Use structured messages when adjacent turns matter, and explicit output names
|
|
272
|
+
at the completed model boundary:
|
|
273
|
+
|
|
274
|
+
```ruby
|
|
275
|
+
message_result = Olyx::Guardrails.check_messages(
|
|
276
|
+
[{role: "user", content: prompt}, {role: "assistant", content: draft}],
|
|
277
|
+
policy: POLICY
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
output_result = Olyx::Guardrails.check_output(completion, policy: POLICY)
|
|
281
|
+
safe_output = Olyx::Guardrails.redact_output(completion, policy: POLICY)[:text]
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
`check_output` and `redact_output` operate on completed values. They are
|
|
285
|
+
deliberately distinct entry-point names so applications can instrument input
|
|
286
|
+
and output boundaries without implying token-stream enforcement.
|
|
287
|
+
|
|
288
|
+
## `Olyx::Guardrails.check`
|
|
289
|
+
|
|
290
|
+
```ruby
|
|
291
|
+
Olyx::Guardrails.check(
|
|
292
|
+
input,
|
|
293
|
+
policy: Olyx::Guardrails::Policy.default,
|
|
294
|
+
ai_analyzer: nil
|
|
295
|
+
)
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
| Option | Type | Default | Description |
|
|
299
|
+
|---|---|---|---|
|
|
300
|
+
| `input` | any | — | Converted via `to_s` |
|
|
301
|
+
| `policy` | `Olyx::Guardrails::Policy` | `Policy.default` | Limits, built-in blocking behavior, and named restricted-content rules |
|
|
302
|
+
| `ai_analyzer` | callable or nil | `nil` | Optional semantic analyzer |
|
|
303
|
+
|
|
304
|
+
`Policy.default` preserves the built-in defaults: 10,000-character input limit,
|
|
305
|
+
block injections, flag PII and secrets, and no custom restricted-content
|
|
306
|
+
rules. Invalid policy configuration raises `ArgumentError` when the policy is
|
|
307
|
+
constructed; mistakes do not silently degrade into an allow decision.
|
|
308
|
+
|
|
309
|
+
```ruby
|
|
310
|
+
{
|
|
311
|
+
allowed: Boolean,
|
|
312
|
+
pii_detected: Boolean,
|
|
313
|
+
injection_attempt: Boolean,
|
|
314
|
+
secret_leaked: Boolean,
|
|
315
|
+
policy_name: String,
|
|
316
|
+
policy_violated: Boolean,
|
|
317
|
+
policy_findings: Array<Hash>,
|
|
318
|
+
risk_score: Float,
|
|
319
|
+
checks: [
|
|
320
|
+
{ type: "pii", allowed: true, detected: Boolean },
|
|
321
|
+
{ type: "injection", allowed: Boolean, injection_attempt: Boolean, patterns: Array },
|
|
322
|
+
{ type: "secret", allowed: Boolean, leaked: Boolean, count: Integer },
|
|
323
|
+
{ type: "policy", allowed: Boolean, violated: Boolean, count: Integer, findings: Array },
|
|
324
|
+
{ type: "length", allowed: Boolean, length: Integer, max_length: Integer }
|
|
325
|
+
],
|
|
326
|
+
ai_analysis: Hash # only when ai_analyzer is supplied and runs
|
|
327
|
+
}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
The length check runs first. When it fails, content and AI checks are skipped and
|
|
331
|
+
content checks carry `skipped: true`.
|
|
332
|
+
|
|
333
|
+
## `Olyx::Guardrails.redact`
|
|
334
|
+
|
|
335
|
+
```ruby
|
|
336
|
+
Olyx::Guardrails.redact(
|
|
337
|
+
input,
|
|
338
|
+
policy: Olyx::Guardrails::Policy.default
|
|
339
|
+
)
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
The returned `:text` has every regex-detected PII and secret match removed.
|
|
343
|
+
Repeated credentials in the same category are all redacted. Findings expose a
|
|
344
|
+
masked value, a short SHA-256 fingerprint, and source offsets—not plaintext
|
|
345
|
+
credentials.
|
|
346
|
+
|
|
347
|
+
```ruby
|
|
348
|
+
{
|
|
349
|
+
text: String,
|
|
350
|
+
redacted: Boolean,
|
|
351
|
+
pii_detected: Boolean,
|
|
352
|
+
secret_leaked: Boolean,
|
|
353
|
+
policy_name: String,
|
|
354
|
+
policy_violated: Boolean,
|
|
355
|
+
policy_findings: Array<Hash>,
|
|
356
|
+
findings: [
|
|
357
|
+
{
|
|
358
|
+
category: String,
|
|
359
|
+
matched: String, # masked
|
|
360
|
+
fingerprint: String, # "sha256:..."
|
|
361
|
+
start: Integer,
|
|
362
|
+
end: Integer
|
|
363
|
+
}
|
|
364
|
+
]
|
|
365
|
+
}
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
An oversized input raises `ArgumentError` rather than running an unbounded
|
|
369
|
+
transformation.
|
|
370
|
+
|
|
371
|
+
## AI analyzer hook
|
|
372
|
+
|
|
373
|
+
The optional hook receives `(text, context)` and returns a Hash or a schema
|
|
374
|
+
model implementing `deep_to_h`/`to_h`:
|
|
375
|
+
|
|
376
|
+
```ruby
|
|
377
|
+
hook = lambda do |text, context|
|
|
378
|
+
{
|
|
379
|
+
injection_attempt: false,
|
|
380
|
+
pii_detected: false,
|
|
381
|
+
secret_leaked: false,
|
|
382
|
+
risk_score: 0.2,
|
|
383
|
+
reason: "No semantic policy violation found"
|
|
384
|
+
}
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
result = Olyx::Guardrails.check(input, ai_analyzer: hook)
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
Boolean findings must be actual `true` or `false` values. Invalid responses and
|
|
391
|
+
hook exceptions are recorded under `result[:ai_analysis][:error]`; the policy's
|
|
392
|
+
`ai_failure_mode` then allows the deterministic result, blocks, or raises. AI
|
|
393
|
+
findings can add a violation but cannot clear one.
|
|
394
|
+
|
|
395
|
+
The hook receives raw input. If it calls a third-party model, the caller is
|
|
396
|
+
responsible for data-residency, privacy, and vendor-trust requirements.
|
|
397
|
+
The context includes locally matched `policy_violated` and `policy_rules`
|
|
398
|
+
signals, but the analyzer result contract does not invent custom policy
|
|
399
|
+
matches. Organization rules in this release remain deterministic; applications
|
|
400
|
+
that require semantic topic policy should make that separate decision explicit
|
|
401
|
+
rather than relabeling it as injection, PII, or a secret.
|
|
402
|
+
|
|
403
|
+
### OpenAI structured-output connector
|
|
404
|
+
|
|
405
|
+
Add the official SDK only in applications that use this connector:
|
|
406
|
+
|
|
407
|
+
```ruby
|
|
408
|
+
gem "openai", "~> 0.62"
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
```ruby
|
|
412
|
+
require "openai"
|
|
413
|
+
require "olyx/guardrails"
|
|
414
|
+
require "olyx/guardrails/integrations/openai_analyzer"
|
|
415
|
+
|
|
416
|
+
openai = OpenAI::Client.new(api_key: ENV.fetch("OPENAI_API_KEY"))
|
|
417
|
+
|
|
418
|
+
analyzer = Olyx::Guardrails::Integrations::OpenAIAnalyzer.new(
|
|
419
|
+
client: openai,
|
|
420
|
+
model: ENV.fetch("OPENAI_MODEL"),
|
|
421
|
+
request_options: { timeout: 15, max_retries: 1 }
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
result = Olyx::Guardrails.check(input, ai_analyzer: analyzer)
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
The connector sends the built-in
|
|
428
|
+
`OpenAIAnalyzer::AnalysisSchema < OpenAI::BaseModel` through the Responses API
|
|
429
|
+
and consumes the SDK's parsed schema-model object. Requests default to
|
|
430
|
+
`store: false`; refusals, API failures, and missing parsed output are bounded
|
|
431
|
+
under `result[:ai_analysis][:error]` without clearing local findings.
|
|
432
|
+
|
|
433
|
+
A custom OpenAI schema model can be supplied with `schema:`. It should expose
|
|
434
|
+
the analyzer fields shown above; unknown fields are discarded by
|
|
435
|
+
`Guardrails.check`.
|
|
436
|
+
|
|
437
|
+
#### Model compatibility and minimum
|
|
438
|
+
|
|
439
|
+
The connector has no model allowlist or baked-in default. It forwards String or
|
|
440
|
+
Symbol model identifiers unchanged, so aliases, dated snapshots, and fine-tuned
|
|
441
|
+
model IDs work when the selected model supports both:
|
|
442
|
+
|
|
443
|
+
1. text output through `v1/responses`; and
|
|
444
|
+
2. Structured Outputs.
|
|
445
|
+
|
|
446
|
+
That capability check—not a particular model name—is the technical minimum.
|
|
447
|
+
The API returns an error under `result[:ai_analysis][:error]` when the selected
|
|
448
|
+
model cannot satisfy it. Verify both features on the
|
|
449
|
+
[OpenAI model catalog](https://developers.openai.com/api/docs/models) because
|
|
450
|
+
model availability and capabilities change independently of this gem.
|
|
451
|
+
|
|
452
|
+
Do not use models whose catalog entry says **Structured outputs: Not
|
|
453
|
+
supported**. This excludes GPT-3.5 Turbo, GPT-4, GPT-4 Turbo, Realtime models,
|
|
454
|
+
and specialized image, video, audio, transcription, speech, embedding, and
|
|
455
|
+
moderation models. JSON mode is not used as a fallback because valid JSON alone
|
|
456
|
+
does not guarantee this connector's schema.
|
|
457
|
+
|
|
458
|
+
For the broadest model portability, leave `response_options: {}`. Parameters
|
|
459
|
+
such as `temperature`, reasoning effort, verbosity, and tool configuration are
|
|
460
|
+
model-specific and can make an otherwise compatible model reject the request.
|
|
461
|
+
Custom and fine-tuned models remain compatible only when their effective model
|
|
462
|
+
supports Structured Outputs; custom schemas must also stay within that model's
|
|
463
|
+
supported JSON Schema subset.
|
|
464
|
+
|
|
465
|
+
Mini and nano text models that support Structured Outputs are technically
|
|
466
|
+
compatible; model size is not rejected in code. However, schema conformance
|
|
467
|
+
only guarantees the output shape, not correct security classification.
|
|
468
|
+
Therefore, do not establish a production minimum from cost, latency, or a
|
|
469
|
+
successful schema response alone. Require representative adversarial
|
|
470
|
+
evaluations for false negatives, false positives, refusals, latency, and cost.
|
|
471
|
+
Keep the local deterministic findings authoritative. OpenAI's
|
|
472
|
+
[Structured Outputs guide](https://developers.openai.com/api/docs/guides/structured-outputs)
|
|
473
|
+
also notes that structured responses can still contain mistakes.
|
|
474
|
+
|
|
475
|
+
The gem and its optional OpenAI connector require Ruby 3.4 or newer.
|
|
476
|
+
|
|
477
|
+
## Component APIs
|
|
478
|
+
|
|
479
|
+
### PII
|
|
480
|
+
|
|
481
|
+
```ruby
|
|
482
|
+
PiiScrubber.scrub(text)
|
|
483
|
+
PiiScrubber.scrub_messages(messages)
|
|
484
|
+
PiiScrubber.scrub_messages_with_detection(messages)
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
String message content and array-style text content blocks are supported
|
|
488
|
+
without changing symbol/string key style.
|
|
489
|
+
|
|
490
|
+
### Injection detection
|
|
491
|
+
|
|
492
|
+
```ruby
|
|
493
|
+
InjectionDetector.scan(messages)
|
|
494
|
+
InjectionDetector.injection?(text)
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
Multi-turn patterns require an adjacent `user` → `assistant` role transition.
|
|
498
|
+
|
|
499
|
+
### Secret handling
|
|
500
|
+
|
|
501
|
+
The operation is explicit:
|
|
502
|
+
|
|
503
|
+
```ruby
|
|
504
|
+
SecretScanner.scan(text, custom_patterns: []) # detect only
|
|
505
|
+
SecretScanner.redact(text, custom_patterns: []) # transform
|
|
506
|
+
SecretScanner.scan!(text, custom_patterns: []) # raise Blocked on detection
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
Invalid custom regexes raise `ArgumentError`. Custom regexes are trusted
|
|
510
|
+
configuration: review them for pathological backtracking before deployment.
|
|
511
|
+
This low-level option extends credential detection and reports matches as
|
|
512
|
+
secrets. Use `PolicyRule` for business restrictions so findings retain a name,
|
|
513
|
+
description, block decision, and distinct `policy` classification.
|
|
514
|
+
|
|
515
|
+
## Risk score
|
|
516
|
+
|
|
517
|
+
The heuristic score is injection (`0.50`) + secret (`0.25`) + restricted-policy
|
|
518
|
+
match (`0.25`) + PII (`0.10`) + any blocked check (`0.15`), clamped to
|
|
519
|
+
`0.0..1.0`. It supports graduated responses; it is not a probability or
|
|
520
|
+
calibrated model confidence.
|
|
521
|
+
|
|
522
|
+
## Notifications
|
|
523
|
+
|
|
524
|
+
```ruby
|
|
525
|
+
notifier = Olyx::Guardrails::Notifier.new(
|
|
526
|
+
policy: POLICY,
|
|
527
|
+
handlers: {
|
|
528
|
+
audit_log: ->(event) { Rails.logger.warn(event.to_json) },
|
|
529
|
+
incident_queue: ->(event) { IncidentNotificationJob.perform_later(event) }
|
|
530
|
+
}
|
|
531
|
+
)
|
|
532
|
+
|
|
533
|
+
notifier.notify(
|
|
534
|
+
result,
|
|
535
|
+
input: input,
|
|
536
|
+
metadata: { user_id: current_user.id, endpoint: request.path }
|
|
537
|
+
)
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
Pass the same policy used by `Guardrails.check`. Each named handler receives the
|
|
541
|
+
same frozen, vendor-neutral event. A handler can write to a logger, enqueue a
|
|
542
|
+
job, send email, publish to a message bus, or invoke any incident/webhook SDK.
|
|
543
|
+
Handlers run synchronously and failures are isolated; enqueue work when delivery
|
|
544
|
+
must be asynchronous.
|
|
545
|
+
|
|
546
|
+
Input previews, AI reasons, metadata keys, metadata values, and handler errors
|
|
547
|
+
are bounded and redacted with the policy's restricted-content rules, custom
|
|
548
|
+
credential patterns, and the built-in PII and secret detectors.
|
|
549
|
+
|
|
550
|
+
## Limitations
|
|
551
|
+
|
|
552
|
+
- Pattern-based injection detection normalizes NFKC text, common Greek/Cyrillic
|
|
553
|
+
homoglyphs, zero-width characters, and one bounded layer of URL, HTML-entity,
|
|
554
|
+
Unicode-escape, or Base64 encoding. It can still miss paraphrasing,
|
|
555
|
+
translation, nested/stacked encoding beyond one layer, unsupported
|
|
556
|
+
homoglyphs, and new attack techniques.
|
|
557
|
+
- Secret coverage includes common cloud/SaaS tokens, JWTs, PEM private keys,
|
|
558
|
+
credential-bearing database URLs, and private endpoints, but remains
|
|
559
|
+
format-based and cannot reliably classify arbitrary high-entropy strings or
|
|
560
|
+
every provider-specific credential. Coverage is intentionally not
|
|
561
|
+
exhaustive — additional vendor token formats are a good first
|
|
562
|
+
contribution; see [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
563
|
+
- PII patterns remain biased toward common US/Western formats (plus
|
|
564
|
+
Luhn-valid Canadian SIN). Broader international coverage is a good fit for
|
|
565
|
+
a community PR rather than something this library tries to front-load.
|
|
566
|
+
- Restricted-content policies are pattern-based. They do not infer semantic
|
|
567
|
+
equivalents, translations, or obfuscated terms unless explicitly configured.
|
|
568
|
+
- Completed-output APIs do not inspect streaming tokens before they reach the
|
|
569
|
+
caller. Upload checks require caller-supplied text extraction.
|
|
570
|
+
- In-process decisions, notifications, and metrics are local to one Ruby
|
|
571
|
+
process. The gem does not provide cross-service policy distribution, global
|
|
572
|
+
quotas, centralized audit retention, managed retries, or fleet dashboards.
|
|
573
|
+
- Redaction addresses recognized data patterns; it is not a replacement for
|
|
574
|
+
authorization or a complete data-loss-prevention system.
|
|
575
|
+
|
|
576
|
+
## Gem and Olyx Cloud boundary
|
|
577
|
+
|
|
578
|
+
The gem is intended to be useful on its own: deterministic checks, custom
|
|
579
|
+
policies, completed input/output enforcement, Rails boundary adapters, safe
|
|
580
|
+
local telemetry, and caller-owned notification handlers all run in the
|
|
581
|
+
application process.
|
|
582
|
+
|
|
583
|
+
Olyx Cloud is the operational control plane for teams that outgrow local
|
|
584
|
+
enforcement: a full LLM proxy route, centralized and versioned policy rollout,
|
|
585
|
+
cross-service audit and retention, distributed quotas, managed provider routing
|
|
586
|
+
and retries, streaming enforcement, fleet analytics, and managed asynchronous
|
|
587
|
+
delivery. Those distributed capabilities are intentionally not simulated by
|
|
588
|
+
the gem, so adopting the open-source library does not create a later migration
|
|
589
|
+
away from its public APIs.
|
|
590
|
+
|
|
591
|
+
## Development and security
|
|
592
|
+
|
|
593
|
+
```bash
|
|
594
|
+
bundle exec rake test
|
|
595
|
+
COVERAGE=true bundle exec rake test
|
|
596
|
+
bundle exec ruby script/flog_gate.rb
|
|
597
|
+
gem build olyx-guardrails.gemspec
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for development expectations and
|
|
601
|
+
[SECURITY.md](SECURITY.md) for private vulnerability reporting. Everyone
|
|
602
|
+
interacting in this project's codebase, issue tracker, and other spaces is
|
|
603
|
+
expected to follow the [Code of Conduct](CODE_OF_CONDUCT.md).
|
|
604
|
+
|
|
605
|
+
## Changelog
|
|
606
|
+
|
|
607
|
+
See [CHANGELOG.md](CHANGELOG.md) for release history.
|
|
608
|
+
|
|
609
|
+
## License
|
|
610
|
+
|
|
611
|
+
Apache-2.0. See [LICENSE](LICENSE).
|
data/SECURITY.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Supported versions
|
|
4
|
+
|
|
5
|
+
Security fixes are provided for the latest released minor version. Users should
|
|
6
|
+
upgrade to the latest patch release before reporting an issue.
|
|
7
|
+
|
|
8
|
+
| Version | Supported |
|
|
9
|
+
|---|---|
|
|
10
|
+
| 1.0.x | Yes |
|
|
11
|
+
| < 1.0 | No |
|
|
12
|
+
|
|
13
|
+
## Reporting a vulnerability
|
|
14
|
+
|
|
15
|
+
Do not open a public issue for a suspected vulnerability.
|
|
16
|
+
|
|
17
|
+
Email `security@olyxai.io` with:
|
|
18
|
+
|
|
19
|
+
- the affected version and component;
|
|
20
|
+
- reproduction steps or a minimal proof of concept;
|
|
21
|
+
- expected and observed behavior;
|
|
22
|
+
- the potential confidentiality, integrity, or availability impact; and
|
|
23
|
+
- any suggested remediation or disclosure constraints.
|
|
24
|
+
|
|
25
|
+
You should receive an acknowledgement within three business days and an initial
|
|
26
|
+
assessment within seven business days. We will coordinate remediation and
|
|
27
|
+
disclosure with the reporter. Please avoid accessing data that is not yours,
|
|
28
|
+
degrading services, or publicly disclosing the issue before a fix is available.
|
|
29
|
+
|
|
30
|
+
## Scope notes
|
|
31
|
+
|
|
32
|
+
Pattern-based guardrails are defense-in-depth controls, not complete semantic
|
|
33
|
+
security boundaries. Documented detection limitations alone are not
|
|
34
|
+
vulnerabilities, but bypasses of a documented invariant—especially redaction,
|
|
35
|
+
blocking, or third-party data-egress guarantees—are in scope.
|