From review. - /profile asked before signing in keeps its query for ten minutes, and the sign-in (password or OIDC) returns to it: VS Code's sign-in state came back lost, and the confirmation came every time. Only /profile is ever returned to. - The extension keeps an https gateway at https when a link names it by plain http (a proxy ending TLS unannounced), so the key is never sent in the clear. The install command says --force, since dev builds share a version and VS Code kept the old one. - Every frame of every install recording is checked for a key, a password or a private key, not only the page. - The admin API document again matches the API: f94d902's MCPServerRequest.clear was not in it. - CHANGELOG for this round's fixes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
131 lines
4.1 KiB
Go
131 lines
4.1 KiB
Go
package web
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"regexp"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestInstallGuideShowsEveryStepAndRecording(t *testing.T) {
|
|
code, body := get(t, Install("v1.2.3"), "http://llmgw.example/install", nil)
|
|
if code != http.StatusOK {
|
|
t.Fatalf("status %d", code)
|
|
}
|
|
for _, want := range []string{
|
|
"<!doctype html>",
|
|
`id="before"`, `id="binary"`, `id="setup"`, `id="check"`, `id="use"`, `id="trouble"`,
|
|
"sha256sum -c --ignore-missing",
|
|
"sudo llmgw server init",
|
|
`href="/about"`,
|
|
"/_static/guide/admin.png?v=",
|
|
// The recording's own screens, not a description of them.
|
|
"High availability", "All three are ready.", "The control plane is up",
|
|
} {
|
|
if !strings.Contains(body, want) {
|
|
t.Errorf("the guide does not have %q", want)
|
|
}
|
|
}
|
|
if n := strings.Count(body, `class="tty" data-src="/install/cast/`); n != len(guide.Casts) {
|
|
t.Errorf("%d players on the page, %d recordings", n, len(guide.Casts))
|
|
}
|
|
}
|
|
|
|
// The recording was of a real install, with a real administrator's password
|
|
// and a real key on screen. Neither may reach the page.
|
|
func TestInstallGuideCarriesNoSecret(t *testing.T) {
|
|
_, body := get(t, Install("v1.2.3"), "http://llmgw.example/install", nil)
|
|
for _, rx := range []*regexp.Regexp{
|
|
regexp.MustCompile(`sk-llmgw-[A-Za-z0-9_-]{8,}`),
|
|
regexp.MustCompile(`password(\s|<[^>]*>)+[a-z0-9]{16,}`),
|
|
} {
|
|
if m := rx.FindString(body); m != "" {
|
|
t.Errorf("the guide shows %q", m)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestEveryGuideStepHasItsScreen(t *testing.T) {
|
|
for _, name := range []string{"setup-start", "setup-choices", "setup-admin", "setup-machines",
|
|
"setup-checks", "setup-plan", "setup-install", "setup-done"} {
|
|
s, ok := guide.Stills[name]
|
|
if !ok || len(s.Lines) == 0 {
|
|
t.Errorf("no screen %s", name)
|
|
continue
|
|
}
|
|
if _, ok := guide.Casts[s.Cast]; !ok {
|
|
t.Errorf("screen %s is of a recording, %s, the guide does not have", name, s.Cast)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestInstallGuideAsTheWebsite(t *testing.T) {
|
|
d := NewInstallData("v1.2.3")
|
|
d.Site = true
|
|
var b bytes.Buffer
|
|
if err := RenderInstall(&b, d); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
body := b.String()
|
|
for _, not := range []string{"<!doctype", "</body>", "/_static/", `href="/about"`} {
|
|
if strings.Contains(body, not) {
|
|
t.Errorf("the website's guide has %q", not)
|
|
}
|
|
}
|
|
if !strings.Contains(body, `src="guide/admin.png"`) {
|
|
t.Error("the website's guide does not take its pictures from beside it")
|
|
}
|
|
}
|
|
|
|
func TestTheRecordingsAreServedCompressed(t *testing.T) {
|
|
r := httptest.NewRequest("GET", "http://llmgw.example/install/cast/2-setup.json", nil)
|
|
r.SetPathValue("file", "2-setup.json")
|
|
r.Header.Set("Accept-Encoding", "gzip, br")
|
|
w := httptest.NewRecorder()
|
|
InstallCast(w, r)
|
|
if w.Code != http.StatusOK || w.Header().Get("Content-Encoding") != "gzip" {
|
|
t.Fatalf("%d %v", w.Code, w.Header())
|
|
}
|
|
// And plain to a client that does not take gzip.
|
|
r = httptest.NewRequest("GET", "http://llmgw.example/install/cast/2-setup.json", nil)
|
|
r.SetPathValue("file", "2-setup.json")
|
|
w = httptest.NewRecorder()
|
|
InstallCast(w, r)
|
|
if !strings.HasPrefix(w.Body.String(), `{"rows":`) {
|
|
t.Errorf("plain body starts %.20q", w.Body.String())
|
|
}
|
|
r = httptest.NewRequest("GET", "http://llmgw.example/install/cast/nothing.json", nil)
|
|
r.SetPathValue("file", "nothing.json")
|
|
w = httptest.NewRecorder()
|
|
InstallCast(w, r)
|
|
if w.Code != http.StatusNotFound {
|
|
t.Errorf("an unknown recording answered %d", w.Code)
|
|
}
|
|
}
|
|
|
|
func TestNoRecordingCarriesASecret(t *testing.T) {
|
|
// The page shows one screen of each; the player fetches every frame.
|
|
// Each frame as written, and as the player decodes it, is looked at.
|
|
secret := []*regexp.Regexp{
|
|
regexp.MustCompile(`sk-llmgw-[A-Za-z0-9_-]{8,}`),
|
|
regexp.MustCompile(`(?i)(password|passphrase|token)[^\n]{0,40}?[:=]\s*[A-Za-z0-9+/_-]{12,}`),
|
|
regexp.MustCompile(`-----BEGIN [A-Z ]*PRIVATE KEY-----`),
|
|
}
|
|
for name, c := range guide.Casts {
|
|
for i, f := range c.Frames {
|
|
var text string
|
|
_ = json.Unmarshal(f, &text)
|
|
for _, s := range []string{string(f), text} {
|
|
for _, rx := range secret {
|
|
if m := rx.FindString(s); m != "" {
|
|
t.Errorf("recording %s frame %d shows %q", name, i, m)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|