diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 00a07011..e18c986b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,6 +2,11 @@ name: Python on: [push, pull_request, workflow_dispatch] +# Least privilege: no job needs to write back to the repo. Publishing to PyPI +# uses TWINE_* secrets, not GITHUB_TOKEN, so read access is sufficient here. +permissions: + contents: read + defaults: run: shell: bash diff --git a/.github/workflows/ci-scripts.yml b/.github/workflows/ci-scripts.yml index a5b656b9..23e0fca2 100644 --- a/.github/workflows/ci-scripts.yml +++ b/.github/workflows/ci-scripts.yml @@ -2,6 +2,10 @@ name: Makefile on: [push, pull_request, workflow_dispatch] +# Least privilege: this workflow only builds/tests, it never writes to the repo. +permissions: + contents: read + defaults: run: shell: bash diff --git a/src/p4p/_p4p.pyx b/src/p4p/_p4p.pyx index e27621a9..3f3da3ba 100644 --- a/src/p4p/_p4p.pyx +++ b/src/p4p/_p4p.pyx @@ -53,10 +53,10 @@ cdef extern from "" namespace "p4p": void disconnectDynamic(const shared_ptr[server.Source]& src) except+ # pvxs_client.cpp - void opHandler[Builder](Builder& builder, object handler) - void opBuilder[Builder](Builder& builder, object handler) - void opEvent(client.MonitorBuilder& builder, object handler) - object monPop(const shared_ptr[client.Subscription]& mon) with gil + void opHandler[Builder](Builder& builder, object handler) except+ + void opBuilder[Builder](Builder& builder, object handler) except+ + void opEvent(client.MonitorBuilder& builder, object handler) except+ + object monPop(const shared_ptr[client.Subscription]& mon) except+ with gil cimport numpy # must cimport after p4p.h is included diff --git a/src/p4p/asLib/__init__.py b/src/p4p/asLib/__init__.py index d5e02174..841ac91f 100644 --- a/src/p4p/asLib/__init__.py +++ b/src/p4p/asLib/__init__.py @@ -240,7 +240,9 @@ def create(self, channel, group, user, host, level, roles=[]): with self._lock: - uags = self._uag.get(user, set()) + # copy the stored set; |= below is in-place and would otherwise + # permanently merge client-asserted roles into the shared UAG state + uags = set(self._uag.get(user, ())) for role in roles: uags |= self._uag.get('role/'+role, set()) hags = self._hag_addr.get(host, set()) diff --git a/src/pvxs_source.cpp b/src/pvxs_source.cpp index 6304b487..70803d5b 100644 --- a/src/pvxs_source.cpp +++ b/src/pvxs_source.cpp @@ -136,9 +136,8 @@ void disconnectDynamic(const std::shared_ptr& src) if(!src) return; - auto dynsrc = dynamic_cast(src.get()); - - dynsrc->handler = nullptr; + if(auto dynsrc = dynamic_cast(src.get())) + dynsrc->handler = nullptr; } }