Skip to content

accumulated fixes and improvements from neodb#44

Merged
alphatownsman merged 2 commits into
avaraline:mainfrom
neodb-social:contribute-to-incarnator
Jul 25, 2026
Merged

accumulated fixes and improvements from neodb#44
alphatownsman merged 2 commits into
avaraline:mainfrom
neodb-social:contribute-to-incarnator

Conversation

@alphatownsman

@alphatownsman alphatownsman commented May 23, 2026

Copy link
Copy Markdown
Collaborator

Main fix is QuoteAuthorization which was incomplete in previous implementation.

ActivityPub compatibility

  • accept list-form attributedTo (WriteFreely blog Articles)
  • follow Link rel=alternate to resolve AP Article URLs
  • only accept URL-shaped quote values, validate length and scheme
  • give identity Update/Delete/Move activities unique IDs (deduplication)
  • serve QuoteAuthorization at a dereferenceable URL (FEP-044f)
  • treat image-only Notes as Posts instead of Interactions
  • skip emoji tag ingestion when author has no domain
  • accept Article posts without local Article row (Post-only renderer)
  • coalesce list-typed poll option name to string (pl.fediverse.pl)
  • handle vote-type in PostInteraction undo to prevent ValueError
  • handle UnicodeDecodeError when parsing nodeinfo well-known response
  • ignore Undo Block when block does not exist locally
  • treat 410 Gone as final on POST delivery (no retry)
  • validate URI scheme before fetching posts and replies
  • return 400 for unparseable HTTP signatures
  • return 404 for non-numeric IDs in API pk lookups
  • use ActivityPubError subtypes for invalid inbound activities
  • polls (Question): federated tally Updates, close at expiry, hidden totals, API create/edit/vote, web voting UI
  • Page/Image/Audio/Video/Event convert to statuses, full AS object kept in type_data, icon exposed as preview card
  • AP 7.1.2 reply forwarding, LD-signed fan-out, group-announced Create/Update/Delete resolved against origin (Lemmy interop)
  • accept JSON-LD list-valued "type" on actors, inbox routing, search and posts
  • Article web view: cover image and linked hashtags

Security

  • add SSRF protection to all outbound HTTP requests (core.files)
  • enforce CSRF verification for session-authenticated API requests
  • harden OAuth token exchange against error leak and timing attack
  • force safe Content-Type on media proxy responses
  • use CodeQL-recognized sanitizers for url-redirection
  • fix XXE in host-meta parsing and weak PRNG for tokens
  • fix LD signature creator-actor binding
  • fix inbox signature verification
  • address several CodeQL code-scanning alerts

Performance

  • avoid OR-subquery in notification queries, add partial index
  • match bell-follows by target_id, not Follow id
  • collapse Hashtag stator stats into 2 queries
  • use timestamp ranges in Hashtag stat filters (avoid per-row casts)
  • avoid slow OR query in quote post URL lookup
  • bulk_create fan-outs, skip_locked task claiming, case-insensitive functional indexes for local handle lookups

Robustness

  • translate ClearTimeline deadlocks to TryAgainLater so Stator reschedules
  • less noisy logging for DNS errors
  • use utf-8 encoding for URL hashing in ProxyAbsoluteUrl
  • OAuth: single-use authorization codes, urlencoded redirect params, 400 on missing redirect_uri, non-ASCII credentials no longer 500
  • shape Application redirect_uri/uris (verify_credentials failed validation for every app)
  • follow counts track follow state; 4xx on Follow delivery classified instead of retried for a day
  • resolve bare mentions deterministically; fix FieldError on bare-domain handle search
  • recalculate reply counts when a reply is deleted; empty metadata value no longer drops the remaining fields
  • preview card and media proxy stream with byte caps; media proxy regains its SSRF-safe client and image-only Content-Type
  • default avatar resolves via static() instead of a path that 404s, and is federated
  • refresh_question_from_remote only accepts the document living at the requested URI

Python 3.14

  • requires-python >=3.14
  • blurhash-python replaced by blurhash-rs (no wheels past cp312); cryptography/pillow/psycopg floors raised

@alphatownsman
alphatownsman force-pushed the contribute-to-incarnator branch from abdec60 to 2cd26de Compare May 23, 2026 16:37
Aggregated, NeoDB-agnostic changes accumulated on neodb-social/neodb-incarnator
since upstream/main diverged. Reviewable as a single squash; happy to split per
maintainer preference.

ActivityPub compatibility
- accept list-form attributedTo (WriteFreely blog Articles)
- follow Link rel=alternate to resolve AP Article URLs
- only accept URL-shaped quote values (validate scheme and length)
- give identity Update/Delete/Move activities unique IDs (deduplication)
- serve QuoteAuthorization at a dereferenceable URL (FEP-044f):
  new QuoteAuthorization model + view + migration
- treat image-only Notes as Posts instead of Interactions
- skip emoji tag ingestion when author has no domain
- handle vote-type in PostInteraction undo to prevent ValueError
- validate URI scheme before fetching posts and replies
- return 400 (VerificationFormatError) for unparseable HTTP signatures
- use ActivityPubError subtypes for invalid inbound activities
  (ActorMismatchError / ActivityPubFormatError instead of ValueError)
- coalesce list-typed poll option name to string (pl.fediverse.pl)
- expose federation.enabled in nodeinfo metadata (FEP-0151)

Security
- add SSRF protection to all outbound HTTP requests
  (core.files.check_url_safety + SSRFAttemptError, attached via httpx
  event_hooks so redirect hops are also checked)
- enforce CSRF verification for session-authenticated API requests
- harden OAuth token exchange against error leak and timing attack
  (constant-time compare for client_id/secret, single-shot code lookup)
