From b23621d5a9af7dc438dabbfa7c51f1de6e4c50d0 Mon Sep 17 00:00:00 2001 From: stevenhorsman Date: Thu, 25 Jun 2026 16:42:44 +0100 Subject: [PATCH] ci: add static check for agent policy coverage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add ci/check_agent_policy_coverage.sh, a static check that asserts every RPC method declared in the AgentService block of agent.proto has: 1. A policy gate (is_allowed or do_set_policy call) in the corresponding handler in src/agent/src/rpc.rs. 2. A default rule entry in src/tools/genpolicy/rules.rego. This prevents future handlers from silently bypassing the policy boundary — the gap that prompted this work was MemAgentMemcgSet and MemAgentCompactSet, which previously had no is_allowed call and no rules.rego entry. The script is self-contained and can be run standalone from any directory in the repository tree. Generated-By: IBM Bob Assisted-by: Google AI Mode Signed-off-by: stevenhorsman --- ci/check_agent_policy_coverage.sh | 123 ++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100755 ci/check_agent_policy_coverage.sh diff --git a/ci/check_agent_policy_coverage.sh b/ci/check_agent_policy_coverage.sh new file mode 100755 index 0000000000..8a5518e312 --- /dev/null +++ b/ci/check_agent_policy_coverage.sh @@ -0,0 +1,123 @@ +#!/usr/bin/env bash +# +# Copyright (c) 2026 IBM Corporation +# +# SPDX-License-Identifier: Apache-2.0 +# +# check_agent_policy_coverage.sh - Static check that every RPC method in +# AgentService has a policy gate in rpc.rs and a default rule in rules.rego. +# +# Usage: ./ci/check_agent_policy_coverage.sh + +set -o errexit +set -o nounset +set -o pipefail + +[[ -n "${DEBUG:-}" ]] && set -o xtrace + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +repo_root="$(cd "${script_dir}/.." && pwd)" + +PROTO="${repo_root}/src/libs/protocols/protos/agent.proto" +RPC_RS="${repo_root}/src/agent/src/rpc.rs" +RULES_REGO="${repo_root}/src/tools/genpolicy/rules.rego" + +# --------------------------------------------------------------------------- +# 1. Extract unique request types from AgentService in the proto file +# --------------------------------------------------------------------------- +request_types=() + +while IFS= read -r req_type; do + [[ -n "${req_type}" ]] && request_types+=("${req_type}") +done < <( + awk ' + /^service AgentService/,/^}/ { + if ($0 ~ /^[[:space:]]*rpc[[:space:]]+/) { + match($0, /\([A-Za-z0-9_]+\)/) + if (RSTART > 0) { + print substr($0, RSTART + 1, RLENGTH - 2) + } + } + } + ' "${PROTO}" | sort -u +) + +if [[ ${#request_types[@]} -eq 0 ]]; then + echo "ERROR: failed to extract any request types from ${PROTO}" >&2 + exit 1 +fi + +echo "Checking ${#request_types[@]} unique request types from AgentService..." + +# --------------------------------------------------------------------------- +# 2. Pre-process rpc.rs into an in-memory string cache +# This isolates the impl block and flattens newlines so multi-line +# method headers match perfectly on both Mac and Linux. +# --------------------------------------------------------------------------- +impl_start=$(grep -m 1 -n 'impl agent_ttrpc::AgentService for AgentService' "${RPC_RS}" | cut -d: -f1 || true) +if [[ -z "${impl_start}" ]]; then + echo "ERROR: could not find impl block in ${RPC_RS}" >&2 + exit 1 +fi + +# Read from impl_start onwards, flatten all whitespace into spaces for easy matching +rpc_cache=$(tail -n +"${impl_start}" "${RPC_RS}" | tr '\n' ' ' | tr -s ' ') + +# --------------------------------------------------------------------------- +# 3. Check each request type for policy gates and rules +# --------------------------------------------------------------------------- +missing_rpc=() +missing_rego=() + +for req_type in "${request_types[@]}"; do + # --- Check rpc.rs --- + # Match the handler function that takes this request type as its parameter, + # extracting from "async fn" up to the closing "}" of that method body. + method_block=$(grep -oE "async fn [A-Za-z0-9_]+[^}]+(req|config)[[:space:]]*:[[:space:]]*(protocols::agent::)?${req_type}[^}]+}" <<< "${rpc_cache}" | head -1 || true) + + if [[ -z "${method_block}" ]]; then + missing_rpc+=("${req_type} [no handler found in rpc.rs impl block]") + continue + fi + + # Ensure the method body actually invokes the required security assertions + if ! grep -qE 'is_allowed|do_set_policy' <<< "${method_block}"; then + missing_rpc+=("${req_type} [handler found, but has no is_allowed/do_set_policy call]") + fi + + # --- Check rules.rego --- + if ! grep -q "default ${req_type} " "${RULES_REGO}"; then + missing_rego+=("${req_type}") + fi +done + +# --------------------------------------------------------------------------- +# 4. Report results +# --------------------------------------------------------------------------- +failed=0 + +if (( ${#missing_rpc[@]} > 0 )); then + echo -e "\nFAIL: The following request types are missing a policy gate in ${RPC_RS}:" + for entry in "${missing_rpc[@]}"; do + echo " - ${entry}" + done + failed=1 +fi + +if (( ${#missing_rego[@]} > 0 )); then + echo -e "\nFAIL: The following request types are missing a 'default ' rule in ${RULES_REGO}:" + for entry in "${missing_rego[@]}"; do + echo " - ${entry}" + done + echo -e "\n Add entries like:" + for entry in "${missing_rego[@]}"; do + echo " default ${entry} := false" + done + failed=1 +fi + +if (( failed == 0 )); then + echo "OK: all ${#request_types[@]} AgentService request types have policy gates and rules.rego entries." +fi + +exit "${failed}"