feat(sandbox): Seatbelt lockdown translation — deny-all outbound, allow loopback proxy port (#372)

* feat(sandbox): Seatbelt lockdown translation confines outbound to the PMG proxy

Under network_via_proxy_only the Seatbelt profile now denies all
network-outbound (with a target=direct violation marker) and allows only
the loopback proxy port. SBPL is last-match-wins, so the broad deny is
emitted first, specific allows after, and the allow_network_bind rules
last — keeping loopback-to-loopback dev traffic working under lockdown.
allow_direct_dns re-opens the /var/run/mDNSResponder unix socket that
the deny otherwise covers.

Replaces the temporary fail-closed rejection in the Seatbelt driver with
the real translation; non-lockdown profiles translate byte-identically
to before.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PqMU5GNBbQvQct9nxek1VS

* test(sandbox): assert deny marker presence before ordering comparison

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PqMU5GNBbQvQct9nxek1VS

* feat(sandbox): render lockdown profiles without a proxy as deny-only with runtime note

pmg sandbox profile show renders profiles for debugging and must not
fail on lockdown profiles. Without a running proxy the translator keeps
the broad deny (rendered profile stays fail-closed, never looser than
runtime) and documents the runtime-only proxy-port allow in an SBPL
comment instead of fabricating a port. Execution is unaffected: the
driver validates the proxy address before translating.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PqMU5GNBbQvQct9nxek1VS

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Abhisek Datta
2026-07-10 21:11:56 +05:30
committed by GitHub
co-authored by Claude Fable 5
parent 5131c3f641
commit 3ac83a436d
5 changed files with 210 additions and 60 deletions
+1 -15
View File
@@ -11,9 +11,6 @@ import (
"time"
"github.com/safedep/dry/log"
"github.com/safedep/dry/usefulerror"
"github.com/safedep/dry/utils"
"github.com/safedep/pmg/errcodes"
"github.com/safedep/pmg/sandbox"
)
@@ -43,18 +40,7 @@ func (s *seatbeltSandbox) Execute(ctx context.Context, cmd *exec.Cmd, policy *sa
return nil, err
}
// Temporary fail-closed stub until the Seatbelt translator emits the
// lockdown profile: without it, a lockdown policy would silently get the
// pre-lockdown network rules. Removed when lockdown translation lands.
if utils.SafelyGetValue(policy.NetworkViaProxyOnly) {
return nil, usefulerror.NewUsefulError().
WithCode(errcodes.UnsupportedPlatform).
WithHumanError("network_via_proxy_only is not yet enforced by this pmg build").
WithHelp("Disable network_via_proxy_only for this profile until lockdown enforcement ships.").
Wrap(fmt.Errorf("network_via_proxy_only translation is not yet implemented (%s sandbox)", s.Name()))
}
sbProfile, err := s.translator.translate(policy)
sbProfile, err := s.translator.translate(policy, rt)
if err != nil {
return nil, fmt.Errorf("failed to translate sandbox policy: %w", err)
}