1-SEC contributors · Reviewed · Module: ai_containment

Connected-Account Unlinking: Local Tracker and Provider Revocation

The optional Token Vault component processes connected-account events.

What 1-SEC checks

The optional Token Vault component processes connected-account events. An unlink event calls the account tracker with user and connection identifiers; account tracking is part of the local monitoring and authorization context used by this integration.

Read the implementation: handleAccountUnlink

Required telemetry and context

Relevant input events: connected_account_link, connected_account_unlink, connected_account_usage. Use a trusted producer at the real application boundary.

  • user_id
  • connection
  • agent_id where applicable

Field names above describe relevant context, not a complete API request. Consult the current event contract for envelope and detail requirements. Preserve booleans, numbers, and strings as the receiving handler expects them. Agent-provided text cannot independently verify identity or authorization.

Deployment, prevention, and response

Perform provider-side revocation through the identity/provider service and invalidate application credential caches. Then verify both the local state and an attempted use through the real authorization path. Keep connection/user identifiers bound to the authenticated principal rather than model text.

Record the source event, affected agent/host, actual target, policy decision, and response result. Review enforcement configuration separately from detection. A recommendation, selected action, dry-run record, and completed action are different outcomes.

Reproduce the source fixtures

From a checkout of the linked 1-SEC source revision, run the selected fixtures with the repository's Go toolchain. These commands run tests; they do not execute a real attack against your systems.

go test ./internal/modules/tokenvault -run '^(TestConnectedAccountTracker|TestEventTypes)$' -count=1

The referenced source fixtures ran in the selected local package suites. Helper tests and synthetic events establish only the assertions shown, not production prevention or framework compatibility.

TestConnectedAccountTracker

Full fixture and assertions — run in its original package with its helpers.

func TestConnectedAccountTracker(t *testing.T) {
	cat := NewConnectedAccountTracker()

	cat.RecordLink("user-1", "google-oauth2", "openid profile email")
	cat.RecordLink("user-1", "github", "repo read:user")

	if cat.AccountCount() != 2 {
		t.Errorf("expected 2 accounts, got %d", cat.AccountCount())
	}

	// Agent not authorized yet
	if cat.IsAgentAuthorized("user-1", "google-oauth2", "agent-1") {
		t.Error("agent should not be authorized before delegation")
	}

	// Authorize agent
	cat.AuthorizeAgent("user-1", "google-oauth2", "agent-1", "openid profile", time.Now().Add(time.Hour))
	if !cat.IsAgentAuthorized("user-1", "google-oauth2", "agent-1") {
		t.Error("agent should be authorized after delegation")
	}

	// Different connection — not authorized
	if cat.IsAgentAuthorized("user-1", "github", "agent-1") {
		t.Error("agent should not be authorized for unlinked connection")
	}

	if cat.AgentCount() != 1 {
		t.Errorf("expected 1 authorized agent, got %d", cat.AgentCount())
	}

	// Unlink revokes agent authorization
	cat.RecordUnlink("user-1", "google-oauth2")
	if cat.IsAgentAuthorized("user-1", "google-oauth2", "agent-1") {
		t.Error("agent authorization should be revoked after unlink")
	}
	if cat.AccountCount() != 1 {
		t.Errorf("expected 1 account after unlink, got %d", cat.AccountCount())
	}
}
TestEventTypes

Full fixture and assertions — run in its original package with its helpers.

func TestEventTypes(t *testing.T) {
	types := EventTypes()
	if len(types) == 0 {
		t.Fatal("expected non-empty event types")
	}

	required := []string{
		"token_exchange", "connected_account_link", "connected_account_unlink",
		"oauth_grant",
	}
	typeSet := make(map[string]bool, len(types))
	for _, et := range types {
		typeSet[et] = true
	}
	for _, r := range required {
		if !typeSet[r] {
			t.Errorf("missing required event type: %s", r)
		}
	}
}

Tuning, evaluation, and coverage limits

Updating local state is not proof that every already-issued credential was revoked at the external provider. The component is opt-in and needs the correct event producer. A missed unlink event or a separate credential cache can leave the local view incomplete.

Evaluate this behavior with representative authorized activity and a controlled known-positive case. Investigate the exact rule, inputs, and state before suppressing an alert. Do not assume a hard-coded heuristic is a configurable setting. Measure your own deployment; the source fixtures do not establish a benchmark, detection rate, compliance certification, or incident-prevention claim.

Related implemented controls

Implementation reviewed at source revision 2af8ea9dab2057a1ffc3cce9bf44220271fab18e. Recheck the actual release and integration before deployment. Sources and corrections policy.