Skip to content

fix(harness): enforce parent DENY rules for spawned subagents - #2477

Merged
jujn merged 1 commit into
agentscope-ai:mainfrom
RainYuY:fix/subagent-deny-inheritance
Jul 29, 2026
Merged

fix(harness): enforce parent DENY rules for spawned subagents#2477
jujn merged 1 commit into
agentscope-ai:mainfrom
RainYuY:fix/subagent-deny-inheritance

Conversation

@RainYuY

@RainYuY RainYuY commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

Summary

Ensure spawned subagents cannot execute tools explicitly denied by their parent agent.

Problem

SubagentDeclaration.inheritParentPermissions defaults to true, but parent DENY rules were not effective for automatically created HarnessAgent subagents.

There were three causes:

  1. HarnessAgent wraps a ReActAgent delegate but is not itself a ReActAgent, so the existing type check skipped propagation.
  2. The previous logic updated the default permission engine instead of the child’s actual (userId, sessionId) slot.
  3. Updating only PermissionEngine left AgentState.permissionContext unchanged, allowing the runtime to treat the context as trivial and bypass the engine.

As a result, a subagent could execute a tool explicitly denied by its parent.

Changes

  • Resolve both direct ReActAgent children and HarnessAgent delegates.
  • Merge parent DENY rules into the child’s actual execution slot.
  • Keep AgentState, the permission engine cache, and persisted state synchronized.
  • Reapply current parent restrictions when:
    • spawning a new child;
    • reusing a persistent child;
    • sending a message to an existing child.
  • Preserve the child’s existing:
    • permission mode;
    • ALLOW rules;
    • ASK rules;
    • working directories;
    • local DENY rules.
  • Respect inheritParentPermissions=false.
  • Avoid adding duplicate DENY rules.
  • Report automatic rule rejection as Permission denied by rules.

Only parent DENY rules are inherited. This change does not inherit parent ALLOW rules, ASK rules, or working directories.

For an initially trivial child context, BYPASS mode preserves the previous behavior for unmatched PASSTHROUGH tools, while explicit DENY rules still take precedence.

Tests

Added coverage for:

  • DENY propagation through a HarnessAgent delegate.
  • Direct ReActAgent children.
  • Real (userId, childSessionId) slot updates.
  • Preservation of the child’s existing permission context.
  • Disabled inheritance.
  • Persistent child reuse.
  • agent_send synchronization.
  • Rule deduplication.
  • State-store persistence and session isolation.

@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@oss-maintainer oss-maintainer left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: fix(harness): enforce parent DENY rules for spawned subagents

Verdict: Approved ✅

Security Analysis

This PR fixes a real permission bypass where spawned subagents could execute tools explicitly denied by their parent. Three root causes are correctly addressed:

  1. Type check gap — wraps a delegate but isn't itself a , so the old instanceof ReActAgent check skipped propagation. Fixed by resolving the delegate via harnessAgent.getDelegate().

  2. Wrong target slot — Previous logic updated the default permission engine instead of the child's actual (userId, sessionId) slot. Fixed by threading userId + childSessionId through to mergeParentDenyRulesIntoSlot().

  3. Stale AgentState.permissionContext — Updating only PermissionEngine left the state's context unchanged, allowing the runtime to treat it as trivial and bypass the engine. Fixed by the new installPermissionContext() helper that keeps state, engine cache, and persistence synchronized.

Design Quality

  • Conservative merging: Only parent DENY rules are inherited; child's ALLOW/ASK rules, working directories, and mode are preserved. This is the correct security boundary — deny lists propagate, allow lists don't.
  • Trivial child fallback: When a child has no explicit permissions (isTrivial()), the merged context uses BYPASS mode, preserving backward compatibility for PASSTHROUGH tools while still enforcing explicit DENY rules.
  • Deduplication: !targetRules.contains(rule) prevents duplicate rule accumulation on repeated spawn/send calls.
  • Three reapplication points: spawn, reuse, and agent_send — covers all entry paths for parent→child interaction.

Minor Observations

  • "Permission denied by user""Permission denied by rules" is a good accuracy improvement for automatic rule rejections.
  • The replacePermissionContext() public API on ReActAgent is well-documented with clear Javadoc about in-flight call semantics.
  • 385 lines of tests covering HarnessAgent delegate, direct ReActAgent, slot isolation, persistence, dedup, and inheritParentPermissions=false — thorough.

CI Status

Builds pending, CLA ✅. Looks good to merge once CI passes.

@oss-maintainer oss-maintainer left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: fix(harness): enforce parent DENY rules for spawned subagents

Verdict: Approved ✅

Security Analysis

This PR fixes a real permission bypass where spawned subagents could execute tools explicitly denied by their parent. Three root causes are correctly addressed:

  1. Type check gapHarnessAgent wraps a ReActAgent delegate but isn't itself a ReActAgent, so the old instanceof ReActAgent check skipped propagation. Fixed by resolving the delegate via harnessAgent.getDelegate().

  2. Wrong target slot — Previous logic updated the default permission engine instead of the child's actual (userId, sessionId) slot. Fixed by threading userId + childSessionId through to mergeParentDenyRulesIntoSlot().

  3. Stale AgentState.permissionContext — Updating only PermissionEngine left the state's context unchanged, allowing the runtime to treat it as trivial and bypass the engine. Fixed by the new installPermissionContext() helper that keeps state, engine cache, and persistence synchronized.

Design Quality

  • Conservative merging: Only parent DENY rules are inherited; child's ALLOW/ASK rules, working directories, and mode are preserved. This is the correct security boundary — deny lists propagate, allow lists don't.
  • Trivial child fallback: When a child has no explicit permissions (isTrivial()), the merged context uses BYPASS mode, preserving backward compatibility for PASSTHROUGH tools while still enforcing explicit DENY rules.
  • Deduplication: !targetRules.contains(rule) prevents duplicate rule accumulation on repeated spawn/send calls.
  • Three reapplication points: spawn, reuse, and agent_send — covers all entry paths for parent→child interaction.

Minor Observations

  • "Permission denied by user""Permission denied by rules" is a good accuracy improvement for automatic rule rejections.
  • The replacePermissionContext() public API on ReActAgent is well-documented with clear Javadoc about in-flight call semantics.
  • 385 lines of tests covering HarnessAgent delegate, direct ReActAgent, slot isolation, persistence, dedup, and inheritParentPermissions=false — thorough.

CI Status

Builds pending, CLA ✅. Looks good to merge once CI passes.

@codecov

codecov Bot commented Jul 29, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 88.33333% with 7 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...e/src/main/java/io/agentscope/core/ReActAgent.java 61.53% 4 Missing and 1 partial ⚠️
.../agentscope/harness/agent/tool/AgentSpawnTool.java 95.74% 0 Missing and 2 partials ⚠️

📢 Thoughts on this report? Let us know!

@jujn
jujn merged commit 371dfe8 into agentscope-ai:main Jul 29, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants