Skip to content

Add security hardening to HTTP server for browser extension communication - #15295

Closed
FynnianB wants to merge 20 commits into
JabRef:mainfrom
FynnianB:feature/jabsrv-security
Closed

FynnianB wants to merge 20 commits into
JabRef:mainfrom
FynnianB:feature/jabsrv-security

Conversation

@FynnianB

@FynnianB FynnianB commented Mar 8, 2026 •

Copy link
Copy Markdown
Contributor

Related issues and pull requests

Closes no related issue

PR Description

This PR hardens the HTTP server used for browser extension communication by adding two security layers:
First, CORS origin whitelisting and a custom X-JabRef-Connector header validation ensure only legitimate browser extension requests are processed.
Second, a PIN-based pairing mechanism generates a bearer token that authenticates all subsequent API requests, preventing unauthorized extensions from accessing the endpoint.

This PR is part of my bachelorthesis regarding the communication between the browser extension and the JabRef Desktop-App. The corresponding PR in the JabRef-Browser-Extension-fresh repository will follow shortly.

Important: This breaks the current communication with the fresh extension because it enforces the X-JabRef-Connector header so this PR should not be merged before the corresponding PR in JabRef-Browser-Extension-fresh is merged.

_Hint: This PR will follow another one which adds the MADR for it.

Steps to test

Quick test with curl (no extension needed):

  1. Start JabRef, enable the HTTP server in preferences

  2. Verify the health check still works without headers:

    curl http://localhost:23119/
    

    Expected: HTML response (200 OK)

  3. Request a protected endpoint without Origin (local tool behavior, allowed):

    curl http://localhost:23119/libraries
    

    Expected: 200 OK

  4. Request with an unknown origin (simulates a malicious website):

    curl -H "Origin: https://evil.example.com" http://localhost:23119/libraries
    

    Expected: 403 Forbidden

  5. Request with extension origin but no custom header:

    curl -H "Origin: chrome-extension://test" http://localhost:23119/libraries
    

    Expected: 403 Forbidden

  6. Request with extension origin and custom header but no token:

    curl -H "Origin: chrome-extension://test" -H "X-JabRef-Connector: true" http://localhost:23119/libraries
    

    Expected: 401 Unauthorized

  7. Generate a PIN in Preferences > General, then pair:

    curl -X POST -H "Origin: chrome-extension://test" -H "X-JabRef-Connector: true" \
         -H "Content-Type: application/json" -d '{"pin":"<YOUR_PIN>"}' \
         http://localhost:23119/auth/pair
    

    Expected: {"token":"..."} (200 OK)

  8. Use the token:

    curl -H "Origin: chrome-extension://test" -H "X-JabRef-Connector: true" \
         -H "Authorization: Bearer <TOKEN>" http://localhost:23119/libraries
    

    Expected: 200 OK

How the browser extension will use this later:

The extension will send X-JabRef-Connector and Authorization: Bearer <token> on every request. On first use, a pairing dialog will prompt the user for the PIN shown in JabRef. The token is stored in chrome.storage.local and reused across sessions.

pin-pair

Checklist

  • I own the copyright of the code submitted and I license it under the MIT license
  • I manually tested my changes in running JabRef (always required)
  • I added JUnit tests for changes (if applicable)
  • I added screenshots in the PR description (if change is visible to the user)
  • [/] I added a screenshot in the PR description showing a library with a single entry with me as author and as title the issue number
  • I described the change in CHANGELOG.md in a way that can be understood by the average user (if change is visible to the user)
  • I checked the user documentation for up to dateness and submitted a pull request to our user documentation repository

FynnianB added 4 commits March 7, 2026 15:16
… header validation

- Implemented CORS origin whitelisting to replace wildcard `*` policy in the HTTP server.
- Added a security filter to validate custom headers and origins.
- Introduced configurable allowed origins to support browser extensions and JabMap.
…ensions

