ruvnet/ruflo · error · Error
signAttributionArtifact: privateKey must be 32 bytes
Error message
signAttributionArtifact: privateKey must be 32 bytes (got ${privateKey.length}) What it means
Every session operation (save, load, resume) builds the session file path via getSessionPath(), which first runs validateSessionId(): the ID must match /^[a-zA-Z0-9_-]+$/, be at most 128 chars, and contain no '..', '/', or '\'. This error is the charset/length gate — it exists because the sessionId becomes a filename under .claude-flow/sessions/, and any other character class would open the door to path manipulation.
Solutions
- Sanitize the ID before calling the tool: sessionId.replace(/[^a-zA-Z0-9_-]/g, '_')
- Use IDs you generated from the safe alphabet — the tool's own generated IDs (timestamp + base36 random) always pass
- Standard UUIDs are fine as-is (hex + hyphens), just strip surrounding braces or quotes
- If you need arbitrary metadata attached to a session, keep it inside the session payload, not in the ID
Example fix
// before
await client.callTool('session_resume', { sessionId: 'deploy.2026-08-18 10:00' }); // dots/space -> throws [1125]
// after
const sessionId = 'deploy.2026-08-18 10:00'.replace(/[^a-zA-Z0-9_-]/g, '_'); // deploy_2026-08-18_10_00
await client.callTool('session_resume', { sessionId }); Defensive patterns
Strategy: validation
Validate before calling
function isValidSessionId(id: string): boolean {
return /^[a-zA-Z0-9_-]+$/.test(id) && id.length <= 128 && !id.includes('..');
}
function sanitizeSessionId(raw: string): string {
return raw.replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 128);
} Type guard
const SAFE_SESSION_ID = /^[a-zA-Z0-9_-]{1,128}$/;
function isSafeSessionId(id: unknown): id is string {
return typeof id === 'string' && SAFE_SESSION_ID.test(id) && !id.includes('..');
} Prevention
- Generate session IDs from [a-zA-Z0-9_-] only (UUIDs and the tool's own IDs qualify)
- Run sanitizeSessionId() on any user-supplied ID before it reaches a session tool
- Attach free-form metadata to the session payload, never to the ID string
When it happens
Trigger: sessionId="session.123" (dot not allowed); sessionId with a space or unicode from user input; an empty string; sessionId="sess/../../etc/passwd" or "back\\slash"; an ID longer than 128 characters; a UUID formatted with braces like "{550e8400-...}" — braces fail the regex.
Common situations: Passing an email, hostname, or free-form label as the session ID; copy-pasting IDs with trailing whitespace or invisible characters; generating IDs with a library that uses dots (e.g. nanoid custom alphabets, dotted ULIDs); truncation bugs producing 129+ char strings.
Related errors
- localSingleEntryPageRank: sourceIndex
- Cannot select from empty array
- Dangerous key segment rejected
- FORBIDDEN_HEADER
- FORBIDDEN_PROTOCOL
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/b2d8b480bcc7e01f.
Report an issue: GitHub.
Appendix: source
Thrown at plugins/ruflo-neural-trader/src/signed-attribution.ts:101
* Sign the body of an attribution artifact and return the fully-formed
* `SignedAttributionArtifact` envelope.
*
* The signature covers the artifact body WITHOUT `witnessSignature` and
* WITHOUT `witnessPublicKey` (CWE-347 pattern, same as Phase 4). The
* verifier MUST pin to a trusted key for the pin to be a real defense.
*
* @param body — artifact body (everything except signature fields + schema)
* @param privateKeyHex — 32-byte Ed25519 private key as hex (no 'ed25519:' prefix)
* @returns the signed artifact ready to be stored
*/
export async function signAttributionArtifact(
body: SignedAttributionArtifactBody,
privateKeyHex: string,
): Promise<SignedAttributionArtifact> {
const ed = await import('@noble/ed25519');
const privateKey = hexToBytes(privateKeyHex);
if (privateKey.length !== 32) {
throw new Error(
`signAttributionArtifact: privateKey must be 32 bytes (got ${privateKey.length})`,
);
}
const canonical = canonicalBytes(body);
const signatureBytes = await ed.signAsync(canonical, privateKey);
const publicKeyBytes = await ed.getPublicKeyAsync(privateKey);
return {
schema: 'ruflo-neural-trader-attribution/v1',
...body,
witnessPublicKey: `ed25519:${bytesToHex(publicKeyBytes)}`,
witnessSignature: bytesToHex(signatureBytes),
};
}
/**
* Verify a signed attribution artifact against a caller-supplied trustedView on GitHub (pinned to fa13ee4ad6)