Zum Hauptinhalt springen
Echtzeit-Radar & Feeds
Alle RSS Feeds ➔
👥 Community & Social
Windows Tipps & SecurityGrafikkarte vor Überhitzung schützen: So geht’s(25.09.2026 um 08:00 Uhr)
••••••••••
Windows Tipps & SecurityGrafikkarte vor Überhitzung schützen: So geht’s(25.09.2026 um 08:00 Uhr)
••••••••••
Intelligence View
⚡ tsecurity.de Intelligence

Static Typing - Ruby's missing tools

For the last 20 years, Rubyists have adopted dozens of tools and technologies that allow us to write better software, scale projects, and ship what needs to be shipped to production the way we want it. I will name just a few of them:…

0
↗ Quelle (dev.to)
Reagiere als Erste:r — dein Feedback zählt!

For the last 20 years, Rubyists have adopted dozens of tools and technologies that allow us to write better software, scale projects, and ship what needs to be shipped to production the way we want it. I will name just a few of them: Docker, ruby-lsp, AI, RuboCop, MiniTest, RSpec, Cucumber.



The interesting fact, however, is that all these tools faced criticism when they were introduced. Some were heavily criticized, others faced a little skepticism. But the fact is, eventually, we adopted them and now it’s hard to imagine our programming life without them. We no longer argue about spaces or tabs; we just do gem install rubocop and then rubocop -a. We adopted these tools so that we could achieve even more. We delegated part of what we were doing to these artificial electronic helpers.



Think about it. The first version (and some subsequent ones as well) of Ruby on Rails was implemented by DHH in TextMate with just syntax highlighting. No code completion, no linters, no IDEs, no AIs. I remember those days. I was using Notepad++ on Windows for PHP and Ruby development.



As we see across the years, the process of adopting new tools and new ways to help us ship more, faster, and better is endless. If we cannot come up with something internally, like RuboCop, we look elsewhere and adopt things used in other ecosystems like Docker, or MiniTest (which is an adaptation of a Java library).



For the last several years, I have been actively working with JVM languages like Java and Kotlin. When I first started working with them, I was quite irritated by the amount of code and things I needed to do just to run my program. Over time, however, I noticed the significant confidence I gained every time the compiler successfully built my code. It doesn’t guarantee that the code works as I expect, but it already gives me a lot - a full understanding of the data flow and all the connections between various parts of my code. My software may still crash once started, but I’m still certain that a big deal of correctness is already ensured. With a few additional tests, I can make sure the business logic works just as it should.



What about Ruby? Without a single line of tests, you don’t know anything about your code. Do you pass the correct data? Does your business logic even make sense? With simple scripts, you might guess and imagine how the code works, but will you be right? What about a simple refactoring or a feature request where you have no idea what kind of input you’re dealing with? Are they simple primitives like Strings or Integers? Or maybe instances of some classes or, even worse, nested Arrays or Hashes?



The only ways to check this are either to run the code and see, or to write tests. So we go with tests:




  • Tests to ensure the code accepts the correct data.

  • Tests to ensure the business logic is correct.

  • Tests to ensure edge cases won’t cause issues.



Tests are great, don’t get me wrong. But looking at the picture I described above, it seems we rely too much on one single source of confidence. What if they fail? And this happens. What if the tests are wrong? Maybe we stubbed too much and now we’re testing stubs, not the real code. No one will save us then. It looks like it’s time to look around and see what else we can do to make things better.



Here is a simple Ruby script:




# frozen_string_literal: true

require 'json'
require 'time'
require 'httparty'

module RubyVersion
class Response
attr_reader :code
attr_reader :json

def initialize(code:, json:)
@code = code
@json = json
end

def success? = (200..299).include?(code)
end

API_URL = 'https://api.github.com/repos/ruby/ruby/releases/latest'

def self.fetch_response
http = HTTParty.get(API_URL, headers: { 'User-Agent' => 'static-typing-demo' })
Response.new(code: http.code.to_i, json: JSON.parse(http.body))
end

def self.fetch(printer)
resp = fetch_response
raise "HTTP #{resp.code}" unless resp.success?

data = resp.json
published_at = Time.parse((data['published_at'] || Time.now.utc.iso8601).to_s)
printer&.call((data['tag_name'] || data['name']).to_s, data['html_url'], published_at)
end
end

PRINTER = proc { |tag, url, published_at|
puts "Fetched tag=#{tag} published_at=#{published_at.utc.iso8601} (url=#{url})"
nil
}

