You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat: unify crawl engine, harden scan jobs, and enrich the link graph
Share one crawl engine between /v1/site-audit and persistent link-graph
scans, then make background scans survive process restarts and represent
non-200 pages faithfully in the graph.
Crawl engine
- Extract SiteCrawler.crawl returning a SiteCrawlSnapshot; link_graph now
consumes it instead of running a second BFS, so robots, sitemap seeding,
URL normalization, and depth/page budgets behave identically everywhere.
- Add cooperative cancellation and progress reporting to SiteCrawler.
- Skip non-HTML asset URLs at enqueue time via shared is_html_like_url.
- Expose use_sitemap through the scan API and persisted scan options.
Scan jobs
- Bound concurrent scans with SEO_SCAN_JOB_WORKERS and add worker leases,
heartbeats, and a reconcile loop governed by SEO_SCAN_JOB_LEASE_SECONDS.
- Coordinate cancellation through SQLite so any process can cancel a scan.
- Requeue in-flight work on graceful shutdown; recover abandoned leases.
- Enable WAL and a busy timeout; give in-memory storage a shared-cache DSN.
Link graph
- Model redirects, robots-blocked, fetch-failed, and out-of-scope pages as
real graph nodes with redirect edges and preserved chains.
- Merge link zones (header/nav/content/footer) per target and carry them
into edges, incoming links, and dashboard filters.
- Recompute inbound/outbound counts from stats and apply issue penalties
incrementally with a matching grade.
Interfaces
- Escape inline dashboard JSON against script-tag breakout.
- Add cancel/rerun/dashboard controls and error surfacing to the browser UI;
fetch the dashboard with the API key instead of a bare link.
- Persist SQLite in a dedicated /data volume for the read-only container.
- Document every link-graph route, scan data retention, and limitations.
Tests: 100 passed, 90% coverage; ruff, mypy, and pip-audit clean.
|`GET`|`/app`| Minimal browser UI for unified link-graph scans |
45
64
|`POST`|`/api/projects`| Create a persistent crawl project |
65
+
|`GET`|`/api/projects`| List projects |
66
+
|`GET`|`/api/projects/{project_id}`| Get one project |
46
67
|`POST`|`/api/projects/{project_id}/scans`| Start a background crawl + graph + SEO scan |
68
+
|`GET`|`/api/projects/{project_id}/scans`| List project scans |
69
+
|`GET`|`/api/scans/{scan_id}/status`| Get progress and terminal status |
70
+
|`POST`|`/api/scans/{scan_id}/cancel`| Cancel pending or running work |
71
+
|`POST`|`/api/scans/{scan_id}/rerun`| Create a new scan with the same options |
72
+
|`GET`|`/api/scans/{scan_id}/pages`| List page records and attached SEO data |
73
+
|`GET`|`/api/scans/{scan_id}/page?url=...`| Resolve a page by normalized URL |
74
+
|`GET`|`/api/scans/{scan_id}/pages/{graph_node_id}`| Resolve a page by graph node ID |
75
+
|`GET`|`/api/scans/{scan_id}/links`| List internal, external, or redirect links |
47
76
|`GET`|`/api/scans/{scan_id}/graph`| Graph nodes/edges with SEO data attached |
77
+
|`GET`|`/api/scans/{scan_id}/seo/issues`| List page and site-level SEO issues |
78
+
|`GET`|`/api/scans/{scan_id}/stats`| Site totals, duplicates, cycles, orphans, and failures |
48
79
|`GET`|`/api/scans/{scan_id}/dashboard`| Interactive graph dashboard for a completed scan |
49
80
|`GET`|`/analyze`| Backwards-compatible original full-analysis shape plus `v2` data |
50
81
|`GET`|`/quick-score`| Score, warnings, and top recommendations |
@@ -107,7 +138,9 @@ If `SEO_API_KEY` is set, add `-H 'X-API-Key: …'` to protected endpoints.
107
138
108
139
## Docker
109
140
110
-
The image runs as a non-root user. The Compose example binds only to loopback, drops Linux capabilities, uses a read-only filesystem, and adds a health check.
141
+
The image runs as a non-root user. The Compose example binds only to loopback,
142
+
drops Linux capabilities, uses a read-only root filesystem, and persists SQLite
143
+
in the `analyzer-data` volume mounted at `/data`.
111
144
112
145
```bash
113
146
cp .env.example .env
@@ -129,7 +162,8 @@ All settings use the `SEO_` prefix. See [`.env.example`](.env.example) for the c
Copy file name to clipboardExpand all lines: docs/SECURITY.md
+12-2Lines changed: 12 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -44,11 +44,21 @@ Analysis responses add a request ID, `nosniff`, a no-referrer policy and `Cache-
44
44
45
45
PageSpeed is disabled by default. When enabled and requested, the final validated public URL is sent to Google PageSpeed Insights. That creates a third-party data and quota dependency; document it in your privacy/processing model and protect `SEO_PAGESPEED_API_KEY` in the environment.
46
46
47
-
Page HTML is processed in memory and cached in summarized analysis form for a bounded TTL. This project has no database and does not intentionally persist fetched HTML. Multi-worker deployments have independent caches and metrics.
47
+
Page HTML is processed in memory and cached in summarized analysis form for a
48
+
bounded TTL. Persistent scans store normalized URLs, extracted metadata, links,
49
+
SEO findings, graph data, and status/timing summaries in SQLite; raw HTML response
50
+
bodies are not intentionally persisted. Multi-worker deployments have independent
51
+
caches and metrics, while scan ownership and cancellation are coordinated through
52
+
SQLite worker leases.
48
53
49
54
## Container posture
50
55
51
-
The supplied image runs as a non-root system user. Compose binds to loopback, drops all Linux capabilities, sets `no-new-privileges`, uses a read-only root filesystem, provides a small `/tmp` tmpfs and declares a memory limit. Put a production reverse proxy in front; do not expose the Uvicorn development topology as a complete security perimeter.
56
+
The supplied image runs as a non-root system user. Compose binds to loopback,
57
+
drops all Linux capabilities, sets `no-new-privileges`, uses a read-only root
58
+
filesystem, persists scan data in a dedicated `/data` volume, provides a small
59
+
`/tmp` tmpfs, and declares a memory limit. Put a production reverse proxy in
60
+
front; do not expose the Uvicorn development topology as a complete security
61
+
perimeter.
52
62
53
63
Container restrictions do not replace outbound firewall rules. For higher-risk deployments, allow egress only to public HTTP(S), run in a dedicated network/namespace, configure DNS deliberately and set infrastructure-level CPU/request/time limits.
0 commit comments