diffstat for xdg-dbus-proxy-0.1.6 xdg-dbus-proxy-0.1.6

 debian/changelog                                                                                  |   12 
 debian/patches/GHSA-r7hp-698j-2h6c/Revert-Don-t-require-TALK-permission-for-broadcast-rules.patch |   83 +
 debian/patches/series                                                                             |   10 
 debian/patches/tests/tests-Add-access-control-rules-and-connections-that-match.patch              |  150 ++
 debian/patches/tests/tests-Assert-that-ability-to-own-names-is-enforced.patch                     |  132 +
 debian/patches/tests/tests-Avoid-a-warning-with-newer-GLib.patch                                  |   26 
 debian/patches/tests/tests-Factor-out-fixture_start_proxy.patch                                   |  144 ++
 debian/patches/tests/tests-Filter-and-count-incoming-messages-on-each-unsandbo.patch              |  195 ++
 debian/patches/tests/tests-Optionally-run-a-dbus-monitor.patch                                    |   66 
 debian/patches/tests/tests-Send-xdg-dbus-proxy-stdout-to-a-copy-of-its-stderr.patch               |   34 
 debian/patches/tests/tests-Test-method-calls-being-blocked-or-allowed.patch                       |  172 ++
 debian/patches/tests/tests-Test-that-messages-from-outside-sandbox-are-not-rec.patch              |  191 ++
 flatpak-proxy.c                                                                                   |   10 
 tests/test-proxy.c                                                                                |  693 +++++++++-
 14 files changed, 1884 insertions(+), 34 deletions(-)

