Skip to content

feat(plugins): Add GovernancePlugin for runtime agent governance#4897

Closed
sunilp wants to merge 2 commits intogoogle:mainfrom
sunilp:feat/governance-plugin
Closed

feat(plugins): Add GovernancePlugin for runtime agent governance#4897
sunilp wants to merge 2 commits intogoogle:mainfrom
sunilp:feat/governance-plugin

Conversation

@sunilp
Copy link

@sunilp sunilp commented Mar 19, 2026

Summary

Ref #4764

Adds a GovernancePlugin that provides runtime governance for ADK agents — policy-based tool filtering, delegation scope enforcement, and structured audit trails — without modifying agent logic.

This directly addresses the three asks from #4764:

  1. Before/after callbacks on tool execution — policy checks run before tool invocation; audit receipts emitted after
  2. Sub-agent delegation governance — delegation scope constraints with monotonic narrowing
  3. Structured audit eventsAuditEvent schema with pluggable AuditHandler

What's included

Schemas:

  • PolicyDecision — structured ALLOW/DENY with reason, evaluator, timestamp
  • AuditEvent — structured audit record (action type, agent, tool, args/result hashes, policy decision, session/invocation IDs)
  • ToolPolicy — per-tool config (allowed, call limits, audit toggle)
  • DelegationScope — authority constraints for sub-agents (allowed tools, allowed sub-agents, max depth)

Protocols:

  • PolicyEvaluator — standard integration point for custom policy engines. Two methods: evaluate_tool_call() and evaluate_agent_delegation()
  • AuditHandler — pluggable audit event sink (default: LoggingAuditHandler)

Plugin behavior:

  • before_tool_callback: Evaluates tool calls against blocked list → per-tool policy → call limits → delegation scope → custom PolicyEvaluator. Denied calls return error response and emit audit event.
  • after_tool_callback: Emits audit receipt with args/result hashes
  • on_tool_error_callback: Records tool errors in audit trail
  • before_agent_callback: Enforces delegation scope via PolicyEvaluator
  • before_run_callback / after_run_callback: Lifecycle tracking and counter management

Usage

from google.adk.plugins import GovernancePlugin, ToolPolicy, DelegationScope

# Simple: block specific tools
plugin = GovernancePlugin(blocked_tools={"dangerous_tool"})

# Advanced: custom policy engine + delegation scopes
plugin = GovernancePlugin(
    policy_evaluator=my_evaluator,
    tool_policies={
        "sql_tool": ToolPolicy(max_calls_per_invocation=5),
        "internal_api": ToolPolicy(allowed=False),
    },
    delegation_scopes={
        "sub_agent": DelegationScope(allowed_tools={"safe_read"}),
    },
)

runner = Runner(agent=root_agent, plugins=[plugin])

# Access audit trail
for event in plugin.audit_log:
    print(event.action, event.tool_name, event.policy_decision)

Design decisions

  • Extends BasePlugin — works within the existing plugin system rather than requiring framework changes. This is pragmatic for immediate adoption while the "mandatory callback tier" discussion evolves.
  • Protocol-based extensibilityPolicyEvaluator and AuditHandler are typing.Protocol interfaces, not abstract classes. Users implement what they need without inheritance.
  • Hashes, not values — audit events store SHA-256 hashes of tool args/results, not the values themselves. This supports compliance auditing without leaking sensitive data.
  • Layered evaluation — checks run in order (blocked list → policy → limits → scope → evaluator) with early exit on deny. Each layer is independently useful.

Test plan

Unit tests

  • 25 new tests in test_governance_plugin.py covering:
    • PolicyDecision factories and timestamps
    • Plugin initialization (default and custom)
    • Blocked tools denial
    • Tool policy disabled
    • Call count limit enforcement (2 allowed, 3rd denied)
    • Custom PolicyEvaluator deny/allow
    • Delegation scope restricting/allowing tools
    • Audit receipt emission and skip-when-not-required
    • Tool error auditing
    • Agent delegation denied/allowed/unconstrained
    • Invocation lifecycle start/end
    • Counter cleanup after run
    • Audit log accumulation
    • Hash utility correctness

