From 4b33655117a731b1ab7aae7ca47479d8bc5cd362 Mon Sep 17 00:00:00 2001 From: tannevaled Date: Sun, 4 Oct 2026 21:28:30 +0200 Subject: [PATCH] docs: a security policy, because there was none and nine findings are now public There is no SECURITY.md in this repository and none in the organisation's .github repository, so a researcher who finds something has nowhere to send it except the public issue tracker. That mattered less before today; the security audit in #768 has just filed nine findings in the open, which is an invitation for the next person to do the same with something worse. The policy is deliberately modest about response: this is a small project with no security team, and promising a timeline nobody is on call for would be worth less than saying so. Two parts carry the actual content. What is IN scope is written from what the audit actually found, not from a template: the host process dying or hanging without bound on Ruby input (because `Run` returns an error, and an embedder cannot defend against a `fatal error:` from outside); failing to raise where MRI raises when a program relies on the raise to stay safe -- the fail-open class, which is invisible to the caller; reaching past the documented capability set; and the CI/release path. What is NOT a vulnerability is the part that keeps the inbox usable. `Run` is not a sandbox, and that is documented rather than accidental, so "system() executes a command" is working as intended. By the same reasoning, faithfully reproducing a behaviour that is dangerous in MRI -- Marshal.load on hostile bytes -- is conformance here, though it is still worth hearing if the documentation fails to warn about it. It also lists the six already-filed classes, so a duplicate costs the reporter nothing to avoid. The channel is GitHub's private vulnerability reporting, which is currently DISABLED on this repository and has to be turned on for the policy to be truthful -- see the pull request. It was chosen over an email address because it does not publish anyone's personal address on a public repository. The link to the README's "Run is not a sandbox" section resolves once #770 merges, which is what adds that heading. Co-Authored-By: Claude Opus 5 --- SECURITY.md | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..e4781d53 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,76 @@ +# Security policy + +## Reporting + +Please report anything you believe is a security issue through GitHub's private +vulnerability reporting, from the **Security** tab of this repository +("Report a vulnerability"). That keeps the report out of the public issue +tracker until there is something to say about it. + +This is a small project without a security team, so please do not expect a +same-day answer. What you can expect is that a report will be read, that the +reply will say plainly whether it is in scope, and that if a fix lands the +report will be credited unless you ask otherwise. + +If private reporting is unavailable to you for any reason, open a normal issue +describing the *shape* of the problem without a working trigger, and say that you +have more to share. + +## What is in scope + +`rbgo` aims at MRI-Ruby conformance and is also a Go library (`ruby.Run`) that +other programs embed. Both of those shape what counts. + +In scope: + +- **The host process dying, or hanging without bound, because of Ruby input.** + A Go `panic` that reaches an embedder, a `fatal error:` that `recover()` cannot + contain, an unbounded allocation, or a call that never returns. `Run` returns + an `error`; anything that takes the process down instead is a defect, because + an embedder has no way to defend against it from the outside. +- **Failing to raise where MRI raises**, when the difference is what a program + relies on to stay safe. A check that reads as "passed" because a limit was hit, + an allow-list that permits more than it says, a `rescue` that cannot fire + because the exception class is never raised. These fail *open*, which is worse + than failing loudly, and they are not visible to the caller. +- **Reaching further than the documented capability set**, including any path + from Ruby to the embedding Go program's own state, or writing outside the + `io.Writer` the embedder passed. +- Secrets or credentials committed to this repository, and anything in the + release or CI path that would let someone else's code run with this + repository's permissions. + +## What is not a vulnerability + +**`Run` is not a sandbox, and that is documented, not accidental.** See +[*`Run` is not a sandbox*](README.md#run-is-not-a-sandbox) in the README, which +carries the measurements. Ruby source passed to `Run` can spawn `/bin/sh`, read +and write the filesystem as the host's uid, open sockets, `require` any absolute +path, and change the host's `ENV` and working directory — exactly as MRI can, +because conformance is the goal. Do not pass untrusted Ruby to it. + +So a report that "`system("...")` executes a command" is working as intended. A +report that a *documented restriction* does not hold is not. + +By the same reasoning, faithfully reproducing an MRI behaviour that is dangerous +in MRI — `Marshal.load` on attacker-controlled bytes, for instance — is +conformance rather than a defect here. It is still worth telling us if the +documentation does not warn about it. + +## Already known + +Please check the open issues before reporting; several classes are already filed +with measurements, and a duplicate costs you more than it costs us: + +- no call-depth limit, so runaway recursion is a fatal Go stack overflow (#768, + and go-ruby-parser/parser#48 for the same thing in the parser) +- `Net::HTTP` to a server in the program's own `Thread` hangs without bound (#771) +- `Run` returns `nil` for `exit`/`exit!`/`abort` (#773) +- `YAML.safe_load` instantiates arbitrary classes (#775) +- `Regexp.timeout=` is never applied, and a fired limit reads as "no match" (#776) +- `Array#*`/`String#*` reach `make()` with a rejected size (#777) + +## Supported versions + +There is no released version yet and no backport branch: fixes land on `main`. +If you are embedding this, pin a commit and expect to move it.