[pitboss] phase 15: Track B — Go + PHP + Ruby harness emitter shapes

This commit is contained in:
pitboss 2026-05-14 17:45:42 -05:00
parent 919bc4e7e2
commit a9b61a9126
39 changed files with 2142 additions and 186 deletions

View file

@ -0,0 +1,18 @@
// Phase 15 — flag.Parse CLI, benign.
// Echoes a fixed string; argv is discarded.
package entry
import (
"flag"
"fmt"
"os/exec"
)
func Run() {
flag.Parse()
_ = flag.Args()
cmd := exec.Command("echo", "hello")
out, _ := cmd.CombinedOutput()
fmt.Print(string(out))
}

View file

@ -0,0 +1,3 @@
module nyx_flag_cli_fixture
go 1.21

View file

@ -0,0 +1,23 @@
// Phase 15 — flag.Parse CLI, vulnerable.
// Reads the first non-flag argv positional and pipes to /bin/sh -c.
// Entry: Run() Cap: CODE_EXEC
package entry
import (
"flag"
"fmt"
"os/exec"
)
func Run() {
fmt.Print("__NYX_SINK_HIT__\n")
flag.Parse()
payload := ""
if flag.NArg() > 0 {
payload = flag.Arg(0)
}
cmd := exec.Command("sh", "-c", "echo hello "+payload)
out, _ := cmd.CombinedOutput()
fmt.Print(string(out))
}

View file

@ -0,0 +1,19 @@
// Phase 15 — fuzz-style variadic harness, benign.
// Validates input length then echoes a fixed string.
package entry
import (
"fmt"
"os/exec"
)
func FuzzHandle(data []byte) error {
if len(data) > 1024 {
return fmt.Errorf("too long")
}
cmd := exec.Command("echo", "hello")
out, _ := cmd.CombinedOutput()
fmt.Print(string(out))
return nil
}

View file

@ -0,0 +1,3 @@
module nyx_fuzz_variadic_fixture
go 1.21

View file

@ -0,0 +1,18 @@
// Phase 15 — fuzz-style variadic harness, vulnerable.
// Takes raw bytes and pipes to /bin/sh -c.
// Entry: FuzzHandle(data []byte) error Cap: CODE_EXEC
package entry
import (
"fmt"
"os/exec"
)
func FuzzHandle(data []byte) error {
fmt.Print("__NYX_SINK_HIT__\n")
cmd := exec.Command("sh", "-c", "echo hello "+string(data))
out, _ := cmd.CombinedOutput()
fmt.Print(string(out))
return nil
}

View file

@ -0,0 +1,19 @@
// Phase 15 — gin handler, benign.
// Echoes a fixed string; query value is discarded.
package entry
import (
"fmt"
"os/exec"
"nyx-harness/entry/gin"
)
func Handle(c *gin.Context) {
_ = c.Query("payload")
cmd := exec.Command("echo", "hello")
out, _ := cmd.CombinedOutput()
fmt.Print(string(out))
c.String(200, "%s", string(out))
}

View file

@ -0,0 +1,3 @@
module nyx_gin_handler_fixture
go 1.21

View file

@ -0,0 +1,21 @@
// Phase 15 — gin handler, vulnerable.
// Reads gin context query value and pipes to /bin/sh -c.
// Entry: Handle(c *gin.Context) Cap: CODE_EXEC
package entry
import (
"fmt"
"os/exec"
"nyx-harness/entry/gin"
)
func Handle(c *gin.Context) {
fmt.Print("__NYX_SINK_HIT__\n")
payload := c.Query("payload")
cmd := exec.Command("sh", "-c", "echo hello "+payload)
out, _ := cmd.CombinedOutput()
fmt.Print(string(out))
c.String(200, "%s", string(out))
}

View file

@ -0,0 +1,19 @@
// Phase 15 — http.HandlerFunc, benign.
// Echoes a fixed string; query value is discarded.
package entry
import (
"fmt"
"net/http"
"os/exec"
)
func Handle(w http.ResponseWriter, r *http.Request) {
_ = r.URL.Query().Get("payload")
cmd := exec.Command("echo", "hello")
out, _ := cmd.CombinedOutput()
fmt.Print(string(out))
w.WriteHeader(http.StatusOK)
w.Write(out)
}