diff -Nru xdg-dbus-proxy-0.1.6/debian/changelog xdg-dbus-proxy-0.1.6/debian/changelog
--- xdg-dbus-proxy-0.1.6/debian/changelog	2026-04-10 23:31:23.000000000 +0100
+++ xdg-dbus-proxy-0.1.6/debian/changelog	2026-08-11 13:43:30.000000000 +0100
@@ -1,3 +1,15 @@
+xdg-dbus-proxy (0.1.6-1+deb13u2) trixie-security; urgency=high
+
+  * d/p/GHSA-r7hp-698j-2h6c/*.patch:
+    Fix a vulnerability in access control for receiving broadcasts.
+    In 0.1.6 and 0.1.7, a malicious or compromised Flatpak app could
+    receive any broadcast D-Bus message on the session bus or the AT-SPI
+    bus, leading to unintended information disclosure.
+  * d/p/tests/*.patch:
+    Add automated test coverage for message filtering
+
+ -- Simon McVittie <smcv@debian.org>  Tue, 11 Aug 2026 13:43:30 +0100
+
 xdg-dbus-proxy (0.1.6-1+deb13u1) trixie-security; urgency=high
 
   * d/gbp.conf: Configure for trixie
diff -Nru xdg-dbus-proxy-0.1.6/debian/patches/GHSA-r7hp-698j-2h6c/Revert-Don-t-require-TALK-permission-for-broadcast-rules.patch xdg-dbus-proxy-0.1.6/debian/patches/GHSA-r7hp-698j-2h6c/Revert-Don-t-require-TALK-permission-for-broadcast-rules.patch
--- xdg-dbus-proxy-0.1.6/debian/patches/GHSA-r7hp-698j-2h6c/Revert-Don-t-require-TALK-permission-for-broadcast-rules.patch	1970-01-01 01:00:00.000000000 +0100
+++ xdg-dbus-proxy-0.1.6/debian/patches/GHSA-r7hp-698j-2h6c/Revert-Don-t-require-TALK-permission-for-broadcast-rules.patch	2026-08-11 13:43:30.000000000 +0100
@@ -0,0 +1,83 @@
+From: Simon McVittie <smcv@debian.org>
+Date: Sun, 26 Jul 2026 14:03:16 +0100
+Subject: Revert "Don't require TALK permission for broadcast rules"
+MIME-Version: 1.0
+Content-Type: text/plain; charset="utf-8"
+Content-Transfer-Encoding: 8bit
+
+The commit reverted here appears to have been based on a misunderstanding
+of how `--broadcast` rules were designed to work. Adding a `--broadcast` or
+`--call` rule gives the sandboxed D-Bus client a *limited* version of the
+TALK permission: it is allowed to call methods or listen for broadcasts
+(as appropriate), and it is allowed to call `StartServiceByName()`
+for the requested bus name.
+
+However, when the rules
+`--broadcast=org.a11y.atspi.Registry.EventListenerRegistered=@/org/a11y/atspi/registry`
+and
+`--broadcast=org.a11y.atspi.Registry.EventListenerDeregistered=@/org/a11y/atspi/registry`
+were added to Flatpak in https://github.com/flatpak/flatpak/pull/5828,
+the meaning of those rules was not as intended: they're matching bus names
+`org.a11y.atspi.Registry.EventListenerRegistered` and …Deregistered`,
+which in practice don't actually exist. As a result, the rules didn't
+appear to work until the Flatpak contributor either added `--talk`
+(which is *unrestricted* TALK access, clearly not desired), or altered
+the xdg-dbus-proxy code so that `--broadcast` would bypass the intended
+broadcast filtering.
+
+Allowing broadcasts in the presence of any rule with the TALK policy
+meant that adding *any* `--broadcast` rule would allow receiving
+*all* broadcasts, regardless of object path, interface or member name
+(any_filter_matches() would not be called), which was clearly not as
+intended.
+
+An attacker could use this to receive host-side broadcasts that are not
+intended to be visible to the sandboxed app, weakening the sandbox. For
+example, a malicious or compromised Flatpak app could receive any
+broadcast on the session bus or the AT-SPI bus.
+
+Undo this change. Instead, we should fix the match rules used on the
+AT-SPI bus by Flatpak to what they were intended to be, which I believe
+means
+`--broadcast=org.a11y.atspi.Registry=org.a11y.atspi.Registry.EventListenerRegistered@/org/a11y/atspi/registry`
+and the same for `…Deregistered`. If non-Flatpak app frameworks use
+xdg-dbus-proxy, they might need similar changes.
+
+Potential collateral damage from this change is that some Flatpak apps
+might have accidentally been relying on the ability to receive all
+broadcast signals on the session bus, which they would have been able
+to do since Flatpak 1.15.9. If such apps exist, they will need to be
+updated to add explicit `--talk-name` rules for the relevant D-Bus names.
+Again, if non-Flatpak app frameworks use xdg-dbus-proxy, those frameworks'
+apps might need similar changes.
+
+This reverts commit 029784535ed9cbec6c431b12ba1a9eca6c147055.
+
+Bug: https://github.com/flatpak/xdg-dbus-proxy/security/advisories/GHSA-r7hp-698j-2h6c
+Signed-off-by: Simon McVittie <smcv@debian.org>
+Origin: upstream, 0.1.8
+---
+ flatpak-proxy.c | 10 +++++-----
+ 1 file changed, 5 insertions(+), 5 deletions(-)
+
+diff --git a/flatpak-proxy.c b/flatpak-proxy.c
+index 8a4d597..a97e62b 100644
+--- a/flatpak-proxy.c
++++ b/flatpak-proxy.c
+@@ -2806,11 +2806,11 @@ got_buffer_from_bus (FlatpakProxyClient *client, ProxySide *side, Buffer *buffer
+           policy = flatpak_proxy_client_get_max_policy_and_matched (client, header->sender, &filters);
+ 
+           if (policy == FLATPAK_POLICY_OWN ||
+-              policy == FLATPAK_POLICY_TALK ||
+-              any_filter_matches (filters, FILTER_TYPE_BROADCAST,
+-                                  header->path,
+-                                  header->interface,
+-                                  header->member))
++              (policy == FLATPAK_POLICY_TALK &&
++               any_filter_matches (filters, FILTER_TYPE_BROADCAST,
++                                   header->path,
++                                   header->interface,
++                                   header->member)))
+             filtered = FALSE;
+ 
+           if (filtered)
diff -Nru xdg-dbus-proxy-0.1.6/debian/patches/series xdg-dbus-proxy-0.1.6/debian/patches/series
--- xdg-dbus-proxy-0.1.6/debian/patches/series	2026-04-10 23:31:23.000000000 +0100
+++ xdg-dbus-proxy-0.1.6/debian/patches/series	2026-08-11 13:43:30.000000000 +0100
@@ -1 +1,11 @@
 Improve-detection-of-eavesdrop-true.patch
+tests/tests-Send-xdg-dbus-proxy-stdout-to-a-copy-of-its-stderr.patch
+tests/tests-Avoid-a-warning-with-newer-GLib.patch
+tests/tests-Optionally-run-a-dbus-monitor.patch
+tests/tests-Factor-out-fixture_start_proxy.patch
+tests/tests-Add-access-control-rules-and-connections-that-match.patch
+tests/tests-Assert-that-ability-to-own-names-is-enforced.patch
+tests/tests-Filter-and-count-incoming-messages-on-each-unsandbo.patch
+tests/tests-Test-method-calls-being-blocked-or-allowed.patch
+GHSA-r7hp-698j-2h6c/Revert-Don-t-require-TALK-permission-for-broadcast-rules.patch
+tests/tests-Test-that-messages-from-outside-sandbox-are-not-rec.patch
diff -Nru xdg-dbus-proxy-0.1.6/debian/patches/tests/tests-Add-access-control-rules-and-connections-that-match.patch xdg-dbus-proxy-0.1.6/debian/patches/tests/tests-Add-access-control-rules-and-connections-that-match.patch
--- xdg-dbus-proxy-0.1.6/debian/patches/tests/tests-Add-access-control-rules-and-connections-that-match.patch	1970-01-01 01:00:00.000000000 +0100
+++ xdg-dbus-proxy-0.1.6/debian/patches/tests/tests-Add-access-control-rules-and-connections-that-match.patch	2026-08-11 13:43:30.000000000 +0100
@@ -0,0 +1,150 @@
+From: Simon McVittie <smcv@debian.org>
+Date: Sun, 26 Jul 2026 11:21:12 +0100
+Subject: tests: Add access-control rules,
+ and connections that match those rules
+
+Signed-off-by: Simon McVittie <smcv@debian.org>
+Origin: upstream, 0.1.8
+---
+ tests/test-proxy.c | 100 +++++++++++++++++++++++++++++++++++++++++++++++++++++
+ 1 file changed, 100 insertions(+)
+
+diff --git a/tests/test-proxy.c b/tests/test-proxy.c
+index 938a4a4..ed833c8 100644
+--- a/tests/test-proxy.c
++++ b/tests/test-proxy.c
+@@ -34,6 +34,31 @@
+ #define DBUS_PATH_DBUS "/org/freedesktop/DBus"
+ #define DBUS_INTERFACE_DBUS "org.freedesktop.DBus"
+ 
++#define DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER 1
++#define DBUS_REQUEST_NAME_REPLY_IN_QUEUE 2
++#define DBUS_REQUEST_NAME_REPLY_EXISTS 3
++#define DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER 4
++
++#define CANNOT_ACCESS_NAME "com.example.CannotAccess"
++#define CAN_SEE_NAME "com.example.CanSee"
++#define CAN_TALK_NAME "com.example.CanTalk"
++#define CAN_OWN_NAME "com.example.CanOwn"
++#define CAN_CALL_ANYTHING_NAME "com.example.CanCallAny"
++#define CAN_CALL_SOME_NAME "com.example.CanCallSome"
++#define CAN_RECEIVE_ANYTHING_NAME "com.example.CanReceiveAny"
++#define CAN_RECEIVE_SOME_NAME "com.example.CanReceiveSome"
++
++#define EXAMPLE_IFACE "net.example.AnyInterface"
++#define EXAMPLE_METHOD "Echo"
++#define EXAMPLE_SIGNAL "Shouted"
++#define EXAMPLE_PATH "/path"
++#define CAN_CALL_SOME_IFACE "org.example.CanCallThis"
++#define CAN_CALL_SOME_METHOD "OnlyThisMethod"
++#define CAN_CALL_SOME_PATH "/only/this/path"
++#define CAN_RECEIVE_SOME_IFACE "org.example.CanReceiveThis"
++#define CAN_RECEIVE_SOME_SIGNAL "JustThisSignal"
++#define CAN_RECEIVE_SOME_PATH "/just/this/path"
++
+ typedef struct
+ {
+   GDBusConnection *conn;
+@@ -60,6 +85,14 @@ connection_clear (Connection *self)
+ typedef struct
+ {
+   Connection proxied;
++  Connection cannot_access_conn;
++  Connection can_see_conn;
++  Connection can_talk_conn;
++  Connection can_own_conn;
++  Connection can_call_anything_conn;
++  Connection can_call_some_conn;
++  Connection can_receive_anything_conn;
++  Connection can_receive_some_conn;
+   GSubprocess *dbus_daemon;
+   GSubprocess *monitor;
+   GSubprocess *proxy;
+@@ -76,6 +109,55 @@ typedef struct
+   int dummy;
+ } Config;
+ 
++/*
++ * Open a direct connection to the bus
++ */
++static void
++fixture_connect (Fixture *f,
++                 Connection *conn,
++                 const char *name)
++{
++  g_autoptr(GError) error = NULL;
++
++  g_return_if_fail (conn != NULL);
++  g_return_if_fail (conn->conn == NULL);
++  conn->conn = g_dbus_connection_new_for_address_sync (f->dbus_address,
++                                                       (G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT
++                                                        | G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION),
++                                                       NULL,    /* observer */
++                                                       NULL,    /* cancellable */
++                                                       &error);
++  g_assert_no_error (error);
++  g_assert_nonnull (conn->conn);
++  conn->unique_name = g_dbus_connection_get_unique_name (conn->conn);
++
++  if (name != NULL)
++    {
++      g_autoptr(GVariant) tuple = NULL;
++      guint32 result = 0;
++
++      tuple = g_dbus_connection_call_sync (conn->conn,
++                                           DBUS_SERVICE_DBUS,
++                                           DBUS_PATH_DBUS,
++                                           DBUS_INTERFACE_DBUS,
++                                           "RequestName",
++                                           g_variant_new ("(su)",
++                                                           name,
++                                                           (G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT
++                                                           | G_BUS_NAME_OWNER_FLAGS_REPLACE
++                                                           | G_BUS_NAME_OWNER_FLAGS_DO_NOT_QUEUE)),
++                                           G_VARIANT_TYPE ("(u)"),
++                                           G_DBUS_CALL_FLAGS_NONE,
++                                           -1,
++                                           NULL,    /* cancellable */
++                                           &error);
++      g_assert_no_error (error);
++      g_assert_nonnull (tuple);
++      g_variant_get (tuple, "(u)", &result);
++      g_assert_cmpuint (result, ==, DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER);
++    }
++}
++
+ static void
+ setup (Fixture *f,
+        gconstpointer context G_GNUC_UNUSED)
+@@ -148,6 +230,15 @@ setup (Fixture *f,
+   f->proxy_socket = g_build_filename (f->temp_directory, "proxy", NULL);
+   escaped = g_dbus_address_escape_value (f->proxy_socket);
+   f->proxy_address = g_strdup_printf ("unix:path=%s", escaped);
++
++  fixture_connect (f, &f->cannot_access_conn, CANNOT_ACCESS_NAME);
++  fixture_connect (f, &f->can_see_conn, CAN_SEE_NAME);
++  fixture_connect (f, &f->can_talk_conn, CAN_TALK_NAME);
++  fixture_connect (f, &f->can_own_conn, CAN_OWN_NAME);
++  fixture_connect (f, &f->can_call_anything_conn, CAN_CALL_ANYTHING_NAME);
++  fixture_connect (f, &f->can_call_some_conn, CAN_CALL_SOME_NAME);
++  fixture_connect (f, &f->can_receive_anything_conn, CAN_RECEIVE_ANYTHING_NAME);
++  fixture_connect (f, &f->can_receive_some_conn, CAN_RECEIVE_SOME_NAME);
+ }
+ 
+ enum
+@@ -184,6 +275,15 @@ fixture_start_proxy (Fixture *f)
+                                           "--fd=3",
+                                           f->dbus_address,
+                                           f->proxy_socket,
++                                          "--filter",
++                                          "--log",
++                                          "--see=" CAN_SEE_NAME,
++                                          "--talk=" CAN_TALK_NAME,
++                                          "--own=" CAN_OWN_NAME,
++                                          "--call=" CAN_CALL_ANYTHING_NAME "=*",
++                                          "--call=" CAN_CALL_SOME_NAME "=" CAN_CALL_SOME_IFACE "." CAN_CALL_SOME_METHOD "@" CAN_CALL_SOME_PATH,
++                                          "--broadcast=" CAN_RECEIVE_ANYTHING_NAME "=*",
++                                          "--broadcast=" CAN_RECEIVE_SOME_NAME "=" CAN_RECEIVE_SOME_IFACE "." CAN_RECEIVE_SOME_SIGNAL "@" CAN_RECEIVE_SOME_PATH,
+                                           NULL);
+   g_assert_no_error (error);
+   g_assert_nonnull (f->proxy);
diff -Nru xdg-dbus-proxy-0.1.6/debian/patches/tests/tests-Assert-that-ability-to-own-names-is-enforced.patch xdg-dbus-proxy-0.1.6/debian/patches/tests/tests-Assert-that-ability-to-own-names-is-enforced.patch
--- xdg-dbus-proxy-0.1.6/debian/patches/tests/tests-Assert-that-ability-to-own-names-is-enforced.patch	1970-01-01 01:00:00.000000000 +0100
+++ xdg-dbus-proxy-0.1.6/debian/patches/tests/tests-Assert-that-ability-to-own-names-is-enforced.patch	2026-08-11 13:43:30.000000000 +0100
@@ -0,0 +1,132 @@
+From: Simon McVittie <smcv@debian.org>
+Date: Sun, 26 Jul 2026 11:49:34 +0100
+Subject: tests: Assert that ability to own names is enforced
+
+Signed-off-by: Simon McVittie <smcv@debian.org>
+Origin: upstream, 0.1.8
+---
+ tests/test-proxy.c | 104 +++++++++++++++++++++++++++++++++++++++++++++++++++++
+ 1 file changed, 104 insertions(+)
+
+diff --git a/tests/test-proxy.c b/tests/test-proxy.c
+index ed833c8..d90592d 100644
+--- a/tests/test-proxy.c
++++ b/tests/test-proxy.c
+@@ -341,6 +341,109 @@ test_basics (Fixture *f,
+   g_assert_true (found);
+ }
+ 
++typedef struct
++{
++  const char *name;
++  gboolean can_own;
++  gboolean can_see;
++} OwnTest;
++
++static const OwnTest own_tests[] =
++{
++  { CANNOT_ACCESS_NAME, .can_own = FALSE, .can_see = FALSE },
++  { CAN_SEE_NAME, .can_own = FALSE, .can_see = TRUE },
++  { CAN_TALK_NAME, .can_own = FALSE, .can_see = TRUE },
++  { CAN_OWN_NAME, .can_own = TRUE, .can_see = TRUE },
++  { CAN_CALL_ANYTHING_NAME, .can_own = FALSE, .can_see = TRUE },
++  { CAN_CALL_SOME_NAME, .can_own = FALSE, .can_see = TRUE },
++  { CAN_RECEIVE_ANYTHING_NAME, .can_own = FALSE, .can_see = TRUE },
++  { CAN_RECEIVE_SOME_NAME, .can_own = FALSE, .can_see = TRUE },
++};
++
++static void
++test_own (Fixture *f,
++          gconstpointer context G_GNUC_UNUSED)
++{
++  alarm (30);
++  fixture_start_proxy (f);
++
++  for (size_t i = 0; i < G_N_ELEMENTS (own_tests); i++)
++    {
++      const OwnTest *t = &own_tests[i];
++      g_autoptr(GError) error = NULL;
++      g_autoptr(GVariant) tuple = NULL;
++      const char *owner = NULL;
++
++      g_test_message ("#%zu: sandboxed connection %s be allowed to own %s",
++                      i, t->can_own ? "should" : "should not", t->name);
++
++      tuple = g_dbus_connection_call_sync (f->proxied.conn,
++                                           DBUS_SERVICE_DBUS,
++                                           DBUS_PATH_DBUS,
++                                           DBUS_INTERFACE_DBUS,
++                                           "RequestName",
++                                           g_variant_new ("(su)",
++                                                           t->name,
++                                                           (G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT
++                                                           | G_BUS_NAME_OWNER_FLAGS_REPLACE
++                                                           | G_BUS_NAME_OWNER_FLAGS_DO_NOT_QUEUE)),
++                                           G_VARIANT_TYPE ("(u)"),
++                                           G_DBUS_CALL_FLAGS_NONE,
++                                           -1,
++                                           NULL,    /* cancellable */
++                                           &error);
++
++      if (tuple != NULL)
++        g_test_message ("-> Was allowed");
++      else
++        g_test_message ("-> Was not allowed: %s", error->message);
++
++      if (t->can_own)
++        {
++          guint32 result = 0;
++
++          g_assert_no_error (error);
++          g_assert_nonnull (tuple);
++          g_variant_get (tuple, "(u)", &result);
++          g_assert_cmpuint (result, ==, DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER);
++        }
++      else if (t->can_see)
++        {
++          g_assert_error (error, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED);
++        }
++      else
++        {
++          g_assert_error (error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN);
++        }
++
++      g_clear_error (&error);
++      g_clear_pointer (&tuple, g_variant_unref);
++
++      /* Use a different connection to check who actually owns the name */
++      tuple = g_dbus_connection_call_sync (f->cannot_access_conn.conn,
++                                           DBUS_SERVICE_DBUS,
++                                           DBUS_PATH_DBUS,
++                                           DBUS_INTERFACE_DBUS,
++                                           "GetNameOwner",
++                                           g_variant_new ("(s)", t->name),
++                                           G_VARIANT_TYPE ("(s)"),
++                                           G_DBUS_CALL_FLAGS_NONE,
++                                           -1,
++                                           NULL,    /* cancellable */
++                                           NULL);
++
++      if (tuple != NULL)
++        g_variant_get (tuple, "(&s)", &owner);
++      else
++        owner = "";
++
++      if (t->can_own)
++        g_assert_cmpstr (owner, ==, f->proxied.unique_name);
++      else
++        g_assert_cmpstr (owner, !=, f->proxied.unique_name);
++    }
++}
++
+ static void
+ teardown (Fixture *f,
+           gconstpointer context G_GNUC_UNUSED)
+@@ -408,6 +511,7 @@ main (int argc,
+   g_test_init (&argc, &argv, NULL);
+ 
+   g_test_add ("/basics", Fixture, NULL, setup, test_basics, teardown);
++  g_test_add ("/own", Fixture, NULL, setup, test_own, teardown);
+ 
+   return g_test_run ();
+ }
diff -Nru xdg-dbus-proxy-0.1.6/debian/patches/tests/tests-Avoid-a-warning-with-newer-GLib.patch xdg-dbus-proxy-0.1.6/debian/patches/tests/tests-Avoid-a-warning-with-newer-GLib.patch
--- xdg-dbus-proxy-0.1.6/debian/patches/tests/tests-Avoid-a-warning-with-newer-GLib.patch	1970-01-01 01:00:00.000000000 +0100
+++ xdg-dbus-proxy-0.1.6/debian/patches/tests/tests-Avoid-a-warning-with-newer-GLib.patch	2026-08-11 13:43:30.000000000 +0100
@@ -0,0 +1,26 @@
+From: Simon McVittie <smcv@debian.org>
+Date: Sun, 26 Jul 2026 11:48:21 +0100
+Subject: tests: Avoid a warning with newer GLib
+
+Signed-off-by: Simon McVittie <smcv@debian.org>
+Origin: upstream, 0.1.8
+---
+ tests/test-proxy.c | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+diff --git a/tests/test-proxy.c b/tests/test-proxy.c
+index df1f00b..ae106d9 100644
+--- a/tests/test-proxy.c
++++ b/tests/test-proxy.c
+@@ -138,7 +138,11 @@ test_basics (Fixture *f,
+ 
+   alarm (30);
+ 
++#if GLIB_CHECK_VERSION (2, 78, 0)
++  g_unix_open_pipe (sync_pipe, O_CLOEXEC, &error);
++#else
+   g_unix_open_pipe (sync_pipe, FD_CLOEXEC, &error);
++#endif
+   g_assert_no_error (error);
+   f->sync_pipe = sync_pipe[READ_END];
+ 
diff -Nru xdg-dbus-proxy-0.1.6/debian/patches/tests/tests-Factor-out-fixture_start_proxy.patch xdg-dbus-proxy-0.1.6/debian/patches/tests/tests-Factor-out-fixture_start_proxy.patch
--- xdg-dbus-proxy-0.1.6/debian/patches/tests/tests-Factor-out-fixture_start_proxy.patch	1970-01-01 01:00:00.000000000 +0100
+++ xdg-dbus-proxy-0.1.6/debian/patches/tests/tests-Factor-out-fixture_start_proxy.patch	2026-08-11 13:43:30.000000000 +0100
@@ -0,0 +1,144 @@
+From: Simon McVittie <smcv@debian.org>
+Date: Sun, 26 Jul 2026 14:27:10 +0100
+Subject: tests: Factor out fixture_start_proxy()
+
+Signed-off-by: Simon McVittie <smcv@debian.org>
+Origin: upstream, 0.1.8
+---
+ tests/test-proxy.c | 80 ++++++++++++++++++++++++++++++++++--------------------
+ 1 file changed, 51 insertions(+), 29 deletions(-)
+
+diff --git a/tests/test-proxy.c b/tests/test-proxy.c
+index 772fad5..938a4a4 100644
+--- a/tests/test-proxy.c
++++ b/tests/test-proxy.c
+@@ -36,7 +36,30 @@
+ 
+ typedef struct
+ {
+-  GDBusConnection *proxied_conn;
++  GDBusConnection *conn;
++  const char *unique_name;
++} Connection;
++
++static void
++connection_clear (Connection *self)
++{
++  if (self->conn != NULL)
++    {
++      g_autoptr(GError) error = NULL;
++
++      g_dbus_connection_close_sync (self->conn, NULL, &error);
++
++      if (error != NULL)
++        g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CLOSED);
++    }
++
++  self->unique_name = NULL;
++  g_clear_object (&self->conn);
++}
++
++typedef struct
++{
++  Connection proxied;
+   GSubprocess *dbus_daemon;
+   GSubprocess *monitor;
+   GSubprocess *proxy;
+@@ -135,21 +158,13 @@ enum
+ };
+ 
+ static void
+-test_basics (Fixture *f,
+-             gconstpointer context G_GNUC_UNUSED)
++fixture_start_proxy (Fixture *f)
+ {
+   g_autoptr(GSubprocessLauncher) launcher = NULL;
+   g_autoptr(GError) error = NULL;
+-  g_autoptr(GVariant) tuple = NULL;
+-  g_auto(GStrv) strv = NULL;
+-  const char *proxied_name;
+   int sync_pipe[PIPE_FDS];
+   char buf;
+   ssize_t bytes_read;
+-  gsize i;
+-  gboolean found;
+-
+-  alarm (30);
+ 
+ #if GLIB_CHECK_VERSION (2, 78, 0)
+   g_unix_open_pipe (sync_pipe, O_CLOEXEC, &error);
+@@ -177,15 +192,32 @@ test_basics (Fixture *f,
+   bytes_read = read (sync_pipe[READ_END], &buf, 1);
+   g_assert_cmpint (bytes_read, ==, 1);
+ 
+-  f->proxied_conn = g_dbus_connection_new_for_address_sync (f->proxy_address,
+-                                                            G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT
+-                                                            | G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION,
+-                                                            NULL, NULL, &error);
++  f->proxied.conn = g_dbus_connection_new_for_address_sync (f->proxy_address,
++                                                            (G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT
++                                                             | G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION),
++                                                            NULL,    /* observer */
++                                                            NULL,    /* cancellable */
++                                                            &error);
+   g_assert_no_error (error);
+-  g_assert_nonnull (f->proxied_conn);
+-  proxied_name = g_dbus_connection_get_unique_name (f->proxied_conn);
++  g_assert_nonnull (f->proxied.conn);
++  f->proxied.unique_name = g_dbus_connection_get_unique_name (f->proxied.conn);
++}
+ 
+-  tuple = g_dbus_connection_call_sync (f->proxied_conn, DBUS_SERVICE_DBUS,
++static void
++test_basics (Fixture *f,
++             gconstpointer context G_GNUC_UNUSED)
++{
++  g_autoptr(GError) error = NULL;
++  g_autoptr(GVariant) tuple = NULL;
++  g_auto(GStrv) strv = NULL;
++  gsize i;
++  gboolean found;
++
++  alarm (30);
++
++  fixture_start_proxy (f);
++
++  tuple = g_dbus_connection_call_sync (f->proxied.conn, DBUS_SERVICE_DBUS,
+                                        DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS,
+                                        "ListNames", NULL,
+                                        G_VARIANT_TYPE ("(as)"),
+@@ -202,7 +234,7 @@ test_basics (Fixture *f,
+     {
+       g_test_message ("ListNames(): %s", strv[i]);
+ 
+-      if (g_strcmp0 (strv[i], proxied_name) == 0)
++      if (g_strcmp0 (strv[i], f->proxied.unique_name) == 0)
+         found = TRUE;
+     }
+ 
+@@ -243,16 +275,7 @@ teardown (Fixture *f,
+       g_assert_no_error (error);
+     }
+ 
+-  if (f->proxied_conn != NULL)
+-    {
+-      g_dbus_connection_close_sync (f->proxied_conn, NULL, &error);
+-
+-      if (error != NULL)
+-        {
+-          g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CLOSED);
+-          g_clear_error (&error);
+-        }
+-    }
++  connection_clear (&f->proxied);
+ 
+   if (f->proxy_socket != NULL)
+     {
+@@ -271,7 +294,6 @@ teardown (Fixture *f,
+     }
+ 
+   g_clear_object (&f->monitor);
+-  g_clear_object (&f->proxied_conn);
+   g_clear_object (&f->dbus_daemon);
+   g_clear_object (&f->proxy);
+   g_free (f->dbus_address);
diff -Nru xdg-dbus-proxy-0.1.6/debian/patches/tests/tests-Filter-and-count-incoming-messages-on-each-unsandbo.patch xdg-dbus-proxy-0.1.6/debian/patches/tests/tests-Filter-and-count-incoming-messages-on-each-unsandbo.patch
--- xdg-dbus-proxy-0.1.6/debian/patches/tests/tests-Filter-and-count-incoming-messages-on-each-unsandbo.patch	1970-01-01 01:00:00.000000000 +0100
+++ xdg-dbus-proxy-0.1.6/debian/patches/tests/tests-Filter-and-count-incoming-messages-on-each-unsandbo.patch	2026-08-11 13:43:30.000000000 +0100
@@ -0,0 +1,195 @@
+From: Simon McVittie <smcv@debian.org>
+Date: Sun, 26 Jul 2026 13:09:54 +0100
+Subject: tests: Filter and count incoming messages on each unsandboxed
+ connection
+
+Signed-off-by: Simon McVittie <smcv@debian.org>
+Origin: upstream, 0.1.8
+---
+ tests/test-proxy.c | 108 +++++++++++++++++++++++++++++++++++++++++++++++++++++
+ 1 file changed, 108 insertions(+)
+
+diff --git a/tests/test-proxy.c b/tests/test-proxy.c
+index d90592d..6d10359 100644
+--- a/tests/test-proxy.c
++++ b/tests/test-proxy.c
+@@ -62,7 +62,12 @@
+ typedef struct
+ {
+   GDBusConnection *conn;
++  const char *label;
+   const char *unique_name;
++  guint filter;
++  int n_method_calls;       /* atomic */
++  int n_unicast_signals;    /* atomic */
++  int n_broadcasts;         /* atomic */
+ } Connection;
+ 
+ static void
+@@ -72,6 +77,12 @@ connection_clear (Connection *self)
+     {
+       g_autoptr(GError) error = NULL;
+ 
++      if (self->filter != 0)
++        {
++          g_dbus_connection_remove_filter (self->conn, self->filter);
++          self->filter = 0;
++        }
++
+       g_dbus_connection_close_sync (self->conn, NULL, &error);
+ 
+       if (error != NULL)
+@@ -93,6 +104,7 @@ typedef struct
+   Connection can_call_some_conn;
+   Connection can_receive_anything_conn;
+   Connection can_receive_some_conn;
++  GHashTable *connections_by_name;
+   GSubprocess *dbus_daemon;
+   GSubprocess *monitor;
+   GSubprocess *proxy;
+@@ -109,6 +121,76 @@ typedef struct
+   int dummy;
+ } Config;
+ 
++static GDBusMessage *
++conn_filter_cb (GDBusConnection *conn,
++                GDBusMessage *message,
++                gboolean incoming,
++                void *user_data)
++{
++  Connection *conn_info = user_data;
++
++  g_assert (conn == conn_info->conn);
++
++  if (incoming)
++    {
++      GDBusMessageType type = g_dbus_message_get_message_type (message);
++      const char *sender = g_dbus_message_get_sender (message);
++      const char *dest = g_dbus_message_get_destination (message);
++      const char *path = g_dbus_message_get_path (message);
++      const char *iface = g_dbus_message_get_interface (message);
++      const char *member = g_dbus_message_get_member (message);
++
++      g_test_message ("%s got message type %u, from=%s, to=%s, path=%s, %s.%s",
++                      conn_info->label,
++                      type,
++                      sender ?: "(message bus)",
++                      dest ?: "(broadcast)",
++                      path ?: "(none)",
++                      iface ?: "(none)",
++                      member ?: "(none)");
++
++      switch (type)
++        {
++          case G_DBUS_MESSAGE_TYPE_METHOD_CALL:
++            g_test_message ("%s got method call", conn_info->label);
++            g_atomic_int_inc (&conn_info->n_method_calls);
++            break;
++
++          case G_DBUS_MESSAGE_TYPE_SIGNAL:
++            if (sender == NULL)
++              {
++                g_test_message ("%s got signal from message bus, ignoring", conn_info->label);
++              }
++            else if (dest != NULL && dest[0] != '\0')
++              {
++                g_test_message ("%s got unicast signal", conn_info->label);
++                g_atomic_int_inc (&conn_info->n_unicast_signals);
++              }
++            else
++              {
++                g_test_message ("%s got broadcast signal", conn_info->label);
++                g_atomic_int_inc (&conn_info->n_broadcasts);
++              }
++
++            break;
++
++          case G_DBUS_MESSAGE_TYPE_METHOD_RETURN:
++            g_test_message ("%s got method reply", conn_info->label);
++            break;
++
++          case G_DBUS_MESSAGE_TYPE_ERROR:
++            g_test_message ("%s got error reply", conn_info->label);
++            break;
++
++          case G_DBUS_MESSAGE_TYPE_INVALID:
++          default:
++            g_assert_not_reached ();
++        }
++    }
++
++  return message;
++}
++
+ /*
+  * Open a direct connection to the bus
+  */
+@@ -121,6 +203,12 @@ fixture_connect (Fixture *f,
+ 
+   g_return_if_fail (conn != NULL);
+   g_return_if_fail (conn->conn == NULL);
++
++  if (name != NULL)
++    conn->label = name;
++  else
++    conn->label = "(unnamed connection)";
++
+   conn->conn = g_dbus_connection_new_for_address_sync (f->dbus_address,
+                                                        (G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT
+                                                         | G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION),
+@@ -155,7 +243,16 @@ fixture_connect (Fixture *f,
+       g_assert_nonnull (tuple);
+       g_variant_get (tuple, "(u)", &result);
+       g_assert_cmpuint (result, ==, DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER);
++
++      g_hash_table_replace (f->connections_by_name,
++                            g_strdup (name),
++                            conn);
+     }
++
++  conn->filter = g_dbus_connection_add_filter (conn->conn,
++                                               conn_filter_cb,
++                                               conn,
++                                               NULL);
+ }
+ 
+ static void
+@@ -231,6 +328,11 @@ setup (Fixture *f,
+   escaped = g_dbus_address_escape_value (f->proxy_socket);
+   f->proxy_address = g_strdup_printf ("unix:path=%s", escaped);
+ 
++  f->connections_by_name = g_hash_table_new_full (g_str_hash,
++                                                  g_str_equal,
++                                                  g_free,
++                                                  NULL);
++
+   fixture_connect (f, &f->cannot_access_conn, CANNOT_ACCESS_NAME);
+   fixture_connect (f, &f->can_see_conn, CAN_SEE_NAME);
+   fixture_connect (f, &f->can_talk_conn, CAN_TALK_NAME);
+@@ -292,6 +394,7 @@ fixture_start_proxy (Fixture *f)
+   bytes_read = read (sync_pipe[READ_END], &buf, 1);
+   g_assert_cmpint (bytes_read, ==, 1);
+ 
++  f->proxied.label = "Sandboxed connection";
+   f->proxied.conn = g_dbus_connection_new_for_address_sync (f->proxy_address,
+                                                             (G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT
+                                                              | G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION),
+@@ -301,6 +404,10 @@ fixture_start_proxy (Fixture *f)
+   g_assert_no_error (error);
+   g_assert_nonnull (f->proxied.conn);
+   f->proxied.unique_name = g_dbus_connection_get_unique_name (f->proxied.conn);
++  f->proxied.filter = g_dbus_connection_add_filter (f->proxied.conn,
++                                                    conn_filter_cb,
++                                                    &f->proxied,
++                                                    NULL);
+ }
+ 
+ static void
+@@ -496,6 +603,7 @@ teardown (Fixture *f,
+       g_free (f->temp_directory);
+     }
+ 
++  g_clear_pointer (&f->connections_by_name, g_hash_table_unref);
+   g_clear_object (&f->monitor);
+   g_clear_object (&f->dbus_daemon);
+   g_clear_object (&f->proxy);
diff -Nru xdg-dbus-proxy-0.1.6/debian/patches/tests/tests-Optionally-run-a-dbus-monitor.patch xdg-dbus-proxy-0.1.6/debian/patches/tests/tests-Optionally-run-a-dbus-monitor.patch
--- xdg-dbus-proxy-0.1.6/debian/patches/tests/tests-Optionally-run-a-dbus-monitor.patch	1970-01-01 01:00:00.000000000 +0100
+++ xdg-dbus-proxy-0.1.6/debian/patches/tests/tests-Optionally-run-a-dbus-monitor.patch	2026-08-11 13:43:30.000000000 +0100
@@ -0,0 +1,66 @@
+From: Simon McVittie <smcv@debian.org>
+Date: Sun, 26 Jul 2026 14:26:08 +0100
+Subject: tests: Optionally run a dbus-monitor
+
+This can help when debugging whether particular messages are getting
+delivered or suppressed.
+
+Signed-off-by: Simon McVittie <smcv@debian.org>
+Origin: upstream, 0.1.8
+---
+ tests/test-proxy.c | 21 +++++++++++++++++++++
+ 1 file changed, 21 insertions(+)
+
+diff --git a/tests/test-proxy.c b/tests/test-proxy.c
+index ae106d9..772fad5 100644
+--- a/tests/test-proxy.c
++++ b/tests/test-proxy.c
+@@ -38,6 +38,7 @@ typedef struct
+ {
+   GDBusConnection *proxied_conn;
+   GSubprocess *dbus_daemon;
++  GSubprocess *monitor;
+   GSubprocess *proxy;
+   gchar *dbus_address;
+   gchar *temp_directory;
+@@ -102,6 +103,18 @@ setup (Fixture *f,
+   *newline = '\0';
+   f->dbus_address = g_strdup (address_buffer);
+ 
++  if (g_getenv ("TEST_DBUS_MONITOR") != NULL)
++    {
++      g_autoptr(GSubprocessLauncher) monitor_launcher = NULL;
++
++      monitor_launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_NONE);
++      g_subprocess_launcher_take_stdout_fd (monitor_launcher, dup (STDERR_FILENO));
++      f->monitor = g_subprocess_launcher_spawn (monitor_launcher, NULL,
++                                                "dbus-monitor",
++                                                "--address", f->dbus_address,
++                                                NULL);
++    }
++
+   f->proxy_path = g_getenv ("DBUS_PROXY");
+ 
+   if (f->proxy_path == NULL)
+@@ -202,6 +215,13 @@ teardown (Fixture *f,
+ {
+   g_autoptr(GError) error = NULL;
+ 
++  if (f->monitor != NULL)
++    {
++      g_subprocess_send_signal (f->monitor, SIGTERM);
++      g_subprocess_wait (f->monitor, NULL, &error);
++      g_assert_no_error (error);
++    }
++
+   if (f->dbus_daemon != NULL)
+     {
+       g_subprocess_send_signal (f->dbus_daemon, SIGTERM);
+@@ -250,6 +270,7 @@ teardown (Fixture *f,
+       g_free (f->temp_directory);
+     }
+ 
++  g_clear_object (&f->monitor);
+   g_clear_object (&f->proxied_conn);
+   g_clear_object (&f->dbus_daemon);
+   g_clear_object (&f->proxy);
diff -Nru xdg-dbus-proxy-0.1.6/debian/patches/tests/tests-Send-xdg-dbus-proxy-stdout-to-a-copy-of-its-stderr.patch xdg-dbus-proxy-0.1.6/debian/patches/tests/tests-Send-xdg-dbus-proxy-stdout-to-a-copy-of-its-stderr.patch
--- xdg-dbus-proxy-0.1.6/debian/patches/tests/tests-Send-xdg-dbus-proxy-stdout-to-a-copy-of-its-stderr.patch	1970-01-01 01:00:00.000000000 +0100
+++ xdg-dbus-proxy-0.1.6/debian/patches/tests/tests-Send-xdg-dbus-proxy-stdout-to-a-copy-of-its-stderr.patch	2026-08-11 13:43:30.000000000 +0100
@@ -0,0 +1,34 @@
+From: Simon McVittie <smcv@debian.org>
+Date: Sun, 26 Jul 2026 13:08:55 +0100
+Subject: tests: Send xdg-dbus-proxy stdout to a copy of its stderr
+
+Otherwise, if we enable `--log` or `G_MESSAGES_DEBUG`, the log messages
+on stdout will build up in the stdout pipe until the buffer is full,
+leading to a deadlock because we never read from the pipe.
+
+I think this was originally `G_SUBPROCESS_FLAGS_STDOUT_PIPE` because I'd
+originally intended to use --fd=1 for the sync fd, before realising that
+stdout is not a suitable fd for that purpose because xdg-dbus-proxy uses
+it for other purposes.
+
+Fixes: e339140bb59dae022cd30b91d785a601aa1f1aeb "Add a simple unit test"
+Signed-off-by: Simon McVittie <smcv@debian.org>
+Origin: upstream, 0.1.8
+---
+ tests/test-proxy.c | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/tests/test-proxy.c b/tests/test-proxy.c
+index 54a72ed..df1f00b 100644
+--- a/tests/test-proxy.c
++++ b/tests/test-proxy.c
+@@ -142,7 +142,8 @@ test_basics (Fixture *f,
+   g_assert_no_error (error);
+   f->sync_pipe = sync_pipe[READ_END];
+ 
+-  launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDOUT_PIPE);
++  launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_NONE);
++  g_subprocess_launcher_take_fd (launcher, dup (STDERR_FILENO), STDOUT_FILENO);
+   g_subprocess_launcher_take_fd (launcher, sync_pipe[WRITE_END], 3);
+   sync_pipe[WRITE_END] = -1;
+ 
diff -Nru xdg-dbus-proxy-0.1.6/debian/patches/tests/tests-Test-method-calls-being-blocked-or-allowed.patch xdg-dbus-proxy-0.1.6/debian/patches/tests/tests-Test-method-calls-being-blocked-or-allowed.patch
--- xdg-dbus-proxy-0.1.6/debian/patches/tests/tests-Test-method-calls-being-blocked-or-allowed.patch	1970-01-01 01:00:00.000000000 +0100
+++ xdg-dbus-proxy-0.1.6/debian/patches/tests/tests-Test-method-calls-being-blocked-or-allowed.patch	2026-08-11 13:43:30.000000000 +0100
@@ -0,0 +1,172 @@
+From: Simon McVittie <smcv@debian.org>
+Date: Sun, 26 Jul 2026 14:32:58 +0100
+Subject: tests: Test method calls being blocked or allowed
+
+Signed-off-by: Simon McVittie <smcv@debian.org>
+Origin: upstream, 0.1.8
+---
+ tests/test-proxy.c | 137 +++++++++++++++++++++++++++++++++++++++++++++++++++++
+ 1 file changed, 137 insertions(+)
+
+diff --git a/tests/test-proxy.c b/tests/test-proxy.c
+index 6d10359..829b88c 100644
+--- a/tests/test-proxy.c
++++ b/tests/test-proxy.c
+@@ -59,6 +59,18 @@
+ #define CAN_RECEIVE_SOME_SIGNAL "JustThisSignal"
+ #define CAN_RECEIVE_SOME_PATH "/just/this/path"
+ 
++static void
++ready_cb (GObject *source_object,
++          GAsyncResult *result,
++          void *user_data)
++{
++  GAsyncResult **result_p = user_data;
++
++  g_assert_nonnull (result_p);
++  g_assert_null (*result_p);
++  *result_p = g_object_ref (result);
++}
++
+ typedef struct
+ {
+   GDBusConnection *conn;
+@@ -448,6 +460,130 @@ test_basics (Fixture *f,
+   g_assert_true (found);
+ }
+ 
++typedef struct
++{
++  const char *name;
++  const char *path;
++  const char *iface;
++  const char *method;
++  gboolean can_call;
++  gboolean can_see;
++} CallTest;
++
++static const CallTest call_tests[] =
++{
++  { CANNOT_ACCESS_NAME, EXAMPLE_PATH, EXAMPLE_IFACE, EXAMPLE_METHOD,
++    .can_call = FALSE, .can_see = FALSE },
++  { CAN_SEE_NAME, EXAMPLE_PATH, EXAMPLE_IFACE, EXAMPLE_METHOD,
++    .can_call = FALSE, .can_see = TRUE },
++  { CAN_TALK_NAME, EXAMPLE_PATH, EXAMPLE_IFACE, EXAMPLE_METHOD,
++    .can_call = TRUE, .can_see = TRUE },
++  { CAN_OWN_NAME, EXAMPLE_PATH, EXAMPLE_IFACE, EXAMPLE_METHOD,
++    .can_call = TRUE, .can_see = TRUE },
++  { CAN_RECEIVE_ANYTHING_NAME, EXAMPLE_PATH, EXAMPLE_IFACE, EXAMPLE_METHOD,
++    .can_call = FALSE, .can_see = TRUE },
++  { CAN_RECEIVE_SOME_NAME, EXAMPLE_PATH, EXAMPLE_IFACE, EXAMPLE_METHOD,
++    .can_call = FALSE, .can_see = TRUE },
++
++  { CAN_CALL_ANYTHING_NAME, EXAMPLE_PATH, EXAMPLE_IFACE, EXAMPLE_METHOD,
++    .can_call = TRUE, .can_see = TRUE },
++
++  { CAN_CALL_SOME_NAME, EXAMPLE_PATH, EXAMPLE_IFACE, EXAMPLE_METHOD,
++    .can_call = FALSE, .can_see = TRUE },
++  { CAN_CALL_SOME_NAME, CAN_CALL_SOME_PATH, CAN_CALL_SOME_IFACE, CAN_CALL_SOME_METHOD,
++    .can_call = TRUE, .can_see = TRUE },
++  { CAN_CALL_SOME_NAME, EXAMPLE_PATH, CAN_CALL_SOME_IFACE, CAN_CALL_SOME_METHOD,
++    .can_call = FALSE, .can_see = TRUE },
++  { CAN_CALL_SOME_NAME, CAN_CALL_SOME_PATH, EXAMPLE_IFACE, CAN_CALL_SOME_METHOD,
++    .can_call = FALSE, .can_see = TRUE },
++  { CAN_CALL_SOME_NAME, CAN_CALL_SOME_PATH, CAN_CALL_SOME_IFACE, EXAMPLE_METHOD,
++    .can_call = FALSE, .can_see = TRUE },
++};
++
++static void
++test_call (Fixture *f,
++           gconstpointer context G_GNUC_UNUSED)
++{
++  alarm (30);
++  fixture_start_proxy (f);
++
++  for (size_t i = 0; i < G_N_ELEMENTS (call_tests); i++)
++    {
++      const CallTest *t = &call_tests[i];
++      g_autoptr(GAsyncResult) result = NULL;
++      g_autoptr(GError) error = NULL;
++      g_autoptr(GVariant) tuple = NULL;
++      const Connection *dest;
++      int n_calls_before;
++
++      g_test_message ("#%zu: sandboxed connection %s be allowed to call %s:%s.%s on %s",
++                      i,
++                      t->can_call ? "should" : "should not",
++                      t->path,
++                      t->iface,
++                      t->method,
++                      t->name);
++
++      dest = g_hash_table_lookup (f->connections_by_name, t->name);
++      g_assert_nonnull (dest);
++      n_calls_before = g_atomic_int_get (&dest->n_method_calls);
++
++      g_dbus_connection_call (f->proxied.conn,
++                              t->name,
++                              t->path,
++                              t->iface,
++                              t->method,
++                              NULL,
++                              G_VARIANT_TYPE ("()"),
++                              G_DBUS_CALL_FLAGS_NONE,
++                              -1,
++                              NULL,    /* cancellable */
++                              ready_cb,
++                              &result);
++
++      while (result == NULL)
++        g_main_context_iteration (NULL, TRUE);
++
++      tuple = g_dbus_connection_call_finish (f->proxied.conn, result, &error);
++      g_assert_nonnull (error);
++      g_assert_null (tuple);
++      g_test_message ("-> %s", error->message);
++
++      if (t->can_call)
++        {
++          /* If the method call was allowed, we just return an error,
++           * because for simplicity we didn't actually implement any
++           * method calls. */
++          g_assert_cmpstr (g_quark_to_string (error->domain),
++                           ==, g_quark_to_string (G_DBUS_ERROR));
++
++          switch (error->code)
++            {
++              case G_DBUS_ERROR_UNKNOWN_METHOD:
++              case G_DBUS_ERROR_UNKNOWN_INTERFACE:
++              case G_DBUS_ERROR_UNKNOWN_OBJECT:
++                /* OK */
++                break;
++
++              default:
++                g_assert_not_reached ();
++            }
++
++          g_assert_cmpint (g_atomic_int_get (&dest->n_method_calls), ==, n_calls_before + 1);
++        }
++      else if (t->can_see)
++        {
++          g_assert_error (error, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED);
++          g_assert_cmpint (g_atomic_int_get (&dest->n_method_calls), ==, n_calls_before);
++        }
++      else
++        {
++          g_assert_error (error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN);
++          g_assert_cmpint (g_atomic_int_get (&dest->n_method_calls), ==, n_calls_before);
++        }
++    }
++}
++
+ typedef struct
+ {
+   const char *name;
+@@ -619,6 +755,7 @@ main (int argc,
+   g_test_init (&argc, &argv, NULL);
+ 
+   g_test_add ("/basics", Fixture, NULL, setup, test_basics, teardown);
++  g_test_add ("/call", Fixture, NULL, setup, test_call, teardown);
+   g_test_add ("/own", Fixture, NULL, setup, test_own, teardown);
+ 
+   return g_test_run ();
diff -Nru xdg-dbus-proxy-0.1.6/debian/patches/tests/tests-Test-that-messages-from-outside-sandbox-are-not-rec.patch xdg-dbus-proxy-0.1.6/debian/patches/tests/tests-Test-that-messages-from-outside-sandbox-are-not-rec.patch
--- xdg-dbus-proxy-0.1.6/debian/patches/tests/tests-Test-that-messages-from-outside-sandbox-are-not-rec.patch	1970-01-01 01:00:00.000000000 +0100
+++ xdg-dbus-proxy-0.1.6/debian/patches/tests/tests-Test-that-messages-from-outside-sandbox-are-not-rec.patch	2026-08-11 13:43:30.000000000 +0100
@@ -0,0 +1,191 @@
+From: Simon McVittie <smcv@debian.org>
+Date: Sun, 26 Jul 2026 14:34:34 +0100
+Subject: tests: Test that messages from outside sandbox are (not) received
+
+In particular, this asserts that the vulnerability
+https://github.com/flatpak/xdg-dbus-proxy/security/advisories/GHSA-r7hp-698j-2h6c
+has been fixed.
+
+Signed-off-by: Simon McVittie <smcv@debian.org>
+Origin: upstream, 0.1.8
+---
+ tests/test-proxy.c | 138 +++++++++++++++++++++++++++++++++++++++++++++++++++++
+ 1 file changed, 138 insertions(+)
+
+diff --git a/tests/test-proxy.c b/tests/test-proxy.c
+index 829b88c..777c1d7 100644
+--- a/tests/test-proxy.c
++++ b/tests/test-proxy.c
+@@ -33,6 +33,7 @@
+ #define DBUS_SERVICE_DBUS "org.freedesktop.DBus"
+ #define DBUS_PATH_DBUS "/org/freedesktop/DBus"
+ #define DBUS_INTERFACE_DBUS "org.freedesktop.DBus"
++#define DBUS_INTERFACE_PEER "org.freedesktop.DBus.Peer"
+ 
+ #define DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER 1
+ #define DBUS_REQUEST_NAME_REPLY_IN_QUEUE 2
+@@ -367,6 +368,7 @@ fixture_start_proxy (Fixture *f)
+ {
+   g_autoptr(GSubprocessLauncher) launcher = NULL;
+   g_autoptr(GError) error = NULL;
++  g_autoptr(GVariant) tuple = NULL;
+   int sync_pipe[PIPE_FDS];
+   char buf;
+   ssize_t bytes_read;
+@@ -416,6 +418,18 @@ fixture_start_proxy (Fixture *f)
+   g_assert_no_error (error);
+   g_assert_nonnull (f->proxied.conn);
+   f->proxied.unique_name = g_dbus_connection_get_unique_name (f->proxied.conn);
++  tuple = g_dbus_connection_call_sync (f->proxied.conn,
++                                       DBUS_SERVICE_DBUS,
++                                       DBUS_PATH_DBUS,
++                                       DBUS_INTERFACE_DBUS,
++                                       "AddMatch",
++                                       g_variant_new ("(s)", ""),
++                                       G_VARIANT_TYPE ("()"),
++                                       G_DBUS_CALL_FLAGS_NONE,
++                                       -1,
++                                       NULL,    /* cancellable */
++                                       &error);
++  g_assert_no_error (error);
+   f->proxied.filter = g_dbus_connection_add_filter (f->proxied.conn,
+                                                     conn_filter_cb,
+                                                     &f->proxied,
+@@ -687,6 +701,129 @@ test_own (Fixture *f,
+     }
+ }
+ 
++typedef struct
++{
++  const char *name;
++  const char *path;
++  const char *iface;
++  const char *member;
++  gboolean can_receive_broadcast;
++} ReceiveTest;
++
++static const ReceiveTest receive_tests[] =
++{
++  { CANNOT_ACCESS_NAME, EXAMPLE_PATH, EXAMPLE_IFACE, EXAMPLE_SIGNAL, FALSE },
++  { CAN_SEE_NAME, EXAMPLE_PATH, EXAMPLE_IFACE, EXAMPLE_SIGNAL, FALSE },
++  { CAN_TALK_NAME, EXAMPLE_PATH, EXAMPLE_IFACE, EXAMPLE_SIGNAL, TRUE },
++  { CAN_OWN_NAME, EXAMPLE_PATH, EXAMPLE_IFACE, EXAMPLE_SIGNAL, TRUE },
++
++  /* Before GHSA-r7hp-698j-2h6c was fixed, both of these would receive the
++   * broadcast, but that was unintended */
++  { CAN_CALL_ANYTHING_NAME, EXAMPLE_PATH, EXAMPLE_IFACE, EXAMPLE_SIGNAL, FALSE },
++  { CAN_CALL_SOME_NAME, EXAMPLE_PATH, EXAMPLE_IFACE, EXAMPLE_SIGNAL, FALSE },
++
++  { CAN_RECEIVE_ANYTHING_NAME, EXAMPLE_PATH, EXAMPLE_IFACE, EXAMPLE_SIGNAL, TRUE },
++
++  { CAN_RECEIVE_SOME_NAME, CAN_RECEIVE_SOME_PATH, CAN_RECEIVE_SOME_IFACE, CAN_RECEIVE_SOME_SIGNAL, TRUE },
++
++  /* Before GHSA-r7hp-698j-2h6c was fixed, all of these would receive the broadcast,
++   * but that was unintended */
++  { CAN_RECEIVE_SOME_NAME, EXAMPLE_PATH, CAN_RECEIVE_SOME_IFACE, CAN_RECEIVE_SOME_SIGNAL, FALSE },
++  { CAN_RECEIVE_SOME_NAME, CAN_RECEIVE_SOME_PATH, EXAMPLE_IFACE, CAN_RECEIVE_SOME_SIGNAL, FALSE },
++  { CAN_RECEIVE_SOME_NAME, CAN_RECEIVE_SOME_PATH, CAN_RECEIVE_SOME_IFACE, EXAMPLE_SIGNAL, FALSE },
++  { CAN_RECEIVE_SOME_NAME, EXAMPLE_PATH, EXAMPLE_IFACE, EXAMPLE_SIGNAL, FALSE },
++};
++
++static void
++test_receive (Fixture *f,
++              gconstpointer context G_GNUC_UNUSED)
++{
++  alarm (30);
++  fixture_start_proxy (f);
++
++  for (size_t i = 0; i < G_N_ELEMENTS (receive_tests); i++)
++    {
++      g_autoptr(GAsyncResult) result = NULL;
++      g_autoptr(GVariant) tuple = NULL;
++      g_autoptr(GError) error = NULL;
++      const Connection *sender;
++      const ReceiveTest *t = &receive_tests[i];
++      int n_unicasts_before;
++      int n_broadcasts_before;
++      int n_calls_before;
++
++      g_test_message ("#%zu: sandboxed connection %s be allowed to receive broadcast from %s:%s.%s on %s",
++                      i,
++                      t->can_receive_broadcast ? "should" : "should not",
++                      t->path,
++                      t->iface,
++                      t->member,
++                      t->name);
++
++      sender = g_hash_table_lookup (f->connections_by_name, t->name);
++      g_assert_nonnull (sender);
++
++      n_calls_before = g_atomic_int_get (&f->proxied.n_method_calls);
++      n_unicasts_before = g_atomic_int_get (&f->proxied.n_unicast_signals);
++      n_broadcasts_before = g_atomic_int_get (&f->proxied.n_broadcasts);
++
++      /* The sandboxed recipient is only sometimes allowed to receive
++       * broadcasts. We do this first, because D-Bus preserves message
++       * order, therefore by the time we have received the unicast signal
++       * and/or the method call, it's guaranteed that this broadcast
++       * has been processed (and received, or not, as appropriate). */
++      g_dbus_connection_emit_signal (sender->conn,
++                                     NULL,
++                                     t->path,
++                                     t->iface,
++                                     t->member,
++                                     NULL,
++                                     &error);
++      g_assert_no_error (error);
++
++      /* The sandboxed recipient is always allowed to receive
++       * unicast signals. */
++      g_dbus_connection_emit_signal (sender->conn,
++                                     f->proxied.unique_name,
++                                     t->path,
++                                     t->iface,
++                                     t->member,
++                                     NULL,
++                                     &error);
++      g_assert_no_error (error);
++
++      /* The sandboxed recipient is always allowed to receive
++       * method calls. */
++      g_dbus_connection_call (sender->conn,
++                              f->proxied.unique_name,
++                              "/",
++                              DBUS_INTERFACE_PEER,
++                              "Ping",
++                              NULL,
++                              G_VARIANT_TYPE ("()"),
++                              G_DBUS_CALL_FLAGS_NONE,
++                              -1,
++                              NULL,    /* cancellable */
++                              ready_cb,
++                              &result);
++
++      while (result == NULL)
++        g_main_context_iteration (NULL, TRUE);
++
++      tuple = g_dbus_connection_call_finish (sender->conn, result, &error);
++      g_assert_no_error (error);
++      g_assert_nonnull (tuple);
++
++      g_assert_cmpint (g_atomic_int_get (&f->proxied.n_method_calls), ==, n_calls_before + 1);
++      g_assert_cmpint (g_atomic_int_get (&f->proxied.n_unicast_signals), ==, n_unicasts_before + 1);
++
++      if (t->can_receive_broadcast)
++        g_assert_cmpint (g_atomic_int_get (&f->proxied.n_broadcasts), ==, n_broadcasts_before + 1);
++      else
++        g_assert_cmpint (g_atomic_int_get (&f->proxied.n_broadcasts), ==, n_broadcasts_before);
++    }
++}
++
+ static void
+ teardown (Fixture *f,
+           gconstpointer context G_GNUC_UNUSED)
+@@ -757,6 +894,7 @@ main (int argc,
+   g_test_add ("/basics", Fixture, NULL, setup, test_basics, teardown);
+   g_test_add ("/call", Fixture, NULL, setup, test_call, teardown);
+   g_test_add ("/own", Fixture, NULL, setup, test_own, teardown);
++  g_test_add ("/receive", Fixture, NULL, setup, test_receive, teardown);
+ 
+   return g_test_run ();
+ }
diff -Nru xdg-dbus-proxy-0.1.6/flatpak-proxy.c xdg-dbus-proxy-0.1.6/flatpak-proxy.c
--- xdg-dbus-proxy-0.1.6/flatpak-proxy.c	2026-08-11 14:18:54.000000000 +0100
+++ xdg-dbus-proxy-0.1.6/flatpak-proxy.c	2026-08-11 14:18:54.000000000 +0100
@@ -2806,11 +2806,11 @@
           policy = flatpak_proxy_client_get_max_policy_and_matched (client, header->sender, &filters);
 
           if (policy == FLATPAK_POLICY_OWN ||
-              policy == FLATPAK_POLICY_TALK ||
-              any_filter_matches (filters, FILTER_TYPE_BROADCAST,
-                                  header->path,
-                                  header->interface,
-                                  header->member))
+              (policy == FLATPAK_POLICY_TALK &&
+               any_filter_matches (filters, FILTER_TYPE_BROADCAST,
+                                   header->path,
+                                   header->interface,
+                                   header->member)))
             filtered = FALSE;
 
           if (filtered)
diff -Nru xdg-dbus-proxy-0.1.6/tests/test-proxy.c xdg-dbus-proxy-0.1.6/tests/test-proxy.c
--- xdg-dbus-proxy-0.1.6/tests/test-proxy.c	2024-08-22 15:42:11.000000000 +0100
+++ xdg-dbus-proxy-0.1.6/tests/test-proxy.c	2026-08-11 14:18:54.000000000 +0100
@@ -33,11 +33,93 @@
 #define DBUS_SERVICE_DBUS "org.freedesktop.DBus"
 #define DBUS_PATH_DBUS "/org/freedesktop/DBus"
 #define DBUS_INTERFACE_DBUS "org.freedesktop.DBus"
+#define DBUS_INTERFACE_PEER "org.freedesktop.DBus.Peer"
+
+#define DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER 1
+#define DBUS_REQUEST_NAME_REPLY_IN_QUEUE 2
+#define DBUS_REQUEST_NAME_REPLY_EXISTS 3
+#define DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER 4
+
+#define CANNOT_ACCESS_NAME "com.example.CannotAccess"
+#define CAN_SEE_NAME "com.example.CanSee"
+#define CAN_TALK_NAME "com.example.CanTalk"
+#define CAN_OWN_NAME "com.example.CanOwn"
+#define CAN_CALL_ANYTHING_NAME "com.example.CanCallAny"
+#define CAN_CALL_SOME_NAME "com.example.CanCallSome"
+#define CAN_RECEIVE_ANYTHING_NAME "com.example.CanReceiveAny"
+#define CAN_RECEIVE_SOME_NAME "com.example.CanReceiveSome"
+
+#define EXAMPLE_IFACE "net.example.AnyInterface"
+#define EXAMPLE_METHOD "Echo"
+#define EXAMPLE_SIGNAL "Shouted"
+#define EXAMPLE_PATH "/path"
+#define CAN_CALL_SOME_IFACE "org.example.CanCallThis"
+#define CAN_CALL_SOME_METHOD "OnlyThisMethod"
+#define CAN_CALL_SOME_PATH "/only/this/path"
+#define CAN_RECEIVE_SOME_IFACE "org.example.CanReceiveThis"
+#define CAN_RECEIVE_SOME_SIGNAL "JustThisSignal"
+#define CAN_RECEIVE_SOME_PATH "/just/this/path"
+
+static void
+ready_cb (GObject *source_object,
+          GAsyncResult *result,
+          void *user_data)
+{
+  GAsyncResult **result_p = user_data;
+
+  g_assert_nonnull (result_p);
+  g_assert_null (*result_p);
+  *result_p = g_object_ref (result);
+}
+
+typedef struct
+{
+  GDBusConnection *conn;
+  const char *label;
+  const char *unique_name;
+  guint filter;
+  int n_method_calls;       /* atomic */
+  int n_unicast_signals;    /* atomic */
+  int n_broadcasts;         /* atomic */
+} Connection;
+
+static void
+connection_clear (Connection *self)
+{
+  if (self->conn != NULL)
+    {
+      g_autoptr(GError) error = NULL;
+
+      if (self->filter != 0)
+        {
+          g_dbus_connection_remove_filter (self->conn, self->filter);
+          self->filter = 0;
+        }
+
+      g_dbus_connection_close_sync (self->conn, NULL, &error);
+
+      if (error != NULL)
+        g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CLOSED);
+    }
+
+  self->unique_name = NULL;
+  g_clear_object (&self->conn);
+}
 
 typedef struct
 {
-  GDBusConnection *proxied_conn;
+  Connection proxied;
+  Connection cannot_access_conn;
+  Connection can_see_conn;
+  Connection can_talk_conn;
+  Connection can_own_conn;
+  Connection can_call_anything_conn;
+  Connection can_call_some_conn;
+  Connection can_receive_anything_conn;
+  Connection can_receive_some_conn;
+  GHashTable *connections_by_name;
   GSubprocess *dbus_daemon;
+  GSubprocess *monitor;
   GSubprocess *proxy;
   gchar *dbus_address;
   gchar *temp_directory;
@@ -52,6 +134,140 @@
   int dummy;
 } Config;
 
+static GDBusMessage *
+conn_filter_cb (GDBusConnection *conn,
+                GDBusMessage *message,
+                gboolean incoming,
+                void *user_data)
+{
+  Connection *conn_info = user_data;
+
+  g_assert (conn == conn_info->conn);
+
+  if (incoming)
+    {
+      GDBusMessageType type = g_dbus_message_get_message_type (message);
+      const char *sender = g_dbus_message_get_sender (message);
+      const char *dest = g_dbus_message_get_destination (message);
+      const char *path = g_dbus_message_get_path (message);
+      const char *iface = g_dbus_message_get_interface (message);
+      const char *member = g_dbus_message_get_member (message);
+
+      g_test_message ("%s got message type %u, from=%s, to=%s, path=%s, %s.%s",
+                      conn_info->label,
+                      type,
+                      sender ?: "(message bus)",
+                      dest ?: "(broadcast)",
+                      path ?: "(none)",
+                      iface ?: "(none)",
+                      member ?: "(none)");
+
+      switch (type)
+        {
+          case G_DBUS_MESSAGE_TYPE_METHOD_CALL:
+            g_test_message ("%s got method call", conn_info->label);
+            g_atomic_int_inc (&conn_info->n_method_calls);
+            break;
+
+          case G_DBUS_MESSAGE_TYPE_SIGNAL:
+            if (sender == NULL)
+              {
+                g_test_message ("%s got signal from message bus, ignoring", conn_info->label);
+              }
+            else if (dest != NULL && dest[0] != '\0')
+              {
+                g_test_message ("%s got unicast signal", conn_info->label);
+                g_atomic_int_inc (&conn_info->n_unicast_signals);
+              }
+            else
+              {
+                g_test_message ("%s got broadcast signal", conn_info->label);
+                g_atomic_int_inc (&conn_info->n_broadcasts);
+              }
+
+            break;
+
+          case G_DBUS_MESSAGE_TYPE_METHOD_RETURN:
+            g_test_message ("%s got method reply", conn_info->label);
+            break;
+
+          case G_DBUS_MESSAGE_TYPE_ERROR:
+            g_test_message ("%s got error reply", conn_info->label);
+            break;
+
+          case G_DBUS_MESSAGE_TYPE_INVALID:
+          default:
+            g_assert_not_reached ();
+        }
+    }
+
+  return message;
+}
+
+/*
+ * Open a direct connection to the bus
+ */
+static void
+fixture_connect (Fixture *f,
+                 Connection *conn,
+                 const char *name)
+{
+  g_autoptr(GError) error = NULL;
+
+  g_return_if_fail (conn != NULL);
+  g_return_if_fail (conn->conn == NULL);
+
+  if (name != NULL)
+    conn->label = name;
+  else
+    conn->label = "(unnamed connection)";
+
+  conn->conn = g_dbus_connection_new_for_address_sync (f->dbus_address,
+                                                       (G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT
+                                                        | G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION),
+                                                       NULL,    /* observer */
+                                                       NULL,    /* cancellable */
+                                                       &error);
+  g_assert_no_error (error);
+  g_assert_nonnull (conn->conn);
+  conn->unique_name = g_dbus_connection_get_unique_name (conn->conn);
+
+  if (name != NULL)
+    {
+      g_autoptr(GVariant) tuple = NULL;
+      guint32 result = 0;
+
+      tuple = g_dbus_connection_call_sync (conn->conn,
+                                           DBUS_SERVICE_DBUS,
+                                           DBUS_PATH_DBUS,
+                                           DBUS_INTERFACE_DBUS,
+                                           "RequestName",
+                                           g_variant_new ("(su)",
+                                                           name,
+                                                           (G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT
+                                                           | G_BUS_NAME_OWNER_FLAGS_REPLACE
+                                                           | G_BUS_NAME_OWNER_FLAGS_DO_NOT_QUEUE)),
+                                           G_VARIANT_TYPE ("(u)"),
+                                           G_DBUS_CALL_FLAGS_NONE,
+                                           -1,
+                                           NULL,    /* cancellable */
+                                           &error);
+      g_assert_no_error (error);
+      g_assert_nonnull (tuple);
+      g_variant_get (tuple, "(u)", &result);
+      g_assert_cmpuint (result, ==, DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER);
+
+      g_hash_table_replace (f->connections_by_name,
+                            g_strdup (name),
+                            conn);
+    }
+
+  conn->filter = g_dbus_connection_add_filter (conn->conn,
+                                               conn_filter_cb,
+                                               conn,
+                                               NULL);
+}
+
 static void
 setup (Fixture *f,
        gconstpointer context G_GNUC_UNUSED)
@@ -102,6 +318,18 @@
   *newline = '\0';
   f->dbus_address = g_strdup (address_buffer);
 
+  if (g_getenv ("TEST_DBUS_MONITOR") != NULL)
+    {
+      g_autoptr(GSubprocessLauncher) monitor_launcher = NULL;
+
+      monitor_launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_NONE);
+      g_subprocess_launcher_take_stdout_fd (monitor_launcher, dup (STDERR_FILENO));
+      f->monitor = g_subprocess_launcher_spawn (monitor_launcher, NULL,
+                                                "dbus-monitor",
+                                                "--address", f->dbus_address,
+                                                NULL);
+    }
+
   f->proxy_path = g_getenv ("DBUS_PROXY");
 
   if (f->proxy_path == NULL)
@@ -112,6 +340,20 @@
   f->proxy_socket = g_build_filename (f->temp_directory, "proxy", NULL);
   escaped = g_dbus_address_escape_value (f->proxy_socket);
   f->proxy_address = g_strdup_printf ("unix:path=%s", escaped);
+
+  f->connections_by_name = g_hash_table_new_full (g_str_hash,
+                                                  g_str_equal,
+                                                  g_free,
+                                                  NULL);
+
+  fixture_connect (f, &f->cannot_access_conn, CANNOT_ACCESS_NAME);
+  fixture_connect (f, &f->can_see_conn, CAN_SEE_NAME);
+  fixture_connect (f, &f->can_talk_conn, CAN_TALK_NAME);
+  fixture_connect (f, &f->can_own_conn, CAN_OWN_NAME);
+  fixture_connect (f, &f->can_call_anything_conn, CAN_CALL_ANYTHING_NAME);
+  fixture_connect (f, &f->can_call_some_conn, CAN_CALL_SOME_NAME);
+  fixture_connect (f, &f->can_receive_anything_conn, CAN_RECEIVE_ANYTHING_NAME);
+  fixture_connect (f, &f->can_receive_some_conn, CAN_RECEIVE_SOME_NAME);
 }
 
 enum
@@ -122,27 +364,25 @@
 };
 
 static void
-test_basics (Fixture *f,
-             gconstpointer context G_GNUC_UNUSED)
+fixture_start_proxy (Fixture *f)
 {
   g_autoptr(GSubprocessLauncher) launcher = NULL;
   g_autoptr(GError) error = NULL;
   g_autoptr(GVariant) tuple = NULL;
-  g_auto(GStrv) strv = NULL;
-  const char *proxied_name;
   int sync_pipe[PIPE_FDS];
   char buf;
   ssize_t bytes_read;
-  gsize i;
-  gboolean found;
-
-  alarm (30);
 
+#if GLIB_CHECK_VERSION (2, 78, 0)
+  g_unix_open_pipe (sync_pipe, O_CLOEXEC, &error);
+#else
   g_unix_open_pipe (sync_pipe, FD_CLOEXEC, &error);
+#endif
   g_assert_no_error (error);
   f->sync_pipe = sync_pipe[READ_END];
 
-  launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDOUT_PIPE);
+  launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_NONE);
+  g_subprocess_launcher_take_fd (launcher, dup (STDERR_FILENO), STDOUT_FILENO);
   g_subprocess_launcher_take_fd (launcher, sync_pipe[WRITE_END], 3);
   sync_pipe[WRITE_END] = -1;
 
@@ -151,6 +391,15 @@
                                           "--fd=3",
                                           f->dbus_address,
                                           f->proxy_socket,
+                                          "--filter",
+                                          "--log",
+                                          "--see=" CAN_SEE_NAME,
+                                          "--talk=" CAN_TALK_NAME,
+                                          "--own=" CAN_OWN_NAME,
+                                          "--call=" CAN_CALL_ANYTHING_NAME "=*",
+                                          "--call=" CAN_CALL_SOME_NAME "=" CAN_CALL_SOME_IFACE "." CAN_CALL_SOME_METHOD "@" CAN_CALL_SOME_PATH,
+                                          "--broadcast=" CAN_RECEIVE_ANYTHING_NAME "=*",
+                                          "--broadcast=" CAN_RECEIVE_SOME_NAME "=" CAN_RECEIVE_SOME_IFACE "." CAN_RECEIVE_SOME_SIGNAL "@" CAN_RECEIVE_SOME_PATH,
                                           NULL);
   g_assert_no_error (error);
   g_assert_nonnull (f->proxy);
@@ -159,15 +408,49 @@
   bytes_read = read (sync_pipe[READ_END], &buf, 1);
   g_assert_cmpint (bytes_read, ==, 1);
 
-  f->proxied_conn = g_dbus_connection_new_for_address_sync (f->proxy_address,
-                                                            G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT
-                                                            | G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION,
-                                                            NULL, NULL, &error);
+  f->proxied.label = "Sandboxed connection";
+  f->proxied.conn = g_dbus_connection_new_for_address_sync (f->proxy_address,
+                                                            (G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT
+                                                             | G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION),
+                                                            NULL,    /* observer */
+                                                            NULL,    /* cancellable */
+                                                            &error);
+  g_assert_no_error (error);
+  g_assert_nonnull (f->proxied.conn);
+  f->proxied.unique_name = g_dbus_connection_get_unique_name (f->proxied.conn);
+  tuple = g_dbus_connection_call_sync (f->proxied.conn,
+                                       DBUS_SERVICE_DBUS,
+                                       DBUS_PATH_DBUS,
+                                       DBUS_INTERFACE_DBUS,
+                                       "AddMatch",
+                                       g_variant_new ("(s)", ""),
+                                       G_VARIANT_TYPE ("()"),
+                                       G_DBUS_CALL_FLAGS_NONE,
+                                       -1,
+                                       NULL,    /* cancellable */
+                                       &error);
   g_assert_no_error (error);
-  g_assert_nonnull (f->proxied_conn);
-  proxied_name = g_dbus_connection_get_unique_name (f->proxied_conn);
+  f->proxied.filter = g_dbus_connection_add_filter (f->proxied.conn,
+                                                    conn_filter_cb,
+                                                    &f->proxied,
+                                                    NULL);
+}
 
-  tuple = g_dbus_connection_call_sync (f->proxied_conn, DBUS_SERVICE_DBUS,
+static void
+test_basics (Fixture *f,
+             gconstpointer context G_GNUC_UNUSED)
+{
+  g_autoptr(GError) error = NULL;
+  g_autoptr(GVariant) tuple = NULL;
+  g_auto(GStrv) strv = NULL;
+  gsize i;
+  gboolean found;
+
+  alarm (30);
+
+  fixture_start_proxy (f);
+
+  tuple = g_dbus_connection_call_sync (f->proxied.conn, DBUS_SERVICE_DBUS,
                                        DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS,
                                        "ListNames", NULL,
                                        G_VARIANT_TYPE ("(as)"),
@@ -184,19 +467,376 @@
     {
       g_test_message ("ListNames(): %s", strv[i]);
 
-      if (g_strcmp0 (strv[i], proxied_name) == 0)
+      if (g_strcmp0 (strv[i], f->proxied.unique_name) == 0)
         found = TRUE;
     }
 
   g_assert_true (found);
 }
 
+typedef struct
+{
+  const char *name;
+  const char *path;
+  const char *iface;
+  const char *method;
+  gboolean can_call;
+  gboolean can_see;
+} CallTest;
+
+static const CallTest call_tests[] =
+{
+  { CANNOT_ACCESS_NAME, EXAMPLE_PATH, EXAMPLE_IFACE, EXAMPLE_METHOD,
+    .can_call = FALSE, .can_see = FALSE },
+  { CAN_SEE_NAME, EXAMPLE_PATH, EXAMPLE_IFACE, EXAMPLE_METHOD,
+    .can_call = FALSE, .can_see = TRUE },
+  { CAN_TALK_NAME, EXAMPLE_PATH, EXAMPLE_IFACE, EXAMPLE_METHOD,
+    .can_call = TRUE, .can_see = TRUE },
+  { CAN_OWN_NAME, EXAMPLE_PATH, EXAMPLE_IFACE, EXAMPLE_METHOD,
+    .can_call = TRUE, .can_see = TRUE },
+  { CAN_RECEIVE_ANYTHING_NAME, EXAMPLE_PATH, EXAMPLE_IFACE, EXAMPLE_METHOD,
+    .can_call = FALSE, .can_see = TRUE },
+  { CAN_RECEIVE_SOME_NAME, EXAMPLE_PATH, EXAMPLE_IFACE, EXAMPLE_METHOD,
+    .can_call = FALSE, .can_see = TRUE },
+
+  { CAN_CALL_ANYTHING_NAME, EXAMPLE_PATH, EXAMPLE_IFACE, EXAMPLE_METHOD,
+    .can_call = TRUE, .can_see = TRUE },
+
+  { CAN_CALL_SOME_NAME, EXAMPLE_PATH, EXAMPLE_IFACE, EXAMPLE_METHOD,
+    .can_call = FALSE, .can_see = TRUE },
+  { CAN_CALL_SOME_NAME, CAN_CALL_SOME_PATH, CAN_CALL_SOME_IFACE, CAN_CALL_SOME_METHOD,
+    .can_call = TRUE, .can_see = TRUE },
+  { CAN_CALL_SOME_NAME, EXAMPLE_PATH, CAN_CALL_SOME_IFACE, CAN_CALL_SOME_METHOD,
+    .can_call = FALSE, .can_see = TRUE },
+  { CAN_CALL_SOME_NAME, CAN_CALL_SOME_PATH, EXAMPLE_IFACE, CAN_CALL_SOME_METHOD,
+    .can_call = FALSE, .can_see = TRUE },
+  { CAN_CALL_SOME_NAME, CAN_CALL_SOME_PATH, CAN_CALL_SOME_IFACE, EXAMPLE_METHOD,
+    .can_call = FALSE, .can_see = TRUE },
+};
+
+static void
+test_call (Fixture *f,
+           gconstpointer context G_GNUC_UNUSED)
+{
+  alarm (30);
+  fixture_start_proxy (f);
+
+  for (size_t i = 0; i < G_N_ELEMENTS (call_tests); i++)
+    {
+      const CallTest *t = &call_tests[i];
+      g_autoptr(GAsyncResult) result = NULL;
+      g_autoptr(GError) error = NULL;
+      g_autoptr(GVariant) tuple = NULL;
+      const Connection *dest;
+      int n_calls_before;
+
+      g_test_message ("#%zu: sandboxed connection %s be allowed to call %s:%s.%s on %s",
+                      i,
+                      t->can_call ? "should" : "should not",
+                      t->path,
+                      t->iface,
+                      t->method,
+                      t->name);
+
+      dest = g_hash_table_lookup (f->connections_by_name, t->name);
+      g_assert_nonnull (dest);
+      n_calls_before = g_atomic_int_get (&dest->n_method_calls);
+
+      g_dbus_connection_call (f->proxied.conn,
+                              t->name,
+                              t->path,
+                              t->iface,
+                              t->method,
+                              NULL,
+                              G_VARIANT_TYPE ("()"),
+                              G_DBUS_CALL_FLAGS_NONE,
+                              -1,
+                              NULL,    /* cancellable */
+                              ready_cb,
+                              &result);
+
+      while (result == NULL)
+        g_main_context_iteration (NULL, TRUE);
+
+      tuple = g_dbus_connection_call_finish (f->proxied.conn, result, &error);
+      g_assert_nonnull (error);
+      g_assert_null (tuple);
+      g_test_message ("-> %s", error->message);
+
+      if (t->can_call)
+        {
+          /* If the method call was allowed, we just return an error,
+           * because for simplicity we didn't actually implement any
+           * method calls. */
+          g_assert_cmpstr (g_quark_to_string (error->domain),
+                           ==, g_quark_to_string (G_DBUS_ERROR));
+
+          switch (error->code)
+            {
+              case G_DBUS_ERROR_UNKNOWN_METHOD:
+              case G_DBUS_ERROR_UNKNOWN_INTERFACE:
+              case G_DBUS_ERROR_UNKNOWN_OBJECT:
+                /* OK */
+                break;
+
+              default:
+                g_assert_not_reached ();
+            }
+
+          g_assert_cmpint (g_atomic_int_get (&dest->n_method_calls), ==, n_calls_before + 1);
+        }
+      else if (t->can_see)
+        {
+          g_assert_error (error, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED);
+          g_assert_cmpint (g_atomic_int_get (&dest->n_method_calls), ==, n_calls_before);
+        }
+      else
+        {
+          g_assert_error (error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN);
+          g_assert_cmpint (g_atomic_int_get (&dest->n_method_calls), ==, n_calls_before);
+        }
+    }
+}
+
+typedef struct
+{
+  const char *name;
+  gboolean can_own;
+  gboolean can_see;
+} OwnTest;
+
+static const OwnTest own_tests[] =
+{
+  { CANNOT_ACCESS_NAME, .can_own = FALSE, .can_see = FALSE },
+  { CAN_SEE_NAME, .can_own = FALSE, .can_see = TRUE },
+  { CAN_TALK_NAME, .can_own = FALSE, .can_see = TRUE },
+  { CAN_OWN_NAME, .can_own = TRUE, .can_see = TRUE },
+  { CAN_CALL_ANYTHING_NAME, .can_own = FALSE, .can_see = TRUE },
+  { CAN_CALL_SOME_NAME, .can_own = FALSE, .can_see = TRUE },
+  { CAN_RECEIVE_ANYTHING_NAME, .can_own = FALSE, .can_see = TRUE },
+  { CAN_RECEIVE_SOME_NAME, .can_own = FALSE, .can_see = TRUE },
+};
+
+static void
+test_own (Fixture *f,
+          gconstpointer context G_GNUC_UNUSED)
+{
+  alarm (30);
+  fixture_start_proxy (f);
+
+  for (size_t i = 0; i < G_N_ELEMENTS (own_tests); i++)
+    {
+      const OwnTest *t = &own_tests[i];
+      g_autoptr(GError) error = NULL;
+      g_autoptr(GVariant) tuple = NULL;
+      const char *owner = NULL;
+
+      g_test_message ("#%zu: sandboxed connection %s be allowed to own %s",
+                      i, t->can_own ? "should" : "should not", t->name);
+
+      tuple = g_dbus_connection_call_sync (f->proxied.conn,
+                                           DBUS_SERVICE_DBUS,
+                                           DBUS_PATH_DBUS,
+                                           DBUS_INTERFACE_DBUS,
+                                           "RequestName",
+                                           g_variant_new ("(su)",
+                                                           t->name,
+                                                           (G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT
+                                                           | G_BUS_NAME_OWNER_FLAGS_REPLACE
+                                                           | G_BUS_NAME_OWNER_FLAGS_DO_NOT_QUEUE)),
+                                           G_VARIANT_TYPE ("(u)"),
+                                           G_DBUS_CALL_FLAGS_NONE,
+                                           -1,
+                                           NULL,    /* cancellable */
+                                           &error);
+
+      if (tuple != NULL)
+        g_test_message ("-> Was allowed");
+      else
+        g_test_message ("-> Was not allowed: %s", error->message);
+
+      if (t->can_own)
+        {
+          guint32 result = 0;
+
+          g_assert_no_error (error);
+          g_assert_nonnull (tuple);
+          g_variant_get (tuple, "(u)", &result);
+          g_assert_cmpuint (result, ==, DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER);
+        }
+      else if (t->can_see)
+        {
+          g_assert_error (error, G_DBUS_ERROR, G_DBUS_ERROR_ACCESS_DENIED);
+        }
+      else
+        {
+          g_assert_error (error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN);
+        }
+
+      g_clear_error (&error);
+      g_clear_pointer (&tuple, g_variant_unref);
+
+      /* Use a different connection to check who actually owns the name */
+      tuple = g_dbus_connection_call_sync (f->cannot_access_conn.conn,
+                                           DBUS_SERVICE_DBUS,
+                                           DBUS_PATH_DBUS,
+                                           DBUS_INTERFACE_DBUS,
+                                           "GetNameOwner",
+                                           g_variant_new ("(s)", t->name),
+                                           G_VARIANT_TYPE ("(s)"),
+                                           G_DBUS_CALL_FLAGS_NONE,
+                                           -1,
+                                           NULL,    /* cancellable */
+                                           NULL);
+
+      if (tuple != NULL)
+        g_variant_get (tuple, "(&s)", &owner);
+      else
+        owner = "";
+
+      if (t->can_own)
+        g_assert_cmpstr (owner, ==, f->proxied.unique_name);
+      else
+        g_assert_cmpstr (owner, !=, f->proxied.unique_name);
+    }
+}
+
+typedef struct
+{
+  const char *name;
+  const char *path;
+  const char *iface;
+  const char *member;
+  gboolean can_receive_broadcast;
+} ReceiveTest;
+
+static const ReceiveTest receive_tests[] =
+{
+  { CANNOT_ACCESS_NAME, EXAMPLE_PATH, EXAMPLE_IFACE, EXAMPLE_SIGNAL, FALSE },
+  { CAN_SEE_NAME, EXAMPLE_PATH, EXAMPLE_IFACE, EXAMPLE_SIGNAL, FALSE },
+  { CAN_TALK_NAME, EXAMPLE_PATH, EXAMPLE_IFACE, EXAMPLE_SIGNAL, TRUE },
+  { CAN_OWN_NAME, EXAMPLE_PATH, EXAMPLE_IFACE, EXAMPLE_SIGNAL, TRUE },
+
+  /* Before GHSA-r7hp-698j-2h6c was fixed, both of these would receive the
+   * broadcast, but that was unintended */
+  { CAN_CALL_ANYTHING_NAME, EXAMPLE_PATH, EXAMPLE_IFACE, EXAMPLE_SIGNAL, FALSE },
+  { CAN_CALL_SOME_NAME, EXAMPLE_PATH, EXAMPLE_IFACE, EXAMPLE_SIGNAL, FALSE },
+
+  { CAN_RECEIVE_ANYTHING_NAME, EXAMPLE_PATH, EXAMPLE_IFACE, EXAMPLE_SIGNAL, TRUE },
+
+  { CAN_RECEIVE_SOME_NAME, CAN_RECEIVE_SOME_PATH, CAN_RECEIVE_SOME_IFACE, CAN_RECEIVE_SOME_SIGNAL, TRUE },
+
+  /* Before GHSA-r7hp-698j-2h6c was fixed, all of these would receive the broadcast,
+   * but that was unintended */
+  { CAN_RECEIVE_SOME_NAME, EXAMPLE_PATH, CAN_RECEIVE_SOME_IFACE, CAN_RECEIVE_SOME_SIGNAL, FALSE },
+  { CAN_RECEIVE_SOME_NAME, CAN_RECEIVE_SOME_PATH, EXAMPLE_IFACE, CAN_RECEIVE_SOME_SIGNAL, FALSE },
+  { CAN_RECEIVE_SOME_NAME, CAN_RECEIVE_SOME_PATH, CAN_RECEIVE_SOME_IFACE, EXAMPLE_SIGNAL, FALSE },
+  { CAN_RECEIVE_SOME_NAME, EXAMPLE_PATH, EXAMPLE_IFACE, EXAMPLE_SIGNAL, FALSE },
+};
+
+static void
+test_receive (Fixture *f,
+              gconstpointer context G_GNUC_UNUSED)
+{
+  alarm (30);
+  fixture_start_proxy (f);
+
+  for (size_t i = 0; i < G_N_ELEMENTS (receive_tests); i++)
+    {
+      g_autoptr(GAsyncResult) result = NULL;
+      g_autoptr(GVariant) tuple = NULL;
+      g_autoptr(GError) error = NULL;
+      const Connection *sender;
+      const ReceiveTest *t = &receive_tests[i];
+      int n_unicasts_before;
+      int n_broadcasts_before;
+      int n_calls_before;
+
+      g_test_message ("#%zu: sandboxed connection %s be allowed to receive broadcast from %s:%s.%s on %s",
+                      i,
+                      t->can_receive_broadcast ? "should" : "should not",
+                      t->path,
+                      t->iface,
+                      t->member,
+                      t->name);
+
+      sender = g_hash_table_lookup (f->connections_by_name, t->name);
+      g_assert_nonnull (sender);
+
+      n_calls_before = g_atomic_int_get (&f->proxied.n_method_calls);
+      n_unicasts_before = g_atomic_int_get (&f->proxied.n_unicast_signals);
+      n_broadcasts_before = g_atomic_int_get (&f->proxied.n_broadcasts);
+
+      /* The sandboxed recipient is only sometimes allowed to receive
+       * broadcasts. We do this first, because D-Bus preserves message
+       * order, therefore by the time we have received the unicast signal
+       * and/or the method call, it's guaranteed that this broadcast
+       * has been processed (and received, or not, as appropriate). */
+      g_dbus_connection_emit_signal (sender->conn,
+                                     NULL,
+                                     t->path,
+                                     t->iface,
+                                     t->member,
+                                     NULL,
+                                     &error);
+      g_assert_no_error (error);
+
+      /* The sandboxed recipient is always allowed to receive
+       * unicast signals. */
+      g_dbus_connection_emit_signal (sender->conn,
+                                     f->proxied.unique_name,
+                                     t->path,
+                                     t->iface,
+                                     t->member,
+                                     NULL,
+                                     &error);
+      g_assert_no_error (error);
+
+      /* The sandboxed recipient is always allowed to receive
+       * method calls. */
+      g_dbus_connection_call (sender->conn,
+                              f->proxied.unique_name,
+                              "/",
+                              DBUS_INTERFACE_PEER,
+                              "Ping",
+                              NULL,
+                              G_VARIANT_TYPE ("()"),
+                              G_DBUS_CALL_FLAGS_NONE,
+                              -1,
+                              NULL,    /* cancellable */
+                              ready_cb,
+                              &result);
+
+      while (result == NULL)
+        g_main_context_iteration (NULL, TRUE);
+
+      tuple = g_dbus_connection_call_finish (sender->conn, result, &error);
+      g_assert_no_error (error);
+      g_assert_nonnull (tuple);
+
+      g_assert_cmpint (g_atomic_int_get (&f->proxied.n_method_calls), ==, n_calls_before + 1);
+      g_assert_cmpint (g_atomic_int_get (&f->proxied.n_unicast_signals), ==, n_unicasts_before + 1);
+
+      if (t->can_receive_broadcast)
+        g_assert_cmpint (g_atomic_int_get (&f->proxied.n_broadcasts), ==, n_broadcasts_before + 1);
+      else
+        g_assert_cmpint (g_atomic_int_get (&f->proxied.n_broadcasts), ==, n_broadcasts_before);
+    }
+}
+
 static void
 teardown (Fixture *f,
           gconstpointer context G_GNUC_UNUSED)
 {
   g_autoptr(GError) error = NULL;
 
+  if (f->monitor != NULL)
+    {
+      g_subprocess_send_signal (f->monitor, SIGTERM);
+      g_subprocess_wait (f->monitor, NULL, &error);
+      g_assert_no_error (error);
+    }
+
   if (f->dbus_daemon != NULL)
     {
       g_subprocess_send_signal (f->dbus_daemon, SIGTERM);
@@ -218,16 +858,7 @@
       g_assert_no_error (error);
     }
 
-  if (f->proxied_conn != NULL)
-    {
-      g_dbus_connection_close_sync (f->proxied_conn, NULL, &error);
-
-      if (error != NULL)
-        {
-          g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CLOSED);
-          g_clear_error (&error);
-        }
-    }
+  connection_clear (&f->proxied);
 
   if (f->proxy_socket != NULL)
     {
@@ -245,7 +876,8 @@
       g_free (f->temp_directory);
     }
 
-  g_clear_object (&f->proxied_conn);
+  g_clear_pointer (&f->connections_by_name, g_hash_table_unref);
+  g_clear_object (&f->monitor);
   g_clear_object (&f->dbus_daemon);
   g_clear_object (&f->proxy);
   g_free (f->dbus_address);
@@ -260,6 +892,9 @@
   g_test_init (&argc, &argv, NULL);
 
   g_test_add ("/basics", Fixture, NULL, setup, test_basics, teardown);
+  g_test_add ("/call", Fixture, NULL, setup, test_call, teardown);
+  g_test_add ("/own", Fixture, NULL, setup, test_own, teardown);
+  g_test_add ("/receive", Fixture, NULL, setup, test_receive, teardown);
 
   return g_test_run ();
 }