Checklist

  • Read CONTRIBUTING.md
  • Self-reviewed code
  • Added tests for new functionality
  • All tests pass locally (25 passed)
  • Code formatted with pyink and isort

Adds a new GovernancePlugin that provides runtime governance for ADK
agents without modifying agent logic:

- Policy-based tool filtering via pluggable PolicyEvaluator protocol
- Delegation scope enforcement with monotonic narrowing
- Structured audit trail with AuditEvent schema and AuditHandler protocol
- Per-tool policies (blocklist, call limits, audit toggles)
- Built-in LoggingAuditHandler, extensible to OTel/external systems

Includes 25 unit tests covering all governance scenarios.

Ref google#4764
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a robust GovernancePlugin to the ADK framework, significantly enhancing the security and compliance capabilities of agents. The plugin enables runtime enforcement of policies for tool usage and agent delegation, ensuring that agents operate within defined boundaries. It also provides a structured audit trail for all governance-related decisions, offering transparency and accountability. This addition allows for greater control over agent behavior and interactions within complex systems.

Highlights

  • Runtime Governance for ADK Agents: Introduced a new GovernancePlugin to provide policy-based tool filtering, delegation scope enforcement, and structured audit trails for ADK agents without altering their core logic.
  • Policy-Based Tool Filtering: Implemented mechanisms to evaluate tool calls against a configurable policy engine before execution, allowing for blocking specific tools, enforcing per-tool policies (e.g., call limits), and restricting tools based on delegation scope.
  • Sub-Agent Delegation Governance: Added functionality to validate sub-agent delegations against defined authority scopes, ensuring monotonic narrowing of permissions and preventing unauthorized actions.
  • Structured Audit Trails: Designed and implemented a structured AuditEvent schema and a pluggable AuditHandler protocol to emit detailed audit events at every governance decision point, supporting compliance and traceability.
  • Extensibility and Integration: Utilized typing.Protocol for PolicyEvaluator and AuditHandler to enable flexible, protocol-based extensibility, allowing users to implement custom policy engines and audit sinks without strict inheritance, and integrated within the existing BasePlugin system.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a robust GovernancePlugin for ADK agents, addressing key aspects of runtime governance including policy-based tool filtering, delegation scope enforcement, and structured audit trails. The design leverages existing plugin mechanisms and protocol-based extensibility, which is commendable. The inclusion of comprehensive unit tests demonstrates a thorough approach to ensuring the reliability of the new functionality. Overall, this is a well-designed and implemented feature that significantly enhances the governance capabilities of ADK agents.

Comment on lines +126 to +127
MODEL_REQUEST = "model_request"
INVOCATION_START = "invocation_start"
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The AuditAction.MODEL_REQUEST enum member is defined but does not appear to be used anywhere within the governance_plugin.py file. If this action is not currently utilized, consider removing it to avoid dead code and potential confusion, or add a comment explaining its intended future use.

Comment on lines +448 to +449
session_id=callback_context._invocation_context.session.id,
)
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

Accessing callback_context._invocation_context.session.id directly uses a private attribute. While it works, relying on private attributes can lead to brittle code that breaks if internal implementations change. It would be more robust to expose session_id via a public property or method on CallbackContext if it's intended for external use by plugins.

Suggested change
session_id=callback_context._invocation_context.session.id,
)
session_id=callback_context.invocation_context.session.id,

Comment on lines +567 to +568
invocation_id=inv_id,
session_id=tool_context._invocation_context.session.id,
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

Accessing tool_context._invocation_context.session.id directly uses a private attribute. Similar to the before_agent_callback, it's generally better to expose necessary data through public interfaces rather than relying on private attributes to maintain encapsulation and reduce coupling. Consider adding a public property for session_id to ToolContext.

Suggested change
invocation_id=inv_id,
session_id=tool_context._invocation_context.session.id,
session_id=tool_context.invocation_context.session.id,

