mirror of
https://github.com/git-pkgs/proxy.git
synced 2026-06-02 00:38:16 -04:00
Compare commits
1 commit
main
...
validate-p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
37cc7abfc7 |
6 changed files with 112 additions and 0 deletions
|
|
@ -140,6 +140,10 @@ type BulkResponse struct {
|
|||
func (h *APIHandler) HandlePackagePath(w http.ResponseWriter, r *http.Request) {
|
||||
ecosystem := chi.URLParam(r, "ecosystem")
|
||||
wildcard := chi.URLParam(r, "*")
|
||||
if err := validatePackagePath(wildcard); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
segments := splitWildcardPath(wildcard)
|
||||
|
||||
if ecosystem == "" || len(segments) == 0 {
|
||||
|
|
@ -274,6 +278,10 @@ func (h *APIHandler) getVersion(w http.ResponseWriter, r *http.Request, ecosyste
|
|||
func (h *APIHandler) HandleVulnsPath(w http.ResponseWriter, r *http.Request) {
|
||||
ecosystem := chi.URLParam(r, "ecosystem")
|
||||
wildcard := chi.URLParam(r, "*")
|
||||
if err := validatePackagePath(wildcard); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
segments := splitWildcardPath(wildcard)
|
||||
|
||||
if ecosystem == "" || len(segments) == 0 {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/git-pkgs/proxy/internal/database"
|
||||
|
|
@ -48,6 +49,35 @@ func TestHandlePackagePath_MissingParams(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestHandlePackagePath_InvalidName(t *testing.T) {
|
||||
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
||||
svc := enrichment.New(logger)
|
||||
h := NewAPIHandler(svc, nil)
|
||||
|
||||
r := chi.NewRouter()
|
||||
r.Get("/api/package/{ecosystem}/*", h.HandlePackagePath)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
path string
|
||||
}{
|
||||
{"null byte", "/api/package/npm/lodash%00"},
|
||||
{"too long", "/api/package/npm/" + strings.Repeat("a", maxPackagePathLen+1)},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
req := httptest.NewRequest("GET", tt.path, nil)
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Errorf("expected status 400, got %d", w.Code)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleVulnsPath_MissingParams(t *testing.T) {
|
||||
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
||||
svc := enrichment.New(logger)
|
||||
|
|
|
|||
|
|
@ -133,6 +133,10 @@ type BrowseFileInfo struct {
|
|||
func (s *Server) handleBrowsePath(w http.ResponseWriter, r *http.Request) {
|
||||
ecosystem := chi.URLParam(r, "ecosystem")
|
||||
wildcard := chi.URLParam(r, "*")
|
||||
if err := validatePackagePath(wildcard); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
segments := splitWildcardPath(wildcard)
|
||||
|
||||
if ecosystem == "" || len(segments) < 2 {
|
||||
|
|
@ -185,6 +189,10 @@ func (s *Server) handleBrowsePath(w http.ResponseWriter, r *http.Request) {
|
|||
func (s *Server) handleComparePath(w http.ResponseWriter, r *http.Request) {
|
||||
ecosystem := chi.URLParam(r, "ecosystem")
|
||||
wildcard := chi.URLParam(r, "*")
|
||||
if err := validatePackagePath(wildcard); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
segments := splitWildcardPath(wildcard)
|
||||
|
||||
if ecosystem == "" || len(segments) < 3 {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,39 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/git-pkgs/proxy/internal/database"
|
||||
)
|
||||
|
||||
// maxPackagePathLen bounds the wildcard portion of package routes (name plus
|
||||
// version and any suffix). npm caps names at 214 and Maven coordinates can be
|
||||
// longer, so 512 leaves room without admitting pathological inputs.
|
||||
const maxPackagePathLen = 512
|
||||
|
||||
// validatePackagePath rejects wildcard package paths that cannot be valid in
|
||||
// any supported ecosystem. It is a coarse filter applied before database or
|
||||
// enrichment lookups; ecosystem-specific name rules are layered on top.
|
||||
func validatePackagePath(path string) error {
|
||||
if path == "" {
|
||||
return fmt.Errorf("package name required")
|
||||
}
|
||||
if len(path) > maxPackagePathLen {
|
||||
return fmt.Errorf("package path exceeds %d bytes", maxPackagePathLen)
|
||||
}
|
||||
for _, r := range path {
|
||||
if r == 0 {
|
||||
return fmt.Errorf("package path contains null byte")
|
||||
}
|
||||
if unicode.IsControl(r) {
|
||||
return fmt.Errorf("package path contains control character %#U", r)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// resolvePackageName determines the package name from a wildcard path by
|
||||
// checking the database. This handles namespaced packages like Composer's
|
||||
// vendor/name format where the package name contains a slash.
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package server
|
|||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/git-pkgs/proxy/internal/database"
|
||||
|
|
@ -118,3 +119,36 @@ func TestSplitWildcardPath(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidatePackagePath(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
path string
|
||||
wantErr bool
|
||||
}{
|
||||
{"simple", "lodash", false},
|
||||
{"with version", "lodash/4.17.21", false},
|
||||
{"npm scoped", "@babel/core/7.0.0", false},
|
||||
{"composer namespaced", "symfony/console/6.0.0", false},
|
||||
{"maven coordinates", "org.apache.commons/commons-lang3/3.12.0", false},
|
||||
{"unicode", "café/1.0.0", false},
|
||||
{"empty", "", true},
|
||||
{"null byte", "lodash\x00/4.17.21", true},
|
||||
{"null byte suffix", "lodash\x00", true},
|
||||
{"newline", "lodash\n4.17.21", true},
|
||||
{"carriage return", "lodash\r", true},
|
||||
{"escape", "lodash\x1b[31m", true},
|
||||
{"delete", "lodash\x7f", true},
|
||||
{"too long", strings.Repeat("a", maxPackagePathLen+1), true},
|
||||
{"at limit", strings.Repeat("a", maxPackagePathLen), false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := validatePackagePath(tt.path)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("validatePackagePath(%q) error = %v, wantErr %v", tt.path, err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -615,6 +615,10 @@ func (s *Server) handlePackagesList(w http.ResponseWriter, r *http.Request) {
|
|||
func (s *Server) handlePackagePath(w http.ResponseWriter, r *http.Request) {
|
||||
ecosystem := chi.URLParam(r, "ecosystem")
|
||||
wildcard := chi.URLParam(r, "*")
|
||||
if err := validatePackagePath(wildcard); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
segments := splitWildcardPath(wildcard)
|
||||
|
||||
if ecosystem == "" || len(segments) == 0 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue