Files
tutti/packages/connector/runtime/node_package_installer.go
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

740 lines
28 KiB
Go

package runtime
import (
"context"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"sort"
"strings"
"sync"
"time"
agentruntime "github.com/tutti-os/tutti/packages/agent/daemon/runtime"
market "github.com/tutti-os/tutti/packages/connector/daemon/core"
"golang.org/x/mod/semver"
"gopkg.in/yaml.v3"
)
const (
cliInstallationReceiptSchema = "tutti.connector.cli-installation.v1"
cliInstallationReceiptFile = ".tutti-cli-installation.json"
defaultConnectorPnpmVersion = "10.11.0"
maxCLIInstallationOutput = 4 << 20
)
type NodePackageInstallerConfig struct {
RootDir string
Runtimes ConnectorRuntimeResolver
Processes agentruntime.ProcessTransport
PnpmVersion string
Timeout time.Duration
Environ func() []string
}
// NodePackageInstaller compiles validated node_package intents into pnpm
// invocations. All connectors share one content-addressed store and one
// Corepack cache while retaining a release-scoped node_modules link tree.
type NodePackageInstaller struct {
rootDir string
runtimes ConnectorRuntimeResolver
processes agentruntime.ProcessTransport
pnpmVersion string
timeout time.Duration
environ func() []string
mu sync.Mutex
connectorLanes map[string]*sync.Mutex
installSlots chan struct{}
}
var _ market.CLIInstallationManager = (*NodePackageInstaller)(nil)
func NewNodePackageInstaller(config NodePackageInstallerConfig) (*NodePackageInstaller, error) {
root := strings.TrimSpace(config.RootDir)
if !filepath.IsAbs(root) {
return nil, errors.New("connector node package root must be absolute")
}
if config.Runtimes == nil || config.Processes == nil {
return nil, errors.New("connector node package runtime and process transport are required")
}
pnpmVersion := strings.TrimSpace(config.PnpmVersion)
if pnpmVersion == "" {
pnpmVersion = defaultConnectorPnpmVersion
}
if !semver.IsValid("v" + pnpmVersion) {
return nil, errors.New("connector pnpm version must be exact semver")
}
timeout := config.Timeout
if timeout <= 0 {
timeout = 5 * time.Minute
}
environ := config.Environ
if environ == nil {
environ = os.Environ
}
return &NodePackageInstaller{rootDir: filepath.Clean(root), runtimes: config.Runtimes,
processes: config.Processes, pnpmVersion: pnpmVersion, timeout: timeout, environ: environ,
connectorLanes: make(map[string]*sync.Mutex), installSlots: make(chan struct{}, 4)}, nil
}
func (installer *NodePackageInstaller) InstallCLI(ctx context.Context, request market.InstallCLIRequest) (market.CLIInstallationReceipt, error) {
if installer == nil {
return market.CLIInstallationReceipt{}, errors.New("connector node package installer is unavailable")
}
if err := market.ValidateReleaseShape(request.Release); err != nil {
return market.CLIInstallationReceipt{}, err
}
managed, cli, nodePackage, err := nodePackageIntent(request.Release)
if err != nil {
return market.CLIInstallationReceipt{}, err
}
if strings.TrimSpace(request.OperationID) == "" {
return market.CLIInstallationReceipt{}, errors.New("connector CLI installation operation id is required")
}
releaseLane := installer.lockConnector(request.Release.ConnectorKey)
defer releaseLane()
select {
case installer.installSlots <- struct{}{}:
defer func() { <-installer.installSlots }()
case <-ctx.Done():
return market.CLIInstallationReceipt{}, ctx.Err()
}
resolved, node, err := installer.resolveNode(ctx, managed.Runtime)
if err != nil {
return market.CLIInstallationReceipt{}, err
}
target := installer.installRoot(request.Release.ConnectorKey, request.Release.ReleaseDigest)
if receipt, err := installer.readAndVerifyReceipt(request.Release, cli.Entrypoint, resolved, node, target); err == nil {
receipt.OperationID = request.OperationID
return receipt, nil
}
if err := os.RemoveAll(target); err != nil {
return market.CLIInstallationReceipt{}, fmt.Errorf("remove invalid connector CLI installation: %w", err)
}
staging := filepath.Join(installer.rootDir, "staging", request.OperationID)
if !pathWithin(installer.rootDir, staging) {
return market.CLIInstallationReceipt{}, errors.New("connector CLI staging path escapes package root")
}
if err := os.RemoveAll(staging); err != nil {
return market.CLIInstallationReceipt{}, fmt.Errorf("reset connector CLI staging: %w", err)
}
defer os.RemoveAll(staging)
shared := installer.sharedPaths()
for _, directory := range []string{staging, shared.store, shared.corepack, shared.npmCache, shared.pnpmHome} {
if err := os.MkdirAll(directory, 0o700); err != nil {
return market.CLIInstallationReceipt{}, fmt.Errorf("create connector CLI package directory: %w", err)
}
}
privateHome := filepath.Join(staging, ".home")
if err := os.MkdirAll(privateHome, 0o700); err != nil {
return market.CLIInstallationReceipt{}, err
}
if err := writeConnectorPackageJSON(staging, *nodePackage, installer.pnpmVersion); err != nil {
return market.CLIInstallationReceipt{}, err
}
corepackEntrypoint := filepath.Join(resolved.Root, "node", "lib", "node_modules", "corepack", "dist", "corepack.js")
if !ordinaryFile(corepackEntrypoint) {
return market.CLIInstallationReceipt{}, errors.New("managed Node runtime Corepack entrypoint is unavailable")
}
installArgs := []string{corepackEntrypoint, "pnpm@" + installer.pnpmVersion, "install", "--dir", staging,
"--ignore-workspace", "--ignore-scripts", "--store-dir", shared.store, "--package-import-method", "hardlink"}
if err := installer.runManagedNode(ctx, resolved, node, staging, privateHome, shared, installArgs); err != nil {
return market.CLIInstallationReceipt{}, fmt.Errorf("install connector node package: %w", err)
}
packageRoot, err := installedNodePackageRoot(staging, nodePackage.Package)
if err != nil {
return market.CLIInstallationReceipt{}, err
}
for _, lifecycle := range nodePackage.Lifecycle {
entrypoint, err := safeInstalledFile(packageRoot, lifecycle.Entrypoint)
if err != nil {
return market.CLIInstallationReceipt{}, fmt.Errorf("resolve connector node package lifecycle: %w", err)
}
args := append([]string{entrypoint}, lifecycle.Arguments...)
if err := installer.runManagedNode(ctx, resolved, node, packageRoot, privateHome, shared, args); err != nil {
return market.CLIInstallationReceipt{}, fmt.Errorf("run connector node package %s: %w", lifecycle.Event, err)
}
}
receipt, err := installer.buildReceipt(request, cli.Entrypoint, resolved, node, staging, shared.store)
if err != nil {
return market.CLIInstallationReceipt{}, err
}
receipt.InstallRoot = target
if err := writeCLIInstallationReceipt(staging, receipt); err != nil {
return market.CLIInstallationReceipt{}, err
}
if err := os.MkdirAll(filepath.Dir(target), 0o700); err != nil {
return market.CLIInstallationReceipt{}, err
}
if err := os.Rename(staging, target); err != nil {
return market.CLIInstallationReceipt{}, fmt.Errorf("activate connector CLI installation: %w", err)
}
return receipt, nil
}
func (installer *NodePackageInstaller) ResolveCLI(ctx context.Context, release market.Release) (market.CLIInstallationReceipt, error) {
if installer == nil {
return market.CLIInstallationReceipt{}, errors.New("connector node package installer is unavailable")
}
managed, cli, _, err := nodePackageIntent(release)
if err != nil {
return market.CLIInstallationReceipt{}, err
}
releaseLane := installer.lockConnector(release.ConnectorKey)
defer releaseLane()
resolved, node, err := installer.resolveNode(ctx, managed.Runtime)
if err != nil {
return market.CLIInstallationReceipt{}, err
}
root := installer.installRoot(release.ConnectorKey, release.ReleaseDigest)
if _, statErr := os.Stat(root); errors.Is(statErr, os.ErrNotExist) {
return market.CLIInstallationReceipt{}, market.ErrReleaseInstallationAbsent
} else if statErr != nil {
return market.CLIInstallationReceipt{}, fmt.Errorf("inspect connector CLI installation: %w", statErr)
}
receipt, err := installer.readAndVerifyReceipt(release, cli.Entrypoint, resolved, node, root)
if err != nil {
return market.CLIInstallationReceipt{}, fmt.Errorf("%w: %v", market.ErrReleaseInstallationInvalid, err)
}
return receipt, nil
}
func (installer *NodePackageInstaller) RemoveCLI(ctx context.Context, request market.RemoveCLIRequest) error {
if err := ctx.Err(); err != nil {
return err
}
if installer == nil || !safeCLIPathSegment(request.ConnectorKey) || !isSHA256Hex(request.ReleaseDigest) {
return errors.New("connector CLI removal identity is invalid")
}
releaseLane := installer.lockConnector(request.ConnectorKey)
defer releaseLane()
target := installer.installRoot(request.ConnectorKey, request.ReleaseDigest)
if !pathWithin(installer.rootDir, target) {
return errors.New("connector CLI removal path escapes package root")
}
if err := removeAllWithinRoot(installer.rootDir, target); err != nil {
return fmt.Errorf("remove connector CLI installation: %w", err)
}
return nil
}
func (installer *NodePackageInstaller) RemoveConnector(
ctx context.Context,
request market.RemoveConnectorInstallationRequest,
) error {
if err := ctx.Err(); err != nil {
return err
}
if installer == nil || !safeCLIPathSegment(request.ConnectorKey) {
return errors.New("connector CLI removal identity is invalid")
}
releaseLane := installer.lockConnector(request.ConnectorKey)
defer releaseLane()
target := filepath.Join(installer.rootDir, "packages", request.ConnectorKey)
if !pathWithin(installer.rootDir, target) {
return errors.New("connector CLI removal path escapes package root")
}
if err := removeAllWithinRoot(installer.rootDir, target); err != nil {
return fmt.Errorf("remove Connector CLI installations: %w", err)
}
return nil
}
func (installer *NodePackageInstaller) lockConnector(connectorKey string) func() {
installer.mu.Lock()
lane := installer.connectorLanes[connectorKey]
if lane == nil {
lane = &sync.Mutex{}
installer.connectorLanes[connectorKey] = lane
}
installer.mu.Unlock()
lane.Lock()
return lane.Unlock
}
type connectorNodeSharedPaths struct{ store, corepack, npmCache, pnpmHome string }
func (installer *NodePackageInstaller) sharedPaths() connectorNodeSharedPaths {
root := filepath.Join(installer.rootDir, "shared")
return connectorNodeSharedPaths{store: filepath.Join(root, "pnpm-store"), corepack: filepath.Join(root, "corepack"),
npmCache: filepath.Join(root, "npm-cache"), pnpmHome: filepath.Join(root, "pnpm-home")}
}
func (installer *NodePackageInstaller) installRoot(connectorKey, releaseDigest string) string {
return filepath.Join(installer.rootDir, "packages", connectorKey, releaseDigest)
}
func (installer *NodePackageInstaller) resolveNode(ctx context.Context, requirement market.RuntimeRequirement) (ResolvedConnectorRuntime, ConnectorExecutable, error) {
resolved, err := installer.runtimes.ResolveProfile(ctx, requirement.Profile)
if err != nil {
return ResolvedConnectorRuntime{}, ConnectorExecutable{}, err
}
if err := VerifyRuntimeABI(requirement, resolved); err != nil {
return ResolvedConnectorRuntime{}, ConnectorExecutable{}, err
}
nodeVersion := resolved.Components["node"]
if !nodeVersionSatisfies(nodeVersion, requirement.VersionRange) {
return ResolvedConnectorRuntime{}, ConnectorExecutable{}, fmt.Errorf("managed Node %s does not satisfy %s", nodeVersion, requirement.VersionRange)
}
node, err := installer.runtimes.VerifyLaunch(requirement.Profile, "node")
return resolved, node, err
}
func (installer *NodePackageInstaller) runManagedNode(ctx context.Context, resolved ResolvedConnectorRuntime,
node ConnectorExecutable, cwd, privateHome string, shared connectorNodeSharedPaths, args []string) error {
runCtx, cancel := context.WithTimeout(ctx, installer.timeout)
defer cancel()
temporaryRoot := filepath.Join(privateHome, "tmp")
if err := os.MkdirAll(temporaryRoot, 0o700); err != nil {
return err
}
inheritedEnv := installer.environ()
pathEntries := append([]string{filepath.Join(resolved.Root, "node", "bin")},
filepath.SplitList(environmentValue(inheritedEnv, "PATH"))...)
pathValue := strings.Join(uniquePaths(pathEntries), string(os.PathListSeparator))
env := allowedNodePackageInstallEnvironment(inheritedEnv)
env = append(env, "HOME="+privateHome, "USERPROFILE="+privateHome, "COREPACK_HOME="+shared.corepack,
"TMPDIR="+temporaryRoot, "TMP="+temporaryRoot, "TEMP="+temporaryRoot,
"NPM_CONFIG_CACHE="+shared.npmCache, "PNPM_HOME="+shared.pnpmHome, "PATH="+pathValue)
connection, err := installer.processes.Start(runCtx, agentruntime.ProcessSpec{Provider: "connector-installer", CWD: cwd,
Command: append([]string{node.Path}, args...), Env: env,
ExecutableIdentity: &agentruntime.ExecutableIdentity{SHA256: node.SHA256, SizeBytes: node.SizeBytes}})
if err != nil {
if errors.Is(err, agentruntime.ErrProcessSpecInvalid) {
return fmt.Errorf("%w: %w", market.ErrPermanentInstallFailure, err)
}
return err
}
defer connection.Close()
if graceful, ok := connection.(agentruntime.GracefulProcessConnection); ok {
_ = graceful.CloseInput()
}
return waitCLIInstallation(runCtx, connection)
}
func waitCLIInstallation(ctx context.Context, connection agentruntime.ProcessConnection) error {
var output strings.Builder
for {
var frame agentruntime.ProcessFrame
var err error
if contextual, ok := connection.(agentruntime.ContextProcessConnection); ok {
frame, err = contextual.RecvContext(ctx)
} else {
frame, err = connection.Recv()
}
if err != nil {
if errors.Is(err, io.EOF) {
return nil
}
return err
}
output.Write(frame.Stdout)
output.Write(frame.Stderr)
if output.Len() > maxCLIInstallationOutput {
return errors.New("connector CLI installation output exceeded its limit")
}
if frame.ExitCode != nil {
if *frame.ExitCode != 0 {
message := strings.TrimSpace(output.String())
if len(message) > 4096 {
message = message[len(message)-4096:]
}
return fmt.Errorf("managed package command exited with code %d: %s", *frame.ExitCode, message)
}
return nil
}
}
}
func uniquePaths(values []string) []string {
result := make([]string, 0, len(values))
seen := make(map[string]struct{}, len(values))
for _, value := range values {
if strings.TrimSpace(value) == "" {
continue
}
value = filepath.Clean(value)
if _, exists := seen[value]; exists {
continue
}
seen[value] = struct{}{}
result = append(result, value)
}
return result
}
// allowedNodePackageInstallEnvironment projects the inherited environment onto
// the installer allow-list. Keys are folded case-insensitively and emitted in
// one canonical upper-case form: the connector process contract rejects two
// entries that differ only by case, so POSIX hosts exporting both HTTP_PROXY
// and http_proxy must not produce two entries here.
func allowedNodePackageInstallEnvironment(environment []string) []string {
allowed := map[string]struct{}{
"ALL_PROXY": {}, "COMSPEC": {}, "HTTP_PROXY": {}, "HTTPS_PROXY": {},
"LANG": {}, "LC_ALL": {}, "LC_CTYPE": {}, "LC_MESSAGES": {}, "NO_PROXY": {},
"NODE_EXTRA_CA_CERTS": {}, "PATHEXT": {}, "SSL_CERT_DIR": {}, "SSL_CERT_FILE": {},
"SYSTEMROOT": {},
}
values := make(map[string]string, len(allowed))
for _, item := range environment {
key, value, ok := strings.Cut(item, "=")
if !ok {
continue
}
canonical := strings.ToUpper(key)
if _, permitted := allowed[canonical]; !permitted {
continue
}
values[canonical] = value
}
keys := make([]string, 0, len(values))
for key := range values {
keys = append(keys, key)
}
sort.Strings(keys)
result := make([]string, 0, len(keys))
for _, key := range keys {
result = append(result, key+"="+values[key])
}
return result
}
func environmentValue(environment []string, key string) string {
for index := len(environment) - 1; index >= 0; index-- {
candidateKey, value, ok := strings.Cut(environment[index], "=")
if ok && strings.EqualFold(candidateKey, key) {
return value
}
}
return ""
}
func nodePackageIntent(release market.Release) (*market.ManagedStdioImplementation, *market.ManagedCLIInterface, *market.NodePackageInstallation, error) {
managed := release.Manifest.Implementation.ManagedStdio
if managed == nil || managed.CLI == nil || managed.CLI.Install == nil || managed.CLI.Install.Kind != "node_package" || managed.CLI.Install.NodePackage == nil {
return nil, nil, nil, errors.New("connector release does not declare a node package CLI installation")
}
return managed, managed.CLI, managed.CLI.Install.NodePackage, nil
}
func writeConnectorPackageJSON(root string, install market.NodePackageInstallation, pnpmVersion string) error {
payload := struct {
Private bool `json:"private"`
PackageManager string `json:"packageManager"`
Dependencies map[string]string `json:"dependencies"`
}{Private: true, PackageManager: "pnpm@" + pnpmVersion, Dependencies: map[string]string{install.Package: install.Version}}
data, err := json.Marshal(payload)
if err != nil {
return err
}
return os.WriteFile(filepath.Join(root, "package.json"), data, 0o600)
}
func (*NodePackageInstaller) buildReceipt(request market.InstallCLIRequest, executable string,
resolved ResolvedConnectorRuntime, node ConnectorExecutable, root, storeRoot string) (market.CLIInstallationReceipt, error) {
_, _, install, _ := nodePackageIntent(request.Release)
verified, err := verifyInstalledNodePackage(root, *install, executable)
if err != nil {
return market.CLIInstallationReceipt{}, err
}
entrypointDigest, err := cliFileSHA256(verified.entrypoint)
if err != nil {
return market.CLIInstallationReceipt{}, err
}
if install.Launch.Kind == "native" && entrypointDigest != install.Launch.SHA256 {
return market.CLIInstallationReceipt{}, errors.New("installed connector native CLI digest does not match manifest")
}
return market.CLIInstallationReceipt{SchemaVersion: cliInstallationReceiptSchema, OperationID: request.OperationID,
ConnectorKey: request.Release.ConnectorKey, ReleaseDigest: request.Release.ReleaseDigest,
RuntimeProfile: request.Release.Manifest.Implementation.ManagedStdio.Runtime.Profile,
RuntimeABI: resolved.ABI, NodeVersion: resolved.Components["node"], NodeSHA256: node.SHA256,
Package: install.Package, PackageVersion: install.Version, PackageIntegrity: install.Integrity, LaunchKind: install.Launch.Kind,
InstallRoot: root, StoreRoot: storeRoot, Entrypoint: verified.relativeEntrypoint,
EntrypointSHA256: entrypointDigest, EntrypointSize: verified.entrypointSize, LockSHA256: verified.lockDigest}, nil
}
func (installer *NodePackageInstaller) readAndVerifyReceipt(release market.Release, executable string,
resolved ResolvedConnectorRuntime, node ConnectorExecutable, root string) (market.CLIInstallationReceipt, error) {
data, err := os.ReadFile(filepath.Join(root, cliInstallationReceiptFile))
if err != nil || len(data) > 1<<20 {
return market.CLIInstallationReceipt{}, errors.New("connector CLI installation receipt is unavailable")
}
var receipt market.CLIInstallationReceipt
if json.Unmarshal(data, &receipt) != nil {
return market.CLIInstallationReceipt{}, errors.New("connector CLI installation receipt is invalid")
}
_, _, install, _ := nodePackageIntent(release)
shared := installer.sharedPaths()
if receipt.SchemaVersion != cliInstallationReceiptSchema || receipt.ConnectorKey != release.ConnectorKey ||
receipt.ReleaseDigest != release.ReleaseDigest || receipt.RuntimeProfile != release.Manifest.Implementation.ManagedStdio.Runtime.Profile ||
receipt.RuntimeABI != resolved.ABI || receipt.NodeVersion != resolved.Components["node"] || receipt.NodeSHA256 != node.SHA256 ||
receipt.Package != install.Package || receipt.PackageVersion != install.Version || receipt.PackageIntegrity != install.Integrity ||
receipt.LaunchKind != install.Launch.Kind || receipt.EntrypointSize <= 0 ||
filepath.Clean(receipt.InstallRoot) != filepath.Clean(root) || filepath.Clean(receipt.StoreRoot) != filepath.Clean(shared.store) {
return market.CLIInstallationReceipt{}, errors.New("connector CLI installation receipt identity is invalid")
}
verified, err := verifyInstalledNodePackage(root, *install, executable)
if err != nil || verified.relativeEntrypoint != receipt.Entrypoint || verified.lockDigest != receipt.LockSHA256 ||
verified.entrypointSize != receipt.EntrypointSize {
return market.CLIInstallationReceipt{}, errors.New("connector CLI installation changed after activation")
}
digest, err := cliFileSHA256(verified.entrypoint)
if err != nil || digest != receipt.EntrypointSHA256 || (install.Launch.Kind == "native" && digest != install.Launch.SHA256) {
return market.CLIInstallationReceipt{}, errors.New("connector CLI entrypoint changed after activation")
}
return receipt, nil
}
func writeCLIInstallationReceipt(root string, receipt market.CLIInstallationReceipt) error {
data, err := json.Marshal(receipt)
if err != nil {
return err
}
return os.WriteFile(filepath.Join(root, cliInstallationReceiptFile), data, 0o600)
}
type installedPackageJSON struct {
Name string `json:"name"`
Version string `json:"version"`
Bin json.RawMessage `json:"bin"`
}
type verifiedInstalledNodePackage struct {
entrypoint string
relativeEntrypoint string
lockDigest string
entrypointSize int64
}
func verifyInstalledNodePackage(root string, install market.NodePackageInstallation, executable string) (verifiedInstalledNodePackage, error) {
packageRoot, err := installedNodePackageRoot(root, install.Package)
if err != nil {
return verifiedInstalledNodePackage{}, err
}
data, err := os.ReadFile(filepath.Join(packageRoot, "package.json"))
if err != nil {
return verifiedInstalledNodePackage{}, fmt.Errorf("read installed connector package manifest: %w", err)
}
var manifest installedPackageJSON
if json.Unmarshal(data, &manifest) != nil || manifest.Name != install.Package || manifest.Version != install.Version {
return verifiedInstalledNodePackage{}, errors.New("installed connector package identity does not match manifest")
}
entrypointPath := install.Launch.Entrypoint
if install.Launch.Kind == "node_script" {
entrypointPath, err = packageBinEntrypoint(manifest.Bin, install.Package, executable)
if err != nil {
return verifiedInstalledNodePackage{}, err
}
}
entrypoint, err := safeInstalledFile(packageRoot, entrypointPath)
if err != nil {
return verifiedInstalledNodePackage{}, err
}
rootReal, err := filepath.EvalSymlinks(root)
if err != nil {
return verifiedInstalledNodePackage{}, errors.New("installed connector CLI root is unavailable")
}
relative, err := filepath.Rel(rootReal, entrypoint)
if err != nil || !safeRelativePath(relative) {
return verifiedInstalledNodePackage{}, errors.New("installed connector CLI entrypoint escapes installation root")
}
lockDigest, err := verifyPnpmLock(filepath.Join(root, "pnpm-lock.yaml"), install.Package, install.Version, install.Integrity)
if err != nil {
return verifiedInstalledNodePackage{}, err
}
info, err := os.Stat(entrypoint)
if err != nil || !info.Mode().IsRegular() || info.Size() <= 0 {
return verifiedInstalledNodePackage{}, errors.New("installed connector CLI entrypoint identity is unavailable")
}
return verifiedInstalledNodePackage{entrypoint: entrypoint, relativeEntrypoint: filepath.ToSlash(relative),
lockDigest: lockDigest, entrypointSize: info.Size()}, nil
}
func installedNodePackageRoot(root, packageName string) (string, error) {
parts := strings.Split(packageName, "/")
if len(parts) > 2 || len(parts) == 0 {
return "", errors.New("connector node package name is invalid")
}
segments := append([]string{root, "node_modules"}, parts...)
packageRoot := filepath.Join(segments...)
if !pathWithin(root, packageRoot) {
return "", errors.New("connector node package root escapes installation")
}
return packageRoot, nil
}
func packageBinEntrypoint(raw json.RawMessage, packageName, executable string) (string, error) {
var entries map[string]string
if json.Unmarshal(raw, &entries) == nil {
if value := strings.TrimSpace(entries[executable]); value != "" {
return value, nil
}
}
var single string
if json.Unmarshal(raw, &single) == nil && executable == strings.TrimPrefix(packageName[strings.LastIndex(packageName, "/")+1:], "@") {
return single, nil
}
return "", errors.New("installed connector package does not expose the declared CLI entrypoint")
}
func safeInstalledFile(root, relative string) (string, error) {
if !safeRelativePath(relative) {
return "", errors.New("installed connector package entrypoint is unsafe")
}
target := filepath.Join(root, filepath.FromSlash(relative))
rootReal, rootErr := filepath.EvalSymlinks(root)
targetReal, targetErr := filepath.EvalSymlinks(target)
if rootErr != nil || targetErr != nil || !pathWithin(rootReal, targetReal) || !ordinaryFile(targetReal) {
return "", errors.New("installed connector package entrypoint is unavailable")
}
return targetReal, nil
}
func safeRelativePath(value string) bool {
value = filepath.ToSlash(strings.TrimSpace(value))
clean := filepath.ToSlash(filepath.Clean(filepath.FromSlash(value)))
return value != "" && value == clean && clean != "." && clean != ".." && !strings.HasPrefix(clean, "../") && !filepath.IsAbs(value)
}
func ordinaryFile(path string) bool {
info, err := os.Stat(path)
return err == nil && info.Mode().IsRegular()
}
func pathWithin(root, target string) bool {
root = filepath.Clean(root)
target = filepath.Clean(target)
return target != root && strings.HasPrefix(target, root+string(filepath.Separator))
}
func removeAllWithinRoot(root, target string) error {
root = filepath.Clean(root)
target = filepath.Clean(target)
if !pathWithin(root, target) {
return errors.New("removal path escapes configured root")
}
relative, err := filepath.Rel(root, target)
if err != nil {
return err
}
current := root
parts := strings.Split(relative, string(filepath.Separator))
for index, part := range append([]string{""}, parts...) {
if index > 0 {
current = filepath.Join(current, part)
}
info, statErr := os.Lstat(current)
if errors.Is(statErr, os.ErrNotExist) {
return nil
}
if statErr != nil {
return statErr
}
if info.Mode()&os.ModeSymlink != 0 {
return errors.New("removal path contains a symbolic link")
}
if index < len(parts) && !info.IsDir() {
return errors.New("removal path parent is not a directory")
}
}
return os.RemoveAll(target)
}
func safeCLIPathSegment(value string) bool {
return value != "" && value == filepath.Base(value) && value != "." && value != ".." && !strings.ContainsAny(value, `/\\\x00`)
}
func cliFileSHA256(path string) (string, error) {
file, err := os.Open(path)
if err != nil {
return "", err
}
defer file.Close()
hash := sha256.New()
if _, err := io.Copy(hash, file); err != nil {
return "", err
}
return hex.EncodeToString(hash.Sum(nil)), nil
}
type pnpmLock struct {
Packages map[string]struct {
Resolution struct {
Integrity string `yaml:"integrity"`
} `yaml:"resolution"`
} `yaml:"packages"`
}
func verifyPnpmLock(path, packageName, version, integrity string) (string, error) {
data, err := os.ReadFile(path)
if err != nil {
return "", fmt.Errorf("read connector pnpm lock: %w", err)
}
var lock pnpmLock
if yaml.Unmarshal(data, &lock) != nil {
return "", errors.New("connector pnpm lock is invalid")
}
wanted := packageName + "@" + version
found := false
for key, entry := range lock.Packages {
normalized := strings.TrimPrefix(key, "/")
if normalized == wanted || strings.HasPrefix(normalized, wanted+"(") {
if entry.Resolution.Integrity != integrity {
return "", errors.New("connector package integrity does not match the published manifest")
}
found = true
break
}
}
if !found {
return "", errors.New("connector package is missing from pnpm lock")
}
hash := sha256.Sum256(data)
return hex.EncodeToString(hash[:]), nil
}
func nodeVersionSatisfies(version, constraint string) bool {
version = "v" + strings.TrimPrefix(strings.TrimSpace(version), "v")
if !semver.IsValid(version) {
return false
}
for _, part := range strings.Fields(constraint) {
operator := ""
for _, candidate := range []string{">=", "<=", ">", "<"} {
if strings.HasPrefix(part, candidate) {
operator = candidate
break
}
}
candidate := "v" + strings.TrimPrefix(strings.TrimSpace(strings.TrimPrefix(part, operator)), "v")
if operator == "" || !semver.IsValid(candidate) {
return false
}
comparison := semver.Compare(version, candidate)
if (operator == ">=" && comparison < 0) || (operator == ">" && comparison <= 0) ||
(operator == "<=" && comparison > 0) || (operator == "<" && comparison >= 0) {
return false
}
}
return strings.TrimSpace(constraint) != ""
}
func isSHA256Hex(value string) bool {
if len(value) != sha256.Size*2 {
return false
}
for _, character := range value {
if (character < '0' || character > '9') && (character < 'a' || character > 'f') {
return false
}
}
return true
}