Comment on lines +673 to +677
"""Produce a stable SHA-256 hash of a dictionary for audit purposes."""
try:
serialized = json.dumps(d, sort_keys=True, default=str)
return hashlib.sha256(serialized.encode()).hexdigest()[:16]
except (TypeError, ValueError):
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The _hash_dict function truncates the SHA-256 hash to 16 characters (64 bits). While sort_keys=True ensures determinism, truncating a cryptographic hash significantly reduces its collision resistance. For "audit purposes" where the integrity and uniqueness of records are important, a 64-bit hash might be insufficient to prevent collisions, especially over a large number of events. Consider using the full SHA-256 hash (64 hex characters) or at least a longer truncation (e.g., 128 bits / 32 hex characters) to provide a stronger guarantee against accidental hash collisions, or clarify in the docstring that this truncation is acceptable for the specific collision probability tolerance of the audit system.

Suggested change
"""Produce a stable SHA-256 hash of a dictionary for audit purposes."""
try:
serialized = json.dumps(d, sort_keys=True, default=str)
return hashlib.sha256(serialized.encode()).hexdigest()[:16]
except (TypeError, ValueError):
return hashlib.sha256(serialized.encode()).hexdigest()

Comment on lines +646 to +647
session_id=tool_context._invocation_context.session.id,
args_hash=_hash_dict(tool_args),
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

Accessing tool_context._invocation_context.session.id directly uses a private attribute. It's recommended to expose session_id through a public property or method on ToolContext to avoid relying on internal implementation details.

Suggested change
session_id=tool_context._invocation_context.session.id,
args_hash=_hash_dict(tool_args),
session_id=tool_context.invocation_context.session.id,

- Remove unused AuditAction.MODEL_REQUEST enum member
- Replace _invocation_context.session.id private attr access with
  _get_session_id() helper that tries public attrs first
- Use full SHA-256 hex digest (64 chars) instead of truncated 16 chars
  for improved collision resistance in audit hashes
@rohityan rohityan self-assigned this Mar 19, 2026
@rohityan
Copy link
Collaborator

Hi @sunilp , Thank you for your contribution! We appreciate you taking the time to submit this pull request.
Closing this PR here as it belongs to adk-python-community repo.
We highly recommend releasing the feature as a standalone package that we will then share through: https://google.github.io/adk-docs/integrations/

@rohityan rohityan closed this Mar 19, 2026
@rohityan rohityan added the community repo [Community] FRs/issues well suited for google/adk-python-community repository label Mar 19, 2026
sunilp added a commit to sunilp/adk-python-community that referenced this pull request Mar 19, 2026
Adds policy-based tool filtering, delegation scope enforcement,
and structured audit trails for ADK agents.

Moved from google/adk-python#4897 per maintainer guidance.
imran-siddique added a commit to imran-siddique/agent-governance-toolkit that referenced this pull request Mar 19, 2026
Implements the PolicyEvaluator protocol from google/adk-python#4897:
- ADKPolicyEvaluator: YAML-configurable policy engine for ADK agents
- GovernanceCallbacks: wires into before/after tool/agent hooks
- DelegationScope: monotonic scope narrowing for sub-agents
- Structured audit events with pluggable handlers
- Sample policy config (examples/policies/adk-governance.yaml)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
imran-siddique added a commit to microsoft/agent-governance-toolkit that referenced this pull request Mar 20, 2026
* feat(security): harden against agent sandbox escape vectors

Address 3 critical gaps identified in Ona/Veto agent security research:

1. Tool content hashing (defeats tool aliasing/wrapping attacks):
   - ToolRegistry now computes SHA-256 hash of handler source at registration
   - execute_tool() verifies integrity before execution, blocks on mismatch
   - New ContentHashInterceptor in base.py for intercept-level hash verification
   - Integrity violation audit log with get_integrity_violations()

