mirror of
https://github.com/usestrix/firewall.git
synced 2026-11-09 05:19:35 -10:00
Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c7b08a9dee | ||
|
|
c8449338bc | ||
|
|
3308978a38 | ||
|
|
1acd02fc37 | ||
|
|
45c48874f4 | ||
|
|
383f974eb7 | ||
|
|
733e751fb9 |
No files matched your search
@@ -0,0 +1,93 @@
|
||||
# Strix's Dependency Firewall
|
||||
|
||||
Stops malicious packages before they install. Built by [Strix](https://strix.ai/blog/firewall-announcement).
|
||||
|
||||
Screening runs on your device: downloads go straight to the registry, never through a Strix server.
|
||||
Each package's name and version are looked up [Strix's malicious-package blocklist](https://github.com/usestrix/firewall-blocklist), so installs are blocked system-wide *before* reaching your machine.
|
||||
|
||||
## Install
|
||||
|
||||
**Machine-wide**, from the [latest release](https://github.com/usestrix/firewall/releases/latest):
|
||||
|
||||
- macOS: open `Firewall.pkg`, then approve the extension when macOS asks.
|
||||
- Windows: run the `.msi` as an admin.
|
||||
|
||||
**Prefix-mode**
|
||||
|
||||
cargo:
|
||||
```sh
|
||||
cargo install strix-firewall
|
||||
```
|
||||
|
||||
curl:
|
||||
```sh
|
||||
curl -LsSf https://github.com/usestrix/firewall/releases/latest/download/strix-firewall-installer.sh | sh
|
||||
```
|
||||
|
||||
Powershell:
|
||||
```powershell
|
||||
powershell -c "irm https://github.com/usestrix/firewall/releases/latest/download/strix-firewall-installer.ps1 | iex"
|
||||
```
|
||||
|
||||
## Platforms
|
||||
|
||||
| | Machine-wide | Command prefix |
|
||||
|---|---|---|
|
||||
| macOS 13+ | ✅ Apple silicon or T2 chip | ✅ |
|
||||
| Windows 10/11, x86-64 | ✅ with a TPM 2.0 | ✅ |
|
||||
| Linux (glibc 2.35+), x86-64 or arm64 | 🚧 Coming Soon | ✅ |
|
||||
|
||||
## Supported registries
|
||||
|
||||
| Registry | Package managers |
|
||||
|---|---|
|
||||
| npm | npm, pnpm, bun |
|
||||
| PyPI | pip, uv, pdm, pipenv |
|
||||
| crates.io | cargo |
|
||||
| Go module proxy | go |
|
||||
| RubyGems | gem |
|
||||
|
||||
Mirrors of these, such as Artifactory or Nexus, can be added in via a config file.
|
||||
|
||||
## Features
|
||||
|
||||
- **Up-to-the-minute data:** Strix constantly monitors updates to top packages (covering 99% of installed packages) to scan for malware, and publishes the results. We also consult [OSSF's blocklist](https://github.com/ossf/malicious-packages) and [OSV](osv.dev), as well as other public reports. Updates take effect without any user action.
|
||||
- **Holds back new versions.** New packages are blocked by default, since malware is usually detected within a day. (Although this can be disabled, configured, and bypassed).
|
||||
- **Alerts you.** On macOS and Windows a popup names the blocked package and links its advisory.
|
||||
- **Two modes:**
|
||||
- **Machine-wide:** screens downloads from every app, across your entire machine.
|
||||
- **Command prefix:** e.g., `firewall npm install left-pad`, or create an alias with `alias npm='firewall npm`.
|
||||
|
||||
## Configuration
|
||||
|
||||
Settings are in a `config.json`:
|
||||
|
||||
- Machine-wide: `/Library/Application Support/Firewall/` on macOS, `%ProgramData%\Strix Firewall\`
|
||||
on Windows. Only an admin can edit it; restart from the menu bar or tray to apply.
|
||||
- Command prefix: `~/.config/firewall/` on Linux, `~/Library/Application Support/com.usestrix.firewall/`
|
||||
on macOS, `%APPDATA%\usestrix\firewall\config\` on Windows.
|
||||
|
||||
```json
|
||||
{
|
||||
"min_age_hours": 48,
|
||||
"allow": { "npm": [{ "name": "left-pad", "version": "1.3.0" }] },
|
||||
"registries": {
|
||||
"npm": ["https://npm.example.com/"],
|
||||
"pypi": ["https://pypi.example.com/"]
|
||||
},
|
||||
"telemetry": false
|
||||
}
|
||||
```
|
||||
|
||||
| Key | Default | |
|
||||
|---|---|---|
|
||||
| `min_age_hours` | `24` | How long new versions are held back. `0` turns this off. |
|
||||
| `allow` | none | Packages exempt from the hold, by ecosystem (`npm`, `pypi`, `cargo`, `gem`, `go`). Omit `version` for every version. Known malware is still blocked. |
|
||||
| `registries` | none | Mirrors, by the registry they copy (`npm`, `pypi`, `gem`, `go`, `cargo/index`). Both modes screen every mirror listed; command-prefix mode also downloads from the first one. |
|
||||
| `fail_open` | `true` | Allow a package when it can't be checked. |
|
||||
| `telemetry` | `true` | Log anonymized usage events to Strix. |
|
||||
| `port` | `8080` | Local port for command-prefix mode. |
|
||||
|
||||
## License
|
||||
|
||||
Apache 2.0. See [LICENSE](LICENSE).
|
||||
@@ -38,17 +38,14 @@ path = "src/main.rs"
|
||||
[[bin]]
|
||||
name = "firewall-service"
|
||||
path = "src/bin/firewall-service.rs"
|
||||
required-features = ["machine-wide"]
|
||||
|
||||
[[bin]]
|
||||
name = "firewall-tray"
|
||||
path = "src/bin/firewall-tray.rs"
|
||||
required-features = ["machine-wide"]
|
||||
|
||||
[[bin]]
|
||||
name = "firewall-uninstall"
|
||||
path = "src/bin/firewall-uninstall.rs"
|
||||
required-features = ["machine-wide"]
|
||||
|
||||
[[example]]
|
||||
name = "proxy-test"
|
||||
@@ -90,10 +87,6 @@ path = "tests/tpm.rs"
|
||||
name = "managers"
|
||||
path = "tests/managers.rs"
|
||||
|
||||
[features]
|
||||
# Builds the machine-wide binaries: the Windows service and tray, and the macOS uninstaller.
|
||||
machine-wide = []
|
||||
|
||||
[dependencies]
|
||||
clap = "4.6.6"
|
||||
directories = "6.0.0"
|
||||
|
||||
@@ -26,7 +26,6 @@ use std::net::{IpAddr, Ipv4Addr};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tracing_appender::rolling::{RollingFileAppender, Rotation};
|
||||
use windows::Win32::Foundation::ERROR_GEN_FAILURE;
|
||||
use windows_service::service::*;
|
||||
use windows_service::service_control_handler::{
|
||||
self, ServiceControlHandlerResult, ServiceStatusHandle,
|
||||
@@ -74,12 +73,8 @@ fn run_until_stopped() -> Result<(), BoxError> {
|
||||
| ServiceControl::Shutdown
|
||||
| ServiceControl::Pause
|
||||
| ServiceControl::Continue => {
|
||||
// Answered once handled, so a failed pause fails the caller's ControlService.
|
||||
let (reply, handled) = tokio::sync::oneshot::channel();
|
||||
let _ = control_sender.send((control, reply));
|
||||
handled
|
||||
.blocking_recv()
|
||||
.unwrap_or(ServiceControlHandlerResult::NoError)
|
||||
let _ = control_sender.send(control);
|
||||
ServiceControlHandlerResult::NoError
|
||||
}
|
||||
ServiceControl::Interrogate => ServiceControlHandlerResult::NoError,
|
||||
_ => ServiceControlHandlerResult::NotImplemented,
|
||||
@@ -94,26 +89,16 @@ fn run_until_stopped() -> Result<(), BoxError> {
|
||||
}
|
||||
};
|
||||
// Only pausing removes our hosts entries; a stop or crash keeps them, failing closed.
|
||||
while let Some((control, reply)) = control_receiver.recv().await {
|
||||
while let Some(control) = control_receiver.recv().await {
|
||||
let (hosts, state) = match control {
|
||||
ServiceControl::Pause => (&[][..], ServiceState::Paused),
|
||||
ServiceControl::Continue => (&hosts[..], ServiceState::Running),
|
||||
_ => break,
|
||||
};
|
||||
let result = match write_hosts_entries(hosts) {
|
||||
Ok(()) => {
|
||||
set_status(&status, state, false)?;
|
||||
ServiceControlHandlerResult::NoError
|
||||
}
|
||||
Err(error) => {
|
||||
tracing::error!(%error, "could not rewrite the hosts file");
|
||||
let code = error
|
||||
.raw_os_error()
|
||||
.map_or(ERROR_GEN_FAILURE.0, |code| code as u32);
|
||||
ServiceControlHandlerResult::Other(code)
|
||||
}
|
||||
};
|
||||
let _ = reply.send(result);
|
||||
match write_hosts_entries(hosts) {
|
||||
Ok(()) => set_status(&status, state, false)?,
|
||||
Err(error) => tracing::error!(%error, "could not rewrite the hosts file"),
|
||||
}
|
||||
}
|
||||
Ok::<_, BoxError>(())
|
||||
});
|
||||
@@ -162,7 +147,7 @@ async fn start_screening(status: &ServiceStatusHandle) -> Result<Vec<&'static st
|
||||
resolver: resolver.clone(),
|
||||
service: service.clone(),
|
||||
};
|
||||
let upstream = ConsumeErrLayer::trace_as_warning().into_layer(upstream);
|
||||
let upstream = ConsumeErrLayer::trace_as_debug().into_layer(upstream);
|
||||
tokio::spawn(listen_on(address, port, &exec)?.serve(upstream));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,11 +144,7 @@ impl Tray {
|
||||
}
|
||||
|
||||
fn show_error(error: BoxError) {
|
||||
let text = match error.source() {
|
||||
Some(source) => format!("{error}: {source}"),
|
||||
None => error.to_string(),
|
||||
};
|
||||
let text = HSTRING::from(text);
|
||||
let text = HSTRING::from(error.to_string());
|
||||
unsafe { MessageBoxW(None, &text, w!("Strix Firewall"), MB_ICONERROR) };
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ ci = "github"
|
||||
installers = ["shell", "powershell", "homebrew", "msi"]
|
||||
# core/wix/main.wxs is edited to install the service.
|
||||
allow-dirty = ["msi"]
|
||||
features = ["machine-wide"]
|
||||
tap = "usestrix/homebrew-tap"
|
||||
publish-jobs = ["homebrew", "./publish-crates"]
|
||||
targets = ["aarch64-apple-darwin", "x86_64-apple-darwin", "aarch64-unknown-linux-gnu", "x86_64-unknown-linux-gnu", "x86_64-pc-windows-msvc"]
|
||||
@@ -2,7 +2,6 @@ import AppKit
|
||||
import NetworkExtension
|
||||
import OSLog
|
||||
import Security
|
||||
import ServiceManagement
|
||||
import SystemExtensions
|
||||
|
||||
enum StepStatus: Equatable {
|
||||
@@ -115,11 +114,6 @@ final class FirewallModel: NSObject, ObservableObject {
|
||||
work: trustCertificateAuthority)
|
||||
else { return }
|
||||
|
||||
do {
|
||||
try SMAppService.mainApp.register()
|
||||
} catch {
|
||||
log("Could not open the app at login: \(error.localizedDescription)")
|
||||
}
|
||||
completed = true
|
||||
log("Setup complete")
|
||||
offerRestart()
|
||||
@@ -160,7 +154,6 @@ final class FirewallModel: NSObject, ObservableObject {
|
||||
await removeConfiguration()
|
||||
let strandedKey = removeStoredCA()
|
||||
await deactivate()
|
||||
try? await SMAppService.mainApp.unregister()
|
||||
if let strandedKey { trustStep = .failed(strandedKey) }
|
||||
|
||||
let failed = [extensionStep, screeningStep, trustStep].contains(where: \.isFailed)
|
||||
|
||||
+1
-1
@@ -133,7 +133,7 @@ targets:
|
||||
arm64|arm64e) triple=aarch64-apple-darwin ;;
|
||||
x86_64) triple=x86_64-apple-darwin ;;
|
||||
esac
|
||||
cargo build --bin firewall-uninstall --features machine-wide --target $triple $([ "$CONFIGURATION" = Debug ] || echo --release)
|
||||
cargo build --bin firewall-uninstall --target $triple $([ "$CONFIGURATION" = Debug ] || echo --release)
|
||||
bins="$bins target/$triple/$CARGO_PROFILE/firewall-uninstall"
|
||||
done
|
||||
lipo -create $bins -output "$TARGET_BUILD_DIR/$EXECUTABLE_FOLDER_PATH/firewall-uninstall"
|
||||
Reference in new issue
Block a user