Problem/Motivation

See #3035979: [DISCUSSION] Should a facility be added to JSON:API which limits automatic exposure of all entities for prior related discussion.

In #3035979-14: [DISCUSSION] Should a facility be added to JSON:API which limits automatic exposure of all entities, @greggles said:

[Drupal 8's HTTP APIs are] shining light on some code paths that are not as well hardened as the rest of Drupal. As a community of developers and site builders our sophistication around writing and testing these kinds of sites is advancing.

In other words, if the "First" in API-First means making APIs a first-class citizen (i.e., making everything a site can do via HTML also accessible via APIs), then that's a great design principle and goal. However, we also need to be aware of Drupal's actual history, where HTML came "first" (chronologically), and it's taking time for Drupal contrib and custom module developers to adjust to the security implications surfaced by HTTP APIs.

For example:

  • One can write an entity type for which there is no HTML route at which it can be viewed independently of being edited (this is common for config entity types, but can also include content entity types). And in the process of doing so, one can accidentally introduce an access control handler that allows 'view' permission to someone who does not have 'edit' permission. And not realize this until someone is able to get that entity data via an HTTP API, that they can't get via HTML.
  • The above can also happen for individual fields within a given entity type or within any entity type.
  • One can write an entity type or a field whose HTML form (via Drupal's Form API) performs critical input validation that isn't performed by the entity type's or fields' validation constraints or serialization denormalizers. And again, not realize this until someone is able to corrupt the data and/or exploit something via that corrupted data, in a way that they can't do via the HTML form.

Ideally, all module developers would know about all these pitfalls, and code all of their entity types, field types, validation constraints, normalizers, and access control handlers, correctly and securely. And also, have as much automated testing and perform as much manual testing, for HTTP API usage of their module's features, as they do for the HTML usage. But, we're not there yet.

So, let's figure out what, if any, additional safeguards and mitigations could be added without harming the DX and UX for people working with Drupal for API-First projects.

Proposed resolution

Several ideas were proposed throughout #3035979: [DISCUSSION] Should a facility be added to JSON:API which limits automatic exposure of all entities. Let's add child issues for ones we want to explore further.

Remaining tasks

Issues yet to be filed:

  • A simple status report message indicating JSON:API's read/write status (proposal from UX team meeting that we agreed was not a hard blocker and could use further discussion).
  • An issue for:

    "Limit to accessed API's" or something as a starting point when you go into production.

    ...potentially dependent on #1537198: Add a Production/Development toggle.

  • Potential issues for @gabesullice's ideas in #3.
  • Ideas for promoting further security research on contrib code with JSON:API. Many contrib vulnerabilities continue to be found with REST, and if they also exist for JSON:API they become more severe because of JSON:API's more permissive configuration.
  • Ideas for educating developers about writing secure code for an API-first Drupal.
  • Other issues to harden the "GET everything by default" configuration.
  • Issues for mitigating risks relating to config entities.

User interface changes

API changes

Data model changes

Release notes snippet

Comments

effulgentsia created an issue. See original summary.

xjm’s picture

I went ahead and parented all the existing issues (although a couple of them look to be sort-of-duplicates of each other). I think there's also several to file yet before we mark #3035979: [DISCUSSION] Should a facility be added to JSON:API which limits automatic exposure of all entities fixed.

gabesullice’s picture

This was not proposed in the other issue because that issue was JSON:API specific and time-constrained.

I'm very interested in exploring ways that we can force entity type and field implementations to consider their security in the abstract, not tied to any particular representation (via Form API, JSON:API, config export, etc.).

I have lots of ideas for how we might do that:

  1. I think it would be good to sacrifice some DX in exchange for stricter security by returning AccessResult:: forbidden('Access logic has not yet been implemented for this (entity|field), follow the guidelines at <link> to implement a secure control.') from EntityAccessControlHandler. Currently, checkFieldAccess returns allowed by default (I'm not sure what it does at the entity level). This would be limited to Drupal 9 of course.
  2. For Drupal 8, a compromise could be to create a special subclass of AccessResult called UnconfirmedAccessResult and use that in the base implementation. We could then throw some deprecation notices when these are encountered as an end result. This object could have a ->confirm() method on it to make it easy to affirm the default behavior in the base class, if it doesn't need to be overriden.
  3. Following up on the last idea, we could have a "safe mode" that treats all unconfirmed results as forbidden. Toggling this behavior would be a way to track down vulnerabilities.
  4. Another useful tool would be an auditing mechanism which would provide security hardening suggestions/recommendations. I saw a blog post about an entity level tool that already exists, do we know if there's a field level tool?
  5. A really nice low-hanging thing we could do is have a flowchart for implementors that helps them evaluate their access controls and ensure they've thought of everything. This would also be a way for the security team to audit and approve entity types that could be considered safe to enable write operations on by default.
  6. One thing that has bitten us before is that access operations (view, create, edit, update, ...) are not clearly defined anywhere. We have loose conventions for what they mean, but no standardization. I know that changing them from strings would be highly disruptive, but what about creating a constant like EntityAccessControleHandlerInterface::VIEW = 'view' with a clear definition of what it means, then making it a code smell to use raw strings?
  7. We're missing access check methods for both entities and fields that do not rely on having an instance of the object for which they check access. This has caused us a lot of pain WRT API-driven entity listings and filtering. Having these would also save ourselves a lot of DB round trips if we could check access prior to executing a query or entity loads.

Finally, I'd like to suggest that we view API-First Drupal as an opportunity to make all of Drupal more secure...

There are far fewer moving parts to consider in the API-First world. There are no form alters, no Form API (no magic array keys!), no Twig templates, no ways to execute a DB query or render a View in a template, no places an entity might accidentally be exposed on a forgotten page via a custom block, ... the list goes on.

JSON:API exposes a Drupal site's data in such an unadulterated fashion, it provides a great mental exercise to gut-check your access results and it encourages better organized code. An implementor doesn't have to wonder if it's better to add an #access render array key value or implement a field access hook. There's just one way to do it!

In short, while it's a bit scary and opens up another surface to attack, the API-First architecture can bring a lot of clarity to a significantly muddied space.

</pontification> 😉

xjm’s picture

Issue summary: View changes
Issue tags: +Needs followup

Lots of great stuff in #3, thanks @gabesullice!

Adding a few stragglers from #3035979: [DISCUSSION] Should a facility be added to JSON:API which limits automatic exposure of all entities that don't have issues yet to the remaining tasks.

Then, two more things that I think we should add are:

  1. Ideas for promoting further security research on contrib code with JSON:API. Many contrib vulnerabilities continue to be found with REST, and if they also exist for JSON:API they become more severe because of JSON:API's more permissive configuration.
  2. Ideas for educating developers about writing secure code for an API-first Drupal. (Similar to what @gabesullice alludes to at the end of #3, I think.)

Another thing we should look at specifically is how to further harden the default "read everything" behavior. Most security team members were comfortable with our MVP in #3039568: Add a read-only mode to JSON:API, but a few raised concerns that this default behavior was still too permissive in terms of the risk of information disclosure.

One particular topic that came up is config entities, which many developers might not think of as being potentially available outside their modules' admin UIs. There's also the added risk that info disclosures with config entities might contain additional data that could be used to attack the site.

So adding those to the IS bullets as well!

effulgentsia’s picture

Issues for mitigating risks relating to config entities.

That's #3040372: Allow for API-First content without requiring all configuration to also be API-first.

Other issues to harden the "GET everything by default" configuration.

That's #3040364: Allow modules to declare the entity types they want to consume with HTTP APIs.

xjm’s picture

@effulgentsia, I think #3040372: Allow for API-First content without requiring all configuration to also be API-first is great, but I think there might be additional things beyond #3040364: Allow modules to declare the entity types they want to consume with HTTP APIs that we should pursue to harden the read-everything behavior.

xjm’s picture

@Wim Leers added #3043245: Allow marking a config schema types to be marked as "internal" (to avoid exposing them via HTTP APIs) for an issue we encountered for (un-)exposing Layout Builder defaults data, which adds data to the third-party settings of bundle config entities.

gabesullice’s picture

Whoa! Finally ready to get this going again.

I'd like for all of us to come to a consensus on what does and does not make sense to implement in order to further harden JSON:API from a security standpoint. In undertaking that process, I think we can also prioritize those things that we do want to tackle.

To help us accomplish that, I went through all of the child issues and aggregated them into a matrix of sorts. This matrix was useful to me because I found it pretty difficult to keep the many child issues of this discussion in my head at once and even harder to consider them relative to one another. Hopefully you all find it helpful as well.

For each issue, I created number of axes by which to evaluate the proposal:

  • Does the proposal require contrib code to be written/changed?
  • Does the proposal require require custom code to be written/changed?
  • Does the proposal require config/configuration pages?
  • Does the proposal make JSON:API safer by default?
  • Is the proposal actionable by the end-user?
  • Does the proposal make JSON:API easier or harder for a newcomer to use?

Each proposal was ranked -1, 0 or 1, depending on whether the proposal had a negative outcome, no effect or a positive outcome.

Finally, I summed these values up and also ranked each proposal with a very rough level-of-effort (LOE). LOE is ranked 0 if it the work has already been done, 1 if it is something fairly straightforward, 2 if it is something that will take a moderate amount of work and 3 for the proposal that seemed most complicated to implement.

I think the two biggest shortcomings with this matrix approach is that it is fairly subjective and all of the axes are equally weighted. I've made the document open for public commenting. If you feel I've misrepresented whether something is negative, neutral or positive just comment on the cell. Same if you have a question. If I've missed an axis completely, just add a comment on this issue with the axis label and your ranking for each issue and I'll add it to the matrix.

Matrix available here.

Early conclusions:

There's a really nice correlation between things which are easier to implement and that have a positive impact. The two issues with the highest LOE are also among the issues that have the least positive outcome:

I propose we close these two issues:

  • #3037534: Add a mechanism to mark TypedData (entities and fields) internal via settings.php would be tremendously complicated to implement, complicated to configure and does not make JSON:API safer by default. While it is the proposal that offers the most granular tool for securing JSON:API, that pro doesn't seem to outweigh the costs IMO.
  • #3040364: Allow modules to declare the entity types they want to consume with HTTP APIs this proposal would be a little less complicated to implement than the former, but it would require contrib and custom code and it would make JSON:API harder to use. JSON:API has strived to be very easy to use adopt even for non-Drupal developers. This would reverse that. It would make JSON:API safer-by-default and wouldn't require contrib though. Perhaps the idea could be refined to require less contrib/custom code (i.e. via usage detection), but I think that would only increase the LOE further.

On a more positive note, there is some low-hanging fruit:

I think we should make this one our first priority:

The issue with the highest positive outcome is thankfully among the middle LOEs.

I think these issues should be our second highest priority:

  1. #3040372: Allow for API-First content without requiring all configuration to also be API-first would make JSON:API easier to use while at the same time making it safer by default. It isn't end-user actionable, but I think it would dramatically reduce the attack surface area of JSON:API. I think its one drawback is that it is quite blunt. I suspect that it will lead us directly to...
  2. #3040354: Introduce entity type annotation key for indicating that an entity type is *safe* for HTTP API usage should be merged with the previous issue or made a required followup to it.

What about the rest?

wim leers’s picture

#8 🥳 👏👏👏👏👏👏

I am missing one axis: Does the proposal make other HTTP APIs safer by default?.

I transplanted your feedback/analysis of every issue onto the corresponding core issue, to ensure that people reading those respective issues and not this particular issue are also awrae.

RE: closed.

RE: first priority. Agreed.

RE: second priority:

  • #3040372. Agreed.
  • #3040354. I am not sure that this should be merged. I think both approaches can be considered orthogonal. Because for #3040372, it's possible to add a reference to a config entity type that was not previously reachable from content, and that should cause it to be exposed automatically.
gabesullice’s picture

I added your axis. I don't think it really changes my conclusions on its own (I have yet to read your comments on specific issues though). Notably, it pushed #3039687: Add an API read-only mode to Drupal core and #3037534: Add a mechanism to mark TypedData (entities and fields) internal via settings.php out of the "neutral" bucket into a slight positive. That makes sense to me.

Thanks for transplanting my comments! I'm going to go read all of your replies (and possibly reply to you). Then I'll post another comment back here summarizing any noteworthy updates to our thinking.

gabesullice’s picture

Okay, status update as promised:

wim leers’s picture

wim leers’s picture

wim leers’s picture

#11 was a great status update. But that's 10 weeks ago. What's the status today?

  1. #3037534: Add a mechanism to mark TypedData (entities and fields) internal via settings.php was closed after 10 weeks of waiting for feedback.
  2. #3040364: Allow modules to declare the entity types they want to consume with HTTP APIs was also closed after 10 weeks of waiting for feedback.
  3. #3040354: Introduce entity type annotation key for indicating that an entity type is *safe* for HTTP API usage has a high-level plan.
  4. #3039712: Discuss whether we want an entity access audit status report boils down to "add https://www.drupal.org/project/entity_access_audit to Drupal core" — it has value outside of API-First Drupal too. In other words: this also has a plan.
  5. #3039715: Discuss whether we want an API routes audit status report is blocked on @xjm agreeing that it can be closed.
  6. #3039687: Add an API read-only mode to Drupal core has been RTBC for nearly two months.
  7. #3037039: Create a public API for indicating resource types should not be exposed has been RTBC for nearly one month.
  8. #3043245: Allow marking a config schema types to be marked as "internal" (to avoid exposing them via HTTP APIs) was deprioritized. Blocked on further feedback, but the other issues in this list are deemed more important.
  9. #3068275: Add status report message about JSON:API's read-only mode has been RTBC for exactly one month.

That leaves only #3039715: Discuss whether we want an API routes audit status report and #3043245: Allow marking a config schema types to be marked as "internal" (to avoid exposing them via HTTP APIs). Everything else is either closed, RTBC or already has a plan.

xjm’s picture

Thanks for the update @Wim Leers!

To clarify, nothing should be blocked on or assigned to me -- these are all framework decisions, not release management decisions and way outside my personal expertise. I was posting a whole lot of issue comments summarizing discussions that I attended, without them being my specific requests personally. :)

xjm’s picture

Probably should have done this awhile ago as well, but tagging for framework management feedback on the plan(s) as summarized in #11 and #14.

xjm’s picture

So the status on #3039687: Add an API read-only mode to Drupal core keeps confusing me; it's marked RTBC, but the request there is to wontfix it.

wim leers’s picture

#17 because it needs a committer to sign off on it. At Needs review it would never get looked at. Perhaps I'm wrong there?

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

gabesullice’s picture

@xjm just opened #3087525: [meta] Determine which core entity types should not expose their data to JSON:API (for DX or security reasons). I like its direction and I think it belongs as a child of this issue (so I made it one). It's an obvious companion to #14.1.

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.