puts RubyVersion.fetch(PRINTER) if $PROGRAM_NAME == __FILE__







Before we move on, let’s check what this code does. The script fetches information about the latest Ruby release via the .fetch_response method. The information is returned as an instance of the RubyVersion::Response class. The .fetch_response method is called by our main entry point .fetch, which accepts one mandatory parameter printer - a Proc defined in the constant PRINTER.



Just like we would do in real life, we’ll cover this code with the necessary tests. Here is a simple MiniTest file that covers all major aspects of our script.




# frozen_string_literal: true

require 'minitest/autorun'
require_relative '../app/fetch_ruby_latest_with_types'

class TestRubyVersionFetch < Minitest::Test
def test_fetch_response_structure
resp = RubyVersion.fetch_response
assert_kind_of RubyVersion::Response, resp
assert_kind_of Integer, resp.code
assert_kind_of Hash, resp.json
end

def test_fetch_with_printer_proc
received = {}
printer = proc do |tag, url, published_at|
received[:tag] = tag
received[:url] = url
received[:published_at] = published_at
nil
end
summary = RubyVersion.fetch(printer)
assert_nil summary
assert received[:tag]
assert received[:url]
assert_kind_of Time, received[:published_at]
end

def test_fetch_without_printer
summary = RubyVersion.fetch(nil)
assert_nil summary
end
end







Let’s check what we do here. With test_fetch_response_structure, we ensure that .fetch_response works correctly, doesn’t raise exceptions, and returns a non-empty response of the correct type. With test_fetch_with_printer_proc, we cover our main business logic of accepting the Proc, calling .fetch_response, and passing all the necessary data to it. Finally, with test_fetch_without_printer, we test what happens when we pass nil. Even though the printer parameter is defined as mandatory, without any guard statement, we can still pass any value — including nil.



Now let’s add a file with type definitions.




module RubyVersion
class Response
attr_reader code: Integer
attr_reader json: Hash[String, untyped]

def initialize: (code: Integer, json: Hash[String, untyped]) -> void
def success?: () -> bool
end

API_URL: ::String

def self.fetch_response: () -> Response
def self.fetch: (^(String, String, Time) -> nil) -> nil
end

PRINTER: ^(String, String, Time) -> nil







For simplicity, I didn’t provide detailed type definitions for the Hash we receive from GitHub’s endpoint. In real life, we might want to go deeper and cover that part with types as well.



The syntax of RBS is very similar to Ruby, so it’s not difficult to understand what it describes. You may notice that for our .fetch method, we specified what type of data it supports. This means we don’t just “allow” a printer parameter to be present (though nil is possible), but we describe exactly what kind of value we accept. The same applies to all other parameters and return values.



Let’s break the code a bit and pass nil to the .fetch method like this:

puts RubyVersion.fetch(nil) if $PROGRAM_NAME == __FILE__.



When we run steep check, it will immediately spot the issue:




> bundle exec steep check
# Type checking files:

.F

app/fetch_ruby_latest_with_types.rb:49:23: [error] Cannot pass a value of type `nil` as an argument of type `^(::String, ::String, ::Time) -> nil`
│ nil <: ^(::String, ::String, ::Time) -> nil
│
│ Diagnostic ID: Ruby::ArgumentTypeMismatch
│
└ puts RubyVersion.fetch(nil) if $PROGRAM_NAME == __FILE__
~~~

Detected 1 problem from 1 file






It tells us:

Cannot pass a value of type nil as an argument of type ^(::String, ::String, ::Time) -> nil

— which gives us enough information about the issue and how to fix it. Now, without even running our tests, we can ensure we’ll never pass invalid data.



That also means we no longer need this test:




  def test_fetch_without_printer
summary = RubyVersion.fetch(nil)
assert_nil summary
end






Steep (a static type checker) simply won’t let us pass anything but a Proc of the right shape. We can go even further and simplify other tests.




# frozen_string_literal: true

require 'minitest/autorun'
require_relative '../app/fetch_ruby_latest_with_types'

class TestRubyVersionMinimal < Minitest::Test
def test_fetch_response_structure
resp = RubyVersion.fetch_response
refute_nil resp
end

def test_fetch_with_printer_proc
received = {}
printer = proc do |tag, url, published_at|
received[:tag] = tag
received[:url] = url
received[:published_at] = published_at
nil
end
summary = RubyVersion.fetch(printer)
assert_nil summary
assert received[:tag]
assert received[:url]
end
end






