security(skills): validate GitHub repo format and limit download size
Add regex validation for 'owner/repo' format to prevent URL injection in InstallFromGitHub. Add 5 MB LimitReader on skill file downloads to prevent memory exhaustion. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
b391e649d2
commit
48b08110de
1 changed files with 10 additions and 1 deletions
|
|
@ -7,12 +7,17 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sipeed/picoclaw/pkg/fileutil"
|
"github.com/sipeed/picoclaw/pkg/fileutil"
|
||||||
"github.com/sipeed/picoclaw/pkg/utils"
|
"github.com/sipeed/picoclaw/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const maxSkillFileSize int64 = 5 << 20 // 5 MB — SKILL.md files should never approach this
|
||||||
|
|
||||||
|
var repoPattern = regexp.MustCompile(`^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+$`)
|
||||||
|
|
||||||
type SkillInstaller struct {
|
type SkillInstaller struct {
|
||||||
workspace string
|
workspace string
|
||||||
}
|
}
|
||||||
|
|
@ -24,6 +29,10 @@ func NewSkillInstaller(workspace string) *SkillInstaller {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (si *SkillInstaller) InstallFromGitHub(ctx context.Context, repo string) error {
|
func (si *SkillInstaller) InstallFromGitHub(ctx context.Context, repo string) error {
|
||||||
|
if !repoPattern.MatchString(repo) {
|
||||||
|
return fmt.Errorf("invalid repository format %q: must be 'owner/repo'", repo)
|
||||||
|
}
|
||||||
|
|
||||||
skillDir := filepath.Join(si.workspace, "skills", filepath.Base(repo))
|
skillDir := filepath.Join(si.workspace, "skills", filepath.Base(repo))
|
||||||
|
|
||||||
if _, err := os.Stat(skillDir); err == nil {
|
if _, err := os.Stat(skillDir); err == nil {
|
||||||
|
|
@ -48,7 +57,7 @@ func (si *SkillInstaller) InstallFromGitHub(ctx context.Context, repo string) er
|
||||||
return fmt.Errorf("failed to fetch skill: HTTP %d", resp.StatusCode)
|
return fmt.Errorf("failed to fetch skill: HTTP %d", resp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
body, err := io.ReadAll(resp.Body)
|
body, err := io.ReadAll(io.LimitReader(resp.Body, maxSkillFileSize))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to read response: %w", err)
|
return fmt.Errorf("failed to read response: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue