The Problem

Paywalled articles and CORS-restricted resources break developer tools, scrapers, and research workflows. Existing services like 12ft.io are hosted and unreliable. Ladder gives you a self-hosted HTTP proxy that strips CORS headers, rewrites HTML, and applies per-domain rulesets so you can inspect how sites serve content under different client conditions.

What This Does

Ladder is a Go HTTP proxy. It intercepts requests, applies domain-specific rulesets from rulesets/ (e.g. rulesets/us/nytimes-com.yaml), modifies request/response headers, and injects or removes HTML/CSS/JS. It supports custom User-Agent, X-Forwarded-For spoofing, FlareSolverr integration for Cloudflare-protected sites, and exposes a REST API plus raw HTML fetching.

The frontend is a minimal React/Tailwind dashboard (tailwind.config.js, styles/input.css) served alongside the proxy. Deployment options include Docker, docker-compose, Helm chart, and prebuilt binaries via GoReleaser.

How It Is Wired

Execution starts in cmd/main.go at main (line 25), which routes to ProxySite in handlers/proxy.go. That function is the hub: it's called from 3 places, and it calls NewRuleset (the most-connected function, called from 4 places) to load rules, then fetchSite to hit the target URL. fetchSite checks domain allowlists via StringInSlice and retrieves the applicable rule.

The ruleset system lives in pkg/ruleset/ruleset.go — 12 functions including NewRuleset, loadRulesFromLocalDir, and loadRulesFromRemoteFile. It supports merging rulesets via handlers/cli/cli.go (HandleRulesetMerge, which calls gzipMerge/yamlMerge). The API handler in handlers/api.go exposes JSON endpoints, and handlers/raw.go returns unmodified HTML.

The call graph shows no circular dependencies — 13 code files, 0 cycles. The highest blast radius is NewRuleset (4 callers) and ProxySite (3 callers); changing their signatures breaks most of the proxy path. handlers/cli/cli.go has high branching density (18 branch points over 51 lines), making it the least maintainable spot.

How To Use It

Setup: go build -o ladder cmd/main.go or docker build -t ladder . (Dockerfile present).

Configuration: Set LADDER_PORT (default 8080), LADDER_BASIC_AUTH for auth, and LADDER_RULESET_DIR to point at rulesets/. FlareSolverr is optional via LADDER_FLARESOLVERR_URL.

Running: ./ladder -r ruleset.yaml (per README). For Docker: docker compose up -d using docker-compose.yaml. The proxy listens on port 8080; browse to http://localhost:8080/https://example.com to proxy a URL.

Real-World Use

A QA engineer testing paywall behavior across user agents:

curl -x http://localhost:8080 -H "User-Agent: Googlebot" \
  -H "X-Forwarded-For: 66.249.66.1" \
  https://www.nytimes.com/article.html

The response comes back with CORS headers stripped, CSP removed, and the rulesets/us/nytimes-com.yaml modifications applied — without touching the production site.

Code Health & Issues

Static analysis (not opinion) found 8 issues, 3 high severity:

  • High – CI runs no tests despite 4 test files existing. A green check means nothing.
  • High – GitHub Actions pinned to mutable tags (pnpm/action-setup@v6, goreleaser/goreleaser-action@v7, sigstore/cosign-installer@main) — supply-chain risk.
  • Highbuild-css.yaml pushes directly to the default branch with no test gate.
  • Medium – Docker base images (golang:1.26, gcr.io/distroless/static-debian13:nonroot) not pinned by digest.
  • Medium – No dependency vulnerability scan in CI.
  • Medium – Checkout leaves persist-credentials: true in build-css.yaml.
  • Medium – No non-root USER in Dockerfile.
  • Low – No timeout-minutes on workflow jobs.

The Bottom Line

Ladder is a solid, well-structured Go proxy for legitimate paywall testing and CORS debugging. The core proxy path is clean with no circular dependencies. The CI/CD pipeline is the weak point — it can ship untested, unpinned builds. Use it if you need a self-hosted content-testing proxy and are willing to harden the build pipeline yourself.