DEV Community

Evan Mercer
Evan Mercer

Posted on

Validate X post URLs before requesting video metadata in JavaScript

A pasted social URL is untrusted input. Before a backend tries to retrieve media, it should extract a small, well-defined identifier and discard everything else. This article implements that boundary for public X post URLs in JavaScript. I maintain Twitter Viewer; the parser below is an example of input handling, not a claim that these snippets reproduce its backend.

Parse the URL before matching the path

Matching a full URL with a loose regular expression tends to accept misleading hosts. A string can contain x.com in a username, query parameter, or an attacker-controlled subdomain. Let the URL parser separate those components first.

export function parsePostUrl(raw) {
  let url;
  try {
    url = new URL(raw.trim());
  } catch {
    throw new Error('Enter an absolute post URL');
  }
  if (url.protocol !== 'https:') throw new Error('HTTPS required');
  if (url.username || url.password) throw new Error('Credentials rejected');
  if (url.port && url.port !== '443') throw new Error('Unexpected port');
  const host = url.hostname.toLowerCase();
  const allowed = new Set(['x.com', 'www.x.com', 'twitter.com', 'www.twitter.com']);
  if (!allowed.has(host)) throw new Error('Unsupported host');
  const match = url.pathname.match(/^\/([A-Za-z0-9_]{1,15})\/status\/([0-9]{1,20})(?:\/(?:video|photo)\/[1-9][0-9]*)?\/?$/);
  if (!match) throw new Error('Expected a post URL');
  return {
    author: match[1],
    id: match[2],
    canonicalUrl: `https://x.com/${match[1]}/status/${match[2]}`
  };
}
Enter fullscreen mode Exit fullscreen mode

This intentionally accepts a narrow set of common web links. It does not promise support for every historical Twitter route. Mobile hosts, share wrappers, and links without a handle can be added as explicit cases after they have tests. A rejected unfamiliar route should produce a useful message rather than silently falling through to arbitrary fetching.

Keep the post ID as a string. Large decimal identifiers can exceed JavaScript's safe integer range; converting one to Number may change its value before the request even reaches the server. The handle is useful for a human-readable link but should not be treated as an immutable account identity.

Test deceptive hosts as well as normal links

Save the parser as post-url.mjs, then create post-url.test.mjs:

import test from 'node:test';
import assert from 'node:assert/strict';
import { parsePostUrl } from './post-url.mjs';

test('drops share parameters and keeps the ID exact', () => {
  const result = parsePostUrl('https://twitter.com/example/status/18446744073709551615?s=20');
  assert.equal(result.id, '18446744073709551615');
  assert.equal(result.canonicalUrl, 'https://x.com/example/status/18446744073709551615');
});

test('rejects URLs that merely contain a trusted hostname', () => {
  for (const url of [
    'https://x.com.example.org/example/status/123',
    'https://x.com@other.example/example/status/123',
    'https://other.example/?next=https://x.com/example/status/123',
    'http://x.com/example/status/123'
  ]) assert.throws(() => parsePostUrl(url));
});

test('accepts a media-view suffix but removes it', () => {
  assert.equal(parsePostUrl('https://x.com/example/status/123/video/1').id, '123');
});
Enter fullscreen mode Exit fullscreen mode

Run node --test post-url.test.mjs. These tests do not need an X account or network access. They describe the input contract, so upstream changes cannot turn a basic parser test into an intermittent integration failure.

A valid URL does not imply downloadable media

After parsing, there are still independent outcomes: a deleted post, a private account, a post with no native video, an upstream timeout, and a successful lookup with several variants. Keep these separate in the response model. Returning “invalid URL” for every outcome teaches users to keep editing a perfectly valid link.

For a manual workflow, my twitter video downloader accepts public tweet links and offers available MP4 variants. A third-party embedded player is a different media source; the fact that it appears inside a post does not make it a native X video. Saving a file also does not grant permission to republish it.

Revalidate the boundary on the server

Client validation improves feedback, but a caller can skip the client. Apply the same host and path policy on the server. Prefer passing the extracted ID into a constrained lookup rather than fetching the original pasted URL. If a backend follows redirects or later fetches an upstream media URL, that fetch needs its own destination and redirect policy; validating the initial social URL does not validate a later destination.

Top comments (0)