paperclipai/paperclip · error · Error

${description} must be owned by the Paperclip process user

Error message

${description} must be owned by the Paperclip process user

What it means

Ownership guard in decision-signing (assertOwnedByCurrentUser, skipped on Windows): the key file or secrets directory's uid does not match the Paperclip process's uid. File-based secret material must be owned by the running user to prevent cross-user tampering with decision signing.

Source

Thrown at server/src/services/decision-signing.ts:18

import { createHmac, randomBytes, timingSafeEqual } from "node:crypto";
import { chmodSync, linkSync, lstatSync, mkdirSync, readFileSync, type Stats, unlinkSync, writeFileSync } from "node:fs";
import path from "node:path";
import { resolveDefaultSecretsKeyFilePath } from "../home-paths.js";

const VERSION = "decision-spec-v1";
const MIN_SECRET_LENGTH = 32;

function resolveGeneratedSecretFilePath() {
  return path.join(path.dirname(resolveDefaultSecretsKeyFilePath()), "decision-signing.key");
}

function assertOwnedByCurrentUser(stats: Stats, description: string) {
  if (process.platform === "win32") return;

  const currentUserId = process.getuid?.();
  if (currentUserId !== undefined && stats.uid !== currentUserId) {
    throw new Error(`${description} must be owned by the Paperclip process user`);
  }
}

function enforceKeyFilePermissions(keyPath: string) {
  let stats = lstatSync(keyPath);
  if (!stats.isFile()) {
    throw new Error(`Decision signing key at ${keyPath} must be a regular file`);
  }
  assertOwnedByCurrentUser(stats, `Decision signing key at ${keyPath}`);
  if (process.platform === "win32") return;

  const mode = stats.mode & 0o777;
  if ((mode & 0o077) !== 0) {
    chmodSync(keyPath, 0o600);
    stats = lstatSync(keyPath);
    if (!stats.isFile()) {
      throw new Error(`Decision signing key at ${keyPath} must be a regular file`);
    }

View on GitHub (pinned to 120ae5428f)

Solutions

  1. Change ownership of the named file/directory to the Paperclip process user (chown).
Defensive patterns

Strategy: validation

When it happens

Trigger: Thrown at server/src/services/decision-signing.ts:18 when the library encounters an invalid state.

Common situations: See trigger scenarios.


AI-assisted analysis of paperclipai/paperclip@120ae5428f (2026-08-18). Data as JSON: /api/errors/e80a494491c60e36. Report an issue: GitHub.