- Introduced a PIN generation feature in preferences for pairing browser extensions with the HTTP server.
- Implemented bearer token authentication for secure communication between paired extensions and the server.
- Updated the security filter to validate tokens and manage pairing endpoints.
@testlens-app

This comment has been minimized.

@github-actions github-actions Bot added the status: changes-required Pull requests that are not yet complete label Mar 8, 2026
@testlens-app

This comment has been minimized.

@github-actions github-actions Bot added status: no-bot-comments and removed status: changes-required Pull requests that are not yet complete labels Mar 8, 2026
@koppor koppor added status: ready-for-review Pull Requests that are ready to be reviewed by the maintainers and removed status: no-bot-comments labels Mar 9, 2026
@github-actions
github-actions Bot marked this pull request as ready for review March 9, 2026 05:15
Injector.setModelOrService(ConnectorTokenManager.class, JabRefGUI.connectorTokenManager);

JabRefGUI.httpServerManager = new HttpServerManager();
JabRefGUI.httpServerManager.setTokenManager(JabRefGUI.connectorTokenManager);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why putting this a second time in a static context, if you already put this into the injector already?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, removed the static context

Injector.setModelOrService(RemoteListenerServerManager.class, JabRefGUI.remoteListenerServerManager);

JabRefGUI.connectorTokenManager = new ConnectorTokenManager(preferences.getRemotePreferences());
Injector.setModelOrService(ConnectorTokenManager.class, JabRefGUI.connectorTokenManager);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't putting the token manager in a static context also a security risk on its own?

private String activePin = "";
private Instant pinExpiration = Instant.MIN;

public ConnectorTokenManager(RemotePreferences remotePreferences) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have the very strong suspicion, that this class is not a manager at all.
This is just a couple of helper methods if I see it correctly around a variable active pin and remoteprefences. Looks like to me that this could also just be a utility class.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For me, it was more of a manager class, since it's stateful (active PIN, expiration time). I would expect a utility class to be completely stateless.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

State can be stored in preferences or state manager. But we are trying to avoid excessive growth of singletons/managers

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The token should be stored in the preferences to survive JabRef sessions.

PIN is only used temporarily during authentication process to generate the token.

PIN -- "Manager", maybe better "AuthenticationTask"?

Token -- Preferences

Need to check the code if this is fulfilled.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Token is already saved in Preferences. I renamed the class to ConnectorAuthenticationTask

@testlens-app

This comment has been minimized.

@testlens-app

This comment has been minimized.

@github-actions github-actions Bot added status: changes-required Pull requests that are not yet complete and removed status: ready-for-review Pull Requests that are ready to be reviewed by the maintainers status: no-bot-comments labels Mar 10, 2026
@palukku

palukku commented Mar 11, 2026

Copy link
Copy Markdown
Member

I was curious about the cite as you write (cayw) endpoint and if its still working with the security filter, it still works.
But in the discussion with @koppor we came along that security should be enabled as default so maybe we should make the cayw endpoint also secure OR disable the cayw per default and make it a preference to enable the cayw endpoint there explicitly.

dependabot Bot and others added 2 commits March 11, 2026 12:54
* Chore(deps): Bump pdfbox from 3.0.6 to 3.0.7 in /versions

Bumps `pdfbox` from 3.0.6 to 3.0.7.

Updates `org.apache.pdfbox:fontbox` from 3.0.6 to 3.0.7

Updates `org.apache.pdfbox:pdfbox` from 3.0.6 to 3.0.7

Updates `org.apache.pdfbox:xmpbox` from 3.0.6 to 3.0.7

---
updated-dependencies:
- dependency-name: org.apache.pdfbox:fontbox
  dependency-version: 3.0.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.apache.pdfbox:pdfbox
  dependency-version: 3.0.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.apache.pdfbox:xmpbox
  dependency-version: 3.0.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* fix new schema urls

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Siedlerchr <siedlerkiller@gmail.com>
@testlens-app

