mirror of
https://github.com/safedep/pmg.git
synced 2026-08-03 07:24:09 +02:00
* feat(sandbox): add network_via_proxy_only and allow_direct_dns policy fields Config surface for network lockdown: network_via_proxy_only confines a sandboxed package manager's outbound network to the PMG proxy; allow_direct_dns is its escape hatch re-opening direct DNS. Both follow the existing pointer-bool inheritance pattern in MergeWithParent. Lint warns when allow_direct_dns is set without network_via_proxy_only, where it has no effect. The fields are declared and inherited but unread; enforcement lands with the ExecutionContext plumbing and Seatbelt lockdown translation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PqMU5GNBbQvQct9nxek1VS * fix(sandbox): deep-copy new pointer flags in profile resolution expandPolicyPaths re-points the older pointer booleans so callers cannot corrupt the registry-cached policy; the new NetworkViaProxyOnly and AllowDirectDNS fields need the same isolation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PqMU5GNBbQvQct9nxek1VS --------- Co-authored-by: Claude <noreply@anthropic.com>
118 lines
3.3 KiB
Go
118 lines
3.3 KiB
Go
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
|
|
}
|