Skip to content

add before/after tool call hooks#4938

Open
DharunThota wants to merge 1 commit intoakto-api-security:masterfrom
DharunThota:feat/gemini-cli-tool-hooks
Open

add before/after tool call hooks#4938
DharunThota wants to merge 1 commit intoakto-api-security:masterfrom
DharunThota:feat/gemini-cli-tool-hooks

Conversation

@DharunThota
Copy link
Copy Markdown
Contributor

No description provided.

Copilot AI review requested due to automatic review settings April 24, 2026 11:53
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds Gemini CLI hook support for tool calls (BeforeTool/AfterTool) to validate MCP/non-MCP tool executions with Akto Guardrails and ingest tool I/O, including new wrapper scripts and Windows example settings.

Changes:

  • Extend settings*.json.example to include BeforeTool/AfterTool hooks (plus minor JSON formatting fixes).
  • Add new pre-/post-tool Python hooks and corresponding shell/PowerShell wrappers.
  • Expand .env.example with tool-hook related configuration variables.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
apps/mcp-endpoint-shield/gemini-cli-hooks/settings.windows.json.example New Windows example settings including BeforeTool/AfterTool hook commands
apps/mcp-endpoint-shield/gemini-cli-hooks/settings.json.example Add BeforeTool/AfterTool hooks to the existing example settings
apps/mcp-endpoint-shield/gemini-cli-hooks/akto-validate-response-wrapper.ps1 New PowerShell wrapper for response hook
apps/mcp-endpoint-shield/gemini-cli-hooks/akto-validate-prompt-wrapper.sh Fix prompt wrapper to call the correct Python script
apps/mcp-endpoint-shield/gemini-cli-hooks/akto-validate-prompt-wrapper.ps1 New PowerShell wrapper for prompt hook
apps/mcp-endpoint-shield/gemini-cli-hooks/akto-validate-pre-tool.py New BeforeTool hook implementation (validation + blocked-ingest flow)
apps/mcp-endpoint-shield/gemini-cli-hooks/akto-validate-pre-tool-wrapper.sh New shell wrapper for pre-tool hook
apps/mcp-endpoint-shield/gemini-cli-hooks/akto-validate-pre-tool-wrapper.ps1 New PowerShell wrapper for pre-tool hook
apps/mcp-endpoint-shield/gemini-cli-hooks/akto-validate-post-tool.py New AfterTool hook implementation (response guardrails + ingestion + blocked flow)
apps/mcp-endpoint-shield/gemini-cli-hooks/akto-validate-post-tool-wrapper.sh New shell wrapper for post-tool hook
apps/mcp-endpoint-shield/gemini-cli-hooks/akto-validate-post-tool-wrapper.ps1 New PowerShell wrapper for post-tool hook
apps/mcp-endpoint-shield/gemini-cli-hooks/.env.example Document additional env vars for tool-hook behavior and ingestion

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

"type": "HTTP/1.1",
"status": "200",
"akto_account_id": "1000000",
"akto_vxlan_id": 0,
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

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

akto_vxlan_id is hard-coded to 0 here, but the existing BeforeModel/AfterModel hooks use the machine/device identifier as akto_vxlan_id. If downstream attribution or tenancy relies on vxlan_id, tool hook events may be mis-grouped. Consider aligning this field with the other hooks (e.g., use DEVICE_ID or the same vxlan convention used elsewhere in this directory).

Suggested change
"akto_vxlan_id": 0,
"akto_vxlan_id": get_machine_id(),

Copilot uses AI. Check for mistakes.
session_info[field] = value

tool_name = str(input_data.get("tool_name") or "")
tool_input = input_data.get("tool_input") or {}
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

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

tool_input = input_data.get("tool_input") or {} rewrites falsy tool inputs to {}, which can change what gets validated/ingested and can cause validation to be skipped due to later truthiness checks. Prefer preserving the original value (e.g., get("tool_input", {}) or None) and avoid using or {} here.

Suggested change
tool_input = input_data.get("tool_input") or {}
tool_input = input_data.get("tool_input", {})

Copilot uses AI. Check for mistakes.
Comment on lines +39 to +40
"command": "bash $GEMINI_PROJECT_DIR/.gemini/hooks/akto-validate-pre-tool-wrapper.sh",
"timeout": 10000,
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

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

The command uses an unquoted $GEMINI_PROJECT_DIR path. If the project directory contains spaces, the hook command can fail to execute. Quote the path (or invoke bash -lc with a properly quoted script path) for both the pre- and post-tool hook commands.

Copilot uses AI. Check for mistakes.
Comment on lines +39 to +40
"command": "powershell.exe -ExecutionPolicy Bypass -File %USERPROFILE%\\.gemini\\hooks\\akto-validate-pre-tool-wrapper.ps1",
"timeout": 10000,
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

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

The PowerShell -File path is not quoted. If %USERPROFILE% contains spaces, the hook command can fail. Quote the expanded path (e.g., -File "%USERPROFILE%\.gemini\hooks\...") for the tool hook commands (and ideally the model hooks as well).

Copilot uses AI. Check for mistakes.


def create_ssl_context():
return ssl._create_unverified_context()
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

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

create_ssl_context() always returns an unverified TLS context, which effectively disables certificate validation even when SSL_VERIFY is set to true. This makes the hook’s calls to AKTO_DATA_INGESTION_URL vulnerable to MITM. Use a default verifying context and only disable verification when explicitly configured; if SSL_CERT_PATH is set, load it into the context.

Suggested change
return ssl._create_unverified_context()
context = ssl.create_default_context() if SSL_VERIFY else ssl._create_unverified_context()
if SSL_CERT_PATH:
context.load_verify_locations(cafile=SSL_CERT_PATH)
return context

Copilot uses AI. Check for mistakes.
Comment on lines +62 to +64
return ssl._create_unverified_context()


Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

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

create_ssl_context() always returns an unverified TLS context, which disables certificate validation even when SSL_VERIFY is true. This is a security risk for calls to AKTO_DATA_INGESTION_URL. Build a verifying context by default and only disable verification when explicitly requested; load SSL_CERT_PATH when provided.

Suggested change
return ssl._create_unverified_context()
if not SSL_VERIFY:
return ssl._create_unverified_context()
context = ssl.create_default_context()
if SSL_CERT_PATH:
context.load_verify_locations(SSL_CERT_PATH)
return context

Copilot uses AI. Check for mistakes.
has_error: bool = False,
session_info: Optional[Dict[str, Any]] = None,
) -> Tuple[bool, str, str]:
if not tool_input or tool_result is None:
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

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

call_guardrails() returns early on if not tool_input or tool_result is None, so tool calls with empty {} args will never have response guardrails applied. This can leave a gap for tools that legitimately take no arguments. Consider checking tool_input is None instead of truthiness, and still validating/ingesting based on tool name and result.

Suggested change
if not tool_input or tool_result is None:
if tool_input is None or tool_result is None:

Copilot uses AI. Check for mistakes.
Comment on lines +473 to +475
"body": json.dumps(
{"x-blocked-by": "Akto Proxy", "reason": reason or "Policy violation"}
)
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

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

responsePayload wraps body as a JSON-encoded string (json.dumps(...)) inside an already JSON-encoded payload. This double-encodes the body and makes the shape inconsistent with the non-blocked path (where body is an object). Set body to the object directly and only JSON-encode once at the outermost responsePayload.

Suggested change
"body": json.dumps(
{"x-blocked-by": "Akto Proxy", "reason": reason or "Policy violation"}
)
"body": {
"x-blocked-by": "Akto Proxy",
"reason": reason or "Policy violation",
}

Copilot uses AI. Check for mistakes.
"type": "HTTP/1.1",
"status": status,
"akto_account_id": "1000000",
"akto_vxlan_id": 0,
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

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

akto_vxlan_id is set to 0 here, while the other Gemini CLI hooks in this directory use the device/machine id for akto_vxlan_id. If vxlan_id is used for attribution, tool result events may not join correctly with prompt/response traffic. Consider using the same vxlan_id strategy as the other hooks.

Suggested change
"akto_vxlan_id": 0,
"akto_vxlan_id": get_machine_id(),

Copilot uses AI. Check for mistakes.
Comment on lines +283 to +284
if not tool_input:
return True, "", ""
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

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

call_guardrails() returns early on if not tool_input, so tool calls with empty arguments (or arguments that were coerced to {}) will skip validation entirely (fail-open) and won’t be ingested. Consider treating None/missing input differently from an intentionally empty {} and validate/ingest based on tool name even when args are empty.

Suggested change
if not tool_input:
return True, "", ""
if tool_input is None:
tool_input = {}

Copilot uses AI. Check for mistakes.
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.

2 participants