Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .cursor/agents/proxy-bypass-contain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
name: proxy-bypass-contain
description: Refuses a Protected badge in system-proxy mode while known SOCKS-ignoring apps are running uncontained. Use proactively for Chrome/Electron/Slack bypass, finish_protected, or proxy-mode leak holes.
---

You own the documented proxy-mode bypass hole.

When invoked:
1. Read `src-tauri/src/detect.rs`, `src-tauri/src/bypass.rs`, and
`finish_protected`.
2. In proxy mode without the NIC lock, do not report Protected if a known
stock bypasser is live and uncontained (Chrome/Discord/Slack always;
Firefox/Cursor/VS Code/Claude only when their helper is not configured).
3. Inspect live process args only to decide containment; never store, log,
or export full command lines.
4. The error must tell the user to quit the app, apply Apps helpers, or
use TUN / NIC lock. Do not silently claim Protected.
5. TUN or a live NIC lock can still be Protected; those boundaries contain
ignore-SOCKS apps.

A Network Extension may supplement pf/TUN. It is forbidden as the only
lock. Apple can hide processes from the filter and the framework can fail
open; that must Degrade the session, never punch clearnet. Do not weaken
Tor isolation.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,6 @@ priuvacy-sexy.txt
*.njsproj
*.sln
*.sw?

# Local Swift build of the connection-filter system extension
macos/OnionGateFilter/build/
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ OnionGate uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added

- macOS **connection filter** (Network Extension): LuLu-style hold-and-drop for
outbound flows that are not already Tor. Default verdict is drop. pf/TUN stay
the fail-closed lock. Apple can hide some of its own processes from the
filter, and a crashed or flooded filter can fail open — Connect then reports
Degraded, not Protected. Loading it needs a signed build with a
network-extension profile and approval in System Settings. This is parked
until Apple Developer Program enrollment is decided.

## [0.2.3] - 2026-08-14

### Fixed
Expand Down
11 changes: 10 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

.PHONY: help setup install deps \
start dev run build build-frontend preview \
downloads icons \
downloads macos-filter icons \
check typecheck test lint fmt fmt-check audit \
changelog-check changelog-sync release-check release-bundle-local sidecar-sbom \
docs docs-build docs-preview \
Expand Down Expand Up @@ -50,8 +50,17 @@ preview: ## Serve the built frontend (vite preview)
# Build
# ---------------------------------------------------------------------------

macos-filter: ## Compile the macOS connection-filter system extension (needs Xcode + a Developer ID profile to load)
$(MAKE) -C macos/OnionGateFilter

