google-gemini / google-gemini/gemini-cli
bug(a2a-server): express.json() mounted after A2A SDK routes breaks JSON-RPC body parsing
- Dominant language
- TypeScript
- Stars
- 107k
- Forks
- 14.6k
- Avg merge
- 2d 3h
- Merged PRs (30d)
- 45
Description
### What happened?
In `packages/a2a-server/src/http/app.ts` (main @ 812f7a2bc), `express.json()` middleware is mounted **after** the A2A SDK routes, causing JSON body parsing to fail for SDK-handled endpoints while succeeding for custom routes — creating an asymmetric unauthenticated backdoor.
`app.ts` lines 275-284:
```ts
let expressApp = express();
expressApp.use((req, res, next) => {
requestStorage.run({ req }, next);
});
const appBuilder = new A2AExpressApp(requestHandler, customUserBuilder);
expressApp = appBuilder.setupRoutes(expressApp, ''); // SDK routes: POST /, /.well-known/agent-card.json
expressApp.use(express.json()); // ← TOO LATE for SDK routes
expressApp.post('/tasks', async (req, res) => {
const taskId = uuidv4();
const agentSettings = req.body.agentSettings as AgentSettings|undefined; // works (after middleware)
// ...
});
```
`A2AExpressApp.setupRoutes` registers `POST /` for JSON-RPC (`message/send`, `tasks/get`, etc.) which expects `req.body` to be parsed JSON. With `express.json()` after `setupRoutes`, those SDK routes receive `req.body = undefined`. The handler may throw or silently fail, while custom routes (`/tasks`, `/executeCommand`, `/listCommands`) defined **after** the middleware work correctly.
This compounds the auth issue (#29001): even if auth were enforced via `customUserBuilder`, the SDK's JSON-RPC path is broken, so legitimate authenticated task messages fail while the unauthenticated custom endpoints succeed.
### What did you expect to happen?
`express.json()` should be mounted **before** `appBuilder.setupRoutes`:
```ts
let expressApp = express();
expressApp.use((req, res, next) => { requestStorage.run({ req }, next); });
expressApp.use(express.json()); // ← before SDK routes
const appBuilder = new A2AExpressApp(requestHandler, customUserBuilder);
expressApp = appBuilder.setupRoutes(expressApp, '');
```
Alternatively, the SDK should be verified to add its own JSON parser internally (it does not — checked `@a2a-js/sdk@1.0.1` express handler, which expects `req.body` to already be parsed).
### Client information
- Source-level finding verified against upstream `main` at commit `812f7a2bc`
- File: `packages/a2a-server/src/http/app.ts:275-284`
- Affects all platforms running `@google/gemini-cli-a2a-server`
- SDK version: `@a2a-js/sdk@1.0.1`
### Login information
Not applicable.
### Anything else we need to know?
Sources:
- https://github.com/google-gemini/gemini-cli/blob/812f7a2bc/packages/a2a-server/src/http/app.ts#L275-L284
- Verified `setupRoutes` does not add JSON parsing internally (inspected `node_modules/@a2a-js/sdk/dist/server/express/index.js` — `jsonRpcHandler` reads `req.body` directly)
**Repro:**
1. `await createApp()` (test helper, port 41242)
2. `curl -X POST http://localhost:41242/ -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1,"method":"message/send","params":{"message":{"role":"user","parts":[{"text":"hi"}]}}}'` → handler sees `req.body = undefined` → 400 or silent failure
3. `curl -X POST http://localhost:41242/tasks -H "Content-Type: application/json" -d '{"agentSettings":{}}'` → `201` (works because after middleware)
Searched existing issues for "express.json", "a2a middleware", "request body undefined" — no open duplicate found.
Contributor guide
Research direction
Start in packages/a2a-server/src/http/app.ts around lines 275-284 and inspect the ordering of express.json() and A2AExpressApp.setupRoutes. Reproduce with the createApp helper and the provided JSON-RPC curl request, then verify that SDK JSON-RPC endpoints parse requests while the existing custom routes still return their expected responses.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- express, typescript
- Domain
- api, backend, security
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 88/100