[management] Let usage_viewer read Agent Network access logs (#7750) (#7885)

usage_viewer saw account-wide usage but only its own request logs, so
the people reviewing cost could not drill into the requests behind it.
The role now also holds Read on agent_network.logs, which makes the
access-log and session endpoints return every caller's rows instead of
self-scoping. Logs can contain captured prompts, so this widens what the
role exposes; policies, guardrails, budgets and settings stay hidden.


(cherry picked from commit 82e5428c2f)

Co-authored-by: Misha Bragin <bangvalo@gmail.com>
This commit is contained in:
Nicolas Frati
2026-10-01 00:51:47 +02:00
committed by GitHub
co-authored by Misha Bragin
parent 7a53a7b319
commit 3e2ed39de9
3 changed files with 28 additions and 23 deletions
+6 -6
View File
@@ -110,12 +110,12 @@ Two roles delegate Agent Network access without account-admin rights:
read-only users, groups, peers, and account info (needed to build policies). read-only users, groups, peers, and account info (needed to build policies).
Nothing else in the account. Nothing else in the account.
- **`usage_viewer`** — the regular User baseline plus read on - **`usage_viewer`** — the regular User baseline plus read on
`agent_network.usage` (the aggregated usage and cost overview) and read-only `agent_network.usage` (the aggregated usage and cost overview) and
access to the resources the usage filters resolve against: users, groups, `agent_network.logs` (the account-wide request-level access logs, which can
peers, and the provider list (connection config redacted — no upstream URLs contain captured prompts), and read-only access to the resources those
or operator-supplied header values). No policies, and no account-wide filters resolve against: users, groups, peers, and the provider list
request-level access logs; like any caller, it still reads its own requests (connection config redacted — no upstream URLs or operator-supplied header
through the self-scoped endpoints below. values). No policies, guardrails, budgets, or settings.
Every authenticated user, regardless of role, can read the caller-scoped Every authenticated user, regardless of role, can read the caller-scoped
self-service endpoint `GET /api/agent-network/agent-config` (the endpoint, providers, self-service endpoint `GET /api/agent-network/agent-config` (the endpoint, providers,
@@ -62,11 +62,11 @@ func TestAgentNetworkAdminRole(t *testing.T) {
} }
} }
// TestUsageViewerRole pins the least-privilege cost role: read on the // TestUsageViewerRole pins the read-only usage role: read on the aggregated
// aggregated usage overview plus read-only on the resources its filters // usage overview and the account-wide request-level logs, plus read-only on
// and display columns resolve against (users, groups, peers, the provider // the resources their filters and display columns resolve against (users,
// list) — no policies, no request-level logs (which can contain captured // groups, peers, the provider list) — no policies, guardrails, budgets, or
// prompts), nothing else in the account. // settings, nothing else in the account.
func TestUsageViewerRole(t *testing.T) { func TestUsageViewerRole(t *testing.T) {
manager := NewManager(nil) manager := NewManager(nil)
ctx := context.Background() ctx := context.Background()
@@ -76,6 +76,7 @@ func TestUsageViewerRole(t *testing.T) {
readOnly := []modules.Module{ readOnly := []modules.Module{
modules.AgentNetworkUsage, modules.AgentNetworkUsage,
modules.AgentNetworkLogs,
modules.AgentNetworkProviders, modules.AgentNetworkProviders,
modules.Users, modules.Users,
modules.Groups, modules.Groups,
@@ -83,7 +84,7 @@ func TestUsageViewerRole(t *testing.T) {
} }
for _, m := range readOnly { for _, m := range readOnly {
assert.True(t, manager.ValidateRoleModuleAccess(ctx, "account", role, m, operations.Read), assert.True(t, manager.ValidateRoleModuleAccess(ctx, "account", role, m, operations.Read),
"usage_viewer must read %s for the usage view and its filters", m) "usage_viewer must read %s for the usage and log views and their filters", m)
for _, op := range []operations.Operation{operations.Create, operations.Update, operations.Delete} { for _, op := range []operations.Operation{operations.Create, operations.Update, operations.Delete} {
assert.False(t, manager.ValidateRoleModuleAccess(ctx, "account", role, m, op), assert.False(t, manager.ValidateRoleModuleAccess(ctx, "account", role, m, op),
"usage_viewer must not have %s on %s", op, m) "usage_viewer must not have %s on %s", op, m)
@@ -95,7 +96,6 @@ func TestUsageViewerRole(t *testing.T) {
modules.AgentNetworkPolicies, modules.AgentNetworkPolicies,
modules.AgentNetworkGuardrails, modules.AgentNetworkGuardrails,
modules.AgentNetworkBudgets, modules.AgentNetworkBudgets,
modules.AgentNetworkLogs,
modules.AgentNetworkSettings, modules.AgentNetworkSettings,
modules.Networks, modules.Networks,
modules.SetupKeys, modules.SetupKeys,
@@ -7,16 +7,15 @@ import (
) )
// UsageViewer is the regular User baseline plus read access to the // UsageViewer is the regular User baseline plus read access to the
// aggregated Agent Network usage and cost overview, and read-only access // aggregated Agent Network usage and cost overview and to the account-wide
// to the resources the usage filters and display columns resolve against: // request-level access logs (which can contain captured prompts), and
// users and groups (identity filters and name resolution), peers (agent // read-only access to the resources the usage and log filters and display
// principals in the caller column), and the provider list (provider and // columns resolve against: users and groups (identity filters and name
// model filter options — the manager redacts connection config such as // resolution), peers (agent principals in the caller column), and the
// upstream URLs and operator-supplied header values for callers holding // provider list (provider and model filter options — the manager redacts
// read without update). It sees no policies and no account-wide // connection config such as upstream URLs and operator-supplied header
// request-level access logs (which can contain captured prompts); its own // values for callers holding read without update). It sees no policies,
// requests remain readable through the self-scoped endpoints, like any // guardrails, budgets, or Agent Network settings.
// caller's.
var UsageViewer = RolePermissions{ var UsageViewer = RolePermissions{
Role: types.UserRoleUsageViewer, Role: types.UserRoleUsageViewer,
AutoAllowNew: map[operations.Operation]bool{ AutoAllowNew: map[operations.Operation]bool{
@@ -32,6 +31,12 @@ var UsageViewer = RolePermissions{
operations.Update: false, operations.Update: false,
operations.Delete: false, operations.Delete: false,
}, },
modules.AgentNetworkLogs: {
operations.Read: true,
operations.Create: false,
operations.Update: false,
operations.Delete: false,
},
modules.AgentNetworkProviders: { modules.AgentNetworkProviders: {
operations.Read: true, operations.Read: true,
operations.Create: false, operations.Create: false,