fix: harden target recovery metadata safety

This commit is contained in:
proxyctl coordinator
2026-07-17 10:59:14 +08:00
parent a5360028ca
commit 613fe56540
7 changed files with 436 additions and 10 deletions
+51 -3
View File
@@ -22,8 +22,9 @@ import (
)
const (
integrationStart = "# >>> proxyctl shell integration >>>"
integrationEnd = "# <<< proxyctl shell integration <<<"
integrationStart = "# >>> proxyctl shell integration >>>"
integrationEnd = "# <<< proxyctl shell integration <<<"
shellRecoveryVersion = 1
)
var proxyVariables = []string{
@@ -149,7 +150,8 @@ func (f OSFileSystem) AtomicWrite(path string, data []byte, mode os.FileMode, ow
}
type recovery struct {
Original map[string]string `json:"original,omitempty"`
Version int `json:"version"`
Original map[string]string `json:"original"`
OriginalScript *string `json:"originalScript,omitempty"`
Fingerprint string `json:"fingerprint"`
}
@@ -288,6 +290,7 @@ func (a *Adapter) enable(active profile.Profile) error {
return fmt.Errorf("read Shell recovery metadata: %w", err)
}
if !managed {
metadata.Version = shellRecoveryVersion
if existing, readErr := a.files.ReadFile(a.configuration.EnvironmentFile); readErr == nil && recognizesProxy(existing) {
original := string(existing)
metadata.OriginalScript = &original
@@ -344,12 +347,57 @@ func (a *Adapter) readRecovery() (recovery, bool, error) {
if err != nil {
return recovery{}, false, err
}
var fields map[string]json.RawMessage
if err := json.Unmarshal(data, &fields); err != nil {
return recovery{}, true, err
}
if fields == nil {
return recovery{}, true, errors.New("Shell recovery metadata must be a JSON object")
}
var value recovery
if err := json.Unmarshal(data, &value); err != nil {
return recovery{}, true, err
}
if err := validateRecovery(value, fields); err != nil {
return recovery{}, true, err
}
return value, true, nil
}
func validateRecovery(value recovery, fields map[string]json.RawMessage) error {
for _, name := range []string{"version", "original", "fingerprint"} {
if _, present := fields[name]; !present {
return fmt.Errorf("Shell recovery metadata is missing required field %q", name)
}
}
if value.Version != shellRecoveryVersion {
return fmt.Errorf("unsupported Shell recovery metadata version %d", value.Version)
}
decoded, err := hex.DecodeString(value.Fingerprint)
if err != nil || len(decoded) != sha256.Size {
return errors.New("Shell recovery metadata has an invalid fingerprint")
}
_, hasOriginalScript := fields["originalScript"]
if hasOriginalScript {
if value.OriginalScript == nil || value.Original != nil || len(*value.OriginalScript) == 0 || !recognizesProxy([]byte(*value.OriginalScript)) {
return errors.New("Shell recovery metadata has inconsistent original script state")
}
return nil
}
if value.Original == nil {
return errors.New("Shell recovery metadata is missing the original environment snapshot")
}
for name := range value.Original {
known := false
for _, candidate := range proxyVariables {
known = known || name == candidate
}
if !known {
return fmt.Errorf("Shell recovery metadata contains unsupported variable %q", name)
}
}
return nil
}
func (a *Adapter) ownership() *Ownership {
if !a.configuration.OwnFiles {
return nil