Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .assets-revision
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
# is a git revision (branch name like `main`, a tag, or a specific commit
# sha). Override at runtime with the ASSETS_REVISION env var.
repo: ChilleD/WebHarbor
revision: 8d8e4069588ef55594622fee1ab9fa51c2011d07
revision: 480c892e976bada6c0ea3f5a66e2b9efda65525d
12 changes: 6 additions & 6 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ A coding agent (Claude Code, Cursor, Aider, Codex, ...) is reading this. Read on

## What it is

19 Flask mirror websites (Amazon, GitHub, BBC News, ...) packaged into one Docker image, plus a control plane on `:8101` for resetting per-site state. Used as a deterministic offline environment for web-agent benchmarks. ~3 GB image.
20 Flask mirror websites (Amazon, GitHub, BBC News, ...) packaged into one Docker image, plus a control plane on `:8101` for resetting per-site state. Used as a deterministic offline environment for web-agent benchmarks. ~3 GB image.

Two repos:
- **code** (this one) — Flask apps, control plane, scripts.
Expand Down Expand Up @@ -48,17 +48,17 @@ Inside the image, sites live at `/opt/WebSyn/<site>/`. The path predates the ren
# fresh clone
./scripts/fetch_assets.sh # pulls assets from HF
./scripts/build.sh # docker build -t webharbor:dev .
docker run -d -p 8101:8101 -p 40000-40018:40000-40018 webharbor:dev
docker run -d -p 8101:8101 -p 40000-40019:40000-40019 webharbor:dev
```

Or use the published image directly:

```bash
docker run -d -p 8101:8101 -p 40000-40018:40000-40018 \
docker run -d -p 8101:8101 -p 40000-40019:40000-40019 \
battalion7244/webharbor:latest
```

Sites are on `40000`-`40018` in the order declared by `SITES=( ... )` in `websyn_start.sh`. Control plane:
Sites are on `40000`-`40019` in the order declared by `SITES=( ... )` in `websyn_start.sh`. Control plane:

| Method | Path | Purpose |
|--------|---------------------|-------------------------------------------|
Expand Down Expand Up @@ -136,13 +136,13 @@ python3 -m py_compile sites/<site>/app.py

# 3. run on alt ports (don't collide with anything you already have running)
docker run -d --rm --name wh-test \
-p 8201:8101 -p 41000-41018:40000-40018 webharbor:dev
-p 8201:8101 -p 41000-41019:40000-40019 webharbor:dev

# 4. control plane healthy, all sites alive
curl -s http://localhost:8201/health | python3 -m json.tool | head

# 5. every site renders 200
for p in $(seq 41000 41018); do
for p in $(seq 41000 41019); do
curl -so /dev/null -w "$p:%{http_code}\n" http://localhost:$p/
done

Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ The full agent guide is loaded above via `@AGENTS.md`. The notes below apply onl

## Existing containers

If a container is already running on `:8101` / `:40000-40018`, treat it as the user's working environment — don't `docker stop` or `docker rm` it without explicit confirmation. Spin up your test container under a different name on alt ports (`:8201`, `:41000-41018`).
If a container is already running on `:8101` / `:40000-40019`, treat it as the user's working environment — don't `docker stop` or `docker rm` it without explicit confirmation. Spin up your test container under a different name on alt ports (`:8201`, `:41000-41019`).
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ git clone https://github.com/<you>/webharbor && cd webharbor
./scripts/fetch_assets.sh # pull current assets
./scripts/new_site.py mywebsite # OR edit an existing site
./scripts/build.sh && docker run -d --rm \
-p 8101:8101 -p 40000-40018:40000-40018 webharbor:dev
-p 8101:8101 -p 40000-40019:40000-40019 webharbor:dev
# iterate locally...

./scripts/extract_assets.sh ../webharbor-static-pr/ # split assets out
Expand Down
7 changes: 4 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# WebHarbor — slim, self-contained image.
# 19 Flask mirror sites + control plane on :8101.
# 20 Flask mirror sites + control plane on :8101.

FROM python:3.12-slim-bookworm

Expand Down Expand Up @@ -31,15 +31,16 @@ COPY sites/ /opt/WebSyn/
# IKEA's seed is reproducibly materialized from the tracked source catalog so code-only content fixes do not require an asset-repository write. Product images still come from the pinned asset bundle.
RUN cd /opt/WebSyn/ikea && PYTHONHASHSEED=0 python seed_data.py && rm -rf instance

# Apply tracked, idempotent Phys.org and Target data corrections to their seed assets.
# Apply tracked, idempotent data corrections to downloaded seed assets.
RUN cd /opt/WebSyn/phys_org && PYTHONHASHSEED=0 python migrate_seed.py && rm -rf instance
RUN cd /opt/WebSyn/target && PYTHONHASHSEED=0 python migrate_seed.py && rm -rf instance
RUN cd /opt/WebSyn/ted && PYTHONHASHSEED=0 python migrate_seed.py && rm -rf instance

COPY websyn_start.sh /opt/websyn_start.sh
COPY control_server.py /opt/control_server.py
COPY site_runner.py /opt/site_runner.py
RUN chmod +x /opt/websyn_start.sh

EXPOSE 8101 40000-40018
EXPOSE 8101 40000-40019

CMD ["/opt/websyn_start.sh"]
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,17 +36,17 @@ WebHarbor takes a different approach. We leverage coding agent (e.g., Claude Cod
- **Deep features unlocked** — carts, checkouts, accounts, all fully testable
- **Evolving** — harder tasks drive richer mirrors; the environment grows with agents
- **RL-ready** — sub-second database resets between rollouts
- **Community-driven** — 19 sites today, scaling to 100+ together
- **Community-driven** — 20 sites today, scaling to 100+ together

## 🚀 Quickstart

One command to run all web environments:

```bash
docker run -p 8101:8101 -p 40000-40018:40000-40018 battalion7244/webharbor:latest
docker run -p 8101:8101 -p 40000-40019:40000-40019 battalion7244/webharbor:latest
```

Then point your agent at `http://localhost:40000` through `http://localhost:40018` to explore 19 local mirrors of webvoyager sites: `Allrecipes, Amazon, Apple, ArXiv, BBC News, Booking, GitHub, Google Flights, Google Maps, Google Search, Hugging Face, Wolfram Alpha, Cambridge Dictionary, Coursera, ESPN, Merriam-Webster, IKEA, Phys.org, and Target`.
Then point your agent at `http://localhost:40000` through `http://localhost:40019` to explore 20 local mirrors of webvoyager sites: `Allrecipes, Amazon, Apple, ArXiv, BBC News, Booking, GitHub, Google Flights, Google Maps, Google Search, Hugging Face, Wolfram Alpha, Cambridge Dictionary, Coursera, ESPN, Merriam-Webster, IKEA, Phys.org, Target, and TED`.