2. PolicyEngine freeze (prevents runtime self-modification):
   - New freeze() method makes engine immutable after initialization
   - add_constraint, set/update_agent_context, add_conditional_permission
     all raise RuntimeError when frozen
   - Full mutation audit log records all operations (allowed and blocked)
   - is_frozen property for inspection

3. Approval quorum and fatigue detection (defeats approval fatigue):
   - New QuorumConfig dataclass for M-of-N approval requirements
   - EscalationHandler supports quorum-based vote counting
   - Fatigue detection: auto-DENY when agent exceeds escalation rate threshold
   - Per-agent rate tracking with configurable window and threshold
   - EscalationRequest.votes field tracks individual approver votes

All changes are backward-compatible: new parameters are optional with
defaults that preserve existing behavior. 33 new tests, 53 total pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(security): address PR review feedback on sandbox hardening

- PolicyEngine.freeze() now converts dicts to MappingProxyType/frozenset
  for true immutability (not just boolean guard) — addresses HIGH finding
- Removed insecure bytecode fallback from _compute_handler_hash; returns
  empty string with warning for unverifiable handlers — addresses CRITICAL
- Added CHANGELOG entries for all new security features
- Added 2 new tests: frozen dicts are immutable proxies, permissions are
  frozensets

55 tests pass (20 existing + 35 new).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* docs: add security hardening section to README

Document the 3 sandbox escape defenses with usage examples:
- Tool content hashing with ToolRegistry and ContentHashInterceptor
- PolicyEngine.freeze() with MappingProxyType immutability
- Approval quorum (QuorumConfig) and fatigue detection

Addresses docs-sync-checker feedback on PR #297.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat(adk): add Google ADK governance adapter with PolicyEvaluator

Implements the PolicyEvaluator protocol from google/adk-python#4897:
- ADKPolicyEvaluator: YAML-configurable policy engine for ADK agents
- GovernanceCallbacks: wires into before/after tool/agent hooks
- DelegationScope: monotonic scope narrowing for sub-agents
- Structured audit events with pluggable handlers
- Sample policy config (examples/policies/adk-governance.yaml)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(security): address all 24 security findings across codebase

Critical (9 fixed):
- CWE-502: Replace pickle.loads with JSON in process_isolation.py and agent_hibernation.py
- CWE-78: Convert shell=True to list-form subprocess in prepare_release.py, prepare_pypi.py
- CWE-94: Replace eval() with safe AST walker in calculator.py
- CWE-77: Sanitize issue title injection in ai-spec-drafter.yml
- CWE-829: Pin setup-node action to SHA in ai-agent-runner/action.yml
- CWE-494: Add SHA-256 verification for NuGet download in publish.yml
- CWE-1395: Tighten cryptography>=44.0.0, django>=4.2 across 7 pyproject.toml files

High (6 fixed):
- CWE-798: Replace hardcoded API key placeholder in VS Code extension
- CWE-502: yaml.safe_load + json.load in github-reviewer example
- CWE-94: Replace eval() docstring example in langchain tools
- CWE-22: Add path traversal validation in .NET FileTrustStore
- CWE-295: Remove non-hash pip install fallback in ci.yml and publish.yml
- GHSA-rf6f-7fwh-wjgh: Fix flatted prototype pollution in 3 npm packages

Medium (6 fixed):
- CWE-79: Replace innerHTML with safe DOM APIs in Chrome extension
- CWE-328: Replace MD5 with SHA-256 in github-reviewer
- CWE-330: Replace random.randint with secrets module in defi-sentinel
- CWE-327: Add deprecation warnings on HMAC-SHA256 fallback in .NET
- CWE-250: Narrow scorecard.yml permissions
- Audit all 10 pull_request_target workflows for HEAD checkout safety

Low (3 fixed):
- Replace weak default passwords in examples
- Add security justification comments to safe workflows

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(ci): restore working pip install syntax for test jobs

The --require-hashes with inline --hash flags breaks when mixed
with editable installs. Restore the working pattern for test deps
while keeping hash verification for the lint requirements file.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

community repo [Community] FRs/issues well suited for google/adk-python-community repository

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants