a2aproject / a2aproject/a2a-js

[Bug]: Timestamp fields are unvalidated strings and are never normalized to UTC

Open
#641 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
613
Forks
169
Avg merge
1d 6h
Merged PRs (30d)
21

Description

### What happened?

`google.protobuf.Timestamp` fields are passed straight through as strings. The generated
code is:

```ts
timestamp: isSet(object.timestamp) ? globalThis.String(object.timestamp) : undefined,
```

and `toJSON` copies it back out unchanged. Nothing parses it and nothing checks it, which
causes two separate problems.

**Malformed timestamps are accepted and forwarded.** Every one of these is stored and
re-emitted verbatim:

```js
import { TaskStatus } from '@a2a-js/sdk';
const ts = (t) => TaskStatus.toJSON(TaskStatus.fromJSON({ state: 'TASK_STATE_WORKING', timestamp: t }));

ts('not-a-timestamp'); // { state: 'TASK_STATE_WORKING', timestamp: 'not-a-timestamp' }
ts(''); // { ..., timestamp: '' }
ts(12345); // { ..., timestamp: '12345' } a JSON number becomes a string
```

`a2a-python` rejects all of them. So a JS server accepts a `Task` whose
`status.timestamp` is arbitrary text, hands it downstream, and the Python peer is the one
that throws. The component that let the bad data in isn't the component that reports it,
which makes this annoying to track down in a mixed deployment.

**Offsets are never normalized.** proto3 JSON output is always Z-normalized.
`a2a-python` does that; this SDK keeps whatever offset arrived:

```
input "2026-01-01T05:30:00+05:30"
python "2026-01-01T00:00:00Z"
js "2026-01-01T05:30:00+05:30"
```

Same instant, different bytes. That's fine until something compares bytes rather than
instants, which is what canonicalization does. `src/signature.ts:188` canonicalizes via
`AgentCard.toJSON(AgentCard.fromJSON(card))`, so this codec sits on the signing path.

Storing the field unparsed also means any timestamp comparison or ordering inside the SDK
is operating on a string whose format isn't guaranteed.

### What I'd expect

`fromJSON` parses and validates RFC 3339, rejecting input that isn't a timestamp, and
`toJSON` emits the Z-normalized form.

### How this was found

Round-tripping a shared corpus through this SDK and `a2a-python` and diffing the JSON.
Both generate from the same `.proto`.

Reproduced on `@a2a-js/sdk` 1.0.1 from npm and on `main` at `1c6eb32`, against
`a2a-python` at `cff6727`.

Same question as the other codec issues I'm filing: this is in the generated
`src/types/pb/a2a.ts`, so tell me where you'd like the fix and I'll send it.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.