ruvnet/ruflo · error · TypeError
Federation canonicalization accepts only plain objects
Error message
Federation canonicalization accepts only plain objects
What it means
After arrays, the JCS canonicalizer accepts only plain objects: Object.getPrototypeOf must return Object.prototype or null. Class instances, Map/Set/Date, and objects from other realms (vm contexts, iframes) fail because their JSON serialization is implementation-defined and cannot be canonicalized deterministically for signing.
Source
Thrown at v3/@claude-flow/plugin-agent-federation/src/application/inbound-dispatcher.ts:189
throw new TypeError('Federation canonicalization rejects cyclic values');
}
ancestors.add(object);
try {
if (Array.isArray(value)) {
const items: string[] = [];
for (let index = 0; index < value.length; index += 1) {
if (!Object.prototype.hasOwnProperty.call(value, index)) {
throw new TypeError('Federation canonicalization rejects sparse arrays');
}
items.push(canonicalizeJcsValue(value[index], ancestors));
}
return `[${items.join(',')}]`;
}
const prototype = Object.getPrototypeOf(value);
if (prototype !== Object.prototype && prototype !== null) {
throw new TypeError('Federation canonicalization accepts only plain objects');
}
const record = value as Record<string, unknown>;
const entries = Object.keys(record)
.sort()
.map((key) => `${JSON.stringify(key)}:${canonicalizeJcsValue(record[key], ancestors)}`);
return `{${entries.join(',')}}`;
} finally {
ancestors.delete(object);
}
}
function envelopeSignatureVersion(message: AgentMessage): EnvelopeSignatureVersion {
const version = (message.metadata as Record<string, unknown> | undefined)?.signatureVersion;
if (version === undefined) return 'legacy-v1';
if (version === 'jcs-v1') return version;
throw new TypeError(`Unsupported federation signature version: ${String(version)}`);
}View on GitHub (pinned to fa13ee4ad6)
Solutions
- Convert to plain data before signing: JSON round-trip or an explicit to-POJO mapper
- Dates to ISO strings; Maps/Sets to plain objects/arrays
- Ensure received payloads go through JSON.parse before canonicalization (parse output is always plain)
- For cross-realm data, re-create objects in the signing realm via structured copy
Example fix
// before metadata.when = new Date(); // Date instance // after metadata.when = new Date().toISOString();
Defensive patterns
Strategy: type-guard
Validate before calling
// convert to plain data before signing: dates -> strings, keep only own props
function toPojo(v: unknown): unknown {
if (Array.isArray(v)) return v.map(toPojo);
if (v instanceof Date) return v.toISOString();
if (v && typeof v === 'object') {
const out: Record<string, unknown> = {};
for (const [k, val] of Object.entries(v)) out[k] = toPojo(val);
return out;
}
return v;
} Type guard
function isPlainObject(v: unknown): v is Record<string, unknown> {
if (typeof v !== 'object' || v === null) return false;
const p = Object.getPrototypeOf(v);
return p === Object.prototype || p === null;
} Prevention
- Never place Date/Map/Set or class instances in signed payloads
- Route received data through JSON.parse before canonicalization
- Watch cross-realm objects from vm/iframe contexts — re-create them locally
- Map domain objects to POJOs explicitly at the envelope boundary
When it happens
Trigger: Passing class instances (with getters or methods) as message metadata; Date, Map, or Set objects embedded in the payload; plain-looking objects created inside another vm/iframe realm whose prototype is a different Object.prototype.
Common situations: Domain objects copied into envelopes via spread (spread keeps the prototype for class instances is wrong — but direct reference does); timestamps left as Date; cross-realm messaging in Electron or vm-based sandboxes.
Related errors
- Federation canonicalization rejects non-canonical numbers
- Federation canonicalization rejects unsafe integers
- Federation canonicalization rejects ${typeof value}
- Federation canonicalization rejects cyclic values
- Federation canonicalization rejects sparse arrays
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/ab15fe388e5a1a28.
Report an issue: GitHub.