For sub-second reset between rollouts, expose the control plane and call `/reset/<site>`:

Expand All @@ -65,7 +65,7 @@ git clone https://github.com/aiming-lab/WebHarbor && cd WebHarbor

## 🤝 Contribute

We have built 18 high-quality mirrors covering the [WebVoyager](https://github.com/MinorJerry/WebVoyager) benchmark. The next goal is **100+ sites**, covering everything in [Online-Mind2Web](https://huggingface.co/datasets/osunlp/Online-Mind2Web). We are inviting the community to build this together.
We have built 20 high-quality mirrors covering the [WebVoyager](https://github.com/MinorJerry/WebVoyager) benchmark. The next goal is **100+ sites**, covering everything in [Online-Mind2Web](https://huggingface.co/datasets/osunlp/Online-Mind2Web). We are inviting the community to build this together.

There are two ways to join the author list:

Expand Down
2 changes: 1 addition & 1 deletion agent_demo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export OPENAI_BASE_URL=https://api.openai.com/v1 # or your Azure / vLLM endpoi

## Run a task

WebHarbor must already be running locally (`docker run -p 8101:8101 -p 40000-40018:40000-40018 battalion7244/webharbor:latest`).
WebHarbor must already be running locally (`docker run -p 8101:8101 -p 40000-40019:40000-40019 battalion7244/webharbor:latest`).

Run a single task from a site's `tasks.jsonl`:

Expand Down
2 changes: 1 addition & 1 deletion control_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
'allrecipes', 'amazon', 'apple', 'arxiv', 'bbc_news', 'booking',
'github', 'google_flights', 'google_map', 'google_search',
'huggingface', 'wolfram_alpha', 'cambridge_dictionary',
'coursera', 'espn', 'merriam_webster', 'ikea', 'phys_org', 'target',
'coursera', 'espn', 'merriam_webster', 'ikea', 'phys_org', 'target', 'ted',
]
BASE_PORT = 40000
WEBSYN_DIR = '/opt/WebSyn'
Expand Down
39 changes: 39 additions & 0 deletions review-reports/PR-85-FINAL-AUDIT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# PR #85 independent review and remediation audit

## Scope

This audit reviewed GitHub PR #85 head `18fa53a96b6131af0364f00aa2cbd07609a902c3` against base `90afddb6d4af382935ded9a385f2eead604188cf`. Seven independent review contexts assessed the pristine original head. Their raw reports are retained outside the repository under `/data/zhaoyang-user-projects/websyn/_wh_review_tools/pr85-agents/reports-original-final/`.

## Review-agent findings and dispositions

| Review agent | Primary findings on the original head | Verification and disposition |
|---|---|---|
| Security | Hard-coded Flask secret, open redirects after login/save, no CSRF protection, GET logout, and default/weak registration password behavior. | Confirmed directly in the original `sites/ted/app.py` and templates. Fixed with an environment/random secret, strict local-path redirects, Flask-WTF CSRF, POST logout, session rotation, request/field limits, and server-side registration validation. HTTP regression tests cover CSRF, redirect rejection, credential non-disclosure, invalid registration, and safe GET behavior. |
| Tasks and data | Exact duration/view arithmetic and seed account state were correct; TED task URLs were all `40016`; task 12 allowed multiple valid non-AI choices; playlist membership required independent confirmation. | Data facts were verified from the downloaded SQLite seed. The integration review established that TED is site index 19 and therefore port `40019`; all 20 task URLs were corrected. Task 12 now reports the exact removed title while still accepting any one valid non-AI row through a database-bound verifier. Playlist ordering and membership were checked directly. `judge_rubric` is not included in the web-agent prompt: `agent_demo/agent.py` passes only `ques` to `build_messages` and carries the rubric solely into judge input. |
| Verifiers | LLM checks became implicit passes under `--no_llm`; URL checks used substrings without origin/path/query binding; several required search/topic/filter steps were unenforced; multiple answers were unbound or negation-sensitive; state checks were incomplete. | All 20 verifiers and `verify_lib.py` were replaced with deterministic same-origin path/query/order checks, click/submit/input checks, exact task identity, non-empty answers, negation-aware fact binding, complete read-only database comparison, and exact global state-table deltas. Positive, answer-only, wrong-task, external-origin, missing-filter, negated-answer, swapped-value, wrong-removal, unrelated-mutation, and same-table-extra-mutation tests pass. |
| UI, responsive, accessibility | Cards and the lead story hid talk titles; the stacked mobile header remained sticky; controls lacked labels; repeated event buttons lacked event-specific accessible names; talk images had empty alt text; focus styles were absent; footer alignment was poor on mobile. | Talk and lead cards now render titles and speakers, the mobile header is static, filters and note/search fields are labeled, event buttons carry event-specific accessible names, talk images have descriptive alt text, focus-visible styles are present, and mobile footer alignment is corrected. Automated 320 px, 390 px, and 1440 px checks cover 30 route/viewport combinations with no horizontal overflow or broken images. |
| Integration and assets | The original HF main pin did not contain `ted.tar.gz`; all tasks targeted IKEA's `40016`; docs still described 19 sites and ended at `40018`; committed smoke/HF/PR evidence was stale; runtime startup could manufacture a mutable seed and mask missing assets. | HF dataset PR #2 was merged and the repository-wide pin now references merge commit `480c892e976bada6c0ea3f5a66e2b9efda65525d`, which contains all 20 tarballs. The temporary TED override was removed. Full 20-site asset fetch and clean Docker build pass. Site registration and docs now use 20 sites and ports `40000-40019`. Runtime fails closed when the TED seed is absent and only copies the authoritative seed into `instance`. Stale PR-65 evidence was removed and replaced by this exact-head audit. |
| Application and data model | Runtime seed creation was nondeterministic and could mutate `instance_seed`; partial seed gates were unsafe; topic matching was case-sensitive; search was brittle for simple morphology; registration accepted invalid/default credentials; CSRF and secret handling were unsafe; legacy SQLAlchemy lookup was used. | Runtime seed generation was removed; startup requires the HF seed. Topic matching is case-insensitive, duration filtering uses exact seconds, and search accepts simple prefixes/plurals. Registration validation, CSRF, secret handling, foreign-key enforcement, unique indexes, duplicate-race handling, and SQLAlchemy 2 lookup are implemented. The TED seed migration is idempotent and adds unique saved-talk and registration indexes. |
| Evidence quality | The PR-authored evidence described PR #65, an older head, 17-site/old-port smoke output, a different HF revision, and a pre-existing image rather than a clean PR #85 source build. | All stale evidence files were removed. Current evidence includes reproducible unit/adversarial tests, actual browser trajectories with initial/after databases and verifier JSON, responsive screenshots/results, immutable asset download logs, a clean local image build, 20/20 site health, TED reset hash identity, and reset-all success. |

## Task validation

All 20 tasks were completed through the rendered UI from a fresh seed database. Each generated trajectory was evaluated by its corresponding deterministic verifier using the captured initial and after-state databases. Result: `20/20 PASS`.

The complete local browser evidence is retained under `/data/zhaoyang-user-projects/websyn/_wh_review_tools/pr85-fixes/e2e/`. Responsive results and screenshots are retained under `/data/zhaoyang-user-projects/websyn/_wh_review_tools/pr85-fixes/responsive/`.

## Repository and container validation

- Python compilation, shell syntax, Ruff fatal/undefined-name checks, and `git diff --check`: PASS.
- TED HTTP/app, verifier, migration, and environment regression suite: 27 tests PASS.
- All TED templates and all 64 talk details render successfully: PASS.
- Immutable TED asset fetch plus all-site asset fetch: PASS; 20 tarballs extracted.
- TED migration: creates two unique indexes on first application and zero on the second application.
- Clean Docker build from the remediated source tree: PASS (`sha256:212755ac90732a0a031cd997dc764eec39879efc97241a8809860f0d8b184e11`).
- Control-plane health and all 20 site roots: PASS.
- `/reset/ted`: PASS and runtime/seed SHA-256 values match.
- `/reset-all`: PASS for all 20 sites.

## Asset status

Hugging Face dataset PR #2 was merged as `480c892e976bada6c0ea3f5a66e2b9efda65525d`. The repository-wide asset pin now references that merge commit, which contains all 20 tarballs including `ted.tar.gz`; the temporary TED-specific override has been removed.
3 changes: 3 additions & 0 deletions sites/ted/_health.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""Per-site health probe (optional, called by control_server)."""
def health():
return {"ok": True, "site": "ted", "paths": ["/", "/talks", "/search", "/events"]}
Loading