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_owner?(owner, target) ⇒ Boolean

Whether +name+'s owner is ambient surface rather than Service behaviour: a core meta module, a callable gadget type, or — when target is itself a Class or Module bound as a Service — the singleton class that owns its class-level API (+File.popen+ / Kernel.system, unreachable via any fixed core-module list). A plain object's own singleton method (+def obj.x+) stays reachable, since target is not a Module there.

Returns:

  • (Boolean)


77
78
79
80
81
# File 'lib/kobako/transport/reflection.rb', line 77

def ambient_owner?(owner, target)
  META_OWNERS.include?(owner) ||
    GADGET_OWNERS.include?(owner) ||
    (target.is_a?(Module) && owner.singleton_class?)
end

.ambient_refusal(target, name) ⇒ Object

Guard against ambient reflection methods. A public method whose owner is a META_OWNERS or GADGET_OWNERS module — or a singleton class, the owner of every class-level method (+File.popen+ / Kernel.system, unreachable via any fixed core-module list) — 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.



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

def ambient_refusal(target, name)
  owner = target.public_method(name).owner
  return nil unless ambient_owner?(owner, target)
  return nil if GADGET_OWNERS.include?(owner) && 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.



88
89
90
91
92
93
# File 'lib/kobako/transport/reflection.rb', line 88

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_owner?Boolean

Parameters:

  • owner (Module)
  • target (Object)

Returns:

  • (Boolean)


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

def self?.ambient_owner?: (Module owner, untyped target) -> bool

#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)


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

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?