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

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.

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