HH-438: close upload and remote URL attack surfaces (#89)

* HH-438: close upload and remote URL attack surfaces

* HH-438: use valid PNG in direct upload test

* HH-438: align PostgreSQL upload staging schema

* HH-438: run upload migrations before PostgreSQL E2E

---------

Co-authored-by: Rogee <rogee@ipao.vip>
This commit is contained in:
Rogee
2026-08-21 20:36:52 +08:00
committed by GitHub
co-authored by rogee
parent 7a9fec33c5
commit 77c91662d2
18 changed files with 1311 additions and 259 deletions
@@ -3,6 +3,7 @@ package widget
import (
"bytes"
"context"
"encoding/json"
"mime/multipart"
"net/http"
"net/http/httptest"
@@ -340,6 +341,43 @@ func (s *WidgetThemeHandlerTestSuite) TestStageFileUpload_InvalidWebsiteToken()
assert.Equal(s.T(), http.StatusBadRequest, w.Code)
}
func (s *WidgetThemeHandlerTestSuite) TestStageAndGetFileUpload_RequiresMatchingSession() {
account, inbox := s.seedAccountAndWidgetInbox()
s.Require().NoError(s.db.Model(inbox).Update("channel_config", `{"website_token":"secure_upload_token"}`).Error)
contact := &model.Contact{AccountID: account.ID, Name: "Secure Uploader"}
s.Require().NoError(s.db.Create(contact).Error)
s.Require().NoError(s.db.Create(&model.ContactInbox{ContactID: contact.ID, InboxID: inbox.ID, PubsubToken: "secure_widget_session"}).Error)
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
part, err := writer.CreateFormFile("file", "secure.png")
s.Require().NoError(err)
_, err = part.Write([]byte{0x89, 'P', 'N', 'G', '\r', '\n', 0x1a, '\n', 0, 0, 0, 0})
s.Require().NoError(err)
s.Require().NoError(writer.Close())
req := httptest.NewRequest(http.MethodPost, "/widget/secure_upload_token/uploads", body)
req.Header.Set("Content-Type", writer.FormDataContentType())
req.Header.Set("X-Auth-Token", "secure_widget_session")
w := httptest.NewRecorder()
s.router.ServeHTTP(w, req)
s.Require().Equal(http.StatusCreated, w.Code)
var upload map[string]any
s.Require().NoError(json.Unmarshal(w.Body.Bytes(), &upload))
uuid, _ := upload["upload_uuid"].(string)
s.Require().NotEmpty(uuid)
statusReq := httptest.NewRequest(http.MethodGet, "/widget/secure_upload_token/uploads/"+uuid, nil)
statusReq.Header.Set("X-Auth-Token", "secure_widget_session")
statusW := httptest.NewRecorder()
s.router.ServeHTTP(statusW, statusReq)
s.Require().Equal(http.StatusOK, statusW.Code)
deniedReq := httptest.NewRequest(http.MethodGet, "/widget/secure_upload_token/uploads/"+uuid, nil)
deniedW := httptest.NewRecorder()
s.router.ServeHTTP(deniedW, deniedReq)
s.Require().Equal(http.StatusBadRequest, deniedW.Code)
}
func (s *WidgetThemeHandlerTestSuite) TestGetFileUploadStatus_MissingUploadUUID() {
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/widget/test_token/uploads/", nil)