{"record":{"id":"eff83c7a23c5f448","repo":"ruvnet/ruflo","slug":"self-approval-forbidden","errorCode":null,"errorMessage":"self-approval-forbidden","messagePattern":"self-approval-forbidden","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/security/src/policy/engine.ts","lineNumber":106,"sourceCode":"    }\n    const index = this.state.rules.findIndex((item) => item.id === rule.id);\n    if (index >= 0) this.state.rules[index] = structuredClone(rule);\n    else this.state.rules.push(structuredClone(rule));\n  }\n\n  setBudget(limit: BudgetLimit): void {\n    if (limit.periodMs <= 0) throw new Error('invalid-budget-period');\n    if (!Number.isFinite(limit.periodMs)\n      || [limit.maxCostUsd, limit.maxTokens].some((value) => (\n        value !== undefined && (!Number.isFinite(value) || value < 0)\n      ))) throw new Error('invalid-budget-limit');\n    const index = this.state.budgets.findIndex((item) => item.id === limit.id);\n    if (index >= 0) this.state.budgets[index] = structuredClone(limit);\n    else this.state.budgets.push(structuredClone(limit));\n  }\n\n  issueApproval(approval: Omit<PolicyApproval, 'uses' | 'issuedAt'> & { uses?: number; issuedAt?: number }): PolicyApproval {\n    if (approval.issuedBy === approval.principal) throw new Error('self-approval-forbidden');\n    if (this.approvalIssuerVerifier?.(approval.issuedBy) !== true) {\n      throw new Error('untrusted-approval-issuer');\n    }\n    const issuedAt = approval.issuedAt ?? this.now();\n    const record: PolicyApproval = { ...approval, issuedAt, uses: approval.uses ?? 0 };\n    if (this.state.approvals.some((item) => item.id === record.id)) throw new Error('duplicate-approval-id');\n    if (!record.id\n      || record.expiresAt <= issuedAt\n      || !Number.isInteger(record.maxUses)\n      || record.maxUses <= 0\n      || !Number.isInteger(record.uses)\n      || record.uses < 0\n      || record.uses > record.maxUses) throw new Error('invalid-approval');\n    this.state.approvals.push(record);\n    return structuredClone(record);\n  }\n\n  revokeApproval(id: string): boolean {","sourceCodeStart":88,"sourceCodeEnd":124,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/v3/@claude-flow/security/src/policy/engine.ts#L88-L124","documentation":"PolicyEngine.issueApproval() enforces separation of duties: the approver (issuedBy) may not equal the beneficiary (principal). Passing the same identity in both roles throws Error('self-approval-forbidden'), preventing an agent or user from minting approvals for its own requests.","triggerScenarios":"issueApproval({ id, issuedBy: 'agent-1', principal: 'agent-1', ... }); single-principal automation where the same hardcoded id is used for issuer and beneficiary; a supervisor 'approving' a sub-task but reusing its own identity in both fields.","commonSituations":"Scripts that default both fields to the same service account; migrating from a system without separation of duties; tests that pass the same principal everywhere.","solutions":["Issue approvals from a distinct principal: a human approver id or a dedicated supervisor/issuer identity.","Register that issuer in the engine's approvalIssuerVerifier so it also passes the untrusted-approval-issuer check that runs next.","Audit call sites where issuedBy and principal are populated from the same variable."],"exampleFix":"// before\nengine.issueApproval({ id: 'a1', issuedBy: agentId, principal: agentId, ... });\n\n// after\nengine.issueApproval({ id: 'a1', issuedBy: 'human-ops', principal: agentId, ... });","handlingStrategy":"validation","validationCode":"if (approval.issuedBy === approval.principal) {\n  throw new Error(\n    `approval ${approval.id}: issuer and principal must differ (both ${approval.issuedBy})`\n  );\n}\nengine.issueApproval(approval);","typeGuard":null,"tryCatchPattern":"try {\n  return engine.issueApproval(approval);\n} catch (err) {\n  if (err instanceof Error && err.message === 'self-approval-forbidden') {\n    return forbidden('an approver cannot approve their own request');\n  }\n  throw err;\n}","preventionTips":["Model the approver as a distinct principal (human operator or supervisor agent) in your identity design.","Never default issuedBy and principal to the same service-account variable.","Add a startup audit that greps approval call sites for shared identity variables."],"tags":["policy","authorization","approval","security"],"backgroundTag":"self-approval-forbidden","analyzedSha":"fa13ee4ad60ac2090b1480656eb233521790d640","analyzedAt":"2026-08-18T21:34:22.708Z","contentChangedAt":"2026-08-18T21:34:22.708Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}