This fixes Linux-related issue when UI process do not start automatically after upgrade.
- replace direct current_exe relaunch usage with verified launch program resolution
- consider both current_exe and argv0, but only accept verified launchable file paths
- fail relaunch with explicit error when no safe executable path is available
- in reconnect flow, exit current UI only if relaunch spawn succeeds
- if relaunch request fails, keep current UI process running and continue normal startup
https://github.com/safing/portmaster-shadow/issues/40
- Add fixed output path for portmaster-core debug configurations
- Prevents creation of temporary debug binaries with random suffixes
- Reuses same binary across debug sessions
The changes add support for tracking and properly routing connections to the IVPN client's local service port,
which is needed when the default firewall action is to block unknown connections.
The verdict handler is now registered earlier in the connection flow to handle connections while the client is connecting.
https://github.com/safing/portmaster-shadow/issues/34
Access option 'spn/enable' only after full initialisation by replacing
the eagerly-initialised struct field (config.GetAsBool called at
construction time) with local variables declared at the call-site of
each function that needs the option.
Affected: service/control, service/interop/ivpn
When IVPN connects, virtual interface IPs can cause netenv to detect
the VPN egress as the device's physical location, leading to a wrong
SPN home hub selection. Fix this by gating interface- and traceroute-
based location methods behind a new flag.
- Add DisableNetworkDerivedLocation(bool) to netenv/location.go backed
by an atomic.Bool; getLocationFromInterfaces and getLocationFromTraceroute
return early when the flag is set
- Call DisableNetworkDerivedLocation(true) in onConnectionStarting and
DisableNetworkDerivedLocation(false) in onConnectionStopped and in the
connectIvpnClient defer (covers unexpected client disconnects)
https://github.com/safing/portmaster-shadow/issues/34
Add a Windows-specific hook that calls conf.SetBindAddr with the default
physical interface's addresses when SPN is connecting. This ensures SPN
hub traffic bypasses the IVPN tunnel rather than being routed through it.
- Add hook_windows.go with spnConnectingHook that reads the default
interface via netenv.GetDefaultInterface and sets the SPN bind address
- Update hook_default.go build constraint to exclude windows
- Export DefaultNetInterface and GetDefaultInterface in netenv to allow
cross-package access from the ivpn interop layer
https://github.com/safing/portmaster-shadow/issues/34
Introduce mark 1709 (MarkAcceptFinal) and a corresponding
PermanentAcceptFinal() method that sets this mark on packets belonging
to Portmaster-owned outbound connections.
Add iptables rules (both IPv4 and IPv6, filter and mangle chains) to
ACCEPT packets/connections carrying mark 1709, so further OUTPUT rules from
third-party software (e.g. iVPN) cannot override the allow decision.
https://github.com/safing/portmaster-shadow/issues/34
Add a synchronous HookMgr[T] that lets callers register pre-connect
hooks before SPN dials a home hub. The IVPN interop layer subscribes
to this hook and uses Linux ip-rule/ip-route to steer SPN hub IPs
through a dedicated routing table (717) pointing to the non-VPN default
gateway, preventing SPN control traffic from being tunnelled into IVPN.
- service/mgr: add generic HookMgr[T] (synchronous, cancellable)
- spn/captain: expose HookSPNConnecting; invoke it in connectToHomeHub
- service/netenv: add GatewayInfo + GatewaysInfo() with interface/mask
- service/interop/ivpn: add ensureSpnHubBypassVpnRoutes managing policy
routing; call it from the SPN pre-connect hook and on VPN stop/connect
- nfq/packet: add hex comments next to mark constants
https://github.com/safing/portmaster-shadow/issues/34
When nft/nftables is unavailable on a Linux host, wg-quick falls back to
installing equivalent kill-switch rules via iptables (raw/PREROUTING).
This change adds support for that fallback path so SPN reverse-NAT loopback
traffic is allowed in both backends, while keeping rule scope narrow and
cleanup idempotent.
- extend SPN compatibility reconcile logic to support both nft and iptables backends
- keep nft path as preferred behavior and continue tracking/removing inserted nft rule by handle
- add strict iptables raw PREROUTING fallback rule for loopback reverse-NAT traffic when nft is unavailable
- make rule lifecycle idempotent by cleaning previously managed rules before re-adding on state changes
- refresh inline documentation to describe dual-backend behavior and safety scope
- ensure compatibility rules are reconciled/removed on IVPN disconnect and failed connection teardown
https://github.com/safing/portmaster-shadow/issues/34
- split IVPN event handlers into dedicated file and add ConnectedResp handling
- store connected session details in client status for follow-up compatibility logic
- add Linux-only SPN compatibility hook that reconciles an nft allow rule for WG loopback reverse-NAT traffic
- track and clean up inserted nft rule handles to avoid stale rules
- add non-Linux no-op hook implementation and trigger compatibility reconciliation on config changes
https://github.com/safing/portmaster-shadow/issues/34
Previously, the external verdict handler was placed in filterHandler,
which is only called for new packets. This meant it was silently
bypassed when connections were re-evaluated via resetConnectionVerdict.
Move the handler into FilterConnection so it is consistently applied
for all filtering paths, including verdict resets.
https://github.com/safing/portmaster-shadow/issues/34
Add ensureJumpRulesAtTop and reinsertDisplacedRules to detect and fix
iptables jump rules that have been displaced by other services during
boot. A background worker runs checks at 5s, 15s, and 45s after nfqueue
interception starts, reinserting any out-of-position rules.
- fixed issue where downgrades were blocked due to an overly broad
Published date check
- added symmetric version/date mismatch checks for both upgrades and
downgrades, skipped when current index is locally generated
- locally generated indexes now follow the standard upgrade path;
upgrading to the same version is explicitly blocked
https://github.com/safing/portmaster-shadow/issues/39
Config options registered after loadConfig() runs were silently
discarded — ReplaceConfig() only applies disk values to already-known
options, so any module registering in Start() rather than New() would
always reset to its DefaultValue on every restart.
Changes:
- base/notifications: move prep()/registerConfig() from Start() to
New(), ensuring all options exist before loadConfig() runs
- base/config: introduce cfgInitialized flag, set unconditionally in
start() before loadConfig()
- base/config: add guard in Register() that rejects registrations
after config is initialized, making the violation explicit
Fixes: core/useSystemNotifications always restoring to true despite
being saved as false in config.json
https://github.com/safing/portmaster/issues/2020
Add "Don't show again" action to the stale cache notification.
Suppression state is stored in the database and checked on startup.
System notification is shown only on first occurrence.
Reset handler in broadcasts now also clears the suppression record.
https://github.com/safing/portmaster/issues/2061
Actions with visibility "in-app-only" are shown in the UI but skipped
when displaying system-level notifications (Tauri/OS). Updates Go,
TypeScript/Angular and Rust projects accordingly.
When Portmaster connects to the IVPN Client, display an info notification
informing the user that IVPN connections are allowed and that DNS will be
handled by Portmaster's local resolver when configured.
The notification includes a "Do not notify me anymore" action that
permanently suppresses future notifications by writing a marker record to
the core database. The check runs before showing the notification on each
subsequent connection.
The "Reset Notification States" API endpoint (and matching UI menu item)
now also clears the IVPN suppression record alongside the broadcast states,
so all suppressed notifications can be restored at once.
- service/interop/ivpn: add notification.go with initAndShowNotification,
isNotificationSuppressed, and suppressNotification
- service/interop/ivpn/ivpn.go: show notification on connect if not suppressed
- service/broadcasts/api.go: extend reset-state handler to also delete the
IVPN suppression record; update endpoint name and description
- desktop: rename "Reset Broadcast State" menu item and toast messages to
"Reset Notifications State"
Add a `Visibility` field to the `Action` struct allowing actions to be
hidden in the compact notification view and only shown when the user
expands the full notification (value: "detailed").
- base/notifications: add `ActionVisibility` type and `ActionVisibilityDetailed`
constant to `Action` struct
- notifications.types.ts: expose `Visibility` field on the frontend `BaseAction`
interface
- notification-list.component.html: filter out `detailed` actions in the
compact list view
- generic-setting.ts: set default `Visibility: ''` on the inline UI action
When a permitted connection is initiated by Portmaster itself, clear the
write flag on the classify result so subsequent filters in the sublayer
chain cannot override the permit action.
Add IRP_MJ_CREATE and IRP_MJ_CLEANUP handlers to capture and clear the
PID of the user-space process that holds the device handle open.
- wdk/ffi: expose PsGetCurrentProcessId kernel API
- wdk/irp_helpers: add CreateRequest and CleanupRequest wrappers
- driver/device: add owner_pid AtomicU32 field with lock-free access
for callouts; add is_owner_pid() helper
- driver/entry: register driver_create/driver_cleanup dispatch routines
that store/clear owner_pid on connect/disconnect
Add Verdict::RedirectSplitTunnel (11) with PM_SPLIT_TUN_PORT redirection.
Wire up the new verdict in ALE, packet, and device layers alongside the
existing redirect verdicts. RedirectSplitTunnel is kept last in the enum
so older Portmaster versions (which only know verdicts 0–10) remain
unaffected.
Rename action_block() to action_block_hard(), which additionally calls
clear_write_flag(). This prevents lower-weight filters in the same WFP
sublayer from overwriting a block action set by Portmaster's callout.
Updated call sites:
- ALE layer: PermanentBlock, Undeterminable, Failed, and inbound Block
- Packet layer: PermanentBlock
When completing a Reauthorization classify defer, reset_all_filters()
would fail with STATUS_FWP_TXN_IN_PROGRESS if another WFP transaction
was already running (e.g. from a concurrent ClearCache command). This
caused the packet to be silently dropped instead of injected.
This error is safe to ignore: the concurrent transaction will trigger
WFP reauthorization for all connections anyway, and the verdict for the
current connection is already written to the connection_cache before
complete() is called, so the callout will apply the correct verdict when
the injected packet passes through.
All other errors from reset_all_filters() are still propagated.