This comment has been minimized.

@koppor

koppor commented Mar 22, 2026

Copy link
Copy Markdown
Member

Preflight / PNA header: JabRef still does not set Access-Control-Allow-Private-Network. CORS is added in CORSFilter. The preflight request is an OPTIONS requests which bypasses the SecurityFilter (RequestFilter), so it can reach the CORSFilter (ResponseFilter).

This should be an issue easy to solve?

@tobiasdiez

Copy link
Copy Markdown
Member

so any website the user opened could talk to JabRef’s localhost API in the same way as a legitimate client.

Browsers block all requests to localhost, or at the very least send a preflight request that needs to be accepted. So this is for the case that a malicious website managed to jump over those brower security hoops?

Problem was that we sent Access-Control-Allow-Origin: * so the preflight was always successful

Can you give an example/steps on how to reproduce this? I played around a bit and all requests were denied for me. Could you also please point me to the code in JabRef that is responsible for accepting the preflight (i.e. sets Access-Control-Allow-Private-Network: true)

Repro in Chrome: I hosted a small page over HTTPS and call fetch('http://127.0.0.1:23119/...') where you can test the behavior here, Chrome often shows a “allow access to local network / local apps” prompt (Local Network Access). This is Chromiums PNA setup which you saw on your tests earlier (all requests to localhost denied). But after you allow, you can see whether the request completes and what CORS headers come back. You see that without the changes the page can easily fetch the libraries because of ACAO: * - with the changes it doesnt work because the origin isnt matched by JabRef. You can also see that we need to enforce the custom X-Jabref-Connector header so the browser initiates the preflight

Thanks a lot, this is really helpful. It's scary how easy websites can communicate with stuff on localhost (Firefox doesn't even show the confirmation prompt that you mentioned). Your example page also explains why my tests were failing: I always sent some custom headers with it, so the preflight was issued and then the main request got blocked. That brings me to

Preflight / PNA header: JabRef still does not set Access-Control-Allow-Private-Network. CORS is added in CORSFilter. The preflight request is an OPTIONS requests which bypasses the SecurityFilter (RequestFilter), so it can reach the CORSFilter (ResponseFilter).

If I try your example in Firefox & Edge, the "with headers" option is not able to get anything from localhost as the preflight is coming back without tha ACAPN header. Is this working for you/on Chrome? So requiring the X-Jabref-Connector header would be sufficient to block all access from websites, right? (With the option to later whitelist certain websites and do send a ACAPN if we do want to allow them to talk to the local db)

Having such a header is anyway a good idea as you don't want to accept requests that are purely accidentally sent to the 'wrong' localhost port.

Extensions: The thing is that, as far as I know, the flow on a webpage is also not the same as the extension path (host permissions). I think PNA only gates webpages for now. Even when the browser gates sites (and maybe even extension in the future), we still need server-side allowlisting + auth for traffic that is allowed to hit localhost - we shouldn’t depend on the browser being the only line of defense imo.

As extensions have the power to change the headers (also protected ones like 'origin' [not totally sure about requests to localhost, but the origin of requests to other websites can definitely be changed]), there is not much you can do here anyway.

Also if the user decides to install an extension that then talks to JabRef, what's the problem? We are not a bank that needs to force users to go through hurdles to protect their money. If you really want to have a higher security standard, then I would suggest to do the token exchange automatically via native messing (which has a built-in verification of the extensions that are allowed to trigger the run of an application).

@github-actions github-actions Bot added status: changes-required Pull requests that are not yet complete and removed status: no-bot-comments labels Mar 28, 2026
@koppor

koppor commented Apr 4, 2026

Copy link
Copy Markdown
Member

As extensions have the power to change the headers (also protected ones like 'origin' [not totally sure about requests to localhost, but the origin of requests to other websites can definitely be changed]),

We should investigate.

Also if the user decides to install an extension that then talks to JabRef, what's the problem?

The trade-off discussion is between

  • maintainance effort of mutliple APIs (native messaging, http) - includes installation and backend support
  • developer friendliness w.r.t. of multiple APIs (assuming that "the community" is also maintaining the eco system not just a single person of the core team)
  • UX w.r.t. security

@FynnianB Am I missing some trade-offs here?

Siedlerchr added 6 commits May 4, 2026 22:01
…rity

* upstream/main: (204 commits)
  New Crowdin updates (JabRef#15669)
  Fix OpenRewrite (JabRef#15670)
  Udpate heylogs (and fix CHANGELOG.md) (JabRef#15671)
  Improve security and prevent shell injection for push2applications (JabRef#15628)
  Fix depdency analysis (JabRef#15668)
  Always use CI-local "gradle", instead of gradlew (JabRef#15667)
  Change OpenRewrite task to use rewriteDryRun (JabRef#15664)
  Add small documentation to parameter (JabRef#15666)
  Fix markbaseChanged for "imported entries" (JabRef#15610)
  Add forgotten --fresh
  chore(deps): update dependency com.github.ben-manes.caffeine:caffeine to v3.2.4 (JabRef#15662)
  chore(deps): update jackson monorepo to v3.1.3 (JabRef#15659)
  chore(deps): update dependency org.glassfish.hk2:hk2-utils to v4.0.1 (JabRef#15657)
  chore(deps): update dependency org.glassfish.hk2:hk2-locator to v4.0.1 (JabRef#15656)
  fix gemsfx missing icon resolving (JabRef#15655)
  chore(deps): update dependency org.glassfish.hk2:hk2-api to v4.0.1 (JabRef#15654)
  chore(deps): update dependency org.postgresql:postgresql to v42.7.11 (JabRef#15634)
  Chore(deps): Bump tools.jackson:jackson-bom in /versions (JabRef#15653)
  Chore(deps): Bump dev.langchain4j:langchain4j-bom in /versions (JabRef#15652)
  Chore(deps): Bump com.dlsc.gemsfx:gemsfx in /versions (JabRef#15651)
  ...
@github-actions github-actions Bot added status: no-bot-comments and removed status: changes-required Pull requests that are not yet complete labels May 4, 2026
@github-actions github-actions Bot added status: changes-required Pull requests that are not yet complete and removed status: no-bot-comments labels May 12, 2026
@github-actions

Copy link
Copy Markdown
Contributor

The requested changes were not addressed for 10 days. Please follow-up in the next 10 days or your PR will be automatically closed. You can check the contributing guidelines for hints on the pull request process.

@github-actions github-actions Bot added the status: stale Issues marked by a bot as "stale". All issues need to be investigated manually. label Jun 14, 2026
@koppor
koppor marked this pull request as draft July 25, 2026 11:49
@koppor koppor self-assigned this Jul 25, 2026
@koppor

koppor commented Jul 25, 2026

Copy link
Copy Markdown
Member

As usual, after submitting a thesis, students re-focus themselves.

This PR is important - we need to leave it open (and finish before 6.0 release)

@koppor koppor added this to the 6.0 milestone Jul 25, 2026
@koppor koppor added the status: freeze Issues posponed to a (much) later future label Aug 17, 2026
@koppor

koppor commented Aug 17, 2026

Copy link
Copy Markdown
Member

put it as freeze as we don't have time any more to think. We will continue as soon as we find time.

@koppor koppor closed this Aug 17, 2026
@koppor

koppor commented Aug 23, 2026

Copy link
Copy Markdown
Member

Also Safari needs to be investigated: JabRef/JabRef-Browser-Extension#681 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

📌 Pinned project: jabcon status: changes-required Pull requests that are not yet complete status: freeze Issues posponed to a (much) later future status: stale Issues marked by a bot as "stale". All issues need to be investigated manually.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants