koala73/worldmonitor · warning · Error

Webhook URL must not point to a metadata endpoint

Error message

Webhook URL must not point to a metadata endpoint

What it means

saveImportedFramework() enforces a cap of MAX_IMPORTED = 20 user-imported analysis frameworks stored in localStorage under the key 'wm-analysis-frameworks' (built-in frameworks are not counted). When the imported array already holds 20 entries, the save throws before any payload validation. The cap bounds localStorage growth of the analysis framework library.

Solutions

  1. Delete one or more imported frameworks first via deleteImportedFramework(id) (built-ins cannot be deleted and do not count against the cap)
  2. Verify the actual count: JSON.parse(localStorage.getItem('wm-analysis-frameworks')).length
  3. Export and prune: keep only frameworks you actively use, then re-import selectively
  4. If the visible list looks smaller than 20, clear the stale 'wm-analysis-frameworks' key and re-import what you need

Example fix

// before
saveImportedFramework(fw); // throws when 20 imports already stored

// after
const importedCount = loadFrameworkLibrary().filter(f => !f.isBuiltIn).length;
if (importedCount >= 20) {
  promptUserToDeleteFirst();
  return;
}
saveImportedFramework(fw);
Defensive patterns

Strategy: validation

Validate before calling

const importedCount = loadFrameworkLibrary().filter(f => !f.isBuiltIn).length;
if (importedCount >= 20) { promptDeleteFirst(); return; }
saveImportedFramework(fw);

Try / catch

try {
  saveImportedFramework(fw);
} catch (e) {
  if (e instanceof Error && e.message.startsWith('Library is full')) offerFrameworkDeletionUI();
  else throw e;
}

Prevention

When it happens

Trigger: Calling saveImportedFramework(fw) when loadFromStorage('wm-analysis-frameworks') already returns 20 items, e.g., importing the 21st framework through the import UI or restoring a backup collection.

Common situations: Power users accumulating imports over time; scripts or tests importing in a loop; stale localStorage from an older version holding more entries than the visible list suggests.

Related errors


AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-08-21). Data as JSON: /api/errors/ae3665b5f36f3f19. Report an issue: GitHub.

Appendix: source

Thrown at api/_notification-webhook-ssrf.ts:239

async function defaultResolveHostname(hostname: string): Promise<string[]> {
  const records = await Promise.all([
    resolveDnsJson(hostname, 'A'),
    resolveDnsJson(hostname, 'AAAA'),
  ]);
  return records.flat();
}

/**
 * Fail fast at registration when the webhook hostname currently resolves to a
 * private or reserved address. Delivery repeats this check (and pins its
 * connection) because DNS can change after registration.
 */
export async function assertNotificationWebhookRegistrationUrlSafe(
  rawUrl: string,
  resolveHostname: ResolveHostname = defaultResolveHostname,
): Promise<void> {
  const staticError = blockedNotificationWebhookUrlReason(rawUrl);
  if (staticError) throw new Error(staticError);

  const hostname = new URL(rawUrl).hostname.toLowerCase();
  if (isIpLiteral(hostname)) return;
  let resolvedAddresses: string[];
  try {
    resolvedAddresses = await resolveHostname(hostname);
  } catch (error) {
    const message = error instanceof Error ? error.message : String(error);
    throw new Error(`Webhook URL DNS resolution failed: ${message}`);
  }
  if (!resolvedAddresses.length) throw new Error('Webhook URL DNS resolution returned no addresses');
  if (resolvedAddresses.some(isBlockedNotificationResolvedAddress)) {
    throw new Error('Webhook URL must not point to a private/local address');
  }
}

View on GitHub (pinned to 7d06c8633d)