mirror of
https://github.com/safedep/vet.git
synced 2025-12-11 01:01:10 -06:00
OpenVSX extensions scanning support (#536)
* feat(readers): Add OpenVSX ecosystem support * refactor: use better naming conventions * refactor: improve extensions reader with structured config
This commit is contained in:
parent
c3d96dbef5
commit
06988f9b33
2
go.mod
2
go.mod
@ -4,7 +4,7 @@ go 1.24.3
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
buf.build/gen/go/safedep/api/grpc/go v1.5.1-20250610075857-7cfdb61a0bfa.2
|
buf.build/gen/go/safedep/api/grpc/go v1.5.1-20250610075857-7cfdb61a0bfa.2
|
||||||
buf.build/gen/go/safedep/api/protocolbuffers/go v1.36.6-20250610075857-7cfdb61a0bfa.1
|
buf.build/gen/go/safedep/api/protocolbuffers/go v1.36.6-20250705071048-7ad8e6be7c05.1
|
||||||
entgo.io/ent v0.14.4
|
entgo.io/ent v0.14.4
|
||||||
github.com/AlecAivazis/survey/v2 v2.3.7
|
github.com/AlecAivazis/survey/v2 v2.3.7
|
||||||
github.com/BurntSushi/toml v1.5.0
|
github.com/BurntSushi/toml v1.5.0
|
||||||
|
|||||||
2
go.sum
2
go.sum
@ -10,6 +10,8 @@ buf.build/gen/go/safedep/api/grpc/go v1.5.1-20250610075857-7cfdb61a0bfa.2 h1:ENb
|
|||||||
buf.build/gen/go/safedep/api/grpc/go v1.5.1-20250610075857-7cfdb61a0bfa.2/go.mod h1:WDOWZglnweQ4njVEJpLYYpLMx9fD+e94KbKdt8oJrxY=
|
buf.build/gen/go/safedep/api/grpc/go v1.5.1-20250610075857-7cfdb61a0bfa.2/go.mod h1:WDOWZglnweQ4njVEJpLYYpLMx9fD+e94KbKdt8oJrxY=
|
||||||
buf.build/gen/go/safedep/api/protocolbuffers/go v1.36.6-20250610075857-7cfdb61a0bfa.1 h1:wOZtKj81Wq5fvHf4STR0vxEl8/peoEJkRzuQI+zwE2I=
|
buf.build/gen/go/safedep/api/protocolbuffers/go v1.36.6-20250610075857-7cfdb61a0bfa.1 h1:wOZtKj81Wq5fvHf4STR0vxEl8/peoEJkRzuQI+zwE2I=
|
||||||
buf.build/gen/go/safedep/api/protocolbuffers/go v1.36.6-20250610075857-7cfdb61a0bfa.1/go.mod h1:uR95GqsnNCRn6cTyRBte6uMJMm0rEBRxTGpakKCNL9I=
|
buf.build/gen/go/safedep/api/protocolbuffers/go v1.36.6-20250610075857-7cfdb61a0bfa.1/go.mod h1:uR95GqsnNCRn6cTyRBte6uMJMm0rEBRxTGpakKCNL9I=
|
||||||
|
buf.build/gen/go/safedep/api/protocolbuffers/go v1.36.6-20250705071048-7ad8e6be7c05.1 h1:4sM5O5dx0yUucJ1trjZ8Cm9IGX2loEc4cUyh3Xy+5eU=
|
||||||
|
buf.build/gen/go/safedep/api/protocolbuffers/go v1.36.6-20250705071048-7ad8e6be7c05.1/go.mod h1:uR95GqsnNCRn6cTyRBte6uMJMm0rEBRxTGpakKCNL9I=
|
||||||
cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=
|
cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=
|
||||||
cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
|
cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
|
||||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
|||||||
@ -35,6 +35,7 @@ const (
|
|||||||
EcosystemTerraformModule = "TerraformModule"
|
EcosystemTerraformModule = "TerraformModule"
|
||||||
EcosystemTerraformProvider = "TerraformProvider"
|
EcosystemTerraformProvider = "TerraformProvider"
|
||||||
EcosystemVSCodeExtensions = "VSCodeExtensions"
|
EcosystemVSCodeExtensions = "VSCodeExtensions"
|
||||||
|
EcosystemOpenVSXExtensions = "OpenVSXExtensions"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ManifestSourceType string
|
type ManifestSourceType string
|
||||||
@ -240,6 +241,8 @@ func (pm *PackageManifest) GetControlTowerSpecEcosystem() packagev1.Ecosystem {
|
|||||||
return packagev1.Ecosystem_ECOSYSTEM_TERRAFORM_PROVIDER
|
return packagev1.Ecosystem_ECOSYSTEM_TERRAFORM_PROVIDER
|
||||||
case EcosystemVSCodeExtensions:
|
case EcosystemVSCodeExtensions:
|
||||||
return packagev1.Ecosystem_ECOSYSTEM_VSCODE
|
return packagev1.Ecosystem_ECOSYSTEM_VSCODE
|
||||||
|
case EcosystemOpenVSXExtensions:
|
||||||
|
return packagev1.Ecosystem_ECOSYSTEM_OPENVSX
|
||||||
default:
|
default:
|
||||||
return packagev1.Ecosystem_ECOSYSTEM_UNSPECIFIED
|
return packagev1.Ecosystem_ECOSYSTEM_UNSPECIFIED
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,128 +0,0 @@
|
|||||||
package readers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/safedep/vet/pkg/common/logger"
|
|
||||||
"github.com/safedep/vet/pkg/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
vsCodeExtensionExtensionsFileName = "extensions.json"
|
|
||||||
)
|
|
||||||
|
|
||||||
type vsCodeExtensionIdentifier struct {
|
|
||||||
Id string `json:"id"`
|
|
||||||
Uuid string `json:"uuid"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type vsCodeExtensionLocation struct {
|
|
||||||
Path string `json:"path"`
|
|
||||||
Scheme string `json:"scheme"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type vsCodeExtension struct {
|
|
||||||
Identifier vsCodeExtensionIdentifier `json:"identifier"`
|
|
||||||
Version string `json:"version"`
|
|
||||||
Location vsCodeExtensionLocation `json:"location"`
|
|
||||||
RelativeLocation string `json:"relativeLocation"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type vsCodeExtensionList struct {
|
|
||||||
Extensions []vsCodeExtension
|
|
||||||
}
|
|
||||||
|
|
||||||
type vscodeExtReader struct {
|
|
||||||
distributionHomeDir map[string]string
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ PackageManifestReader = (*vscodeExtReader)(nil)
|
|
||||||
|
|
||||||
func NewVSCodeExtReader(distributions []string) (*vscodeExtReader, error) {
|
|
||||||
customDistributions := make(map[string]string)
|
|
||||||
for i, distribution := range distributions {
|
|
||||||
customDistributions[fmt.Sprintf("custom-%d", i)] = distribution
|
|
||||||
}
|
|
||||||
|
|
||||||
return newVSCodeExtReaderFromDistributions(customDistributions)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewVSCodeExtReaderFromDefaultDistributions() (*vscodeExtReader, error) {
|
|
||||||
homeDir, err := os.UserHomeDir()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get user home directory: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
distributionHomeDir := map[string]string{
|
|
||||||
"code": filepath.Join(homeDir, ".vscode", "extensions"),
|
|
||||||
"cursor": filepath.Join(homeDir, ".cursor", "extensions"),
|
|
||||||
}
|
|
||||||
|
|
||||||
return &vscodeExtReader{distributionHomeDir: distributionHomeDir}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newVSCodeExtReaderFromDistributions(d map[string]string) (*vscodeExtReader, error) {
|
|
||||||
return &vscodeExtReader{distributionHomeDir: d}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *vscodeExtReader) Name() string {
|
|
||||||
return "VSCode Extensions Reader"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *vscodeExtReader) ApplicationName() (string, error) {
|
|
||||||
return "installed-vscode-extensions", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *vscodeExtReader) EnumManifests(handler func(*models.PackageManifest, PackageReader) error) error {
|
|
||||||
for distribution := range r.distributionHomeDir {
|
|
||||||
extensions, path, err := r.readExtensions(distribution)
|
|
||||||
if err != nil {
|
|
||||||
logger.Errorf("failed to read extensions for distribution %s: %v", distribution, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
manifest := models.NewPackageManifestFromLocal(path, models.EcosystemVSCodeExtensions)
|
|
||||||
for _, extension := range extensions.Extensions {
|
|
||||||
pkg := &models.Package{
|
|
||||||
PackageDetails: models.NewPackageDetail(models.EcosystemVSCodeExtensions, extension.Identifier.Id, extension.Version),
|
|
||||||
}
|
|
||||||
|
|
||||||
manifest.AddPackage(pkg)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = handler(manifest, NewManifestModelReader(manifest))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *vscodeExtReader) readExtensions(distribution string) (*vsCodeExtensionList, string, error) {
|
|
||||||
if _, ok := r.distributionHomeDir[distribution]; !ok {
|
|
||||||
return nil, "", fmt.Errorf("distribution %s not supported", distribution)
|
|
||||||
}
|
|
||||||
|
|
||||||
extensionsFile := filepath.Join(r.distributionHomeDir[distribution], vsCodeExtensionExtensionsFileName)
|
|
||||||
if _, err := os.Stat(extensionsFile); os.IsNotExist(err) {
|
|
||||||
return nil, "", fmt.Errorf("extensions file does not exist: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
file, err := os.Open(extensionsFile)
|
|
||||||
if err != nil {
|
|
||||||
return nil, "", fmt.Errorf("failed to open extensions file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
var extensions vsCodeExtensionList
|
|
||||||
if err := json.NewDecoder(file).Decode(&extensions.Extensions); err != nil {
|
|
||||||
return nil, "", fmt.Errorf("failed to decode extensions file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &extensions, extensionsFile, nil
|
|
||||||
}
|
|
||||||
169
pkg/readers/vsix_ext_reader.go
Normal file
169
pkg/readers/vsix_ext_reader.go
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
package readers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/safedep/vet/pkg/common/logger"
|
||||||
|
"github.com/safedep/vet/pkg/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
vsCodeExtensionExtensionsFileName = "extensions.json"
|
||||||
|
)
|
||||||
|
|
||||||
|
var editors = map[string]distributionInfo{
|
||||||
|
"code": {
|
||||||
|
FilePath: ".vscode/extensions",
|
||||||
|
Ecosystem: models.EcosystemVSCodeExtensions,
|
||||||
|
},
|
||||||
|
"vscodium": {
|
||||||
|
FilePath: ".vscode-oss/extensions",
|
||||||
|
Ecosystem: models.EcosystemOpenVSXExtensions,
|
||||||
|
},
|
||||||
|
"cursor": {
|
||||||
|
FilePath: ".cursor/extensions",
|
||||||
|
Ecosystem: models.EcosystemOpenVSXExtensions,
|
||||||
|
},
|
||||||
|
"windsurf": {
|
||||||
|
FilePath: ".windsurf/extensions",
|
||||||
|
Ecosystem: models.EcosystemOpenVSXExtensions,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
type vsCodeExtensionIdentifier struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
Uuid string `json:"uuid"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type vsCodeExtensionLocation struct {
|
||||||
|
Path string `json:"path"`
|
||||||
|
Scheme string `json:"scheme"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type vsCodeExtension struct {
|
||||||
|
Identifier vsCodeExtensionIdentifier `json:"identifier"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
Location vsCodeExtensionLocation `json:"location"`
|
||||||
|
RelativeLocation string `json:"relativeLocation"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type vsCodeExtensionList struct {
|
||||||
|
Extensions []vsCodeExtension
|
||||||
|
}
|
||||||
|
|
||||||
|
type distributionInfo struct {
|
||||||
|
FilePath string // Path to the extensions directory
|
||||||
|
Ecosystem string // Type of extension marketplace (VSCode or OpenVSX)
|
||||||
|
}
|
||||||
|
|
||||||
|
type vsixExtReader struct {
|
||||||
|
distributions map[string]distributionInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ PackageManifestReader = (*vsixExtReader)(nil)
|
||||||
|
|
||||||
|
func NewVSIXExtReader(distributions []string) (*vsixExtReader, error) {
|
||||||
|
customDistributions := make(map[string]distributionInfo)
|
||||||
|
ecosystem := models.EcosystemVSCodeExtensions
|
||||||
|
|
||||||
|
for i, distribution := range distributions {
|
||||||
|
for _, eco := range editors {
|
||||||
|
if strings.Contains(distribution, eco.FilePath) {
|
||||||
|
ecosystem = eco.Ecosystem
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
customDistributions[fmt.Sprintf("custom-%d", i)] = distributionInfo{
|
||||||
|
FilePath: distribution,
|
||||||
|
Ecosystem: ecosystem,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newVSCodeExtReaderFromDistributions(customDistributions)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewVSIXExtReaderFromDefaultDistributions() (*vsixExtReader, error) {
|
||||||
|
homeDir, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get user home directory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
distributions := make(map[string]distributionInfo)
|
||||||
|
for editorName, config := range editors {
|
||||||
|
distributions[editorName] = distributionInfo{
|
||||||
|
FilePath: filepath.Join(homeDir, config.FilePath),
|
||||||
|
Ecosystem: config.Ecosystem,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &vsixExtReader{distributions: distributions}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newVSCodeExtReaderFromDistributions(d map[string]distributionInfo) (*vsixExtReader, error) {
|
||||||
|
return &vsixExtReader{distributions: d}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *vsixExtReader) Name() string {
|
||||||
|
return "VSIX Extensions Reader"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *vsixExtReader) ApplicationName() (string, error) {
|
||||||
|
return "installed-vsix-extensions", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *vsixExtReader) EnumManifests(handler func(*models.PackageManifest, PackageReader) error) error {
|
||||||
|
for distribution := range r.distributions {
|
||||||
|
extensions, path, err := r.readExtensions(distribution)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("failed to read extensions for distribution %s: %v", distribution, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
info := r.distributions[distribution]
|
||||||
|
manifest := models.NewPackageManifestFromLocal(path, info.Ecosystem)
|
||||||
|
for _, extension := range extensions.Extensions {
|
||||||
|
pkg := &models.Package{
|
||||||
|
PackageDetails: models.NewPackageDetail(info.Ecosystem, extension.Identifier.Id, extension.Version),
|
||||||
|
}
|
||||||
|
|
||||||
|
manifest.AddPackage(pkg)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = handler(manifest, NewManifestModelReader(manifest))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *vsixExtReader) readExtensions(distribution string) (*vsCodeExtensionList, string, error) {
|
||||||
|
info, ok := r.distributions[distribution]
|
||||||
|
if !ok {
|
||||||
|
return nil, "", fmt.Errorf("distribution %s not supported", distribution)
|
||||||
|
}
|
||||||
|
|
||||||
|
extensionsFile := filepath.Join(info.FilePath, vsCodeExtensionExtensionsFileName)
|
||||||
|
if _, err := os.Stat(extensionsFile); os.IsNotExist(err) {
|
||||||
|
return nil, "", fmt.Errorf("extensions file does not exist: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.Open(extensionsFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", fmt.Errorf("failed to open extensions file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
var extensions vsCodeExtensionList
|
||||||
|
if err := json.NewDecoder(file).Decode(&extensions.Extensions); err != nil {
|
||||||
|
return nil, "", fmt.Errorf("failed to decode extensions file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &extensions, extensionsFile, nil
|
||||||
|
}
|
||||||
@ -8,13 +8,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestVSCodeExtReaderInit(t *testing.T) {
|
func TestVSCodeExtReaderInit(t *testing.T) {
|
||||||
reader, err := NewVSCodeExtReaderFromDefaultDistributions()
|
reader, err := NewVSIXExtReaderFromDefaultDistributions()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, reader)
|
assert.NotNil(t, reader)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestVSCodeExtReaderEnumManifests(t *testing.T) {
|
func TestVSCodeExtReaderEnumManifests(t *testing.T) {
|
||||||
reader, err := NewVSCodeExtReader([]string{"./fixtures/vsx"})
|
reader, err := NewVSIXExtReader([]string{"./fixtures/vsx"})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, reader)
|
assert.NotNil(t, reader)
|
||||||
|
|
||||||
4
scan.go
4
scan.go
@ -396,12 +396,12 @@ func internalStartScan() error {
|
|||||||
analytics.TrackCommandScanVSCodeExtScan()
|
analytics.TrackCommandScanVSCodeExtScan()
|
||||||
|
|
||||||
// nolint:ineffassign,staticcheck
|
// nolint:ineffassign,staticcheck
|
||||||
reader, err = readers.NewVSCodeExtReaderFromDefaultDistributions()
|
reader, err = readers.NewVSIXExtReaderFromDefaultDistributions()
|
||||||
} else {
|
} else {
|
||||||
analytics.TrackCommandScanVSCodeExtScan()
|
analytics.TrackCommandScanVSCodeExtScan()
|
||||||
|
|
||||||
// nolint:ineffassign,staticcheck
|
// nolint:ineffassign,staticcheck
|
||||||
reader, err = readers.NewVSCodeExtReader(vsxDirectories)
|
reader, err = readers.NewVSIXExtReader(vsxDirectories)
|
||||||
}
|
}
|
||||||
} else if len(scanImageTarget) != 0 {
|
} else if len(scanImageTarget) != 0 {
|
||||||
analytics.TrackCommandImageScan()
|
analytics.TrackCommandImageScan()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user