(t *testing.T)
| 50 | } |
| 51 | |
| 52 | func TestInjectionAppearanceEscapesMetaAttributes(t *testing.T) { |
| 53 | t.Parallel() |
| 54 | |
| 55 | const ( |
| 56 | applicationName = `Coder"><script>alert(1)</script>` |
| 57 | logoURL = `https://example.com/logo.png"><img src=x onerror=alert(1)>` |
| 58 | ) |
| 59 | |
| 60 | tests := []struct { |
| 61 | name string |
| 62 | authenticated bool |
| 63 | }{ |
| 64 | { |
| 65 | name: "unauthenticated", |
| 66 | }, |
| 67 | { |
| 68 | name: "authenticated", |
| 69 | authenticated: true, |
| 70 | }, |
| 71 | } |
| 72 | |
| 73 | for _, tt := range tests { |
| 74 | t.Run(tt.name, func(t *testing.T) { |
| 75 | t.Parallel() |
| 76 | |
| 77 | siteFS := fstest.MapFS{ |
| 78 | "index.html": &fstest.MapFile{ |
| 79 | Data: []byte(`<meta name="application-name" content="{{ .ApplicationName }}" /><meta property="logo-url" content="{{ .LogoURL }}" />`), |
| 80 | }, |
| 81 | } |
| 82 | db, _ := dbtestutil.NewDB(t) |
| 83 | var appearanceFetcher atomic.Pointer[appearance.Fetcher] |
| 84 | fetcher := appearance.Fetcher(staticAppearanceFetcher{cfg: codersdk.AppearanceConfig{ |
| 85 | ApplicationName: applicationName, |
| 86 | LogoURL: logoURL, |
| 87 | }}) |
| 88 | appearanceFetcher.Store(&fetcher) |
| 89 | handler, err := site.New(&site.Options{ |
| 90 | Telemetry: telemetry.NewNoop(), |
| 91 | Database: db, |
| 92 | SiteFS: siteFS, |
| 93 | AppearanceFetcher: &appearanceFetcher, |
| 94 | }) |
| 95 | require.NoError(t, err) |
| 96 | |
| 97 | r := httptest.NewRequest("GET", "/", nil) |
| 98 | if tt.authenticated { |
| 99 | user := dbgen.User(t, db, database.User{}) |
| 100 | _, token := dbgen.APIKey(t, db, database.APIKey{ |
| 101 | UserID: user.ID, |
| 102 | ExpiresAt: time.Now().Add(time.Hour), |
| 103 | }) |
| 104 | r.Header.Set(codersdk.SessionTokenHeader, token) |
| 105 | } |
| 106 | rw := httptest.NewRecorder() |
| 107 | |
| 108 | handler.ServeHTTP(rw, r) |
| 109 | require.Equal(t, http.StatusOK, rw.Code) |
nothing calls this directly
no test coverage detected