Public relay that files Helia in-app feedback as Forgejo issues through a restricted bot account
  • Rust 87.3%
  • Python 12.7%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
laurence d90b344986
All checks were successful
ci / clippy (push) Successful in 49s
ci / fmt (push) Successful in 16s
ci / reuse (push) Successful in 2s
ci / musl (push) Successful in 59s
ci / deny (push) Successful in 1m43s
ci / test (push) Successful in 40s
Merge pull request 'push-image: send a User-Agent the forge accepts' (#6) from fix/push-image-ua into main
2026-10-19 21:19:56 +01:00
.forgejo/workflows Add the Helia feedback relay 2026-10-19 20:31:50 +01:00
deploy push-image: send a User-Agent the forge accepts 2026-10-19 21:15:27 +01:00
docs Match the app's protocol page: limits, error codes, issue title, ignored unknown members; add the labelling marker and CI.md 2026-10-19 21:03:40 +01:00
LICENSES Add the Helia feedback relay 2026-10-19 20:31:50 +01:00
scripts Match the app's protocol page: limits, error codes, issue title, ignored unknown members; add the labelling marker and CI.md 2026-10-19 21:03:40 +01:00
src Neutralise HTML and image embeds in submitted descriptions 2026-10-19 21:03:40 +01:00
tests Match the app's protocol page: limits, error codes, issue title, ignored unknown members; add the labelling marker and CI.md 2026-10-19 21:03:40 +01:00
.gitattributes Add the Helia feedback relay 2026-10-19 20:31:50 +01:00
.gitignore Match the app's protocol page: limits, error codes, issue title, ignored unknown members; add the labelling marker and CI.md 2026-10-19 21:03:40 +01:00
ADDITIONAL-TERMS.md Add the Helia feedback relay 2026-10-19 20:31:50 +01:00
Cargo.lock Update the lockfile past the time advisory 2026-10-19 20:38:47 +01:00
Cargo.toml Fix formatting, lints and the time advisory; probe the repository with the narrow token 2026-10-19 20:38:18 +01:00
CI.md Match the app's protocol page: limits, error codes, issue title, ignored unknown members; add the labelling marker and CI.md 2026-10-19 21:03:40 +01:00
deny.toml Add the Helia feedback relay 2026-10-19 20:31:50 +01:00
LICENSE Add the Helia feedback relay 2026-10-19 20:31:50 +01:00
README.md Match the app's protocol page: limits, error codes, issue title, ignored unknown members; add the labelling marker and CI.md 2026-10-19 21:03:40 +01:00
REUSE.toml Add the Helia feedback relay 2026-10-19 20:31:50 +01:00
rustfmt.toml Add the Helia feedback relay 2026-10-19 20:31:50 +01:00

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.