Module: Kobako::Transport::Reflection

Defined in:
lib/kobako/transport/reflection.rb,
sig/kobako/transport/reflection.rbs

Overview

The reflection floor a guest→host dispatch must clear before the Dispatcher reaches public_send: which method names on a resolved target count as Service behaviour, and which are Ruby's ambient metaprogramming surface.

Answers with a refusal reason rather than raising, so the error taxonomy stays with the Dispatcher and this module holds only the policy.

Constant Summary collapse

META_OWNERS =

Modules whose instance methods are ambient Ruby reflection / metaprogramming surface (+send+, public_send, instance_eval, method, tap, instance_variable_get, ...) rather than Service behaviour. A guest-supplied method name resolving to one of these is rejected: only methods the bound object itself exposes as Service behaviour are reachable, and public_send(:send, ...) would otherwise let a guest pivot through send into the private Kernel#eval / #system surface (host RCE).

Returns:

  • (Array[Module])
[BasicObject, Kernel, Object, Module, Class].freeze
GADGET_OWNERS =

Callable gadget types whose own public methods are reflection surface (+Proc#binding+ reaches Binding#eval, Method#receiver / #unbind hand back the underlying object) rather than Service behaviour. Only CALLABLE_ALLOW is reachable on a target of these types; a bound lambda stays invocable, its reflective surface does not.

Returns:

  • (Array[Module])
[Proc, Method, UnboundMethod, Binding].freeze
CALLABLE_ALLOW =

The sole methods reachable on a GADGET_OWNERS target: invoking it (+call+ / [] / yield) and the harmless arity / lambda? describers that aid guest-side debugging.

Returns:

  • (Array[Symbol])
%i[call [] yield arity lambda?].freeze

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.ambient_refusal(target, name) ⇒ Object

Guard against ambient reflection methods. A public method whose owner is a META_OWNERS or GADGET_OWNERS module is rejected, except CALLABLE_ALLOW on a gadget target (a bound lambda stays invocable). A name with no concrete public method is allowed only when the target opts into it via respond_to? (dynamic method_missing Services), since the dangerous methods are all concretely defined and therefore never reach that branch.



56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/kobako/transport/reflection.rb', line 56

def ambient_refusal(target, name)
  owner = target.public_method(name).owner
  gadget = GADGET_OWNERS.include?(owner)
  return nil unless META_OWNERS.include?(owner) || gadget
  return nil if gadget && CALLABLE_ALLOW.include?(name)

  "method #{name.inspect} is not a Service method"
rescue NameError
  return nil if target.respond_to?(name)

  "no public method #{name.inspect} on target"
end

.narrowing_refusal(target, name) ⇒ Object

Consult the target's opt-in narrowing predicate. A bound object may define a private respond_to_guest?(name) to restrict which of its methods the guest reaches; a falsy answer refuses the dispatch. It is consulted with the private surface included so the guest's public_send dispatch can never reach respond_to_guest? itself.



74
75
76
77
78
79
# File 'lib/kobako/transport/reflection.rb', line 74

def narrowing_refusal(target, name)
  return nil unless target.respond_to?(:respond_to_guest?, true)
  return nil if target.__send__(:respond_to_guest?, name)

  "method #{name.inspect} is not exposed to the guest"
end

.refusal(target, name) ⇒ Object

The reason name is unreachable on target, or nil when the dispatch may proceed. Composes the ambient-surface floor with the target's own opt-in narrowing, in that order: the predicate only narrows and can never re-open what the floor rejects.



45
46
47
# File 'lib/kobako/transport/reflection.rb', line 45

def refusal(target, name)
  ambient_refusal(target, name) || narrowing_refusal(target, name)
end

Instance Method Details

#self?.ambient_refusalString?

Parameters:

  • target (Object)
  • name (Symbol)

Returns:

  • (String, nil)


10
# File 'sig/kobako/transport/reflection.rbs', line 10

def self?.ambient_refusal: (untyped target, Symbol name) -> String?

#self?.narrowing_refusalString?

Parameters:

  • target (Object)
  • name (Symbol)

Returns:

  • (String, nil)


12
# File 'sig/kobako/transport/reflection.rbs', line 12

def self?.narrowing_refusal: (untyped target, Symbol name) -> String?

#self?.refusalString?

Parameters:

  • target (Object)
  • name (Symbol)

Returns:

  • (String, nil)


8
# File 'sig/kobako/transport/reflection.rbs', line 8

def self?.refusal: (untyped target, Symbol name) -> String?