Now, for the remaining cases, we only test the logic, not the types or the shape of the data we deal with.



In other words, we split responsibilities between MiniTest and Steep: the first validates that the business logic works as expected, the latter ensures we always deal with what was designed.



Static typing in Ruby cannot and will never replace tests. Similar to how it works in strongly typed languages, it’s a tool that brings clarity and confidence to your code.



And just like you’d spend your time in, say, Java, working with RBS or Sorbet (whichever you prefer) is not an “extra” time that none of us has. It’s the same time you’d otherwise spend debugging your app manually or writing an excessive amount of tests. As we’ve seen many times before - using static typing pays off.

1. Sofort-Triage & Abwehrmaßnahmen

SOC Incident Playbook: Remote Code Execution (RCE) Defense
1 Warnungen
title: Detect Exploitation - Static Typing - Ruby's missing tools
id: 5e0969df-f5b9-4330-946a-7145107e34c7
status: experimental
description: Automatisch generierte SIEM-Erkennungsregel basierend auf CTI Intelligence
references:
  - https://tsecurity.de/
author: iShareStuff CTI Automated Detection Engine
date: 2026-09-25
logsource:
  category: network_connection
  product: any
detection:
  selection:
      CommandLine|contains:
        - 'exploit'
  condition: selection
falsepositives:
  - Legitime administrative Zugriffe oder Penetrationstests
level: high
tags:
  - attack.initial_access
Syntax validiert (0 Fehler)
rule CTI_Threat_Indicator {
    meta:
        author = "iShareStuff CTI Automated Detection Engine"
        date = "2026-09-25"
        description = "YARA Signature for "
    strings:
        $str = "Static Typing - Ruby\'s missing" ascii wide
    condition:
        any of them
}
Syntax validiert (0 Fehler)
index=security sourcetype IN ("cisco:asa", "pan:traffic", "zeek_conn", "suricata", "WinEventLog:Security")
("Static Typing - Rubys missing tools")
| stats count earliest(_time) as first_seen latest(_time) as last_seen by src_ip, dest_ip, dest_host, signature
| eval first_seen=strftime(first_seen, "%Y-%m-%d %H:%M:%S"), last_seen=strftime(last_seen, "%Y-%m-%d %H:%M:%S")
| sort - count
Syntax validiert (0 Fehler)
message: "*Static Typing - Rubys missing tools*"
Syntax validiert (0 Fehler)
CommonSecurityLog
| where Message has "Static Typing - Rubys missing tools"
| summarize EventCount = count(), FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated) by SourceIP, DestinationIP, DestinationPort, Activity
| extend DetectionRule = "iShareStuff-CTI-Compiled"
| sort by EventCount desc

2. Cyber Threat Intelligence & Forensik

🎯
MITRE ATT&CK Matrix Navigator 14 Taktiken
Reconnaissance
-
Resource Development
-
Initial Access
Execution
Persistence
-
Privilege Escalation
Defense Evasion
Credential Access
-
Discovery
-
Lateral Movement
-
Collection
-
Command and Control
Exfiltration
-
Impact
tsecurity.de Cognitive Threat RAG
Fokus-Vektor:

Kognitive Analyse für identifizierte Bedrohung: Erhöhte Bedrohungslage im Bereich Static Typing - Ruby&#039;s missing tools.... Basierend auf 368k Vektor-Korrelationen werden sofortige Isolationsmaßnahmen für betroffene Endpunkte empfohlen.

🛡️ Angriffsfläche & Exposure

Netzwerk/Remote-Zugriff ohne Vorauthentifizierung möglich.

⚡ Empfohlene Sofortmaßnahmen
  • 1. Perimeter-Inspektion: Relevante Portfreigaben und exponierte Endpunkte unverzüglich scannen.
  • 2. Patch-Applikation: Hersteller-Hotfix einspielen oder betroffene Daemons in isolierte DMZ-Segmente überführen.
  • 3. Telemetrie & EDR-Alerts: Prozessaufrufe und Child-Processes auf anomale Shell-Spawns überwachen.
🔗 Semantisch verwandte Zero-Days MariaDB 11.7 VEC
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Static Typing - Ruby's missing tools

Thematisch verwandte Begriffe: Static, Typing, Rubys, missing · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-97735 | ITFlow before 26.08 allows SVG attachments in the ticket email parser (c…
Advisory →
tsecurity.de Icon
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag