solid / specification

Solid Technical Reports
https://solidproject.org/TR/
MIT License
491 stars 46 forks source link

Discuss returning 404 for privacy reasons #14

Open RubenVerborgh opened 5 years ago

csarven commented 5 years ago

Some thoughts:

I like the 404 here.

Would it be useful to make sure that the 404 response MUST be accompanied with an expiration time for caches?

If so, related concern: if implementations only include freshness information for the purpose of hiding, then the evilApp could infer that there is a resource being hidden even while they can't access it. So, then MUST all 404s be always accompanied with an expiration time so as to not leak that information?

Note that if 404 with SHOULD or MAY for expiration time, it increases the uncertainty of a resource existing for the evilApp to infer.

humont commented 5 years ago

Hi, new to this community but have some thoughts here:

My impression is that the active use case for 404 in Solid is to check whether a username is taken - which can be done at root level (and return a 404).

Any requests in deeper directories could return a 403 Forbidden if the ACL's don't allow public read. The 403 can also be made default regardless of whether or not the resource/path is valid.

This would prevent inference of resource existence, but avoids using 404 when a resource may in actual fact exist (obfuscation).

humont commented 5 years ago

But such obfuscation is explicitly allowed by the spec (see above); and a constant 403 is no less an obfuscation. So at the moment, I am still leaning toward 404 in case of privacy reasons, if the pod user so desires. On the other hand, a 403 makes it easier, since no such preference should be stated then, and it hints at authenticating (whereas a 404 does not).

There is ambiguity as to which should be used, that's for sure. After browsing around on Stack Overflow and various blog posts, the common wisdom seems to indicate that people assume a 403 implies the existence but no permission (despite this not being in line with the actual definition of 403). So theres that...

My preference (for what it's worth) is a semantic one: 403 = "you're not allowed to ask this question" wheras: 404 = "you're question is allowed, but i won't tell you the answer".

as far as obfuscation go, 404 seems to be more fit for purpose as it provides some confusion as it leaves the question of "did i not find the resource in general, or did i not find it just for you" ?

dmitrizagidulin commented 5 years ago

I would also vote for the '404 by default, if unauthorized or not found' option. (All social media platforms, from LiveJournal to GitHub (private repos) to Facebook, take this approach.)

That said, I do think that it would be useful to add a Web Access Control term (something like wac:allowRequestPermission) that signals a 403 instead of a 404 and allows users to ask for permission.

csarven commented 5 years ago

@dmitrizagidulin Isn't a given that the requesting agent can come forward with credentials on any resource? If I'm interpreting correctly, I think signalling allowRequestPermission along with a 404 works contrary to obfuscating whether a resource actually exists/reachable or not.

csarven commented 5 years ago

I would only ask how much would the 404 approach will cause the applications lose out on considering the option to authenticate (if not already) and re-request.

So, re 404, I think it'd be useful to cover both ends in the Solid spec. The 404 in RFC:

or is not willing to disclose that one exists.

can be supported/clarified with by adding the following:

"The client MAY repeat the request with new or different credentials." -- repurposing the text from 403.

Aside: I wonder if this was already considered in RFC7231 and what was the rationale to omit.

TallTed commented 5 years ago

Speaking as a user, I hate 404 when I'm unauthorized because my login has expired or the like, because it often enough means I go in loops of "where'd that thing I know was here go?" Yes, the RFC says it's OK to do this, but the 403 response seems better to me -- because whether or not the thing does exist, it shows that there might be a change with different authentication, while 404 suggests that authentication doesn't matter.

pmcb55 commented 5 years ago

I agree with @ajs6f in the Trellis issue referenced by @acoburn above (https://github.com/trellis-ldp/trellis/issues/454). Basically any conversion of 403 to 404 should be done at the outermost layer of the architecture (and not in the internals, to @ajs6f's point). But I'd like to see that conversion be controllable/overridable on a per-resource basis (even though an administrator may also provide a default 'conversion setting' for all resources served by an entire Solid server), and controllable by the user themselves (i.e. it's just another piece of resource meta-data that users can set explicitly).

csarven commented 5 years ago

Punting the responsibility/decision making to the "outermost layer of the architecture" certainly sounds reasonable, if and only if, there is an "outermost layer" to speak of. Certainly we expect a Solid server to be self-contained (ie. working without any dependency or knowledge of the outer layer) to the point that it has some opinion on the primary UC, whether that's realised via 403 or 404 out of the box, or even configurable. Put differently, if we do acknowledge the UC, the Solid spec(s) should probably say something about it at the very least for the "internal architecture" so that it is prescribed and have tests. Anything pertaining to the "outermost layer" may be out of Solid spec's scope or at most be only descriptive (as opposed to prescriptive) in the end.

pmcb55 commented 5 years ago

@csarven I'm not sure I follow really. For me the 'outermost' layer of a Web server is pretty easy to define and configure - it's just a JAX-RS filter, and/or the last (or first) processor in a Camel route. In fact I'd see it as a classic example of the filter pattern, i.e. filtering a response to set it's status code to either 403 or 404 based on the context of the request itself and the configuration of the server. If by UC you mean Use-Case, I certainly wouldn't see that being defined by the Solid server or spec, instead it's defined by the context of the request (i.e. the preferences of the particular user) and the configuration of the server (i.e. the preferences of the Pod provider). So from a spec perspective I'd say the server CAN set a 403 or a 404, but that that SHOULD be override-able by user preferences set per resource.

TallTed commented 4 years ago

Having let this gel for a while, it occurs to me that (depending on the usage scenario) different responses may be appropriate for the same resource depending on whether the user is known or unknown, and on specifics of a known user. That is, it may be appropriate to return 404 to unauthenticated users, and 403 to (some or all) authenticated users.

kjetilk commented 4 years ago

To me, this sounds like an implementation issue that the Solid spec does not need to address, or might address in a best practices documentation. The "404 for privacy" is already there in RFC7231, and implementors may want to heed that advice if they are concerned with privacy, which most will want to be, and if so, they have many ways to achieve it, as @pmcb55 says, it is a filter pattern thing.

Even if many will want it so, I fail to see the value of turning it into a stronger normative feature of Solid.

dmitrizagidulin commented 4 years ago

Not sure I agree. Privacy is a core issue to Solid. And different servers handling this behavior differently might become a source of confusion.

csarven commented 4 years ago

I suggest that in order to reach consensus we should dive deeper with a different perspective.

Pat, whether clients can (in)directly influence responses as such seems to be a new feature. If it is sensible for a client to dictate that, at the very least, we need the notion of "hidden" and how to set/undo for a resource. So, lets defer to another issue for that.

Ted suggests a reasonable default. We should look at the specifics of both authn/z.

Kjetil, I agree that leaving things as is would be sufficient but it is not particularly easy to control in our environment without making the bridge that I think Dmitri is after.

Dmitri, let's investigate further and see if we can bubble anything up to a default or a recommendation. To be a bit more grounded, can resolving https://github.com/solid/specification/issues/116 (and other issues at that level) reveal anything useful? For example, if a user is unauthorized to read a resource, does it make sense to hide all references to that resource eg. in container listing. Intuitively, yes, but worth to consider the design any way. What something like that tells me is that we could specify a few specific scenarios considering privacy instead of a catch all. So, bubbling that up may mean that 404 is a reasonable default for such scenarios - which is already hinted in RFC. But where does that leave 403? Only for Write?

Do you find something along these lines to helpful:

"The client MAY repeat the request with new or different credentials." -- repurposing the text from 403.

What kind of a recommendation or non normative text do you think will help?

Can we come up with more scenarios?

kjetilk commented 4 years ago

Following the principle of least confusion, I'm still not sure where the least confusion is. I tend to side with @TallTed 's first comment, that a 404 is confusing if the user next logs in and finds that the resource exists anyway. Also, I'm then more confused by your second comment, @TallTed , because surely, if a resource exists and the user is unauthenticated, the only appropriate response is 401? I.e., they need to have a chance to authenticate before being told that a resource that exists can't be accessed by them?

That leaves a pretty small group that this would be a relevant response for, a class of users that are authenticated, but not trusted enough to even tell them that a whether a resource exists. That could certainly be acl:AuthenticatedAgent users, as #32 is relevant in this situation. Then, language around retrying with different credentials might make sense, but 404 is a fairly catch-all error, I don't think it makes sense to overload it too much. Given this, it still doesn't seem to me that it is a big issue in Solid.

Moreover, given that there are low-impact ways to do this, I wonder if the first milestone is appropriate, I think we should bump this issue to the June milestone.

TallTed commented 4 years ago

@kjetilk — I think the sum of my comments is "there's no single answer, appropriate to all deployments." Also that, unfortunately, RFC blurs the line between authentication and authorization.

The above is not meant to be an exhaustive list of scenarios, but I think it covers most.

kjetilk commented 4 years ago

Thanks a lot, @TallTed , that was a good clarifying comment!

It seems the situation that we're in the worst situation to deal with is the situation where the user is authenticated, but they should try to authenticate with different credentials.

I do think we should bump this to the June milestone though, it neither has the importance, urgency nor the manpower to be solved in a month.

ericprud commented 4 years ago

Novell gave the 401/404 choice to ACL Controllers with a permission called "File Scan". It was not paired with a principals so you couldn't say that group X sees a 401 while everyone else sees a 404.

Overall, I feel like they had a pretty well-thought-out mapping of rights to actions which narrowed the vulnerability of someone deleting and replacing a file just to acquire ACLs control over it.

csarven commented 4 years ago

:bell: Edit: Tables in this comment are a WIP - fixing errors and including consensus as they come up - Majority of this information is already in the spec or can be deduced. Remaining bits will be reviewed and transitioned to spec.


Taking what's discussed above into account, below is a way to reconcile this issue - using some background from https://github.com/solid/specification/issues/116

The order of status codes with least information leakage: 401, 403, 404, 409 based on the following:

Solid's notion of hierarchical containment is loosely coupled with resource-based access control. Resources can be observable or discoverable - "knowable" - by agents having Read access privilege either on the resource or its container (inherited). Some auxiliary resources are discoverable by agents having Read or Control access privilege on the subject resource.

The existence of a resource may be unknowable in that a 403 neither implies that a resource exists or does not exist. When an agent is forbidden to allocate a URI to a resource, 403 is used.

When an agent has Read access to C/ or C/R, the existence of C/R can be known.

When an agent has Read access to C/R, the state of C/R can be known.

Then, 404 and 409 indicate that an agent is authorized to know if a resource exists or its state can be read.

If credentials are not required, 401 doesn't apply, and 403 isn't particularly useful. Then, 2xx or 4xx (besides 401, 403) can be used.

CORS preflight-requests have a successful response (2xx).

The tables below focus on authorization and resource state. All C/'s (containers) apply an access mode that can be inherited by C/R. Access mode marked with - indicates no access mode is explicitly set on resource. To provide some clarity, the tables incorporate some scenarios where access privileges are (hypothetically) set on non-existing resource.


GET C/R
HEAD C/R
OPTIONS C/R
C/ C/R C/R exists C/R doesn't exist
- - 403 403
- Read 200 404
Read - 200 404
Read Read 200 404
Read Write 403 404

POST C/
Slug: R
/ C/ C/ exists C/ doesn't exist
- - 403 403
- Read 403 404
- Append 201 403
- Read,Append 201 404
Read - 403 404
Read Append 201 404

Servers allocate unique URIs to resources on POST C/ requests. "C/R exists" is not applicable.


PUT C/
C/ C/ exists C/ doesn't exist
- 403 403
Read 403 403
Write 200 201

PUT C/R
C/ C/R C/R exists C/R doesn't exist
- - 403 403
- Read 403 403
- Append 403 403
- Write 200 403
Read - 403 403
Append - 403 403
Write - 200 201
Append Write 200 201

Create requires Append (or Write) on C/ and Write on C/R. Replace requires Write on C/R.


PATCH C/R

PATCH based on application/sparql-update data type:

C/ C/R Payload Match C/R exists C/R doesn't exist
- - 403 403
- Read 403 404
- Append INSERT 200 403
- Append DELETE 403 403
- Write INSERT 200 403
- Write DELETE 403 403
Append Write INSERT 200 200
Append Write DELETE 403 403
- Read,Write DELETE true 200 404
- Read,Write DELETE false 409 404
- Read,Write DELETE+INSERT false ? ?

Create requires Append (or Write) on C/ and Write on C/R. Payload with DELETE requires Read on C/R.


DELETE C/R
C/ C/R C/R exists C/R doesn't exist
- - 403 403
- Read 403 404
- Append 403 403
- Write 403 403
Read 403 404
Append 403 403
Append Read 403 404
Write - 204 403
Write Read 403 404
Write Append 403 403

DELETE C/

Deleting C/ behaves like deleting C/R but with additional requirements:

C/ C/ empty C/ exists C/ doesn't exist
- 403 403
Read 403 404
Append 403 403
Write 403 403
Read,Write true 204 404
Read,Write false 409 404
michielbdejong commented 4 years ago

C/ | C/R | C/R exists | C/R doesn't exist Read | - | 200 | 404

Shouldn't that be Read | - | 403 | 404 ? If you don't have read access on C/R then you shouldn't get a 200

michielbdejong commented 4 years ago

POST C/ Slug: R

Slug should only be used as advice, and if C/R exists, the server should pick a different location. It should always return the location along with a 201.

michielbdejong commented 4 years ago

PUT C/R C/ | C/R | C/R exists | C/R doesn't exist Read,Write | - | 200 | 201

Neither creating nor updating C/R should be allowed if you don't have write access to C/R itself.

michielbdejong commented 4 years ago

PATCH C/R C/ | C/R | C/R exists | C/R doesn't exist

  • | Append | 200 | 201

Only if the PATCH is an append-only patch

Append | - | 200 | 201

All PATCH operations should be forbidden if you have neither write nor append on C/R

Write | - | 200 | 201

Again, all PATCH operations should be forbidden if you have neither write nor append on C/R

You're also missing a few combinations in the table for PATCH. You can remove the column for C/ there, it's irrelevant. The cases for a PATCH with only INSERT are:

The cases for a PATCH with both INSERT and DELETE are:

csarven commented 4 years ago

Shouldn't that be Read | - | 403 | 404 ? If you don't have read access on C/R then you shouldn't get a 200

Right, normally a 403, but the tables factor in default:

access mode marked with - indicates no control is set on resource - default access mode is determined.

It just means that the effective access mode is determined via the inheritance algorithm. While C/R doesn't have its own ACL but C does, so C/R can be read.

Perhaps what didn't come out clearly was that all C's in the tables have acl:default. I can make that clear in the proposal/analysis above. Let me know if this clarifies the tables though.


Slug should only be used as advice, and if C/R exists, the server should pick a different location. It should always return the location along with a 201.

I think that's mostly reflected in the table (and that Slug is only MAY) - with the exception of having only Append on Container and in that case server doesn't need to reveal Location for the created resource.


Neither creating nor updating C/R should be allowed if you don't have write access to C/R itself.

Right. Same as above re default.


Only if the PATCH is an append-only patch

Right. Assuming INSERT DATA ("Inserting results in 2xx."). If payload includes DELETE DATA, it'll be 403. Related: https://github.com/solid/specification/issues/118#issuecomment-569648485 . Also pending https://github.com/solid/specification/issues/125 but I think we agree on the general direction.


All PATCH operations should be forbidden if you have neither write nor append on C/R

Right. Same as above re default.


You're also missing a few combinations in the table for PATCH.

I'll add those, thanks! [I held off on these because it wasn't entirely clear in the issues - IIRC]

d-a-v-i-- commented 4 years ago

Novell gave the 401/404 choice to ACL Controllers with a permission called "File Scan". It was not paired with a principals so you couldn't say that group X sees a 401 while everyone else sees a 404.

Overall, I feel like they had a pretty well-thought-out mapping of rights to actions which narrowed the vulnerability of someone deleting and replacing a file just to acquire ACLs control over it.

screenshot for those who ironically get a "page not found or you don't have access" error when trying to access the book:

Screenshot from 2020-09-03 06-48-04

timbl commented 4 years ago

A valuable workflow in Google Drive is the "click here to request access to this resource" . That is valuable for onboarding groups - typically it is hard for the owner to guess the ids of all the people they will want to share it with.

acoburn commented 2 years ago

CORS preflight requests are a vitally important consideration.

What I might suggest is this:

edwardsph commented 2 years ago

This issue has been quiet for a while but I have a question as a result of working on the tests for read access controls. In the table for POST C/ Slug: R there are 2 cases that don't make sense to me. You have read access to the container and that may be inherited. You attempt to POST a new resource to a target child container. The table suggests that if the target exists you would get a 403 as you are not permitted to write to the target. However it suggests that you would get a 404 of the target does not exist since you have read access to the parent container.

# edited to clarify
Read, -, 403, 404
-, Read, 403, 404

I think this is a problem for a few reasons:

csarven commented 2 years ago

The request semantics of POST (including Slug: R in this case, but not particularly important here) is to "perform resource-specific processing on the request payload" targeting a resource (i.e., a container in this case). The server does not have a current representation for the target resource, which is what the 404 indicates so that the client can try again by changing the request (if it wants to).

The content in issue 146 is not fully worked out and overlaps with the work in this issue, specifically the tables.

As mentioned elsewhere, 403 would be a valid (acceptable) response, but 404 is both accurate as per request semantics and more useful for the client.

edwardsph commented 2 years ago

Ok, whilst the discussion is ongoing, I will at least allow 403,404 in the tests.

ericprud commented 2 years ago

I feel like we arrived at the conclusion that sometimes a user wants to hide existence of stuff (404) and sometimes they don't (403, or even 401 if you shortcut someone's miss-impression that they have a prayer of re-authing to get access). Earlier, I mentioned NetWare's control on the directory controlling who gets to ls it. A more fine-grained approach would be to stick the you-cant-see-me control on the resource itself. This seems consistent with the notion that in App-Interop, there are meta controls to save you the some grief of fiddling with detailed, resource-level ACLs.

edwardsph commented 2 years ago

Can I query a row for DELETE?

C/     C/R  C/R exists  C/R doesn't exist
Write  -    204         403

When the resource doesn't exist, why couldn't the response also be 204? If the resource existed, the user would have been able to delete it. Although it didn't exist, the delete operation can be deemed to have succeeded as the resource doesn't exist.

csarven commented 2 years ago

No access is granted to know about the existence of the target resource. Access is granted to remove the target resource. The request semantics can be successfully applied to the target resource when the resource exists and access is granted. Request to remove a non-existing resource is forbidden.

The difference in 204 and 403 is useful to the client in that the user can be informed about whether the target resource is removed or cannot be removed (for any reason, including non-existence, no access, non-owner, permanence, or something else).

Whether read permission on the target resource is required to reveal its past state, you may find the following to be a good formulation of the problem or an invitation to philosophical ramblings: https://github.com/solid/specification/issues/311 .

woutermont commented 1 year ago

I noticed @csarven included this to the proposed milestone of v0.11.0, but I never had the feeling this has been properly resolved, and issues with regards to the status quo 'solution' keep popping up.

A very good (i.m.o. better) alternative to the status quo has already been proposed by @humont, yet has i.m.o. not been given enough attention; at least, no concrete arguments against it have been made, except perhaps @RubenVerborgh's question "Why we would prefer a 403 over a 404?" Let's revisit that proposal and answer that question.


[@humont:] The 403 can also be made default regardless of whether or not the resource/path is valid.

[@RubenVerborgh:] Good point, that does not seem to be disallowed by RFC7231 ... However, the question is why we would prefer a 403 over a 404. You write:

[@humont:] avoids using 404 when a resource may in actual fact exist (obfuscation).

[@RubenVerborgh:] But ... a constant 403 is no less an obfuscation.


The idea is thus to use 403s instead of 404s when permission is lacking,
regardless of resource existence

This approach has a number of advantages.

Note that a 403 response reveals nothing more about the state of the resource than a 404 response. That seems to be an implicit assumption in some comments throughout this discussion. Just like 404, its meaning relates to a target resource, which does not necessarily exist.

csarven commented 1 year ago

To be clear, the comment in https://github.com/solid/specification/issues/14#issuecomment-683480525 is the running rough consensus. It is taken to be consistent with HTTP and the constraints that Solid Protocol sets. The information prior to the tables sets the axioms that's intended to holds the whole thing together. Deviations from that have shown inconsistencies in implementations, and if anything, misunderstanding of the specs, and possibly security concerns. There may be incorrect/invalid data in the tables given the understanding, and as mentioned, that's something we can correct easily. The table is not intended to be complete/show all combinations, e.g., some of it can be derived from HTTP specs. PATCH may need an update since the Solid Protocol currently use N3.

The milestone is set so that we go through the Solid Protocol (and possibly other specs) and update where necessary based on the understanding that we seem to have arrived. (Originally I tried to make the case for when both 403 and 404 would be meaningful... and there is a lot of mention of this everywhere/in other issues...)

woutermont commented 1 year ago

If you mean to say my comment is already in line with the general consensus, I believe the following rows of the tables would need to be corrected.

These are all cases where an unauthorized request is NOT a Read operation. I argue above that in those cases, a 403 response should always be returned, EVEN IF the requesting agent has Read permission on the target resource, since the request was not to Read the resource, but to do something else, which was unauthorized. This improves implementation simplicity and separation of concerns.

EDIT: Deleted subsequent message that maybe too eagerly added some other changes.