fix self-federation (#1044)

This commit is contained in:
slothful-vassal
2026-06-04 10:52:15 +02:00
committed by GitHub
parent 7ad129b841
commit 8153cfe14e
3 changed files with 109 additions and 8 deletions

View File

@@ -49,6 +49,12 @@ func RemoteListGet(e *core.RequestEvent) error {
}
return e.InternalServerError("Sync failed", err)
}
if record.Id == "" {
// Local content that does not exist: performFullListSync
// short-circuits local IRIs and returns the unsaved shell —
// surface a real 404 instead of access/expand on a missing record.
return e.NotFoundError("List not found", nil)
}
} else {
updatedAt := record.GetDateTime("updated").Time()
@@ -111,11 +117,24 @@ func findLocalListByRemoteInfo(e *core.RequestEvent, ctx context.Context, handle
}
func performFullListSync(app core.App, ctx context.Context, reqURL *url.URL, localList *core.Record) (*core.Record, error) {
client := util.SafeHTTPClient()
iri := localList.GetString("iri")
// Never federate with ourselves (see performFullSync in remote_trail.go).
if iri == "" || util.IsLocalIRI(iri) {
if localList.GetBool("needs_full_sync") {
localList.Set("needs_full_sync", false)
if err := app.Save(localList); err != nil {
return localList, err
}
}
return localList, nil
}
client := util.SafeHTTPClient()
remoteUrl, _ := url.Parse(iri)
remoteUrl.RawQuery = reqURL.RawQuery
query := reqURL.Query()
query.Del("handle")
remoteUrl.RawQuery = query.Encode()
origin := fmt.Sprintf("%s://%s", remoteUrl.Scheme, remoteUrl.Host)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, remoteUrl.String(), nil)
@@ -124,10 +143,13 @@ func performFullListSync(app core.App, ctx context.Context, reqURL *url.URL, loc
}
res, err := client.Do(req)
if err != nil || res.StatusCode != 200 {
if err != nil {
return localList, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return localList, fmt.Errorf("remote list fetch %s returned: %d", remoteUrl.String(), res.StatusCode)
}
var remoteMap map[string]any
if err := json.NewDecoder(res.Body).Decode(&remoteMap); err != nil {

View File

@@ -79,6 +79,13 @@ func RemoteTrailGet(e *core.RequestEvent) error {
}
return e.InternalServerError("Sync failed", err)
}
if record.Id == "" {
// Local content that does not exist (e.g. a stale URL to a
// missing local trail): performFullSync short-circuits local
// IRIs and returns the unsaved shell — surface a real 404
// instead of running access/expand on a non-existent record.
return e.NotFoundError("Trail not found", nil)
}
} else {
// We already have it locally. Show and update background.
updatedAt := record.GetDateTime("updated").Time()
@@ -146,19 +153,38 @@ func findLocalTrailByRemoteInfo(e *core.RequestEvent, ctx context.Context, handl
// --- Core Sync Logic ---
func performFullSync(app core.App, ctx context.Context, reqURL *url.URL, localTrail *core.Record) (*core.Record, error) {
client := util.SafeHTTPClient()
iri := localTrail.GetString("iri")
// Never federate with ourselves: a trail whose IRI is empty or points back
// to this instance is local content (we are the source of truth). Syncing it
// would fetch our own origin (or fail on an empty URL); just clear the stale
// flag so the record is no longer stuck in a permanent re-sync loop.
if iri == "" || util.IsLocalIRI(iri) {
if localTrail.GetBool("needs_full_sync") {
localTrail.Set("needs_full_sync", false)
if err := app.Save(localTrail); err != nil {
return localTrail, err
}
}
return localTrail, nil
}
client := util.SafeHTTPClient()
remoteUrl, _ := url.Parse(iri)
remoteUrl.RawQuery = reqURL.RawQuery // Forward params
query := reqURL.Query()
query.Del("handle")
remoteUrl.RawQuery = query.Encode()
origin := fmt.Sprintf("%s://%s", remoteUrl.Scheme, remoteUrl.Host)
req, _ := http.NewRequestWithContext(ctx, "GET", remoteUrl.String(), nil)
res, err := client.Do(req)
if err != nil || res.StatusCode != 200 {
if err != nil {
return localTrail, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return localTrail, fmt.Errorf("remote trail fetch %s returned: %d", remoteUrl.String(), res.StatusCode)
}
var remoteMap map[string]any
if err := json.NewDecoder(res.Body).Decode(&remoteMap); err != nil {

View File

@@ -111,6 +111,28 @@ func generateKeyPair() (*rsa.PrivateKey, *rsa.PublicKey, error) {
return priv, pub, nil
}
// IsLocalIRI reports whether iri belongs to this instance's own ORIGIN.
// It is used to prevent the instance from federating with itself, i.e. treating
// its own content as if it were remote.
func IsLocalIRI(iri string) bool {
if iri == "" {
return false
}
origin := os.Getenv("ORIGIN")
if origin == "" {
return false
}
o, err := url.Parse(origin)
if err != nil {
return false
}
u, err := url.Parse(iri)
if err != nil {
return false
}
return strings.EqualFold(u.Host, o.Host)
}
func TrailFromActivity(activity pub.Activity, app core.App, actor *core.Record) (*core.Record, error) {
t, err := pub.ToObject(activity.Object)
if err != nil {
@@ -118,6 +140,24 @@ func TrailFromActivity(activity pub.Activity, app core.App, actor *core.Record)
}
iri := t.ID.String()
// Own content must never be ingested as if it were remote. An inbound
// activity referencing one of our own trails (e.g. an announce echoed back)
// would otherwise flag the local trail for a full sync and later make the
// instance fetch itself. Only a local actor may resolve a local object id to
// the local record; a remote actor must not be able to reference or attach
// side effects (feeds/shares/notifications) to local content by id.
if IsLocalIRI(iri) {
if !actor.GetBool("isLocal") {
return nil, fmt.Errorf("refusing remote activity referencing local trail %q", iri)
}
trailUrl, parseErr := url.Parse(iri)
if parseErr != nil {
return nil, parseErr
}
return app.FindRecordById("trails", path.Base(trailUrl.Path))
}
var record *core.Record
if actor.GetBool(("isLocal")) {
trailUrl, parseErr := url.Parse(iri)
@@ -409,6 +449,19 @@ func ListFromActivity(activity pub.Activity, app core.App, actor *core.Record) (
}
iri := l.ID.String()
// Own content must never be ingested as if it were remote (see TrailFromActivity).
if IsLocalIRI(iri) {
if !actor.GetBool("isLocal") {
return nil, fmt.Errorf("refusing remote activity referencing local list %q", iri)
}
listURL, parseErr := url.Parse(iri)
if parseErr != nil {
return nil, parseErr
}
return app.FindRecordById("lists", path.Base(listURL.Path))
}
var record *core.Record
if actor.GetBool(("isLocal")) {
listURL, parseErr := url.Parse(iri)