View file

@ -0,0 +1,3 @@
module nyx_handler_func_fixture
go 1.21

View file

@ -0,0 +1,21 @@
// Phase 15 — http.HandlerFunc, vulnerable.
// Reads `?payload=` query value and pipes to /bin/sh -c.
// Entry: Handle(w http.ResponseWriter, r *http.Request) Cap: CODE_EXEC
package entry
import (
"fmt"
"net/http"
"os/exec"
)
func Handle(w http.ResponseWriter, r *http.Request) {
fmt.Print("__NYX_SINK_HIT__\n")
payload := r.URL.Query().Get("payload")
cmd := exec.Command("sh", "-c", "echo hello "+payload)
out, _ := cmd.CombinedOutput()
fmt.Print(string(out))
w.WriteHeader(http.StatusOK)
w.Write(out)
}

View file

@ -0,0 +1,11 @@
<?php
// Phase 15 — CLI script with $argv, benign.
// Validates $argv[1] then runs a fixed echo.
$payload = $argv[1] ?? '';
if (!preg_match('/^[A-Za-z0-9]{1,32}$/', $payload)) {
echo "invalid\n";
exit(0);
}
$out = shell_exec("echo hello");
echo $out;

View file

@ -0,0 +1,6 @@
{
"name": "nyx/cli-script-fixture",
"require": {
"php": ">=8.0"
}
}

View file

@ -0,0 +1,9 @@
<?php
// Phase 15 — CLI script with $argv, vulnerable.
// Top-level body reads $argv[1] and pipes to /bin/sh -c.
echo "__NYX_SINK_HIT__\n";
$payload = $argv[1] ?? '';
$out = shell_exec("echo hello " . $payload);
echo $out;

View file

@ -0,0 +1,17 @@
<?php
// Phase 15 — Slim/Laravel-style route closure, benign.
// Validates payload before invoking sink.
$GLOBALS['__nyx_route'] = function ($payload) {
if (!preg_match('/^[A-Za-z0-9]{1,32}$/', (string)$payload)) {
echo "invalid\n";
return "invalid";
}
$out = shell_exec("echo hello");
echo $out;
return $out;
};
if (false) {
$app->get('/run', $GLOBALS['__nyx_route']);
}

View file

@ -0,0 +1,6 @@
{
"name": "nyx/route-closure-fixture",
"require": {
"php": ">=8.0"
}
}

View file

@ -0,0 +1,17 @@
<?php
// Phase 15 — Slim/Laravel-style route closure, vulnerable.
// Reads payload and pipes to /bin/sh -c.
// Entry: route closure Cap: CODE_EXEC
echo "__NYX_SINK_HIT__\n";
$GLOBALS['__nyx_route'] = function ($payload) {
$out = shell_exec("echo hello " . $payload);
echo $out;
return $out;
};
// Slim-shape marker so PhpShape::detect picks RouteClosure.
if (false) {
$app->get('/run', $GLOBALS['__nyx_route']);
}

View file

@ -0,0 +1,11 @@
<?php
// Phase 15 — top-level script (no function entry), benign.
// Validates payload before invoking sink.
$payload = getenv('NYX_PAYLOAD') ?: '';
if (!preg_match('/^[A-Za-z0-9]{1,32}$/', $payload)) {
echo "invalid\n";
exit(0);
}
$out = shell_exec("echo hello");
echo $out;

View file

@ -0,0 +1,6 @@
{
"name": "nyx/top-level-script-fixture",
"require": {
"php": ">=8.0"
}
}

View file

@ -0,0 +1,9 @@
<?php
// Phase 15 — top-level script (no function entry), vulnerable.
// Body reads NYX_PAYLOAD env var directly and pipes to /bin/sh -c.
echo "__NYX_SINK_HIT__\n";
$payload = getenv('NYX_PAYLOAD') ?: '';
$out = shell_exec("echo hello " . $payload);
echo $out;

