- Rust 87.3%
- Python 12.7%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| .forgejo/workflows | ||
| deploy | ||
| docs | ||
| LICENSES | ||
| scripts | ||
| src | ||
| tests | ||
| .gitattributes | ||
| .gitignore | ||
| ADDITIONAL-TERMS.md | ||
| Cargo.lock | ||
| Cargo.toml | ||
| CI.md | ||
| deny.toml | ||
| LICENSE | ||
| README.md | ||
| REUSE.toml | ||
| rustfmt.toml | ||
helia-feedback-relay
A small public service that files feedback from the Helia desktop app as an issue on laurence/helia.
Forgejo registration is closed (single sign-on only), so the public cannot open issues themselves. The app sends a report to this relay; the relay validates it, strips and annotates the screenshot, and files the issue through a restricted bot account (helia-feedback, issue scope only, no repository write).
Production deployment: https://feedback.discworld.casa. Licence: GPL-3.0-or-later with the additional terms in ADDITIONAL-TERMS.md.
Protocol
See docs/PROTOCOL.md.
Abuse controls
| Control | Default |
|---|---|
| Proof of work (SHA-256, leading zero bits), HMAC-signed single-use challenges, 10 minute life | 20 bits |
| Per-client rate limit (IPv4 address, or the /64 of an IPv6 one), sliding windows, memory only | 3 per hour, 10 per day |
| Global cap, with a metric and an alert when it trips | 50 per day |
| Body limits: screenshot 5 MiB, report part 64 KiB, whole request about 5.3 MiB (also enforced at the proxy) | |
| Strict validation: UTF-8, no control or bidirectional override characters, sizes, PNG signature, decoded dimensions (8192 per side, 16 megapixels), no SVG or other types | |
| Screenshot is fully decoded and re-encoded: EXIF, text, ICC and every other chunk are dropped | |
Spam heuristic (many URLs, spam words, repeated characters, shouting): filed with feedback/spam-suspect, never dropped |
|
Kill switch: FEEDBACK_KILL_SWITCH=1 returns 503 |
off |
The submitter's address is used only in memory for rate limiting. It is never written to the issue, the logs or the metrics.
Client authentication is pluggable
All client authentication is behind the ClientAuth trait in src/auth.rs. Proof of work is today's implementation. A mutual-TLS client-certificate mode can be added (or replace it) without changing the report format: the same endpoints with an auth-method header, or a /api/v2 path.
A key embedded in an open-source binary is extractable, so a baked-in key would identify "a Helia build", not a trusted sender. Real authentication needs per-install or per-user issued certificates (app-generated key, certificate signed after a sign-up step, revocable, rate-limited per certificate). Not built yet.
Configuration
Environment variables, all optional except the token file:
| Variable | Default | Meaning |
|---|---|---|
FEEDBACK_LISTEN |
0.0.0.0:8080 |
public API |
FEEDBACK_ADMIN_LISTEN |
0.0.0.0:9090 |
/healthz and /metrics, never routed publicly |
FEEDBACK_FORGEJO_URL |
https://git.discworld.casa |
|
FEEDBACK_REPO |
laurence/helia |
|
FEEDBACK_TOKEN_FILE |
/run/secrets/forgejo-token |
bot token, re-read on every call so rotation needs no restart |
FEEDBACK_KILL_SWITCH |
0 |
1 returns 503 |
FEEDBACK_POW_DIFFICULTY |
20 |
|
FEEDBACK_RATE_PER_HOUR, FEEDBACK_RATE_PER_DAY, FEEDBACK_GLOBAL_PER_DAY |
3, 10, 50 |
|
FEEDBACK_CLIENT_IP_HEADER |
CF-Connecting-IP |
set by the edge; empty uses the socket peer |
FEEDBACK_LOG |
info |
log filter (JSON logs) |
Build and test
cargo test
cargo build --release --target x86_64-unknown-linux-musl # CC_x86_64_unknown_linux_musl=gcc
The shipped image is FROM scratch plus the static binary. deploy/push-image.py assembles and pushes it without a Docker daemon and prints the digest the stack pins.
Metrics
GET /metrics on the admin port: helia_feedback_requests_total, helia_feedback_issues_created_total, helia_feedback_rejects_total{reason}, helia_feedback_rate_limit_trips_total{scope}, helia_feedback_forgejo_errors_total{op}, helia_feedback_spam_suspects_total, helia_feedback_forgejo_up, helia_feedback_global_remaining, helia_feedback_last_issue_timestamp_seconds, helia_feedback_kill_switch.