ruvnet/ruflo · error · TypeError
Federation canonicalization rejects unsafe integers
Error message
Federation canonicalization rejects unsafe integers
What it means
JCS canonicalization rejects integers beyond the safe range: values where Number.isInteger is true but Number.isSafeInteger is false (magnitude above 2^53 - 1). Such values silently lose precision in IEEE-754 doubles, so sender and receiver could canonicalize different bytes and signature verification would fail spuriously or, worse, verify the wrong value.
Source
Thrown at v3/@claude-flow/plugin-agent-federation/src/application/inbound-dispatcher.ts:155
* Federation messages are wrapped as `AgentMessage{id, type, payload,
* metadata}` on the wire. The `payload` is the actual FederationEnvelope
* (per `plugin.ts sendToNode`); we canonicalize the payload + the
* metadata so the receiver verifies the same bytes the sender signed.
*/
function canonicalizeJcsValue(value: unknown, ancestors: Set<object>): string {
if (value === null) return 'null';
switch (typeof value) {
case 'boolean':
return value ? 'true' : 'false';
case 'string':
return JSON.stringify(value);
case 'number':
if (!Number.isFinite(value) || Object.is(value, -0)) {
throw new TypeError('Federation canonicalization rejects non-canonical numbers');
}
if (Number.isInteger(value) && !Number.isSafeInteger(value)) {
throw new TypeError('Federation canonicalization rejects unsafe integers');
}
return JSON.stringify(value);
case 'bigint':
case 'function':
case 'symbol':
case 'undefined':
throw new TypeError(`Federation canonicalization rejects ${typeof value}`);
case 'object':
break;
default:
throw new TypeError(`Federation canonicalization rejects ${typeof value}`);
}
const object = value as object;
if (ancestors.has(object)) {
throw new TypeError('Federation canonicalization rejects cyclic values');
}
ancestors.add(object);View on GitHub (pinned to fa13ee4ad6)
Solutions
- Serialize 64-bit identifiers and timestamps as strings inside signed envelopes
- Use safe-integer ID generation, or split hi/lo halves, for values that must remain numeric
- Validate at the boundary: reject integers where Number.isSafeInteger is false before signing
Example fix
// before metadata.id = snowflakeId; // 64-bit integer, not safe // after metadata.id = String(snowflakeId);
Defensive patterns
Strategy: type-guard
Validate before calling
// boundary check: reject unsafe integers before signing
function assertSafeNumbers(value: unknown): void {
if (typeof value === 'number' && Number.isInteger(value) && !Number.isSafeInteger(value)) {
throw new RangeError('unsafe integer in signed payload: ' + value);
}
if (typeof value === 'object' && value !== null) {
for (const v of Object.values(value)) assertSafeNumbers(v);
}
} Type guard
function isSafeCanonicalInteger(v: unknown): v is number {
return (
typeof v === 'number' &&
Number.isInteger(v) &&
Number.isSafeInteger(v)
);
} Prevention
- Serialize 64-bit IDs and high-resolution timestamps as strings
- Use safe-integer ID generators for values that end up in envelopes
- Validate external numbers at the trust boundary before signing
- Watch for int64 JSON from non-JS services
When it happens
Trigger: 64-bit IDs (snowflake IDs, database bigint keys) or nanosecond timestamps placed as JSON numbers in signed metadata; values received from serializers in other languages that happily emit 64-bit JSON integers.
Common situations: Porting pipelines from languages whose JSON writers emit int64 numbers; mixing JS with services that use 64-bit identifiers; log-precision timestamps in envelopes.
Related errors
- Federation canonicalization rejects non-canonical numbers
- Federation canonicalization rejects ${typeof value}
- Federation canonicalization rejects cyclic values
- Federation canonicalization rejects sparse arrays
- Federation canonicalization accepts only plain objects
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/cd4bab44b15d5908.
Report an issue: GitHub.