ruvnet/ruflo · error

policy administration requires an authenticated user context

Error message

policy administration requires an authenticated user context

What it means

All mutating policy admin tools (policy_rule_upsert, policy_budget_set, policy_approve, policy_revoke) call requireAuthenticatedAdministrator(context), which asserts the tool-call context has principalType === 'user' AND a string principalId. Anything else — an agent principal, a service identity, or a missing context — throws this error before any policy state is loaded or written. It is an intentional authorization gate: agents may evaluate policy (policy_evaluate) but only authenticated humans may change it.

Solutions

  1. Route policy administration through a call path that supplies context = { principalType: 'user', principalId: '<user id>' }.
  2. Use policy_evaluate and policy_status for non-human callers — they have no admin requirement.
  3. If you are the MCP host, propagate the authenticated user's identity into the tool-call context before dispatching admin tools.
  4. Do not work around this by faking a user principal from agent code — the gate exists so agents cannot self-authorize (ADR-324).

Example fix

// before
await mcp.call('policy_rule_upsert', { rule: {...} }); // context undefined -> throws

// after
await mcp.callWithUser('policy_rule_upsert', { rule: {...} }, userPrincipal); // context = { principalType: 'user', principalId: user.id }
Defensive patterns

Strategy: type-guard

Validate before calling

function hasUserPrincipal(context: unknown): boolean {
  return typeof context === 'object' && context !== null &&
    (context as any).principalType === 'user' &&
    typeof (context as any).principalId === 'string';
}
if (!hasUserPrincipal(toolContext)) {
  return { error: 'policy administration is reserved for authenticated humans; use policy_evaluate/policy_status instead' };
}
await callPolicyAdminTool(args, toolContext);

Type guard

interface UserContext { principalId: string; principalType: 'user' }
function isUserContext(c: unknown): c is UserContext {
  return typeof c === 'object' && c !== null &&
    (c as Record<string, unknown>).principalType === 'user' &&
    typeof (c as Record<string, unknown>).principalId === 'string' &&
    (c as Record<string, unknown>).principalId.length > 0;
}

Try / catch

try {
  await callTool('policy_rule_upsert', args, context);
} catch (e) {
  if (e instanceof Error && e.message === 'policy administration requires an authenticated user context') {
    return { error: 'Sign in as a user principal to administer policy; agents may only evaluate it.' };
  }
  throw e;
}

Prevention

When it happens

Trigger: Invoking any of policy_rule_upsert / policy_budget_set / policy_approve / policy_revoke with context = undefined, context.principalType = 'agent', or principalId missing/non-string. This happens when the MCP client does not forward user identity in the tool-call context, or when an agent-driven automation tries to self-approve a policy change.

Common situations: MCP server wiring that never populates the principal context; scripts/CI calling admin policy tools with a service account; an agent loop attempting to escalate its own permissions via policy_approve (correctly blocked); upgrading from a version where these tools were unauthenticated.

Understand the failure class

Related errors


AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18). Data as JSON: /api/errors/5cacf88d649b7b13. Report an issue: GitHub.

Appendix: source

Thrown at v3/@claude-flow/cli/src/mcp-tools/policy-tools.ts:24

  PolicyState,
} from '@claude-flow/security';
import type { MCPTool } from './types.js';
import {
  evaluatePolicyRequest,
  issuePolicyApproval,
  loadPolicyState,
  revokePolicyApproval,
  setPolicyBudget,
  setPolicyMode,
  upsertPolicyRule,
  verifyPolicyLedger,
} from '../services/policy-runtime.js';

function requireAuthenticatedAdministrator(
  context: Record<string, unknown> | undefined,
): asserts context is Record<string, unknown> & { principalId: string; principalType: 'user' } {
  if (context?.principalType !== 'user' || typeof context.principalId !== 'string') {
    throw new Error('policy administration requires an authenticated user context');
  }
}

export const policyTools: MCPTool[] = [
  {
    name: 'policy_evaluate',
    description: 'Evaluate an agent action against ADR-324 policy and persist a tamper-evident decision receipt. Use when a consequential tool, deployment, network, spend, or promotion action needs authorization.',
    category: 'security',
    inputSchema: {
      type: 'object',
      properties: {
        request: { type: 'object', description: 'Policy request containing identity, action, and optional evidence/envelope context' },
      },
      required: ['request'],
    },
    handler: async (input, context) => evaluatePolicyRequest(
      input.request as PolicyRequest,
      typeof context?.projectRoot === 'string' ? context.projectRoot : process.cwd(),

View on GitHub (pinned to fa13ee4ad6)