Skip to content

Commit 45b856c

Browse files
dahliaclaude
andcommitted
FEP-fe34 origin-based security model
Implements comprehensive origin-based security checks following FEP-fe34 specification to prevent content spoofing attacks and maintain secure federation practices. Key changes: - Add crossOrigin option to property accessors and lookupObject() with three modes: "ignore" (default), "throw", and "trust" - Implement trust tracking system for embedded objects using trust sets - Add origin validation for object @id vs document URL in lookupObject() - Add origin validation for property objects vs their parent object - Update documentation with security model explanations and examples - Add comprehensive tests for all cross-origin scenarios This replaces the previous FEP-c7d3 ownership model with the more robust origin-based approach, ensuring objects and their properties respect origin boundaries to prevent malicious content spoofing. See also http://w3id.org/fep/fe34 Fixes #440 Co-Authored-By: Claude <noreply@anthropic.com>
1 parent a7db279 commit 45b856c

12 files changed

Lines changed: 11030 additions & 1459 deletions

File tree

CHANGES.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,27 @@ To be released.
1010

1111
### @fedify/fedify
1212

13+
- Implemented [FEP-fe34] origin-based security model to protect against
14+
content spoofing attacks and ensure secure federation practices. The
15+
security model enforces same-origin policy for ActivityPub objects and
16+
their properties, preventing malicious actors from impersonating content
17+
from other servers. [[#440]]
18+
19+
- Added `crossOrigin` option to Activity Vocabulary property accessors
20+
(`get*()` methods) with three security levels: `"ignore"` (default,
21+
logs warning and returns `null`), `"throw"` (throws error), and
22+
`"trust"` (bypasses checks).
23+
- Added `LookupObjectOptions.crossOrigin` option to `lookupObject()`
24+
function and `Context.lookupObject()` method for controlling
25+
cross-origin validation.
26+
- Embedded objects are now validated against their parent object's origin
27+
and only trusted when they share the same origin or are explicitly
28+
marked as trusted.
29+
- Property hydration now respects origin-based security, automatically
30+
performing remote fetches when embedded objects have different origins.
31+
- Internal trust tracking system maintains security context throughout
32+
object lifecycles (construction, cloning, and property access).
33+
1334
- Fixed handling of ActivityPub objects containing relative URLs. The
1435
Activity Vocabulary classes now automatically resolve relative URLs by
1536
inferring the base URL from the object's `@id` or document URL, eliminating
@@ -73,6 +94,7 @@ To be released.
7394
Node.js's `--experimental-require-module` flag and resolves dual package
7495
hazard issues. [[#429], [#431]]
7596

97+
[FEP-fe34]: https://w3id.org/fep/fe34
7698
[FEP-5711]: https://w3id.org/fep/5711
7799
[OStatus 1.0 Draft 2]: https://www.w3.org/community/ostatus/wiki/images/9/93/OStatus_1.0_Draft_2.pdf
78100
[RFC 7033 Section 4.4.4.3]: https://datatracker.ietf.org/doc/html/rfc7033#section-4.4.4.3
@@ -88,6 +110,7 @@ To be released.
88110
[#411]: https://github.com/fedify-dev/fedify/issues/411
89111
[#429]: https://github.com/fedify-dev/fedify/issues/429
90112
[#431]: https://github.com/fedify-dev/fedify/pull/431
113+
[#440]: https://github.com/fedify-dev/fedify/issues/440
91114
[#443]: https://github.com/fedify-dev/fedify/pull/443
92115

93116
### @fedify/cli

FEDERATION.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ Supported FEPs
3131
- [FEP-8b32][]: Object Integrity Proofs
3232
- [FEP-521a][]: Representing actor's public keys
3333
- [FEP-5feb][]: Search indexing consent for actors
34-
- [FEP-c7d3][]: Ownership
34+
- [FEP-fe34][]: Origin-based security model
3535
- [FEP-c0e0][]: Emoji reactions
3636
- [FEP-e232][]: Object Links
3737

@@ -42,7 +42,7 @@ Supported FEPs
4242
[FEP-8b32]: https://w3id.org/fep/8b32
4343
[FEP-521a]: https://w3id.org/fep/521a
4444
[FEP-5feb]: https://w3id.org/fep/5feb
45-
[FEP-c7d3]: https://w3id.org/fep/c7d3
45+
[FEP-fe34]: https://w3id.org/fep/fe34
4646
[FEP-c0e0]: https://w3id.org/fep/c0e0
4747
[FEP-e232]: https://w3id.org/fep/e232
4848

docs/manual/context.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,44 @@ const note = await ctx.lookupObject(
412412
> `DocumentLoader`*](#getting-an-authenticated-documentloader)
413413
> section for details.
414414
415+
> [!CAUTION]
416+
> For security reasons, the `~Context.lookupObject()` method implements
417+
> origin-based validation following [FEP-fe34]. If the fetched JSON-LD
418+
> document contains an `@id` that has a different origin than the requested
419+
> URL, the method will return `null` by default to prevent content spoofing
420+
> attacks.
421+
>
422+
> For example, if you request `https://example.com/notes/123` but the fetched
423+
> document has `@id: "https://malicious.com/notes/456"`, the method will
424+
> refuse to return the object and log a warning instead.
425+
>
426+
> You can control this behavior using the `crossOrigin` option:
427+
>
428+
> ~~~~ typescript twoslash
429+
> import { type Context } from "@fedify/fedify";
430+
> const ctx = null as unknown as Context<void>;
431+
> // ---cut-before---
432+
> // Default behavior: return null for cross-origin objects (recommended)
433+
> const objectDefault = await ctx.lookupObject("https://example.com/notes/123");
434+
>
435+
> // Throw an error when encountering cross-origin objects
436+
> const objectStrict = await ctx.lookupObject(
437+
> "https://example.com/notes/123",
438+
> { crossOrigin: "throw" }
439+
> );
440+
>
441+
> // Bypass origin checks (not recommended, potential security risk)
442+
> const objectBypass = await ctx.lookupObject(
443+
> "https://example.com/notes/123",
444+
> { crossOrigin: "trust" }
445+
> );
446+
> ~~~~
447+
>
448+
> Only use `crossOrigin: "trust"` if you fully understand the security
449+
> implications and have implemented additional validation measures.
450+
451+
[FEP-fe34]: https://w3id.org/fep/fe34
452+
415453
416454
WebFinger lookups
417455
-----------------

docs/manual/vocab.md

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,94 @@ corresponding TypeScript types:
495495
[`CryptoKey`]: https://developer.mozilla.org/en-US/docs/Web/API/CryptoKey
496496

497497

498+
Origin-based security model
499+
---------------------------
500+
501+
*This section is applicable since Fedify 1.9.0.*
502+
503+
Fedify implements an origin-based security model following [FEP-fe34] to protect
504+
against content spoofing attacks and maintain secure federation practices.
505+
This security model ensures that objects and their properties respect origin
506+
boundaries, preventing malicious actors from impersonating content from other
507+
servers.
508+
509+
[FEP-fe34]: https://w3id.org/fep/fe34
510+
511+
### Same-origin policy for properties
512+
513+
When accessing properties of ActivityPub objects, Fedify enforces same-origin
514+
policy rules. Even if an object appears to be embedded in the JSON-LD
515+
representation, property accessors will automatically perform hydration (remote
516+
fetching) if the embedded object's `@id` has a different origin than its parent
517+
object.
518+
519+
For example, consider this JSON-LD representation:
520+
521+
~~~~ json
522+
{
523+
"@context": "https://www.w3.org/ns/activitystreams",
524+
"type": "Create",
525+
"id": "https://example.com/activities/123",
526+
"actor": "https://example.com/users/alice",
527+
"object": {
528+
"type": "Note",
529+
"id": "https://different-origin.com/notes/456",
530+
"content": "This is from a different origin"
531+
}
532+
}
533+
~~~~
534+
535+
In this case, when you access the `object` property of the `Create` activity,
536+
Fedify will not trust the embedded `Note` object because its `@id` has a
537+
different origin (`different-origin.com`) than the parent activity's origin
538+
(`example.com`). Instead, it will fetch the `Note` object directly from
539+
`https://different-origin.com/notes/456` to verify its authenticity.
540+
541+
### Controlling origin checks
542+
543+
You can control this behavior using the `crossOrigin` option when calling
544+
property accessors:
545+
546+
~~~~ typescript twoslash
547+
import { Create } from "@fedify/fedify";
548+
const create = {} as unknown as Create;
549+
// ---cut-before---
550+
// Default behavior: ignore untrusted embedded objects (recommended)
551+
const objectDefault = await create.getObject();
552+
553+
// Throw an error when encountering cross-origin objects
554+
const objectStrict = await create.getObject({ crossOrigin: "throw" });
555+
556+
// Bypass origin checks (not recommended, potential security risk)
557+
const objectBypass = await create.getObject({ crossOrigin: "trust" });
558+
~~~~
559+
560+
The `crossOrigin` option accepts the following values:
561+
562+
`"ignore"` (default)
563+
: Ignore untrusted embedded objects and fetch from origin
564+
565+
`"throw"`
566+
: Throw an error when encountering cross-origin embedded objects
567+
568+
`"trust"`
569+
: Trust embedded objects regardless of origin (⚠️ security risk)
570+
571+
> [!WARNING]
572+
> Using `crossOrigin: "trust"` can expose your application to security
573+
> vulnerabilities, including content spoofing attacks. Only use this option
574+
> if you fully understand the security implications and have implemented
575+
> additional validation measures.
576+
577+
### Trust tracking
578+
579+
Internally, Fedify maintains trust information for each property value. Objects
580+
that are constructed locally, fetched directly from their authoritative source,
581+
or explicitly validated are marked as trusted. This trust information is used
582+
to determine whether property accessors need to perform additional validation
583+
or fetching.
584+
585+
498586
Extending the vocabulary
499587
------------------------
500588

0 commit comments

Comments
 (0)