Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

security_test.gno

6.15 Kb · 156 lines
  1package home
  2
  3import (
  4	"chain/runtime"
  5	"encoding/base64"
  6	"net/url"
  7	"strconv"
  8	"strings"
  9	"testing"
 10
 11	"gno.land/p/nt/avl/v0"
 12	"gno.land/p/nt/testutils/v0"
 13	"gno.land/p/nt/uassert/v0"
 14)
 15
 16// A guess arrives in the page link, so anyone can write one: none of it may
 17// reach the rendered markdown.
 18func TestGuessIsNeverEchoed(t *testing.T) {
 19	setDay(launchDay)
 20	uassert.True(t, strings.Contains(Render("wordle/1?guess=abc"), "> That guess is not five letters, A to Z."))
 21	for _, guess := range []string{
 22		"[claim your reward](https://evil.example)",
 23		"x\n\n# OFFICIAL NOTICE",
 24		"![](https://evil.example/pixel.png)",
 25		`<gno-form exec="Transfer"><gno-input name="to" /></gno-form>`,
 26	} {
 27		// Any rejected guess renders the very page a harmless rejected one does.
 28		page := Render("wordle/1?guess=" + url.QueryEscape(guess))
 29		uassert.Equal(t, Render("wordle/1?guess=abc"), page, "guess leaked into the page: "+guess)
 30	}
 31}
 32
 33func TestOnlyAdminWrites(cur realm, t *testing.T) {
 34	for _, caller := range []runtime.Realm{
 35		testing.NewUserRealm(testutils.TestAddress("mallory")),
 36		testing.NewCodeRealm("gno.land/r/evil/relay"),
 37	} {
 38		testing.SetRealm(caller)
 39		uassert.AbortsWithMessage(t, cur, "unauthorized", func() { Set(cross(cur), "header", "pwned") })
 40		uassert.AbortsWithMessage(t, cur, "unauthorized", func() { Delete(cross(cur), "header") })
 41	}
 42	uassert.False(t, strings.Contains(Render(""), "pwned"))
 43}
 44
 45func TestChartTextIsEscaped(cur realm, t *testing.T) {
 46	testing.SetRealm(testing.NewUserRealm(admin))
 47	old := Get(reviewActivitySlot)
 48	Set(cross(cur), reviewActivitySlot, `title reviews; series <script>alert(1)</script>,a&b; 2026-01 1 2`)
 49	out := renderChart(reviewActivitySlot)
 50	raw, err := base64.StdEncoding.DecodeString(out[strings.Index(out, "base64,")+7 : len(out)-2])
 51	uassert.NoError(t, err)
 52	uassert.False(t, strings.Contains(string(raw), "<script>"))
 53	uassert.True(t, strings.Contains(string(raw), "&lt;script&gt;"))
 54	uassert.True(t, strings.Contains(string(raw), "a&amp;b"))
 55	Set(cross(cur), reviewActivitySlot, old)
 56}
 57
 58func TestHeartsListIsPaged(t *testing.T) {
 59	hearts = avl.NewTree()
 60	for i := 0; i < 250; i++ {
 61		hearts.Set(testutils.TestAddress("fan"+strconv.Itoa(i)).String(), int64(i))
 62	}
 63	first := Render("hearts")
 64	uassert.Equal(t, heartsPerPage, strings.Count(first, "\n- "))
 65	uassert.True(t, strings.Contains(first, "Page 1 of 3."))
 66	uassert.True(t, strings.Contains(first, "[Next]("+home+":hearts/2)"))
 67	uassert.False(t, strings.Contains(first, "[Previous]"))
 68	last := Render("hearts/3")
 69	uassert.Equal(t, 50, strings.Count(last, "\n- "))
 70	uassert.True(t, strings.Contains(last, "[Previous]("+home+":hearts/2)"))
 71	uassert.False(t, strings.Contains(last, "[Next]"))
 72	for _, bad := range []string{"hearts/0", "hearts/4", "hearts/x", "hearts/-1"} {
 73		uassert.True(t, strings.Contains(Render(bad), "There is no such page."), bad)
 74	}
 75	hearts = avl.NewTree()
 76}
 77
 78func TestOrderSurvivesBadEntries(cur realm, t *testing.T) {
 79	testing.SetRealm(testing.NewUserRealm(admin))
 80	old := Get(orderSlot)
 81	Set(cross(cur), orderSlot, "nosuchsection,,header")
 82	page := Render("")
 83	uassert.True(t, strings.HasPrefix(strings.TrimSpace(page), "I'm a developer engineer"))
 84	Delete(cross(cur), orderSlot)
 85	uassert.True(t, strings.Contains(Render(""), "This page is a contract"))
 86	Set(cross(cur), orderSlot, old)
 87}
 88
 89func TestQueryDoesNotBreakPages(t *testing.T) {
 90	uassert.Equal(t, Render(""), Render("?utm_source=x"))
 91	uassert.Equal(t, Render(""), Render("?fbclid=abc&x=1"))
 92	uassert.Equal(t, Render("hearts"), Render("hearts?x=1"))
 93	// The game keeps its query: it carries the guess.
 94	setDay(launchDay)
 95	uassert.True(t, strings.Contains(Render("wordle/1?guess=crane"), "CRANE"))
 96}
 97
 98func TestLongPathIsRefused(t *testing.T) {
 99	setDay(launchDay)
100	// The longest link the game writes: six guesses and a typed one.
101	longest := "wordle/99999/CRANE.SLATE.ROUTE.TRACE.QUERY.PROXY?guess=" + url.QueryEscape("  crane  ")
102	uassert.True(t, len(longest) <= maxPathLen)
103	uassert.False(t, strings.HasPrefix(Render(longest), "# Not found"))
104	for _, p := range []string{
105		"wordle/1?" + strings.Repeat("a=&", 100),
106		"wordle/1/" + strings.Repeat(".", 300),
107		"hearts/" + strings.Repeat("9", 300),
108	} {
109		uassert.True(t, strings.HasPrefix(Render(p), "# Not found"), p[:20])
110	}
111}
112
113func TestHeroTextIsEditable(cur realm, t *testing.T) {
114	testing.SetRealm(testing.NewUserRealm(admin))
115	old := Get(heroSlot)
116	decode := func() string {
117		out := heroImage()
118		raw, err := base64.StdEncoding.DecodeString(out[strings.Index(out, "base64,")+7 : len(out)-1])
119		uassert.NoError(t, err)
120		return string(raw)
121	}
122	uassert.True(t, strings.Contains(decode(), ">David G.</text>"))
123
124	Set(cross(cur), heroSlot, "title Dave; subtitle <b>&co</b>; bogus x; tagline hello")
125	svg := decode()
126	uassert.True(t, strings.Contains(svg, ">Dave</text>"))
127	uassert.True(t, strings.Contains(svg, ">&lt;b&gt;&amp;co&lt;/b&gt;</text>"))
128	uassert.True(t, strings.Contains(svg, ">hello</text>"))
129	// A key the section leaves out keeps its default.
130	uassert.True(t, strings.Contains(svg, ">gno.land core contributor</text>"))
131	uassert.True(t, strings.HasPrefix(heroImage(), "![David G., developer engineer at Samouraï Coop]("))
132
133	Set(cross(cur), heroSlot, "alt a ]( trap")
134	uassert.True(t, strings.HasPrefix(heroImage(), "![a )( trap]("))
135
136	Set(cross(cur), heroSlot, old)
137}
138
139func TestSetActivity(cur realm, t *testing.T) {
140	oldPR, oldRev, oldAreas := Get(activitySlot), Get(reviewActivitySlot), Get(areasSlot)
141	testing.SetRealm(testing.NewUserRealm(testutils.TestAddress("mallory")))
142	uassert.AbortsWithMessage(t, cur, "unauthorized", func() { SetActivity(cross(cur), "x", "x", "x") })
143	uassert.Equal(t, oldPR, Get(activitySlot))
144
145	testing.SetRealm(testing.NewUserRealm(admin))
146	SetActivity(cross(cur), "title a; series s; 2026-01 1", "", "title c; slice 3 Docs")
147	uassert.Equal(t, "title a; series s; 2026-01 1", Get(activitySlot))
148	uassert.Equal(t, oldRev, Get(reviewActivitySlot)) // empty leaves it as it is
149	uassert.Equal(t, "title c; slice 3 Docs", Get(areasSlot))
150	out := renderChart(activitySlot)
151	raw, err := base64.StdEncoding.DecodeString(out[strings.Index(out, "base64,")+7 : len(out)-2])
152	uassert.NoError(t, err)
153	uassert.True(t, strings.Contains(string(raw), ">1 a</text>"))
154
155	SetActivity(cross(cur), oldPR, oldRev, oldAreas)
156}