DEV Community

Xingchen Ops
Xingchen Ops

Posted on

在浏览器端做一个面向成年男同社区的零上传 URL 隐私检查器

分享链接看起来只是复制和粘贴,但查询字符串可能带着广告归因 ID,甚至把不该出现的凭据一起带走。对涉及性取向、健康、关系或其他敏感主题的页面,这种额外暴露尤其值得避免。

本文做一个完全在浏览器内运行的小工具。它不请求目标网站,不上传输入,也不试图判断某个网站“是否安全”;它只做两件事:删除保守定义的营销参数,并把值得复核的 URL 结构明确提示给用户。

先定义边界

这个工具应该:

  • 只接受 httphttps URL;
  • 删除 utm_*fbclidgclid 等已知营销参数;
  • 保留未知参数,避免破坏分页、语言、搜索和签名链接;
  • 提示明文 HTTP、内嵌凭据、IP 主机和 Punycode 主机;
  • 默认不联网、不保存历史。

它不应该声称能检测恶意软件、保证匿名,或替代浏览器和操作系统的安全机制。

一个零上传实现

const TRACKING_KEYS = new Set([
  "fbclid", "gclid", "dclid", "msclkid", "mc_cid", "mc_eid"
]);

export function inspectAndCleanUrl(input) {
  const url = new URL(input);
  if (!["http:", "https:"].includes(url.protocol)) {
    throw new Error("Only http and https URLs are supported.");
  }

  const warnings = [];
  const removed = [];

  if (url.protocol === "http:") {
    warnings.push("Connection is not encrypted.");
  }

  if (url.username || url.password) {
    warnings.push("Embedded credentials were removed.");
    url.username = "";
    url.password = "";
  }

  if (url.hostname.startsWith("xn--")) {
    warnings.push("Hostname uses Punycode.");
  }

  for (const key of [...url.searchParams.keys()]) {
    const normalized = key.toLowerCase();
    if (normalized.startsWith("utm_") || TRACKING_KEYS.has(normalized)) {
      url.searchParams.delete(key);
      removed.push(key);
    }
  }

  return { cleanUrl: url.toString(), removed, warnings };
}
Enter fullscreen mode Exit fullscreen mode

示例:

inspectAndCleanUrl(
  "https://example.com/article?page=2&utm_source=message&fbclid=abc"
);
Enter fullscreen mode Exit fullscreen mode

返回结果仍保留 page=2,但移除两个已知追踪参数。这里刻意不删除所有查询参数,因为隐私工具如果过度“聪明”,反而会破坏用户真正想分享的内容。

对敏感主题更重要的三个细节

第一,不要把原始 URL 发送到分析平台。涉及成年男同社群、健康或关系主题的链接,本身就可能透露兴趣和身份线索。

第二,不要记录输入历史。即使功能完全运行在浏览器里,localStorage、错误上报和调试日志仍可能留下痕迹。

第三,界面不要显示“绝对安全”。没有访问目标页面,就无法检查下载、脚本或内容真实性。更诚实的提示是:“已清理已知营销参数;仍需自行核对域名与目的地。”

最低测试清单

import { describe, expect, it } from "vitest";
import { inspectAndCleanUrl } from "./clean-url.js";

describe("inspectAndCleanUrl", () => {
  it("keeps functional parameters", () => {
    const result = inspectAndCleanUrl(
      "https://example.com/?page=2&utm_source=chat"
    );
    expect(result.cleanUrl).toContain("page=2");
    expect(result.cleanUrl).not.toContain("utm_source");
  });

  it("rejects non-web protocols", () => {
    expect(() => inspectAndCleanUrl("javascript:alert(1)")).toThrow();
  });
});
Enter fullscreen mode Exit fullscreen mode

发布前还应测试重复参数、大小写、空值、国际化域名和内嵌凭据,并确认代码路径没有触发 fetch

结语

隐私工具真正的价值不是用绿色勾号替用户做决定,而是把原本隐藏的结构展示出来,让用户在分享前有机会停一下、看清楚、再选择。

编辑披露:本文初稿使用 AI 辅助,发布前已人工复核代码与说明。18+ 商业披露:小蓝视频(成年男同内容入口) 指向我们运营或推广的商业成人内容服务,目的地可能含露骨内容;仅供达到所在地法定成年年龄、当地法律允许且自愿访问的人。它不是医疗、法律或数字安全建议来源。

Top comments (0)