feat(security): finish Windows firewall Public opt-in wiring + vuln-disclosure + doc cleanup

Firewall (the service.rs core landed in efb1ba2): scope the web-console rule
(TCP 47992) to Domain+Private by default with a `--allow-public-network` opt-in
that deletes-then-re-adds the rule, and add the installer "Allow connections on
Public networks" task (unchecked) forwarding the flag to `service install` and
`web setup`. Default is now trusted-networks-only; Public is explicit.

Vulnerability disclosure: SECURITY.md (report to security@punktfunk.com, scope,
SLAs, safe harbor), a Gitea issue-template contact link, a README security line,
and a Reporting section on the docs Security page.

Docs: the Security page now documents the Private/Domain firewall default (and
how to fix a misclassified-Public network / opt in); removed internal design-doc
and CLAUDE.md links from the user-facing docs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-03 14:08:17 +00:00
parent efb1ba26d7
commit b9fde03f1e
9 changed files with 159 additions and 27 deletions
+9
View File
@@ -0,0 +1,9 @@
# Shown on the "new issue" chooser so security reports go to the private channel, not a public issue.
blank_issues_enabled: true
contact_links:
- name: 🔒 Report a security vulnerability
url: https://git.unom.io/unom/punktfunk/src/branch/main/SECURITY.md
about: >-
Found a security issue? Please report it privately by email to security@punktfunk.com — do not
open a public issue, so other users aren't exposed before a fix ships. See SECURITY.md for the
full policy.
+3 -1
View File
@@ -15,6 +15,9 @@ your local network.
💬 **Community: [Discord](https://discord.gg/kaPNvzMuGU)** — chat, support, and **Android beta 💬 **Community: [Discord](https://discord.gg/kaPNvzMuGU)** — chat, support, and **Android beta
access** · **[r/Punktfunk](https://www.reddit.com/r/Punktfunk/)**. access** · **[r/Punktfunk](https://www.reddit.com/r/Punktfunk/)**.
🔒 **Security:** found a vulnerability? Report it privately to **security@punktfunk.com** — see
[SECURITY.md](SECURITY.md). Please don't open a public issue.
punktfunk pairs a **virtual-display streaming host** with native clients on every platform. It speaks punktfunk pairs a **virtual-display streaming host** with native clients on every platform. It speaks
the existing **GameStream** protocol, so any [Moonlight](https://moonlight-stream.org/) client works the existing **GameStream** protocol, so any [Moonlight](https://moonlight-stream.org/) client works
day one — and adds its own faster **`punktfunk/1`** protocol that breaks the ~1 Gbps FEC wall with a day one — and adds its own faster **`punktfunk/1`** protocol that breaks the ~1 Gbps FEC wall with a
@@ -138,7 +141,6 @@ clients/
web/ web console (TanStack) over the management API — status · devices · pairing · GPUs · performance · logs web/ web console (TanStack) over the management API — status · devices · pairing · GPUs · performance · logs
packaging/ apt · rpm / COPR · Arch · Flatpak · Bazzite bootc image packaging/ apt · rpm / COPR · Arch · Flatpak · Bazzite bootc image
docs-site/ public documentation site (Fumadocs) — https://docs.punktfunk.unom.io docs-site/ public documentation site (Fumadocs) — https://docs.punktfunk.unom.io
design/ design notes & deep-dive plans (index: design/README.md)
include/punktfunk_core.h cbindgen-generated C header (checked in) include/punktfunk_core.h cbindgen-generated C header (checked in)
tools/ latency-probe · loss-harness (measurement) tools/ latency-probe · loss-harness (measurement)
``` ```
+69
View File
@@ -0,0 +1,69 @@
# Security Policy
punktfunk is a low-latency desktop/game streaming stack. A host is effectively remote control of a
machine, so we take security reports seriously and appreciate responsible disclosure.
## Reporting a vulnerability
**Please report security issues privately by email to security@punktfunk.com.**
Do **not** open a public issue, pull request, or chat/forum post for a suspected vulnerability — that
exposes other users before a fix exists.
### What to include
The more of this you can give us, the faster we can act:
- The component and version (e.g. `punktfunk-host 0.6.0`, Windows or Linux, which client).
- The impact — what an attacker can do, and from what position (same LAN, a local service account,
admin, a paired client, …).
- Steps to reproduce, a proof-of-concept, or a crash/log if you have one.
- Any suggested fix or mitigation (optional).
## What to expect
We're a small team, so timelines are best-effort, but we commit to:
- **Acknowledge** your report within **3 business days**.
- Give an **initial assessment** (severity + whether we can reproduce) within about **7 days**.
- Keep you updated, and tell you when a fix ships.
- **Credit** you in the advisory / release notes when the fix is public — unless you'd rather stay
anonymous.
We practice **coordinated disclosure**: please give us reasonable time to release a fix before
publishing details. We aim to resolve valid issues within **90 days** and will agree a disclosure
date with you.
## Scope
In scope — the code in this repository:
- The host (`punktfunk-host`), its Windows drivers, and the protocol/crypto core (`punktfunk-core`).
- The native clients (Apple, Linux, Windows, Android), the web management console, and the management
API.
Known limits — documented behavior, not vulnerabilities (see
https://docs.punktfunk.unom.io/docs/security):
- **Admin/SYSTEM already on the host = out of scope.** An attacker who is already administrator or
SYSTEM on the host owns the machine regardless of punktfunk.
- **The virtual display is a real monitor** — any process already in the interactive desktop session
can capture it via the normal OS screen-capture APIs, exactly as it could a physical monitor.
- **GameStream/Moonlight compatibility** (`--gamestream`) uses legacy encryption and is documented as
opt-in, trusted-LAN-only.
- **Public-internet exposure is unsupported** — issues that only arise from exposing the host to the
WAN are expected; keep the host on a trusted LAN or a VPN.
If you're unsure whether something is in scope, report it anyway — we'd rather hear about it.
## Safe harbor
We consider good-faith security research that follows this policy to be authorized, and we won't
pursue legal action against researchers who:
- make a good-faith effort to avoid privacy violations, data loss, and service disruption,
- only test systems they own or have explicit permission to test,
- give us reasonable time to remediate before public disclosure,
- don't exfiltrate more data than needed to demonstrate the issue.
Thank you for helping keep punktfunk and its users safe.
@@ -392,6 +392,21 @@ fn web_setup(args: &[String]) -> Result<()> {
register_web_task(&cmd)?; register_web_task(&cmd)?;
// 4. firewall: inbound TCP 47992. The console serves HTTPS (HTTP/1.1 over TLS) with the host's // 4. firewall: inbound TCP 47992. The console serves HTTPS (HTTP/1.1 over TLS) with the host's
// identity cert. (No UDP/HTTP-3: browsers won't use QUIC against a self-signed/no-SAN cert.) // identity cert. (No UDP/HTTP-3: browsers won't use QUIC against a self-signed/no-SAN cert.)
// Scoped to the same profiles as the streaming ports — Domain + Private by default, Public
// only with `--allow-public-network`. Delete any prior rule first so an upgrade re-scopes it
// instead of stacking a second (possibly all-profiles) rule behind the new one.
let fw_profile =
crate::service::firewall_profile_arg(crate::service::allow_public_network(args));
run_quiet(
"netsh",
&[
"advfirewall",
"firewall",
"delete",
"rule",
"name=punktfunk web console (TCP 47992)",
],
);
if !run_quiet( if !run_quiet(
"netsh", "netsh",
&[ &[
@@ -404,6 +419,7 @@ fn web_setup(args: &[String]) -> Result<()> {
"action=allow", "action=allow",
"protocol=TCP", "protocol=TCP",
"localport=47992", "localport=47992",
fw_profile,
], ],
) { ) {
eprintln!("warning: could not add the firewall rule for TCP 47992"); eprintln!("warning: could not add the firewall rule for TCP 47992");
+2 -2
View File
@@ -19,8 +19,8 @@ punktfunk-host serve
Add `--gamestream` (alias `--moonlight`) to **also** run the GameStream/Moonlight-compatible planes Add `--gamestream` (alias `--moonlight`) to **also** run the GameStream/Moonlight-compatible planes
(nvhttp pairing, RTSP, ENet control, `_nvstream` mDNS) — required for stock [Moonlight](/docs/moonlight) (nvhttp pairing, RTSP, ENet control, `_nvstream` mDNS) — required for stock [Moonlight](/docs/moonlight)
clients. This is **opt-in** because GameStream carries inherent on-path weaknesses (pairing over plain clients. This is **opt-in** because GameStream carries inherent on-path weaknesses (pairing over plain
HTTP; its legacy control encryption can reuse GCM nonces — security-review #5/#9), so enable it **only HTTP; its legacy control encryption can reuse GCM nonces), so enable it **only on a trusted LAN**. The
on a trusted LAN**. The native plane is immune to those issues. native plane is immune to those issues.
```sh ```sh
punktfunk-host serve --gamestream punktfunk-host serve --gamestream
+11 -5
View File
@@ -8,8 +8,8 @@ always-available host, run it as a service. There are two cases.
> The bundled unit `scripts/punktfunk-host.service` runs `serve --gamestream`, so it serves both the > The bundled unit `scripts/punktfunk-host.service` runs `serve --gamestream`, so it serves both the
> native `punktfunk/1` plane and stock [Moonlight](/docs/moonlight) clients. For a **secure native-only > native `punktfunk/1` plane and stock [Moonlight](/docs/moonlight) clients. For a **secure native-only
> host** (no GameStream — its pairing runs over plain HTTP and its legacy encryption is weaker; > host** (no GameStream — its pairing runs over plain HTTP and its legacy encryption is weaker), drop
> security-review #5/#9), drop `--gamestream` from the unit's `ExecStart` and use bare `serve`. > `--gamestream` from the unit's `ExecStart` and use bare `serve`.
## A. A desktop you log into ## A. A desktop you log into
@@ -101,9 +101,15 @@ registers + starts the service for you (`/VERYSILENT` for unattended). Upgrades
handled through Add/Remove Programs. handled through Add/Remove Programs.
Prefer the CLI? Run `punktfunk-host service install` from an elevated prompt — see Prefer the CLI? Run `punktfunk-host service install` from an elevated prompt — see
[Windows service](https://git.unom.io/unom/punktfunk/src/branch/main/docs/windows-service.md). For [Windows Host](/docs/windows-host). For hardware encode you need a GPU — NVIDIA (NVENC), AMD (AMF), or
hardware encode you need a GPU — NVIDIA (NVENC), AMD (AMF), or Intel (QSV); the host falls back to Intel (QSV); the host falls back to software H.264 without one.
software H.264 without one.
> **Firewall scope.** The installer opens the streaming + console ports on **Private and Domain**
> networks only — not **Public**. If your LAN is (mis)classified Public, clients won't connect until
> you set it to Private (Windows Settings → Network), and the host logs a warning when it's on a Public
> network. For a trusted network Windows insists is Public, tick **"Allow connections on Public
> networks"** at install (or pass `--allow-public-network` to `service install`). See
> [Security & Safe Use](/docs/security) for the reasoning.
## Verifying ## Verifying
+30 -15
View File
@@ -54,12 +54,21 @@ If you want to stream from outside your home, tunnel in instead of opening up:
- **Don't** map a router port to the host. A port-forward turns "trusted LAN service" into - **Don't** map a router port to the host. A port-forward turns "trusted LAN service" into
"internet-facing service" with none of the protections that implies. "internet-facing service" with none of the protections that implies.
A note for **portable machines**: the installer opens the streaming ports on the firewall for *all* A note on **Windows network profiles**: the installer opens the streaming and console ports only on
network profiles, including Public. That's convenient at home but means that if you take a laptop host **Private and Domain** networks — the profiles Windows uses for networks you've marked as trusted. On a
onto an untrusted network — a café, a hotel, a conference — other devices on that network can reach the network Windows classifies as **Public** (cafés, hotels, conferences — the default for unknown
ports and attempt to pair. Pairing still protects you (an attacker who doesn't know the PIN can't get networks), the ports stay **closed**, so a laptop host won't accept connections there. That's the safe
in), but the safest habit is to stop the host service, or firewall it off, when you're on a network you default, and it's the behavior you want on the move. Two things follow from it:
don't control.
- **If your home network is *misclassified* as Public, clients won't connect.** Set it to Private
(Windows Settings → Network & internet → your network → **Private network**). The host also logs a
warning at startup when it detects it's on a Public network, so this doesn't fail silently.
- **If you have a trusted network that Windows insists on marking Public** (some headless or
no-gateway LAN setups), you can opt in during install — the **"Allow connections on Public
networks"** checkbox (off by default). Only do this for a network you actually trust.
Either way, pairing is what ultimately gates access — but keeping the host off untrusted networks is
the first line, and on the move the safest habit is still to stop the service when you don't need it.
## What actually protects you ## What actually protects you
@@ -109,9 +118,7 @@ We mitigate this deliberately:
full-system. (This is why punktfunk dropped ViGEmBus.) full-system. (This is why punktfunk dropped ViGEmBus.)
- **Sealed internal channels.** The desktop-frame ring and the gamepad input/output channels are - **Sealed internal channels.** The desktop-frame ring and the gamepad input/output channels are
passed between the host and its drivers as duplicated handles to unnamed objects, so another local passed between the host and its drivers as duplicated handles to unnamed objects, so another local
service can't open them by name to read your screen or forge controller input. (Details: service can't open them by name to read your screen or forge controller input.
[`idd-push-security.md`](https://git.unom.io/unom/punktfunk/src/branch/main/design/idd-push-security.md)
and [`gamepad-channel-sealing.md`](https://git.unom.io/unom/punktfunk/src/branch/main/design/gamepad-channel-sealing.md).)
- **Secrets are locked down.** The management token, the host identity key, and the console password - **Secrets are locked down.** The management token, the host identity key, and the console password
are stored with Administrators/SYSTEM-only permissions. are stored with Administrators/SYSTEM-only permissions.
@@ -142,12 +149,20 @@ applies: keep it on a trusted LAN or a VPN, require pairing, and don't expose it
- **Keep the host updated** — security fixes ship in new builds. - **Keep the host updated** — security fixes ship in new builds.
- **On portable hosts**, stop the service when you're on an untrusted network. - **On portable hosts**, stop the service when you're on an untrusted network.
## For the technically curious ## Reporting a vulnerability
The deeper security design lives in the repository, and it's candid about residual limits: Found a security issue? **Email [security@punktfunk.com](mailto:security@punktfunk.com).** Please
don't open a public issue, pull request, or chat post for a suspected vulnerability — that exposes
other users before a fix is available.
- [`design/idd-push-security.md`](https://git.unom.io/unom/punktfunk/src/branch/main/design/idd-push-security.md) — the sealed frame channel (why the Windows capture path is isolated), and its honest floor. Helpful things to include:
- [`design/gamepad-channel-sealing.md`](https://git.unom.io/unom/punktfunk/src/branch/main/design/gamepad-channel-sealing.md) — the sealed gamepad channel.
- [`design/security-review-2026-06-28.md`](https://git.unom.io/unom/punktfunk/src/branch/main/design/security-review-2026-06-28.md) and [`design/security-review.md`](https://git.unom.io/unom/punktfunk/src/branch/main/design/security-review.md) — the standing security reviews.
Found a security issue? Please report it privately rather than opening a public issue. - The component and version — e.g. `punktfunk-host 0.6.0`, Windows or Linux, and which client.
- The impact, and the attacker's position (same LAN, a paired client, a local service account,
admin, …).
- Steps to reproduce, a proof-of-concept, or a crash/log if you have one.
We acknowledge reports within **3 business days** and practice coordinated disclosure — we'll keep
you posted, agree a disclosure date, and credit you when the fix ships (unless you'd rather stay
anonymous). The full policy is in
[`SECURITY.md`](https://git.unom.io/unom/punktfunk/src/branch/main/SECURITY.md).
+1 -2
View File
@@ -4,8 +4,7 @@ description: "Where the work stands across the core, the host, and the native cl
--- ---
A high-level view of where punktfunk stands. The ordered plan of work is on the A high-level view of where punktfunk stands. The ordered plan of work is on the
[Roadmap](/docs/roadmap), and milestone-level detail lives in [Roadmap](/docs/roadmap).
[`CLAUDE.md`](https://git.unom.io/unom/punktfunk/src/branch/main/CLAUDE.md).
## Milestones at a glance ## Milestones at a glance
+18 -2
View File
@@ -146,6 +146,11 @@ Name: "installhdrlayer"; Description: "Install the HDR Vulkan layer (lets Vulkan
; in host.env; a hand-customized value is left alone). Checked = the Moonlight-compatible unified ; in host.env; a hand-customized value is left alone). Checked = the Moonlight-compatible unified
; host (the common Windows setup); unchecked = the secure native-only host (punktfunk clients only). ; host (the common Windows setup); unchecked = the secure native-only host (punktfunk clients only).
Name: "gamestream"; Description: "Enable GameStream (Moonlight) compatibility - lets stock Moonlight clients connect (uses legacy plain-HTTP pairing; for trusted LANs)" Name: "gamestream"; Description: "Enable GameStream (Moonlight) compatibility - lets stock Moonlight clients connect (uses legacy plain-HTTP pairing; for trusted LANs)"
; Firewall scope, forwarded as `--allow-public-network` to `service install` / `web setup`. Unchecked
; (default) = accept connections on Private + Domain networks only (the trusted-network profiles
; punktfunk is meant for). Check ONLY for a network you trust that Windows classifies as Public (e.g.
; some headless / no-gateway LAN setups) - it opens the streaming + console ports on Public too.
Name: "allowpublicfw"; Description: "Allow connections on Public networks (only for a trusted network Windows marks as Public)"; Flags: unchecked
Name: "startservice"; Description: "Start the punktfunk host service now (also starts on every boot)" Name: "startservice"; Description: "Start the punktfunk host service now (also starts on every boot)"
; The per-user status tray (punktfunk-tray.exe): shows running/stopped/failed at a glance and ; The per-user status tray (punktfunk-tray.exe): shows running/stopped/failed at a glance and
; offers open-console / start / stop / restart without a terminal. HKLM Run = every user who signs ; offers open-console / start / stop / restart without a terminal. HKLM Run = every user who signs
@@ -239,7 +244,7 @@ Filename: "powershell.exe"; \
; Register (or re-point, on upgrade - idempotent) the SYSTEM service from its FINAL {app} location: ; Register (or re-point, on upgrade - idempotent) the SYSTEM service from its FINAL {app} location:
; service install records current_exe() as the SCM binPath, so it must run from {app}, not {tmp}. ; service install records current_exe() as the SCM binPath, so it must run from {app}, not {tmp}.
; --gamestream=on|off carries the wizard's GameStream task choice into host.env's PUNKTFUNK_HOST_CMD. ; --gamestream=on|off carries the wizard's GameStream task choice into host.env's PUNKTFUNK_HOST_CMD.
Filename: "{app}\punktfunk-host.exe"; Parameters: "service install {code:GamestreamParam}"; WorkingDir: "{app}"; \ Filename: "{app}\punktfunk-host.exe"; Parameters: "service install {code:GamestreamParam}{code:PublicFwParam}"; WorkingDir: "{app}"; \
StatusMsg: "Registering the punktfunk host service..."; Flags: runhidden waituntilterminated StatusMsg: "Registering the punktfunk host service..."; Flags: runhidden waituntilterminated
Filename: "{app}\punktfunk-host.exe"; Parameters: "service start"; WorkingDir: "{app}"; \ Filename: "{app}\punktfunk-host.exe"; Parameters: "service start"; WorkingDir: "{app}"; \
StatusMsg: "Starting the punktfunk host service..."; Flags: runhidden waituntilterminated; Tasks: startservice StatusMsg: "Starting the punktfunk host service..."; Flags: runhidden waituntilterminated; Tasks: startservice
@@ -247,7 +252,7 @@ Filename: "{app}\punktfunk-host.exe"; Parameters: "service start"; WorkingDir: "
; Provision the console AFTER the host service is up (so the mgmt token exists): write the ACL'd ; Provision the console AFTER the host service is up (so the mgmt token exists): write the ACL'd
; login password, register the PunktfunkWeb scheduled task (boot, SYSTEM, restart-on-failure), ; login password, register the PunktfunkWeb scheduled task (boot, SYSTEM, restart-on-failure),
; open TCP 47992, and start it. {code:WebSetupParams} appends -PasswordFile only on a fresh install. ; open TCP 47992, and start it. {code:WebSetupParams} appends -PasswordFile only on a fresh install.
Filename: "{app}\punktfunk-host.exe"; Parameters: "web setup {code:WebSetupParams}"; WorkingDir: "{app}"; \ Filename: "{app}\punktfunk-host.exe"; Parameters: "web setup {code:WebSetupParams}{code:PublicFwParam}"; WorkingDir: "{app}"; \
StatusMsg: "Setting up the punktfunk web console..."; Flags: runhidden waituntilterminated StatusMsg: "Setting up the punktfunk web console..."; Flags: runhidden waituntilterminated
#endif #endif
; Launch the status tray as the SIGNED-IN user (not the elevated install user) right away, so the ; Launch the status tray as the SIGNED-IN user (not the elevated install user) right away, so the
@@ -291,6 +296,17 @@ begin
Result := '--gamestream=off'; Result := '--gamestream=off';
end; end;
{ Firewall scope: the "allowpublicfw" task opens the streaming + console ports on Public networks too
(default = Private/Domain only). Forwarded to both `service install` and `web setup`. Returns a
LEADING SPACE so it concatenates after the preceding {code:...} param without a gap. }
function PublicFwParam(Param: String): String;
begin
if WizardIsTaskSelected('allowpublicfw') then
Result := ' --allow-public-network'
else
Result := '';
end;
#ifdef WithWeb #ifdef WithWeb
var var
WebPwPage: TInputQueryWizardPage; WebPwPage: TInputQueryWizardPage;