Files

146 lines
3.4 KiB
Go
Raw Permalink Normal View History

// Pass 4 — security-sensitive manifest validation tests.
//
// Locks in the regex / path-traversal / NUL-byte rules added in Pass 2 so
// regressions in Manifest.Validate are caught at CI time instead of via
// manual review.
package plugin
import (
"strings"
"testing"
)
func TestPluginNameRegexp(t *testing.T) {
cases := []struct {
name string
ok bool
}{
{"hello", true},
{"game-detection", true},
{"a", true},
{"a1_b-c", true},
{"abcdefghijklmnopqrstuvwxyz0123456789_-abcdefghijklmnopqrstuvwxyz", true}, // 64 chars
{"abcdefghijklmnopqrstuvwxyz0123456789_-abcdefghijklmnopqrstuvwxyz0", false}, // 65 chars
{"", false},
{"Hello", false},
{"_leading", false},
{"-leading", false},
{"..", false},
{"a/b", false},
{"a.b", false},
{"a b", false},
{"a\x00b", false},
{"hello!", false},
}
for _, c := range cases {
got := pluginNameRegexp.MatchString(c.name)
if got != c.ok {
t.Errorf("pluginNameRegexp.MatchString(%q) = %v, want %v", c.name, got, c.ok)
}
}
}
func TestValidateRelativePath(t *testing.T) {
cases := []struct {
path string
ok bool
}{
{"a.wasm", true},
{"assets/index.html", true},
{"dir/sub/file.js", true},
{"hello.wasm", true},
// failures
{"", false},
{"/abs/path", false},
{"../escape", false},
{"./not-clean", false},
{"dir//double", false},
{"dir/", false},
{"dir\\win", false},
{"file\x00name", false},
{"..", false},
{".", false},
{"a/../b", false},
{"a/./b", false},
}
for _, c := range cases {
err := validateRelativePath(c.path)
got := err == nil
if got != c.ok {
t.Errorf("validateRelativePath(%q) ok=%v err=%v, want ok=%v", c.path, got, err, c.ok)
}
}
}
func TestManifestValidateRejectsBadName(t *testing.T) {
m := &Manifest{
Name: "Bad Name",
Version: "0.1.0",
Entrypoint: "hello.wasm",
}
if err := m.Validate(); err == nil {
t.Fatal("expected validation failure for invalid name")
}
}
func TestManifestValidateRejectsBadAsset(t *testing.T) {
m := &Manifest{
Name: "hello",
Version: "0.1.0",
Entrypoint: "hello.wasm",
UI: UISpec{
Tabs: []UITab{
{ID: "main", Asset: "../../../etc/passwd"},
},
},
}
if err := m.Validate(); err == nil {
t.Fatal("expected validation failure for traversal asset")
}
}
func TestManifestValidateRejectsAbsoluteEntrypoint(t *testing.T) {
m := &Manifest{
Name: "hello",
Version: "0.1.0",
Entrypoint: "/etc/passwd.wasm",
}
if err := m.Validate(); err == nil {
t.Fatal("expected validation failure for absolute entrypoint")
}
}
func TestManifestValidateAcceptsMinimal(t *testing.T) {
m := &Manifest{
Name: "hello",
Version: "0.1.0",
Entrypoint: "hello.wasm",
}
if err := m.Validate(); err != nil {
t.Fatalf("expected minimal manifest to validate, got: %v", err)
}
}
func TestManifestValidateRejectsOversizedVersion(t *testing.T) {
m := &Manifest{
Name: "hello",
Version: strings.Repeat("v", 65),
Entrypoint: "hello.wasm",
}
if err := m.Validate(); err == nil {
t.Fatal("expected validation failure for oversized version")
}
}
func TestManifestValidateRejectsUnknownPermission(t *testing.T) {
m := &Manifest{
Name: "hello",
Version: "0.1.0",
Entrypoint: "hello.wasm",
Permissions: []string{"commands", "filesystem"},
}
if err := m.Validate(); err == nil {
t.Fatal("expected validation failure for unknown permission")
}
}