{
  "schema": "aaes/capability-pack/1",
  "provider": "grafana",
  "version": "1.0.0",
  "description": "Read Grafana dashboard and folder listings, data sources, organization users, and provisioned alert rules. User listing requires granular role-based access control; this pack registers no writes.",
  "notes": "The endpoint host 'grafana-instance.invalid' is a reserved non-production placeholder (an RFC 2606 reserved TLD): substitute the customer's own Grafana host — an on-prem instance host or a Grafana Cloud stack host (<stack>.grafana.net) — in the endpoints, the resource locators and the egress allowlist before enabling; the allowlist matches exactly, so all three must be substituted together, substitution is a required deployment step, and allowlisting the reserved host does not make an unsubstituted pack safe (the reserved name is a deliberate fail-closed default: it resolves nowhere by design, so an unsubstituted pack cannot call anything until the installer binds the real host).\n\nEvery read endpoint here is a collection read only: single-record reads (GET /api/dashboards/uid/:uid, GET /api/datasources/uid/:uid, GET /api/v1/provisioning/alert-rules/:uid) are not registered. Dashboard search (GET /api/search) returns the dashboards and folders the token's service account may see, so the result set itself is authorization-scoped by Grafana. This pack is read-only.\n\nDocumented exclusions — this pack does not register: dashboard creation, update or deletion (POST /api/dashboards/db, DELETE /api/dashboards/uid/:uid and the dashboard.grafana.app API group), data source creation, update, deletion or queries (POST /api/ds/query — caller-controlled query text against every configured data source cannot be pinned by a pack, the same rule as the postgres pack's query capabilities, and this pack supplies no reviewed adapter to constrain it), user or team administration, alert rule provisioning writes (POST/PUT/DELETE on /api/v1/provisioning/alert-rules — writing alert rules can create or silence the alerts that wake people, an effect-level risk this pack does not register), silences (the Alertmanager silences API — creating a silence suppresses alert notifications and can hide incidents from responders, an effect this pack does not register), service account administration, and the legacy /api/alerts endpoint.",
  "credential": {
    "secret_ref_id": "secret-ref-grafana-service-account-token",
    "custody": "customer_vault",
    "inject": "header",
    "inject_name": "Authorization",
    "note": "A Grafana service account token for the customer's own instance, held in the client's own secret manager. The stored material must be the complete Authorization header value: the scheme 'Bearer', one separating space, then the token — 'Bearer <token>'. Use a dedicated service account with the least-privilege role this read-only pack needs — never a personal user's credentials, and never the legacy admin API key form.\n\nViewer alone is not sufficient for the complete pack: Grafana documents the org-users endpoint as requiring the org admin role under basic roles, and the alert-provisioning reads as gated by their own RBAC permissions, so the service account needs a fixed role (Grafana RBAC, available in Grafana Enterprise and Grafana Cloud) granting exactly the read permissions for these four operations — dashboards:read, datasources:read, org.users:read and alert.provisioning:read — and nothing broader.\n\nWhere RBAC is unavailable, a read-only deployment cannot reach the org-users endpoint at all and must remove grafana.users.read from its copy rather than grant org admin. Grafana's token value is shown once at creation, so capture it into the vault at creation time.\n\nTokens default to never expiring (the creation API's secondsToLive defaults to 0): set an explicit expiry and rotate — renewal ownership sits with an external deployment process, and the executor injects the stored value verbatim and performs no token lifecycle of its own.\n\nRevocation is deleting the token (DELETE /api/serviceaccounts/:id/tokens/:tokenId), disabling or deleting the service account; do not assume that restricting new token issuance invalidates tokens already issued — delete the issued token itself.\n\nExecutor hygiene: the value must live only in executor memory for the lifetime of a call, must never be written to logs, traces or diagnostics, and must not appear in crash dumps; a deployment that cannot meet that bar must not enable the pack."
  },
  "capabilities": [
    {
      "capability_id": "grafana.dashboards.read",
      "version": "1.0.0",
      "kind": "tool",
      "risk_tier": "R0",
      "owner": "REPLACE-WITH-ACCOUNTABLE-HUMAN",
      "data_class": "internal",
      "egress_allowlist": [
        "grafana-instance.invalid"
      ],
      "endpoint": "https://grafana-instance.invalid/api/search",
      "method": "GET",
      "connector": "http",
      "custody": "customer_vault",
      "secret_ref_id": "secret-ref-grafana-service-account-token",
      "inject": "header",
      "inject_name": "Authorization",
      "actions": [
        "read"
      ],
      "resource_id": "grafana-instance",
      "description": "Search dashboards and folders: titles, uids, tags and folder structure visible to the service account, which reveal internal operational topology. The registered path covers the collection read only — single-dashboard reads (GET /api/dashboards/uid/:uid) and dashboard writes are not registered by this pack.",
      "requires_review": true
    },
    {
      "capability_id": "grafana.datasources.read",
      "version": "1.0.0",
      "kind": "tool",
      "risk_tier": "R0",
      "owner": "REPLACE-WITH-ACCOUNTABLE-HUMAN",
      "data_class": "internal",
      "egress_allowlist": [
        "grafana-instance.invalid"
      ],
      "endpoint": "https://grafana-instance.invalid/api/datasources",
      "method": "GET",
      "connector": "http",
      "custody": "customer_vault",
      "secret_ref_id": "secret-ref-grafana-service-account-token",
      "inject": "header",
      "inject_name": "Authorization",
      "actions": [
        "read"
      ],
      "resource_id": "grafana-instance",
      "description": "List configured data sources: names, types and connection URLs, which reveal internal infrastructure. The registered path covers the collection read only — single data-source reads, data source queries (POST /api/ds/query) and data source writes are not registered by this pack.",
      "requires_review": true
    },
    {
      "capability_id": "grafana.users.read",
      "version": "1.0.0",
      "kind": "tool",
      "risk_tier": "R0",
      "owner": "REPLACE-WITH-ACCOUNTABLE-HUMAN",
      "data_class": "pii",
      "egress_allowlist": [
        "grafana-instance.invalid"
      ],
      "endpoint": "https://grafana-instance.invalid/api/org/users",
      "method": "GET",
      "connector": "http",
      "custody": "customer_vault",
      "secret_ref_id": "secret-ref-grafana-service-account-token",
      "inject": "header",
      "inject_name": "Authorization",
      "actions": [
        "read"
      ],
      "resource_id": "grafana-org-users",
      "description": "List users in the current organization: logins, emails, org roles and last-seen timestamps, which are personal data. Grafana documents this endpoint as accessible to users with the org admin role — so under basic roles a read-only service account cannot reach it. The granular org.users:read permission depends on Grafana RBAC, which is an Enterprise and Grafana Cloud feature; on editions without it, remove this capability from the deployment's copy rather than grant org admin or imply Viewer suffices.\n\nWhere RBAC is available and the deployment deliberately grants org.users:read, a 403 here without that grant is the control working, not a fault. Collection read; there is no record variant registered.",
      "requires_review": true
    },
    {
      "capability_id": "grafana.alerts.read",
      "version": "1.0.0",
      "kind": "tool",
      "risk_tier": "R0",
      "owner": "REPLACE-WITH-ACCOUNTABLE-HUMAN",
      "data_class": "internal",
      "egress_allowlist": [
        "grafana-instance.invalid"
      ],
      "endpoint": "https://grafana-instance.invalid/api/v1/provisioning/alert-rules",
      "method": "GET",
      "connector": "http",
      "custody": "customer_vault",
      "secret_ref_id": "secret-ref-grafana-service-account-token",
      "inject": "header",
      "inject_name": "Authorization",
      "actions": [
        "read"
      ],
      "resource_id": "grafana-instance",
      "description": "List all provisioned alert rules: names, conditions, queries and notification routing, which reveal what the deployment watches and who gets paged. The registered path covers the collection read only — single-rule reads (GET /api/v1/provisioning/alert-rules/:uid), export and every provisioning write are not registered by this pack.",
      "requires_review": true
    }
  ],
  "resources": [
    {
      "resource_id": "grafana-instance",
      "kind": "api",
      "locator": "https://grafana-instance.invalid/api",
      "data_class": "internal",
      "owner": "REPLACE-WITH-ACCOUNTABLE-HUMAN"
    },
    {
      "resource_id": "grafana-org-users",
      "kind": "api",
      "locator": "https://grafana-instance.invalid/api/org/users",
      "data_class": "pii",
      "owner": "REPLACE-WITH-ACCOUNTABLE-HUMAN"
    }
  ],
  "review": {
    "state": "library",
    "date": "2026-09-22",
    "sources": [
      "https://grafana.com/docs/grafana/latest/developers/http_api/folder_dashboard_search/ (retrieved 2026-09-21)",
      "https://grafana.com/docs/grafana/latest/developers/http_api/dashboard/ (retrieved 2026-09-21)",
      "https://grafana.com/docs/grafana/latest/developers/http_api/data_source/ (retrieved 2026-09-21)",
      "https://grafana.com/docs/grafana/latest/developers/http_api/org/ (retrieved 2026-09-21)",
      "https://grafana.com/docs/grafana/latest/developers/http_api/alerting_provisioning/ (retrieved 2026-09-21)",
      "https://grafana.com/docs/grafana/latest/developers/http_api/serviceaccount/ (retrieved 2026-09-21)",
      "https://grafana.com/docs/grafana/latest/administration/roles-and-permissions/access-control/custom-role-actions-scopes/ (retrieved 2026-09-21)"
    ],
    "reviewer_id": "aaes-eng"
  }
}
