1-SEC contributors · Reviewed · Module: token_vault
Agent Token Exchange: Audit Counters, Failure Signals and Scope Changes
The optional Token Vault component records exchange requests and aggregate connection counters.
What 1-SEC checks
The optional Token Vault component records exchange requests and aggregate connection counters. Its direct Auth0 exchange client updates success/failure counters for its own operations. Reported response events with success equal to the string false or a nonempty error are logged; they do not update those counters or independently emit a failure alert. Scope monitoring establishes a first-observed set per agent and connection, then alerts when later requests introduce unseen scope strings. Newly observed scopes become part of that local set.
Required telemetry and context
Relevant input events: token_exchange, token_exchange_request, token_exchange_response. Use a trusted producer at the real application boundary.
connectionagent_idsubject_token_typescopes (space-separated string)success (string on response events)error
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
Enable Token Vault deliberately and configure the actual identity-provider integration before active exchange. Send trusted agent/connection context and preserve provider decisions in your own audit system. Treat scope-change alerts as investigation signals and apply least privilege at the identity provider and resource server. Distinguish direct-client failures from reported failure telemetry when interpreting counters. Never place subject tokens, refresh tokens or client secrets in example events, URLs or retained diagnostics.
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 '^(TestExchangeTracker|TestScopeEscalation|TestTokenExchangeConstants|TestEventTypes)$' -count=1The fixtures test aggregate request/success/failure counters, scope-baseline behavior and unchanged/new-scope controls, constants and subscribed event types. They do not call Auth0, exercise provider errors or validate an audience, rate-limiting integration or durable audit store.
TestExchangeTracker
Full fixture and assertions — run in its original package with its helpers.
func TestExchangeTracker(t *testing.T) {
et := NewExchangeTracker()
id1 := et.RecordRequest("google-oauth2", SubjectTokenTypeRefreshToken)
et.RecordSuccess(id1, "openid profile email")
id2 := et.RecordRequest("github", SubjectTokenTypeAccessToken)
et.RecordFailure(id2, "invalid_grant")
stats := et.Stats()
if stats.TotalExchanges != 2 {
t.Errorf("expected 2 total exchanges, got %d", stats.TotalExchanges)
}
if stats.SuccessCount != 1 {
t.Errorf("expected 1 success, got %d", stats.SuccessCount)
}
if stats.FailureCount != 1 {
t.Errorf("expected 1 failure, got %d", stats.FailureCount)
}
if stats.ByConnection["google-oauth2"] != 1 {
t.Errorf("expected 1 google-oauth2 exchange, got %d", stats.ByConnection["google-oauth2"])
}
}TestScopeEscalation
Full fixture and assertions — run in its original package with its helpers.
func TestScopeEscalation(t *testing.T) {
et := NewExchangeTracker()
// First request — baseline
esc := et.DetectScopeEscalation("agent-1", "google-oauth2", "openid profile")
if esc != "" {
t.Errorf("expected no escalation for first request, got %q", esc)
}
// Same scopes — no escalation
esc = et.DetectScopeEscalation("agent-1", "google-oauth2", "openid profile")
if esc != "" {
t.Errorf("expected no escalation for same scopes, got %q", esc)
}
// New scope — escalation
esc = et.DetectScopeEscalation("agent-1", "google-oauth2", "openid profile email calendar")
if esc == "" {
t.Error("expected escalation for new scopes")
}
if esc != "email, calendar" {
t.Errorf("expected escalation %q, got %q", "email, calendar", esc)
}
}TestTokenExchangeConstants
Full fixture and assertions — run in its original package with its helpers.
func TestTokenExchangeConstants(t *testing.T) {
// Verify constants match Auth0 official SDK values
if GrantTypeFederatedConnectionAccessToken != "urn:auth0:params:oauth:grant-type:token-exchange:federated-connection-access-token" {
t.Error("grant type does not match Auth0 SDK")
}
if SubjectTokenTypeRefreshToken != "urn:ietf:params:oauth:token-type:refresh_token" {
t.Error("refresh token type does not match RFC 8693")
}
if SubjectTokenTypeAccessToken != "urn:ietf:params:oauth:token-type:access_token" {
t.Error("access token type does not match RFC 8693")
}
if RequestedTokenTypeFederatedConnection != "http://auth0.com/oauth/token-type/federated-connection-access-token" {
t.Error("requested token type does not match Auth0 SDK")
}
}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
Observed scope history is not an approved grant policy or proof that the provider granted those scopes. The first request establishes a baseline without escalation alerting. The recent-exchange helper has agent timestamps in a separate map, but the normal request-recording path does not populate that map, so do not rely on the advertised per-agent rate threshold. Aggregate counters and a bounded in-memory request list are not an immutable per-exchange audit ledger. There is no demonstrated audience validation or sender-constraining in these event handlers. Helper fixtures do not prove a live Auth0 exchange works.
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.
Protocol and format references
These references describe the underlying format or protocol. The implementation and fixtures above define the subset 1-SEC actually checks.
Related implemented controls
- Agent Output Secrets: API-Key Patterns, JWT Shapes and Leak Limits
- Agent Delegation Records: Verified Claims, Expiry and Scope Limits
- Connected Accounts: Link, Delegated Usage and Revocation Signals
- Agent Token Reuse: Revocation Signals and Observed Token Activity
- Agent OAuth Consent: Scope Accumulation and Grant-Rate Signals
- Service Account Misuse: Activity Volume and Baseline Limits