Skip to content

Commit 0ff9245

Browse files
committed
Allow direct IP webui sessions
1 parent 2d73d76 commit 0ff9245

5 files changed

Lines changed: 63 additions & 3 deletions

File tree

cmd/workspace/embedkeep.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
embedded workspace placeholder

pkg/api/server.go

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,25 @@ func requestOrigin(r *http.Request) string {
343343
return scheme + "://" + canonicalOriginHost(r.Host, scheme == "https")
344344
}
345345

346+
func sameSiteOrigin(originA, originB string) bool {
347+
parsedA, err := url.Parse(strings.TrimSpace(originA))
348+
if err != nil || parsedA == nil {
349+
return false
350+
}
351+
parsedB, err := url.Parse(strings.TrimSpace(originB))
352+
if err != nil || parsedB == nil {
353+
return false
354+
}
355+
schemeA := strings.ToLower(strings.TrimSpace(parsedA.Scheme))
356+
schemeB := strings.ToLower(strings.TrimSpace(parsedB.Scheme))
357+
if schemeA == "" || schemeB == "" || schemeA != schemeB {
358+
return false
359+
}
360+
hostA := strings.ToLower(strings.TrimSpace(parsedA.Hostname()))
361+
hostB := strings.ToLower(strings.TrimSpace(parsedB.Hostname()))
362+
return hostA != "" && hostA == hostB
363+
}
364+
346365
func (s *Server) isTrustedOrigin(r *http.Request) bool {
347366
if r == nil {
348367
return false
@@ -360,7 +379,17 @@ func (s *Server) isTrustedOrigin(r *http.Request) bool {
360379

361380
func (s *Server) shouldUseCrossSiteCookie(r *http.Request) bool {
362381
origin := normalizeOrigin(r.Header.Get("Origin"))
363-
return origin != "" && origin != requestOrigin(r) && s.isTrustedOrigin(r)
382+
if origin == "" || !s.isTrustedOrigin(r) {
383+
return false
384+
}
385+
reqOrigin := requestOrigin(r)
386+
if origin == reqOrigin {
387+
return false
388+
}
389+
if sameSiteOrigin(origin, reqOrigin) {
390+
return false
391+
}
392+
return true
364393
}
365394

366395
func (s *Server) websocketUpgrader() *websocket.Upgrader {

pkg/api/server_node_artifacts_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -209,9 +209,10 @@ func TestHandleWebUINodeArtifactsAppliesRetentionConfig(t *testing.T) {
209209
t.Fatalf("save config: %v", err)
210210
}
211211
srv.SetConfigPath(cfgPath)
212+
base := time.Now().UTC().Add(-2 * time.Hour)
212213
auditLines := strings.Join([]string{
213-
"{\"time\":\"2026-03-09T00:00:00Z\",\"node\":\"edge-a\",\"action\":\"screen_snapshot\",\"ok\":true,\"artifacts\":[{\"name\":\"one.txt\",\"kind\":\"text\",\"mime_type\":\"text/plain\",\"content_base64\":\"b25l\"}]}",
214-
"{\"time\":\"2026-03-09T00:01:00Z\",\"node\":\"edge-a\",\"action\":\"screen_snapshot\",\"ok\":true,\"artifacts\":[{\"name\":\"two.txt\",\"kind\":\"text\",\"mime_type\":\"text/plain\",\"content_base64\":\"dHdv\"}]}",
214+
fmt.Sprintf("{\"time\":%q,\"node\":\"edge-a\",\"action\":\"screen_snapshot\",\"ok\":true,\"artifacts\":[{\"name\":\"one.txt\",\"kind\":\"text\",\"mime_type\":\"text/plain\",\"content_base64\":\"b25l\"}]}", base.Format(time.RFC3339)),
215+
fmt.Sprintf("{\"time\":%q,\"node\":\"edge-a\",\"action\":\"screen_snapshot\",\"ok\":true,\"artifacts\":[{\"name\":\"two.txt\",\"kind\":\"text\",\"mime_type\":\"text/plain\",\"content_base64\":\"dHdv\"}]}", base.Add(time.Minute).Format(time.RFC3339)),
215216
}, "\n") + "\n"
216217
if err := os.WriteFile(filepath.Join(workspace, "memory", "nodes-dispatch-audit.jsonl"), []byte(auditLines), 0o644); err != nil {
217218
t.Fatalf("write audit: %v", err)

pkg/api/server_security_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,34 @@ func TestHandleWebUIAuthSessionSetsCrossSiteCookieForAllowedOrigin(t *testing.T)
197197
}
198198
}
199199

200+
func TestHandleWebUIAuthSessionKeepsLaxCookieForSameIPDifferentPort(t *testing.T) {
201+
t.Parallel()
202+
203+
srv := NewServer("0.0.0.0", 0, "secret-token", nil)
204+
205+
req := httptest.NewRequest(http.MethodPost, "http://134.195.210.114:18790/api/auth/session", nil)
206+
req.Host = "134.195.210.114:18790"
207+
req.Header.Set("Origin", "http://134.195.210.114:3000")
208+
req.Header.Set("Authorization", "Bearer secret-token")
209+
rec := httptest.NewRecorder()
210+
211+
srv.withCORS(http.HandlerFunc(srv.handleWebUIAuthSession)).ServeHTTP(rec, req)
212+
213+
if rec.Code != http.StatusOK {
214+
t.Fatalf("expected 200, got %d: %s", rec.Code, rec.Body.String())
215+
}
216+
cookies := rec.Result().Cookies()
217+
if len(cookies) != 1 {
218+
t.Fatalf("expected one cookie, got %d", len(cookies))
219+
}
220+
if cookies[0].SameSite != http.SameSiteLaxMode {
221+
t.Fatalf("expected SameSite=Lax for same-IP direct session, got %v", cookies[0].SameSite)
222+
}
223+
if cookies[0].Secure {
224+
t.Fatalf("expected non-secure cookie for plain HTTP direct IP session")
225+
}
226+
}
227+
200228
func TestHandleWebUIUploadDoesNotExposeAbsolutePath(t *testing.T) {
201229
t.Parallel()
202230

workspace/embedkeep.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
workspace assets placeholder

0 commit comments

Comments
 (0)