Projects#

JavaScript projects build when the target is browser-based, when the recon stack is Node, or when scraping demands a real headless browser instead of an HTTP client.

Headless scraper#

Drive Chrome via Playwright to render a target page (including its JavaScript) and capture HTML, console output, and network traffic.

// src/scrape.js
import { chromium } from "playwright";

async function scrape(url) {
  const browser = await chromium.launch({ headless: true });
  const ctx = await browser.newContext();
  const page = await ctx.newPage();
  const requests = [];
  page.on("request", r => requests.push({ url: r.url(), method: r.method() }));
  await page.goto(url, { waitUntil: "networkidle" });
  const html = await page.content();
  await browser.close();
  return { html, requests };
}

scrape(process.argv[2]).then(r => console.log(JSON.stringify(r, null, 2)));
$ pnpm add playwright
$ pnpm exec playwright install chromium
$ node src/scrape.js https://target.example.com/

Browser extension#

A WebExtension (Chrome / Firefox compatible) that adds an operator-facing toolbar action, reads page DOM, and posts back to a local listener.

// manifest.json
{
  "manifest_version": 3,
  "name": "operator-toolbox",
  "version": "0.1.0",
  "action": { "default_popup": "popup.html" },
  "permissions": ["activeTab", "scripting"],
  "host_permissions": ["http://localhost:9000/*"]
}

// popup.js
document.querySelector("#scan").addEventListener("click", async () => {
  const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
  const [{ result }] = await chrome.scripting.executeScript({
    target: { tabId: tab.id },
    func: () => ({ title: document.title, links: document.links.length }),
  });
  fetch("http://localhost:9000/collect", {
    method: "POST",
    body: JSON.stringify(result),
  });
});

Node CLI#

A small CLI tool drop on a workstation. Uses commander for arg parsing and writes to stdout.

// src/cli.js
#!/usr/bin/env node
import { Command } from "commander";

const program = new Command()
  .name("op-dns")
  .description("Resolve A/AAAA/MX for a list of domains")
  .argument("<file>", "file with one domain per line")
  .option("-c, --concurrency <n>", "parallel queries", parseInt, 20);

program.action(async (file, opts) => {
  const dns = await import("node:dns/promises");
  const domains = (await import("node:fs/promises"))
    .readFile(file, "utf8").then(t => t.split("\n").filter(Boolean));

  for (const d of await domains) {
    try {
      const a = await dns.resolve(d, "A");
      console.log(d, a.join(","));
    } catch (e) {
      console.error(d, e.code);
    }
  }
});

program.parse();

Live DOM analysis server#

A small Express service run locally, accepts URLs from a browser extension, runs Playwright, and stores findings in SQLite.

import express from "express";
import Database from "better-sqlite3";
import { chromium } from "playwright";

const db = new Database("findings.db");
db.exec("CREATE TABLE IF NOT EXISTS findings (ts INTEGER, url TEXT, json TEXT)");

const app = express();
app.use(express.json());

app.post("/collect", async (req, res) => {
  db.prepare("INSERT INTO findings VALUES (?, ?, ?)")
    .run(Date.now(), req.body.url, JSON.stringify(req.body));
  res.status(204).end();
});

app.listen(9000);

References#