- use safer URL-redirection sanitizers (_safe_remote_redirect) so CodeQL
  recognises the sanitiser pattern
- treat DNS resolution failures as httpx.ConnectError instead of
  SSRFAttemptError so logs are less noisy

Performance
- partial index on TimelineEvent(identity, -id) WHERE NOT dismissed; bell
  follows matched by target_id, not Follow id; avoid OR-subquery in
  notification queries
- collapse Hashtag stator stats into 2 queries
- use timestamp ranges in Hashtag stat filters (avoid per-row
  AT TIME ZONE / EXTRACT casts)

Robustness
- translate ClearTimeline deadlocks to TryAgainLater so Stator silently
  reschedules instead of logging OperationalError
- bump sentry-sdk

Article rendering
- preserve full AS Article on inbound by storing type_data = {"object": data}
  so name/summary/source survive (the old ArticleData parse with
  extra="ignore" dropped them)
- add templates/activities/_type_article.html so Article posts render as
  title + body instead of falling through to the unknown-type path

Tests
- QuoteAuthorization, list-form attributedTo, search URL with
  Link rel=alternate fallback, Article type_data shape, ClearTimeline
  deadlock translation, json edge cases, identity RSS feed (with quote link)
@alphatownsman
alphatownsman force-pushed the contribute-to-incarnator branch from 2cd26de to 3068808 Compare May 23, 2026 16:44
Brings across the NeoDB-agnostic work that accumulated in
neodb-social/neodb's vendored takahe fork since it was de-submoduled
(590c9e7, 2026-05-21) up to main @ 53011a71 (2026-07-24), then moves the
project to Python 3.14.

Features
- Polls (Question), full support: a new question_open post state that
  federates throttled tally Updates and closes the poll at expiry;
  closed/hide_totals/effective_end_time on QuestionData; vote validation
  (own-poll, out-of-range, additive multiple-choice, row locking);
  poll create/edit through the client API with Mastodon's limits;
  /api/v1/polls visibility plus stale-tally refetch; end-of-poll
  notifications; interactive voting on the server-rendered post page.
- Converted ActivityStreams object types: Page, Image, Audio, Video and
  Event become ordinary statuses while the complete AS object is retained
  in type_data, with the object icon exposed as a preview card.
- Federation content spread and Lemmy interop: LD-signed fan-out for
  public posts, FanOut.forward plus inbox raw_document capture for AP
  7.1.2 reply forwarding, group-announced Create/Update/Delete resolved
  against the origin server, refresh_from_origin, idempotent boosts.
- JSON-LD list-valued "type" handled for actors, inbox routing, search
  and posts (get_first_concrete_type).
- Article web view: cover image and linked hashtags.

Fixes
- OAuth: authorization codes are single-use under concurrency, redirect
  params are urlencoded (state could inject query parameters), a missing
  redirect_uri returns 400, and non-ASCII client credentials now fail
  as access_denied instead of raising TypeError out of compare_digest.
- Application redirect_uri/uris are shaped correctly; verify_credentials
  previously raised a validation error for every app.
- Follow counts track follow state, and a 4xx on Follow delivery is
  classified instead of retried for a day.
- Bare mentions resolve deterministically.
- Identity handle search no longer raises FieldError on a bare domain.
- Empty profile metadata values no longer drop the remaining fields.
- Reply counts are recalculated when a reply is deleted.
- Preview card fetches stream with a byte cap and clamp to column widths.
- Media proxy regains the SSRF-safe client and image Content-Type
  allowlist it had lost, streams with a byte cap, and falls back to the
  default avatar instead of surfacing a broken image.
- The default avatar is served from a path this project actually
  publishes: resolved via static() rather than the /s/ prefix, which is
  an alias in NeoDB's nginx and 404s here. It is also federated now, so
  the wrong URL would have been advertised fediverse-wide.
- refresh_question_from_remote only accepts the document that lives at
  the requested URI; without that check a host we hold a poll from could
  return another server's object id and rewrite our copy of that post.
- A Question whose type_data is missing or the wrong shape no longer
  raises out of the poll template tag, which would 500 every page the
  post appears on.

Performance
- bulk_create fan-outs, skip_locked task claiming, case-insensitive
  functional indexes for local handle lookups, and a stale
  config_identity cache is dropped after a write.

Python 3.14
- requires-python >=3.14, .python-version, Dockerfile base image, CI
  matrix and pre-commit default_language_version all move to 3.14.
- blurhash-python (no wheels past cp312) is replaced by blurhash-rs;
  cryptography, pillow and psycopg floors raised for cp314 wheels.
  uv.lock re-resolved without --upgrade, so only those change.
- ruff applies PEP 758 unparenthesized except groups across 18 files.

Tests
- Ports the upstream suite for the new behaviour (poll lifecycle,
  announced activities, inbox forwarding, converted types, fan-out
  signing, poll voting, OAuth, notifications, media proxy) and adds
  regression tests for the two locally-found bugs above.
- tests/conftest.py: @pytest.mark.django_db on a fixture does nothing;
  the fixtures now take the db fixture, and the warning filter that hid
  this is removed.
- tests/api/notifications.py was never collected; renamed to
  test_notifications.py.

470 tests pass on 3.14.3, pre-commit is clean, migrations apply from
scratch and makemigrations --check reports no drift. NeoDB-specific code
(ext_neodb, takahe.neodb, NEODB_MQ wiring, Sentry instrumentation) is
deliberately excluded.
@alphatownsman
alphatownsman merged commit 3cf932b into avaraline:main Jul 25, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant