# Credential and integration hygiene

> Credential age and rotation, stale or failing connections, orphaned servers, and integrations nobody uses any more.

Source: https://triagic.com/checkups/credential-hygiene

## Scope [#scope]

A sweep of the org's integration estate: what is connected, whether it still works,
whether anyone still uses it, and what each connection would cost the org if its
credential leaked. Runs on the connection inventory available to this run plus each
provider's read-only identity calls. The subject is each credential the org holds
for an external system — never this application's own configuration.

**Never read, print, echo, or reconstruct a credential value.** Secrets are stored
encrypted and no read-only tool exposes them; if any tool result contains something
that looks like a key, token, password or connection string, that is itself a
critical finding — report its location and shape ("a 40-character token in the
description field of the X server"), never the value.

## Procedure [#procedure]

1. **Inventory the connections.** Using the connected-server data available to
   you, list every MCP server configured for this org: provider, display name, who
   created it, when it was created and last updated, whether it is shared at org
   level or personal to one user, and its current running state and last error.
   Personal servers matter here: a server owned by one user's account is a
   single-point dependency and a deprovisioning hazard when that person leaves.
2. **Health.** Group the inventory by state. Anything not running is either broken
   or abandoned, and the two need different actions: read the last error text. An
   authentication error means an expired, rotated or revoked credential; a
   network or spawn error usually means a host or runtime change. A server that has
   been failing for weeks with nobody noticing is a monitoring finding as much as a
   credential one — say so.
3. **Age and rotation.** Use the created/updated timestamps as the proxy for
   credential age: a connection whose credentials have not been updated in over a
   year is overdue on any rotation policy, and one over two years is a finding on
   its own. Where the provider exposes token metadata read-only, use the better
   source instead: GitHub and GitLab expose token scopes and expiry, Snowflake
   exposes `LAST_SUCCESS_LOGIN` and key-pair versus password authentication, cloud
   providers expose access-key age. Prefer provider truth over the local
   timestamp and say which one each finding used.
4. **Use.** Cross-reference the inventory against what actually runs: which
   connections real investigations and reports have drawn evidence from, using the
   ticket and investigation history available to you. A connected integration no
   work has touched is an *orphan* — it holds a live credential, widens the blast
   radius, and buys nothing. That is this checkup's highest-yield finding and the
   easiest remediation in the whole library.
5. **Blast radius.** For each connection, state what the credential can reach if it
   leaks: the account or project, and how broad the granted role is. A read-only
   role scoped to one schema and an account-admin role are the same "connected
   integration" on the card and wildly different exposures. Where a provider's
   read-only identity call exists (whoami, current-user, current-role), use it to
   confirm what the credential actually is rather than what someone intended it to
   be — the gap between those two is a common and unglamorous finding.
6. **Duplicates and drift.** Look for several connections to the same underlying
   system, particularly under different owners. Duplicates multiply the credentials
   in circulation, and usually one of them is the stale one nobody rotates. Note
   naming that hides which environment a connection points at (a server called
   "postgres" that is production).
7. **Deprovisioning check.** Cross-reference personal server owners against the
   current member roster: a server owned by a user who is no longer active is both
   an orphaned credential and a broken dependency waiting to surface.

## Finding keys [#finding-keys]

The `key` names the *connection and the problem*, not this run, so a stale
credential still stale next week updates one ledger row instead of opening a
second, and a rotated credential that goes stale again is caught as a regression.
Use `server:<server-key-or-name>:<issue-slug>`, using the connection's stable key
or name rather than a numeric id that could change. Never put dates or age values
in the key.

* `server:snowflake-prod:credential-stale`
* `server:github-main:auth-failing`
* `server:zendesk-legacy:orphaned`
* `server:postgres-analytics:overbroad-role`
* `server:mongo-personal:owner-inactive`
* `secret-exposure:server-description:token-in-plaintext`

## Severity rubric [#severity-rubric]

* **critical** — a credential value visible anywhere it should not be (a
  description, a playbook body, a ticket, a tool result); a live connection owned
  by a person who has left the org; a connection whose identity call proves it
  holds account-admin rights it was never meant to have.
* **high** — a credential over two years old on a system holding customer or
  production data; a connection failing authentication for weeks with nobody
  noticing; an orphaned connection to a production system.
* **medium** — a credential over a year old; an orphaned connection to a non-
  production system; duplicate connections to the same system under different
  owners; a personal-scope server that the whole team depends on.
* **low** — naming and tidiness issues, an unused connection to a sandbox, a
  connection that has been failing since it was created and clearly never worked.
* **info** — the estate summary itself: how many connections, by provider and state,
  recorded so next week's run can see what changed.

## Output guidance [#output-guidance]

Open with a one-paragraph executive summary: how many integrations
are connected, how many are healthy, how many are orphaned, and the single biggest
exposure. Then sections for Health, Credential age and rotation, Orphaned and
unused, and Blast radius, each naming where its evidence came from (the connection
inventory versus a provider identity call). Close with a
recommendations table (Connection | Issue | Recommended action | Urgency), rotations
and disconnections first. Never print a credential value anywhere in the report —
identify a credential by its connection, provider and owner only. If a secret was
found somewhere it should not be, describe the location and shape and recommend
rotation, not the value.

<!-- generated by apps/server/scripts/export-checkups.ts, do not edit -->
