Module: Inquirex::SafeSource
- Defined in:
- lib/inquirex/safe_source.rb,
lib/inquirex/safe_source/call_spec.rb,
lib/inquirex/safe_source/validator.rb,
lib/inquirex/safe_source/vocabulary.rb
Overview
Decides whether a string of flow DSL is safe to evaluate — without evaluating it.
load_dsl is an eval. Whenever the text comes from somewhere
other than your own repository — a database column a customer edits, an
upload, an LLM, a visual builder's "sync" button — evaluating it unguarded
is arbitrary code execution in the process that loads the flow. As of 0.9.2
load_dsl therefore runs SafeSource.validate! first, and only source that
matches the real DSL vocabulary (Vocabulary) with literal arguments gets
past it.
Use SafeSource.validate or SafeSource.safe? directly to audit stored definitions without
loading them — the answer is "would load_dsl accept this?", which is
exactly what you want before a deploy tightens the allowlist.
Defined Under Namespace
Modules: Vocabulary Classes: CallSpec, Validator
Constant Summary collapse
- DEFAULT_MAX_SOURCE_BYTES =
Default ceiling on DSL source size, in bytes.
Real flows are tiny: the largest definition anywhere in this project or its downstream app — a 47-step loan application with three levels of composed rules — is under 6 KB. 64 KiB is an order of magnitude above that, comfortably fits a several-hundred-step questionnaire, and still bounds the work handed to Prism (and to any formatter the host runs on the same text), neither of which should be fed megabytes of hostile input.
64 * 1024
- DEFAULT_MAX_DEPTH =
Default ceiling on AST nesting depth.
Measured against every flow in this gem's specs and examples and the downstream app's fixtures, the deepest legitimate construct needs 9 levels (
define→ block →ask→ block →transition→all→any→equals→ literal). 24 leaves better than 2.5x headroom while keeping the validator's own recursion clear of a stack overflow triggered byall(all(all(...)))nested a million deep. 24
Class Attribute Summary collapse
-
.max_depth ⇒ Integer
Ceiling on AST nesting depth, applied by SafeSource.validate and therefore by load_dsl.
-
.max_source_bytes ⇒ Integer
Ceiling on DSL source size in bytes, applied by SafeSource.validate and therefore by load_dsl.
Class Method Summary collapse
-
.safe?(source, max_bytes: max_source_bytes, max_depth: self.max_depth) ⇒ Boolean
True when the source is inside the allowlist.
-
.validate(source, max_bytes: max_source_bytes, max_depth: self.max_depth) ⇒ Array<String>
Every reason the source would be rejected.
-
.validate!(source, max_bytes: max_source_bytes, max_depth: self.max_depth) ⇒ String
Validates, and raises unless the source is inside the allowlist.
Class Attribute Details
.max_depth ⇒ Integer
Ceiling on AST nesting depth, applied by validate and therefore by Inquirex.load_dsl.
73 74 75 |
# File 'lib/inquirex/safe_source.rb', line 73 def max_depth @max_depth end |
.max_source_bytes ⇒ Integer
Ceiling on DSL source size in bytes, applied by validate and therefore by Inquirex.load_dsl.
67 68 69 |
# File 'lib/inquirex/safe_source.rb', line 67 def max_source_bytes @max_source_bytes end |
Class Method Details
.safe?(source, max_bytes: max_source_bytes, max_depth: self.max_depth) ⇒ Boolean
Returns true when the source is inside the allowlist.
92 93 94 |
# File 'lib/inquirex/safe_source.rb', line 92 def safe?(source, max_bytes: max_source_bytes, max_depth: self.max_depth) validate(source, max_bytes:, max_depth:).empty? end |
.validate(source, max_bytes: max_source_bytes, max_depth: self.max_depth) ⇒ Array<String>
Every reason the source would be rejected. An empty array means the
source is inside the allowlist — it says nothing about whether the flow
is semantically valid (unknown step reference, missing start), which
only evaluation can decide.
84 85 86 |
# File 'lib/inquirex/safe_source.rb', line 84 def validate(source, max_bytes: max_source_bytes, max_depth: self.max_depth) Validator.new(source, max_bytes:, max_depth:).violations end |
.validate!(source, max_bytes: max_source_bytes, max_depth: self.max_depth) ⇒ String
Validates, and raises unless the source is inside the allowlist.
103 104 105 106 107 108 |
# File 'lib/inquirex/safe_source.rb', line 103 def validate!(source, max_bytes: max_source_bytes, max_depth: self.max_depth) violations = validate(source, max_bytes:, max_depth:) raise Errors::UnsafeSourceError, violations unless violations.empty? source end |