-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Harden access to internal API #3925
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
The editor already strips script tags from fields, but was allowing through Javascript in things like onclick handlers. We block this now, as the editor context has access to internal APIs that we don't want to expose to untrusted third-party code.
We were previously inspecting the referrer, but that is spoofable, and doesn't guard against other processes on the machine. To accomplish this, we use a request interceptor to automatically add an auth token to webviews with the right context. Some related changes were required: - We avoid storing _page, which was leading to leaks & warning on exit - At webview creation (or set_kind() invocation), we assign either an authenticated or unauthenticated web profile. - Some of our screens initialize the AnkiWebView when calling, e.g., aqt.forms.stats.Ui_Dialog(). They then immediately call .set_kind(). This reveals a race condition in our DOM handling code: the webview initialization creates an empty page with the injected script, which causes a domDone signal to be sent back. This signal arrives after we've created another page with .set_kind(), causing our code to think the DOM is ready when it's not. Then when we try to inject the dynamic styling, we get an error, as the DOM is not ready yet. In the absence of better solutions, I've added a hack to set_kind() to deal with this for now.
| self.onCmd = bridge_handler | ||
| def _profileForPage(self, kind: AnkiWebViewKind) -> QWebEngineProfile: | ||
| have_api_access = kind in ( | ||
| AnkiWebViewKind.DECK_OPTIONS, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| AnkiWebViewKind.DECK_OPTIONS, | |
| AnkiWebViewKind.MAIN, | |
| AnkiWebViewKind.DECK_OPTIONS, |
The congrats screen fails to load otherwise
EDIT: that's not a good idea, as the deck overview webview is also used by the reviewer. this would also work:
diff --git a/qt/aqt/mediasrv.py b/qt/aqt/mediasrv.py
index 6cb61d916..966f9801b 100644
--- a/qt/aqt/mediasrv.py
+++ b/qt/aqt/mediasrv.py
@@ -720,6 +720,7 @@ def _check_dynamic_request_permissions():
"/_anki/getSchedulingStatesWithContext",
"/_anki/setSchedulingStates",
"/_anki/i18nResources",
+ "/_anki/congratsInfo"
):
pass
else:There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AnkiWebViewKind.DECK_OPTIONS,
IMPORT_LOG, IMPORT_CSV and IMPORT_ANKI_PACKAGE should be whitelisted as well, for their respective import dialogs to work
aps-amboss
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey, commenting from this account as I primarily evaluated the changes from an AMBOSS add-on compat standpoint (which makes heavy use of add-on-controlled web views and can thus be a good litmus test for compatibility issues).
I left a couple of suggestions which would be necessary to keep web views working for a number of add-ons (collected in this branch).
Did not evaluate the changes in terms of the security intent and have not had a chance yet to test with a broader suite of add-ons (but I do think that the suggestions address the main breakages).
qt/aqt/webview.py
Outdated
| def __init__( | ||
| self, | ||
| onBridgeCmd: BridgeCommandHandler, | ||
| kind: AnkiWebViewKind, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A number of add-ons unfortunately depend on the current AnkiWebPage signature (e.g. AMBOSS, AnkiHub, likely many more), so I would recommend that we make kind and profile optional, setting the defaults to effectively mimic the previous behavior for add-on controlled web views. My suggestion would be e.g. fd2c95a
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Working with AnkiHub after this change ✅
| return profile | ||
|
|
||
| def _setupBridge(self) -> None: | ||
| class Bridge(QObject): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some add-ons like the AMBOSS add-on override and re-implement AnkiWebPage.__init__ completely, and so they depend on _setupBridge injecting the bridge JS into the profile scripts.
To maintain compatibility, especially given that we need to push this fix quickly, I would recommend that we do something like this: 2a97b13
Ideally over time add-ons would migrate away from initializing the web page manually / calling _setupBridge directly, but some additional changes to AnkiWebPage might be necessary in that case to provide more robust solutions to enable the corresponding use cases that currently require these types of patches and overrides.
(I think for the AMBOSS add-on, that use case was to use AnkiWebPage with a custom web engine profile, and patching the init the only way to fulfill Qt object tree / ownership requirements. There might have been better ways to solve this, but at the time I wasn't able to find one. I would have to revisit this a bit more in-depth in the future to be sure and check what changes AnkiWebPage would need to no longer make this necessary. In the meantime, I would highly recommend that we push a change like the above to avoid breaking add-on installations)
|
One way I found to possibly gain API access in the reviewer is through a simple image tag, e.g. |
Some add-ons fully override AnkiWebPage.__init__ and thus depend on _setupBridge injecting the JS bridge script. With this change we account for these cases, while giving add-ons the opportunity to look for solutions that do not require overriding AnkiWebPage.__init__ completely.
Thanks to Abdo for the report
|
Thanks everyone. Happy with how things are now? |
glutanimate
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM also from a general add-on compatibility standpoint
|
Thanks again all 🙏 |
| return page_context if page_context else PageContext.UNKNOWN | ||
| else: | ||
| return PageContext.UNKNOWN | ||
| _APIKEY = "".join(random.choices(string.ascii_letters + string.digits, k=32)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for intruding, but it better be secrets.token_urlsafe. random is more or less predictable:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I lost track of this. This change will be in the next patch release.
#3925 (comment) (cherry picked from commit 757247d)
I received a report recently that our existing mitigations in mediasrv.py are not sufficient, meaning that a malicious shared deck could potentially access our internal API, and thus arbitrary data on the system. As users have an expectation that shared decks are safe to download, we'll need to get a fix for this out promptly. This is the least disruptive/invasive solution I could come up with:
@abdnh @glutanimate @iamllama feedback/testing would be appreciated. If nobody objects or has better ideas in the next ~24 hours, I'll merge this into main, and aim to get a bugfix build out with only this change about a day later.