So the new OnionShare is out and it has a bunch of exciting new features and some improvements in the UI/UX designs of the tool. One of the main new features that I helped build was the anonymous chat feature in OnionShare. Just like the other modes (share, receive, and website), there is now a chat mode. So if you want to start a chat service, you just start the chat server, share the onion address of the server with people you want to chat with, everyone opens this onion address in Tor Browser and voila! You have an anonymous chat.
Let's dive in a little deeper into the feature.
A common question that we got during developing this feature is what's the use of an anonymous chat room since we already have end-to-end encrypted messaging apps. It leaves a lot fewer traces.
The way we achieve this is very simple. There is no form of storage whatsoever in OnionShare chat mode. The chat is not persistent. The chat server stores no information at all (not even the usernames of people chatting). So once the chat server is closed, and the Tor Browser tab with the chat client is closed, there is no data (or metadata) related to chat that remains, even in the person's system who started the server. Hence, it leaves much less trace compared to other chat applications.
A good example of the above as mentioned by Micah in his blog is:
If, for example, you send a message to a Signal group, a copy of your message ends up on each device (the devices, and computers if they set up Signal Desktop of each member of the group). Even if disappearing messages is turned on it’s hard to confirm all copies of the messages are actually deleted from all devices, and from any other places (like notifications databases) they may have been saved to. OnionShare chat rooms don’t store any messages anywhere, so the problem is reduced to a minimum.
Given that the OnionShare chat feature works over the onion network, so it also has the additional anonymity feature. Also, adding to the anonymity feature, OnionShare chat doesn't need any form of signing in. Hence, people chatting can stay anonymous, and everything happens inside the tor network. One can just start a chat server, share the link via some disposable way, and then wait for the other people to join while maintaining anonymity.
Because it's an onion service, there is no way for an attacker to eavesdrop on the messages. The closest they can get is if they run a malicious Tor rendezvous node that's chosen for the onion service, they'll be able to spy on encrypted onion traffic. So, there's no capturing ciphertext to decrypt later on.
The chat feature is dependent on flask-socketio and eventlet for the WebSocket server implementation, and socket.io client js for the frontend implementation of the chat client. So when a chat server is started, the WebSocket is started in a namespace "/chat". Whenever a new user joins the link, they are given a randomly generated username and they are added to the room "default". There is only one room, and the actual name of the room can be set from the OnionShare settings-related code, but it doesn't really impact anything in the implementation. Both the room name and the randomly generated username are stored in a flask session. But that information is also completely gone once the chat server is stopped. The room and username information are only there to emit the messages properly.
You can also change the randomly generated username to a username (or pseudo username) of your choice for that particular session.
There are two main types of messages:
All of these WebSocket communication happens over the Tor onion services. OnionShare in itself doesn't implement any encryption algorithm to the chat and heavily relies on the Tor onion service's encryptions for the same. The message from the client to the OnionShare server is E2EE as it goes via Tor's onion connection. Then the OnionShare server broadcasts the message to all the other clients connected to the chat room through their E2EE onion connection, over WebSockets.
I feel, as of now, the OnionShare anonymous chat is great if you quickly want to have an anonymous, secure, non-persistent conversation with someone or a group of people. It is also great if a whistleblower wants to share some details over chat with a journalist and then remove all traces of that conversation completely. But I feel if someone needs to host a chat server for a long time where people can connect anonymously, this is probably not the best solution for that.
There are still some issues that we will hopefully improve in the next releases. Firstly, we need to try and make it a bit more asynchronous. Right now, if left inactive for a long time, the Tor connection over WebSocket is sometimes dropped which isn't a great user experience. We are working on improving that.
Also, we will improve the UI for the chat more to give a better user experience.
With the new tabs feature, one can have all different modes (even multiple servers of same mode) running at the same time. So you can have a chat server and share mode server running at the same time. All the modes are very independent of each other and hence don't affect one another in any way.
I hope you all enjoy the new chat feature and leave feedbacks/suggestions on how to improve it. You can also read more about this and other features at docs.onionshare.org
Content Security Policy (or CSP) is a way of avoiding certain types of website-related attacks like cross-site scripting and malicious data injections. It is a way by which website developers can tell the browser what content origins are approved so that everything else is blocked. One needs to add a Content-Security-Policy
HTTP header mentioning the sources which they allow for loading scripts, styles, images, etc.
To read in detail about CSP, check Content Security Policy Level 3 working draft.
We are going to discuss here why sha256 hashes often don't let inline styles to not pass in chromium browsers. Chromium browser console complains about the style-src hashes mismatch even though it shows them to be the same. Why? And how to solve it?
TL;DR: If using <style>
, use style-src
. If using style=""
attribute in HTML tag, use style-src-attr
Now, if you are interested in more information, let's dive a little deeper into what's going on.
The usual practice of having a tight, secure CSP is to not allow any inline style or inline scripts. This helps mitigate malicious scripts entered via data injection from getting executed.
When I say inline scripts, one might understand 2 different scenarios:
<!-- Scenario 1 -->
<script>alert('Hello world');</script>
or
<!-- Scenario 2 -->
<button onclick="alert('Hello world');">
Click me!
</button>
Now, the easiest way to allow this would be to add unsafe-inline
in script-src
of the CSP. But then we are back to the problem of executing malicious scripts entered by data injection. There are two ways to still allow only these scripts to work: nonce
and sha256
hashes. We are going to talk about sha256
hashes here.
The idea is to get the sha256 hash of the entire script and add it to the script-src
. So in this case, it would be something like this:
script-src 'self' 'sha256-DUTqIDSUj1HagrQbSjhJtiykfXxVQ74BanobipgodCo='
You can get the hash from https://report-uri.com/home/hash. Also, chromium browsers will usually show the hash that should be added for a particular inline script.
Now, all this sounds good, and in Firefox, just adding the above to your CSP will make both the scripts to work. However, in chromium, the above CSP will work only in Scenario 1 but not in Scenario 2. You can read more about the discussion here: https://bugs.chromium.org/p/chromium/issues/detail?id=546106#c8
In JavaScript, I think in general scenario 1 will be much more encouraged than scenario 2. So scenario 2 might not be encountered that often. However, the situation changes, when it comes to styles (or CSS)
In case of inline styles, following are the scenarios:
<!-- scenario 1 -->
<style>p{color: blue;}</style>
and
<!-- scenario 2 -->
<p style="color: blue;">This is a text</p>
In CSS, the second scenario is much more common when someone does inline styles than scenario 1. But again, in this case, adding a sha256 hash to style-src
won't execute the scenario 2 in chromium browsers.
This is because styles added in scenario 2 are part of the style attribute in the HTML tag which in CSP terms are essentially event handlers. According to w3c CSP draft, the hash in style-src
allows the inline styles mentioned inside <style>
tag to pass but doesn't allow event handlers (as is the case in scenario 2). There's more on this discussion here.
Yes, it is a feature. In chromium browsers, adding a hash to style-src
only allows any inline style written inside the <style>
tags to execute. This is by design. If you need to execute the inline styles present in style=
attribute of HTML tags, you need to use another directive in CSP called style-src-attr
. Similarly, script-src-attr
should be used if you are doing JavaScript event handling in the HTML tag itself.
So, for example, if you want to only allow an inline CSS such as this:
<p style="color: blue;">This is a text</p>
all you need to do is put the sha256 hash in style-src-attr
along with 'unsafe-hashes'
. This will tell the browser to allow any inline style, with the hashes that you added in style-src-attr
to be executed.
So the CSP will have something like this:
style-src-attr 'unsafe-hashes' 'sha256-C8uD/9cXZAvqgnwxgdb67jgkSDq7f8xjP8F6lhY1Gtk='
And, that's it! That will do the trick in any chromium browser. The related code for chromium can be found here. According to caniuse.com, all chromium browsers above 75 supports this behaviour.
Even though firefox still doesn't have support for style-src-attr
but it allows inline styles and scripts of all types to pass based on style-src
and script-src
hashes. So as long as the hash is mentioned in both style-src
and style-src-attr
, it should work in most of the browsers.
As for the explanation behind why 'unsafe-hashes'
, there is a pretty good explainer document written by andypaicu talking about exactly this.
Also, read more about style-src-attr
in detail in the w3c draft to understand exactly what's happening and what kind of risk it may still pose.
PS: Inline JavaScript event handlers using script-src-attr
can be very risky given an attacker can trigger a passing javascript from within an unrelated HTML tag.
Anyone who has ever created an OpenPGP key knows that it is a terrifying day in their life. Be it someone skilled in computers, or someone who just cares about their privacy, creating OpenPGP key is a fearsome incident. Add moving all subkeys properly to yubikey along with managing all passphrases, and the terror just increases manifold.
Well, do not fear, Tumpa is here.
For most journalists, lawyers, activists, or anyone who wants to have secure communication, OpenPGP key is a great way to send and receive encrypted messages. But most people dread a black terminal (or command line) with some text menu. That's the only way to probably create OpenPGP keys and transfer them to a smartcard (e.g, Yubikey) till now. So, when Kushal showed me johnnycanencrypt, his python library for various OpenPGP key based operations, we had this idea that it would be simply amazing if we can provide a Graphical User Interface (GUI) for people to create keys and transfer their keys to yubikey.
Being a digital security trainer, I can vouch that most journalists, lawyers, activists and anyone who doesn't sit in front of a terminal all day would rather have a desktop application to click a few buttons, fill up a few forms, and get their result, rather than typing command after command in a black screen.
And that's exactly what Tumpa does!
Tumpa provides a simple form where you need to add your name, all emails that you want to associate with your OpenPGP key, a passphrase for your OpenPGP key, click on the big "Generate" button, and boom!
That's it!
You have your OpenPGP key with proper subkeys and everything!
Well, what about transferring the key to the smart card? Just plug your Yubikey, click on the big "Upload to SmartCard" button, add the necessary passphrases, and done!
You have your key transferred to a physical key!
Usually, a training session to teach someone to create OpenPGP key properly and transferring everything properly to a smartcard like yubikey takes about 3-4 hours. And after such a session, usually, everyone loses a bit of their sanity in the process.
The first time I and Kushal got the first draft working and went through the entire flow, we were both positively surprised and probably laughing hysterically (thanks Anwesha for tolerating us for the last few days).
Tumpa optimistically reduces work which you would take hours, into a few minutes. And also lets everyone keep their sanity. Most of the operations that would need you to type a lot of commands and understand some command-line options, can be achieved by a few clicks.
You can download the .deb
package from the release page.
Then, install using dpkg -i ./tumpa_0.1.0+buster+nmu1_all.deb
, preferrably on an airgapped computer inside of Tails.
Tumpa is at a very early stage of development. We have tried to make Tumpa feature complete to the most necessary ones and make the initial release. But there's still a lot of work left to be done.
We want to make Tumpa even easier to use for people who don't want to get into all the intricacies of OpenPGP key while giving more advanced options to the more experimental and curious users.
Right now, Tumpa uses Curve25519 to create keys and subkeys with an expiration date of 3 years. We want to give options to possibly select these based on a user's need in case they really care and want to change things are. There are many such customizations and also simplifications that we will slowly add in the next releases trying to improve the entire user experience even more.
We have started conducting user interviews. We would really love more people to do usability studies with a varied group of technologists, lawyers, journalists, activists, or anyone interested, to improve the UX manifold.
The UI, for now, is very simple and probably not the best. So we can definitely use any feedback or suggestions.
We are available on #tumpa
channel on Freenode. Feel free to drop by with all your comments.
Also, read Kushal's release blog on Tumpa to know more about installation and packaging.
I understand that the title of this post is a little confusing. Recently, while working on the Projects API in Weblate, I came across an interesting issue. The Projects API in Weblate allowed you to get an attribute called source_language
. Every project has only one source_language
and in the API, it was a read-only property.
{
"name": "master_locales",
"slug": "master_locales",
"web": "https://example.site",
"source_language": {
"code": "en",
"name": "English",
"direction": "ltr",
"web_url": "http:/example.site/languages/en/",
"url": "http://example.site/api/languages/en/"
},
"web_url": "http://example.site/projects/master_locales/",
"url": "http://example.site/api/projects/master_locales/",
"components_list_url": "http://example.site/api/projects/master_locales/components/",
"repository_url": "http://example.site/api/projects/master_locales/repository/",
"statistics_url": "http://example.site/api/projects/master_locales/statistics/",
"changes_list_url": "http://example.site/api/projects/master_locales/changes/",
"languages_url": "http://example.site/api/projects/master_locales/languages/"
}
As you can see, unlike the other relational fields, it's not a HyperLinkedIdentityField
. It uses the nested language serializer to show all the attributes of the source_language
.
Now, previously, when a project was created via API, a default language was always assigned to the project and there was no way to define the source_language
while creating the project via API.
Doing GET on Language Serializer when sending POST on Project Serializer
So we needed to add the feature to define the source_language
of the project when we send a POST request to the Project API. And also edit the project via API to update the source_language
. So, to use the same serializer, the request body for the POST request would look something like this:
{
"name": "master_locales",
"slug": "master_locales",
"web": "https://example.site",
"source_language": {
"code": "ru",
"name": "Russian",
"direction": "ltr",
}
}
Now, in general, we would have a python serializer like this:
class LanguageSerializer(serializers.ModelSerializer):
web_url = AbsoluteURLField(source="get_absolute_url", read_only=True)
class Meta:
model = Language
fields = ("code", "name", "direction", "web_url", "url")
extra_kwargs = {
"url": {"view_name": "api:language-detail", "lookup_field": "code"}
}
class ProjectSerializer(serializers.ModelSerializer):
source_language = LanguageSerializer(required=False)
# ...
# Other parts of the serializer
The problem with having code like this is, when the ProjectSerializer
gets a request like shown above and tries to validate the data in the request, it also validates the LanguageSerializer
part. The LanguageSerializer
part whenever it gets data, it will automatically try to validate the data. The code
property of Language model has a unique constraint. So, when LanguageSerializer
tries to validate
{
"code": "ru",
"name": "Russian",
"direction": "ltr",
}
it will throw an error "This field must be unique" for code
property in case a language with codename ru
already exists in the database.
So there are few steps to get this done.
Remove validators from code
field
extra_kwargs = {
"url": {"view_name": "api:language-detail", "lookup_field": "code"},
"code": {"validators": []},
}
Add "code": {"validators": []}
to the extra_kwargs
to remove the validator from the LanguageSerializer on every data request it receives.
Add manual validation for code
field
Removing validator will also remove the validation while doing POST request. Now, the LanguageSerializer in Weblate specifically doesn't support POST, but in any case, you would manually need to add a validation function to the LanguageSerializer
so if someone checks for validity before adding language, it throws an error. To do that, add a function validate_code
like this:
def validate_code(self, value):
check_query = Language.objects.filter(code=value)
if check_query.exists() and not (
isinstance(self.parent, ProjectSerializer)
and self.field_name == "source_language"
):
raise serializers.ValidationError(
"Language with this Language code already exists."
)
if not check_query.exists():
raise serializers.ValidationError(
"Language with this language code was not found."
)
return value
Note: The name of the function must be validate_{field_name}
when you are trying to validate a field based on how DRF handles validation.
Overwrite create()
in ProjectSerializer
Finally, we would want to overwrite the create()
function of ProjectSerializer to:
source_language
data using the above validation to check if the language with that code
existssource_language
key of the validated_data
to have the Language
model object rather than the dictionary, so it can be used to create a project with the foreign key.validata_data
The code would look something like this:
def create(self, validated_data):
source_language_validated = validated_data.get("source_language")
if source_language_validated:
validated_data["source_language"] = Language.objects.get(
code=source_language_validated.get("code")
)
project = Project.objects.create(**validated_data)
return project
And now, if you create a project, using the source_language
key, you can define the source language for the project while using the Project API. There might be several other ways to go about it. But this is one of the ways I found works.
Also, this feature is now live in Weblate 4.* versions which allows you to define the source_language
via the API.
Recently, while trying to work on a query parser feature in Weblate, I came across this search engine library called Whoosh. It provides certain nice features like indexing of text, parsing of search queries, scoring algorithms, etc. One good thing about this library is most of these features are customizable and extensible.
Now, the feature I was trying to implement is an exact search query. An exact search query would behave in a way such that the backend would search for an exact match of any query text provided to it instead of the normal substring search. Whoosh provides a plugin for regex, which can be accessed via whoosh.qparser.RegexPlugin()
. So we can technically go about writing a regex to do the exact match. But a regex search will have worse performance than a simple string comparison.
So, one of the ways of doing a new kind of query parsing is creating a custom whoosh plugin. And that's what this blog is going to be about.
In some cases, you will probably not need a complicated plugin, but just want to extend the feature of an existing plugin to match a different kind of query. For example, let's say you want to extend the ability of SingleQuotePlugin
to parse queries wrapped in either single-quotes or double-quotes.
class QuotePlugin(whoosh.qparser.SingleQuotePlugin):
"""Single and double quotes to specify a term."""
expr = r"(^|(?<=\W))['\"](?P<text>.*?)['\"](?=\s|\]|[)}]|$)"
In the above example, QuotePlugin
extends the already existing SingleQuotePlugin
class. It just overrides the expression to parse the query. The expression, mentioned in the variable expr
is usually a regex expression with ?P<text>
part denoting the TermQuery
. A TermQuery
is the final term/terms searched for in the database. So in the above regex, we say to parse any query such that the TermQuery
is wrapped in between single-quotes or double-quotes.
A query class is the class, whose instance the final parsed term will be. Unless otherwise mentioned, it's usually <Term>
. So if we want our plugin to parse the query and show it as an instance of a custom class, we need to define a custom query class.
class Exact(whoosh.query.Term):
"""Class for queries with exact operator."""
pass
So, as you can say, we can just have a simple class just extending whoosh.query.Term
so that while checking the parsed terms, we can get is as an instance of Exact
. That will help us differentiate the query from a normal Term
instance.
After writing the query class, we will need to write the custom plugin class.
class ExactPlugin(whoosh.qparser.TaggingPlugin):
"""Exact match plugin with quotes to specify an exact term."""
class ExactNode(whoosh.qparser.syntax.TextNode):
qclass = Exact
def r(self):
return "Exact %r" % self.text
expr = r"\=(^|(?<=\W))(['\"]?)(?P<text>.*?)\2(?=\s|\]|[)}]|$)"
nodetype = ExactNode
In the above example, unlike the simple case, we extend TaggingPlugin
instead of any other pre-defined plugin. Most of the pre-defined plugins in whoosh also extend TaggingPlugin
. So it is a good fit as a parent class.
Then, we create a ExactNode
class. This we will assign to the node type for the custom plugin. A node type class basically defines the query class to be used in this custom plugin, along with various representations and properties of the parsed node. qclass
will have the query class created before to denote the Exact
instance to the final parsed term.
Apart from that, we have the expr
which contains the regex just like in the simple example to parse the query term.
After creating the custom plugin, you can:
isinstance()
check when making database queriesmodule.exports = api => {
const isTest = api.env('test');
if (isTest) {
return {
presets: [
[
'@babel/preset-env',
{
modules: false,
},
],
'@babel/preset-react',
],
plugins: [
"@babel/plugin-transform-modules-commonjs",
],
}
} else {
return {
presets: [
[
'@babel/preset-env',
{
modules: false,
},
],
'@babel/preset-react',
],
}
}
};
module.exports = {
moduleNameMapper: {
'^~/(.*)$': '<rootDir>/path/to/jsRoot/$1'
}
}
![]() |
https://caniuse.com/#feat=shadowdomv1 |
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Title</title>
</head>
<body>
<div>
<h1>This is header</h1>
<p>This is a
<a href="https://www.saptaks.blog">
link
</a>
</p>
</div>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Title</title>
</head>
<body>
<div id="shadowHost">
</div>
</body>
</html>
const shadowHost = document.getElementById('shadowHost');
const shadowRoot = shadowHost.attachShadow({mode: 'open'});
shadowRoot.innerHTML = '<h1>Hello Shadow DOM</h1>';
brew install zsh
sh -c "$(curl -fsSL https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"
brew install tmux
chsh -s `which zsh`
# set shell
set -g default-shell /bin/zsh
killall tmux