View file

@ -0,0 +1,4 @@
source 'https://rubygems.org'
# Phase 15 fixture — generic controller-method shape. No framework
# dep is required at runtime; the Gemfile is informational.

View file

@ -0,0 +1,13 @@
# Phase 15 — generic instance method on a controller, benign.
class LoginController
def authenticate(payload)
unless payload =~ /\A[A-Za-z0-9]{1,32}\z/
STDOUT.print("invalid\n")
return "invalid"
end
out = `echo hello`
STDOUT.print(out)
out
end
end

View file

@ -0,0 +1,12 @@
# Phase 15 — generic instance method on a controller, vulnerable.
# No framework markers — RubyShape::detect picks ControllerMethod
# from the class+def pair.
class LoginController
def authenticate(payload)
STDOUT.print("__NYX_SINK_HIT__\n")
out = `echo hello #{payload}`
STDOUT.print(out)
out
end
end

View file

@ -0,0 +1,6 @@
source 'https://rubygems.org'
# Phase 15 fixture — Rack middleware shape. The harness constructs
# a Rack-shaped env hash and dispatches; the rack gem is not required
# at runtime because the env-hash invocation pattern is standalone.
gem 'rack'

View file

@ -0,0 +1,16 @@
# Phase 15 — Rack middleware, benign.
class NyxRackApp
def initialize(app = nil); @app = app; end
def call(env)
payload = env['nyx.payload'] || ENV['NYX_PAYLOAD'] || ''
unless payload =~ /\A[A-Za-z0-9]{1,32}\z/
[400, { 'Content-Type' => 'text/plain' }, ['invalid']]
else
out = `echo hello`
STDOUT.print(out)
[200, { 'Content-Type' => 'text/plain' }, [out]]
end
end
end

View file

@ -0,0 +1,14 @@
# Phase 15 — Rack middleware, vulnerable.
# `call(env)` reads env['nyx.payload'] and pipes to /bin/sh -c.
class NyxRackApp
def initialize(app = nil); @app = app; end
def call(env)
STDOUT.print("__NYX_SINK_HIT__\n")
payload = env['nyx.payload'] || ENV['NYX_PAYLOAD'] || ''
out = `echo hello #{payload}`
STDOUT.print(out)
[200, { 'Content-Type' => 'text/plain' }, [out]]
end
end

View file

@ -0,0 +1,7 @@
source 'https://rubygems.org'
# Phase 15 fixture — Rails action shape. The harness instantiates
# the controller via .new and calls the action through reflection;
# the rails gem is not actually required at runtime. The Gemfile is
# informational so cargo-side fixture pickup sees a non-empty manifest.
gem 'rails'

View file

@ -0,0 +1,24 @@
# Phase 15 — Rails-style controller action, benign.
class ApplicationController
def initialize; end
end
class UsersController < ApplicationController
def initialize
super
@__nyx_payload = nil
@__nyx_request = nil
end
def index
payload = @__nyx_payload || ENV['NYX_PAYLOAD'] || ''
unless payload =~ /\A[A-Za-z0-9]{1,32}\z/
STDOUT.print("invalid\n")
return "invalid"
end
out = `echo hello`
STDOUT.print(out)
out
end
end

View file

@ -0,0 +1,23 @@
# Phase 15 — Rails-style controller action, vulnerable.
# Controller inherits the conventional ApplicationController name so
# RubyShape::detect picks RailsAction.
class ApplicationController
def initialize; end
end
class UsersController < ApplicationController
def initialize
super
@__nyx_payload = nil
@__nyx_request = nil
end
def index
STDOUT.print("__NYX_SINK_HIT__\n")
payload = @__nyx_payload || ENV['NYX_PAYLOAD'] || ''
out = `echo hello #{payload}`
STDOUT.print(out)
out
end
end

View file

@ -0,0 +1,6 @@
source 'https://rubygems.org'
# Phase 15 fixture — Sinatra route shape. The harness emits its own
# route registry shim so the real sinatra gem is not required at
# runtime; the Gemfile is informational for cargo-side fixture pickup.
gem 'sinatra'

View file

@ -0,0 +1,13 @@
# Phase 15 — Sinatra route, benign.
# Validates payload then runs a fixed echo.
# nyx-shape: sinatra
get '/run' do |payload|
unless payload =~ /\A[A-Za-z0-9]{1,32}\z/
STDOUT.print("invalid\n")
next "invalid"
end
out = `echo hello`
STDOUT.print(out)
out
end

View file

@ -0,0 +1,11 @@
# Phase 15 — Sinatra route, vulnerable.
# Reads payload (passed by harness via block argument) and pipes through /bin/sh.
# Entry: route block Cap: CODE_EXEC
# nyx-shape: sinatra
get '/run' do |payload|
STDOUT.print("__NYX_SINK_HIT__\n")
out = `echo hello #{payload}`
STDOUT.print(out)
out
end

View file

@ -9,6 +9,8 @@
//!
//! Run with: `cargo nextest run --features dynamic --test go_fixtures`
mod common;
#[cfg(feature = "dynamic")]
mod go_fixture_tests {
use nyx_scanner::commands::scan::Diag;
@ -446,3 +448,175 @@ mod go_fixture_tests {
}
}
}
// ── Phase 15: per-shape acceptance ───────────────────────────────────────────
#[cfg(feature = "dynamic")]
mod phase15_shape_tests {
use crate::common::fixture_harness::run_shape_fixture_lang;
use nyx_scanner::dynamic::spec::PayloadSlot;
use nyx_scanner::evidence::{EntryKind, VerifyResult, VerifyStatus};
use nyx_scanner::labels::Cap;
use nyx_scanner::symbol::Lang;
fn go_available() -> bool {
std::process::Command::new("go")
.arg("version")
.output()
.map(|o| o.status.success())
.unwrap_or(false)
}
fn assert_confirmed(shape: &str, result: &VerifyResult) {
assert_eq!(
result.status,
VerifyStatus::Confirmed,
"{shape}/vuln: expected Confirmed, got {:?} ({:?})",
result.status,
result.detail,
);
}
fn assert_not_confirmed(shape: &str, result: &VerifyResult) {
assert!(
matches!(
result.status,
VerifyStatus::NotConfirmed | VerifyStatus::Inconclusive
),
"{shape}/benign: expected NotConfirmed (or Inconclusive), got {:?} ({:?})",
result.status,
result.detail,
);
assert_ne!(
result.status,
VerifyStatus::Confirmed,
"{shape}/benign: must not confirm",
);
}
fn run(
shape: &str,
file: &str,
func: &str,
cap: Cap,
sink_line: u32,
kind: EntryKind,
slot: PayloadSlot,
) -> VerifyResult {
run_shape_fixture_lang(
Lang::Go, "go", shape, file, func, cap, sink_line, kind, slot,
)
}
// ── handler_func ─────────────────────────────────────────────────────────
#[test]
fn handler_func_vuln_is_confirmed() {
if !go_available() {
eprintln!("SKIP: go not available");
return;
}
let r = run(
"handler_func", "vuln.go", "Handle", Cap::CODE_EXEC, 17,
EntryKind::HttpRoute, PayloadSlot::QueryParam("payload".into()),
);
assert_confirmed("handler_func", &r);
}
#[test]
fn handler_func_benign_not_confirmed() {
if !go_available() {
eprintln!("SKIP: go not available");
return;
}
let r = run(
"handler_func", "benign.go", "Handle", Cap::CODE_EXEC, 14,
EntryKind::HttpRoute, PayloadSlot::QueryParam("payload".into()),
);
assert_not_confirmed("handler_func", &r);
}
// ── gin_handler ──────────────────────────────────────────────────────────
#[test]
fn gin_handler_vuln_is_confirmed() {
if !go_available() {
eprintln!("SKIP: go not available");
return;
}
let r = run(
"gin_handler", "vuln.go", "Handle", Cap::CODE_EXEC, 16,
EntryKind::HttpRoute, PayloadSlot::QueryParam("payload".into()),
);
assert_confirmed("gin_handler", &r);
}
#[test]
fn gin_handler_benign_not_confirmed() {
if !go_available() {
eprintln!("SKIP: go not available");
return;
}
let r = run(
"gin_handler", "benign.go", "Handle", Cap::CODE_EXEC, 14,
EntryKind::HttpRoute, PayloadSlot::QueryParam("payload".into()),
);
assert_not_confirmed("gin_handler", &r);
}
// ── flag_cli ─────────────────────────────────────────────────────────────
#[test]
fn flag_cli_vuln_is_confirmed() {
if !go_available() {
eprintln!("SKIP: go not available");
return;
}
let r = run(
"flag_cli", "vuln.go", "Run", Cap::CODE_EXEC, 19,
EntryKind::CliSubcommand, PayloadSlot::Argv(0),
);
assert_confirmed("flag_cli", &r);
}
#[test]
fn flag_cli_benign_not_confirmed() {
if !go_available() {
eprintln!("SKIP: go not available");
return;
}
let r = run(
"flag_cli", "benign.go", "Run", Cap::CODE_EXEC, 15,
EntryKind::CliSubcommand, PayloadSlot::Argv(0),
);
assert_not_confirmed("flag_cli", &r);
}
// ── fuzz_variadic ────────────────────────────────────────────────────────
#[test]
fn fuzz_variadic_vuln_is_confirmed() {
if !go_available() {
eprintln!("SKIP: go not available");
return;
}
let r = run(
"fuzz_variadic", "vuln.go", "FuzzHandle", Cap::CODE_EXEC, 14,
EntryKind::Function, PayloadSlot::Param(0),
);
assert_confirmed("fuzz_variadic", &r);
}
#[test]
fn fuzz_variadic_benign_not_confirmed() {
if !go_available() {
eprintln!("SKIP: go not available");
return;
}
let r = run(
"fuzz_variadic", "benign.go", "FuzzHandle", Cap::CODE_EXEC, 14,
EntryKind::Function, PayloadSlot::Param(0),
);
assert_not_confirmed("fuzz_variadic", &r);
}
}

View file

@ -9,6 +9,8 @@
//!
//! Run with: `cargo nextest run --features dynamic --test php_fixtures`
mod common;
#[cfg(feature = "dynamic")]
mod php_fixture_tests {
use nyx_scanner::commands::scan::Diag;
@ -446,3 +448,147 @@ mod php_fixture_tests {
}
}
}
// ── Phase 15: per-shape acceptance ───────────────────────────────────────────
#[cfg(feature = "dynamic")]
mod phase15_shape_tests {
use crate::common::fixture_harness::run_shape_fixture_lang;
use nyx_scanner::dynamic::spec::PayloadSlot;
use nyx_scanner::evidence::{EntryKind, VerifyResult, VerifyStatus};
use nyx_scanner::labels::Cap;
use nyx_scanner::symbol::Lang;
fn php_available() -> bool {
std::process::Command::new("php")
.arg("--version")
.output()
.map(|o| o.status.success())
.unwrap_or(false)
}
fn assert_confirmed(shape: &str, result: &VerifyResult) {
assert_eq!(
result.status,
VerifyStatus::Confirmed,
"{shape}/vuln: expected Confirmed, got {:?} ({:?})",
result.status,
result.detail,
);
}
fn assert_not_confirmed(shape: &str, result: &VerifyResult) {
assert!(
matches!(
result.status,
VerifyStatus::NotConfirmed | VerifyStatus::Inconclusive
),
"{shape}/benign: expected NotConfirmed (or Inconclusive), got {:?} ({:?})",
result.status,
result.detail,
);
assert_ne!(
result.status,
VerifyStatus::Confirmed,
"{shape}/benign: must not confirm",
);
}
fn run(
shape: &str,
file: &str,
func: &str,
cap: Cap,
sink_line: u32,
kind: EntryKind,
slot: PayloadSlot,
) -> VerifyResult {
run_shape_fixture_lang(
Lang::Php, "php", shape, file, func, cap, sink_line, kind, slot,
)
}
// ── route_closure ────────────────────────────────────────────────────────
#[test]
fn route_closure_vuln_is_confirmed() {
if !php_available() {
eprintln!("SKIP: php not available");
return;
}
let r = run(
"route_closure", "vuln.php", "run", Cap::CODE_EXEC, 10,
EntryKind::HttpRoute, PayloadSlot::Param(0),
);
assert_confirmed("route_closure", &r);
}
#[test]
fn route_closure_benign_not_confirmed() {
if !php_available() {
eprintln!("SKIP: php not available");
return;
}
let r = run(
"route_closure", "benign.php", "run", Cap::CODE_EXEC, 11,
EntryKind::HttpRoute, PayloadSlot::Param(0),
);
assert_not_confirmed("route_closure", &r);
}
// ── cli_script ───────────────────────────────────────────────────────────
#[test]
fn cli_script_vuln_is_confirmed() {
if !php_available() {
eprintln!("SKIP: php not available");
return;
}
let r = run(
"cli_script", "vuln.php", "main", Cap::CODE_EXEC, 8,
EntryKind::CliSubcommand, PayloadSlot::Argv(0),
);
assert_confirmed("cli_script", &r);
}
#[test]
fn cli_script_benign_not_confirmed() {
if !php_available() {
eprintln!("SKIP: php not available");
return;
}
let r = run(
"cli_script", "benign.php", "main", Cap::CODE_EXEC, 11,
EntryKind::CliSubcommand, PayloadSlot::Argv(0),
);
assert_not_confirmed("cli_script", &r);
}
// ── top_level_script ─────────────────────────────────────────────────────
#[test]
fn top_level_script_vuln_is_confirmed() {
if !php_available() {
eprintln!("SKIP: php not available");
return;
}
let r = run(
"top_level_script", "vuln.php", "", Cap::CODE_EXEC, 8,
EntryKind::Function, PayloadSlot::EnvVar("NYX_PAYLOAD".into()),
);
assert_confirmed("top_level_script", &r);
}
#[test]
fn top_level_script_benign_not_confirmed() {
if !php_available() {
eprintln!("SKIP: php not available");
return;
}
let r = run(
"top_level_script", "benign.php", "", Cap::CODE_EXEC, 10,
EntryKind::Function, PayloadSlot::EnvVar("NYX_PAYLOAD".into()),
);
assert_not_confirmed("top_level_script", &r);
}
}

182
tests/ruby_fixtures.rs Normal file
View file

@ -0,0 +1,182 @@
//! Ruby fixture integration tests (Phase 15 acceptance gate).
//!
//! Per-shape acceptance for the Ruby emitter shapes shipped in Phase 15
//! (Track B Ruby vertical): Sinatra route, Rails action, Rack middleware,
//! and generic controller method. Each shape ships a `vuln.rb` + `benign.rb`
//! pair under `tests/dynamic_fixtures/ruby/<shape>/`.
//!
//! Prerequisites: skips cleanly when `ruby` is unavailable on the host.
//!
//! Run with: `cargo nextest run --features dynamic --test ruby_fixtures`
mod common;
#[cfg(feature = "dynamic")]
mod phase15_shape_tests {
use crate::common::fixture_harness::run_shape_fixture_lang;
use nyx_scanner::dynamic::spec::PayloadSlot;
use nyx_scanner::evidence::{EntryKind, VerifyResult, VerifyStatus};
use nyx_scanner::labels::Cap;
use nyx_scanner::symbol::Lang;
fn ruby_available() -> bool {
std::process::Command::new("ruby")
.arg("--version")
.output()
.map(|o| o.status.success())
.unwrap_or(false)
}
fn assert_confirmed(shape: &str, result: &VerifyResult) {
assert_eq!(
result.status,
VerifyStatus::Confirmed,
"{shape}/vuln: expected Confirmed, got {:?} ({:?})",
result.status,
result.detail,
);
}
fn assert_not_confirmed(shape: &str, result: &VerifyResult) {
assert!(
matches!(
result.status,
VerifyStatus::NotConfirmed | VerifyStatus::Inconclusive
),
"{shape}/benign: expected NotConfirmed (or Inconclusive), got {:?} ({:?})",
result.status,
result.detail,
);
assert_ne!(
result.status,
VerifyStatus::Confirmed,
"{shape}/benign: must not confirm",
);
}
fn run(
shape: &str,
file: &str,
func: &str,
cap: Cap,
sink_line: u32,
kind: EntryKind,
slot: PayloadSlot,
) -> VerifyResult {
run_shape_fixture_lang(
Lang::Ruby, "ruby", shape, file, func, cap, sink_line, kind, slot,
)
}
// ── sinatra_route ────────────────────────────────────────────────────────
#[test]
fn sinatra_route_vuln_is_confirmed() {
if !ruby_available() {
eprintln!("SKIP: ruby not available");
return;
}
let r = run(
"sinatra_route", "vuln.rb", "run", Cap::CODE_EXEC, 7,
EntryKind::HttpRoute, PayloadSlot::Param(0),
);
assert_confirmed("sinatra_route", &r);
}
#[test]
fn sinatra_route_benign_not_confirmed() {
if !ruby_available() {
eprintln!("SKIP: ruby not available");
return;
}
let r = run(
"sinatra_route", "benign.rb", "run", Cap::CODE_EXEC, 10,
EntryKind::HttpRoute, PayloadSlot::Param(0),
);
assert_not_confirmed("sinatra_route", &r);
}
// ── rails_action ─────────────────────────────────────────────────────────
#[test]
fn rails_action_vuln_is_confirmed() {
if !ruby_available() {
eprintln!("SKIP: ruby not available");
return;
}
let r = run(
"rails_action", "vuln.rb", "index", Cap::CODE_EXEC, 17,
EntryKind::HttpRoute, PayloadSlot::EnvVar("NYX_PAYLOAD".into()),
);
assert_confirmed("rails_action", &r);
}
#[test]
fn rails_action_benign_not_confirmed() {
if !ruby_available() {
eprintln!("SKIP: ruby not available");
return;
}
let r = run(
"rails_action", "benign.rb", "index", Cap::CODE_EXEC, 20,
EntryKind::HttpRoute, PayloadSlot::EnvVar("NYX_PAYLOAD".into()),
);
assert_not_confirmed("rails_action", &r);
}
// ── rack_middleware ──────────────────────────────────────────────────────
#[test]
fn rack_middleware_vuln_is_confirmed() {
if !ruby_available() {
eprintln!("SKIP: ruby not available");
return;
}
let r = run(
"rack_middleware", "vuln.rb", "call", Cap::CODE_EXEC, 9,
EntryKind::HttpRoute, PayloadSlot::EnvVar("NYX_PAYLOAD".into()),
);
assert_confirmed("rack_middleware", &r);
}
#[test]
fn rack_middleware_benign_not_confirmed() {
if !ruby_available() {
eprintln!("SKIP: ruby not available");
return;
}
let r = run(
"rack_middleware", "benign.rb", "call", Cap::CODE_EXEC, 11,
EntryKind::HttpRoute, PayloadSlot::EnvVar("NYX_PAYLOAD".into()),
);
assert_not_confirmed("rack_middleware", &r);
}
// ── controller_method ────────────────────────────────────────────────────
#[test]
fn controller_method_vuln_is_confirmed() {
if !ruby_available() {
eprintln!("SKIP: ruby not available");
return;
}
let r = run(
"controller_method", "vuln.rb", "authenticate", Cap::CODE_EXEC, 7,
EntryKind::Function, PayloadSlot::Param(0),
);
assert_confirmed("controller_method", &r);
}
#[test]
fn controller_method_benign_not_confirmed() {
if !ruby_available() {
eprintln!("SKIP: ruby not available");
return;
}
let r = run(
"controller_method", "benign.rb", "authenticate", Cap::CODE_EXEC, 10,
EntryKind::Function, PayloadSlot::Param(0),
);
assert_not_confirmed("controller_method", &r);
}
}