# Activity status can be disabled; excluded from API responses when off

- **Pattern:** `ab-000731` (`community-privacy-controls.account-control.activity-status-toggle`)
- **Severity:** low
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000731
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000731)

## Why it matters

Activity status — 'last seen 3 minutes ago', 'currently online', 'typing' — reveals real-time behavioral patterns that users may want to keep private for safety or personal reasons. Domestic abuse victims, people avoiding specific contacts, and users who want to read messages without triggering social pressure are all impacted by forced activity disclosure. GDPR Art. 25 (privacy by design) requires that data minimization be the default: broadcast activity only when users have affirmatively chosen to share it. CWE-200 captures the information exposure when status fields are included in API responses regardless of user preference.

## Severity rationale

Low because the exposed data is behavioral metadata rather than account credentials or PII, but the real-time nature makes it a safety concern for vulnerable users.

## Remediation

Check the `activityStatusVisible` setting before including any `lastSeen`, `isOnline`, or typing indicators in API responses or WebSocket broadcasts. Apply the check server-side — omitting the field from the response is more reliable than frontend suppression. In your user profile serializer:

```ts
// src/lib/users/serialize.ts
export async function serializeUser(user: User, viewerId: string): Promise<PublicUser> {
  const settings = await db.userSettings.findUnique({ where: { userId: user.id } });

  return {
    id: user.id,
    name: user.name,
    // conditionally include activity
    ...(settings?.activityStatusVisible && {
      lastSeen: user.lastSeen,
      isOnline: isUserOnline(user.id)
    })
  };
}
```

Also gate the WebSocket presence broadcast: skip emitting `user:online` and `user:typing` events for users with activity status disabled.

## Detection

- **ID:** `activity-status-toggle`
- **Severity:** `low`
- **What to look for:** Enumerate every relevant item. Check whether the platform shows "last seen", "currently active", "typing", or similar activity status. Look for a settings toggle to hide this status. Verify that when disabled, the status is not revealed in API responses or via WebSocket events.
- **Pass criteria:** At least 1 of the following conditions is met. Settings include a toggle to disable activity status sharing. When disabled, `last_seen`, `isOnline`, or similar fields are omitted from API responses. Activity is not broadcast via real-time channels.
- **Fail criteria:** Activity status always visible to other users. No setting to disable. Status is sent via API even if user has disabled it.
- **Skip (N/A) when:** The platform does not display activity status.
- **Detail on fail:** Example: `"Activity status always visible in API responses regardless of user setting."`
- **Remediation:** Implement activity status privacy:

  ```ts
  // In user profile response
  async function getUserProfile(userId: string, viewerId: string) {
    const user = await db.user.findUnique({ where: { id: userId } });
    const settings = await db.userSettings.findUnique({ where: { userId } });

    const profile = {
      id: user.id,
      name: user.name,
      // ... other fields
    };

    // Only include activity if user allows it
    if (settings?.activityStatusVisible) {
      profile.lastSeen = user.lastSeen;
      profile.isOnline = isUserOnline(userId);
    }

    return profile;
  }
  ```

## External references

- gdpr Art. 25
- cwe CWE-200
- cwe CWE-285

Taxons: access-control, privacy-consent

HTML version: https://auditbuffet.com/patterns/ab-000731
