Harden marketplace install inputs
This commit is contained in:
parent
d54a57e158
commit
b87a0da3ba
3 changed files with 68 additions and 5 deletions
|
|
@ -124,7 +124,9 @@ func TestMarketplace_registerMarketplaceActions_Good(t *testing.T) {
|
|||
installed, ok := installResult.Value.(map[string]any)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, installDir, installed["install_dir"])
|
||||
assert.Equal(t, filepath.Join(installDir, "core-ui"), installed["target_dir"])
|
||||
resolvedInstallDir, err := filepath.EvalSymlinks(installDir)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, filepath.Join(resolvedInstallDir, "core-ui"), installed["target_dir"])
|
||||
|
||||
contents, err := os.ReadFile(gitLog)
|
||||
require.NoError(t, err)
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
|
@ -130,7 +131,7 @@ func (i Installer) Install(ctx context.Context, manifest Manifest) (string, erro
|
|||
if err := validateManifestName(manifest.Name); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := validateCloneArg("repository", manifest.Repository); err != nil {
|
||||
if err := validateRepositorySource(manifest.Repository); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := validateCloneArgOptional("ref", manifest.Ref); err != nil {
|
||||
|
|
@ -139,16 +140,20 @@ func (i Installer) Install(ctx context.Context, manifest Manifest) (string, erro
|
|||
if err := os.MkdirAll(i.InstallDir, 0o755); err != nil {
|
||||
return "", err
|
||||
}
|
||||
targetDir := filepath.Join(i.InstallDir, safeName(manifest.Name))
|
||||
rootAbs, err := filepath.Abs(i.InstallDir)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
rootResolved, err := filepath.EvalSymlinks(rootAbs)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
targetDir := filepath.Join(rootResolved, safeName(manifest.Name))
|
||||
targetAbs, err := filepath.Abs(targetDir)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
rel, err := filepath.Rel(rootAbs, targetAbs)
|
||||
rel, err := filepath.Rel(rootResolved, targetAbs)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
|
@ -238,6 +243,41 @@ func validateCloneArgOptional(label, value string) error {
|
|||
return validateCloneArg(label, trimmed)
|
||||
}
|
||||
|
||||
func validateRepositorySource(value string) error {
|
||||
trimmed := strings.TrimSpace(value)
|
||||
if trimmed == "" {
|
||||
return errors.New("repository is required")
|
||||
}
|
||||
if strings.ContainsAny(trimmed, "\x00\r\n") {
|
||||
return errors.New("repository contains invalid control characters")
|
||||
}
|
||||
if strings.HasPrefix(strings.ToLower(trimmed), "ext::") {
|
||||
return errors.New("repository must not use git remote helper protocols")
|
||||
}
|
||||
if strings.HasPrefix(trimmed, "-") {
|
||||
return errors.New("repository must not begin with a dash")
|
||||
}
|
||||
if strings.Contains(trimmed, "://") {
|
||||
parsed, err := url.Parse(trimmed)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch strings.ToLower(parsed.Scheme) {
|
||||
case "http", "https", "ssh", "git":
|
||||
default:
|
||||
return fmt.Errorf("repository scheme %q is not allowed", parsed.Scheme)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if strings.Contains(trimmed, string(filepath.Separator)) || filepath.IsAbs(trimmed) {
|
||||
return errors.New("repository path clones are not allowed")
|
||||
}
|
||||
if !strings.Contains(trimmed, ":") {
|
||||
return errors.New("repository must be a URL or scp-style remote")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func DigestManifest(manifest Manifest) string {
|
||||
hash := sha256.Sum256([]byte(manifest.Name + ":" + manifest.Version + ":" + manifest.Repository + ":" + manifest.Ref))
|
||||
return hex.EncodeToString(hash[:])
|
||||
|
|
|
|||
|
|
@ -185,7 +185,9 @@ func TestMarketplace_Install_Good(t *testing.T) {
|
|||
Ref: "main",
|
||||
}))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, filepath.Join(targetRoot, "core-ui"), targetDir)
|
||||
resolvedRoot, err := filepath.EvalSymlinks(targetRoot)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, filepath.Join(resolvedRoot, "core-ui"), targetDir)
|
||||
_, err = os.Stat(targetDir)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
|
@ -366,6 +368,25 @@ func TestMarketplace_validateCloneArg_Ugly(t *testing.T) {
|
|||
assert.Contains(t, err.Error(), "invalid control characters")
|
||||
}
|
||||
|
||||
func TestMarketplace_validateRepositorySource_Good(t *testing.T) {
|
||||
require.NoError(t, validateRepositorySource("https://example.com/core-ui.git"))
|
||||
require.NoError(t, validateRepositorySource("git@example.com:core-ui.git"))
|
||||
}
|
||||
|
||||
func TestMarketplace_validateRepositorySource_Bad(t *testing.T) {
|
||||
err := validateRepositorySource("ext::sh -c id")
|
||||
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "remote helper")
|
||||
}
|
||||
|
||||
func TestMarketplace_validateRepositorySource_Ugly(t *testing.T) {
|
||||
err := validateRepositorySource("file:///tmp/core-ui.git")
|
||||
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "not allowed")
|
||||
}
|
||||
|
||||
func TestMarketplace_decodeManifestList_Good(t *testing.T) {
|
||||
manifests, err := decodeManifestList([]byte(`[{"name":"core-ui","version":"1.2.3"},{"name":"core-chat","version":"0.9.0"}]`))
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue