ate-env-api accepts any caller: no authentication on the gRPC or MCP surface, while it holds a privileged ateapi identity
Los mantenedores suelen responder en 1 día
Nadie ha tomado este issue todavía.
- #100 de @tomergee — cerrado sin fusionar
Evaluación
- Dificultad
- 4/5
- Tiempo estimado
- 3-5 días
- Aptitud para principiantes
- 22/100
Línea de trabajo
Start in cmd/ate-env-api/main.go, where grpc.NewServer() and the http.ServeMux are built without interceptors or middleware, then check internal/apiservice and internal/mcp for the request paths. Read the README around the port-forward line to see how access is documented. Done means the maintainers have picked an auth mode, and requests without valid credentials are rejected on both the gRPC and MCP surfaces while /healthz stays open; the choice between a static bearer and TokenReview is a design decision to settle first.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
Summary
ate-env-api serves the EnvironmentService, ProcessService and FileSystemService gRPC APIs and the /v1alpha/envs/{id}/mcp HTTP endpoint on a plaintext h2c listener (cmd/ate-env-api/main.go: grpc.NewServer() with no interceptors, http.ServeMux with no middleware, SetUnencryptedHTTP2(true)). There is no authentication or authorization of the caller anywhere in the request path: no bearer check, no mTLS, no Kubernetes TokenReview/SubjectAccessReview, no per-env ownership. The only credential in the binary is outbound (--ateapi-token-file, a projected ServiceAccount token with the ateapi audience), so the process acts on the control plane with its own privileged identity on behalf of whoever can open a TCP connection to it.
In practice: anything that can reach svc/ate-env-api:7777 can create and delete environments in the default atespace, start processes inside any environment it can name as the guest's user (root on Substrate v0.4.0, which ignores the image USER; StartProcess has no uid restriction), and read or write any file in any environment's filesystem. The environment id (sent as x-env-id / x-env-atespace metadata) is the only thing a caller needs; ids are operator-chosen names, and anyone with read access to Actors in the cluster can list them.
This is fine for the documented path (kubectl port-forward, README line 143) and becomes a real exposure as soon as the Service is reached any other way — which is the first thing a non-trivial deployment does (we needed it from a second cluster via an internal load balancer; a NeMo-Gym trainer or an agent framework will need it from its own pods).
Impact
- Who hits it: any deployment where
ate-env-apiis reachable from more than the operator's laptop: cross-cluster callers, multi-tenant clusters, any namespace without a NetworkPolicy that blocks the port. - What is exposed: full control of every environment behind the server — arbitrary command execution as root in the sandboxes, file read/write (which includes whatever credentials the workload keeps on disk), environment create/delete — using the server's own ateapi token, so ateapi's authentication (substrate#2636) does not help: ate-env-api is the authenticated party.
- Severity: High; P1. Not an exploit of a bug, but a missing control on a privileged proxy, and nothing in the manifests or README says so.
Environment
agent-substrate/env@1c08a7a(2026-10-14). Same shape on currentmain(cmd/ate-env-api/main.go,internal/apiservice,internal/mcp).- Deployed on Substrate
v0.4.0-gke.0viaate-env manifest;ate-env-apifronted by an internal load balancer so a Deployment in a second GKE cluster can drive environments.
Steps to reproduce
- Deploy with
ate-env manifest | kubectl apply -f -. - From any pod in the cluster (no ServiceAccount, no token, no TLS):
All succeed. AddingH='-H x-env-id:<id> -H x-env-atespace:ate-env' grpcurl -plaintext $H ate-env-api.ate-env.svc:7777 ateenv.v1alpha.EnvironmentService/GetEnvironment grpcurl -plaintext $H -d '{"command":["id"]}' ate-env-api.ate-env.svc:7777 ateenv.v1alpha.ProcessService/StartProcess # -> a Process; StreamProcessOutput shows uid=0(root) on v0.4.0 grpcurl -plaintext $H -d '{"path":"/etc/shadow"}' ate-env-api.ate-env.svc:7777 ateenv.v1alpha.FileSystemService/ReadFile curl -X POST ate-env-api.ate-env.svc:7777/v1alpha/envs/<id>/mcp -d '{"jsonrpc":"2.0","method":"tools/list","id":1}'-H 'authorization: Bearer garbage'changes nothing (the header is never read). - Code:
cmd/ate-env-api/main.go—grpcServer := grpc.NewServer()(noUnaryInterceptor/StreamInterceptor),mux.Handle("/", grpcServer)andmux.Handle("/v1alpha/envs/{id}/mcp", mcp.NewHandler(client))with no wrapper;grep -rn authorization cmd/ate-env-api internal/apiservice internal/mcp→ no matches.
Expected vs actual
- Expected: a privileged proxy either authenticates its callers or refuses to listen on anything but loopback by default, and the docs say which.
- Actual: listens on
0.0.0.0:7777, plaintext, accepts every request; README showsport-forwardand never mentions that the port is unauthenticated.
Workaround (what we run)
An nginx Deployment in front of ate-env-api on the load balancer: if ($http_authorization != "Bearer <token>") { return 401; }, h2c proxy to ate-env-api:7777, client_max_body_size 0 (large WriteFile streams), token from a Kubernetes Secret; plus a NetworkPolicy so only the gate can reach ate-env-api directly. This works but every integrator has to rediscover it, and it is what surfaced the client-side streaming-auth bug (see the companion issue on the Python client).
Suggested fix
- Static bearer, opt-in:
--auth-token-file <path>; when set, a gRPC unary+stream interceptor and an HTTP middleware requireauthorization: Bearer <token>(constant-time compare) on everything except/healthz. ~40 lines; the manifest can mount a Secret; the Python/Go clients already send the header (auth_token=). - Kubernetes-native, opt-in:
--auth-mode=tokenreview[:audience]— validate the bearer as a ServiceAccount token viaTokenReview, so callers use projected tokens exactly asate-env-apiitself does toward ateapi; optionallySubjectAccessReviewon a virtual resource for per-namespace allow-lists. - Secure default: when no auth mode is configured, warn loudly at startup and/or default
--listento127.0.0.1:7777so port-forward keeps working and nothing else does; document the exposure in the README next to theport-forwardline. - Longer term: per-environment ownership (creator identity recorded on
CreateEnvironment, enforced on process/file calls) so one token cannot reach every env.
- Lenguaje dominante
- Go
- Estrellas
- 71
- Forks
- 17
- Merge medio
- 21 h 36 min
- PR fusionados (30 d)
- 19
Preparar el entorno
Este proyecto no incluye contenedor de desarrollo, Dockerfile ni guía de contribución, así que la configuración corre por tu cuenta: empieza por su README y consulta nuestra guía para la primera contribución para los pasos generales.
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de agent-substrate/env
-
Dificultad 4/5 3-5 días Aptitud para principiantes 42/100
agent-substrate/env#123 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 3/5 1-2 días Aptitud para principiantes 74/100
agent-substrate/env#121 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 4/5 3-5 días Aptitud para principiantes 42/100
agent-substrate/env#119 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 4/5 3-5 días Aptitud para principiantes 55/100
agent-substrate/env#118 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 4/5 3-5 días Aptitud para principiantes 57/100
agent-substrate/env#117 ·
Los mantenedores suelen responder en 1 día
Todos los issues de agent-substrate/env
Issues similares
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
Azure/aztfexport#690 · 1 reacción ·
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
kubecost/ibm-finops-agent#289 ·
Los mantenedores suelen responder en 2 días
-
ai-driven-fuzzing bug Minor
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
linagora/cozy-stack#5162 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 76/100
prometheus/prometheus-mcp#213 ·
Los mantenedores suelen responder en 3 días
-
Deleting a client-role keycloak_generic_role_mapper removes all roles of that client from the scope mappingPosiblemente ocupada @albarkauss la tomó hoy. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
keycloak/terraform-provider-keycloak#1786 ·
Los mantenedores suelen responder en 5 días