{"record":{"id":"ecfbf14eacca95ad","repo":"ruvnet/ruflo","slug":"capability-envelope-cannot-grow","errorCode":null,"errorMessage":"capability-envelope-cannot-grow","messagePattern":"capability-envelope-cannot-grow","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"critical","filePath":"v3/@claude-flow/security/src/policy/envelope.ts","lineNumber":107,"sourceCode":"  parent: CapabilityEnvelope,\n  child: CapabilityEnvelope,\n): CapabilityEnvelope {\n  if ((parent.delegationDepth ?? 0) <= 0) {\n    throw new Error('delegation-depth-exhausted');\n  }\n  const reduced = {\n    ...parent,\n    ...child,\n    delegationDepth: Math.min(\n      child.delegationDepth ?? Number.MAX_SAFE_INTEGER,\n      (parent.delegationDepth ?? 0) - 1,\n    ),\n    expiresAt: Math.min(\n      child.expiresAt ?? Number.MAX_SAFE_INTEGER,\n      parent.expiresAt ?? Number.MAX_SAFE_INTEGER,\n    ),\n  };\n  if (!isEnvelopeReduction(parent, reduced)) throw new Error('capability-envelope-cannot-grow');\n  return reduced;\n}\n","sourceCodeStart":89,"sourceCodeEnd":110,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/v3/@claude-flow/security/src/policy/envelope.ts#L89-L110","documentation":"After merging parent and child (child's fields win), delegateEnvelope() verifies with isEnvelopeReduction() that the result is no broader than the parent: listed capabilities must be subsets (monotone shrinking), network and destructive may not appear unless the parent had them, and expiry cannot be extended. Violations throw Error('capability-envelope-cannot-grow') — this is the anti-privilege-escalation invariant.","triggerScenarios":"A child envelope granting a tool/server/namespace absent from the parent; child.network === true when parent.network !== true; child.destructive === true from a non-destructive parent; child.expiresAt further in the future than parent.expiresAt (Math.min clamps expiry, but a broader listed set still throws).","commonSituations":"Building sub-agent envelopes from a general template that includes the full tool list instead of intersecting with the parent's; copy-pasting an envelope and trimming only the principal; delegation code that inherits 'network: true' defaults while parents are offline-only.","solutions":["Derive the child by intersecting: filter the child's tools/servers/namespaces to those the parent already holds.","Only set child.network / child.destructive when the parent grants them; otherwise omit (undefined).","Accept that expiry is clamped to the parent's — do not try to extend a child beyond its parent's lifetime.","Write a helper that constructs children via parent-to-child subtraction so growth is structurally impossible."],"exampleFix":"// before\nconst child = delegateEnvelope(parent, {\n  ...childSpec, // includes tools the parent lacks and network: true\n});\n\n// after\nconst child = delegateEnvelope(parent, {\n  ...childSpec,\n  tools: childSpec.tools.filter((t) => parent.tools.includes(t)),\n  network: parent.network === true ? childSpec.network : undefined,\n  expiresAt: Math.min(childSpec.expiresAt, parent.expiresAt),\n});","handlingStrategy":"validation","validationCode":"function shrinkToParent(parent: CapabilityEnvelope, spec: Partial<CapabilityEnvelope>) {\n  return {\n    ...spec,\n    tools: spec.tools?.filter((t) => parent.tools.includes(t)),\n    servers: spec.servers?.filter((s) => parent.servers.includes(s)),\n    namespaces: spec.namespaces?.filter((n) => parent.namespaces.includes(n)),\n    network: parent.network === true ? spec.network : undefined,\n    destructive: parent.destructive === true ? spec.destructive : undefined,\n    expiresAt: Math.min(spec.expiresAt ?? Infinity, parent.expiresAt ?? Infinity),\n  };\n}\nconst child = delegateEnvelope(parent, shrinkToParent(parent, childSpec));","typeGuard":"function isSubsetOf<T>(child: T[] | undefined, parent: T[] | undefined): boolean {\n  const p = new Set(parent ?? []);\n  return (child ?? []).every((item) => p.has(item));\n}","tryCatchPattern":"try {\n  return delegateEnvelope(parent, child);\n} catch (err) {\n  if (err instanceof Error && err.message === 'capability-envelope-cannot-grow') {\n    // security invariant: never 'fix' by widening the parent automatically.\n    alertSecurityTeam({ parent, child });\n    throw new ForbiddenError('child envelope requested capabilities beyond its parent');\n  }\n  throw err;\n}","preventionTips":["Always derive child envelopes by intersecting with the parent's grants, never from a global template.","Omit network/destructive on children unless the parent explicitly grants them.","Treat this error as a potential privilege-escalation attempt: log and alert, do not auto-widen."],"tags":["capability","privilege-escalation","security","delegation"],"backgroundTag":"privilege-escalation-blocked","analyzedSha":"fa13ee4ad60ac2090b1480656eb233521790d640","analyzedAt":"2026-08-18T21:34:22.708Z","contentChangedAt":"2026-08-18T21:34:22.708Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}