What
Calling .send(:method_name, args) or .public_send(:method_name, args) on the object returned by Fbe.octo bypasses the pre-call quota guard entirely. The call reaches the underlying Octokit client directly without going through the off_quota? check.
Why it happens
Fbe.octo wraps the Octokit client with intercepted (from the intercepted gem), which builds a BasicObject subclass and routes everything through method_missing. BasicObject does not define send or public_send, so:
wrapper.send(:search_issues, 'q') triggers method_missing(:send, :search_issues, 'q').
- The intercepted block sees
m = :send, which is NOT in Fbe::SEARCH_METHODS and is not off_quota?.
- Falls through to
@o.__send__(:send, :search_issues, 'q') on the inner decoor object, which dispatches search_issues directly without the quota check.
Net effect: any caller using .send / .public_send on Fbe.octo (intentionally or via a meta-programming helper) skips the entire quota protection introduced by #447 and the original core check.
How I noticed
Discovered while writing tests for #447. The test test_search_methods_are_routed_through_search_quota_check initially used o.public_send(m, 'q') to iterate over Fbe::SEARCH_METHODS and assert each was blocked. The test failed for every method because public_send itself was being intercepted instead. Switched to o.__send__(m, 'q') to make the test work.
Reproduction
o = Fbe.octo(loog: Loog::NULL, global: {}, options: Judges::Options.new)
# search quota is exhausted (remaining: 1, threshold: 5)
o.search_issues('q') # raises Fbe::OffQuota (correct)
o.send(:search_issues, 'q') # makes the HTTP call (bug)
o.public_send(:search_issues, 'q') # also makes the HTTP call (bug)
o.__send__(:search_issues, 'q') # raises Fbe::OffQuota (correct)
Scope
Affects both core and search quota paths. Predates #447 / PR #448; the search work just made it visible.
Possible fixes
- Define
send and public_send on the intercepted wrapper that re-dispatch through the wrapper's own method_missing. Simplest, but requires upstream change to the intercepted gem (or a local override).
- Document that
Fbe.octo callers must use direct method invocation, never .send / .public_send. Cheapest, but easy to forget.
- Replace
intercepted with a different wrapping strategy that doesn't inherit from BasicObject.
Discovered while working on #447 / PR #448.
What
Calling
.send(:method_name, args)or.public_send(:method_name, args)on the object returned byFbe.octobypasses the pre-call quota guard entirely. The call reaches the underlying Octokit client directly without going through theoff_quota?check.Why it happens
Fbe.octowraps the Octokit client withintercepted(from theinterceptedgem), which builds aBasicObjectsubclass and routes everything throughmethod_missing.BasicObjectdoes not definesendorpublic_send, so:wrapper.send(:search_issues, 'q')triggersmethod_missing(:send, :search_issues, 'q').m = :send, which is NOT inFbe::SEARCH_METHODSand is notoff_quota?.@o.__send__(:send, :search_issues, 'q')on the inner decoor object, which dispatchessearch_issuesdirectly without the quota check.Net effect: any caller using
.send/.public_sendonFbe.octo(intentionally or via a meta-programming helper) skips the entire quota protection introduced by #447 and the original core check.How I noticed
Discovered while writing tests for #447. The test
test_search_methods_are_routed_through_search_quota_checkinitially usedo.public_send(m, 'q')to iterate overFbe::SEARCH_METHODSand assert each was blocked. The test failed for every method becausepublic_senditself was being intercepted instead. Switched too.__send__(m, 'q')to make the test work.Reproduction
Scope
Affects both core and search quota paths. Predates #447 / PR #448; the search work just made it visible.
Possible fixes
sendandpublic_sendon the intercepted wrapper that re-dispatch through the wrapper's ownmethod_missing. Simplest, but requires upstream change to theinterceptedgem (or a local override).Fbe.octocallers must use direct method invocation, never.send/.public_send. Cheapest, but easy to forget.interceptedwith a different wrapping strategy that doesn't inherit fromBasicObject.Discovered while working on #447 / PR #448.