This repository has been archived on 2026-04-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
core/server/sandbox/unicode_scan_test.go
Jeffrey Smith 5d58a1cd4c
All checks were successful
CI/CD / detect-changes (pull_request) Successful in 4s
CI/CD / test-frontend (pull_request) Successful in 5s
CI/CD / test-go-pg (pull_request) Successful in 2m31s
CI/CD / test-sqlite (pull_request) Successful in 2m45s
CI/CD / build-and-deploy (pull_request) Successful in 1m34s
Feat v0.5.1 chat core library + unicode security gate + cluster registry design
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 12:26:16 +00:00

355 lines
9.9 KiB
Go

package sandbox
import (
"context"
"strings"
"testing"
)
// ── ScanSource ──────────────────────────────
func TestScanSource_CleanStarlark(t *testing.T) {
source := `
def on_request(req):
path = req["path"]
method = req["method"]
return {"status": 200, "body": "ok"}
`
findings := ScanSource(source, "clean.star")
if len(findings) != 0 {
t.Errorf("expected 0 findings for clean source, got %d", len(findings))
}
}
func TestScanSource_FewVariationSelectors(t *testing.T) {
// 1-2 variation selectors — legitimate emoji use (e.g. text vs emoji presentation)
source := "emoji = \"\u2764\uFE0F\" # heart with variation selector"
findings := ScanSource(source, "emoji.star")
if len(findings) != 1 {
t.Fatalf("expected 1 finding for single VS, got %d", len(findings))
}
if findings[0].Category != "variation_selector" {
t.Errorf("expected category variation_selector, got %q", findings[0].Category)
}
if findings[0].Rune != '\uFE0F' {
t.Errorf("expected rune U+FE0F, got U+%04X", findings[0].Rune)
}
}
func TestScanSource_GlassWormPattern(t *testing.T) {
// 50+ variation selectors — GlassWorm payload pattern
var sb strings.Builder
sb.WriteString("data = \"")
for i := 0; i < 50; i++ {
sb.WriteRune(rune(0xFE00 + (i % 16))) // cycle through VS1-VS16
}
sb.WriteString("\"")
source := sb.String()
findings := ScanSource(source, "glassworm.star")
count := 0
for _, f := range findings {
if f.Category == "variation_selector" {
count++
}
}
if count != 50 {
t.Errorf("expected 50 variation_selector findings, got %d", count)
}
}
func TestScanSource_BidiOverride(t *testing.T) {
// RLO (U+202E) — Trojan Source attack
source := "name = \"\u202Emalicious\""
findings := ScanSource(source, "bidi.star")
if len(findings) != 1 {
t.Fatalf("expected 1 finding, got %d", len(findings))
}
if findings[0].Category != "bidi_override" {
t.Errorf("expected bidi_override, got %q", findings[0].Category)
}
}
func TestScanSource_BidiIsolate(t *testing.T) {
// LRI (U+2066), PDI (U+2069)
source := "text = \"\u2066hidden\u2069\""
findings := ScanSource(source, "bidi_isolate.star")
bidiCount := 0
for _, f := range findings {
if f.Category == "bidi_override" {
bidiCount++
}
}
if bidiCount != 2 {
t.Errorf("expected 2 bidi_override findings, got %d", bidiCount)
}
}
func TestScanSource_ZeroWidthBelow(t *testing.T) {
// 3 zero-width chars — below threshold
source := "a = \"\u200B\u200B\u200B\""
findings := ScanSource(source, "zw.star")
zwCount := 0
for _, f := range findings {
if f.Category == "zero_width" {
zwCount++
}
}
if zwCount != 3 {
t.Errorf("expected 3 zero_width findings, got %d", zwCount)
}
}
func TestScanSource_ZeroWidthAbove(t *testing.T) {
// 8 zero-width chars — above threshold
source := "a = \"\u200B\u200B\u200B\u200B\u200B\u200B\u200B\u200B\""
findings := ScanSource(source, "zw_many.star")
zwCount := 0
for _, f := range findings {
if f.Category == "zero_width" {
zwCount++
}
}
if zwCount != 8 {
t.Errorf("expected 8 zero_width findings, got %d", zwCount)
}
}
func TestScanSource_TagCharacters(t *testing.T) {
// Tag characters (U+E0001-U+E007F)
var sb strings.Builder
sb.WriteString("tag = \"")
for i := 0; i < 15; i++ {
sb.WriteRune(rune(0xE0001 + i))
}
sb.WriteString("\"")
source := sb.String()
findings := ScanSource(source, "tags.star")
tagCount := 0
for _, f := range findings {
if f.Category == "tag_character" {
tagCount++
}
}
if tagCount != 15 {
t.Errorf("expected 15 tag_character findings, got %d", tagCount)
}
}
func TestScanSource_HangulFiller(t *testing.T) {
source := "x = \"\u3164\"" // Hangul filler
findings := ScanSource(source, "hangul.star")
if len(findings) != 1 {
t.Fatalf("expected 1 finding, got %d", len(findings))
}
if findings[0].Category != "other_invisible" {
t.Errorf("expected other_invisible, got %q", findings[0].Category)
}
}
func TestScanSource_LineTracking(t *testing.T) {
source := "line1\nline2\nline3 = \"\u202E\""
findings := ScanSource(source, "lines.star")
if len(findings) != 1 {
t.Fatalf("expected 1 finding, got %d", len(findings))
}
if findings[0].Line != 3 {
t.Errorf("expected line 3, got %d", findings[0].Line)
}
}
func TestScanSource_VariationSelectorSupplement(t *testing.T) {
// U+E0100 — GlassWorm secondary range (R32)
var sb strings.Builder
sb.WriteString("x = \"")
for i := 0; i < 12; i++ {
sb.WriteRune(rune(0xE0100 + i))
}
sb.WriteString("\"")
source := sb.String()
findings := ScanSource(source, "vs_supp.star")
vsCount := 0
for _, f := range findings {
if f.Category == "variation_selector" {
vsCount++
}
}
if vsCount != 12 {
t.Errorf("expected 12 variation_selector findings from supplement range, got %d", vsCount)
}
}
// ── Verdict ─────────────────────────────────
func TestVerdict_NoFindings(t *testing.T) {
blocked, reason := Verdict(nil)
if blocked {
t.Error("expected not blocked for nil findings")
}
if reason != "" {
t.Errorf("expected empty reason, got %q", reason)
}
}
func TestVerdict_BidiAlwaysBlocks(t *testing.T) {
findings := []Finding{{Rune: 0x202E, Category: "bidi_override"}}
blocked, reason := Verdict(findings)
if !blocked {
t.Error("expected blocked for bidi_override")
}
if !strings.Contains(reason, "bidirectional override") {
t.Errorf("expected bidi reason, got %q", reason)
}
}
func TestVerdict_GlassWormBlocks(t *testing.T) {
var findings []Finding
for i := 0; i < 10; i++ {
findings = append(findings, Finding{Rune: rune(0xFE00 + i), Category: "variation_selector"})
}
blocked, reason := Verdict(findings)
if !blocked {
t.Error("expected blocked for 10 variation selectors")
}
if !strings.Contains(reason, "GlassWorm") {
t.Errorf("expected GlassWorm reason, got %q", reason)
}
}
func TestVerdict_GlassWormMixedVSAndTag(t *testing.T) {
var findings []Finding
for i := 0; i < 5; i++ {
findings = append(findings, Finding{Rune: rune(0xFE00 + i), Category: "variation_selector"})
}
for i := 0; i < 5; i++ {
findings = append(findings, Finding{Rune: rune(0xE0001 + i), Category: "tag_character"})
}
blocked, _ := Verdict(findings)
if !blocked {
t.Error("expected blocked for 5 VS + 5 tag = 10 total")
}
}
func TestVerdict_FewVariationSelectorsPass(t *testing.T) {
findings := []Finding{
{Rune: 0xFE0F, Category: "variation_selector"},
{Rune: 0xFE0E, Category: "variation_selector"},
}
blocked, _ := Verdict(findings)
if blocked {
t.Error("expected not blocked for 2 variation selectors (emoji use)")
}
}
func TestVerdict_ZeroWidthBelowThresholdPasses(t *testing.T) {
var findings []Finding
for i := 0; i < 5; i++ {
findings = append(findings, Finding{Rune: 0x200B, Category: "zero_width"})
}
blocked, _ := Verdict(findings)
if blocked {
t.Error("expected not blocked for 5 zero_width chars")
}
}
func TestVerdict_ZeroWidthAboveThresholdBlocks(t *testing.T) {
var findings []Finding
for i := 0; i < 6; i++ {
findings = append(findings, Finding{Rune: 0x200B, Category: "zero_width"})
}
blocked, reason := Verdict(findings)
if !blocked {
t.Error("expected blocked for 6 zero_width chars")
}
if !strings.Contains(reason, "zero-width") {
t.Errorf("expected zero-width reason, got %q", reason)
}
}
func TestVerdict_OtherInvisibleDoesNotBlock(t *testing.T) {
findings := []Finding{
{Rune: 0x3164, Category: "other_invisible"},
{Rune: 0xFFF9, Category: "other_invisible"},
}
blocked, _ := Verdict(findings)
if blocked {
t.Error("expected not blocked for 2 other_invisible chars")
}
}
// ── GlassWorm payload simulation ────────────
func TestGlassWormPayloadDetection(t *testing.T) {
// Simulate a GlassWorm-style payload: encode each byte of a secret string
// as a variation selector offset (U+FE00 + low nibble, U+E0100 + high nibble).
// This is the core technique: invisible characters carrying data.
secret := "eval(malicious_code)"
var sb strings.Builder
sb.WriteString("innocent_looking_var = \"")
for _, b := range []byte(secret) {
// Encode low nibble as VS1-VS16 (U+FE00-U+FE0F)
sb.WriteRune(rune(0xFE00 + (int(b) & 0x0F)))
// Encode high nibble as VS supplement (U+E0100-U+E010F)
sb.WriteRune(rune(0xE0100 + ((int(b) >> 4) & 0x0F)))
}
sb.WriteString("\"")
source := sb.String()
findings := ScanSource(source, "glassworm_payload.star")
// Should have 2 findings per character (low + high nibble)
expectedFindings := len(secret) * 2
vsCount := 0
for _, f := range findings {
if f.Category == "variation_selector" {
vsCount++
}
}
if vsCount != expectedFindings {
t.Errorf("expected %d variation_selector findings, got %d", expectedFindings, vsCount)
}
// Verdict should block
blocked, reason := Verdict(findings)
if !blocked {
t.Errorf("GlassWorm payload should be blocked, got: %s", reason)
}
if !strings.Contains(reason, "GlassWorm") {
t.Errorf("expected GlassWorm in reason, got: %s", reason)
}
}
// ── Execution gate integration ──────────────
func TestExecGateBlocksBidiSource(t *testing.T) {
sb := New(DefaultConfig())
// Source with a bidi override character
source := "x = \"\u202E\""
_, err := sb.Exec(context.Background(), "bidi.star", source, nil)
if err == nil {
t.Fatal("expected error from unicode gate for bidi override")
}
if !strings.Contains(err.Error(), "unicode security gate") {
t.Errorf("expected 'unicode security gate' error, got: %v", err)
}
}
func TestExecGateAllowsCleanSource(t *testing.T) {
sb := New(DefaultConfig())
_, err := sb.Exec(context.Background(), "clean.star", "x = 42", nil)
if err != nil {
t.Fatalf("clean source should not be blocked: %v", err)
}
}
func TestExecGateAllowsFewVS(t *testing.T) {
sb := New(DefaultConfig())
source := "emoji = \"\u2764\uFE0F\""
_, err := sb.Exec(context.Background(), "emoji.star", source, nil)
if err != nil {
t.Fatalf("2 VS should not be blocked: %v", err)
}
}