reported on 7 July 2026 via https://github.com/feast-dev/feast/security/advisories/GHSA-5px7-7gwg-6g93:
Summary
Feast's registry server (gRPC and REST) is designed to serve multiple projects from a single process - the documented REST API accepts a project query parameter on every request and even has /all endpoints that aggregate results across every project on the server. However, the RBAC SecurityManager that enforces Permission policies is initialized once at server startup using only the server's own configured project, and always loads that one project's permission list for every request it handles, regardless of which project the request actually targets. On a registry server that serves more than one project (an explicitly documented and supported topology), a user who is only granted a role in the server's home project gains that role's access to every other project's resources as well, and permissions defined specifically for those other projects are never consulted at all.
Details
SecurityManager is constructed once when the server starts, bound to the feature store's own project:
# sdk/python/feast/permissions/security_manager.py:24-33
class SecurityManager:
def __init__(self, project: str, registry: BaseRegistry):
self._project = project
self._registry = registry
...
# sdk/python/feast/permissions/server/utils.py:66-84
def init_security_manager(auth_type: AuthManagerType, fs: "feast.FeatureStore"):
...
set_security_manager(SecurityManager(project=fs.project, registry=fs.registry))
fs.project is whatever project: is set in the server process's own feature_store.yaml - a single, fixed value for the life of the process.
Every permission check goes through this same, single SecurityManager instance's permissions property:
# sdk/python/feast/permissions/security_manager.py:50-56
@property
def permissions(self) -> list[Permission]:
return self._registry.list_permissions(project=self._project)
Note this always uses self._project (the server's home project), never anything derived from the current request. Compare this against how every other registry RPC handles the project field - e.g. ListEntities:
# sdk/python/feast/registry_server.py:304-319
def ListEntities(self, request: RegistryServer_pb2.ListEntitiesRequest, context):
paginated_entities, pagination_metadata = apply_pagination_and_sorting(
permitted_resources(
resources=cast(list[FeastObject], self.proxied_registry.list_entities(
project=request.project, # <-- correctly scoped to the REQUESTED project
...
)),
actions=AuthzedAction.DESCRIBE, # permitted_resources -> SecurityManager.permissions,
), # which is scoped to the SERVER's home project, not request.project
...
)
The data fetch is correctly scoped per-request; the permission check that gates it is not. Permission.match_resource (sdk/python/feast/permissions/matcher.py) only matches on resource type, name pattern, and tags - it has no concept of the resource's own project either, so a broad permission (no name_patterns, matching ALL_RESOURCE_TYPES - a completely ordinary way to define a project's reader role) matches resources from any project once it is even considered.
Net effect on a registry server that serves N projects from one process:
- A permission granted only in the server's home project silently applies to every other project's resources too (over-broad grant).
- Permissions that were specifically defined for those other projects are never loaded or consulted at all (the intended, project-owner-defined policy is silently ignored).
PoC
Setup: one Feast REST Registry Server (feast serve_registry --no-grpc --rest-api), one shared SQLite registry, two projects each independently defined via feast apply:
project_a: entity user_id, feature view project_a_secret_metrics, Permission project_a_reader_permission = RoleBasedPolicy(roles=["project_a_reader"]), actions=[DESCRIBE]+READ.
project_b: entity user_id, feature view project_b_public_metrics, Permission project_b_reader_permission = RoleBasedPolicy(roles=["project_b_reader"]), actions=[DESCRIBE]+READ.
The registry server process itself was started from project_a's feature_store.yaml (i.e. its home project is project_a).
A genuine, JWKS-signature-verified token for user bob holding only project_a_reader (no project_b_reader):
$ curl -s -H "Authorization: Bearer $GOODA" "http://127.0.0.1:6572/api/v1/entities?project=project_a"
HTTP_STATUS:200 (expected: bob is a legitimate project_a reader)
The same token, same role, queried against project_b - bob holds no role granted by project_b's own permission (project_b_reader_permission requires project_b_reader), so this should be denied or return an empty/filtered list:
$ curl -s -H "Authorization: Bearer $GOODA" "http://127.0.0.1:6572/api/v1/entities?project=project_b"
{"entities":[{"spec":{"name":"__dummy", ...}},{"spec":{"name":"user_id","joinKey":"user_id"}, ...}]}
HTTP_STATUS:200
Direct fetch of project_b's feature view (including its data source path) with the same project_a-only token:
$ curl -s -H "Authorization: Bearer $GOODA" \
"http://127.0.0.1:6572/api/v1/feature_views/project_b_public_metrics?project=project_b"
{"type":"featureView","spec":{"name":"project_b_public_metrics","entities":["user_id"],
"features":[{"name":"public_score","valueType":"FLOAT"}],"tags":{"owner":"project_b_team","sensitivity":"public"},
"batchSource":{"fileOptions":{"uri":"/.../data/project_b_data.parquet"}, ...}}, ...}
HTTP_STATUS:200
project_b_reader_permission (the policy project_b's own owner defined specifically to gate access to this data) was never consulted - bob's access was granted purely because project_a_reader_permission has no project scoping and matches any Entity/FeatureView regardless of which project it belongs to, combined with the server only ever loading project_a's permission list.
Impact
Any Feast deployment that runs one registry server (gRPC or REST) in front of more than one project - explicitly documented and UI-supported behavior - loses per-project access-control isolation between all of the projects that server serves. A user who is legitimately a low-privilege reader in one project inherits that same access to every other project's entities, feature views, data source definitions (including source file/table paths), and other registry metadata on the same server, while the actual owners of those other projects have no way to restrict it - their own permission definitions are simply never loaded.
version: commit f296d4b
reported on 7 July 2026 via https://github.com/feast-dev/feast/security/advisories/GHSA-5px7-7gwg-6g93:
Summary
Feast's registry server (gRPC and REST) is designed to serve multiple projects from a single process - the documented REST API accepts a
projectquery parameter on every request and even has/allendpoints that aggregate results across every project on the server. However, the RBACSecurityManagerthat enforcesPermissionpolicies is initialized once at server startup using only the server's own configured project, and always loads that one project's permission list for every request it handles, regardless of which project the request actually targets. On a registry server that serves more than one project (an explicitly documented and supported topology), a user who is only granted a role in the server's home project gains that role's access to every other project's resources as well, and permissions defined specifically for those other projects are never consulted at all.Details
SecurityManageris constructed once when the server starts, bound to the feature store's own project:fs.projectis whateverproject:is set in the server process's ownfeature_store.yaml- a single, fixed value for the life of the process.Every permission check goes through this same, single
SecurityManagerinstance'spermissionsproperty:Note this always uses
self._project(the server's home project), never anything derived from the current request. Compare this against how every other registry RPC handles theprojectfield - e.g.ListEntities:The data fetch is correctly scoped per-request; the permission check that gates it is not.
Permission.match_resource(sdk/python/feast/permissions/matcher.py) only matches on resource type, name pattern, and tags - it has no concept of the resource's own project either, so a broad permission (noname_patterns, matchingALL_RESOURCE_TYPES- a completely ordinary way to define a project's reader role) matches resources from any project once it is even considered.Net effect on a registry server that serves N projects from one process:
PoC
Setup: one Feast REST Registry Server (
feast serve_registry --no-grpc --rest-api), one shared SQLite registry, two projects each independently defined viafeast apply:project_a: entityuser_id, feature viewproject_a_secret_metrics, Permissionproject_a_reader_permission=RoleBasedPolicy(roles=["project_a_reader"]),actions=[DESCRIBE]+READ.project_b: entityuser_id, feature viewproject_b_public_metrics, Permissionproject_b_reader_permission=RoleBasedPolicy(roles=["project_b_reader"]),actions=[DESCRIBE]+READ.The registry server process itself was started from
project_a'sfeature_store.yaml(i.e. its home project isproject_a).A genuine, JWKS-signature-verified token for user
bobholding onlyproject_a_reader(noproject_b_reader):The same token, same role, queried against
project_b- bob holds no role granted by project_b's own permission (project_b_reader_permissionrequiresproject_b_reader), so this should be denied or return an empty/filtered list:Direct fetch of project_b's feature view (including its data source path) with the same project_a-only token:
project_b_reader_permission(the policyproject_b's own owner defined specifically to gate access to this data) was never consulted -bob's access was granted purely becauseproject_a_reader_permissionhas no project scoping and matches anyEntity/FeatureViewregardless of which project it belongs to, combined with the server only ever loadingproject_a's permission list.Impact
Any Feast deployment that runs one registry server (gRPC or REST) in front of more than one project - explicitly documented and UI-supported behavior - loses per-project access-control isolation between all of the projects that server serves. A user who is legitimately a low-privilege reader in one project inherits that same access to every other project's entities, feature views, data source definitions (including source file/table paths), and other registry metadata on the same server, while the actual owners of those other projects have no way to restrict it - their own permission definitions are simply never loaded.
version: commit f296d4b