mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-25 21:20:00 +00:00
Check the exact persistence destination before bootstrap
Normalise the selected data directory with the storage path routine before inspection. Raw symlink/.. traversal and trailing separators must not probe an empty peer or hide a linked root while the encrypted importer writes elsewhere. Retain and validate the existing canonical agent credential. Change-source: pulse-maintainer
This commit is contained in:
@@ -24,6 +24,11 @@
|
||||
absent or empty resolved data directory; a regular deployment `.env` is the
|
||||
only allowed pre-start entry. Existing non-Proxmox settings, token/session
|
||||
records, encryption keys, tenant directories and unknown contents all block it.
|
||||
Admission uses the same `NormalizeStorageDir` path as persistence before any
|
||||
filesystem probe: raw symlink/`..` traversal must not inspect an empty peer while
|
||||
the importer writes a different existing destination, and a trailing separator
|
||||
must not hide a final directory link. Canonical destination credential validation
|
||||
and live/dangling link spellings have explicit regression controls.
|
||||
Directory inspection errors, non-directories and symlinks are not absence.
|
||||
Admission reads no file contents and creates no persistence/key. The importer
|
||||
rechecks before creating persistence or writing and audits a classified refusal
|
||||
|
||||
@@ -29,6 +29,7 @@ import (
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/mock"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/models"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/monitoring"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/securityutil"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/telemetry"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/updates"
|
||||
|
||||
@@ -1094,6 +1095,13 @@ var (
|
||||
// A regular .env is the only exception for pre-start deployment overrides;
|
||||
// keep the input archive outside the destination, not alongside retained state.
|
||||
func checkAutoImportDestination(dataDir string) error {
|
||||
// Inspect the exact path persistence will use. Raw symlink/.. traversal or
|
||||
// trailing separators can otherwise inspect a different directory, or hide
|
||||
// the final directory link from Lstat.
|
||||
dataDir, err := securityutil.NormalizeStorageDir(dataDir)
|
||||
if err != nil {
|
||||
return errAutoImportDestinationUnknown
|
||||
}
|
||||
info, err := os.Lstat(dataDir)
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
|
||||
@@ -95,7 +95,7 @@ func TestStartupImportUsesPersistenceDestination(t *testing.T) {
|
||||
canonical := filepath.Join(base, "destination")
|
||||
target := config.NewConfigPersistence(canonical)
|
||||
if err := target.SaveAPITokens([]config.APITokenRecord{{
|
||||
ID: "synthetic-existing-record", Hash: "synthetic-existing-digest",
|
||||
ID: "synthetic-existing-record", Hash: auth.HashAPIToken("synthetic-existing-credential"),
|
||||
Scopes: []string{config.ScopeAgentReport},
|
||||
}}); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -122,6 +122,14 @@ func TestStartupImportUsesPersistenceDestination(t *testing.T) {
|
||||
if after := snapshotAutoImportDestination(t, canonical); !reflect.DeepEqual(before, after) {
|
||||
t.Error("canonical destination was modified")
|
||||
}
|
||||
records, err := target.LoadAPITokens()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
loaded := &config.Config{APITokens: records}
|
||||
if record, ok := loaded.ValidateAPIToken("synthetic-existing-credential"); !ok || record.ID != "synthetic-existing-record" {
|
||||
t.Error("canonical destination credential no longer validates")
|
||||
}
|
||||
if entries, err := os.ReadDir(filepath.Join(peer, "destination")); err != nil || len(entries) != 0 {
|
||||
t.Error("the peer used by raw traversal was modified")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user