Releases: rainmanjam/headwater
Release list
v2.1.0 — Headwater
One self-hosted API for Google Maps, News, Trends and Autocomplete, plus YouTube
transcripts. This release renames the project, fixes several bugs that failed
silently, and moves the image to Python 3.14 on Debian 13.
Breaking
The project is now headwater. The old Docker Hub repository
rainmanjam/social-flood has been deleted, and Docker Hub does not redirect
a renamed or deleted repository. Any docker pull rainmanjam/social-flood in a
compose file, script or CI job stops working immediately.
- image: rainmanjam/social-flood:latest
+ image: rainmanjam/headwater:2.1.0The GitHub repository moved to rainmanjam/headwater; GitHub does redirect, so
existing git remotes keep working. No API path, parameter or response field
changed — only the name.
Maps now rejects impossible requests up front. A max_results/timeout
combination that cannot finish returns 400 with the count you can afford,
instead of timing out minutes later. max_results is capped at 45.
Fixed — bugs that returned success while doing nothing
These are the reason for the release. Each one looked healthy from the outside.
- Record storage never reached Redis.
RecordStorecalled
manager.is_available()on what is a@property, so every call raised
'bool' object is not callable, was swallowed, and silently fell back to
in-memory storage. Maps jobs, monitors and webhooks were lost on restart and
invisible to sibling workers./health/detailednow reports
record_storage_durable, and startup asserts it, so this cannot recur quietly. - The Redis health check tested nothing. It called a
_get_redis_client()
method that does not exist. It now pings the real client. - A broken import crashed every News search, hidden behind the cache.
- Google News ignored the proxy. GNews takes a
{"http": ..., "https": ...}
mapping, not a bare URL string.
Security
- Proxy credentials are no longer logged. A proxy URL carries
user:pass@
inline; two code paths wrote it to the transcript and application logs.
Credentials are masked at every call site now. - NLTK removed, which removes PYSEC-2026-2026 rather than suppressing it.
Article extraction still returns title, authors, date and full text;summary
andkeywordsare nownull, withnlp_available: falseto say so plainly. - Debian security updates are applied at image build.
Proxying is now per host
ENABLE_PROXY was global, which forced one decision for every upstream. The
upstreams disagree: Reddit needs a proxy, YouTube is refused by some providers at
the tunnel, and Google Maps loads through a plain GET but a full browser
navigation through a datacentre proxy never settles.
ENABLE_PROXY=true
PROXY_URLS=http://user:pass@proxy.example.com:8080
NO_PROXY_HOSTS=youtube.com,youtu.be,ytimg.com,google.comNO_PROXY_HOSTS matches on a dot boundary, so youtube.com does not also match
notyoutube.com.example.
Performance
- Google News search: ~80s → ~3s. GNews was launching a whole Chromium
instance per article to resolve redirect URLs. It now decodes them directly. - Maps searches are cached for an hour. The blocking path had no cache at
all, and Maps costs roughly 12 seconds per result. - Trends reference data is cached for a day.
/geo(3,681 locations) and
/categories(1,133) change on the order of months.
API
/geois served from the native Trends endpoint rather than a scrape.POST /batch-get-transcriptsaccepts a JSON body, up to 50 video ids.- Trends endpoints stop passing
Noneinto trendspy, so its own defaults apply. - Exhausted Google Trends quota returns 502, not an empty
200— a quiet
week and a broken scraper should never look the same.
Image and supply chain
- Python 3.14 on Debian 13 (trixie), pinned by digest.
- Multi-arch:
linux/amd64andlinux/arm64. - SBOM and provenance attestations attached, plus a cosign signature.
- Full OCI metadata — source, revision, version, licence, base image.
Docs
The README is rewritten against what the API actually exposes, with real
responses rather than invented ones, and all 29 markdown files were audited
against the code.
Images
docker pull rainmanjam/headwater:2.1.0
docker pull ghcr.io/rainmanjam/headwater:2.1.0Digest: sha256:ab46c57ed6ce94a5f3d24c0e2c16f067749c5c743356a07e46c879d85b127c7c
Verify the signature
cosign verify \
--certificate-identity-regexp 'https://github.com/rainmanjam/headwater/.github/workflows/release.yml@.*' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
ghcr.io/rainmanjam/headwater@sha256:ab46c57ed6ce94a5f3d24c0e2c16f067749c5c743356a07e46c879d85b127c7cFull commit log: v2.0.0...v2.1.0
Release v2.0.0
Release v2.0.0
Changes
- Resolve all findings from the 2026-09-01 stability review (v2.0.0)
- docs: fix a sentence split by the previous docstring edit
- docs: cross-reference the dismissed CodeQL alert in identity.py
- chore: release as 2.0.0, not 1.6.1
- ci(release): tag from main's version instead of committing to main
- chore: drop two unused imports in the Maps scraper
- fix(security): stop sensitive values reaching logs at all
- fix(security): close log injection across the API surface
- fix(security): key the identity digests; stop logging precise locations
- feat: enable Prometheus metrics; remove the dead Postgres layer
- chore(deps): move psycopg2-binary out of production requirements
- ci(release): run publish in the production environment
- chore(deps): lift the redis hold, 7.1.0 -> 8.1.0
- fix(maps): migrate the job service to the owner-scoped store
- fix: stop three paths reporting failure as success
- docs: use X-API-Key, not Authorization: Bearer, in examples
- ci: fix the four failures the new pipeline found on its first run
- chore(typing): enable the pydantic mypy plugin
- chore: ignore agent and code-intelligence tool output
- docs: correct README drift against the remediated behaviour
- ci: raise the coverage floor to the measured 65%
- chore(deps): drop slowapi; it was added for a path that was then deleted
- test(main): assert the new lifespan wiring
- test: reset the global rate-limit store between every test
- test(main): assert the real rate-limit middleware, not slowapi's state
- fix(main): install the real rate limiter, delete the dead slowapi path
- fix(maps): pass the caller's api_key through to monitor and webhook services
- test(auth): /status is key-gated, not public
- test(boot): treat a commented .env.example key as documented
- fix(config): keep .env.example runnable without Docker
- test: retarget auth patches onto _auth_snapshot after AuthSettings removal
- Merge branch 'fix/phase-5-maps-router' into integration
- Merge branch 'fix/phase-5-maps-endpoints' into integration
- Merge branch 'fix/phase-3-news-trends' into integration
- Merge branch 'fix/phase-3-url-guard-hardening' into integration
- fix(security): block CGNAT and IPv6-embedded IPv4 in the SSRF guard
- fix(maps): state the evidence rather than the conclusion, and re-guard monitor URLs
- fix(maps): stop upstream error strings reaching callers on the non-raising path
- chore(news): drop an unused import
- Merge branch 'fix/phase-2-ci' into integration
- Merge branch 'fix/phase-5-maps-scraper' into integration
- Merge branch 'fix/phase-3-rate-limiting' into integration
- Merge branch 'fix/phase-3-security-core' into integration
- Merge branch 'fix/phase-4-proxy-config' into integration
- Merge branch 'fix/phase-4-dependencies' into integration
- Merge branch 'fix/phase-1-boot-auth' into integration
- fix(proxy): read proxy config from Settings, not os.getenv at import
- fix(news): cap the article body, stop caching NLP-degraded responses
- fix(rate-limit): make test isolation and key parsing order-independent
- fix(maps): label the one empty result that cannot be verified
- ci: fail with a clear message when the pip-audit pin is missing
- fix(auth): serialise snapshot refresh; cover the API_KEY placeholder branch
- fix(maps): close three holes found reviewing the scraper diff
- fix(maps): close four fabrication and SSRF gaps found in review
- ci: install from requirements.lock, block on pip-audit, correct coverage floor
- refactor(maps): split the 3,000-line router into modules by concern
- fix(news): revalidate redirects, stop caching partial results, rate-limit routes
- fix(config,auth): act on independent review of the phase-1 boot/auth fixes
- fix(maps): close SSRF sink, scope jobs to their owner, rate limit every route
- feat(maps): replace fabricated endpoints with real implementations
- fix(rate-limit): detect --workers on the command line
- fix(main): require an API key for /metrics
- fix(news,trends): close SSRF sink, un-break News, stop caching failures
- fix(ops): derive base-image tag from the Dockerfile and pin Redis RESP2
- fix(maps): scope jobs to owners, bound browsers, stop faking success
- fix(deps): hold redis at 7.x, flag the stale base-image updater
- fix(core): close CORS/auth disclosure holes, cache corruption and pool leak
- test: add tests/conftest.py and centralise tool config in pyproject.toml
- ci: replace no-op pipeline with real test, boot and docker gates (CRT-6)
- fix(rate-limit): make the limiter actually enforce limits (CRT-8)
- fix(config,auth): boot from documented config; load the documented API key
- fix(deps): escape the starlette 0.50 security dead end, lock the closure, delete the phantom DB layer
- chore: track documentation, untrack generated corpora and scratch files
- feat(maps): add owner-scoped durable record store
- feat(security): add shared outbound URL validator for SSRF defence
- fix(security): stop secrets and signing keys reaching git and Docker contexts
- chore: bump version to v1.6.1
Images
rainmanjam/social-flood:2.0.0ghcr.io/rainmanjam/social-flood:2.0.0- Digest:
sha256:76cfc8f0aafbdfcfc20886acfa2c5290eb220aa7819e57fc195fe556a16dc4f5
Verify the signature
cosign verify \
--certificate-identity-regexp 'https://github.com/rainmanjam/social-flood/.github/workflows/release.yml@.*' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
ghcr.io/rainmanjam/social-flood@sha256:76cfc8f0aafbdfcfc20886acfa2c5290eb220aa7819e57fc195fe556a16dc4f5v1.5.1
0.2.0
What's Changed
- feat: Code improvements by @rainmanjam in #5
- fix: Update for production env. by @rainmanjam in #6
- Update version.py by @rainmanjam in #7
Full Changelog: 0.1.0...0.2.0
0.1.0
What's Changed
- Update main.py by @rainmanjam in #4
- Add scripts for Docker multi-architecture builds, Google Ads API token generation, version incrementing, image signing, and verification
- Implemented
docker_multiarch.shfor building and pushing multi-architecture Docker images using Buildx. - Created
generate_google_ads_refresh_token.pyto facilitate OAuth authentication and refresh token generation for Google Ads API. - Added
increment_version.pyto automate version number increments in theapp/__version__.pyfile following semantic versioning. - Introduced
sign_image.shfor signing Docker images with Cosign and creating attestations for supply chain security. - Developed
verify_attestations.shto verify Docker image signatures and attestations. - Implemented
update_base_image.shto check and update the base image in the Dockerfile. - Added tests for the
BaseRouterclass to ensure proper functionality and error handling. - Created output logs for test results to verify the correctness of the implemented features.
Full Changelog: 0.0.1...0.1.0
0.0.1
What's Changed
- fea: add Google Trends and YouTube Captions by @rainmanjam in #2
- feat: add google autocomplete for search by @rainmanjam in #3
New Contributors
- @rainmanjam made their first contribution in #2
Full Changelog: https://github.com/rainmanjam/social-flood/commits/0.0.1