package sandbox import ( "fmt" "github.com/safedep/dry/utils" "github.com/safedep/pmg/sandbox/util" ) // ResolveProfile loads name via the registry, resolves inheritance, and // returns a SandboxPolicy with all path-bearing fields expanded against opts // (or process env where opts fields are empty). The returned policy is a deep // copy — the registry-cached policy is never mutated. func (r *defaultProfileRegistry) ResolveProfile(name string, opts ResolveOptions) (*SandboxPolicy, error) { policy, err := r.GetProfile(name) if err != nil { return nil, err } resolved, err := expandPolicyPaths(policy, opts) if err != nil { return nil, fmt.Errorf("failed to expand variables for profile %s: %w", name, err) } return resolved, nil } func expandPolicyPaths(p *SandboxPolicy, opts ResolveOptions) (*SandboxPolicy, error) { // Shallow struct copy aliases *bool pointers with the registry-cached // policy. Re-point them so callers mutating the result can't corrupt the // cache. Slice fields below are rebuilt fresh so they're already isolated. out := *p if p.AllowGitConfig != nil { out.AllowGitConfig = utils.PtrTo(*p.AllowGitConfig) } if p.AllowPTY != nil { out.AllowPTY = utils.PtrTo(*p.AllowPTY) } if p.AllowNetworkBind != nil { out.AllowNetworkBind = utils.PtrTo(*p.AllowNetworkBind) } if p.NetworkViaProxyOnly != nil { out.NetworkViaProxyOnly = utils.PtrTo(*p.NetworkViaProxyOnly) } if p.AllowDirectDNS != nil { out.AllowDirectDNS = utils.PtrTo(*p.AllowDirectDNS) } allowRead, err := expandSlice(p.Filesystem.AllowRead, opts) if err != nil { return nil, err } allowWrite, err := expandSlice(p.Filesystem.AllowWrite, opts) if err != nil { return nil, err } denyRead, err := expandSlice(p.Filesystem.DenyRead, opts) if err != nil { return nil, err } denyWrite, err := expandSlice(p.Filesystem.DenyWrite, opts) if err != nil { return nil, err } out.Filesystem = FilesystemPolicy{ AllowRead: allowRead, AllowWrite: allowWrite, DenyRead: denyRead, DenyWrite: denyWrite, } allowExec, err := expandSlice(p.Process.AllowExec, opts) if err != nil { return nil, err } denyExec, err := expandSlice(p.Process.DenyExec, opts) if err != nil { return nil, err } out.Process = ProcessPolicy{AllowExec: allowExec, DenyExec: denyExec} // Network entries are host:port and don't carry path variables, but the // slices are still deep-copied so the caller can safely mutate the result. out.Network = NetworkPolicy{ AllowOutbound: append([]string(nil), p.Network.AllowOutbound...), DenyOutbound: append([]string(nil), p.Network.DenyOutbound...), AllowBind: append([]string(nil), p.Network.AllowBind...), } // Environment entries are variable-name globs, not paths, so they are // deep-copied without expansion so the caller can safely mutate the result. out.Environment = EnvironmentPolicy{ Allow: append([]string(nil), p.Environment.Allow...), Deny: append([]string(nil), p.Environment.Deny...), } out.PackageManagers = append([]string(nil), p.PackageManagers...) return &out, nil } func expandSlice(in []string, opts ResolveOptions) ([]string, error) { if in == nil { return nil, nil } out := make([]string, len(in)) for i, p := range in { exp, err := util.ExpandVariablesWith(p, opts.CWD, opts.Home, opts.TmpDir) if err != nil { return nil, err } out[i] = exp } return out, nil }