Back to Blog
critical SEVERITY3 min read

SchedulePush.disableReminder Missing Authorization Check in push.js

The `disableReminder` method in the `SchedulePush` class allowed any user to disable push notification reminders for arbitrary user IDs by manipulating the `e.user_id` event parameter. The fix adds a `checkFriend()` authorization gate that verifies the requesting user has a valid friendship relationship with the bot before modifying subscription state.

O
By Orbis AppSec
Published September 23, 2026Reviewed September 23, 2026

Answer Summary

The `SchedulePush.disableReminder()` method in push.js accepted a `user_id` from the event object `e.user_id` without verifying the requesting user's authorization to modify that specific subscription. An attacker could disable reminders for any user ID by spoofing or manipulating the event object in certain bot framework configurations. The fix adds a `checkFriend(userId)` check with `getBotName(e)` to enforce authorization before calling `DataManager.setReminderStatus()`. Fixed in the referenced commit with no CVE assigned. CWE-862: Missing Authorization.

Vulnerability at a Glance

cweCWE-862
fixAdded checkFriend() validation before setReminderStatus() call
riskUnauthorized modification of push notification subscriptions
languageJavaScript
root causeUser ID from event object trusted without authorization verification
vulnerabilityMissing Authorization

Affected Versions

Affected not applicable (first-party code)
Fixed in not applicable (first-party code) — see referenced commit
Ecosystem N/A
CVE / GHSA not assigned
CWE CWE-862: Missing Authorization

The Vulnerability Explained

The SchedulePush plugin's disableReminder() method trusted the user_id property from an event object without verifying that the requesting party was authorized to modify that specific user's push notification subscription state.

Here's the vulnerable code pattern:

async disableReminder(e) {
  const userId = e.user_id;
  await DataManager.setReminderStatus(userId, false);
  await e.reply("✅ 已关闭课表订阅");
  return true;
}

The critical flaw: e.user_id is accepted and passed directly to DataManager.setReminderStatus() with no validation. In bot framework configurations where event objects can be constructed or modified—whether through message spoofing, relay attacks, or certain plugin interaction patterns—an attacker could supply arbitrary user_id values.

Real-world impact: An attacker could systematically disable schedule reminders for other users, disrupting their notification workflows. In educational or scheduling bot deployments where timely reminders are critical, this could cause missed classes, appointments, or deadlines.

Attack scenario: A malicious user interacting with a bot instance crafts a message that reaches disableReminder with e.user_id set to a victim's identifier. The DataManager faithfully disables that victim's reminders, with no indication that the requester wasn't the legitimate user.

The Fix

The patch introduces authorization validation through the existing checkFriend() utility, coupled with getBotName() for proper error messaging:

Before:

async disableReminder(e) {
  const userId = e.user_id;
  await DataManager.setReminderStatus(userId, false);
  await e.reply("✅ 已关闭课表订阅");
  return true;
}

After:

async disableReminder(e) {
  const userId = e.user_id;
  const botName = getBotName(e);
  if (!checkFriend(userId)) {
    await e.reply(
      `❌ 操作失败!请先添加${botName}为好友,才能管理课表订阅哦~\n`
    );
    return false;
  }
  await DataManager.setReminderStatus(userId, false);
  await e.reply("✅ 已关闭课表订阅");
  return true;
}

The fix works by leveraging the bot framework's existing friendship system. checkFriend(userId) verifies that the user has established a bidirectional friendship relationship with the bot instance—something that cannot be spoofed through simple event property manipulation. The getBotName(e) call ensures the error message correctly identifies the specific bot instance in multi-bot deployments.

Key Takeaways

  • Event object properties are not authentication: e.user_id indicates which user the event claims to represent, not who sent the request or whether they're authorized to act on that user's behalf.

  • Re-use existing authorization infrastructure: The fix didn't build new auth logic—it applied the existing checkFriend() pattern that was already implemented elsewhere in the codebase, ensuring consistent security policy enforcement.

  • Friendship verification matters for bot security: In bot frameworks, friendship establishment represents a trust boundary that should gate sensitive operations; skipping it for "convenience" creates authorization gaps.

  • State-modifying operations need explicit guards: Any method that changes persistent state (setReminderStatus) should validate authorization at the entry point, not assume upstream validation occurred.

How Orbis AppSec Detected This

Source: The e.user_id property from the event object passed to disableReminder(e)

Sink: DataManager.setReminderStatus(userId, false) which modifies persistent subscription state

Missing control: No call to checkFriend() or equivalent authorization verification before the state-changing operation

CWE: CWE-862 — Missing Authorization

Fix: Added checkFriend(userId) validation with appropriate error handling using getBotName(e) for user feedback

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.

Conclusion

This vulnerability exemplifies a common pattern in bot framework development: assuming that reaching a message handler implies proper authorization. The disableReminder() method's trust in e.user_id allowed arbitrary subscription manipulation until the checkFriend() gate was added. For developers building on similar frameworks, the lesson is clear—verify relationships, not just message routing.

Prevention and further reading

Frequently Asked Questions

Does the checkFriend() call in the fix use the userId from e.user_id or a different identifier?

The fix passes `userId` (extracted from `e.user_id`) directly to `checkFriend(userId)`, validating that the user associated with that ID has established a friendship relationship with the bot before allowing subscription modifications.

What happens if checkFriend(userId) returns false in the fixed disableReminder() method?

The method returns `false` after replying with a localized error message that includes the bot name from `getBotName(e)`, instructing the user to add the bot as a friend before managing schedule subscriptions.

Is the vulnerability exploitable only through direct event spoofing, or could it be triggered through normal bot framework message handling?

While friend verification existed elsewhere, the `disableReminder()` method previously lacked this check entirely, meaning any message that reached this handler—including those in certain bot framework configurations where event properties could be influenced—could manipulate arbitrary user subscriptions.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #29

Related Articles

critical

How Broken Object-Level Authorization happens in Express.js and how to fix it

A critical authorization flaw in `src/v1/routes/index.js` allowed any authenticated API key holder to access arbitrary budgets by manipulating the `budgetSyncId` URL parameter. The fix introduces an environment-based allowlist that validates budget access before processing requests.

high

How Missing Rate Limiting Enables Denial of Service Attacks in Node.js and How to Fix It

The k-skill-proxy server exposed multiple public API endpoints (`/health`, `/v1/vworld/search`, `/v1/fine-dust/report`, `/v1/assembly/bills`) without consistent rate limiting middleware, leaving them vulnerable to denial-of-service attacks. A `buildRateLimiter` function existed but wasn't applied to all endpoints. This fix ensures rate limiting is enforced on all public endpoints, preventing resource exhaustion attacks.

high

How Sandboxed Iframe Popup Restriction Bypass happens in Electron and how to fix it

A high-severity flaw in Electron (CVE-2026-70608) allowed sandboxed iframes to bypass the `allow-popups` sandbox restriction through the internal OpenURL navigation path, letting malicious or compromised embedded content spawn unauthorized popup windows. The fix upgrades Electron from 40.10.6 to 41.10.3 (also patched in 42.0.1 and 39.8.10), closing the navigation-layer gap without requiring any application code changes.

critical

How Missing Authentication on DELETE Endpoints Happens in Python aiohttp and How to Fix It

A critical missing authentication vulnerability in `pz_minimax.py` allowed any network-connected user to delete stored MiniMax prompts via the `DELETE /pz_easyuse/minimax-prompts/{index}` endpoint without any access control. An attacker could enumerate sequential indices to wipe all user-created prompts from the shared JSON file. The fix restricts the DELETE endpoint to localhost-only requests by checking `request.remote` against loopback addresses.

critical

How Information Disclosure Vulnerabilities Happen in Python APIs and How to Fix It

A critical information disclosure vulnerability in the Hermes plugin dashboard API was exposing sensitive filesystem paths, credential file locations, and configuration details without authentication. The fix redacts this sensitive information from API responses, replacing absolute paths with boolean status indicators to prevent attackers from locating and targeting credential files.

critical

boardroom Server Handler Missing Authentication on HTTP Endpoints

The boardroom server's `Handler` class, extending `SimpleHTTPRequestHandler`, exposed sensitive HTTP endpoints without any caller authentication. An attacker could exploit this by making cross-origin requests to internal ports through DNS rebinding, accessing `/alerts.json`, `/dismiss`, and `/events.js` without authorization. The fix adds `is_local_origin()` checks to both `do_GET()` and `do_POST()` methods.