ruvnet/ruflo · critical · Error
capability-envelope-cannot-grow
Error message
capability-envelope-cannot-grow
What it means
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.
Source
Thrown at v3/@claude-flow/security/src/policy/envelope.ts:107
parent: CapabilityEnvelope,
child: CapabilityEnvelope,
): CapabilityEnvelope {
if ((parent.delegationDepth ?? 0) <= 0) {
throw new Error('delegation-depth-exhausted');
}
const reduced = {
...parent,
...child,
delegationDepth: Math.min(
child.delegationDepth ?? Number.MAX_SAFE_INTEGER,
(parent.delegationDepth ?? 0) - 1,
),
expiresAt: Math.min(
child.expiresAt ?? Number.MAX_SAFE_INTEGER,
parent.expiresAt ?? Number.MAX_SAFE_INTEGER,
),
};
if (!isEnvelopeReduction(parent, reduced)) throw new Error('capability-envelope-cannot-grow');
return reduced;
}
View on GitHub (pinned to fa13ee4ad6)
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.
Example fix
// before
const child = delegateEnvelope(parent, {
...childSpec, // includes tools the parent lacks and network: true
});
// after
const child = delegateEnvelope(parent, {
...childSpec,
tools: childSpec.tools.filter((t) => parent.tools.includes(t)),
network: parent.network === true ? childSpec.network : undefined,
expiresAt: Math.min(childSpec.expiresAt, parent.expiresAt),
}); Defensive patterns
Strategy: validation
Validate before calling
function shrinkToParent(parent: CapabilityEnvelope, spec: Partial<CapabilityEnvelope>) {
return {
...spec,
tools: spec.tools?.filter((t) => parent.tools.includes(t)),
servers: spec.servers?.filter((s) => parent.servers.includes(s)),
namespaces: spec.namespaces?.filter((n) => parent.namespaces.includes(n)),
network: parent.network === true ? spec.network : undefined,
destructive: parent.destructive === true ? spec.destructive : undefined,
expiresAt: Math.min(spec.expiresAt ?? Infinity, parent.expiresAt ?? Infinity),
};
}
const child = delegateEnvelope(parent, shrinkToParent(parent, childSpec)); Type guard
function isSubsetOf<T>(child: T[] | undefined, parent: T[] | undefined): boolean {
const p = new Set(parent ?? []);
return (child ?? []).every((item) => p.has(item));
} Try / catch
try {
return delegateEnvelope(parent, child);
} catch (err) {
if (err instanceof Error && err.message === 'capability-envelope-cannot-grow') {
// security invariant: never 'fix' by widening the parent automatically.
alertSecurityTeam({ parent, child });
throw new ForbiddenError('child envelope requested capabilities beyond its parent');
}
throw err;
} Prevention
- 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.
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- worker ${worker.id} capability envelope cannot expand
- delegation-depth-exhausted
- depth-underflow
- scope-expired
- scope-cannot-grow
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/ecfbf14eacca95ad.
Report an issue: GitHub.