Implement controlled app launch (T-401)

This commit is contained in:
ila
2026-07-19 21:10:59 +08:00
parent d0cf333394
commit 87083c387f
32 changed files with 1802 additions and 31 deletions
+55 -6
View File
@@ -39,12 +39,16 @@ type InstalledFile struct {
// InstalledApp is the local installed-app.json v1 protocol.
type InstalledApp struct {
SchemaVersion int `json:"schema_version"`
ID string `json:"id"`
Version string `json:"version"`
Architecture string `json:"architecture"`
Channel string `json:"channel"`
Files []InstalledFile `json:"files"`
SchemaVersion int `json:"schema_version"`
ID string `json:"id"`
Version string `json:"version"`
Architecture string `json:"architecture"`
Channel string `json:"channel"`
Entrypoint string `json:"entrypoint,omitempty"`
WorkingDirectory string `json:"working_directory,omitempty"`
MinOS string `json:"min_os,omitempty"`
RequiresAdmin bool `json:"requires_admin"`
Files []InstalledFile `json:"files"`
}
// InstallationSnapshot contains disk facts without deriving UI status.
@@ -107,6 +111,37 @@ func (store *InstalledAppStore) Read(appID string) (record InstalledApp, found b
return store.readLocked(appID)
}
// ResolveCurrent returns a validated record and the real current directory
// below its app root. It never creates directories and rejects unsafe layouts.
func (store *InstalledAppStore) ResolveCurrent(appID string) (InstalledApp, string, error) {
store.mu.Lock()
defer store.mu.Unlock()
record, found, err := store.readLocked(appID)
if err != nil {
return InstalledApp{}, "", err
}
if !found {
return InstalledApp{}, "", os.ErrNotExist
}
appRoot, exists, err := store.inspectAppRoot(appID)
if err != nil {
return InstalledApp{}, "", err
}
if !exists {
return InstalledApp{}, "", os.ErrNotExist
}
current := filepath.Join(appRoot, "current")
if err := requireRealDirectory(current); err != nil {
if errors.Is(err, os.ErrNotExist) {
return InstalledApp{}, "", fmt.Errorf("%w: current directory is missing: %w", ErrStorageLayoutUnsafe, err)
}
return InstalledApp{}, "", err
}
record.Files = append([]InstalledFile(nil), record.Files...)
return record, current, nil
}
// Inspect reads the installed record and detects an unfinished install journal.
func (store *InstalledAppStore) Inspect(appID string) (InstallationSnapshot, error) {
store.mu.Lock()
@@ -283,6 +318,20 @@ func (record InstalledApp) validate() error {
if record.Channel != "stable" {
return fmt.Errorf("%w: channel=%q", ErrInstalledAppInvalid, record.Channel)
}
if record.Entrypoint != "" {
if err := safepath.ValidateRelative(record.Entrypoint); err != nil {
return fmt.Errorf("%w: entrypoint: %v", ErrInstalledAppInvalid, err)
}
}
if record.WorkingDirectory != "" && record.WorkingDirectory != "." {
if err := safepath.ValidateRelative(record.WorkingDirectory); err != nil {
return fmt.Errorf("%w: working_directory: %v", ErrInstalledAppInvalid, err)
}
}
if record.MinOS != "" && record.MinOS != "windows-7-sp1" &&
record.MinOS != "windows-10" && record.MinOS != "windows-11" {
return fmt.Errorf("%w: min_os=%q", ErrInstalledAppInvalid, record.MinOS)
}
if record.Files == nil {
return fmt.Errorf("%w: files must be an array", ErrInstalledAppInvalid)
}
+60
View File
@@ -239,6 +239,66 @@ func TestInstalledAppStoreMissingRecord(t *testing.T) {
}
}
func TestInstalledAppStoreResolveCurrentRequiresRealCurrentDirectory(t *testing.T) {
appsRoot := filepath.Join(t.TempDir(), "apps")
store := NewInstalledAppStore(appsRoot)
record := validInstalledApp()
if err := store.Write(record); err != nil {
t.Fatalf("Write() error = %v", err)
}
if _, _, err := store.ResolveCurrent(record.ID); !errors.Is(err, ErrStorageLayoutUnsafe) {
t.Fatalf("ResolveCurrent(missing) error = %v, want %v", err, ErrStorageLayoutUnsafe)
}
current := filepath.Join(appsRoot, record.ID, "current")
if err := os.MkdirAll(current, 0o700); err != nil {
t.Fatalf("MkdirAll(current) error = %v", err)
}
loaded, resolvedCurrent, err := store.ResolveCurrent(record.ID)
if err != nil {
t.Fatalf("ResolveCurrent() error = %v", err)
}
if loaded.ID != record.ID || resolvedCurrent != current {
t.Fatalf("ResolveCurrent() = (%#v, %q), want (%#v, %q)", loaded, resolvedCurrent, record, current)
}
}
func TestInstalledAppStoreValidatesOptionalLaunchMetadata(t *testing.T) {
tests := []struct {
name string
mutate func(*InstalledApp)
}{
{
name: "unsafe entrypoint",
mutate: func(record *InstalledApp) {
record.Entrypoint = "../App.exe"
},
},
{
name: "unsafe working directory",
mutate: func(record *InstalledApp) {
record.WorkingDirectory = "bin/.. "
},
},
{
name: "unsupported minimum OS",
mutate: func(record *InstalledApp) {
record.MinOS = "windows-12"
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
record := validInstalledApp()
test.mutate(&record)
err := NewInstalledAppStore(filepath.Join(t.TempDir(), "apps")).Write(record)
if !errors.Is(err, ErrInstalledAppInvalid) {
t.Fatalf("Write() error = %v, want %v", err, ErrInstalledAppInvalid)
}
})
}
}
func validInstalledApp() InstalledApp {
return InstalledApp{
SchemaVersion: 1,