Files
zzet--gortex/internal/astquery/rules_sast_ruby.go
T
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

157 lines
5.8 KiB
Go

package astquery
// Ruby SAST starter pack. The existing sql-string-concat /
// hardcoded-secret detectors already cover Ruby; this file adds
// language-specific patterns.
func init() {
registerRubyEval()
registerRubyYAMLMarshal()
registerRubyOpen3()
registerRubyConstantize()
registerRubyMassAssignment()
registerRubyRailsSendFile()
registerRubyRailsHTMLSafe()
registerRubyDigestWeakHash()
registerRubyOpenURIRedirect()
registerRubyExecBacktick()
}
func registerRubyEval() {
mustRegisterSAST(sastRule{
Name: "ruby-eval-use",
Description: "`eval(str)` / `instance_eval(str)` / `class_eval(str)` / `module_eval(str)` / `binding.eval(str)` — runs arbitrary Ruby. Any caller-controlled input is RCE.",
Severity: "error",
CWE: "CWE-95",
OWASP: "A03:2021-Injection",
Tags: []string{"injection", "code-injection"},
Pat: map[string]string{
"ruby": `((call method: (identifier) @fn) @match (#match? @fn "^(eval|instance_eval|class_eval|module_eval)$"))`,
},
})
}
func registerRubyYAMLMarshal() {
mustRegisterSAST(sastRule{
Name: "ruby-yaml-load",
Description: "`YAML.load(str)` / `Marshal.load(str)` — Ruby YAML / Marshal deserialise arbitrary classes, equivalent to Python `pickle.load`. RCE on untrusted input. Use `YAML.safe_load` / `JSON.parse`.",
Severity: "error",
CWE: "CWE-502",
OWASP: "A08:2021-Software and Data Integrity Failures",
Tags: []string{"deserialization"},
Pat: map[string]string{
"ruby": `((call receiver: (constant) @cls method: (identifier) @fn) @match
(#match? @cls "^(YAML|Marshal)$") (#match? @fn "^(load|load_file|restore)$"))`,
},
})
}
func registerRubyOpen3() {
mustRegisterSAST(sastRule{
Name: "ruby-open3-with-string",
Description: "`Open3.capture3(cmd_string)` / `Open3.popen3(cmd_string)` — a single-string argument is shell-parsed. Pass an argv array (`Open3.capture3('ls', dir)`) to avoid injection.",
Severity: "warning",
CWE: "CWE-78",
Tags: []string{"command-injection"},
Pat: map[string]string{
"ruby": `((call receiver: (constant) @cls method: (identifier) @fn) @match
(#eq? @cls "Open3") (#match? @fn "^(capture3|capture2|popen3|popen2)$"))`,
},
})
}
func registerRubyExecBacktick() {
mustRegisterSAST(sastRule{
Name: "ruby-shell-backtick",
Description: "`` `cmd #{var}` `` / `%x[cmd]` — shell-evaluated backticks. Any interpolation is command injection. Use `Open3.capture3` with an argv array.",
Severity: "error",
CWE: "CWE-78",
Tags: []string{"command-injection"},
Pat: map[string]string{
"ruby": `(subshell) @match`,
},
})
}
func registerRubyConstantize() {
mustRegisterSAST(sastRule{
Name: "ruby-constantize",
Description: "`x.constantize` / `x.safe_constantize` with user input — turns an arbitrary string into a class reference. Combined with `.new` this can construct any object in the autoloader.",
Severity: "warning",
CWE: "CWE-470",
Tags: []string{"reflection"},
Pat: map[string]string{
"ruby": `((call method: (identifier) @fn) @match (#match? @fn "^(constantize|safe_constantize)$"))`,
},
})
}
func registerRubyMassAssignment() {
mustRegisterSAST(sastRule{
Name: "ruby-mass-assignment-params-permit-all",
Description: "`params.permit!` — strong-parameters mass-assignment guard disabled. Equivalent to `attr_accessible :all`. Restore an explicit permit list.",
Severity: "warning",
CWE: "CWE-915",
Tags: []string{"mass-assignment", "rails"},
Pat: map[string]string{
"ruby": `((call receiver: (identifier) @recv method: (identifier) @fn) @match
(#eq? @recv "params") (#eq? @fn "permit!"))`,
},
})
}
func registerRubyRailsSendFile() {
mustRegisterSAST(sastRule{
Name: "ruby-rails-send-file-user-input",
Description: "`send_file(params[:path])` — path comes from request params. Any caller can read files outside the intended directory unless validated.",
Severity: "warning",
CWE: "CWE-22",
Tags: []string{"path-traversal", "rails"},
Pat: map[string]string{
"ruby": `((call method: (identifier) @fn
arguments: (argument_list (element_reference object: (identifier) @recv))) @match
(#eq? @fn "send_file") (#eq? @recv "params"))`,
},
})
}
func registerRubyRailsHTMLSafe() {
mustRegisterSAST(sastRule{
Name: "ruby-html-safe",
Description: "`.html_safe` / `raw(...)` — marks a string as already-HTML-escaped. With user input, this is XSS in any Rails view.",
Severity: "warning",
CWE: "CWE-79",
Tags: []string{"xss", "rails"},
Pat: map[string]string{
"ruby": `((call method: (identifier) @fn) @match (#match? @fn "^(html_safe|raw)$"))`,
},
})
}
func registerRubyDigestWeakHash() {
mustRegisterSAST(sastRule{
Name: "ruby-digest-md5-sha1",
Description: "`Digest::MD5` / `Digest::SHA1` — broken hashes for any security-sensitive use.",
Severity: "error",
CWE: "CWE-327",
Tags: []string{"crypto", "weak-hash"},
Pat: map[string]string{
"ruby": `((scope_resolution scope: (constant) @lib name: (constant) @alg) @match
(#eq? @lib "Digest") (#match? @alg "^(MD5|MD4|SHA1)$"))`,
},
})
}
func registerRubyOpenURIRedirect() {
mustRegisterSAST(sastRule{
Name: "ruby-open-uri-with-userdata",
Description: "`open(user_url)` (with `require 'open-uri'`) — `open` accepts file paths AND `http://` / `https://` URLs. User input invites both file disclosure and SSRF. Use `URI.parse(...).open` with an explicit scheme allow-list.",
Severity: "info",
CWE: "CWE-918",
Tags: []string{"ssrf", "audit-hook"},
Pat: map[string]string{
"ruby": `((call method: (identifier) @fn) @match (#eq? @fn "open"))`,
},
})
}