build: deps ## Build a release app bundle (Tauri)
$(NPM) run tauri build
@if [ "$$(uname -s)" = Darwin ]; then \
$(MAKE) macos-filter || true; \
for app in src-tauri/target/release/bundle/macos/*.app; do \
[ -d "$$app" ] && bash scripts/embed-oniongate-filter.sh "$$app" || true; \
done; \
fi

downloads: release-bundle-local ## Build local installer/download files for this OS

Expand Down
8 changes: 6 additions & 2 deletions TO-DO.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,9 @@ that lock; they are tracked work.
already require a matching peer code signature; unsigned debug stays UID-only.
Windows named-pipe ACL / client identity still needs review.
- Snowflake / meek vs the NIC lock: no honest IP allowlist; leave unsupported.
- Do not add a Network Extension as the enforcement path (Apple daemons bypass
it; it can fail open).
- Network Extension is allowed only as a **supplement** to pf/TUN. It is
forbidden as the only lock. Apple can hide processes from
`NEFilterDataProvider` and the framework can fail open; detect that and
Degrade, never treat the filter as sufficient containment. Loading the
extension is blocked on Apple Developer Program enrollment (which company
enrolls, and approval of the network-extension profile).
18 changes: 18 additions & 0 deletions docs/guide/connection.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,24 @@ later live inspection is unavailable, the badge remains **unverified**. TUN may
remain active to avoid dropping captured traffic onto a direct route; retry the
kill switch or disconnect.

## Connection filter (macOS)

On a signed macOS build, Harden can load a Network Extension that intercepts
new outbound flows, allows only traffic that is already Tor or OnionGate, and
drops everything else.

This is a supplement, not a replacement for `pf` or TUN. Apple can hide some
of its own processes from the filter, and a crashed or flooded extension can
fail open. Home and Verify show when the filter is up and when a live
clearnet flow never reached it. That case is **Degraded**, not Protected.

Unsigned and ad-hoc debug builds do not load the extension. They stay on the
packet lock and the after-the-fact socket watch. This feature is parked until
Apple Developer Program enrollment is decided (which company enrolls, and
approval of the network-extension profile).

There is no “Allow this app on clearnet.” That would be a destination hole.

## Exit country and relay pins

An exit-country selection writes Tor's `ExitNodes` preference. It is a
Expand Down
8 changes: 8 additions & 0 deletions docs/guide/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@ recorded in the recovery journal.
- **Language:** English is currently the only selectable complete translation.
Other listed languages remain disabled until their UI coverage is complete.

## Connection filter

**Connection filter** (macOS) requires the Network Extension once it is
installed. Default is on. The extension holds and drops outbound flows that
are not already Tor. `pf` remains the packet lock. A signed `.pkg` with a
network-extension provisioning profile is required to load it. This setting
is parked until Apple Developer Program enrollment is decided.

## Snowflake volunteer

The Snowflake volunteer control runs a proxy that helps other censored users
Expand Down
5 changes: 5 additions & 0 deletions docs/guide/system.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ firewall and stealth/auto-allow settings, Guest login and Guest SMB, Remote
Login, AirPlay Receiver, Bonjour multicast advertisements, Remote Management,
printer sharing, and immediate password after the screen saver.

**Connection filter (Network Extension)** is a LuLu-style hold-and-drop for
outbound flows that are not already Tor. It is a supplement. `pf` stays the
packet lock. Apple can hide some of its own processes from the filter.
Loading it needs a signed build and Apple Developer Program enrollment.

FileVault, Intel firmware password, and private Wi-Fi address are guide-only
controls that open the appropriate operating-system workflow rather than
forcing a sensitive setting. Tools also include a one-shot DNS-cache flush and
Expand Down
9 changes: 9 additions & 0 deletions docs/reference/release.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,15 @@ the private key or password.

These are optional for `0.x` and mandatory from `1.0.0` onward.

The optional macOS **connection filter** is a Network System Extension
(`content-filter-provider-systemextension`). It will not load on unsigned or
ad-hoc Debug the way the privileged helper can. Shipping it needs an Apple
Developer ID **network-extension** provisioning profile, the host-app
entitlements in `scripts/macos-pkg/OnionGate.entitlements`, and user approval
in System Settings → Network Extensions. Do not attach those entitlements to
unsigned `tauri.conf.json` builds: they would fail to launch. This PR is
parked until Developer Program enrollment is decided.

macOS requires:

- `APPLE_CERTIFICATE`
Expand Down
10 changes: 9 additions & 1 deletion docs/reference/residual-leaks.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,15 @@ Read the [threat model](/reference/threat-model) with this list.
show it.
- **Same-host allowlist hole.** Any local process may open TCP to a listed Tor
endpoint IP without speaking the Tor protocol. Stopping that needs a second
machine or a process-aware filter Apple can exempt.
machine. The optional connection filter can hold other flows; it cannot close
this hole.
- **Connection filter (Network Extension) is not a second lock.** It supplements
`pf`. Apple’s `ContentFilterExclusionList` hides some system processes from
`NEFilterDataProvider`. If the framework floods or the extension crashes,
macOS stops delivering flows and defaults to allow. OnionGate’s provider
still defaults to drop for flows it sees; flows it never sees mark the
session **Degraded**. Loading the extension needs Apple Developer Program
enrollment. Do not disable SIP to “see Apple traffic.”
- **Helper authentication on unsigned debug builds is UID, not code signature.**
Same-user malware can ask that helper to disable the lock. A signed macOS
helper also requires the peer's Team ID / `com.adamsiwiec.oniongate`
Expand Down
6 changes: 6 additions & 0 deletions docs/reference/third-party.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ The exact immutable archive hashes are in
[`scripts/dependencies.sha256`](https://github.com/irruptio-security/oniongate/blob/main/scripts/dependencies.sha256).
The staging script rejects unlisted or mismatched archives.

## Prior art (not derived)

| Upstream | License | What we used |
| --- | --- | --- |
| [Objective-See LuLu](https://github.com/objective-see/LuLu) | GPL-3.0 | The Apple API pattern: a `NEFilterDataProvider` that intercepts outbound flows. OnionGate’s extension, classifier, default-drop policy, and UI are original. LuLu’s tree, rule engine, and “allow Apple / allow installed programs” defaults are not vendored. |

## Notices and source

The distributable notices are maintained in
Expand Down
14 changes: 13 additions & 1 deletion docs/reference/threat-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ OnionGate aims to:
- Malware prevention, exploit containment, or a general endpoint-security
product.
- Paths listed in [Residual leaks](/reference/residual-leaks), including NECP
flows that never hit `pf` and the same-host Tor-endpoint allowlist hole.
flows that never hit `pf`, Apple processes hidden from a Network Extension,
and the same-host Tor-endpoint allowlist hole.
- UDP tunnelling, torrenting, streaming, gaming, or stable geolocation.
- Protecting a local onion service that is itself vulnerable.
- Guaranteeing that a moved, renamed, helper, or child process still matches a
Expand Down Expand Up @@ -63,6 +64,17 @@ deliberately direct. Under **All except selected**, selected applications are
deliberately direct and the Tor-routed remainder shares a default isolation
context.

### Connection filter (macOS Network Extension)

The optional filter is a LuLu-style `NEFilterDataProvider`. It holds and
drops outbound flows that are not already loopback, DHCP, TUN, or a live Tor
endpoint. It is **not** the enforcement path. `pf` and TUN remain the
fail-closed lock. The provider’s default verdict is drop. Apple can hide
processes from the extension and can fail open when the framework floods or
the extension crashes; that marks the session Degraded. Loading it is blocked
on Apple Developer Program enrollment. There is no persistent “allow this app
on clearnet” rule.

### Session Guard

Guarded applications receive isolated SOCKS credentials and must not use a
Expand Down
57 changes: 57 additions & 0 deletions macos/OnionGateFilter/Classifier.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// OnionGate connection filter — flow classifier.
//
// Same rules as src-tauri/src/ne_filter.rs::classify. Keep them in lockstep.
// Default is drop. Allow is only loopback, DHCP, live Tor endpoints, and
// local OnionGate SOCKS/control/DNS.

import Foundation

enum FilterVerdict: String {
case allow
case drop
}

struct FilterAllowlist {
var sessionActive: Bool
var torEndpoints: Set<String>
}

enum FilterClassifier {
static func isLoopback(_ host: String) -> Bool {
host == "127.0.0.1" || host == "::1" || host == "localhost"
}

static func isDhcp(port: UInt16, proto: String) -> Bool {
proto.lowercased() == "udp" && (port == 67 || port == 68)
}

static func isTunV4(_ host: String) -> Bool {
let parts = host.split(separator: ".").compactMap { UInt8($0) }
guard parts.count == 4 else { return false }
return parts[0] == 172 && parts[1] == 19 && parts[2] == 0 && parts[3] <= 3
}

static func classify(
remoteHost: String,
remotePort: UInt16,
proto: String,
allowlist: FilterAllowlist
) -> FilterVerdict {
if !allowlist.sessionActive {
return .allow
}
if isLoopback(remoteHost) {
return .allow
}
if isDhcp(port: remotePort, proto: proto) {
return .allow
}
if isTunV4(remoteHost) {
return .allow
}
if allowlist.torEndpoints.contains(remoteHost) {
return .allow
}
return .drop
}
}
Loading
Loading