@@ -1487,17 +1487,26 @@ def GetProject(self, request: RegistryServer_pb2.GetProjectRequest, context):
14871487 ).to_proto ()
14881488
14891489 def ListProjects (self , request : RegistryServer_pb2 .ListProjectsRequest , context ):
1490- paginated_projects , pagination_metadata = apply_pagination_and_sorting (
1491- permitted_resources (
1492- resources = cast (
1493- list [ FeastObject ],
1494- self . proxied_registry . list_projects (
1495- allow_cache = request . allow_cache ,
1496- tags = dict ( request .tags ) ,
1497- ),
1490+ from feast . constants import PROTECTED_PROJECT_TAG
1491+
1492+ permitted_projects = permitted_resources (
1493+ resources = cast (
1494+ list [ FeastObject ],
1495+ self . proxied_registry . list_projects (
1496+ allow_cache = request .allow_cache ,
1497+ tags = dict ( request . tags ),
14981498 ),
1499- actions = AuthzedAction .DESCRIBE ,
15001499 ),
1500+ actions = AuthzedAction .DESCRIBE ,
1501+ )
1502+
1503+ # Exclude protected projects after RBAC check
1504+ visible_projects = [
1505+ p for p in permitted_projects if p .tags .get (PROTECTED_PROJECT_TAG ) != "true"
1506+ ]
1507+
1508+ paginated_projects , pagination_metadata = apply_pagination_and_sorting (
1509+ visible_projects ,
15011510 pagination = request .pagination ,
15021511 sorting = request .sorting ,
15031512 )
@@ -1724,13 +1733,66 @@ def GetFeature(self, request: RegistryServer_pb2.GetFeatureRequest, context):
17241733 )
17251734
17261735
1736+ def _sync_protected_project_tag (store : FeatureStore ):
1737+ """Sync the protected project tag based on FEAST_PROTECTED_PROJECT env var.
1738+
1739+ When FEAST_PROTECTED_PROJECT=true, tags the project as protected in the
1740+ shared registry. When the env var is absent or false, removes the tag
1741+ if it was previously set — allowing temporary protection that can be
1742+ reversed by removing the annotation from the FeatureStore CR.
1743+ """
1744+ import os
1745+
1746+ from feast .constants import FEAST_PROTECTED_PROJECT_ENV , PROTECTED_PROJECT_TAG
1747+
1748+ should_protect = os .environ .get (FEAST_PROTECTED_PROJECT_ENV , "" ).lower () == "true"
1749+
1750+ try :
1751+ existing = store .registry .get_project (name = store .project , allow_cache = False )
1752+ except Exception :
1753+ if should_protect :
1754+ from feast .project import Project
1755+
1756+ project = Project (
1757+ name = store .project ,
1758+ tags = {PROTECTED_PROJECT_TAG : "true" },
1759+ )
1760+ store .registry .apply_project (project , commit = True )
1761+ logger .info (
1762+ "Tagged project '%s' as protected (%s=true)" ,
1763+ store .project ,
1764+ PROTECTED_PROJECT_TAG ,
1765+ )
1766+ return
1767+
1768+ is_protected = existing .tags .get (PROTECTED_PROJECT_TAG ) == "true"
1769+
1770+ if should_protect and not is_protected :
1771+ existing .tags [PROTECTED_PROJECT_TAG ] = "true"
1772+ store .registry .apply_project (existing , commit = True )
1773+ logger .info (
1774+ "Tagged project '%s' as protected (%s=true)" ,
1775+ store .project ,
1776+ PROTECTED_PROJECT_TAG ,
1777+ )
1778+ elif not should_protect and is_protected :
1779+ del existing .tags [PROTECTED_PROJECT_TAG ]
1780+ store .registry .apply_project (existing , commit = True )
1781+ logger .info (
1782+ "Removed protected tag from project '%s'" ,
1783+ store .project ,
1784+ )
1785+
1786+
17271787def start_server (
17281788 store : FeatureStore ,
17291789 port : int ,
17301790 wait_for_termination : bool = True ,
17311791 tls_key_path : str = "" ,
17321792 tls_cert_path : str = "" ,
17331793):
1794+ _sync_protected_project_tag (store )
1795+
17341796 auth_manager_type = str_to_auth_manager_type (store .config .auth_config .type )
17351797 init_security_manager (auth_type = auth_manager_type , fs = store )
17361798 init_auth_manager (
0 commit comments