feat(plugins): Add GovernancePlugin for runtime agent governance#4897
feat(plugins): Add GovernancePlugin for runtime agent governance#4897sunilp wants to merge 2 commits intogoogle:mainfrom
Conversation
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
Summary of ChangesHello, 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 Highlights
🧠 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 AssistThe 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
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 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
|
There was a problem hiding this comment.
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.
| MODEL_REQUEST = "model_request" | ||
| INVOCATION_START = "invocation_start" |
There was a problem hiding this comment.
| session_id=callback_context._invocation_context.session.id, | ||
| ) |
There was a problem hiding this comment.
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.
| session_id=callback_context._invocation_context.session.id, | |
| ) | |
| session_id=callback_context.invocation_context.session.id, |
| invocation_id=inv_id, | ||
| session_id=tool_context._invocation_context.session.id, |
There was a problem hiding this comment.
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.
| invocation_id=inv_id, | |
| session_id=tool_context._invocation_context.session.id, | |
| session_id=tool_context.invocation_context.session.id, |
| """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): |
There was a problem hiding this comment.
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.
| """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() |
| session_id=tool_context._invocation_context.session.id, | ||
| args_hash=_hash_dict(tool_args), |
There was a problem hiding this comment.
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.
| 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
|
Hi @sunilp , Thank you for your contribution! We appreciate you taking the time to submit this pull request. |
Adds policy-based tool filtering, delegation scope enforcement, and structured audit trails for ADK agents. Moved from google/adk-python#4897 per maintainer guidance.
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>
* 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>
Summary
Ref #4764
Adds a
GovernancePluginthat 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:
AuditEventschema with pluggableAuditHandlerWhat's included
Schemas:
PolicyDecision— structured ALLOW/DENY with reason, evaluator, timestampAuditEvent— 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()andevaluate_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 hasheson_tool_error_callback: Records tool errors in audit trailbefore_agent_callback: Enforces delegation scope via PolicyEvaluatorbefore_run_callback/after_run_callback: Lifecycle tracking and counter managementUsage
Design decisions
PolicyEvaluatorandAuditHandleraretyping.Protocolinterfaces, not abstract classes. Users implement what they need without inheritance.Test plan
Unit tests
test_governance_plugin.pycovering:Checklist