fix(inbound-api): resolve InboundAPI-014..017 — return-value validation, reflection-gateway hardening, deadline-bound routed calls, RouteHelper test coverage
This commit is contained in:
104
tests/ScadaLink.InboundAPI.Tests/ForbiddenApiCheckerTests.cs
Normal file
104
tests/ScadaLink.InboundAPI.Tests/ForbiddenApiCheckerTests.cs
Normal file
@@ -0,0 +1,104 @@
|
||||
namespace ScadaLink.InboundAPI.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// InboundAPI-005 / InboundAPI-015: tests for the script-trust-model checker.
|
||||
///
|
||||
/// InboundAPI-015 hardens the textual walker against reflection reached through
|
||||
/// permitted-type members that never spell a forbidden namespace, e.g.
|
||||
/// <c>typeof(string).Assembly.GetType("System.IO.File")</c>.
|
||||
/// </summary>
|
||||
public class ForbiddenApiCheckerTests
|
||||
{
|
||||
private static bool IsRejected(string script) =>
|
||||
ForbiddenApiChecker.FindViolations(script).Count > 0;
|
||||
|
||||
// --- Baseline: legitimate scripts must still pass ---
|
||||
|
||||
[Theory]
|
||||
[InlineData("return 1 + 1;")]
|
||||
[InlineData("var list = new List<int> { 1, 2, 3 }; return list.Sum();")]
|
||||
[InlineData("return Parameters.Get<int>(\"x\") * 2;")]
|
||||
[InlineData("await Task.Delay(1); return null;")]
|
||||
[InlineData("var r = await Route.To(\"inst\").Call(\"s\"); return r;")]
|
||||
[InlineData("Action a = () => {}; a.Invoke(); return null;")]
|
||||
public void PermittedScript_NotRejected(string script)
|
||||
{
|
||||
Assert.False(IsRejected(script), script);
|
||||
}
|
||||
|
||||
// --- Baseline: forbidden namespaces (textual) must still be rejected ---
|
||||
|
||||
[Theory]
|
||||
[InlineData("System.IO.File.Delete(\"/tmp/x\"); return null;")]
|
||||
[InlineData("System.Diagnostics.Process.Start(\"/bin/sh\"); return null;")]
|
||||
[InlineData("using System.Reflection; return null;")]
|
||||
[InlineData("var s = new System.Net.Sockets.Socket(default, default, default); return null;")]
|
||||
public void ForbiddenNamespace_Rejected(string script)
|
||||
{
|
||||
Assert.True(IsRejected(script), script);
|
||||
}
|
||||
|
||||
// --- InboundAPI-015: reflection reachable without a forbidden namespace token ---
|
||||
|
||||
[Fact]
|
||||
public void Reflection_AssemblyPropertyAccess_Rejected()
|
||||
{
|
||||
// typeof(string).Assembly — .Assembly is a reflection gateway off a permitted type.
|
||||
Assert.True(IsRejected("var a = typeof(string).Assembly; return null;"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Reflection_AssemblyGetType_Rejected()
|
||||
{
|
||||
// The classic bypass: obtain System.IO.File as a Type via a string literal.
|
||||
Assert.True(IsRejected(
|
||||
"var t = typeof(string).Assembly.GetType(\"System.IO.File\"); return null;"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Reflection_ObjectGetType_Rejected()
|
||||
{
|
||||
// x.GetType() returns a System.Type — a reflection gateway.
|
||||
Assert.True(IsRejected("var t = \"\".GetType(); return null;"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Reflection_TypeGetTypeStatic_Rejected()
|
||||
{
|
||||
Assert.True(IsRejected("var t = Type.GetType(\"System.IO.File\"); return null;"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Reflection_ActivatorCreateInstance_Rejected()
|
||||
{
|
||||
Assert.True(IsRejected(
|
||||
"var o = Activator.CreateInstance(Type.GetType(\"System.IO.File\")); return null;"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Reflection_InvokeMember_Rejected()
|
||||
{
|
||||
Assert.True(IsRejected(
|
||||
"typeof(object).InvokeMember(\"x\", default, null, null, null); return null;"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Reflection_GetMethodInvoke_Rejected()
|
||||
{
|
||||
Assert.True(IsRejected(
|
||||
"var m = typeof(object).GetMethod(\"ToString\"); return null;"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Reflection_GetTypeInfo_Rejected()
|
||||
{
|
||||
Assert.True(IsRejected("var ti = \"\".GetType().GetTypeInfo(); return null;"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DynamicKeyword_Rejected()
|
||||
{
|
||||
// dynamic widens late-bound member access the static walker cannot see through.
|
||||
Assert.True(IsRejected("dynamic d = Parameters; return null;"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user