diff --git a/.github/workflows/firebase-hosting-pull-request.yml b/.github/workflows/firebase-hosting-pull-request.yml index 92f274a..d52410d 100644 --- a/.github/workflows/firebase-hosting-pull-request.yml +++ b/.github/workflows/firebase-hosting-pull-request.yml @@ -9,6 +9,15 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 + + - name: Hugo setup + uses: peaceiris/actions-hugo@v2 + with: + hugo-version: 'latest' + + - name: Build + run: hugo --minify + - uses: FirebaseExtended/action-hosting-deploy@v0 with: repoToken: '${{ secrets.GITHUB_TOKEN }}' diff --git a/public/404.html b/public/404.html deleted file mode 100644 index a0e4107..0000000 --- a/public/404.html +++ /dev/null @@ -1,4 +0,0 @@ -404 Page not found | PrivSec.dev
404
\ No newline at end of file diff --git a/public/about/index.html b/public/about/index.html deleted file mode 100644 index 3a73005..0000000 --- a/public/about/index.html +++ /dev/null @@ -1,14 +0,0 @@ -About Us | PrivSec.dev -

About Us

PrivSec.dev is made by a group of enthusiastic individuals looking to provide practical privacy and security advice for the end user. We are security researchers, developers, system administrators… generally people with technical knowledge and work in the field.

We focus on in-depth system configuration, security analysis, and software/hardware recommendations. Our site is based on technical merits, not ideologies and politics.


Tommy

System Administrator. Benevolent dictator for life @privsec.dev.

Website: tommytran.io
Matrix: @tommy:arcticfoxes.net
Email: tommy@privsec.dev

June

Security Researcher. GrapheneOS Developer. Excessively obsessed with Homestuck.

Matrix: @june:grapheneos.org
Email: june@privsec.dev

Wonderfall

Random guy passing by on the Internet who is interested in all kinds of things. Total nerd.

Websites: wonderfall.space and wonderfall.dev
Matrix: @wonderfall:lysergide.dev

Lberrymage

Accrescent developer. Rust shill and man who can’t stop recycling names of his previous projects.

Website: accrescent.app
Matrix: @lberrymage:matrix.org

Randomhydrosol

GrapheneOS Developer. Friendliest Indian tech support on planet Earth.

Matrix: @randomhydrosol:grapheneos.org

Madaidan

Security Researcher. Whonix developer. Uses Firefox, Telegram, and Linux against his own advice like a hypocrite.

Website: https://madaidans-insecurities.github.io/
Matrix: @madaidan.:matrix.org

\ No newline at end of file diff --git a/public/apps/f-droid-security-analysis/index.html b/public/apps/f-droid-security-analysis/index.html deleted file mode 100644 index 0c556f2..0000000 --- a/public/apps/f-droid-security-analysis/index.html +++ /dev/null @@ -1,41 +0,0 @@ -F-Droid Security Analysis | PrivSec.dev -

F-Droid Security Analysis

F-Droid is a popular alternative app repository for Android, especially known for its main repository dedicated to free and open-source software. F-Droid is often recommended among security and privacy enthusiasts, but how does it stack up against Play Store in practice? This write-up will attempt to emphasize major security issues with F-Droid that you should consider.

Before we start, a few things to keep in mind:

  • The main goal of this write-up was to inform users so they can make responsible choices, not to trash someone else’s work. I have respect for any work done in the name of good intentions. Likewise, please don’t misinterpret the intentions of this article.
  • You have your own reasons for using open-source or free/libre/whatever software which won’t be discussed here. A development model shouldn’t be an excuse for bad practices and shouldn’t lure you into believing that it can provide strong guarantees it cannot.
  • A lot of information in this article is sourced from official and trusted sources, but you’re welcome to do your own research.
  • These analyses do not account for threat models and personal preferences. As the author of this article, I’m only interested in facts and not ideologies.

This is not an in-depth security review, nor is it exhaustive.

1. The trusted party problem

To understand why this is a problem, you’ll have to understand a bit about F-Droid’s architecture, the things it does very differently from other app repositories, and the Android platform security model (some of the issues listed in this article are somewhat out of the scope of the OS security model, but the majority is).

Unlike other repositories, F-Droid signs all the apps in the main repository with its own signing keys (unique per app) at the exception of the very few reproducible builds. A signature is a mathematical scheme that guarantees the authenticity of the applications you download. Upon the installation of an app, Android pins the signature across the entire OS (including user profiles): that’s what we call a trust-on-first-use model since all subsequent updates of the app must have the corresponding signature to be installed.

Normally, the developer is supposed to sign their own app prior to its upload on a distribution channel, whether that is a website or a traditional repository (or both). You don’t have to trust the source (usually recommended by the developer) except for the first installation: future updates will have their authenticity cryptographically guaranteed. The issue with F-Droid is that all apps are signed by the same party (F-Droid) which is also not the developer. You’re now adding another party you’ll have to trust since you still have to trust the developer anyway, which isn’t ideal: the fewer parties, the better.

On the other hand, Play Store now manages the app signing keys too, as Play App Signing is required for app bundles which are required for new apps since August 2021. These signing keys can be uploaded or automatically generated, and are securely stored by Google Cloud Key Management Service. It should be noted that the developer still has to sign the app with an upload key so that Google can verify its authenticity before signing it with the app signing key. For apps created before August 2021 that may have not opted in Play App Signing yet, the developer still manages the private key and is responsible for its security, as a compromised private key can allow a third party to sign and distribute malicious code.

F-Droid requires that the source code of the app is exempt from any proprietary library or ad service, according to their inclusion policy. Usually, that means that some developers will have to maintain a slightly different version of their codebase that should comply with F-Droid’s requirements. Besides, their “quality control” offers close to no guarantees as having access to the source code doesn’t mean it can be easily proofread. Saying Play Store is filled with malicious apps is beyond the point: the false sense of security is a real issue. Users should not think of the F-Droid main repository as free of malicious apps, yet unfortunately many are inclined to believe this.

But… can’t I just trust F-Droid and be done with it?

You don’t have to take my word for it: they openly admit themselves it’s a very basic process relying on badness enumeration (this doesn’t work by the way) which consists in a few scripts scanning the code for proprietary blobs and known trackers. You are therefore not exempted from trusting upstream developers and it goes for any repository.

A tempting idea would be to compare F-Droid to the desktop Linux model where users trust their distribution maintainers out-of-the-box (this can be sane if you’re already trusting the OS anyway), but the desktop platform is intrinsically chaotic and heterogeneous for better and for worse. It really shouldn’t be compared to the Android platform in any way.

While we’ve seen that F-Droid controls the signing servers (much like Play App Signing), F-Droid also fully controls the build servers that run the disposable VMs used for building apps. And as of July 2022, their guest VM image officially runs a version of Debian which reached EOL. Undoubtedly, this raises questions about their whole infrastructure security.

How can you be sure that the app repository can be held to account for the code it delivers?

F-Droid’s answer, interesting yet largely unused, is build reproducibility. While deterministic builds are a neat idea in theory, it requires the developer to make their toolchain match with what F-Droid provides. It’s additional work on both ends sometimes resulting in apps severely lagging behind in updates, so reproducible builds are not as common as we would have wanted. It should be noted that reproducible builds in the main repository can be exclusively developer-signed.

Google’s approach is code transparency for app bundles, which is a simple idea addressing some of the concerns with Play App Signing. A JSON Web Token (JWT) signed by a key private to the developer is included in the app bundle before its upload to Play Store. This token contains a list of DEX files and native .so libraries and their hashes, allowing end-users to verify that the running code was built and signed by the app developer. Code transparency has known limitations, however: not all resources can be verified, and this verification can only be done manually since it’s not part of the Android platform itself (so requiring a code transparency file cannot be enforced by the OS right now). Despite its incompleteness, code transparency is still helpful, easy to implement, and thus something we should see more often as time goes by.

What about other app repositories such as Amazon?

To my current knowledge, the Amazon Appstore has always been wrapping APKs with their own code (including their own trackers), and this means they were effectively resigning submitted APKs.

If you understood correctly the information above, Google can’t do this for apps that haven’t opted in Play App Signing. As for apps concerned by Play App Signing, while Google could technically introduce their own code like Amazon, they wouldn’t do that without telling about it since this will be easily noticeable by the developer and more globally researchers. They have other means on the Android app development platform to do so. Believing they won’t do that based on this principle is not a strong guarantee, however: hence the above paragraph about code transparency for app bundles.

Huawei AppGallery seems to have a similar approach to Google, where submitted apps could be developer-signed, but newer apps will be resigned by Huawei.

2. Slow and irregular updates

Since you’re adding one more party to the mix, that party is now responsible for delivering proper builds of the app: it’s a common thing among traditional Linux distributions and their packaging system. They have to catch up with upstream on a regular basis, but very few do it well (Arch Linux comes to my mind). Others, like Debian, prefer making extensive downstream changes and delivering security fixes for a subset of vulnerabilities assigned to a CVE (yeah, it’s as bad as it sounds, but that’s another topic).

Not only does F-Droid require specific changes for the app to comply with its inclusion policy, which often leads to more maintenance work, it also has a rather strange way of triggering new builds. Part of its build process seems to be automated, which is the least you could expect. Now here’s the thing: app signing keys are on an air-gapped server (meaning it’s disconnected from any network, at least that’s what they claim: see their recommendations for reference), which forces an irregular update cycle where a human has to manually trigger the signing process. It is far from an ideal situation, and you may argue it’s the least to be expected since by entrusting all the signing keys to one party, you could also introduce a single point of failure. Should their system be compromised (whether from the inside or the outside), this could lead to serious security issues affecting plenty of users.

This is one of the main reasons why Signal refused to support the inclusion of a third-party build in the F-Droid official repository. While this GitHub issue is quite old, many points still hold true today.

Considering all this, and the fact that their build process is often broken using outdated tools, you have to expect far slower updates compared to a traditional distribution system. Slow updates mean that you will be exposed to security vulnerabilities more often than you should’ve been. It would be unwise to have a full browser updated through the F-Droid official repository, for instance. F-Droid third-party repositories somewhat mitigate the issue of slow updates since they can be managed directly by the developer. It isn’t ideal either as you will see below.

3. Low target API level (SDK) for client & apps

SDK stands for Software Development Kit and is the collection of software to build apps for a given platform. On Android, a higher SDK level means you’ll be able to make use of modern API levels of which each iteration brings security and privacy improvements. For instance, API level 31 makes use of all these improvements on Android 12.

As you may already know, Android has a strong sandboxing model where each application is sandboxed. You could say that an app compiled with the highest API level benefits from all the latest improvements brought to the app sandbox; as opposed to outdated apps compiled with older API levels, which have a weaker sandbox.

# b/35917228 - /proc/misc access
-# This will go away in a future Android release
-allow untrusted_app_25 proc_misc:file r_file_perms;
-
-# Access to /proc/tty/drivers, to allow apps to determine if they
-# are running in an emulated environment.
-# b/33214085 b/33814662 b/33791054 b/33211769
-# https://github.com/strazzere/anti-emulator/blob/master/AntiEmulator/src/diff/strazzere/anti/emulator/FindEmulator.java
-# This will go away in a future Android release
-allow untrusted_app_25 proc_tty_drivers:file r_file_perms;
-

This is a mere sample of the SELinux exceptions that have to be made on older API levels so that you can understand why it matters.

It turns out the official F-Droid client doesn’t care much about this since it lags behind quite a bit, targeting the API level 25 (Android 7.1) of which some SELinux exceptions were shown above. As a workaround, some users recommended third-party clients such as Foxy Droid or Aurora Droid. While these clients might be technically better, they’re poorly maintained for some, and they also introduce yet another party to the mix. Droid-ify (recently rebreanded to Neo-Store) seems to be a better option than the official client in most aspects.

Furthermore, F-Droid doesn’t enforce a minimum target SDK for the official repository. Play Store does that quite aggressively for new apps and app updates:

  • Since August 2021, Play Store requires new apps to target at least API level 30.
  • Since November 2021, existing apps must at least target API level 30 for updates to be submitted.

While it may seem bothersome, it’s a necessity to keep the app ecosystem modern and healthy. Here, F-Droid sends the wrong message to developers (and even users) because they should care about it, and this is why many of us think it may be even harmful to the FOSS ecosystem. Backward compatibility is often the enemy of security, and while there’s a middle-ground for convenience and obsolescence, it shouldn’t be exaggerated. As a result of this philosophy, the main repository of F-Droid is filled with obsolete apps from another era, just for these apps to be able to run on the more than ten years old Android 4.0 Ice Cream Sandwich. Let’s not make the same mistake as the desktop platforms: instead, complain to your vendors for selling devices with no decent OS/firmware support.

There is little practical reason for developers not to increase the target SDK version (targetSdkVersion) along with each Android release. This attribute matches the version of the platform an app is targeting, and allows access to modern improvements, rules and features on a modern OS. The app can still ensure backwards compatibility in such a way that it can run on older platforms: the minSdkVersion attribute informs the system about the minimum API level required for the application to run. Setting it too low isn’t practical though, because this requires having a lot of fallback code (most of it is handled by common libraries) and separate code paths.

At the time of writing:

Overall statistics do not reflect real-world usage of a given app (people using old devices are not necessarily using your app). If anything, it should be viewed as an underestimation.

4. General lack of good practices

The F-Droid client allows multiple repositories to coexist within the same app. Many of the issues highlighted above were focused on the main official repository which most of the F-Droid users will use anyway. However, having other repositories in a single app also violates the security model of Android which was not designed for this at all. The OS expects you to trust an app repository as a single source of apps, yet F-Droid isn’t that by design as it mixes several repositories in one single app. This is important because the OS management APIs and features (such as UserManager) are not meant for this and see F-Droid as a single source, so you’re trusting the app client to not mess up far more than you should, especially when the privileged extension comes into the picture. This is also a problem with the OS first-party source feature.

On that note, it is also worth noting the repository metadata format isn’t properly signed by lacking whole-file signing and key rotation. Their index v1 format uses JAR signing with jarsigner, which has serious security flaws. It seems that work is in progress on a v2 format with support for apksigner, although the final implementation remains to be seen. This just seems to be an over-engineered and flawed approach since better suited tools such as signify could be used to sign the metadata JSON.

As a matter of fact, the new unattended update API added in API level 31 (Android 12) that allows seamless app updates for app repositories without privileged access to the system (such an approach is not compatible with the security model) won’t work with F-Droid “as is”. It should be mentioned that the aforementioned third-party client Neo-Store supports this API, although the underlying issues about the F-Droid infrastructure largely remain. Indeed, this secure API allowing for unprivileged unattended updates not only requires for the app repository client to target API level 31, but the apps to be updated also have to at least target API level 29.

Their client also lacks TLS certificate pinning, unlike Play Store which improves security for all connections to Google (they generally use a limited set of root CAs including their own). Certificate pinning is a way for apps to increase the security of their connection to services by providing a set of public key hashes of known-good certificates for these services instead of trusting pre-installed CAs. This can avoid some cases where an interception (man-in-the-middle attack) could be possible and lead to various security issues considering you’re trusting the app to deliver you other apps.

It is an important security feature that is also straightforward to implement using the declarative network security configuration available since Android 7.0 (API level 24). See how GrapheneOS pins both root and CA certificates in their app repository client:

<!-- res/xml/network_security_config.xml -->
-<network-security-config>
-    <base-config cleartextTrafficPermitted="false"/>
-    <domain-config>
-        <domain includeSubdomains="true">apps.grapheneos.org</domain>
-        <pin-set>
-            <!-- ISRG Root X1 -->
-            <pin digest="SHA-256">C5+lpZ7tcVwmwQIMcRtPbsQtWLABXhQzejna0wHFr8M=</pin>
-            <!-- ISRG Root X2 -->
-            <pin digest="SHA-256">diGVwiVYbubAI3RW4hB9xU8e/CH2GnkuvVFZE8zmgzI=</pin>
-            <!-- Let's Encrypt R3 -->
-            <pin digest="SHA-256">jQJTbIh0grw0/1TkHSumWb+Fs0Ggogr621gT3PvPKG0=</pin>
-            <!-- Let's Encrypt E1 -->
-            <pin digest="SHA-256">J2/oqMTsdhFWW/n85tys6b4yDBtb6idZayIEBx7QTxA=</pin>
-            ...
-        </pin-set>
-    </domain-config>
-</network-security-config>
-

To be fair, they’ve thought several times about adding certificate pinning to their client at least for the default repositories. Relics of preliminary work can even be found in their current codebase, but it’s unfortunate that they haven’t been able to find any working implementation so far. Given the overly complex nature of F-Droid, that’s largely understandable.

F-Droid also has a problem regarding the adoption of new signature schemes as they held out on the v1 signature scheme (which was horrible and deprecated since 2017) until they were forced by Android 11 requirements to support the newer v2/v3 schemes (v2 was introduced in Android 7.0). Quite frankly, this is straight-up bad, and signing APKs with GPG is no better considering how bad PGP and its reference implementation GPG are (even Debian is trying to move away from it). Ideally, F-Droid should fully move on to newer signature schemes, and should completely phase out the legacy signature schemes which are still being used for some apps and metadata.

5. Confusing UX

It is worth mentioning that their website has (for some reason) always been hosting an outdated APK of F-Droid, and this is still the case today, leading to many users wondering why they can’t install F-Droid on their secondary user profile (due to the downgrade prevention enforced by Android). “Stability” seems to be the main reason mentioned on their part, which doesn’t make sense: either your version isn’t ready to be published in a stable channel, or it is and new users should be able to access it easily.

F-Droid should enforce the approach of prefixing the package name of their alternate builds with org.f-droid for instance (or add a .fdroid suffix as some already have). Building and signing while reusing the package name (application ID) is bad practice as it causes signature verification errors when some users try to update/install these apps from other sources, even directly from the developer. That is again due to the security model of Android which enforces a signature check when installing app updates (or installing them again in a secondary user profile). Note that this is going to be an issue with Play App Signing as well, and developers are encouraged to follow this approach should they intend to distribute their apps through different distribution channels.

This results in a confusing user experience where it’s hard to keep track of who signs each app, and from which repository the app should be downloaded or updated.

6. Misleading permissions approach

F-Droid shows a list of the low-level permissions for each app: these low-level permissions are usually grouped in the standard high-level permissions (Location, Microphone, Camera, etc.) and special toggles (nearby Wi-Fi networks, Bluetooth devices, etc.) that are explicitly based on a type of sensitive data. While showing a list of low-level permissions could be useful information for a developer, it’s often a misleading and inaccurate approach for the end-user. Since Android 6, apps have to request the standard permissions at runtime and do not get them simply by being installed, so showing all the “under the hood” permissions without proper context is not useful and makes the permission model unnecessarily confusing.

F-Droid claims that these low-level permissions are relevant because they support Android 5.1+, meaning they support very outdated versions of Android where apps could have install-time permissions. Anyway, if a technical user wants to see all the manifest permissions for some reason, then they can access the app manifest pretty easily (in fact, exposing the raw manifest would be less misleading). But this is already beyond the scope of this article because anyone who cares about privacy and security wouldn’t run a 8 years old version of Android that has not received security updates for years.

To clear up confusion: even apps targeting an API level below 23 (Android 5.1 or older) do not have permissions granted at install time on modern Android, which instead displays a legacy permission grant dialog. Whether or not permissions are granted at install time does not just depend on the app’s targetSdkVersion. And even if this were the case, the OS package installer on modern Android would’ve been designed to show the requested permissions for those legacy apps.

For example, the low-level permission RECEIVE_BOOT_COMPLETED is referred to in F-Droid as the run at startup description, when in fact this permission is not needed to start at boot and just refers to a specific time broadcasted by the system once it finishes booting, and is not about background usage (though power usage may be a valid concern). To be fair, these short summaries used to be provided by the Android documentation years ago, but the permission model has drastically evolved since then and most of them aren’t accurate anymore.

Allows the app to have itself started as soon as the system has finished booting. This can make it take longer to start the phone and allow the app to slow down the overall phone by always running.

In modern Android, the background restriction toggle is what really provides the ability for apps to run in the background. Some low-level permissions don’t even have a security/privacy impact and shouldn’t be misinterpreted as having one. Anyhow, you can be sure that each dangerous low-level permission has a high-level representation that is disabled by default and needs to be granted dynamically to the app (by a toggle or explicit user consent in general).

Another example to illustrate the shortcomings of this approach would be the QUERY_ALL_PACKAGES low-level permission, which is referred to as the query all packages permission that “allows an app to see all installed packages”. While this is somewhat correct, this can also be misleading: apps do not need QUERY_ALL_PACKAGES to list other apps within the same user profile. Even without this permission, some apps are visible automatically (visibility is restricted by default since Android 11). If an app needs more visibility, it will declare a <queries> element in its manifest file: in other words, QUERY_ALL_PACKAGES is only one way to achieve visibility. Again, this goes to show low-level manifest permissions are not intended to be interpreted as high-level permissions the user should fully comprehend.

Moreover, Play Store restricts the use of highly invasive permissions such as MANAGE_EXTERNAL_STORAGE which allows apps to opt out of scoped storage if they can’t work with more privacy friendly approaches (like a file explorer). Apps that can’t justify their use of this permission (which again has to be granted dynamically) may be removed from Play Store. This is where an app repository can actually be useful in their review process to protect end-users from installing poorly made apps that might compromise their privacy. Not that it matters much if these apps target very old API levels that are inclined to require invasive permissions in the first place…

Conclusion: what should you do?

So far, you have been presented with referenced facts that are easily verifiable. In the next part, I’ll allow myself to express my own thoughts and opinions. You’re free to disagree with them, but don’t let that overshadow the rest.

While some improvements could easily be made, I don’t think F-Droid is in an ideal situation to solve all of these issues because some of them are inherent flaws in their architecture. I’d also argue that their core philosophy is not aligned with some security principles expressed in this article. In any case, I can only wish for them to improve since they’re one of the most popular alternatives to commercial app repositories, and are therefore trusted by a large userbase.

F-Droid is often seen as the only way to get and support open-source apps: that is not the case. Sure, F-Droid could help you in finding FOSS apps that you wouldn’t otherwise have known existed. Many developers also publish their FOSS apps on the Play Store or their website directly. Most of the time, releases are available on GitHub, which is great since each GitHub releases page has an Atom feed. If downloading APKs from regular websites, you can use apksigner to validate the authenticity by comparing the certificate fingerprint against the fingerprint from another source (it wouldn’t matter otherwise).

This is how you may proceed to get the app certificate:

apksigner verify --print-certs --verbose myCoolApp.apk
-

Also, as written above: the OS pins the app signature (for all profiles) upon installation, and enforces signature check for app updates. In practice, this means the source doesn’t matter as much after the initial installation.

For most people, I’d recommend just sticking with Play Store. Play Store isn’t quite flawless, but emphasises the adoption of modern security standards which in turn encourages better privacy practices; as strange as it may sound, Google is not always doing bad things in that regard.

Note: this article obviously can’t address all the flaws related to Play Store itself. Again, the main topic of this article is about F-Droid and should not be seen as an exhaustive comparison between different app repositories.

Should I really care?

It’s up to your threat model, and of course your personal preferences. Most likely, your phone won’t turn into a nuclear weapon if you install F-Droid on it - and this is far from the point that this article is trying to make. Still, I believe the information presented will be valuable for anyone who values a practical approach to privacy (rather than an ideological one). Such an approach is partially described below.

But there is more malware in Play Store! How can you say that it’s more secure?

As explained above, it doesn’t matter as you shouldn’t really rely on any quality control to be the sole guarantee that a software is free of malicious or exploitable code. Play Store and even the Apple App Store may have a considerable amount of malware because a full reverse-engineering of any uploaded app isn’t feasible realistically. However, they fulfill their role quite well, and that is all that is expected of them.

With Play App Signing being effectively enforced for new apps, isn’t Play Store as “flawed” as F-Droid?

I’ve seen this comment repeatedly, and it would be dismissing all the other points made in this article. Also, I strongly suggest that you carefully read the sections related to Play App Signing, and preferably the official documentation on this matter. It’s not a black and white question and there are many more nuances to it.

Aren’t open-source apps more secure? Doesn’t it make F-Droid safer?

You can still find and get your open-source apps elsewhere. And no, open-source apps aren’t necessarily more private or secure. Instead, you should rely on the strong security and privacy guarantees provided by a modern operating system with a robust sandboxing/permission model, namely modern Android, GrapheneOS and iOS. Pay close attention to the permissions you grant, and avoid legacy apps as they could require invasive permissions to run.

When it comes to trackers (this really comes up a lot), you shouldn’t believe in the flawed idea that you can enumerate all of them. The enumerating badness approach is known to be flawed in the security field, and the same applies to privacy. You shouldn’t believe that a random script can detect every single line of code that can be used for data exfiltration. Data exfiltration can be properly prevented in the first place by the permission model, which again denies access to sensitive data by default: this is a simple, yet rigorous and effective approach.

No app should be unnecessarily entrusted with any kind of permission. It is only if you deem it necessary that you should allow access to a type of data, and this access should be as fine-grained as possible. That’s the way the Android platform works (regular apps run in the explicit untrusted_app domain) and continues evolving. Contrary to some popular beliefs, usability and most productivity tasks can still be achieved in a secure and private way.

Isn’t Google evil? Isn’t Play Store spyware?

Some people tend to exaggerate the importance of Google in their threat model, at the cost of pragmatism and security/privacy good practices. Play Store isn’t spyware and can run unprivileged like it does on GrapheneOS (including with unattended updates support). On the vast majority of devices though, Google Play is a privileged app and a core part of the OS that provides low-level system modules. In that case, the trust issues involved with Play App Signing could be considered less important since Google Play is already trusted as a privileged component.

Play Store evidently has some privacy issues given it’s a proprietary service which requires an account (this cannot be circumvented), and Google services have a history of nagging users to enable privacy-invasive features. Again, some of these privacy issues can be mitigated by setting up the Play services compatibility layer from GrapheneOS which runs Play services and Play Store in the regular app sandbox (the untrusted_app domain). ProtonAOSP also shares that feature. This solution could very well be ported to other Android-based operating systems. If you want to go further, consider using a properly configured account with the least amount of personally indentifiable information possible (note that the phone number requirement appears to be region-dependent).

If you don’t have Play services installed, you can use a third-party Play Store client called Aurora Store. Aurora Store has some issues of its own, and some of them overlap in fact with F-Droid. Aurora Store somehow still requires the legacy storage permission, has yet to implement certificate pinning, has been known to sometimes retrieve wrong versions of apps, and distributed account tokens over cleartext HTTP until fairly recently; not that it matters much since tokens were designed to be shared between users, which is already concerning. I’d recommend against using the shared “anonymous” accounts feature: you should make your own throwaway account with minimal information.

You should also keep an eye on the great work GrapheneOS does on their future app repository. It will be a simple, secure, modern app repository for a curated list of high-quality apps, some of which will have their own builds (for instance, Signal still uses their original 1024-bits RSA key that has never been rotated since then). Inspired by this work, a GrapheneOS community member is developing a more generic app repository called Accrescent. Hopefully, we’ll see well-made alternatives like these flourish.

Thanks to the GrapheneOS community for proofreading this article. Bear in mind that these are not official recommendations from the GrapheneOS project.

Post-publication note: it’s unfortunate that the release of this article mostly triggered a negative response from the F-Droid team which prefers to dismiss this article on several occasions rather than bringing relevant counterpoints. Some of their core members are also involved in a harassment campaign towards projects and security researchers that do not share their views. While this article remains a technical one, there are definitely ethical concerns to take into consideration.

\ No newline at end of file diff --git a/public/apps/index.html b/public/apps/index.html deleted file mode 100644 index eaeb1d3..0000000 --- a/public/apps/index.html +++ /dev/null @@ -1,7 +0,0 @@ -Applications | PrivSec.dev

F-Droid Security Analysis

F-Droid is a popular alternative app repository for Android, especially known for its main repository dedicated to free and open-source software. F-Droid is often recommended among security and privacy enthusiasts, but how does it stack up against Play Store in practice? This write-up will attempt to emphasize major security issues with F-Droid that you should consider. -Before we start, a few things to keep in mind: -The main goal of this write-up was to inform users so they can make responsible choices, not to trash someone else’s work....

25 min · 5298 words · Wonderfall
\ No newline at end of file diff --git a/public/apps/index.xml b/public/apps/index.xml deleted file mode 100644 index 63e2458..0000000 --- a/public/apps/index.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - Applications on PrivSec.dev - https://privsec.dev/apps/ - Recent content in Applications on PrivSec.dev - Hugo -- gohugo.io - - F-Droid Security Analysis - https://privsec.dev/apps/f-droid-security-analysis/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://privsec.dev/apps/f-droid-security-analysis/ - F-Droid is a popular alternative app repository for Android, especially known for its main repository dedicated to free and open-source software. F-Droid is often recommended among security and privacy enthusiasts, but how does it stack up against Play Store in practice? This write-up will attempt to emphasize major security issues with F-Droid that you should consider. -Before we start, a few things to keep in mind: -The main goal of this write-up was to inform users so they can make responsible choices, not to trash someone else&rsquo;s work. - - - - diff --git a/public/apps/page/1/index.html b/public/apps/page/1/index.html deleted file mode 100644 index e7dfac3..0000000 --- a/public/apps/page/1/index.html +++ /dev/null @@ -1 +0,0 @@ -https://privsec.dev/apps/ \ No newline at end of file diff --git a/public/assets/css/stylesheet.8b523f1730c922e314350296d83fd666efa16519ca136320a93df674d00b6325.css b/public/assets/css/stylesheet.8b523f1730c922e314350296d83fd666efa16519ca136320a93df674d00b6325.css deleted file mode 100644 index feac396..0000000 --- a/public/assets/css/stylesheet.8b523f1730c922e314350296d83fd666efa16519ca136320a93df674d00b6325.css +++ /dev/null @@ -1,7 +0,0 @@ -/* - PaperMod v6 - License: MIT https://github.com/adityatelange/hugo-PaperMod/blob/master/LICENSE - Copyright (c) 2020 nanxiaobei and adityatelange - Copyright (c) 2021-2022 adityatelange -*/ -:root{--gap:24px;--content-gap:20px;--nav-width:1024px;--main-width:720px;--header-height:60px;--footer-height:60px;--radius:8px;--theme:rgb(255, 255, 255);--entry:rgb(255, 255, 255);--primary:rgb(30, 30, 30);--secondary:rgb(108, 108, 108);--tertiary:rgb(214, 214, 214);--content:rgb(31, 31, 31);--hljs-bg:rgb(28, 29, 33);--code-bg:rgb(245, 245, 245);--border:rgb(238, 238, 238)}.dark{--theme:rgb(29, 30, 32);--entry:rgb(46, 46, 51);--primary:rgb(218, 218, 219);--secondary:rgb(155, 156, 157);--tertiary:rgb(65, 66, 68);--content:rgb(196, 196, 197);--hljs-bg:rgb(46, 46, 51);--code-bg:rgb(55, 56, 62);--border:rgb(51, 51, 51)}.list{background:var(--code-bg)}.dark.list{background:var(--theme)}*,::after,::before{box-sizing:border-box}html{-webkit-tap-highlight-color:transparent;overflow-y:scroll}a,button,body,h1,h2,h3,h4,h5,h6{color:var(--primary)}body{font-family:-apple-system,BlinkMacSystemFont,segoe ui,Roboto,Oxygen,Ubuntu,Cantarell,open sans,helvetica neue,sans-serif;font-size:18px;line-height:1.6;word-break:break-word;background:var(--theme)}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section,table{display:block}h1,h2,h3,h4,h5,h6{line-height:1.2}h1,h2,h3,h4,h5,h6,p{margin-top:0;margin-bottom:0}ul{padding:0}a{text-decoration:none}body,figure,ul{margin:0}table{width:100%;border-collapse:collapse;border-spacing:0;overflow-x:auto;word-break:keep-all}button,input,textarea{padding:0;font:inherit;background:0 0;border:0}input,textarea{outline:0}button,input[type=button],input[type=submit]{cursor:pointer}input:-webkit-autofill,textarea:-webkit-autofill{box-shadow:0 0 0 50px var(--theme)inset}img{display:block;max-width:100%}.not-found{position:absolute;left:0;right:0;display:flex;align-items:center;justify-content:center;height:80%;font-size:160px;font-weight:700}.archive-posts{width:100%;font-size:16px}.archive-year{margin-top:40px}.archive-year:not(:last-of-type){border-bottom:2px solid var(--border)}.archive-month{display:flex;align-items:flex-start;padding:10px 0}.archive-month-header{margin:25px 0;width:200px}.archive-month:not(:last-of-type){border-bottom:1px solid var(--border)}.archive-entry{position:relative;padding:5px;margin:10px 0}.archive-entry-title{margin:5px 0;font-weight:400}.archive-count,.archive-meta{color:var(--secondary);font-size:14px}.footer,.top-link{font-size:12px;color:var(--secondary)}.footer{max-width:calc(var(--main-width) + var(--gap) * 2);margin:auto;padding:calc((var(--footer-height) - var(--gap))/2)var(--gap);text-align:center;line-height:24px}.footer span{margin-inline-start:1px;margin-inline-end:1px}.footer span:last-child{white-space:nowrap}.footer a{color:inherit;border-bottom:1px solid var(--secondary)}.footer a:hover{border-bottom:1px solid var(--primary)}.top-link{visibility:hidden;position:fixed;bottom:60px;right:30px;z-index:99;background:var(--tertiary);width:42px;height:42px;padding:12px;border-radius:64px;transition:visibility .5s,opacity .8s linear}.top-link,.top-link svg{filter:drop-shadow(0 0 0 var(--theme))}.footer a:hover,.top-link:hover{color:var(--primary)}.top-link:focus,#theme-toggle:focus{outline:0}.nav{display:flex;flex-wrap:wrap;justify-content:space-between;max-width:calc(var(--nav-width) + var(--gap) * 2);margin-inline-start:auto;margin-inline-end:auto;line-height:var(--header-height)}.nav a{display:block}.logo,#menu{display:flex;margin:auto var(--gap)}.logo{flex-wrap:inherit}.logo a{font-size:24px;font-weight:700}.logo a img{display:inline;vertical-align:middle;pointer-events:none;transform:translate(0,-10%);border-radius:6px;margin-inline-end:8px}button#theme-toggle{font-size:26px;margin:auto 4px}body.dark #moon{vertical-align:middle;display:none}body:not(.dark) #sun{display:none}#menu{list-style:none;word-break:keep-all;overflow-x:auto;white-space:nowrap}#menu li+li{margin-inline-start:var(--gap)}#menu a{font-size:16px}#menu .active{font-weight:500;border-bottom:2px solid}.lang-switch li,.lang-switch ul,.logo-switches{display:inline-flex;margin:auto 4px}.lang-switch{display:flex;flex-wrap:inherit}.lang-switch a{margin:auto 3px;font-size:16px;font-weight:500}.logo-switches{flex-wrap:inherit}.main{position:relative;min-height:calc(100vh - var(--header-height) - var(--footer-height));max-width:calc(var(--main-width) + var(--gap) * 2);margin:auto;padding:var(--gap)}.page-header h1{font-size:40px}.pagination{display:flex}.pagination a{color:var(--theme);font-size:13px;line-height:36px;background:var(--primary);border-radius:calc(36px/2);padding:0 16px}.pagination .next{margin-inline-start:auto}.social-icons{padding:12px 0}.social-icons a:not(:last-of-type){margin-inline-end:12px}.social-icons a svg{height:26px;width:26px}code{direction:ltr}div.highlight,pre{position:relative}.copy-code{display:none;position:absolute;top:4px;right:4px;color:rgba(255,255,255,.8);background:rgba(78,78,78,.8);border-radius:var(--radius);padding:0 5px;font-size:14px;user-select:none}div.highlight:hover .copy-code,pre:hover .copy-code{display:block}.first-entry{position:relative;display:flex;flex-direction:column;justify-content:center;min-height:320px;margin:var(--gap)0 calc(var(--gap) * 2)}.first-entry .entry-header{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:3}.first-entry .entry-header h1{font-size:34px;line-height:1.3}.first-entry .entry-content{margin:14px 0;font-size:16px;-webkit-line-clamp:3}.first-entry .entry-footer{font-size:14px}.home-info .entry-content{-webkit-line-clamp:unset}.post-entry{position:relative;margin-bottom:var(--gap);padding:var(--gap);background:var(--entry);border-radius:var(--radius);transition:transform .1s;border:1px solid var(--border)}.post-entry:active{transform:scale(.96)}.tag-entry .entry-cover{display:none}.entry-header h2{font-size:24px;line-height:1.3}.entry-content{margin:8px 0;color:var(--secondary);font-size:14px;line-height:1.6;overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.entry-footer{color:var(--secondary);font-size:13px}.entry-link{position:absolute;left:0;right:0;top:0;bottom:0}.entry-cover,.entry-isdraft{font-size:14px;color:var(--secondary)}.entry-cover{margin-bottom:var(--gap);text-align:center}.entry-cover img{border-radius:var(--radius);pointer-events:none;width:100%;height:auto}.entry-cover a{color:var(--secondary);box-shadow:0 1px 0 var(--primary)}.page-header,.post-header{margin:24px auto var(--content-gap)}.post-title{margin-bottom:2px;font-size:40px}.post-description{margin-top:10px;margin-bottom:5px}.post-meta,.breadcrumbs{color:var(--secondary);font-size:14px;display:flex;flex-wrap:wrap}.post-meta .i18n_list li{display:inline-flex;list-style:none;margin:auto 3px;box-shadow:0 1px 0 var(--secondary)}.breadcrumbs a{font-size:16px}.post-content{color:var(--content)}.post-content h3,.post-content h4,.post-content h5,.post-content h6{margin:24px 0 16px}.post-content h1{margin:40px auto 32px;font-size:40px}.post-content h2{margin:32px auto 24px;font-size:32px}.post-content h3{font-size:24px}.post-content h4{font-size:16px}.post-content h5{font-size:14px}.post-content h6{font-size:12px}.post-content a,.toc a:hover{box-shadow:0 1px}.post-content a code{margin:auto 0;border-radius:0;box-shadow:0 -1px 0 var(--primary)inset}.post-content del{text-decoration:none;background:linear-gradient(to right,var(--primary) 100%,transparent 0)0/1px 1px repeat-x}.post-content dl,.post-content ol,.post-content p,.post-content figure,.post-content ul{margin-bottom:var(--content-gap)}.post-content ol,.post-content ul{padding-inline-start:20px}.post-content li{margin-top:5px}.post-content li p{margin-bottom:0}.post-content dl{display:flex;flex-wrap:wrap;margin:0}.post-content dt{width:25%;font-weight:700}.post-content dd{width:75%;margin-inline-start:0;padding-inline-start:10px}.post-content dd~dd,.post-content dt~dt{margin-top:10px}.post-content table{margin-bottom:32px}.post-content table th,.post-content table:not(.highlighttable,.highlight table,.gist .highlight) td{min-width:80px;padding:12px 8px;line-height:1.5;border-bottom:1px solid var(--border)}.post-content table th{font-size:14px;text-align:start}.post-content table:not(.highlighttable) td code:only-child{margin:auto 0}.post-content .highlight table{border-radius:var(--radius)}.post-content .highlight:not(table){margin:10px auto;background:var(--hljs-bg)!important;border-radius:var(--radius);direction:ltr}.post-content li>.highlight{margin-inline-end:0}.post-content ul pre{margin-inline-start:calc(var(--gap) * -2)}.post-content .highlight pre{margin:0}.post-content .highlighttable{table-layout:fixed}.post-content .highlighttable td:first-child{width:40px}.post-content .highlighttable td .linenodiv{padding-inline-end:0!important}.post-content .highlighttable td .highlight,.post-content .highlighttable td .linenodiv pre{margin-bottom:0}.post-content code{margin:auto 4px;padding:4px 6px;font-size:.78em;line-height:1.5;background:var(--code-bg);border-radius:2px}.post-content pre code{display:block;margin:auto 0;padding:10px;color:#d5d5d6;background:var(--hljs-bg)!important;border-radius:var(--radius);overflow-x:auto;word-break:break-all}.post-content blockquote{margin:20px 0;padding:0 14px;border-inline-start:3px solid var(--primary)}.post-content hr{margin:30px 0;height:2px;background:var(--tertiary);border:0}.post-content iframe{max-width:100%}.post-content img{border-radius:4px;margin:1rem 0}.post-content img[src*="#center"]{margin:1rem auto}.post-content figure.align-center{text-align:center}.post-content figure>figcaption{color:var(--primary);font-size:16px;font-weight:700;margin:8px 0 16px}.post-content figure>figcaption>p{color:var(--secondary);font-size:14px;font-weight:400}.toc{margin:0 2px 40px;border:1px solid var(--border);background:var(--code-bg);border-radius:var(--radius);padding:.4em}.dark .toc{background:var(--entry)}.toc details summary{cursor:zoom-in;margin-inline-start:20px}.toc details[open] summary{cursor:zoom-out}.toc .details{display:inline;font-weight:500}.toc .inner{margin:0 20px;padding:10px 20px}.toc li ul{margin-inline-start:var(--gap)}.toc summary:focus{outline:0}.post-footer{margin-top:56px}.post-tags li{display:inline-block;margin-inline-end:3px;margin-bottom:5px}.post-tags a,.share-buttons,.paginav{border-radius:var(--radius);background:var(--code-bg);border:1px solid var(--border)}.post-tags a{display:block;padding-inline-start:14px;padding-inline-end:14px;color:var(--secondary);font-size:14px;line-height:34px;background:var(--code-bg)}.post-tags a:hover,.paginav a:hover{background:var(--border)}.share-buttons{margin:14px 0;padding-inline-start:var(--radius);display:flex;justify-content:center;overflow-x:auto}.share-buttons a{margin-top:10px}.share-buttons a:not(:last-of-type){margin-inline-end:12px}h1:hover .anchor,h2:hover .anchor,h3:hover .anchor,h4:hover .anchor,h5:hover .anchor,h6:hover .anchor{display:inline-flex;color:var(--secondary);margin-inline-start:8px;font-weight:500;user-select:none}.paginav{margin:10px 0;display:flex;line-height:30px;border-radius:var(--radius)}.paginav a{padding-inline-start:14px;padding-inline-end:14px;border-radius:var(--radius)}.paginav .title{letter-spacing:1px;text-transform:uppercase;font-size:small;color:var(--secondary)}.paginav .prev,.paginav .next{width:50%}.paginav span:hover:not(.title){box-shadow:0 1px}.paginav .next{margin-inline-start:auto;text-align:right}[dir=rtl] .paginav .next{text-align:left}h1>a>svg{display:inline}img.in-text{display:inline;margin:auto}.buttons,.main .profile{display:flex;justify-content:center}.main .profile{align-items:center;min-height:calc(100vh - var(--header-height) - var(--footer-height) - (var(--gap) * 2));text-align:center}.profile .profile_inner h1{padding:12px 0}.profile img{display:inline-table;border-radius:50%}.buttons{flex-wrap:wrap;max-width:400px;margin:0 auto}.button{background:var(--tertiary);border-radius:var(--radius);margin:8px;padding:6px;transition:transform .1s}.button-inner{padding:0 8px}.button:active{transform:scale(.96)}#searchbox input{padding:4px 10px;width:100%;color:var(--primary);font-weight:700;border:2px solid var(--tertiary);border-radius:var(--radius)}#searchbox input:focus{border-color:var(--secondary)}#searchResults li{list-style:none;border-radius:var(--radius);padding:10px;margin:10px 0;position:relative;font-weight:500}#searchResults{margin:10px 0;width:100%}#searchResults li:active{transition:transform .1s;transform:scale(.98)}#searchResults a{position:absolute;width:100%;height:100%;top:0;left:0;outline:none}#searchResults .focus{transform:scale(.98);border:2px solid var(--tertiary)}.terms-tags li{display:inline-block;margin:10px;font-weight:500}.terms-tags a{display:block;padding:3px 10px;background:var(--tertiary);border-radius:6px;transition:transform .1s}.terms-tags a:active{background:var(--tertiary);transform:scale(.96)}.hljs-comment,.hljs-quote{color:#b6b18b}.hljs-deletion,.hljs-name,.hljs-regexp,.hljs-selector-class,.hljs-selector-id,.hljs-tag,.hljs-template-variable,.hljs-variable{color:#eb3c54}.hljs-built_in,.hljs-builtin-name,.hljs-link,.hljs-literal,.hljs-meta,.hljs-number,.hljs-params,.hljs-type{color:#e7ce56}.hljs-attribute{color:#ee7c2b}.hljs-addition,.hljs-bullet,.hljs-string,.hljs-symbol{color:#4fb4d7}.hljs-section,.hljs-title{color:#78bb65}.hljs-keyword,.hljs-selector-tag{color:#b45ea4}.hljs{display:block;overflow-x:auto;background:#1c1d21;color:#c0c5ce;padding:.5em}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}::-webkit-scrollbar-track{background:0 0}.list:not(.dark)::-webkit-scrollbar-track{background:var(--code-bg)}::-webkit-scrollbar-thumb{background:var(--tertiary);border:5px solid var(--theme);border-radius:var(--radius)}.list:not(.dark)::-webkit-scrollbar-thumb{border:5px solid var(--code-bg)}::-webkit-scrollbar-thumb:hover{background:var(--secondary)}::-webkit-scrollbar:not(.highlighttable,.highlight table,.gist .highlight){background:var(--theme)}.post-content .highlighttable td .highlight pre code::-webkit-scrollbar{display:none}.post-content :not(table) ::-webkit-scrollbar-thumb{border:2px solid var(--hljs-bg);background:#717175}.post-content :not(table) ::-webkit-scrollbar-thumb:hover{background:#a3a3a5}.gist table::-webkit-scrollbar-thumb{border:2px solid #fff;background:#adadad}.gist table::-webkit-scrollbar-thumb:hover{background:#707070}.post-content table::-webkit-scrollbar-thumb{border-width:2px}@media screen and (min-width:768px){::-webkit-scrollbar{width:19px;height:11px}}@media screen and (max-width:768px){:root{--gap:14px}.profile img{transform:scale(.85)}.first-entry{min-height:260px}.archive-month{flex-direction:column}.archive-year{margin-top:20px}.footer{padding:calc((var(--footer-height) - var(--gap) - 10px)/2)var(--gap)}}@media screen and (max-width:900px){.list .top-link{transform:translateY(-5rem)}}@media(prefers-reduced-motion){.terms-tags a:active,.button:active,.post-entry:active,.top-link,#searchResults .focus,#searchResults li:active{transform:none}} \ No newline at end of file diff --git a/public/assets/js/highlight.f413e19d0714851f6474e7ee9632408e58ac146fbdbe62747134bea2fa3415e0.js b/public/assets/js/highlight.f413e19d0714851f6474e7ee9632408e58ac146fbdbe62747134bea2fa3415e0.js deleted file mode 100644 index 93a6f86..0000000 --- a/public/assets/js/highlight.f413e19d0714851f6474e7ee9632408e58ac146fbdbe62747134bea2fa3415e0.js +++ /dev/null @@ -1,44 +0,0 @@ -/* - Highlight.js 10.2.1 (32fb9a1d) - License: BSD-3-Clause - Copyright (c) 2006-2020, Ivan Sagalaev -*/ -var hljs=function(){"use strict";function e(n){Object.freeze(n);var t="function"==typeof n;return Object.getOwnPropertyNames(n).forEach((function(r){!Object.hasOwnProperty.call(n,r)||null===n[r]||"object"!=typeof n[r]&&"function"!=typeof n[r]||t&&("caller"===r||"callee"===r||"arguments"===r)||Object.isFrozen(n[r])||e(n[r])})),n}class n{constructor(e){void 0===e.data&&(e.data={}),this.data=e.data}ignoreMatch(){this.ignore=!0}}function t(e){return e.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function r(e,...n){var t={};for(const n in e)t[n]=e[n];return n.forEach((function(e){for(const n in e)t[n]=e[n]})),t}function a(e){return e.nodeName.toLowerCase()}var i=Object.freeze({__proto__:null,escapeHTML:t,inherit:r,nodeStream:function(e){var n=[];return function e(t,r){for(var i=t.firstChild;i;i=i.nextSibling)3===i.nodeType?r+=i.nodeValue.length:1===i.nodeType&&(n.push({event:"start",offset:r,node:i}),r=e(i,r),a(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:r,node:i}));return r}(e,0),n},mergeStreams:function(e,n,r){var i=0,s="",o=[];function l(){return e.length&&n.length?e[0].offset!==n[0].offset?e[0].offset"}function u(e){s+=""}function g(e){("start"===e.event?c:u)(e.node)}for(;e.length||n.length;){var d=l();if(s+=t(r.substring(i,d[0].offset)),i=d[0].offset,d===e){o.reverse().forEach(u);do{g(d.splice(0,1)[0]),d=l()}while(d===e&&d.length&&d[0].offset===i);o.reverse().forEach(c)}else"start"===d[0].event?o.push(d[0].node):o.pop(),g(d.splice(0,1)[0])}return s+t(r.substr(i))}});const s="",o=e=>!!e.kind;class l{constructor(e,n){this.buffer="",this.classPrefix=n.classPrefix,e.walk(this)}addText(e){this.buffer+=t(e)}openNode(e){if(!o(e))return;let n=e.kind;e.sublanguage||(n=`${this.classPrefix}${n}`),this.span(n)}closeNode(e){o(e)&&(this.buffer+=s)}value(){return this.buffer}span(e){this.buffer+=``}}class c{constructor(){this.rootNode={children:[]},this.stack=[this.rootNode]}get top(){return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(e){this.top.children.push(e)}openNode(e){const n={kind:e,children:[]};this.add(n),this.stack.push(n)}closeNode(){if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)}walk(e){return this.constructor._walk(e,this.rootNode)}static _walk(e,n){return"string"==typeof n?e.addText(n):n.children&&(e.openNode(n),n.children.forEach(n=>this._walk(e,n)),e.closeNode(n)),e}static _collapse(e){"string"!=typeof e&&e.children&&(e.children.every(e=>"string"==typeof e)?e.children=[e.children.join("")]:e.children.forEach(e=>{c._collapse(e)}))}}class u extends c{constructor(e){super(),this.options=e}addKeyword(e,n){""!==e&&(this.openNode(n),this.addText(e),this.closeNode())}addText(e){""!==e&&this.add(e)}addSublanguage(e,n){const t=e.root;t.kind=n,t.sublanguage=!0,this.add(t)}toHTML(){return new l(this,this.options).value()}finalize(){return!0}}function g(e){return e?"string"==typeof e?e:e.source:null}const d="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",h={begin:"\\\\[\\s\\S]",relevance:0},f={className:"string",begin:"'",end:"'",illegal:"\\n",contains:[h]},p={className:"string",begin:'"',end:'"',illegal:"\\n",contains:[h]},m={begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},b=function(e,n,t={}){var a=r({className:"comment",begin:e,end:n,contains:[]},t);return a.contains.push(m),a.contains.push({className:"doctag",begin:"(?:TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):",relevance:0}),a},v=b("//","$"),x=b("/\\*","\\*/"),E=b("#","$");var _=Object.freeze({__proto__:null,IDENT_RE:"[a-zA-Z]\\w*",UNDERSCORE_IDENT_RE:"[a-zA-Z_]\\w*",NUMBER_RE:"\\b\\d+(\\.\\d+)?",C_NUMBER_RE:d,BINARY_NUMBER_RE:"\\b(0b[01]+)",RE_STARTERS_RE:"!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",SHEBANG:(e={})=>{const n=/^#![ ]*\//;return e.binary&&(e.begin=function(...e){return e.map(e=>g(e)).join("")}(n,/.*\b/,e.binary,/\b.*/)),r({className:"meta",begin:n,end:/$/,relevance:0,"on:begin":(e,n)=>{0!==e.index&&n.ignoreMatch()}},e)},BACKSLASH_ESCAPE:h,APOS_STRING_MODE:f,QUOTE_STRING_MODE:p,PHRASAL_WORDS_MODE:m,COMMENT:b,C_LINE_COMMENT_MODE:v,C_BLOCK_COMMENT_MODE:x,HASH_COMMENT_MODE:E,NUMBER_MODE:{className:"number",begin:"\\b\\d+(\\.\\d+)?",relevance:0},C_NUMBER_MODE:{className:"number",begin:d,relevance:0},BINARY_NUMBER_MODE:{className:"number",begin:"\\b(0b[01]+)",relevance:0},CSS_NUMBER_MODE:{className:"number",begin:"\\b\\d+(\\.\\d+)?(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",relevance:0},REGEXP_MODE:{begin:/(?=\/[^/\n]*\/)/,contains:[{className:"regexp",begin:/\//,end:/\/[gimuy]*/,illegal:/\n/,contains:[h,{begin:/\[/,end:/\]/,relevance:0,contains:[h]}]}]},TITLE_MODE:{className:"title",begin:"[a-zA-Z]\\w*",relevance:0},UNDERSCORE_TITLE_MODE:{className:"title",begin:"[a-zA-Z_]\\w*",relevance:0},METHOD_GUARD:{begin:"\\.\\s*[a-zA-Z_]\\w*",relevance:0},END_SAME_AS_BEGIN:function(e){return Object.assign(e,{"on:begin":(e,n)=>{n.data._beginMatch=e[1]},"on:end":(e,n)=>{n.data._beginMatch!==e[1]&&n.ignoreMatch()}})}}),w="of and for in not or if then".split(" ");function N(e,n){return n?+n:function(e){return w.includes(e.toLowerCase())}(e)?0:1}const y={props:["language","code","autodetect"],data:function(){return{detectedLanguage:"",unknownLanguage:!1}},computed:{className(){return this.unknownLanguage?"":"hljs "+this.detectedLanguage},highlighted(){if(!this.autoDetect&&!hljs.getLanguage(this.language))return console.warn(`The language "${this.language}" you specified could not be found.`),this.unknownLanguage=!0,t(this.code);let e;return this.autoDetect?(e=hljs.highlightAuto(this.code),this.detectedLanguage=e.language):(e=hljs.highlight(this.language,this.code,this.ignoreIllegals),this.detectectLanguage=this.language),e.value},autoDetect(){return!(this.language&&(e=this.autodetect,!e&&""!==e));var e},ignoreIllegals:()=>!0},render(e){return e("pre",{},[e("code",{class:this.className,domProps:{innerHTML:this.highlighted}})])}},R={install(e){e.component("highlightjs",y)}},k=t,M=r,{nodeStream:O,mergeStreams:L}=i,A=Symbol("nomatch");return function(t){var a=[],i=Object.create(null),s=Object.create(null),o=[],l=!0,c=/(^(<[^>]+>|\t|)+|\n)/gm,d="Could not find the language '{}', did you forget to load/include a language module?";const h={disableAutodetect:!0,name:"Plain text",contains:[]};var f={noHighlightRe:/^(no-?highlight)$/i,languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:null,__emitter:u};function p(e){return f.noHighlightRe.test(e)}function m(e,n,t,r){var a={code:n,language:e};j("before:highlight",a);var i=a.result?a.result:b(a.language,a.code,t,r);return i.code=a.code,j("after:highlight",i),i}function b(e,t,a,s){var o=t;function c(e,n){var t=E.case_insensitive?n[0].toLowerCase():n[0];return Object.prototype.hasOwnProperty.call(e.keywords,t)&&e.keywords[t]}function u(){null!=R.subLanguage?function(){if(""!==L){var e=null;if("string"==typeof R.subLanguage){if(!i[R.subLanguage])return void O.addText(L);e=b(R.subLanguage,L,!0,M[R.subLanguage]),M[R.subLanguage]=e.top}else e=v(L,R.subLanguage.length?R.subLanguage:null);R.relevance>0&&(I+=e.relevance),O.addSublanguage(e.emitter,e.language)}}():function(){if(!R.keywords)return void O.addText(L);let e=0;R.keywordPatternRe.lastIndex=0;let n=R.keywordPatternRe.exec(L),t="";for(;n;){t+=L.substring(e,n.index);const r=c(R,n);if(r){const[e,a]=r;O.addText(t),t="",I+=a,O.addKeyword(n[0],e)}else t+=n[0];e=R.keywordPatternRe.lastIndex,n=R.keywordPatternRe.exec(L)}t+=L.substr(e),O.addText(t)}(),L=""}function h(e){return e.className&&O.openNode(e.className),R=Object.create(e,{parent:{value:R}})}function p(e){return 0===R.matcher.regexIndex?(L+=e[0],1):(S=!0,0)}var m={};function x(t,r){var i=r&&r[0];if(L+=t,null==i)return u(),0;if("begin"===m.type&&"end"===r.type&&m.index===r.index&&""===i){if(L+=o.slice(r.index,r.index+1),!l){const n=Error("0 width match regex");throw n.languageName=e,n.badRule=m.rule,n}return 1}if(m=r,"begin"===r.type)return function(e){var t=e[0],r=e.rule;const a=new n(r),i=[r.__beforeBegin,r["on:begin"]];for(const n of i)if(n&&(n(e,a),a.ignore))return p(t);return r&&r.endSameAsBegin&&(r.endRe=RegExp(t.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&"),"m")),r.skip?L+=t:(r.excludeBegin&&(L+=t),u(),r.returnBegin||r.excludeBegin||(L=t)),h(r),r.returnBegin?0:t.length}(r);if("illegal"===r.type&&!a){const e=Error('Illegal lexeme "'+i+'" for mode "'+(R.className||"")+'"');throw e.mode=R,e}if("end"===r.type){var s=function(e){var t=e[0],r=o.substr(e.index),a=function e(t,r,a){let i=function(e,n){var t=e&&e.exec(n);return t&&0===t.index}(t.endRe,a);if(i){if(t["on:end"]){const e=new n(t);t["on:end"](r,e),e.ignore&&(i=!1)}if(i){for(;t.endsParent&&t.parent;)t=t.parent;return t}}if(t.endsWithParent)return e(t.parent,r,a)}(R,e,r);if(!a)return A;var i=R;i.skip?L+=t:(i.returnEnd||i.excludeEnd||(L+=t),u(),i.excludeEnd&&(L=t));do{R.className&&O.closeNode(),R.skip||R.subLanguage||(I+=R.relevance),R=R.parent}while(R!==a.parent);return a.starts&&(a.endSameAsBegin&&(a.starts.endRe=a.endRe),h(a.starts)),i.returnEnd?0:t.length}(r);if(s!==A)return s}if("illegal"===r.type&&""===i)return 1;if(j>1e5&&j>3*r.index)throw Error("potential infinite loop, way more iterations than matches");return L+=i,i.length}var E=y(e);if(!E)throw console.error(d.replace("{}",e)),Error('Unknown language: "'+e+'"');var _=function(e){function n(n,t){return RegExp(g(n),"m"+(e.case_insensitive?"i":"")+(t?"g":""))}class t{constructor(){this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0}addRule(e,n){n.position=this.position++,this.matchIndexes[this.matchAt]=n,this.regexes.push([n,e]),this.matchAt+=function(e){return RegExp(e.toString()+"|").exec("").length-1}(e)+1}compile(){0===this.regexes.length&&(this.exec=()=>null);const e=this.regexes.map(e=>e[1]);this.matcherRe=n(function(e,n="|"){for(var t=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./,r=0,a="",i=0;i0&&(a+=n),a+="(";o.length>0;){var l=t.exec(o);if(null==l){a+=o;break}a+=o.substring(0,l.index),o=o.substring(l.index+l[0].length),"\\"===l[0][0]&&l[1]?a+="\\"+(+l[1]+s):(a+=l[0],"("===l[0]&&r++)}a+=")"}return a}(e),!0),this.lastIndex=0}exec(e){this.matcherRe.lastIndex=this.lastIndex;const n=this.matcherRe.exec(e);if(!n)return null;const t=n.findIndex((e,n)=>n>0&&void 0!==e),r=this.matchIndexes[t];return n.splice(0,t),Object.assign(n,r)}}class a{constructor(){this.rules=[],this.multiRegexes=[],this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(e){if(this.multiRegexes[e])return this.multiRegexes[e];const n=new t;return this.rules.slice(e).forEach(([e,t])=>n.addRule(e,t)),n.compile(),this.multiRegexes[e]=n,n}resumingScanAtSamePosition(){return 0!==this.regexIndex}considerAll(){this.regexIndex=0}addRule(e,n){this.rules.push([e,n]),"begin"===n.type&&this.count++}exec(e){const n=this.getMatcher(this.regexIndex);n.lastIndex=this.lastIndex;let t=n.exec(e);if(this.resumingScanAtSamePosition())if(t&&t.index===this.lastIndex);else{const n=this.getMatcher(0);n.lastIndex=this.lastIndex+1,t=n.exec(e)}return t&&(this.regexIndex+=t.position+1,this.regexIndex===this.count&&this.considerAll()),t}}function i(e,n){const t=e.input[e.index-1],r=e.input[e.index+e[0].length];"."!==t&&"."!==r||n.ignoreMatch()}if(e.contains&&e.contains.includes("self"))throw Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.");return function t(s,o){const l=s;if(s.compiled)return l;s.compiled=!0,s.__beforeBegin=null,s.keywords=s.keywords||s.beginKeywords;let c=null;if("object"==typeof s.keywords&&(c=s.keywords.$pattern,delete s.keywords.$pattern),s.keywords&&(s.keywords=function(e,n){var t={};return"string"==typeof e?r("keyword",e):Object.keys(e).forEach((function(n){r(n,e[n])})),t;function r(e,r){n&&(r=r.toLowerCase()),r.split(" ").forEach((function(n){var r=n.split("|");t[r[0]]=[e,N(r[0],r[1])]}))}}(s.keywords,e.case_insensitive)),s.lexemes&&c)throw Error("ERR: Prefer `keywords.$pattern` to `mode.lexemes`, BOTH are not allowed. (see mode reference) ");return l.keywordPatternRe=n(s.lexemes||c||/\w+/,!0),o&&(s.beginKeywords&&(s.begin="\\b("+s.beginKeywords.split(" ").join("|")+")(?=\\b|\\s)",s.__beforeBegin=i),s.begin||(s.begin=/\B|\b/),l.beginRe=n(s.begin),s.endSameAsBegin&&(s.end=s.begin),s.end||s.endsWithParent||(s.end=/\B|\b/),s.end&&(l.endRe=n(s.end)),l.terminator_end=g(s.end)||"",s.endsWithParent&&o.terminator_end&&(l.terminator_end+=(s.end?"|":"")+o.terminator_end)),s.illegal&&(l.illegalRe=n(s.illegal)),void 0===s.relevance&&(s.relevance=1),s.contains||(s.contains=[]),s.contains=[].concat(...s.contains.map((function(e){return function(e){return e.variants&&!e.cached_variants&&(e.cached_variants=e.variants.map((function(n){return r(e,{variants:null},n)}))),e.cached_variants?e.cached_variants:function e(n){return!!n&&(n.endsWithParent||e(n.starts))}(e)?r(e,{starts:e.starts?r(e.starts):null}):Object.isFrozen(e)?r(e):e}("self"===e?s:e)}))),s.contains.forEach((function(e){t(e,l)})),s.starts&&t(s.starts,o),l.matcher=function(e){const n=new a;return e.contains.forEach(e=>n.addRule(e.begin,{rule:e,type:"begin"})),e.terminator_end&&n.addRule(e.terminator_end,{type:"end"}),e.illegal&&n.addRule(e.illegal,{type:"illegal"}),n}(l),l}(e)}(E),w="",R=s||_,M={},O=new f.__emitter(f);!function(){for(var e=[],n=R;n!==E;n=n.parent)n.className&&e.unshift(n.className);e.forEach(e=>O.openNode(e))}();var L="",I=0,T=0,j=0,S=!1;try{for(R.matcher.considerAll();;){j++,S?S=!1:R.matcher.considerAll(),R.matcher.lastIndex=T;const e=R.matcher.exec(o);if(!e)break;const n=x(o.substring(T,e.index),e);T=e.index+n}return x(o.substr(T)),O.closeAllNodes(),O.finalize(),w=O.toHTML(),{relevance:I,value:w,language:e,illegal:!1,emitter:O,top:R}}catch(n){if(n.message&&n.message.includes("Illegal"))return{illegal:!0,illegalBy:{msg:n.message,context:o.slice(T-100,T+100),mode:n.mode},sofar:w,relevance:0,value:k(o),emitter:O};if(l)return{illegal:!1,relevance:0,value:k(o),emitter:O,language:e,top:R,errorRaised:n};throw n}}function v(e,n){n=n||f.languages||Object.keys(i);var t=function(e){const n={relevance:0,emitter:new f.__emitter(f),value:k(e),illegal:!1,top:h};return n.emitter.addText(e),n}(e),r=t;return n.filter(y).filter(T).forEach((function(n){var a=b(n,e,!1);a.language=n,a.relevance>r.relevance&&(r=a),a.relevance>t.relevance&&(r=t,t=a)})),r.language&&(t.second_best=r),t}function x(e){return f.tabReplace||f.useBR?e.replace(c,e=>"\n"===e?f.useBR?"
":e:f.tabReplace?e.replace(/\t/g,f.tabReplace):e):e}function E(e){let n=null;const t=function(e){var n=e.className+" ";n+=e.parentNode?e.parentNode.className:"";const t=f.languageDetectRe.exec(n);if(t){var r=y(t[1]);return r||(console.warn(d.replace("{}",t[1])),console.warn("Falling back to no-highlight mode for this block.",e)),r?t[1]:"no-highlight"}return n.split(/\s+/).find(e=>p(e)||y(e))}(e);if(p(t))return;j("before:highlightBlock",{block:e,language:t}),f.useBR?(n=document.createElement("div")).innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n"):n=e;const r=n.textContent,a=t?m(t,r,!0):v(r),i=O(n);if(i.length){const e=document.createElement("div");e.innerHTML=a.value,a.value=L(i,O(e),r)}a.value=x(a.value),j("after:highlightBlock",{block:e,result:a}),e.innerHTML=a.value,e.className=function(e,n,t){var r=n?s[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),e.includes(r)||a.push(r),a.join(" ").trim()}(e.className,t,a.language),e.result={language:a.language,re:a.relevance,relavance:a.relevance},a.second_best&&(e.second_best={language:a.second_best.language,re:a.second_best.relevance,relavance:a.second_best.relevance})}const w=()=>{if(!w.called){w.called=!0;var e=document.querySelectorAll("pre code");a.forEach.call(e,E)}};function y(e){return e=(e||"").toLowerCase(),i[e]||i[s[e]]}function I(e,{languageName:n}){"string"==typeof e&&(e=[e]),e.forEach(e=>{s[e]=n})}function T(e){var n=y(e);return n&&!n.disableAutodetect}function j(e,n){var t=e;o.forEach((function(e){e[t]&&e[t](n)}))}Object.assign(t,{highlight:m,highlightAuto:v,fixMarkup:function(e){return console.warn("fixMarkup is deprecated and will be removed entirely in v11.0"),console.warn("Please see https://github.com/highlightjs/highlight.js/issues/2534"),x(e)},highlightBlock:E,configure:function(e){f=M(f,e)},initHighlighting:w,initHighlightingOnLoad:function(){window.addEventListener("DOMContentLoaded",w,!1)},registerLanguage:function(e,n){var r=null;try{r=n(t)}catch(n){if(console.error("Language definition for '{}' could not be registered.".replace("{}",e)),!l)throw n;console.error(n),r=h}r.name||(r.name=e),i[e]=r,r.rawDefinition=n.bind(null,t),r.aliases&&I(r.aliases,{languageName:e})},listLanguages:function(){return Object.keys(i)},getLanguage:y,registerAliases:I,requireLanguage:function(e){var n=y(e);if(n)return n;throw Error("The '{}' language is required, but not loaded.".replace("{}",e))},autoDetection:T,inherit:M,addPlugin:function(e){o.push(e)},vuePlugin:R}),t.debugMode=function(){l=!1},t.safeMode=function(){l=!0},t.versionString="10.2.1";for(const n in _)"object"==typeof _[n]&&e(_[n]);return Object.assign(t,_),t}({})}();"object"==typeof exports&&"undefined"!=typeof module&&(module.exports=hljs); -hljs.registerLanguage("apache",function(){"use strict";return function(e){var n={className:"number",begin:"\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?"};return{name:"Apache config",aliases:["apacheconf"],case_insensitive:!0,contains:[e.HASH_COMMENT_MODE,{className:"section",begin:"",contains:[n,{className:"number",begin:":\\d{1,5}"},e.inherit(e.QUOTE_STRING_MODE,{relevance:0})]},{className:"attribute",begin:/\w+/,relevance:0,keywords:{nomarkup:"order deny allow setenv rewriterule rewriteengine rewritecond documentroot sethandler errordocument loadmodule options header listen serverroot servername"},starts:{end:/$/,relevance:0,keywords:{literal:"on off all deny allow"},contains:[{className:"meta",begin:"\\s\\[",end:"\\]$"},{className:"variable",begin:"[\\$%]\\{",end:"\\}",contains:["self",{className:"number",begin:"[\\$%]\\d+"}]},n,{className:"number",begin:"\\d+"},e.QUOTE_STRING_MODE]}}],illegal:/\S/}}}()); -hljs.registerLanguage("bash",function(){"use strict";return function(e){const s={};Object.assign(s,{className:"variable",variants:[{begin:/\$[\w\d#@][\w\d_]*/},{begin:/\$\{/,end:/\}/,contains:[{begin:/:-/,contains:[s]}]}]});const t={className:"subst",begin:/\$\(/,end:/\)/,contains:[e.BACKSLASH_ESCAPE]},n={className:"string",begin:/"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,s,t]};t.contains.push(n);const a={begin:/\$\(\(/,end:/\)\)/,contains:[{begin:/\d+#[0-9a-f]+/,className:"number"},e.NUMBER_MODE,s]},i=e.SHEBANG({binary:"(fish|bash|zsh|sh|csh|ksh|tcsh|dash|scsh)",relevance:10}),c={className:"function",begin:/\w[\w\d_]*\s*\(\s*\)\s*\{/,returnBegin:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/\w[\w\d_]*/})],relevance:0};return{name:"Bash",aliases:["sh","zsh"],keywords:{$pattern:/\b-?[a-z\._-]+\b/,keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp",_:"-ne -eq -lt -gt -f -d -e -s -l -a"},contains:[i,e.SHEBANG(),c,a,e.HASH_COMMENT_MODE,n,{className:"",begin:/\\"/},{className:"string",begin:/'/,end:/'/},s]}}}()); -hljs.registerLanguage("c-like",function(){"use strict";return function(e){function t(e){return"(?:"+e+")?"}var n="(decltype\\(auto\\)|"+t("[a-zA-Z_]\\w*::")+"[a-zA-Z_]\\w*"+t("<.*?>")+")",r={className:"keyword",begin:"\\b[a-z\\d_]*_t\\b"},a={className:"string",variants:[{begin:'(u8?|U|L)?"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE]},{begin:"(u8?|U|L)?'(\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4,8}|[0-7]{3}|\\S)|.)",end:"'",illegal:"."},e.END_SAME_AS_BEGIN({begin:/(?:u8?|U|L)?R"([^()\\ ]{0,16})\(/,end:/\)([^()\\ ]{0,16})"/})]},i={className:"number",variants:[{begin:"\\b(0b[01']+)"},{begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],relevance:0},s={className:"meta",begin:/#\s*[a-z]+\b/,end:/$/,keywords:{"meta-keyword":"if else elif endif define undef warning error line pragma _Pragma ifdef ifndef include"},contains:[{begin:/\\\n/,relevance:0},e.inherit(a,{className:"meta-string"}),{className:"meta-string",begin:/<.*?>/,end:/$/,illegal:"\\n"},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},o={className:"title",begin:t("[a-zA-Z_]\\w*::")+e.IDENT_RE,relevance:0},c=t("[a-zA-Z_]\\w*::")+e.IDENT_RE+"\\s*\\(",l={keyword:"int float while private char char8_t char16_t char32_t catch import module export virtual operator sizeof dynamic_cast|10 typedef const_cast|10 const for static_cast|10 union namespace unsigned long volatile static protected bool template mutable if public friend do goto auto void enum else break extern using asm case typeid wchar_t short reinterpret_cast|10 default double register explicit signed typename try this switch continue inline delete alignas alignof constexpr consteval constinit decltype concept co_await co_return co_yield requires noexcept static_assert thread_local restrict final override atomic_bool atomic_char atomic_schar atomic_uchar atomic_short atomic_ushort atomic_int atomic_uint atomic_long atomic_ulong atomic_llong atomic_ullong new throw return and and_eq bitand bitor compl not not_eq or or_eq xor xor_eq",built_in:"std string wstring cin cout cerr clog stdin stdout stderr stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set pair bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap priority_queue make_pair array shared_ptr abort terminate abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf future isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc realloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf endl initializer_list unique_ptr _Bool complex _Complex imaginary _Imaginary",literal:"true false nullptr NULL"},d=[r,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,i,a],_={variants:[{begin:/=/,end:/;/},{begin:/\(/,end:/\)/},{beginKeywords:"new throw return else",end:/;/}],keywords:l,contains:d.concat([{begin:/\(/,end:/\)/,keywords:l,contains:d.concat(["self"]),relevance:0}]),relevance:0},u={className:"function",begin:"("+n+"[\\*&\\s]+)+"+c,returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:l,illegal:/[^\w\s\*&:<>]/,contains:[{begin:"decltype\\(auto\\)",keywords:l,relevance:0},{begin:c,returnBegin:!0,contains:[o],relevance:0},{className:"params",begin:/\(/,end:/\)/,keywords:l,relevance:0,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,i,r,{begin:/\(/,end:/\)/,keywords:l,relevance:0,contains:["self",e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,i,r]}]},r,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,s]};return{aliases:["c","cc","h","c++","h++","hpp","hh","hxx","cxx"],keywords:l,disableAutodetect:!0,illegal:"",keywords:l,contains:["self",r]},{begin:e.IDENT_RE+"::",keywords:l},{className:"class",beginKeywords:"class struct",end:/[{;:]/,contains:[{begin://,contains:["self"]},e.TITLE_MODE]}]),exports:{preprocessor:s,strings:a,keywords:l}}}}()); -hljs.registerLanguage("c",function(){"use strict";return function(e){var n=e.requireLanguage("c-like").rawDefinition();return n.name="C",n.aliases=["c","h"],n}}()); -hljs.registerLanguage("coffeescript",function(){"use strict";const e=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],n=["true","false","null","undefined","NaN","Infinity"],a=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]);return function(r){var t={keyword:e.concat(["then","unless","until","loop","by","when","and","or","is","isnt","not"]).filter((e=>n=>!e.includes(n))(["var","const","let","function","static"])).join(" "),literal:n.concat(["yes","no","on","off"]).join(" "),built_in:a.concat(["npm","print"]).join(" ")},i="[A-Za-z$_][0-9A-Za-z$_]*",s={className:"subst",begin:/#\{/,end:/}/,keywords:t},o=[r.BINARY_NUMBER_MODE,r.inherit(r.C_NUMBER_MODE,{starts:{end:"(\\s*/)?",relevance:0}}),{className:"string",variants:[{begin:/'''/,end:/'''/,contains:[r.BACKSLASH_ESCAPE]},{begin:/'/,end:/'/,contains:[r.BACKSLASH_ESCAPE]},{begin:/"""/,end:/"""/,contains:[r.BACKSLASH_ESCAPE,s]},{begin:/"/,end:/"/,contains:[r.BACKSLASH_ESCAPE,s]}]},{className:"regexp",variants:[{begin:"///",end:"///",contains:[s,r.HASH_COMMENT_MODE]},{begin:"//[gim]{0,3}(?=\\W)",relevance:0},{begin:/\/(?![ *]).*?(?![\\]).\/[gim]{0,3}(?=\W)/}]},{begin:"@"+i},{subLanguage:"javascript",excludeBegin:!0,excludeEnd:!0,variants:[{begin:"```",end:"```"},{begin:"`",end:"`"}]}];s.contains=o;var c=r.inherit(r.TITLE_MODE,{begin:i}),l={className:"params",begin:"\\([^\\(]",returnBegin:!0,contains:[{begin:/\(/,end:/\)/,keywords:t,contains:["self"].concat(o)}]};return{name:"CoffeeScript",aliases:["coffee","cson","iced"],keywords:t,illegal:/\/\*/,contains:o.concat([r.COMMENT("###","###"),r.HASH_COMMENT_MODE,{className:"function",begin:"^\\s*"+i+"\\s*=\\s*(\\(.*\\))?\\s*\\B[-=]>",end:"[-=]>",returnBegin:!0,contains:[c,l]},{begin:/[:\(,=]\s*/,relevance:0,contains:[{className:"function",begin:"(\\(.*\\))?\\s*\\B[-=]>",end:"[-=]>",returnBegin:!0,contains:[l]}]},{className:"class",beginKeywords:"class",end:"$",illegal:/[:="\[\]]/,contains:[{beginKeywords:"extends",endsWithParent:!0,illegal:/[:="\[\]]/,contains:[c]},c]},{begin:i+":",end:":",returnBegin:!0,returnEnd:!0,relevance:0}])}}}()); -hljs.registerLanguage("cpp",function(){"use strict";return function(e){var i=e.requireLanguage("c-like").rawDefinition();return i.disableAutodetect=!1,i.name="C++",i.aliases=["cc","c++","h++","hpp","hh","hxx","cxx"],i}}()); -hljs.registerLanguage("csharp",function(){"use strict";return function(e){var n={keyword:"abstract as base bool break byte case catch char checked const continue decimal default delegate do double enum event explicit extern finally fixed float for foreach goto if implicit in init int interface internal is lock long object operator out override params private protected public readonly ref sbyte sealed short sizeof stackalloc static string struct switch this try typeof uint ulong unchecked unsafe ushort using virtual void volatile while add alias ascending async await by descending dynamic equals from get global group into join let nameof on orderby partial remove select set value var when where yield",literal:"null false true"},i=e.inherit(e.TITLE_MODE,{begin:"[a-zA-Z](\\.?\\w)*"}),a={className:"number",variants:[{begin:"\\b(0b[01']+)"},{begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],relevance:0},s={className:"string",begin:'@"',end:'"',contains:[{begin:'""'}]},t=e.inherit(s,{illegal:/\n/}),l={className:"subst",begin:"{",end:"}",keywords:n},r=e.inherit(l,{illegal:/\n/}),c={className:"string",begin:/\$"/,end:'"',illegal:/\n/,contains:[{begin:"{{"},{begin:"}}"},e.BACKSLASH_ESCAPE,r]},o={className:"string",begin:/\$@"/,end:'"',contains:[{begin:"{{"},{begin:"}}"},{begin:'""'},l]},g=e.inherit(o,{illegal:/\n/,contains:[{begin:"{{"},{begin:"}}"},{begin:'""'},r]});l.contains=[o,c,s,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,a,e.C_BLOCK_COMMENT_MODE],r.contains=[g,c,t,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,a,e.inherit(e.C_BLOCK_COMMENT_MODE,{illegal:/\n/})];var d={variants:[o,c,s,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},E={begin:"<",end:">",contains:[{beginKeywords:"in out"},i]},_=e.IDENT_RE+"(<"+e.IDENT_RE+"(\\s*,\\s*"+e.IDENT_RE+")*>)?(\\[\\])?",b={begin:"@"+e.IDENT_RE,relevance:0};return{name:"C#",aliases:["cs","c#"],keywords:n,illegal:/::/,contains:[e.COMMENT("///","$",{returnBegin:!0,contains:[{className:"doctag",variants:[{begin:"///",relevance:0},{begin:"\x3c!--|--\x3e"},{begin:""}]}]}),e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:"meta",begin:"#",end:"$",keywords:{"meta-keyword":"if else elif endif define undef warning error line region endregion pragma checksum"}},d,a,{beginKeywords:"class interface",end:/[{;=]/,illegal:/[^\s:,]/,contains:[{beginKeywords:"where class"},i,E,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{beginKeywords:"namespace",end:/[{;=]/,illegal:/[^\s:]/,contains:[i,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{beginKeywords:"record",end:/[{;=]/,illegal:/[^\s:]/,contains:[i,E,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:"meta",begin:"^\\s*\\[",excludeBegin:!0,end:"\\]",excludeEnd:!0,contains:[{className:"meta-string",begin:/"/,end:/"/}]},{beginKeywords:"new return throw await else",relevance:0},{className:"function",begin:"("+_+"\\s+)+"+e.IDENT_RE+"\\s*(\\<.+\\>)?\\s*\\(",returnBegin:!0,end:/\s*[{;=]/,excludeEnd:!0,keywords:n,contains:[{begin:e.IDENT_RE+"\\s*(\\<.+\\>)?\\s*\\(",returnBegin:!0,contains:[e.TITLE_MODE,E],relevance:0},{className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:n,relevance:0,contains:[d,a,e.C_BLOCK_COMMENT_MODE]},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},b]}}}()); -hljs.registerLanguage("css",function(){"use strict";return function(e){var n={begin:/(?:[A-Z\_\.\-]+|--[a-zA-Z0-9_-]+)\s*:/,returnBegin:!0,end:";",endsWithParent:!0,contains:[{className:"attribute",begin:/\S/,end:":",excludeEnd:!0,starts:{endsWithParent:!0,excludeEnd:!0,contains:[{begin:/[\w-]+\(/,returnBegin:!0,contains:[{className:"built_in",begin:/[\w-]+/},{begin:/\(/,end:/\)/,contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.CSS_NUMBER_MODE]}]},e.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,e.C_BLOCK_COMMENT_MODE,{className:"number",begin:"#[0-9A-Fa-f]+"},{className:"meta",begin:"!important"}]}}]};return{name:"CSS",case_insensitive:!0,illegal:/[=\/|'\$]/,contains:[e.C_BLOCK_COMMENT_MODE,{className:"selector-id",begin:/#[A-Za-z0-9_-]+/},{className:"selector-class",begin:/\.[A-Za-z0-9_-]+/},{className:"selector-attr",begin:/\[/,end:/\]/,illegal:"$",contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},{className:"selector-pseudo",begin:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{begin:"@(page|font-face)",lexemes:"@[a-z-]+",keywords:"@page @font-face"},{begin:"@",end:"[{;]",illegal:/:/,returnBegin:!0,contains:[{className:"keyword",begin:/@\-?\w[\w]*(\-\w+)*/},{begin:/\s/,endsWithParent:!0,excludeEnd:!0,relevance:0,keywords:"and or not only",contains:[{begin:/[a-z-]+:/,className:"attribute"},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.CSS_NUMBER_MODE]}]},{className:"selector-tag",begin:"[a-zA-Z-][a-zA-Z0-9_-]*",relevance:0},{begin:"{",end:"}",illegal:/\S/,contains:[e.C_BLOCK_COMMENT_MODE,n]}]}}}()); -hljs.registerLanguage("diff",function(){"use strict";return function(e){return{name:"Diff",aliases:["patch"],contains:[{className:"meta",relevance:10,variants:[{begin:/^@@ +\-\d+,\d+ +\+\d+,\d+ +@@$/},{begin:/^\*\*\* +\d+,\d+ +\*\*\*\*$/},{begin:/^\-\-\- +\d+,\d+ +\-\-\-\-$/}]},{className:"comment",variants:[{begin:/Index: /,end:/$/},{begin:/={3,}/,end:/$/},{begin:/^\-{3}/,end:/$/},{begin:/^\*{3} /,end:/$/},{begin:/^\+{3}/,end:/$/},{begin:/^\*{15}$/}]},{className:"addition",begin:"^\\+",end:"$"},{className:"deletion",begin:"^\\-",end:"$"},{className:"addition",begin:"^\\!",end:"$"}]}}}()); -hljs.registerLanguage("go",function(){"use strict";return function(e){var n={keyword:"break default func interface select case map struct chan else goto package switch const fallthrough if range type continue for import return var go defer bool byte complex64 complex128 float32 float64 int8 int16 int32 int64 string uint8 uint16 uint32 uint64 int uint uintptr rune",literal:"true false iota nil",built_in:"append cap close complex copy imag len make new panic print println real recover delete"};return{name:"Go",aliases:["golang"],keywords:n,illegal:"e(n)).join("")}return function(a){var s={className:"number",relevance:0,variants:[{begin:/([\+\-]+)?[\d]+_[\d_]+/},{begin:a.NUMBER_RE}]},i=a.COMMENT();i.variants=[{begin:/;/,end:/$/},{begin:/#/,end:/$/}];var t={className:"variable",variants:[{begin:/\$[\w\d"][\w\d_]*/},{begin:/\$\{(.*?)}/}]},r={className:"literal",begin:/\bon|off|true|false|yes|no\b/},l={className:"string",contains:[a.BACKSLASH_ESCAPE],variants:[{begin:"'''",end:"'''",relevance:10},{begin:'"""',end:'"""',relevance:10},{begin:'"',end:'"'},{begin:"'",end:"'"}]},c={begin:/\[/,end:/\]/,contains:[i,r,t,l,s,"self"],relevance:0},g="("+[/[A-Za-z0-9_-]+/,/"(\\"|[^"])*"/,/'[^']*'/].map(n=>e(n)).join("|")+")";return{name:"TOML, also INI",aliases:["toml"],case_insensitive:!0,illegal:/\S/,contains:[i,{className:"section",begin:/\[+/,end:/\]+/},{begin:n(g,"(\\s*\\.\\s*",g,")*",n("(?=",/\s*=\s*[^#\s]/,")")),className:"attr",starts:{end:/$/,contains:[i,c,r,t,l,s]}}]}}}()); -hljs.registerLanguage("java",function(){"use strict";function e(e){return e?"string"==typeof e?e:e.source:null}function n(e){return a("(",e,")?")}function a(...n){return n.map(n=>e(n)).join("")}function s(...n){return"("+n.map(n=>e(n)).join("|")+")"}return function(e){var t="false synchronized int abstract float private char boolean var static null if const for true while long strictfp finally protected import native final void enum else break transient catch instanceof byte super volatile case assert short package default double public try this switch continue throws protected public private module requires exports do",i={className:"meta",begin:"@[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*",contains:[{begin:/\(/,end:/\)/,contains:["self"]}]},r=e=>a("[",e,"]+([",e,"_]*[",e,"]+)?"),c={className:"number",variants:[{begin:`\\b(0[bB]${r("01")})[lL]?`},{begin:`\\b(0${r("0-7")})[dDfFlL]?`},{begin:a(/\b0[xX]/,s(a(r("a-fA-F0-9"),/\./,r("a-fA-F0-9")),a(r("a-fA-F0-9"),/\.?/),a(/\./,r("a-fA-F0-9"))),/([pP][+-]?(\d+))?/,/[fFdDlL]?/)},{begin:a(/\b/,s(a(/\d*\./,r("\\d")),r("\\d")),/[eE][+-]?[\d]+[dDfF]?/)},{begin:a(/\b/,r(/\d/),n(/\.?/),n(r(/\d/)),/[dDfFlL]?/)}],relevance:0};return{name:"Java",aliases:["jsp"],keywords:t,illegal:/<\/|#/,contains:[e.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{begin:/\w+@/,relevance:0},{className:"doctag",begin:"@[A-Za-z]+"}]}),e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{className:"class",beginKeywords:"class interface enum",end:/[{;=]/,excludeEnd:!0,keywords:"class interface enum",illegal:/[:"\[\]]/,contains:[{beginKeywords:"extends implements"},e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"new throw return else",relevance:0},{className:"function",begin:"([À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*(<[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*(\\s*,\\s*[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*)*>)?\\s+)+"+e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:t,contains:[{begin:e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,relevance:0,contains:[e.UNDERSCORE_TITLE_MODE]},{className:"params",begin:/\(/,end:/\)/,keywords:t,relevance:0,contains:[i,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE]},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},c,i]}}}()); -hljs.registerLanguage("javascript",function(){"use strict";const e=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],n=["true","false","null","undefined","NaN","Infinity"],a=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]);function s(e){return r("(?=",e,")")}function r(...e){return e.map(e=>(function(e){return e?"string"==typeof e?e:e.source:null})(e)).join("")}return function(t){var i="[A-Za-z$_][0-9A-Za-z$_]*",c={begin:/<[A-Za-z0-9\\._:-]+/,end:/\/[A-Za-z0-9\\._:-]+>|\/>/},o={$pattern:"[A-Za-z$_][0-9A-Za-z$_]*",keyword:e.join(" "),literal:n.join(" "),built_in:a.join(" ")},l={className:"number",variants:[{begin:"\\b(0[bB][01]+)n?"},{begin:"\\b(0[oO][0-7]+)n?"},{begin:t.C_NUMBER_RE+"n?"}],relevance:0},E={className:"subst",begin:"\\$\\{",end:"\\}",keywords:o,contains:[]},d={begin:"html`",end:"",starts:{end:"`",returnEnd:!1,contains:[t.BACKSLASH_ESCAPE,E],subLanguage:"xml"}},g={begin:"css`",end:"",starts:{end:"`",returnEnd:!1,contains:[t.BACKSLASH_ESCAPE,E],subLanguage:"css"}},u={className:"string",begin:"`",end:"`",contains:[t.BACKSLASH_ESCAPE,E]};E.contains=[t.APOS_STRING_MODE,t.QUOTE_STRING_MODE,d,g,u,l,t.REGEXP_MODE];var b=E.contains.concat([{begin:/\(/,end:/\)/,contains:["self"].concat(E.contains,[t.C_BLOCK_COMMENT_MODE,t.C_LINE_COMMENT_MODE])},t.C_BLOCK_COMMENT_MODE,t.C_LINE_COMMENT_MODE]),_={className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,contains:b};return{name:"JavaScript",aliases:["js","jsx","mjs","cjs"],keywords:o,contains:[t.SHEBANG({binary:"node",relevance:5}),{className:"meta",relevance:10,begin:/^\s*['"]use (strict|asm)['"]/},t.APOS_STRING_MODE,t.QUOTE_STRING_MODE,d,g,u,t.C_LINE_COMMENT_MODE,t.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{className:"doctag",begin:"@[A-Za-z]+",contains:[{className:"type",begin:"\\{",end:"\\}",relevance:0},{className:"variable",begin:i+"(?=\\s*(-)|$)",endsParent:!0,relevance:0},{begin:/(?=[^\n])\s/,relevance:0}]}]}),t.C_BLOCK_COMMENT_MODE,l,{begin:r(/[{,\n]\s*/,s(r(/(((\/\/.*$)|(\/\*(.|\n)*\*\/))\s*)*/,i+"\\s*:"))),relevance:0,contains:[{className:"attr",begin:i+s("\\s*:"),relevance:0}]},{begin:"("+t.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*",keywords:"return throw case",contains:[t.C_LINE_COMMENT_MODE,t.C_BLOCK_COMMENT_MODE,t.REGEXP_MODE,{className:"function",begin:"(\\([^(]*(\\([^(]*(\\([^(]*\\))?\\))?\\)|"+t.UNDERSCORE_IDENT_RE+")\\s*=>",returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:t.UNDERSCORE_IDENT_RE},{className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:o,contains:b}]}]},{begin:/,/,relevance:0},{className:"",begin:/\s/,end:/\s*/,skip:!0},{variants:[{begin:"<>",end:""},{begin:c.begin,end:c.end}],subLanguage:"xml",contains:[{begin:c.begin,end:c.end,skip:!0,contains:["self"]}]}],relevance:0},{className:"function",beginKeywords:"function",end:/\{/,excludeEnd:!0,contains:[t.inherit(t.TITLE_MODE,{begin:i}),_],illegal:/\[|%/},{begin:/\$[(.]/},t.METHOD_GUARD,{className:"class",beginKeywords:"class",end:/[{;=]/,excludeEnd:!0,illegal:/[:"\[\]]/,contains:[{beginKeywords:"extends"},t.UNDERSCORE_TITLE_MODE]},{beginKeywords:"constructor",end:/\{/,excludeEnd:!0},{begin:"(get|set)\\s+(?="+i+"\\()",end:/{/,keywords:"get set",contains:[t.inherit(t.TITLE_MODE,{begin:i}),{begin:/\(\)/},_]}],illegal:/#(?!!)/}}}()); -hljs.registerLanguage("json",function(){"use strict";return function(n){var e={literal:"true false null"},i=[n.C_LINE_COMMENT_MODE,n.C_BLOCK_COMMENT_MODE],t=[n.QUOTE_STRING_MODE,n.C_NUMBER_MODE],a={end:",",endsWithParent:!0,excludeEnd:!0,contains:t,keywords:e},l={begin:"{",end:"}",contains:[{className:"attr",begin:/"/,end:/"/,contains:[n.BACKSLASH_ESCAPE],illegal:"\\n"},n.inherit(a,{begin:/:/})].concat(i),illegal:"\\S"},s={begin:"\\[",end:"\\]",contains:[n.inherit(a)],illegal:"\\S"};return t.push(l,s),i.forEach((function(n){t.push(n)})),{name:"JSON",contains:t,keywords:e,illegal:"\\S"}}}()); -hljs.registerLanguage("kotlin",function(){"use strict";return function(e){var n={keyword:"abstract as val var vararg get set class object open private protected public noinline crossinline dynamic final enum if else do while for when throw try catch finally import package is in fun override companion reified inline lateinit init interface annotation data sealed internal infix operator out by constructor super tailrec where const inner suspend typealias external expect actual",built_in:"Byte Short Char Int Long Boolean Float Double Void Unit Nothing",literal:"true false null"},a={className:"symbol",begin:e.UNDERSCORE_IDENT_RE+"@"},i={className:"subst",begin:"\\${",end:"}",contains:[e.C_NUMBER_MODE]},s={className:"variable",begin:"\\$"+e.UNDERSCORE_IDENT_RE},t={className:"string",variants:[{begin:'"""',end:'"""(?=[^"])',contains:[s,i]},{begin:"'",end:"'",illegal:/\n/,contains:[e.BACKSLASH_ESCAPE]},{begin:'"',end:'"',illegal:/\n/,contains:[e.BACKSLASH_ESCAPE,s,i]}]};i.contains.push(t);var r={className:"meta",begin:"@(?:file|property|field|get|set|receiver|param|setparam|delegate)\\s*:(?:\\s*"+e.UNDERSCORE_IDENT_RE+")?"},l={className:"meta",begin:"@"+e.UNDERSCORE_IDENT_RE,contains:[{begin:/\(/,end:/\)/,contains:[e.inherit(t,{className:"meta-string"})]}]},c=e.COMMENT("/\\*","\\*/",{contains:[e.C_BLOCK_COMMENT_MODE]}),o={variants:[{className:"type",begin:e.UNDERSCORE_IDENT_RE},{begin:/\(/,end:/\)/,contains:[]}]},d=o;return d.variants[1].contains=[o],o.variants[1].contains=[d],{name:"Kotlin",aliases:["kt"],keywords:n,contains:[e.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{className:"doctag",begin:"@[A-Za-z]+"}]}),e.C_LINE_COMMENT_MODE,c,{className:"keyword",begin:/\b(break|continue|return|this)\b/,starts:{contains:[{className:"symbol",begin:/@\w+/}]}},a,r,l,{className:"function",beginKeywords:"fun",end:"[(]|$",returnBegin:!0,excludeEnd:!0,keywords:n,illegal:/fun\s+(<.*>)?[^\s\(]+(\s+[^\s\(]+)\s*=/,relevance:5,contains:[{begin:e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,relevance:0,contains:[e.UNDERSCORE_TITLE_MODE]},{className:"type",begin://,keywords:"reified",relevance:0},{className:"params",begin:/\(/,end:/\)/,endsParent:!0,keywords:n,relevance:0,contains:[{begin:/:/,end:/[=,\/]/,endsWithParent:!0,contains:[o,e.C_LINE_COMMENT_MODE,c],relevance:0},e.C_LINE_COMMENT_MODE,c,r,l,t,e.C_NUMBER_MODE]},c]},{className:"class",beginKeywords:"class interface trait",end:/[:\{(]|$/,excludeEnd:!0,illegal:"extends implements",contains:[{beginKeywords:"public protected internal private constructor"},e.UNDERSCORE_TITLE_MODE,{className:"type",begin://,excludeBegin:!0,excludeEnd:!0,relevance:0},{className:"type",begin:/[,:]\s*/,end:/[<\(,]|$/,excludeBegin:!0,returnEnd:!0},r,l]},t,{className:"meta",begin:"^#!/usr/bin/env",end:"$",illegal:"\n"},{className:"number",begin:"\\b(0[bB]([01]+[01_]+[01]+|[01]+)|0[xX]([a-fA-F0-9]+[a-fA-F0-9_]+[a-fA-F0-9]+|[a-fA-F0-9]+)|(([\\d]+[\\d_]+[\\d]+|[\\d]+)(\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))?|\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))([eE][-+]?\\d+)?)[lLfF]?",relevance:0}]}}}()); -hljs.registerLanguage("less",function(){"use strict";return function(e){var n="([\\w-]+|@{[\\w-]+})",a=[],s=[],t=function(e){return{className:"string",begin:"~?"+e+".*?"+e}},r=function(e,n,a){return{className:e,begin:n,relevance:a}},i={begin:"\\(",end:"\\)",contains:s,relevance:0};s.push(e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,t("'"),t('"'),e.CSS_NUMBER_MODE,{begin:"(url|data-uri)\\(",starts:{className:"string",end:"[\\)\\n]",excludeEnd:!0}},r("number","#[0-9A-Fa-f]+\\b"),i,r("variable","@@?[\\w-]+",10),r("variable","@{[\\w-]+}"),r("built_in","~?`[^`]*?`"),{className:"attribute",begin:"[\\w-]+\\s*:",end:":",returnBegin:!0,excludeEnd:!0},{className:"meta",begin:"!important"});var c=s.concat({begin:"{",end:"}",contains:a}),l={beginKeywords:"when",endsWithParent:!0,contains:[{beginKeywords:"and not"}].concat(s)},o={begin:n+"\\s*:",returnBegin:!0,end:"[;}]",relevance:0,contains:[{className:"attribute",begin:n,end:":",excludeEnd:!0,starts:{endsWithParent:!0,illegal:"[<=$]",relevance:0,contains:s}}]},g={className:"keyword",begin:"@(import|media|charset|font-face|(-[a-z]+-)?keyframes|supports|document|namespace|page|viewport|host)\\b",starts:{end:"[;{}]",returnEnd:!0,contains:s,relevance:0}},d={className:"variable",variants:[{begin:"@[\\w-]+\\s*:",relevance:15},{begin:"@[\\w-]+"}],starts:{end:"[;}]",returnEnd:!0,contains:c}},b={variants:[{begin:"[\\.#:&\\[>]",end:"[;{}]"},{begin:n,end:"{"}],returnBegin:!0,returnEnd:!0,illegal:"[<='$\"]",relevance:0,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,l,r("keyword","all\\b"),r("variable","@{[\\w-]+}"),r("selector-tag",n+"%?",0),r("selector-id","#"+n),r("selector-class","\\."+n,0),r("selector-tag","&",0),{className:"selector-attr",begin:"\\[",end:"\\]"},{className:"selector-pseudo",begin:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{begin:"\\(",end:"\\)",contains:c},{begin:"!important"}]};return a.push(e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,g,d,o,b),{name:"Less",case_insensitive:!0,illegal:"[=>'/<($\"]",contains:a}}}()); -hljs.registerLanguage("lua",function(){"use strict";return function(e){var t={begin:"\\[=*\\[",end:"\\]=*\\]",contains:["self"]},a=[e.COMMENT("--(?!\\[=*\\[)","$"),e.COMMENT("--\\[=*\\[","\\]=*\\]",{contains:[t],relevance:10})];return{name:"Lua",keywords:{$pattern:e.UNDERSCORE_IDENT_RE,literal:"true false nil",keyword:"and break do else elseif end for goto if in local not or repeat return then until while",built_in:"_G _ENV _VERSION __index __newindex __mode __call __metatable __tostring __len __gc __add __sub __mul __div __mod __pow __concat __unm __eq __lt __le assert collectgarbage dofile error getfenv getmetatable ipairs load loadfile loadstring module next pairs pcall print rawequal rawget rawset require select setfenv setmetatable tonumber tostring type unpack xpcall arg self coroutine resume yield status wrap create running debug getupvalue debug sethook getmetatable gethook setmetatable setlocal traceback setfenv getinfo setupvalue getlocal getregistry getfenv io lines write close flush open output type read stderr stdin input stdout popen tmpfile math log max acos huge ldexp pi cos tanh pow deg tan cosh sinh random randomseed frexp ceil floor rad abs sqrt modf asin min mod fmod log10 atan2 exp sin atan os exit setlocale date getenv difftime remove time clock tmpname rename execute package preload loadlib loaded loaders cpath config path seeall string sub upper len gfind rep find match char dump gmatch reverse byte format gsub lower table setn insert getn foreachi maxn foreach concat sort remove"},contains:a.concat([{className:"function",beginKeywords:"function",end:"\\)",contains:[e.inherit(e.TITLE_MODE,{begin:"([_a-zA-Z]\\w*\\.)*([_a-zA-Z]\\w*:)?[_a-zA-Z]\\w*"}),{className:"params",begin:"\\(",endsWithParent:!0,contains:a}].concat(a)},e.C_NUMBER_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{className:"string",begin:"\\[=*\\[",end:"\\]=*\\]",contains:[t],relevance:5}])}}}()); -hljs.registerLanguage("makefile",function(){"use strict";return function(e){var i={className:"variable",variants:[{begin:"\\$\\("+e.UNDERSCORE_IDENT_RE+"\\)",contains:[e.BACKSLASH_ESCAPE]},{begin:/\$[@%`]+/}]}]}]};return{name:"HTML, XML",aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"],case_insensitive:!0,contains:[{className:"meta",begin:"",relevance:10,contains:[a,i,t,s,{begin:"\\[",end:"\\]",contains:[{className:"meta",begin:"",contains:[a,s,i,t]}]}]},e.COMMENT("\x3c!--","--\x3e",{relevance:10}),{begin:"<\\!\\[CDATA\\[",end:"\\]\\]>",relevance:10},n,{className:"meta",begin:/<\?xml/,end:/\?>/,relevance:10},{className:"tag",begin:")",end:">",keywords:{name:"style"},contains:[c],starts:{end:"",returnEnd:!0,subLanguage:["css","xml"]}},{className:"tag",begin:")",end:">",keywords:{name:"script"},contains:[c],starts:{end:"<\/script>",returnEnd:!0,subLanguage:["javascript","handlebars","xml"]}},{className:"tag",begin:"",contains:[{className:"name",begin:/[^\/><\s]+/,relevance:0},c]}]}}}()); -hljs.registerLanguage("markdown",function(){"use strict";return function(n){const e={begin:"<",end:">",subLanguage:"xml",relevance:0},a={begin:"\\[.+?\\][\\(\\[].*?[\\)\\]]",returnBegin:!0,contains:[{className:"string",begin:"\\[",end:"\\]",excludeBegin:!0,returnEnd:!0,relevance:0},{className:"link",begin:"\\]\\(",end:"\\)",excludeBegin:!0,excludeEnd:!0},{className:"symbol",begin:"\\]\\[",end:"\\]",excludeBegin:!0,excludeEnd:!0}],relevance:10},i={className:"strong",contains:[],variants:[{begin:/_{2}/,end:/_{2}/},{begin:/\*{2}/,end:/\*{2}/}]},s={className:"emphasis",contains:[],variants:[{begin:/\*(?!\*)/,end:/\*/},{begin:/_(?!_)/,end:/_/,relevance:0}]};i.contains.push(s),s.contains.push(i);var c=[e,a];return i.contains=i.contains.concat(c),s.contains=s.contains.concat(c),{name:"Markdown",aliases:["md","mkdown","mkd"],contains:[{className:"section",variants:[{begin:"^#{1,6}",end:"$",contains:c=c.concat(i,s)},{begin:"(?=^.+?\\n[=-]{2,}$)",contains:[{begin:"^[=-]*$"},{begin:"^",end:"\\n",contains:c}]}]},e,{className:"bullet",begin:"^[ \t]*([*+-]|(\\d+\\.))(?=\\s+)",end:"\\s+",excludeEnd:!0},i,s,{className:"quote",begin:"^>\\s+",contains:c,end:"$"},{className:"code",variants:[{begin:"(`{3,})(.|\\n)*?\\1`*[ ]*"},{begin:"(~{3,})(.|\\n)*?\\1~*[ ]*"},{begin:"```",end:"```+[ ]*$"},{begin:"~~~",end:"~~~+[ ]*$"},{begin:"`.+?`"},{begin:"(?=^( {4}|\\t))",contains:[{begin:"^( {4}|\\t)",end:"(\\n)$"}],relevance:0}]},{begin:"^[-\\*]{3,}",end:"$"},a,{begin:/^\[[^\n]+\]:/,returnBegin:!0,contains:[{className:"symbol",begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0},{className:"link",begin:/:\s*/,end:/$/,excludeBegin:!0}]}]}}}()); -hljs.registerLanguage("nginx",function(){"use strict";return function(e){var n={className:"variable",variants:[{begin:/\$\d+/},{begin:/\$\{/,end:/}/},{begin:"[\\$\\@]"+e.UNDERSCORE_IDENT_RE}]},a={endsWithParent:!0,keywords:{$pattern:"[a-z/_]+",literal:"on off yes no true false none blocked debug info notice warn error crit select break last permanent redirect kqueue rtsig epoll poll /dev/poll"},relevance:0,illegal:"=>",contains:[e.HASH_COMMENT_MODE,{className:"string",contains:[e.BACKSLASH_ESCAPE,n],variants:[{begin:/"/,end:/"/},{begin:/'/,end:/'/}]},{begin:"([a-z]+):/",end:"\\s",endsWithParent:!0,excludeEnd:!0,contains:[n]},{className:"regexp",contains:[e.BACKSLASH_ESCAPE,n],variants:[{begin:"\\s\\^",end:"\\s|{|;",returnEnd:!0},{begin:"~\\*?\\s+",end:"\\s|{|;",returnEnd:!0},{begin:"\\*(\\.[a-z\\-]+)+"},{begin:"([a-z\\-]+\\.)+\\*"}]},{className:"number",begin:"\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?\\b"},{className:"number",begin:"\\b\\d+[kKmMgGdshdwy]*\\b",relevance:0},n]};return{name:"Nginx config",aliases:["nginxconf"],contains:[e.HASH_COMMENT_MODE,{begin:e.UNDERSCORE_IDENT_RE+"\\s+{",returnBegin:!0,end:"{",contains:[{className:"section",begin:e.UNDERSCORE_IDENT_RE}],relevance:0},{begin:e.UNDERSCORE_IDENT_RE+"\\s",end:";|{",returnBegin:!0,contains:[{className:"attribute",begin:e.UNDERSCORE_IDENT_RE,starts:a}],relevance:0}],illegal:"[^\\s\\}]"}}}()); -hljs.registerLanguage("objectivec",function(){"use strict";return function(e){var n=/[a-zA-Z@][a-zA-Z0-9_]*/,_={$pattern:n,keyword:"@interface @class @protocol @implementation"};return{name:"Objective-C",aliases:["mm","objc","obj-c"],keywords:{$pattern:n,keyword:"int float while char export sizeof typedef const struct for union unsigned long volatile static bool mutable if do return goto void enum else break extern asm case short default double register explicit signed typename this switch continue wchar_t inline readonly assign readwrite self @synchronized id typeof nonatomic super unichar IBOutlet IBAction strong weak copy in out inout bycopy byref oneway __strong __weak __block __autoreleasing @private @protected @public @try @property @end @throw @catch @finally @autoreleasepool @synthesize @dynamic @selector @optional @required @encode @package @import @defs @compatibility_alias __bridge __bridge_transfer __bridge_retained __bridge_retain __covariant __contravariant __kindof _Nonnull _Nullable _Null_unspecified __FUNCTION__ __PRETTY_FUNCTION__ __attribute__ getter setter retain unsafe_unretained nonnull nullable null_unspecified null_resettable class instancetype NS_DESIGNATED_INITIALIZER NS_UNAVAILABLE NS_REQUIRES_SUPER NS_RETURNS_INNER_POINTER NS_INLINE NS_AVAILABLE NS_DEPRECATED NS_ENUM NS_OPTIONS NS_SWIFT_UNAVAILABLE NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_END NS_REFINED_FOR_SWIFT NS_SWIFT_NAME NS_SWIFT_NOTHROW NS_DURING NS_HANDLER NS_ENDHANDLER NS_VALUERETURN NS_VOIDRETURN",literal:"false true FALSE TRUE nil YES NO NULL",built_in:"BOOL dispatch_once_t dispatch_queue_t dispatch_sync dispatch_async dispatch_once"},illegal:"/,end:/$/,illegal:"\\n"},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:"class",begin:"("+_.keyword.split(" ").join("|")+")\\b",end:"({|$)",excludeEnd:!0,keywords:_,contains:[e.UNDERSCORE_TITLE_MODE]},{begin:"\\."+e.UNDERSCORE_IDENT_RE,relevance:0}]}}}()); -hljs.registerLanguage("perl",function(){"use strict";return function(e){var n={$pattern:/[\w.]+/,keyword:"getpwent getservent quotemeta msgrcv scalar kill dbmclose undef lc ma syswrite tr send umask sysopen shmwrite vec qx utime local oct semctl localtime readpipe do return format read sprintf dbmopen pop getpgrp not getpwnam rewinddir qq fileno qw endprotoent wait sethostent bless s|0 opendir continue each sleep endgrent shutdown dump chomp connect getsockname die socketpair close flock exists index shmget sub for endpwent redo lstat msgctl setpgrp abs exit select print ref gethostbyaddr unshift fcntl syscall goto getnetbyaddr join gmtime symlink semget splice x|0 getpeername recv log setsockopt cos last reverse gethostbyname getgrnam study formline endhostent times chop length gethostent getnetent pack getprotoent getservbyname rand mkdir pos chmod y|0 substr endnetent printf next open msgsnd readdir use unlink getsockopt getpriority rindex wantarray hex system getservbyport endservent int chr untie rmdir prototype tell listen fork shmread ucfirst setprotoent else sysseek link getgrgid shmctl waitpid unpack getnetbyname reset chdir grep split require caller lcfirst until warn while values shift telldir getpwuid my getprotobynumber delete and sort uc defined srand accept package seekdir getprotobyname semop our rename seek if q|0 chroot sysread setpwent no crypt getc chown sqrt write setnetent setpriority foreach tie sin msgget map stat getlogin unless elsif truncate exec keys glob tied closedir ioctl socket readlink eval xor readline binmode setservent eof ord bind alarm pipe atan2 getgrent exp time push setgrent gt lt or ne m|0 break given say state when"},t={className:"subst",begin:"[$@]\\{",end:"\\}",keywords:n},s={begin:"->{",end:"}"},r={variants:[{begin:/\$\d/},{begin:/[\$%@](\^\w\b|#\w+(::\w+)*|{\w+}|\w+(::\w*)*)/},{begin:/[\$%@][^\s\w{]/,relevance:0}]},i=[e.BACKSLASH_ESCAPE,t,r],a=[r,e.HASH_COMMENT_MODE,e.COMMENT("^\\=\\w","\\=cut",{endsWithParent:!0}),s,{className:"string",contains:i,variants:[{begin:"q[qwxr]?\\s*\\(",end:"\\)",relevance:5},{begin:"q[qwxr]?\\s*\\[",end:"\\]",relevance:5},{begin:"q[qwxr]?\\s*\\{",end:"\\}",relevance:5},{begin:"q[qwxr]?\\s*\\|",end:"\\|",relevance:5},{begin:"q[qwxr]?\\s*\\<",end:"\\>",relevance:5},{begin:"qw\\s+q",end:"q",relevance:5},{begin:"'",end:"'",contains:[e.BACKSLASH_ESCAPE]},{begin:'"',end:'"'},{begin:"`",end:"`",contains:[e.BACKSLASH_ESCAPE]},{begin:"{\\w+}",contains:[],relevance:0},{begin:"-?\\w+\\s*\\=\\>",contains:[],relevance:0}]},{className:"number",begin:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",relevance:0},{begin:"(\\/\\/|"+e.RE_STARTERS_RE+"|\\b(split|return|print|reverse|grep)\\b)\\s*",keywords:"split return print reverse grep",relevance:0,contains:[e.HASH_COMMENT_MODE,{className:"regexp",begin:"(s|tr|y)/(\\\\.|[^/])*/(\\\\.|[^/])*/[a-z]*",relevance:10},{className:"regexp",begin:"(m|qr)?/",end:"/[a-z]*",contains:[e.BACKSLASH_ESCAPE],relevance:0}]},{className:"function",beginKeywords:"sub",end:"(\\s*\\(.*?\\))?[;{]",excludeEnd:!0,relevance:5,contains:[e.TITLE_MODE]},{begin:"-\\w\\b",relevance:0},{begin:"^__DATA__$",end:"^__END__$",subLanguage:"mojolicious",contains:[{begin:"^@@.*",end:"$",className:"comment"}]}];return t.contains=a,s.contains=a,{name:"Perl",aliases:["pl","pm"],keywords:n,contains:a}}}()); -hljs.registerLanguage("php",function(){"use strict";return function(e){var r={begin:"\\$+[a-zA-Z_-ÿ][a-zA-Z0-9_-ÿ]*"},t={className:"meta",variants:[{begin:/<\?php/,relevance:10},{begin:/<\?[=]?/},{begin:/\?>/}]},a={className:"subst",variants:[{begin:/\$\w+/},{begin:/\{\$/,end:/\}/}]},n=e.inherit(e.APOS_STRING_MODE,{illegal:null}),i=e.inherit(e.QUOTE_STRING_MODE,{illegal:null,contains:e.QUOTE_STRING_MODE.contains.concat(a)}),o=e.END_SAME_AS_BEGIN({begin:/<<<[ \t]*(\w+)\n/,end:/[ \t]*(\w+)\b/,contains:e.QUOTE_STRING_MODE.contains.concat(a)}),l={className:"string",contains:[e.BACKSLASH_ESCAPE,t],variants:[e.inherit(n,{begin:"b'",end:"'"}),e.inherit(i,{begin:'b"',end:'"'}),i,n,o]},s={variants:[e.BINARY_NUMBER_MODE,e.C_NUMBER_MODE]},c={keyword:"__CLASS__ __DIR__ __FILE__ __FUNCTION__ __LINE__ __METHOD__ __NAMESPACE__ __TRAIT__ die echo exit include include_once print require require_once array abstract and as binary bool boolean break callable case catch class clone const continue declare default do double else elseif empty enddeclare endfor endforeach endif endswitch endwhile eval extends final finally float for foreach from global goto if implements instanceof insteadof int integer interface isset iterable list new object or private protected public real return string switch throw trait try unset use var void while xor yield",literal:"false null true",built_in:"Error|0 AppendIterator ArgumentCountError ArithmeticError ArrayIterator ArrayObject AssertionError BadFunctionCallException BadMethodCallException CachingIterator CallbackFilterIterator CompileError Countable DirectoryIterator DivisionByZeroError DomainException EmptyIterator ErrorException Exception FilesystemIterator FilterIterator GlobIterator InfiniteIterator InvalidArgumentException IteratorIterator LengthException LimitIterator LogicException MultipleIterator NoRewindIterator OutOfBoundsException OutOfRangeException OuterIterator OverflowException ParentIterator ParseError RangeException RecursiveArrayIterator RecursiveCachingIterator RecursiveCallbackFilterIterator RecursiveDirectoryIterator RecursiveFilterIterator RecursiveIterator RecursiveIteratorIterator RecursiveRegexIterator RecursiveTreeIterator RegexIterator RuntimeException SeekableIterator SplDoublyLinkedList SplFileInfo SplFileObject SplFixedArray SplHeap SplMaxHeap SplMinHeap SplObjectStorage SplObserver SplObserver SplPriorityQueue SplQueue SplStack SplSubject SplSubject SplTempFileObject TypeError UnderflowException UnexpectedValueException ArrayAccess Closure Generator Iterator IteratorAggregate Serializable Throwable Traversable WeakReference Directory __PHP_Incomplete_Class parent php_user_filter self static stdClass"};return{aliases:["php","php3","php4","php5","php6","php7"],case_insensitive:!0,keywords:c,contains:[e.HASH_COMMENT_MODE,e.COMMENT("//","$",{contains:[t]}),e.COMMENT("/\\*","\\*/",{contains:[{className:"doctag",begin:"@[A-Za-z]+"}]}),e.COMMENT("__halt_compiler.+?;",!1,{endsWithParent:!0,keywords:"__halt_compiler"}),t,{className:"keyword",begin:/\$this\b/},r,{begin:/(::|->)+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/},{className:"function",beginKeywords:"fn function",end:/[;{]/,excludeEnd:!0,illegal:"[$%\\[]",contains:[e.UNDERSCORE_TITLE_MODE,{className:"params",begin:"\\(",end:"\\)",excludeBegin:!0,excludeEnd:!0,keywords:c,contains:["self",r,e.C_BLOCK_COMMENT_MODE,l,s]}]},{className:"class",beginKeywords:"class interface",end:"{",excludeEnd:!0,illegal:/[:\(\$"]/,contains:[{beginKeywords:"extends implements"},e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"namespace",end:";",illegal:/[\.']/,contains:[e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"use",end:";",contains:[e.UNDERSCORE_TITLE_MODE]},{begin:"=>"},l,s]}}}()); -hljs.registerLanguage("php-template",function(){"use strict";return function(n){return{name:"PHP template",subLanguage:"xml",contains:[{begin:/<\?(php|=)?/,end:/\?>/,subLanguage:"php",contains:[{begin:"/\\*",end:"\\*/",skip:!0},{begin:'b"',end:'"',skip:!0},{begin:"b'",end:"'",skip:!0},n.inherit(n.APOS_STRING_MODE,{illegal:null,className:null,contains:null,skip:!0}),n.inherit(n.QUOTE_STRING_MODE,{illegal:null,className:null,contains:null,skip:!0})]}]}}}()); -hljs.registerLanguage("plaintext",function(){"use strict";return function(t){return{name:"Plain text",aliases:["text","txt"],disableAutodetect:!0}}}()); -hljs.registerLanguage("properties",function(){"use strict";return function(e){var n="[ \\t\\f]*",t="("+n+"[:=]"+n+"|[ \\t\\f]+)",a="([^\\\\:= \\t\\f\\n]|\\\\.)+",s={end:t,relevance:0,starts:{className:"string",end:/$/,relevance:0,contains:[{begin:"\\\\\\n"}]}};return{name:".properties",case_insensitive:!0,illegal:/\S/,contains:[e.COMMENT("^\\s*[!#]","$"),{begin:"([^\\\\\\W:= \\t\\f\\n]|\\\\.)+"+t,returnBegin:!0,contains:[{className:"attr",begin:"([^\\\\\\W:= \\t\\f\\n]|\\\\.)+",endsParent:!0,relevance:0}],starts:s},{begin:a+t,returnBegin:!0,relevance:0,contains:[{className:"meta",begin:a,endsParent:!0,relevance:0}],starts:s},{className:"attr",relevance:0,begin:a+n+"$"}]}}}()); -hljs.registerLanguage("python",function(){"use strict";return function(e){var n={keyword:"and elif is global as in if from raise for except finally print import pass return exec else break not with class assert yield try while continue del or def lambda async await nonlocal|10",built_in:"Ellipsis NotImplemented",literal:"False None True"},a={className:"meta",begin:/^(>>>|\.\.\.) /},i={className:"subst",begin:/\{/,end:/\}/,keywords:n,illegal:/#/},s={begin:/\{\{/,relevance:0},r={className:"string",contains:[e.BACKSLASH_ESCAPE],variants:[{begin:/(u|b)?r?'''/,end:/'''/,contains:[e.BACKSLASH_ESCAPE,a],relevance:10},{begin:/(u|b)?r?"""/,end:/"""/,contains:[e.BACKSLASH_ESCAPE,a],relevance:10},{begin:/(fr|rf|f)'''/,end:/'''/,contains:[e.BACKSLASH_ESCAPE,a,s,i]},{begin:/(fr|rf|f)"""/,end:/"""/,contains:[e.BACKSLASH_ESCAPE,a,s,i]},{begin:/(u|r|ur)'/,end:/'/,relevance:10},{begin:/(u|r|ur)"/,end:/"/,relevance:10},{begin:/(b|br)'/,end:/'/},{begin:/(b|br)"/,end:/"/},{begin:/(fr|rf|f)'/,end:/'/,contains:[e.BACKSLASH_ESCAPE,s,i]},{begin:/(fr|rf|f)"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,s,i]},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},l={className:"number",relevance:0,variants:[{begin:e.BINARY_NUMBER_RE+"[lLjJ]?"},{begin:"\\b(0o[0-7]+)[lLjJ]?"},{begin:e.C_NUMBER_RE+"[lLjJ]?"}]},t={className:"params",variants:[{begin:/\(\s*\)/,skip:!0,className:null},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,contains:["self",a,l,r,e.HASH_COMMENT_MODE]}]};return i.contains=[r,l,a],{name:"Python",aliases:["py","gyp","ipython"],keywords:n,illegal:/(<\/|->|\?)|=>/,contains:[a,l,{beginKeywords:"if",relevance:0},r,e.HASH_COMMENT_MODE,{variants:[{className:"function",beginKeywords:"def"},{className:"class",beginKeywords:"class"}],end:/:/,illegal:/[${=;\n,]/,contains:[e.UNDERSCORE_TITLE_MODE,t,{begin:/->/,endsWithParent:!0,keywords:"None"}]},{className:"meta",begin:/^[\t ]*@/,end:/$/},{begin:/\b(print|exec)\(/}]}}}()); -hljs.registerLanguage("python-repl",function(){"use strict";return function(n){return{aliases:["pycon"],contains:[{className:"meta",starts:{end:/ |$/,starts:{end:"$",subLanguage:"python"}},variants:[{begin:/^>>>(?=[ ]|$)/},{begin:/^\.\.\.(?=[ ]|$)/}]}]}}}()); -hljs.registerLanguage("ruby",function(){"use strict";return function(e){var n="[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?",a={keyword:"and then defined module in return redo if BEGIN retry end for self when next until do begin unless END rescue else break undef not super class case require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor",literal:"true false nil"},s={className:"doctag",begin:"@[A-Za-z]+"},i={begin:"#<",end:">"},r=[e.COMMENT("#","$",{contains:[s]}),e.COMMENT("^\\=begin","^\\=end",{contains:[s],relevance:10}),e.COMMENT("^__END__","\\n$")],c={className:"subst",begin:"#\\{",end:"}",keywords:a},t={className:"string",contains:[e.BACKSLASH_ESCAPE,c],variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/`/,end:/`/},{begin:"%[qQwWx]?\\(",end:"\\)"},{begin:"%[qQwWx]?\\[",end:"\\]"},{begin:"%[qQwWx]?{",end:"}"},{begin:"%[qQwWx]?<",end:">"},{begin:"%[qQwWx]?/",end:"/"},{begin:"%[qQwWx]?%",end:"%"},{begin:"%[qQwWx]?-",end:"-"},{begin:"%[qQwWx]?\\|",end:"\\|"},{begin:/\B\?(\\\d{1,3}|\\x[A-Fa-f0-9]{1,2}|\\u[A-Fa-f0-9]{4}|\\?\S)\b/},{begin:/<<[-~]?'?(\w+)(?:.|\n)*?\n\s*\1\b/,returnBegin:!0,contains:[{begin:/<<[-~]?'?/},e.END_SAME_AS_BEGIN({begin:/(\w+)/,end:/(\w+)/,contains:[e.BACKSLASH_ESCAPE,c]})]}]},b={className:"params",begin:"\\(",end:"\\)",endsParent:!0,keywords:a},d=[t,i,{className:"class",beginKeywords:"class module",end:"$|;",illegal:/=/,contains:[e.inherit(e.TITLE_MODE,{begin:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?"}),{begin:"<\\s*",contains:[{begin:"("+e.IDENT_RE+"::)?"+e.IDENT_RE}]}].concat(r)},{className:"function",beginKeywords:"def",end:"$|;",contains:[e.inherit(e.TITLE_MODE,{begin:n}),b].concat(r)},{begin:e.IDENT_RE+"::"},{className:"symbol",begin:e.UNDERSCORE_IDENT_RE+"(\\!|\\?)?:",relevance:0},{className:"symbol",begin:":(?!\\s)",contains:[t,{begin:n}],relevance:0},{className:"number",begin:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",relevance:0},{begin:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},{className:"params",begin:/\|/,end:/\|/,keywords:a},{begin:"("+e.RE_STARTERS_RE+"|unless)\\s*",keywords:"unless",contains:[i,{className:"regexp",contains:[e.BACKSLASH_ESCAPE,c],illegal:/\n/,variants:[{begin:"/",end:"/[a-z]*"},{begin:"%r{",end:"}[a-z]*"},{begin:"%r\\(",end:"\\)[a-z]*"},{begin:"%r!",end:"![a-z]*"},{begin:"%r\\[",end:"\\][a-z]*"}]}].concat(r),relevance:0}].concat(r);c.contains=d,b.contains=d;var g=[{begin:/^\s*=>/,starts:{end:"$",contains:d}},{className:"meta",begin:"^([>?]>|[\\w#]+\\(\\w+\\):\\d+:\\d+>|(\\w+-)?\\d+\\.\\d+\\.\\d(p\\d+)?[^>]+>)",starts:{end:"$",contains:d}}];return{name:"Ruby",aliases:["rb","gemspec","podspec","thor","irb"],keywords:a,illegal:/\/\*/,contains:r.concat(g).concat(d)}}}()); -hljs.registerLanguage("rust",function(){"use strict";return function(e){var n="([ui](8|16|32|64|128|size)|f(32|64))?",t="drop i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize f32 f64 str char bool Box Option Result String Vec Copy Send Sized Sync Drop Fn FnMut FnOnce ToOwned Clone Debug PartialEq PartialOrd Eq Ord AsRef AsMut Into From Default Iterator Extend IntoIterator DoubleEndedIterator ExactSizeIterator SliceConcatExt ToString assert! assert_eq! bitflags! bytes! cfg! col! concat! concat_idents! debug_assert! debug_assert_eq! env! panic! file! format! format_args! include_bin! include_str! line! local_data_key! module_path! option_env! print! println! select! stringify! try! unimplemented! unreachable! vec! write! writeln! macro_rules! assert_ne! debug_assert_ne!";return{name:"Rust",aliases:["rs"],keywords:{$pattern:e.IDENT_RE+"!?",keyword:"abstract as async await become box break const continue crate do dyn else enum extern false final fn for if impl in let loop macro match mod move mut override priv pub ref return self Self static struct super trait true try type typeof unsafe unsized use virtual where while yield",literal:"true false Some None Ok Err",built_in:t},illegal:""}]}}}()); -hljs.registerLanguage("scss",function(){"use strict";return function(e){var t={className:"variable",begin:"(\\$[a-zA-Z-][a-zA-Z0-9_-]*)\\b"},i={className:"number",begin:"#[0-9A-Fa-f]+"};return e.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,e.C_BLOCK_COMMENT_MODE,{name:"SCSS",case_insensitive:!0,illegal:"[=/|']",contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:"selector-id",begin:"\\#[A-Za-z0-9_-]+",relevance:0},{className:"selector-class",begin:"\\.[A-Za-z0-9_-]+",relevance:0},{className:"selector-attr",begin:"\\[",end:"\\]",illegal:"$"},{className:"selector-tag",begin:"\\b(a|abbr|acronym|address|area|article|aside|audio|b|base|big|blockquote|body|br|button|canvas|caption|cite|code|col|colgroup|command|datalist|dd|del|details|dfn|div|dl|dt|em|embed|fieldset|figcaption|figure|footer|form|frame|frameset|(h[1-6])|head|header|hgroup|hr|html|i|iframe|img|input|ins|kbd|keygen|label|legend|li|link|map|mark|meta|meter|nav|noframes|noscript|object|ol|optgroup|option|output|p|param|pre|progress|q|rp|rt|ruby|samp|script|section|select|small|span|strike|strong|style|sub|sup|table|tbody|td|textarea|tfoot|th|thead|time|title|tr|tt|ul|var|video)\\b",relevance:0},{className:"selector-pseudo",begin:":(visited|valid|root|right|required|read-write|read-only|out-range|optional|only-of-type|only-child|nth-of-type|nth-last-of-type|nth-last-child|nth-child|not|link|left|last-of-type|last-child|lang|invalid|indeterminate|in-range|hover|focus|first-of-type|first-line|first-letter|first-child|first|enabled|empty|disabled|default|checked|before|after|active)"},{className:"selector-pseudo",begin:"::(after|before|choices|first-letter|first-line|repeat-index|repeat-item|selection|value)"},t,{className:"attribute",begin:"\\b(src|z-index|word-wrap|word-spacing|word-break|width|widows|white-space|visibility|vertical-align|unicode-bidi|transition-timing-function|transition-property|transition-duration|transition-delay|transition|transform-style|transform-origin|transform|top|text-underline-position|text-transform|text-shadow|text-rendering|text-overflow|text-indent|text-decoration-style|text-decoration-line|text-decoration-color|text-decoration|text-align-last|text-align|tab-size|table-layout|right|resize|quotes|position|pointer-events|perspective-origin|perspective|page-break-inside|page-break-before|page-break-after|padding-top|padding-right|padding-left|padding-bottom|padding|overflow-y|overflow-x|overflow-wrap|overflow|outline-width|outline-style|outline-offset|outline-color|outline|orphans|order|opacity|object-position|object-fit|normal|none|nav-up|nav-right|nav-left|nav-index|nav-down|min-width|min-height|max-width|max-height|mask|marks|margin-top|margin-right|margin-left|margin-bottom|margin|list-style-type|list-style-position|list-style-image|list-style|line-height|letter-spacing|left|justify-content|initial|inherit|ime-mode|image-orientation|image-resolution|image-rendering|icon|hyphens|height|font-weight|font-variant-ligatures|font-variant|font-style|font-stretch|font-size-adjust|font-size|font-language-override|font-kerning|font-feature-settings|font-family|font|float|flex-wrap|flex-shrink|flex-grow|flex-flow|flex-direction|flex-basis|flex|filter|empty-cells|display|direction|cursor|counter-reset|counter-increment|content|column-width|column-span|column-rule-width|column-rule-style|column-rule-color|column-rule|column-gap|column-fill|column-count|columns|color|clip-path|clip|clear|caption-side|break-inside|break-before|break-after|box-sizing|box-shadow|box-decoration-break|bottom|border-width|border-top-width|border-top-style|border-top-right-radius|border-top-left-radius|border-top-color|border-top|border-style|border-spacing|border-right-width|border-right-style|border-right-color|border-right|border-radius|border-left-width|border-left-style|border-left-color|border-left|border-image-width|border-image-source|border-image-slice|border-image-repeat|border-image-outset|border-image|border-color|border-collapse|border-bottom-width|border-bottom-style|border-bottom-right-radius|border-bottom-left-radius|border-bottom-color|border-bottom|border|background-size|background-repeat|background-position|background-origin|background-image|background-color|background-clip|background-attachment|background-blend-mode|background|backface-visibility|auto|animation-timing-function|animation-play-state|animation-name|animation-iteration-count|animation-fill-mode|animation-duration|animation-direction|animation-delay|animation|align-self|align-items|align-content)\\b",illegal:"[^\\s]"},{begin:"\\b(whitespace|wait|w-resize|visible|vertical-text|vertical-ideographic|uppercase|upper-roman|upper-alpha|underline|transparent|top|thin|thick|text|text-top|text-bottom|tb-rl|table-header-group|table-footer-group|sw-resize|super|strict|static|square|solid|small-caps|separate|se-resize|scroll|s-resize|rtl|row-resize|ridge|right|repeat|repeat-y|repeat-x|relative|progress|pointer|overline|outside|outset|oblique|nowrap|not-allowed|normal|none|nw-resize|no-repeat|no-drop|newspaper|ne-resize|n-resize|move|middle|medium|ltr|lr-tb|lowercase|lower-roman|lower-alpha|loose|list-item|line|line-through|line-edge|lighter|left|keep-all|justify|italic|inter-word|inter-ideograph|inside|inset|inline|inline-block|inherit|inactive|ideograph-space|ideograph-parenthesis|ideograph-numeric|ideograph-alpha|horizontal|hidden|help|hand|groove|fixed|ellipsis|e-resize|double|dotted|distribute|distribute-space|distribute-letter|distribute-all-lines|disc|disabled|default|decimal|dashed|crosshair|collapse|col-resize|circle|char|center|capitalize|break-word|break-all|bottom|both|bolder|bold|block|bidi-override|below|baseline|auto|always|all-scroll|absolute|table|table-cell)\\b"},{begin:":",end:";",contains:[t,i,e.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,{className:"meta",begin:"!important"}]},{begin:"@(page|font-face)",lexemes:"@[a-z-]+",keywords:"@page @font-face"},{begin:"@",end:"[{;]",returnBegin:!0,keywords:"and or not only",contains:[{begin:"@[a-z-]+",className:"keyword"},t,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,i,e.CSS_NUMBER_MODE]}]}}}()); -hljs.registerLanguage("shell",function(){"use strict";return function(s){return{name:"Shell Session",aliases:["console"],contains:[{className:"meta",begin:"^\\s{0,3}[/\\w\\d\\[\\]()@-]*[>%$#]",starts:{end:"$",subLanguage:"bash"}}]}}}()); -hljs.registerLanguage("sql",function(){"use strict";return function(e){var t=e.COMMENT("--","$");return{name:"SQL",case_insensitive:!0,illegal:/[<>{}*]/,contains:[{beginKeywords:"begin end start commit rollback savepoint lock alter create drop rename call delete do handler insert load replace select truncate update set show pragma grant merge describe use explain help declare prepare execute deallocate release unlock purge reset change stop analyze cache flush optimize repair kill install uninstall checksum restore check backup revoke comment values with",end:/;/,endsWithParent:!0,keywords:{$pattern:/[\w\.]+/,keyword:"as abort abs absolute acc acce accep accept access accessed accessible account acos action activate add addtime admin administer advanced advise aes_decrypt aes_encrypt after agent aggregate ali alia alias all allocate allow alter always analyze ancillary and anti any anydata anydataset anyschema anytype apply archive archived archivelog are as asc ascii asin assembly assertion associate asynchronous at atan atn2 attr attri attrib attribu attribut attribute attributes audit authenticated authentication authid authors auto autoallocate autodblink autoextend automatic availability avg backup badfile basicfile before begin beginning benchmark between bfile bfile_base big bigfile bin binary_double binary_float binlog bit_and bit_count bit_length bit_or bit_xor bitmap blob_base block blocksize body both bound bucket buffer_cache buffer_pool build bulk by byte byteordermark bytes cache caching call calling cancel capacity cascade cascaded case cast catalog category ceil ceiling chain change changed char_base char_length character_length characters characterset charindex charset charsetform charsetid check checksum checksum_agg child choose chr chunk class cleanup clear client clob clob_base clone close cluster_id cluster_probability cluster_set clustering coalesce coercibility col collate collation collect colu colum column column_value columns columns_updated comment commit compact compatibility compiled complete composite_limit compound compress compute concat concat_ws concurrent confirm conn connec connect connect_by_iscycle connect_by_isleaf connect_by_root connect_time connection consider consistent constant constraint constraints constructor container content contents context contributors controlfile conv convert convert_tz corr corr_k corr_s corresponding corruption cos cost count count_big counted covar_pop covar_samp cpu_per_call cpu_per_session crc32 create creation critical cross cube cume_dist curdate current current_date current_time current_timestamp current_user cursor curtime customdatum cycle data database databases datafile datafiles datalength date_add date_cache date_format date_sub dateadd datediff datefromparts datename datepart datetime2fromparts day day_to_second dayname dayofmonth dayofweek dayofyear days db_role_change dbtimezone ddl deallocate declare decode decompose decrement decrypt deduplicate def defa defau defaul default defaults deferred defi defin define degrees delayed delegate delete delete_all delimited demand dense_rank depth dequeue des_decrypt des_encrypt des_key_file desc descr descri describ describe descriptor deterministic diagnostics difference dimension direct_load directory disable disable_all disallow disassociate discardfile disconnect diskgroup distinct distinctrow distribute distributed div do document domain dotnet double downgrade drop dumpfile duplicate duration each edition editionable editions element ellipsis else elsif elt empty enable enable_all enclosed encode encoding encrypt end end-exec endian enforced engine engines enqueue enterprise entityescaping eomonth error errors escaped evalname evaluate event eventdata events except exception exceptions exchange exclude excluding execu execut execute exempt exists exit exp expire explain explode export export_set extended extent external external_1 external_2 externally extract failed failed_login_attempts failover failure far fast feature_set feature_value fetch field fields file file_name_convert filesystem_like_logging final finish first first_value fixed flash_cache flashback floor flush following follows for forall force foreign form forma format found found_rows freelist freelists freepools fresh from from_base64 from_days ftp full function general generated get get_format get_lock getdate getutcdate global global_name globally go goto grant grants greatest group group_concat group_id grouping grouping_id groups gtid_subtract guarantee guard handler hash hashkeys having hea head headi headin heading heap help hex hierarchy high high_priority hosts hour hours http id ident_current ident_incr ident_seed identified identity idle_time if ifnull ignore iif ilike ilm immediate import in include including increment index indexes indexing indextype indicator indices inet6_aton inet6_ntoa inet_aton inet_ntoa infile initial initialized initially initrans inmemory inner innodb input insert install instance instantiable instr interface interleaved intersect into invalidate invisible is is_free_lock is_ipv4 is_ipv4_compat is_not is_not_null is_used_lock isdate isnull isolation iterate java join json json_exists keep keep_duplicates key keys kill language large last last_day last_insert_id last_value lateral lax lcase lead leading least leaves left len lenght length less level levels library like like2 like4 likec limit lines link list listagg little ln load load_file lob lobs local localtime localtimestamp locate locator lock locked log log10 log2 logfile logfiles logging logical logical_reads_per_call logoff logon logs long loop low low_priority lower lpad lrtrim ltrim main make_set makedate maketime managed management manual map mapping mask master master_pos_wait match matched materialized max maxextents maximize maxinstances maxlen maxlogfiles maxloghistory maxlogmembers maxsize maxtrans md5 measures median medium member memcompress memory merge microsecond mid migration min minextents minimum mining minus minute minutes minvalue missing mod mode model modification modify module monitoring month months mount move movement multiset mutex name name_const names nan national native natural nav nchar nclob nested never new newline next nextval no no_write_to_binlog noarchivelog noaudit nobadfile nocheck nocompress nocopy nocycle nodelay nodiscardfile noentityescaping noguarantee nokeep nologfile nomapping nomaxvalue nominimize nominvalue nomonitoring none noneditionable nonschema noorder nopr nopro noprom nopromp noprompt norely noresetlogs noreverse normal norowdependencies noschemacheck noswitch not nothing notice notnull notrim novalidate now nowait nth_value nullif nulls num numb numbe nvarchar nvarchar2 object ocicoll ocidate ocidatetime ociduration ociinterval ociloblocator ocinumber ociref ocirefcursor ocirowid ocistring ocitype oct octet_length of off offline offset oid oidindex old on online only opaque open operations operator optimal optimize option optionally or oracle oracle_date oradata ord ordaudio orddicom orddoc order ordimage ordinality ordvideo organization orlany orlvary out outer outfile outline output over overflow overriding package pad parallel parallel_enable parameters parent parse partial partition partitions pascal passing password password_grace_time password_lock_time password_reuse_max password_reuse_time password_verify_function patch path patindex pctincrease pctthreshold pctused pctversion percent percent_rank percentile_cont percentile_disc performance period period_add period_diff permanent physical pi pipe pipelined pivot pluggable plugin policy position post_transaction pow power pragma prebuilt precedes preceding precision prediction prediction_cost prediction_details prediction_probability prediction_set prepare present preserve prior priority private private_sga privileges procedural procedure procedure_analyze processlist profiles project prompt protection public publishingservername purge quarter query quick quiesce quota quotename radians raise rand range rank raw read reads readsize rebuild record records recover recovery recursive recycle redo reduced ref reference referenced references referencing refresh regexp_like register regr_avgx regr_avgy regr_count regr_intercept regr_r2 regr_slope regr_sxx regr_sxy reject rekey relational relative relaylog release release_lock relies_on relocate rely rem remainder rename repair repeat replace replicate replication required reset resetlogs resize resource respect restore restricted result result_cache resumable resume retention return returning returns reuse reverse revoke right rlike role roles rollback rolling rollup round row row_count rowdependencies rowid rownum rows rtrim rules safe salt sample save savepoint sb1 sb2 sb4 scan schema schemacheck scn scope scroll sdo_georaster sdo_topo_geometry search sec_to_time second seconds section securefile security seed segment select self semi sequence sequential serializable server servererror session session_user sessions_per_user set sets settings sha sha1 sha2 share shared shared_pool short show shrink shutdown si_averagecolor si_colorhistogram si_featurelist si_positionalcolor si_stillimage si_texture siblings sid sign sin size size_t sizes skip slave sleep smalldatetimefromparts smallfile snapshot some soname sort soundex source space sparse spfile split sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_small_result sql_variant_property sqlcode sqldata sqlerror sqlname sqlstate sqrt square standalone standby start starting startup statement static statistics stats_binomial_test stats_crosstab stats_ks_test stats_mode stats_mw_test stats_one_way_anova stats_t_test_ stats_t_test_indep stats_t_test_one stats_t_test_paired stats_wsr_test status std stddev stddev_pop stddev_samp stdev stop storage store stored str str_to_date straight_join strcmp strict string struct stuff style subdate subpartition subpartitions substitutable substr substring subtime subtring_index subtype success sum suspend switch switchoffset switchover sync synchronous synonym sys sys_xmlagg sysasm sysaux sysdate sysdatetimeoffset sysdba sysoper system system_user sysutcdatetime table tables tablespace tablesample tan tdo template temporary terminated tertiary_weights test than then thread through tier ties time time_format time_zone timediff timefromparts timeout timestamp timestampadd timestampdiff timezone_abbr timezone_minute timezone_region to to_base64 to_date to_days to_seconds todatetimeoffset trace tracking transaction transactional translate translation treat trigger trigger_nestlevel triggers trim truncate try_cast try_convert try_parse type ub1 ub2 ub4 ucase unarchived unbounded uncompress under undo unhex unicode uniform uninstall union unique unix_timestamp unknown unlimited unlock unnest unpivot unrecoverable unsafe unsigned until untrusted unusable unused update updated upgrade upped upper upsert url urowid usable usage use use_stored_outlines user user_data user_resources users using utc_date utc_timestamp uuid uuid_short validate validate_password_strength validation valist value values var var_samp varcharc vari varia variab variabl variable variables variance varp varraw varrawc varray verify version versions view virtual visible void wait wallet warning warnings week weekday weekofyear wellformed when whene whenev wheneve whenever where while whitespace window with within without work wrapped xdb xml xmlagg xmlattributes xmlcast xmlcolattval xmlelement xmlexists xmlforest xmlindex xmlnamespaces xmlpi xmlquery xmlroot xmlschema xmlserialize xmltable xmltype xor year year_to_month years yearweek",literal:"true false null unknown",built_in:"array bigint binary bit blob bool boolean char character date dec decimal float int int8 integer interval number numeric real record serial serial8 smallint text time timestamp tinyint varchar varchar2 varying void"},contains:[{className:"string",begin:"'",end:"'",contains:[{begin:"''"}]},{className:"string",begin:'"',end:'"',contains:[{begin:'""'}]},{className:"string",begin:"`",end:"`"},e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE,t,e.HASH_COMMENT_MODE]},e.C_BLOCK_COMMENT_MODE,t,e.HASH_COMMENT_MODE]}}}()); -hljs.registerLanguage("swift",function(){"use strict";return function(e){var i={keyword:"#available #colorLiteral #column #else #elseif #endif #file #fileLiteral #function #if #imageLiteral #line #selector #sourceLocation _ __COLUMN__ __FILE__ __FUNCTION__ __LINE__ Any as as! as? associatedtype associativity break case catch class continue convenience default defer deinit didSet do dynamic dynamicType else enum extension fallthrough false fileprivate final for func get guard if import in indirect infix init inout internal is lazy left let mutating nil none nonmutating open operator optional override postfix precedence prefix private protocol Protocol public repeat required rethrows return right self Self set static struct subscript super switch throw throws true try try! try? Type typealias unowned var weak where while willSet",literal:"true false nil",built_in:"abs advance alignof alignofValue anyGenerator assert assertionFailure bridgeFromObjectiveC bridgeFromObjectiveCUnconditional bridgeToObjectiveC bridgeToObjectiveCUnconditional c compactMap contains count countElements countLeadingZeros debugPrint debugPrintln distance dropFirst dropLast dump encodeBitsAsWords enumerate equal fatalError filter find getBridgedObjectiveCType getVaList indices insertionSort isBridgedToObjectiveC isBridgedVerbatimToObjectiveC isUniquelyReferenced isUniquelyReferencedNonObjC join lazy lexicographicalCompare map max maxElement min minElement numericCast overlaps partition posix precondition preconditionFailure print println quickSort readLine reduce reflect reinterpretCast reverse roundUpToAlignment sizeof sizeofValue sort split startsWith stride strideof strideofValue swap toString transcode underestimateCount unsafeAddressOf unsafeBitCast unsafeDowncast unsafeUnwrap unsafeReflect withExtendedLifetime withObjectAtPlusZero withUnsafePointer withUnsafePointerToObject withUnsafeMutablePointer withUnsafeMutablePointers withUnsafePointer withUnsafePointers withVaList zip"},n=e.COMMENT("/\\*","\\*/",{contains:["self"]}),t={className:"subst",begin:/\\\(/,end:"\\)",keywords:i,contains:[]},a={className:"string",contains:[e.BACKSLASH_ESCAPE,t],variants:[{begin:/"""/,end:/"""/},{begin:/"/,end:/"/}]},r={className:"number",begin:"\\b([\\d_]+(\\.[\\deE_]+)?|0x[a-fA-F0-9_]+(\\.[a-fA-F0-9p_]+)?|0b[01_]+|0o[0-7_]+)\\b",relevance:0};return t.contains=[r],{name:"Swift",keywords:i,contains:[a,e.C_LINE_COMMENT_MODE,n,{className:"type",begin:"\\b[A-Z][\\wÀ-ʸ']*[!?]"},{className:"type",begin:"\\b[A-Z][\\wÀ-ʸ']*",relevance:0},r,{className:"function",beginKeywords:"func",end:"{",excludeEnd:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/[A-Za-z$_][0-9A-Za-z$_]*/}),{begin://},{className:"params",begin:/\(/,end:/\)/,endsParent:!0,keywords:i,contains:["self",r,a,e.C_BLOCK_COMMENT_MODE,{begin:":"}],illegal:/["']/}],illegal:/\[|%/},{className:"class",beginKeywords:"struct protocol class extension enum",keywords:i,end:"\\{",excludeEnd:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/[A-Za-z$_][\u00C0-\u02B80-9A-Za-z$_]*/})]},{className:"meta",begin:"(@discardableResult|@warn_unused_result|@exported|@lazy|@noescape|@NSCopying|@NSManaged|@objc|@objcMembers|@convention|@required|@noreturn|@IBAction|@IBDesignable|@IBInspectable|@IBOutlet|@infix|@prefix|@postfix|@autoclosure|@testable|@available|@nonobjc|@NSApplicationMain|@UIApplicationMain|@dynamicMemberLookup|@propertyWrapper)\\b"},{beginKeywords:"import",end:/$/,contains:[e.C_LINE_COMMENT_MODE,n]}]}}}()); -hljs.registerLanguage("typescript",function(){"use strict";const e=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],n=["true","false","null","undefined","NaN","Infinity"],a=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]);return function(r){var t={$pattern:"[A-Za-z$_][0-9A-Za-z$_]*",keyword:e.concat(["type","namespace","typedef","interface","public","private","protected","implements","declare","abstract","readonly"]).join(" "),literal:n.join(" "),built_in:a.concat(["any","void","number","boolean","string","object","never","enum"]).join(" ")},s={className:"meta",begin:"@[A-Za-z$_][0-9A-Za-z$_]*"},i={className:"number",variants:[{begin:"\\b(0[bB][01]+)n?"},{begin:"\\b(0[oO][0-7]+)n?"},{begin:r.C_NUMBER_RE+"n?"}],relevance:0},o={className:"subst",begin:"\\$\\{",end:"\\}",keywords:t,contains:[]},c={begin:"html`",end:"",starts:{end:"`",returnEnd:!1,contains:[r.BACKSLASH_ESCAPE,o],subLanguage:"xml"}},l={begin:"css`",end:"",starts:{end:"`",returnEnd:!1,contains:[r.BACKSLASH_ESCAPE,o],subLanguage:"css"}},E={className:"string",begin:"`",end:"`",contains:[r.BACKSLASH_ESCAPE,o]};o.contains=[r.APOS_STRING_MODE,r.QUOTE_STRING_MODE,c,l,E,i,r.REGEXP_MODE];var d={begin:"\\(",end:/\)/,keywords:t,contains:["self",r.QUOTE_STRING_MODE,r.APOS_STRING_MODE,r.NUMBER_MODE]},u={className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:t,contains:[r.C_LINE_COMMENT_MODE,r.C_BLOCK_COMMENT_MODE,s,d]};return{name:"TypeScript",aliases:["ts"],keywords:t,contains:[r.SHEBANG(),{className:"meta",begin:/^\s*['"]use strict['"]/},r.APOS_STRING_MODE,r.QUOTE_STRING_MODE,c,l,E,r.C_LINE_COMMENT_MODE,r.C_BLOCK_COMMENT_MODE,i,{begin:"("+r.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*",keywords:"return throw case",contains:[r.C_LINE_COMMENT_MODE,r.C_BLOCK_COMMENT_MODE,r.REGEXP_MODE,{className:"function",begin:"(\\([^(]*(\\([^(]*(\\([^(]*\\))?\\))?\\)|"+r.UNDERSCORE_IDENT_RE+")\\s*=>",returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:r.UNDERSCORE_IDENT_RE},{className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:t,contains:d.contains}]}]}],relevance:0},{className:"function",beginKeywords:"function",end:/[\{;]/,excludeEnd:!0,keywords:t,contains:["self",r.inherit(r.TITLE_MODE,{begin:"[A-Za-z$_][0-9A-Za-z$_]*"}),u],illegal:/%/,relevance:0},{beginKeywords:"constructor",end:/[\{;]/,excludeEnd:!0,contains:["self",u]},{begin:/module\./,keywords:{built_in:"module"},relevance:0},{beginKeywords:"module",end:/\{/,excludeEnd:!0},{beginKeywords:"interface",end:/\{/,excludeEnd:!0,keywords:"interface extends"},{begin:/\$[(.]/},{begin:"\\."+r.IDENT_RE,relevance:0},s,d]}}}()); -hljs.registerLanguage("yaml",function(){"use strict";return function(e){var n="true false yes no null",a="[\\w#;/?:@&=+$,.~*\\'()[\\]]+",s={className:"string",relevance:0,variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/\S+/}],contains:[e.BACKSLASH_ESCAPE,{className:"template-variable",variants:[{begin:"{{",end:"}}"},{begin:"%{",end:"}"}]}]},i=e.inherit(s,{variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/[^\s,{}[\]]+/}]}),l={end:",",endsWithParent:!0,excludeEnd:!0,contains:[],keywords:n,relevance:0},t={begin:"{",end:"}",contains:[l],illegal:"\\n",relevance:0},g={begin:"\\[",end:"\\]",contains:[l],illegal:"\\n",relevance:0},b=[{className:"attr",variants:[{begin:"\\w[\\w :\\/.-]*:(?=[ \t]|$)"},{begin:'"\\w[\\w :\\/.-]*":(?=[ \t]|$)'},{begin:"'\\w[\\w :\\/.-]*':(?=[ \t]|$)"}]},{className:"meta",begin:"^---s*$",relevance:10},{className:"string",begin:"[\\|>]([0-9]?[+-])?[ ]*\\n( *)[\\S ]+\\n(\\2[\\S ]+\\n?)*"},{begin:"<%[%=-]?",end:"[%-]?%>",subLanguage:"ruby",excludeBegin:!0,excludeEnd:!0,relevance:0},{className:"type",begin:"!\\w+!"+a},{className:"type",begin:"!<"+a+">"},{className:"type",begin:"!"+a},{className:"type",begin:"!!"+a},{className:"meta",begin:"&"+e.UNDERSCORE_IDENT_RE+"$"},{className:"meta",begin:"\\*"+e.UNDERSCORE_IDENT_RE+"$"},{className:"bullet",begin:"\\-(?=[ ]|$)",relevance:0},e.HASH_COMMENT_MODE,{beginKeywords:n,keywords:{literal:n}},{className:"number",begin:"\\b[0-9]{4}(-[0-9][0-9]){0,2}([Tt \\t][0-9][0-9]?(:[0-9][0-9]){2})?(\\.[0-9]*)?([ \\t])*(Z|[-+][0-9][0-9]?(:[0-9][0-9])?)?\\b"},{className:"number",begin:e.C_NUMBER_RE+"\\b"},t,g,s],c=[...b];return c.pop(),c.push(i),l.contains=c,{name:"YAML",case_insensitive:!0,aliases:["yml","YAML"],contains:b}}}()); \ No newline at end of file diff --git a/public/categories/index.html b/public/categories/index.html deleted file mode 100644 index 96a7795..0000000 --- a/public/categories/index.html +++ /dev/null @@ -1,4 +0,0 @@ -Categories | PrivSec.dev
    \ No newline at end of file diff --git a/public/categories/index.xml b/public/categories/index.xml deleted file mode 100644 index 6e4599c..0000000 --- a/public/categories/index.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - Categories on PrivSec.dev - https://privsec.dev/categories/ - Recent content in Categories on PrivSec.dev - Hugo -- gohugo.io - - diff --git a/public/donate/index.html b/public/donate/index.html deleted file mode 100644 index bc51460..0000000 --- a/public/donate/index.html +++ /dev/null @@ -1,6 +0,0 @@ -Donate | PrivSec.dev -

    Donate

    The domain costs us $12/year to renew from Google. We got our repository hosted for free on GitHub. We got our site hosted for free with Firebase. It costs Tommy ~$20/month to run the mail server, but that server is used for a bunch of his projects, not just PrivSec, and we doubt it will be used that much anyways. The point is, this website does not cost much to run, and as such we will not be accepting donation as a project.

    The real cost is the time and energy we put into writing, testing, and fact checking the content. Some of our members may want accept donation, and you can donate to them individually.

    June

    Monero: 49b1PUPeHJEDwZqaaQm4MQUjycY8ckEko53jvTcPB5yE2QKoS5haNe3Fnbg1Le7nSkgUkm4tcpj4Z2YmtaT3j6KVUVgBGw2

    Randomhydrosol

    Bitcoin: bc1qchel9lzhuv3ayfp58yfdu7sxsjw2svgugtvj4v -Monero: 49yB5DPXK9TJVj5Jq5DkvrXd4wkFnoeC56mPED85bf5wHTUYmSyoYFEbVyyTciKzjFTo1kxMJMiCpLwuR96fT2NWS1hPVFG


    Alternatively, please consider donating to the projects below. These are projects which we rely on for our own digital safety and recommend to our readers. They are vital for the privacy, security, and safety of thousands of people.

    GrapheneOS

    Donation Link: grapheneos.org/donate

    \ No newline at end of file diff --git a/public/fedora-screenshot.png b/public/fedora-screenshot.png deleted file mode 100644 index 220e970..0000000 Binary files a/public/fedora-screenshot.png and /dev/null differ diff --git a/public/index.html b/public/index.html deleted file mode 100644 index ea0c33f..0000000 --- a/public/index.html +++ /dev/null @@ -1,6 +0,0 @@ -PrivSec.dev

    PrivSec.dev

    A practical approach to privacy and security
    \ No newline at end of file diff --git a/public/index.xml b/public/index.xml deleted file mode 100644 index 2915abf..0000000 --- a/public/index.xml +++ /dev/null @@ -1,94 +0,0 @@ - - - - PrivSec.dev - https://privsec.dev/ - Recent content on PrivSec.dev - Hugo -- gohugo.io - - About Us - https://privsec.dev/about/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://privsec.dev/about/ - PrivSec.dev is made by a group of enthusiastic individuals looking to provide practical privacy and security advice for the end user. We are security researchers, developers, system administrators&hellip; generally people with technical knowledge and work in the field. -We focus on in-depth system configuration, security analysis, and software/hardware recommendations. Our site is based on technical merits, not ideologies and politics. -Tommy System Administrator. Benevolent dictator for life @privsec.dev. -Website: tommytran.io - - - - Choosing Your Desktop Linux Distribution - https://privsec.dev/os/choosing-your-desktop-linux-distribution/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://privsec.dev/os/choosing-your-desktop-linux-distribution/ - Not all Linux distributions are created equal. When choosing a Linux distribution, there are several things you need to keep in mind. -Release cycle You should choose a distribution which stays close to the stable upstream software releases, typically rolling release distributions. This is because frozen release cycle distributions often don’t update package versions and fall behind on security updates. -For frozen distributions, package maintainers are expected to backport patches to fix vulnerabilities (Debian is one such example) rather than bump the software to the “next version” released by the upstream developer. - - - - Docker and OCI Hardening - https://privsec.dev/os/docker-and-oci-hardening/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://privsec.dev/os/docker-and-oci-hardening/ - Containers aren&rsquo;t that new fancy thing anymore, but they were a big deal. And they still are. They are a concrete solution to the following problem: -- Hey, your software doesn&rsquo;t work&hellip; -- Sorry, it works on my computer! Can&rsquo;t help you. -Whether we like them or not, containers are here to stay. Their expressiveness and semantics allow for an abstraction of the OS dependencies that a software has, the latter being often dynamically linked against certain libraries. - - - - Donate - https://privsec.dev/donate/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://privsec.dev/donate/ - The domain costs us $12/year to renew from Google. We got our repository hosted for free on GitHub. We got our site hosted for free with Firebase. It costs Tommy ~$20/month to run the mail server, but that server is used for a bunch of his projects, not just PrivSec, and we doubt it will be used that much anyways. The point is, this website does not cost much to run, and as such we will not be accepting donation as a project. - - - - F-Droid Security Analysis - https://privsec.dev/apps/f-droid-security-analysis/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://privsec.dev/apps/f-droid-security-analysis/ - F-Droid is a popular alternative app repository for Android, especially known for its main repository dedicated to free and open-source software. F-Droid is often recommended among security and privacy enthusiasts, but how does it stack up against Play Store in practice? This write-up will attempt to emphasize major security issues with F-Droid that you should consider. -Before we start, a few things to keep in mind: -The main goal of this write-up was to inform users so they can make responsible choices, not to trash someone else&rsquo;s work. - - - - Linux Insecurities - https://privsec.dev/os/linux-insecurities/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://privsec.dev/os/linux-insecurities/ - There is a common misconception among privacy communities that Linux is one of the more secure operating systems, either because it is open source or because it is widely used in the cloud. This is however, a far cry from reality. -There is already a very indepth technical blog explaning the various security weaknesses of Linux by Madaidan, Whonix&rsquo;s Security Researcher. This page will attempt to address some of the questions commonly raised in reaction to his blog post. - - - - Multi-factor Authentication - https://privsec.dev/knowledge/multi-factor-authentication/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://privsec.dev/knowledge/multi-factor-authentication/ - Multi-factor authentication is a security mechanism that requires additional verification beyond your username (or email) and password. This usually comes in the form of a one time passcode, a push notification, or plugging in and tapping a hardware security key. -Common protocols Email and SMS MFA Email and SMS MFA are examples of the weaker MFA protocols. Email MFA is not great as whoever controls your email account can typically both reset your password and recieve your MFA verification. - - - - Securing OpenSSH with FIDO2 - https://privsec.dev/os/securing-openssh-with-fido2/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://privsec.dev/os/securing-openssh-with-fido2/ - Passwordless authentication with OpenSSH keys has been the de facto security standard for years. SSH keys are more robust since they&rsquo;re cryptographically sane by default, and are therefore resilient to most bruteforce atacks. They&rsquo;re also easier to manage while enabling a form of decentralized authentication (it&rsquo;s easy and painless to revoke them). So, what&rsquo;s the next step? And more exactly, why would one need something even better? -Why? The main problem with SSH keys is that they&rsquo;re not magic: they consist of a key pair, of which the private key is stored on your disk. - - - - diff --git a/public/knowledge/index.html b/public/knowledge/index.html deleted file mode 100644 index a67ca9f..0000000 --- a/public/knowledge/index.html +++ /dev/null @@ -1,6 +0,0 @@ -Knowledge Base | PrivSec.dev

    Multi-factor Authentication

    Multi-factor authentication is a security mechanism that requires additional verification beyond your username (or email) and password. This usually comes in the form of a one time passcode, a push notification, or plugging in and tapping a hardware security key. -Common protocols Email and SMS MFA Email and SMS MFA are examples of the weaker MFA protocols. Email MFA is not great as whoever controls your email account can typically both reset your password and recieve your MFA verification....

    6 min · 1223 words · Tommy
    \ No newline at end of file diff --git a/public/knowledge/index.xml b/public/knowledge/index.xml deleted file mode 100644 index 108e81e..0000000 --- a/public/knowledge/index.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - Knowledge Base on PrivSec.dev - https://privsec.dev/knowledge/ - Recent content in Knowledge Base on PrivSec.dev - Hugo -- gohugo.io - - Multi-factor Authentication - https://privsec.dev/knowledge/multi-factor-authentication/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://privsec.dev/knowledge/multi-factor-authentication/ - Multi-factor authentication is a security mechanism that requires additional verification beyond your username (or email) and password. This usually comes in the form of a one time passcode, a push notification, or plugging in and tapping a hardware security key. -Common protocols Email and SMS MFA Email and SMS MFA are examples of the weaker MFA protocols. Email MFA is not great as whoever controls your email account can typically both reset your password and recieve your MFA verification. - - - - diff --git a/public/knowledge/multi-factor-authentication/index.html b/public/knowledge/multi-factor-authentication/index.html deleted file mode 100644 index c4e74c6..0000000 --- a/public/knowledge/multi-factor-authentication/index.html +++ /dev/null @@ -1,8 +0,0 @@ -Multi-factor Authentication | PrivSec.dev -

    Multi-factor Authentication

    Multi-factor authentication is a security mechanism that requires additional verification beyond your username (or email) and password. This usually comes in the form of a one time passcode, a push notification, or plugging in and tapping a hardware security key.

    Common protocols

    Email and SMS MFA

    Email and SMS MFA are examples of the weaker MFA protocols. Email MFA is not great as whoever controls your email account can typically both reset your password and recieve your MFA verification. SMS on the other hand is problematic due to the lack of any kind of encryption, making it vulnerable to sniffing. Sim swap attacks, if carried out successfully, will allow an attacker to recieve your one time passcode while locking you out of your own account. In certain cases,websites or services may also allow the user to reset their account login by calling them using the phone number used for MFA, which could be faked with a spoofed CallerID.

    Only use these protocols when it is the only option you have, and be very careful with SMS MFA as it could actually worsen your security.

    Push Confirmations

    Push confirmation MFA is typically a notification being sent to an app on your phone asking you to confirm new account logins. This method is a lot better than SMS or email, since an attacker typically wouldn’t be able to get these push notifications without having an already logged-in device.

    Push comfirmation in most cases rely on a third party provider like Duo. This means that trust is placed in a server that neither you nor your service provider control. A malicious push confirmation server could compromise your MFA or profile you based on which website and account you use with the service.

    Even if the push notification application and server is provided by first party as is the case with Microsoft login and Microsoft Authenticator, there is still a risk of you accidentally tapping on the confirmation button.

    Time-based One-time Password (TOTP)

    TOTP is one of the most common forms of MFA available. When you set up TOTP, you setup a “shared secret” with the service that you intend to use and store it in your authentication app.

    The time-limited code is then derived from the shared secret and the current time. As the code is only valid for a short time, without access to the shared secret, an adversary cannot generate new codes.

    If you have a Yubikey, you should store the “shared secrets” on the key itself using the Yubico Authenticator app. After the initial setup, the Yubico Authenticator will only expose the 6 digit code to the machine it is running on, but not the shared secret. Additional security can be set up by requiring touch confirmation, protecting digit codes not in used from a compromised operating system.

    Unlike WebAuthn, TOTP offers no protection against phishing or reuse attacks. If an adversary obtains a valid code from you, they may use it as many times as they like until it expires (generally 60 seconds + grace period).

    Despite of its short comings, we consider TOTP better and safer than Push Confirmations.

    Yubico OTP

    Yubico OTP is an authentication protocol typically implemented in hardware security keys. When you decide to use Yubico OTP, the key will generate a public ID, private ID, and a Secret Key which is then uploaded to the Yubico OTP server.

    When logging into a website, all you need to do is to physically touch the security key. The security key will emulate a keyboard and print out a one-time password into the password field.

    The service will then forward the one-time password to the Yubico OTP server for validation. A counter is incremented both on the key and Yubico’s validation server. The OTP can only be used once, and when a successful authentication occurs, the counter is increased which prevents reuse of the OTP. Yubico provides a detailed document about the process.

    Yubico OTP

    The Yubico validation server is a cloud based service, and you’re placing trust in Yubico that their server won’t be used to bypass your MFA or profile you. The public ID associated with Yubico OTP is reused on every website and could be another avenue for third-parties to profile you. Like TOTP, Yubico OTP does not provide phishing resistance.

    Yubico OTP is an inferior protocol compared to TOTP since TOTP does not need trust in a third party server and most security keys that support Yubico OTP (namely the Yubikey and OnlyKey) supports TOTP anyway. Yubico OTP is still better than Push Confirmation, however.

    FIDO2 (Fast IDentity Online)

    FIDO includes a number of standards, first there was U2F and then later FIDO2 which includes the web standard WebAuthn.

    U2F and FIDO2 refer to the Client to Authenticator Protocol, which is the protocol between the security key and the computer, such as a laptop or phone. It complements WebAuthn which is the component used to authenticate with the website (the “Relying Party”) you’re trying to log in on.

    WebAuthn is the most secure and private form of second factor authentication. While the authentication experience is similar to Yubico OTP, the key does not print out a one-time password and validate with a third-party server. Instead, it uses public key cryptography for authentication.

    Since FIDO2/WebAuthn uses unique cryptographic keys with each internet site, a site pretending to be another one will not be able to get the correct response to the challenge for MFA, making FIDO2/Webauthn is invulnerable phising. It is also because of this authentication mechanism that a physical FIDO2 security key is not identifiable across different services like Yubico OTP. Even better, FIDO2 uses a counter for each authentication, which would help with detecting cloned keys.

    If a website or service supports WebAuthn for the authentication, it is highly recommended that you use it over any other form of MFA.

    Notes

    Initial Set Up

    When buying a security key, it is important that you change the default credentials, set up password protection for the key, and enable touch confirmation if your key supports it. Products such as the YubiKey have multiple interfaces with separate credentials for each one of them, so you should go over each interface and set up protection as well.

    Backups

    You should always have backups for your MFA method. Hardware security keys can get lost, stolen or simply stop working over time. It is recommended that you have a pair of hardware security keys with the same access to your accounts instead of just one.

    When using TOTP with an authenticator app, be sure to back up your recovery keys to an offline and encrypted storage device.

    You are only as secure as the weakest authentication method you use. For instance, it makes little sense to add SMS 2FA as an alternative MFA method if you are already using FIDO2. An adversary who can compromise your SMS 2FA will get into your account just as easily as if you didn’t use FIDO2 at all.

    Thus, it is important to stick to the best authentication method you have acess to. It is better to have 2 Yubikeys for FIDO2 than 1 FIDO2 key and one authenticator app for TOTP. Likewise, it is better to have 1 TOTP instance and a backup key than to use TOTP alongside with Email or SMS 2FA.

    \ No newline at end of file diff --git a/public/knowledge/page/1/index.html b/public/knowledge/page/1/index.html deleted file mode 100644 index 5b57665..0000000 --- a/public/knowledge/page/1/index.html +++ /dev/null @@ -1 +0,0 @@ -https://privsec.dev/knowledge/ \ No newline at end of file diff --git a/public/os/choosing-your-desktop-linux-distribution/index.html b/public/os/choosing-your-desktop-linux-distribution/index.html deleted file mode 100644 index 9ccc9a7..0000000 --- a/public/os/choosing-your-desktop-linux-distribution/index.html +++ /dev/null @@ -1,11 +0,0 @@ -Choosing Your Desktop Linux Distribution | PrivSec.dev -

    Choosing Your Desktop Linux Distribution

    Not all Linux distributions are created equal. When choosing a Linux distribution, there are several things you need to keep in mind.

    Release cycle

    You should choose a distribution which stays close to the stable upstream software releases, typically rolling release distributions. This is because frozen release cycle distributions often don’t update package versions and fall behind on security updates.

    For frozen distributions, package maintainers are expected to backport patches to fix vulnerabilities (Debian is one such example) rather than bump the software to the “next version” released by the upstream developer. Some security fixes do not receive a CVE (particularly less popular software) at all and therefore do not make it into the distribution with this patching model. As a result minor security fixes are sometimes held back until the next major release.

    Holding packages back and applying interim patches is generally not a good idea, as it diverges from the way the developer might have intended the software to work. Richard Brown has a presentation about this:

    Traditional and Atomic updates

    Traditionally, Linux distributions update by sequentially updating the desired packages. Traditional updates such as those used in Fedora, Arch Linux, and Debian based distributions can be less reliable if an error occurs while updating.

    Atomic updating distributions apply updates in full or not at all. Typically, transactional update systems are also atomic.

    A transactional update system creates a snapshot that is made before and after an update is applied. If an update fails at any time (perhaps due to a power failure), the update can be easily rolled back to a “last known good state."

    Adam Šamalík has a presentation with rpm-ostree in action:

    Even if you are worried about the stability of the system because of regularly updated packages (which you shouldn’t), it makes more sense to use a system which you can safely update and rollback instead of an outdated distribution partially made up of unreliable backport packages without an easy to actually roll back in case something goes wrong like Debian.

    Arch-based distributions

    Acrh Linux has very up to date packages with minimal downstream patching. That being said, Arch based distributions are not recommended for those new to Linux, regardless of the distribution. Arch does not have an distribution update mechanism for the underlying software choices. As a result you have to stay aware with current trends and adopt technologies as they supersede older practices on your own.

    For a secure system, you are also expected to have sufficient Linux knowledge to properly set up security for their system such as adopting a mandatory access control system, setting up kernel module blacklists, hardening boot parameters, manipulating sysctl parameters, and knowing what components they need such as Polkit.

    If you are experienced with Linux and wish to use an Arch-based distribution, you should use Arch Linux proper, not any of its derivatives. Here are some examples of why that is the case:

    • Manjaro: This distribution holds packages back for 2 weeks to make sure that their own changes do not break, not to make sure that upstream is stable. When AUR packages are used, they are often built against the latest libraries from Arch’s repositories.
    • Garuda: They use Chaotic-AUR which automatically and blindly compiles packages from the AUR. There is no verification process to make sure that the AUR packages don’t suffer from supply chain attacks.

    Kicksecure

    While you should not use an outdated distributions like Debian, if you decide to use it, it would be a good idea to convert it into Kicksecure. Kicksecure, in oversimplified terms, is a set of scripts, configurations, and packages that substantially reduce the attack surface of Debian. It covers a lot of privacy and hardening recommendations by default.

    “Security-focused” distributions

    There is often some confusion about “security-focused” distributions and “pentesting” distributions. A quick search for “the most secure Linux distribution” will often give results like Kali Linux, Black Arch and Parrot OS. These distributions are offensive penetration testing distributions that bundle tools for testing other systems. They don’t include any “extra security” or defensive mitigations intended for regular use.

    Linux-libre kernel and “Libre” distributions

    Do not the Linux-libre kernel, since it removes security mitigations and suppresses kernel warnings about vulnerable microcode for ideological reasons.

    If you want to use one of these distributions for reasons other than ideology, you should make sure that they there is a way to easily obtain, install and update a proper kernel and missing firmware. For example, if you are looking to use GUIX, you should absolutely use something like the Nonguix repository and get all of the fixes as mentioned above.

    Wayland

    You should a desktop environment that supports the Wayland display protocol as it developed with security in mind. Its predecessor, X11, does not support GUI isolation, allowing all windows to record screen, log and inject inputs in other windows, making any attempt at sandboxing futile. While there are options to do nested X11 such as Xpra or Xephyr, they often come with negative performance consequences and are not convenient to set up and are not preferable over Wayland.

    Fortunately, common environments such as GNOME, KDE, and the window manager Sway have support for Wayland. Some distributions like Fedora and Tumbleweed use it by default, and some others may do so in the future as X11 is in hard maintenance mode. If you’re using one of those environments it is as easy as selecting the “Wayland” session at the desktop display manager (GDM, SDDM).

    Try not to use desktop environments or window managers that do not have Wayland support such as Cinnamon (default on Linux Mint), Pantheon (default on Elementary OS), MATE, Xfce, and i3.

    Generally good distributions

    Here is a quick non authoritative list of distributions that are generally better than others:

    Fedora Workstation

    Fedora

    Fedora Workstation is a great general purpose Linux distribution, especially for those who are new to Linux. It is a semi-rolling release distribution. While some packages like GNOME are frozen until the next Fedora release, most packages (including the kernel) are updated frequently throughout the lifespan of the release. Each Fedora release is supported for one year, with a new version released every 6 months.

    WIth that, Fedora generally adopts newer technologies before other distributions e.g., Wayland, PipeWire, and soon, FS-Verity. These new technologies often come with improvements in security, privacy, and usability in general.

    While lacking transactional or atomic updates, Fedora’s package manager, dnf, has a great rollback and undo feature that is generally missing from other package managers. You can read more about it on Red Hat’s documentation.

    Fedora Silverblue & Kinoite

    Fedora Silverblue and Fedora Kinoite are immutable variants of Fedora with a strong focus on container workflows. Silverblue comes with the GNOME desktop environment while Kinoite comes with KDE. Silverblue and Kinoite follow the same release schedule as Fedora Workstation, benefiting from the same fast updates and staying very close to upstream.

    You can refer to the video by Adam Šamalík linked above on how these distributions work.

    openSUSE Tumbleweed and MicroOS

    Fedora Workstation and Silverblue’s European counterpart. These are rolling release, fast updating distributions with transactional update using Btrfs and Snapper.

    MicroOS has a much smaller base system than Tumbleweed and mounts the running BTRFS subvomumes as read-only (hence its name and why it is considered an immutable distribution). Currently, it is still in Beta so bugs are to be expected. Nevertheless, it is an awesome project.

    Whonix

    Whonix is a distribution focused on anonimity based on Kicksecure. It is meant to run as two virtual machines: a “Workstation” and a Tor “Gateway.” All communications from the Workstation must go through the Tor gateway. This means that even if the Workstation is compromised by malware of some kind, the true IP address remains hidden.

    Some of its features include Tor Stream Isolation, keystroke anonymization, encrypted swap, and a hardened memory allocator.

    Future versions of Whonix will likely include full system AppArmor policies and a sandbox app launcher to fully confine all processes on the system.

    Although Whonix is best used in conjunction with Qubes, Qubes-Whonix has various disadvantages when compared to other hypervisors.

    Tails

    Tails is a live operating system based on Debian focusing on anonimity and amnesia.

    While it is great for counter forensics as nothing is written to the disk; it is not a hardened distribution like Whonix. It lacks many anonymity and security features that Whonix has and gets updated much less often (only once every six weeks). A Tails system that is compromised by malware may potentially bypass the transparent proxy allowing for the user to be deanonymized.

    \ No newline at end of file diff --git a/public/os/docker-and-oci-hardening/index.html b/public/os/docker-and-oci-hardening/index.html deleted file mode 100644 index 50f0506..0000000 --- a/public/os/docker-and-oci-hardening/index.html +++ /dev/null @@ -1,63 +0,0 @@ -Docker and OCI Hardening | PrivSec.dev -

    Docker and OCI Hardening

    Containers aren’t that new fancy thing anymore, but they were a big deal. And they still are. They are a concrete solution to the following problem:

    - Hey, your software doesn’t work…

    - Sorry, it works on my computer! Can’t help you.

    Whether we like them or not, containers are here to stay. Their expressiveness and semantics allow for an abstraction of the OS dependencies that a software has, the latter being often dynamically linked against certain libraries. The developer can therefore provide a known-good environment where it is expected that their software “just works”. That is particularly useful for development to eliminate environment-related issues, and that is often used in production as well.

    Containers are often perceived as a great tool for isolation, that is, they can provide an isolated workspace that won’t pollute your host OS - all that without the overhead of virtual machines. Security-wise: containers, as we know them on Linux, are glorified namespaces at their core. Containers usually share the same kernel with the host, and namespaces is the kernel feature for separating kernel resources across containers (IDs, networks, filesystems, IPC, etc.). Containers also leverage the features of cgroups to separate system resources (CPU, memory, etc.), and security features such as seccomp to restrict syscalls, or MACs (AppArmor, SELinux).

    At first, it seems that containers may not provide the same isolation boundary as virtual machines. That’s fine, they were not designed to. But they can’t be simplified to a simple chroot either. We’ll see that a “container” can mean a lot of things, and their definition may vary a lot depending on the implementation: as such, containers are mostly defined by their semantics.

    Docker is dead, long live Docker… and OCI!

    When people think of containers, a large group of them may think of Docker. While Docker played a big role in the popularity of containers a few years ago, it didn’t introduce the technology: on Linux, LXC did (Linux Containers). In fact, Docker in its early days was a high-level wrapper for LXC which already combined the power of namespaces and cgroups. Docker then replaced LXC with libcontainer which does more or less the same, plus extra features.

    Then, what happened? Open Container Initiative (OCI). That is the current standard that defines the container ecosystem. That means that whether you’re using Docker, Podman, or Kubernetes, you’re in fact running OCI-compliant tools. That is a good thing, as it saves a lot of interoperability headaches.

    Docker is no longer the monolithic platform it once was. libcontainer was absorbed by runc, the reference OCI runtime. The high-level components of Docker split into different parts related to the upstream Moby project (Docker is the “assembled product” of the “Moby components”). When we refer to Docker, we refer in fact at this powerful high-level API that manages OCI containers. By design, Docker is a daemon that communicates with containerd, a lower-level layer, which in turn communicates with the OCI runtime. That also means that you could very well skip Docker altogether and use containerd or even runc directly.

    Docker client <=> Docker daemon <=> containerd <=> containerd-shim <=> runc
    -

    Podman is an alternative to Docker developed by RedHat, that also intends to be a drop-in replacement for Docker. It doesn’t work with a daemon, and can work rootless by design (Docker has support for rootless too, but that is not without caveats). I would largely recommend Podman over Docker for someone who wants a simple tool to run containers and test code on their machine.

    Kubernetes (also known as K8S) is the container platform made by Google. It is designed with scaling in mind, and is about running containers across a cluster whereas Docker focuses on packaging containers on a single node. Docker Swarm is the direct alternative to that, but it has never really took off due to the popularity of K8S.

    For the rest of this article, we will use Docker as the reference for our examples, along with the Compose specification format. Most of these examples can be adapted to other platforms without issues.

    The nightmare of dependencies

    Containers are made from images, and images are typically built from a Dockerfile. Images can be built and distributed through OCI registries: Docker Hub, Google Container Registry, GitHub Container Registry, and so on. You can also set up your own private registry as well, but the reality is that people often pull images from these public registries.

    Images, immutability and versioning

    Images are what make containers, well, containers. Containers made from the same image should behave similary on different machines. Images can have tags, which are useful for software versioning. The usage of generic tags such as latest is often discouraged because it defeats the purpose of the expected behavior of the container. Tags are not necessarily immutable by design, and they shouldn’t be (more on that below). Digest, however, is the attribute of an immutable image, and is often generated with the SHA-256 algorithm.

    docker.io/library/golang:1.17.1@sha256:232a180dbcbcfa7250917507f3827d88a9ae89bb1cdd8fe3ac4db7b764ebb25
    -         ^          ^       ^                                   ^ 
    -         |          |       |                                   |
    -     Registry     Image    Tag                          Digest (immutable)
    -

    Now onto why tags shouldn’t be immutable: as written above, containers bring us an abstraction over the OS dependencies that are used by the packaged software. That is nice indeed, but this shouldn’t lure us into believing that we can forget security updates. The fact is, there is still a whole OS to care about, and we can’t just think of the container as a simple package tool for software.

    For these reasons, good practices were established:

    • An image should be as minimal as possible (Alpine Linux, or scratch/distroless).
    • An image, with a given tag, should be regularly built, without cache to ensure all layers are freshly built.
    • An image should be rebuilt when the images it’s based on are updated.

    A minimal base system

    Alpine Linux is often the choice for official images for the first reason. This is not a typical Linux distribution as it uses musl as its C library, but it works quite well. Actually, I’m quite fond of Alpine Linux and apk (its package manager). If a supervision suite is needed, I’d look into s6. If you need a glibc distribution, Debian provides slim variants for lightweight base images. We can do even better than using Alpine by using distroless images, allowing us to have state-of-the-art application containers.

    “Distroless” is a fancy name referring to an image with a minimal set of dependencies, from none (for fully static binaries) to some common libraries (typically the C library). Google maintains distroless images you can use as a base for your own images. If you were wondering, the difference with scratch (empty starting point) is that distroless images contain common dependencies that “almost-statically compiled” binaries may need, such as ca-certificates.

    However, distroless images are not suited for every application. In my experience though, distroless is an excellent option with pure Go binaries. Going with minimal images drastically reduces the available attack surface in the container. For example, here’s a multi-stage Dockerfile resulting in a minimal non-root image for a simple Go project:

    FROM golang:alpine as build
    -WORKDIR /app
    -COPY . .
    -RUN CGO_ENABLED=0 go mod -o /my_app cmd/my_app
    -
    -FROM gcr.io/distroless/static
    -COPY --from=build /my_app /
    -USER nobody
    -ENTRYPOINT ["/my_app"]
    -

    The main drawback of using minimal images is the lack of tools that help with debugging, which also constitute the very attack surface we’re trying to get rid of. The trade-off is probably not worth the hassle for development-focused containers, and if you’re running such images in production, you have to be confident enough to operate with them. Note that the gcr.io/distroless images have a :debug tag to help in that regard.

    Keeping images up-to-date

    The two other points are highly problematic, because most software vendors just publish an image on release, and forget about it. You should take it up to them if you’re running images that are versioned but not regularly updated. I’d say running scheduled builds once a week is the bare minimum to make sure dependencies stay up-to-date. Alpine Linux is a better choice than most other “stable” distributions because it usually has more recent packages.

    Stable distributions often rely on backporting security fixes from CVEs, which is known to be a flawed approach to security since CVEs aren’t always assigned or even taken care of. Alpine has more recent packages, and it has versioning, so it’s once again a particulary good choice as long as musl doesn’t cause issues.

    Is it really a security nightmare?

    When people say Docker is a security nightmare because of that, that’s a fair point. On a traditional system, you could upgrade your whole system with a single command or two. With Docker, you’ll have to recreate several containers… if the images were kept up-to-date in the first place. Recreating itself is not a big deal actually: hot upgrades of binaries and libraries often require the services that use them to restart, otherwise they could still use an old (and vulnerable) version of them in memory. But yeah, the fact is most people are running outdated containers, and more often than not, they don’t have the choice if they rely on third-party images.

    Trivy is an excellent tool to scan images for a subset of known vulnerabilities an image might have. You should play with it and see for yourself how outdated many publicly available images are.

    Supply-chain attacks

    As with any code downloaded from a software vendor, OCI images are not exempt from supply-chain attacks. The good practice is quite simple: rely on official images, and ideally build and maintain your own images. One should definitely not automatically trust random third-party images they can find on Docker Hub. Half of these images, if not more, contain vulnerabilities, and I bet a good portion of them contains malwares such as miners or worse.

    As an image maintainer, you can sign your images to improve the authenticity assurance. Most official images make use of Docker Content Trust, which works with a OCI registry attached to a Notary server. With the Docker toolset, setting the environment variable DOCKER_CONTENT_TRUST=1 enforces signature verification (a signature is only good if it’s checked in the first place). The SigStore initiative is developing cosign, an alternative that doesn’t require a Notary server because it works with features already provided by the registry such as tags. Kubernetes users may be interested in Connaisseur to ensure all signatures have been validated.

    Leave my root alone!

    Attack surface

    Traditionally, Docker runs as a daemon owned by root. That also means that root in the container is actually the root on the host and may be a few commands away from compromising the host. More generally, the attacker has to exploit the available attack surface to escape the container. There is a huge attack surface, actually: the Linux kernel. Someone wise once said:

    The kernel can effectively be thought of as the largest, most vulnerable setuid root binary on the system.

    That applies particulary to traditional containers which weren’t designed to provide a robust level of isolation. A recent example was CVE-2022-0492: the attacker could abuse root in the container to exploit cgroups v1, and compromise the host. Of course defense-in-depth measures would have prevented that, and we’ll mention them. But fundamentally, container escapes are possible by design.

    Breaking out via the OCI runtime runc is also possible, although CVE-2019-5736 was a particularly nasty bug. The attacker had to gain access to root in the container first in order to access /proc/[runc-pid]/exe, which indicates them where to overwrite the runc binary.

    Good practices have been therefore established:

    • Avoid using root in the container, plain and simple.
    • Keep the host kernel, Docker and the OCI runtime updated.
    • Consider the usage of user namespaces.

    By the way, it goes without saying that any user who has access to the Docker daemon should be considered as privileged as root. Mounting the Docker socket (/var/run/docker.sock) in a container makes it highly privileged, and so it should be avoided. The socket should only be owned by root, and if that doesn’t work with your environment, use Docker rootless or Podman.

    Avoiding root

    root can be avoided in different ways in the final container:

    • Image creation time: setting the USER instruction in the Dockerfile.
    • Container creation time: via the tools available (user: in the Compose file).
    • Container runtime: degrading privileges with entrypoints scripts (gosu UID:GID).

    Well-made images with security in mind will have a USER instruction. In my experience, most people will run images blindly, so it’s good harm reduction. Setting the user manually works in some images that aren’t designed without root in mind, and it’s also great to mitigate some scenarii where the image is controlled by an attacker. You also won’t have surprises when mounting volumes, so I highly recommend setting the user explicitly and make sure volume permissions are correct once.

    Some images allow users to define their own user with UID/GID environment variables, with an entrypoint script that runs as root and takes care of the volume permissions before dropping privileges. While technically fine, it is still attack surface, and it requires the SETUID/SETGID capabilities to be available in the container.

    User namespaces: sandbox or paradox?

    As mentioned just above, user namespaces are a solution to ensure root in the container is not root on the host. Docker supports user namespaces, for instance you could set the default mapping in /etc/docker/daemon.json:

        "userns-remap": "default"
    -

    whoami && sleep 60 in the container will return root, but ps -fC sleep on the host will show us the PID of another user. That is nice, but it has limitations and therefore shouldn’t be considered as a real sandbox. In fact, the paradox is that user namespaces are attack surface (and vulnerabilities are still being found years later), and it’s common wisdom to restrict them to privileged users (kernel.unprivileged_userns_clone=0). That is fine for Docker with its traditional root daemon, but Podman expects you to let unprivileged users interact with user namespaces (so essentially privileged code).

    Enabling userns-remap in Docker shouldn’t be a substitute for running unprivileged application containers (where applicable). User namespaces are mostly useful if you intend to run full-fledged OS containers which need root in order to function, but that is out of the scope of the container technologies mentioned in this article; for them, I’d argue exposing such a vulnerable attack surface from the host kernel for dubious sandboxing benefits isn’t an interesting trade-off to make.

    The no_new_privs bit

    After ensuring root isn’t used in your containers, you should look into setting the no_new_privs bit. This Linux feature restricts syscalls such as execve() from granting privileges, which is what you want to restrict in-container privilege escalation. This flag can be set for a given container in a Compose file:

        security_opt:
    -        - no-new-privileges: true
    -

    Gaining privileges in the container will be much harder that way.

    Capabilities

    Furthermore, we should mention capabilities: root powers are divided into distinct units by the Linux kernel, called capabilities. Each granted capability also grants privilege and therefore access to a significant amount of attack surface. Security researcher Brad Spengler enumerates 19 important capabilities. Docker restricts certain capabilities by default, but some of the most important ones are still available to a container by default.

    You should consider the following rule of thumb:

    • Drop all capabilities by default.
    • Allow only the ones you really need to.

    If you already run your containers unprivileged without root, your container will very likely work fine with all capabilities dropped. That can be done in a Compose file:

        cap_drop:
    -        - ALL
    -    #cap_add:
    -    #  - CHOWN
    -    #  - DAC_READ_SEARCH
    -    #  - SETUID
    -    #  - SETGID
    -

    Never use the --privileged option unless you really need to: a privileged container is given access to almost all capabilities, kernel features and devices.

    Other security features

    MACs and seccomp are robust tools that may vastly improve container security.

    Mandatory Access Control

    MAC stand for Mandatory Access Control: traditionnally a Linux Security Module that will enforce a policy to restrict the userspace. Examples are AppArmor and SELinux: the former being more easy-to-use, the later being more fine-grained. Both are strong tools that can help… Yet, their sole presence does not mean they’re really effective. A robust policy starts from a deny all policy, and only allows the necessary resources to be accessed.

    seccomp

    seccomp (short for secure computing mode) on the other hand is a much simpler and complementary tool, and there is no reason not to use it. What it does is restricting a process to a set of system calls, thus drastically reducing the attack surface available.

    Docker provides default profiles for AppArmor and seccomp, and they’re enabled by default for newly created containers unless the unconfined option is explicitly passed. Note: Kubernetes doesn’t enable the default seccomp profile by default, so you should probably try it.

    These profiles are a great start, but you should do much more if you take security seriously, because they were made to not break compatibility with a large range of images. The default seccomp profile only disables around 44 syscalls, which are mostly not very common and/or obsoleted. Of course, the best profile you can get is supposed to be written for a given program. It also doesn’t make sense to insist on the permissiveness of the default profiles, and a lof of work has gone into hardening containers.

    cgroups

    Use cgroups to restrict access to hardware and system resources. You likely don’t want a guest container to monopolize the host resources. You also don’t want to be vulnerable to stupid fork bomb attacks. In a Compose file, consider setting these limits:

        mem_limit: 4g
    -    cpus: 4
    -    pids_limit: 256
    -

    More runtime options can be found in the official documentation. All of them should have a Compose spec equivalent.

    The --cgroup-parent option should be avoided as it uses the host cgroup and not the one configured from Docker (or else), which is the default.

    Read-only filesystem

    It is good practice to treat the image as some refer to as the “golden image”.

    In other words, you’ll run containers in read-only mode, with an immutable filesystem inherited from the image. Only the mounted volumes will be read/write accessible, and those should ideally be mounted with the noexec, nosuid and nodev options for extra security. If read/write access isn’t needed, mount these volumes as read-only too.

    However, the image may not be perfect and still require read/write access to some parts of the filesystem, likely directories such as /tmp, /run or /var. You can make a tmpfs for those (a temporary filesystem in the container attributed memory), because they’re not persistent data anyway.

    In a Compose file, that would look like the following settings:

        read_only: true
    -    tmpfs:
    -        - /tmp:size=10M,mode=0770,uid=1000,gid=1000,noexec,nosuid,nodev
    -

    That is quite verbose indeed, but that’s to show you the different options for a tmpfs mount. You want to restrict them in size and permissions ideally.

    Network isolation

    By default, all Docker containers will use the default network bridge. They will see and be able to communicate with each other. Each container should have its own user-defined bridge network, and each connection between containers should have an internal network. If you intend to run a reverse proxy in front of several containers, you should make a dedicated network for each container you want to expose to the reverse proxy.

    The --network host option also shouldn’t be used for obvious reasons since the container would share the same network as the host, providing no isolation at all.

    Alternative runtimes (gVisor)

    runc is the reference OCI runtime, but that means other runtimes can exist as well as long as they’re compliant with the OCI standard. These runtimes can be interchanged quite seamlessly. There’s a few alternatives, such as crun or youki, respectively implemented in C and Rust (runc is a Go implementation). However, there is one particular runtime that does a lot more for security: runsc, provided by the gVisor project by the folks at Google.

    Containers are not a sandbox, and while we can improve their security, they will fundamentally share a common attack surface with the host. Virtual machines are a solution to that problem, but you might prefer container semantics and ecosystem. gVisor can be perceived as an attempt to get the “best of both worlds”: containers that are easy to manage while providing a native isolation boundary. gVisor did just that by implementing two things:

    • Sentry: an application kernel in Go, a language known to be memory-safe. It implements the Linux logic in userspace such as various system calls.
    • Gofer: a host process which communicates with Sentry and the host filesystem, since Sentry is restricted in that aspect.

    A platform like ptrace or KVM is used to intercept system calls and redirect them from the application to Sentry, which is running in the userspace. This has some costs: there is a higher per-syscall overhead, and compatibility is reduced since not all syscalls are implemented. On top of that, gVisor employs security mechanisms we’ve glanced over above, such as a very restrictive seccomp profile between Sentry and the host kernel, the no_new_privs bit, and isolated namespaces from the host.

    The security model of gVisor is comparable to what you would expect from a virtual machine. It is also very easy to install and use. The path to runsc along with its different configuration flags (runsc flags) should be added to /etc/docker/daemon.json:

        "runtimes": {
    -        "runsc-ptrace": {
    -            "path": "/usr/local/bin/runsc",
    -            "runtimeArgs": [
    -                "--platform=ptrace"
    -            ]
    -        },
    -        "runsc-kvm": {
    -            "path": "/usr/local/bin/runsc",
    -            "runtimeArgs": [
    -                "--platform=kvm"
    -            ]
    -        }
    -    }
    -

    runsc needs to start with root to set up some mitigations, including the use of its own network stack separated from the host. The sandbox itself drops privileges to nobody as soon as possible. You can still use runsc rootless if you want (which should be needed for Podman):

    ./runsc --rootless do uname -a
    -*** Warning: sandbox network isn't supported with --rootless, switching to host ***
    -Linux 4.4.0 #1 SMP Sun Jan 10 15:06:54 PST 2016 x86_64 GNU/Linux
    -

    Linux 4.4.0 is shown because that is the version of the Linux API that Sentry tries to mimic. As you’ve probably guessed, you’re not really using Linux 4.4.0, but the application kernel that behaves like it. By the way, gVisor is of course compatible with cgroups.

    Conclusion: what’s a container after all?

    Like I wrote above, a container is mostly defined by its semantics and ecosystem. Containers shouldn’t be solely defined by the OCI reference runtime implementation, as we’ve seen with gVisor that provides an entirely different security model.

    Still not convinced? What if I told you a container can leverage the same technologies as a virtual machine? That is exactly what Kata Containers does by using a VMM like QEMU-lite to provide containers that are in fact lightweight virtual machines, with their traditional resources and security model, compatibility with container semantics and toolset, and an optimized overhead. While not in the OCI ecosystem, Amazon achieves quite the same with Firecracker.

    If you’re running untrusted workloads, I highly suggest you consider gVisor instead of a traditional container runtime. Your definition of “untrusted” may vary: for me, almost everything should be considered untrusted. That is how modern security works, and how mobile operating systems work. It’s quite simple, security should be simple, and gVisor simply offers native security.

    Containers are a popular, yet strange world. They revolutionized the way we make and deploy software, but one should not loose the sight of what they really are and aren’t. This hardening guide is non-exhaustive, but I hope it can make you aware of some aspects you’ve never thought of.

    \ No newline at end of file diff --git a/public/os/index.html b/public/os/index.html deleted file mode 100644 index f38be86..0000000 --- a/public/os/index.html +++ /dev/null @@ -1,12 +0,0 @@ -Operating Systems | PrivSec.dev

    Choosing Your Desktop Linux Distribution

    Not all Linux distributions are created equal. When choosing a Linux distribution, there are several things you need to keep in mind. -Release cycle You should choose a distribution which stays close to the stable upstream software releases, typically rolling release distributions. This is because frozen release cycle distributions often don’t update package versions and fall behind on security updates. -For frozen distributions, package maintainers are expected to backport patches to fix vulnerabilities (Debian is one such example) rather than bump the software to the “next version” released by the upstream developer....

    7 min · 1431 words · Tommy

    Docker and OCI Hardening

    Containers aren’t that new fancy thing anymore, but they were a big deal. And they still are. They are a concrete solution to the following problem: -- Hey, your software doesn’t work… -- Sorry, it works on my computer! Can’t help you. -Whether we like them or not, containers are here to stay. Their expressiveness and semantics allow for an abstraction of the OS dependencies that a software has, the latter being often dynamically linked against certain libraries....

    19 min · 3925 words · Wonderfall

    Linux Insecurities

    There is a common misconception among privacy communities that Linux is one of the more secure operating systems, either because it is open source or because it is widely used in the cloud. This is however, a far cry from reality. -There is already a very indepth technical blog explaning the various security weaknesses of Linux by Madaidan, Whonix’s Security Researcher. This page will attempt to address some of the questions commonly raised in reaction to his blog post....

    3 min · 589 words · Tommy

    Securing OpenSSH with FIDO2

    Passwordless authentication with OpenSSH keys has been the de facto security standard for years. SSH keys are more robust since they’re cryptographically sane by default, and are therefore resilient to most bruteforce atacks. They’re also easier to manage while enabling a form of decentralized authentication (it’s easy and painless to revoke them). So, what’s the next step? And more exactly, why would one need something even better? -Why? The main problem with SSH keys is that they’re not magic: they consist of a key pair, of which the private key is stored on your disk....

    5 min · 863 words · Wonderfall
    \ No newline at end of file diff --git a/public/os/index.xml b/public/os/index.xml deleted file mode 100644 index 60a34f9..0000000 --- a/public/os/index.xml +++ /dev/null @@ -1,52 +0,0 @@ - - - - Operating Systems on PrivSec.dev - https://privsec.dev/os/ - Recent content in Operating Systems on PrivSec.dev - Hugo -- gohugo.io - - Choosing Your Desktop Linux Distribution - https://privsec.dev/os/choosing-your-desktop-linux-distribution/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://privsec.dev/os/choosing-your-desktop-linux-distribution/ - Not all Linux distributions are created equal. When choosing a Linux distribution, there are several things you need to keep in mind. -Release cycle You should choose a distribution which stays close to the stable upstream software releases, typically rolling release distributions. This is because frozen release cycle distributions often don’t update package versions and fall behind on security updates. -For frozen distributions, package maintainers are expected to backport patches to fix vulnerabilities (Debian is one such example) rather than bump the software to the “next version” released by the upstream developer. - - - - Docker and OCI Hardening - https://privsec.dev/os/docker-and-oci-hardening/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://privsec.dev/os/docker-and-oci-hardening/ - Containers aren&rsquo;t that new fancy thing anymore, but they were a big deal. And they still are. They are a concrete solution to the following problem: -- Hey, your software doesn&rsquo;t work&hellip; -- Sorry, it works on my computer! Can&rsquo;t help you. -Whether we like them or not, containers are here to stay. Their expressiveness and semantics allow for an abstraction of the OS dependencies that a software has, the latter being often dynamically linked against certain libraries. - - - - Linux Insecurities - https://privsec.dev/os/linux-insecurities/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://privsec.dev/os/linux-insecurities/ - There is a common misconception among privacy communities that Linux is one of the more secure operating systems, either because it is open source or because it is widely used in the cloud. This is however, a far cry from reality. -There is already a very indepth technical blog explaning the various security weaknesses of Linux by Madaidan, Whonix&rsquo;s Security Researcher. This page will attempt to address some of the questions commonly raised in reaction to his blog post. - - - - Securing OpenSSH with FIDO2 - https://privsec.dev/os/securing-openssh-with-fido2/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://privsec.dev/os/securing-openssh-with-fido2/ - Passwordless authentication with OpenSSH keys has been the de facto security standard for years. SSH keys are more robust since they&rsquo;re cryptographically sane by default, and are therefore resilient to most bruteforce atacks. They&rsquo;re also easier to manage while enabling a form of decentralized authentication (it&rsquo;s easy and painless to revoke them). So, what&rsquo;s the next step? And more exactly, why would one need something even better? -Why? The main problem with SSH keys is that they&rsquo;re not magic: they consist of a key pair, of which the private key is stored on your disk. - - - - diff --git a/public/os/linux-insecurities/index.html b/public/os/linux-insecurities/index.html deleted file mode 100644 index 02ad572..0000000 --- a/public/os/linux-insecurities/index.html +++ /dev/null @@ -1,9 +0,0 @@ -Linux Insecurities | PrivSec.dev -

    Linux Insecurities

    There is a common misconception among privacy communities that Linux is one of the more secure operating systems, either because it is open source or because it is widely used in the cloud. This is however, a far cry from reality.

    There is already a very indepth technical blog explaning the various security weaknesses of Linux by Madaidan, Whonix’s Security Researcher. This page will attempt to address some of the questions commonly raised in reaction to his blog post. You can find the original article here.

    Why is Linux used on servers if it is so insecure?

    On servers, while most of the problems referenced in the article still exists, they are somewhat less problematic.

    On Desktop Linux, GUI applications run under your user, and thus have access to all of your files in /home. This is in contrast to how system daemons typically run on servers, where they have their own group and user. For example, NGINX will run under nginx:nginx on Red Hat distributions, or www-data:www-data on Debian based ones. Discreationary Access Control does help with filesystem access control for server processes, but is useless for desktop applications.

    Another thing to keep in mind is that Mandatory Access Control is also somewhat effective on servers, as commonly run system daemons are confined. In contrast, on desktop, there is virtually no AppArmor profile to confine even regularly used apps like Chrome or Firefox, let alone less common ones. On SELinux systems, these apps run in the UNCONFINED SELinux domain.

    Linux servers are lighter than Desktop Linux systems by order of magnitude, without hundreds of packages and dozens of system daemons running like X11, audio servers, printing stack, and so on. Thus, the attack surface is much smaller.

    Linux Hardening Myths

    There is a common claim in response to Madaidan that Linux is only insecure by default, and that an experience user can make it the most secure operating system out there, surpassing the likes of macOS or ChromeOS. Unfortunately, this is wishful thinking. There is no amount of hardening that one can reasonably apply as a user to fix up the inherent issues with Linux.

    Lack of verified boot

    macOS, ChromeOS, and Android have a clear distinction between the system and user installed application. In over simplified terms, the system volume is signed by the OS vendor, and the firmware and boot loader works to make sure that said volume has the authorized signature. The operating system itself is immutable, and nothing the user does will need or be allowed to tamper with the system volume.

    On Linux, there is no such clear distinction between the system and user installed applications. Linux distributions are a bunch of packages put together to make a system that works, and thus every package is treated as part of said system. The end result is that binaries, regardless of whether they are vital for the system to function or just an extra application, are thrown into the same directories as each other (namely /usr/bin and /usr/local/bin). This makes it impossible for an end user to setup a verification mechanism to verify the integrity of “the system”, as said “system” is not clearly defined in the first place.

    Lack of application sandboxing

    Operating systems like Android and ChromeOS have full system mandatory access control, every process from the init process is strictly confined. Regardless of which application you install or how you install them, they have to play by the rules of an untrusted SELinux domain.

    On Linux, it is quite the opposite.

    \ No newline at end of file diff --git a/public/os/page/1/index.html b/public/os/page/1/index.html deleted file mode 100644 index eb17523..0000000 --- a/public/os/page/1/index.html +++ /dev/null @@ -1 +0,0 @@ -https://privsec.dev/os/ \ No newline at end of file diff --git a/public/os/securing-openssh-with-fido2/index.html b/public/os/securing-openssh-with-fido2/index.html deleted file mode 100644 index 8e96d9b..0000000 --- a/public/os/securing-openssh-with-fido2/index.html +++ /dev/null @@ -1,13 +0,0 @@ -Securing OpenSSH with FIDO2 | PrivSec.dev -

    Securing OpenSSH with FIDO2

    Passwordless authentication with OpenSSH keys has been the de facto security standard for years. SSH keys are more robust since they’re cryptographically sane by default, and are therefore resilient to most bruteforce atacks. They’re also easier to manage while enabling a form of decentralized authentication (it’s easy and painless to revoke them). So, what’s the next step? And more exactly, why would one need something even better?

    Why?

    The main problem with SSH keys is that they’re not magic: they consist of a key pair, of which the private key is stored on your disk. You should be wary of various exfiltration attempts, depending on your theat model:

    • If your disk is not encrypted, any physical access could compromise your keys.
    • If your private key isn’t encrypted, malicious applications could compromise it.
    • Even with both encrypted, malicious applications could register your keystrokes.

    All these attempts are particularly a thing on desktop platforms, because they don’t have a proper sandboxing model. On Windows, non-UWP apps could likely have full access to your .ssh directory. On desktop Linux distributions, sandboxing is also lacking, and the situation is even worse if you’re using X.org since it allows apps to spy on each other (and on your keyboard) by design. A first good step would be to only use SSH from a trusted & decently secure system.

    Another layer of defense would obviously be multi-factor authentification, or the fact that you’re relying on a shared secret instead. We can use FIDO2 security keys for that. That way, even if your private key is compromised, the attacker needs physical access to your security key. TOTP is another common 2FA technique, but it’s vulnerable to various attacks, and relies on the quality of the implementation on the server.

    How?

    Fortunately for us, OpenSSH 8.2 (released in February 2020) introduced native support for FIDO2/U2F. Most OpenSSH distributions should have the middleware set to use the libfido2 library, including portable versions such as the one for Win32.

    Basically, ssh-keygen -t ${key_type}-sk will generate for us a token-backed key pair. The key types that are supported depend on your security key. Newer models should support both ECDSA-P256 (ecdsa-sk) and Ed25519 (ed25519-sk). If the latter is available, you should prefer it.

    Client configuration

    To get started:

    ssh-keygen -t ed25519-sk
    -

    This will generate a id_ed25519_sk private key and a id_ed25519_sk.pub public key in .ssh. These are defaults, but you can change them if you want. We will call this key pair a “handle”, because they’re not sufficient by themselves to derive the real secret (as you guessed it, the FIDO2 token is needed). ssh-keygen should ask you to touch the key, and enter the PIN prior to that if you did set one (you probably should).

    You can also generate a resident key (referred to as discoverable credential in the WebAuthn specification):

    ssh-keygen -t ed25519-sk -O resident -O application=ssh:user1
    -

    As you can see, a few options must be specified:

    • -O resident will tell ssh-keygen to generate a resident key, meaning that the private “handle” key will also be stored on the security key itself. This has security implications, but you may want that to move seamlessly between different computers. In that case, you should absolutely protect your key with a PIN beforehand.
    • -O application=ssh: is necessary to instruct that the resident key will use a particular slot, because the security key will have to index the resident keys (by default, they use ssh: with an empty user ID). If this is not specificed, the next key generation might overwrite the previous one.
    • -O verify-required is optional but instructs that a PIN is required to generate/access the key.

    Resident keys can be retrieved using ssh-keygen -K or ssh-add -K if you don’t want to write them to the disk.

    Server configuration

    Next, transfer your public key over to the server (granted you have already access to it with a regular key pair):

    ssh-copy-id -i ~/.ssh/id_ed25519_sk.pub user@server.domain.tld
    -

    Ta-da! But one last thing: we need to make sure the server supports this public key format in sshd_config:

    PubkeyAcceptedKeyTypes ssh-ed25519,sk-ssh-ed25519@openssh.com
    -

    Adding sk-ssh-ed25519@openssh.com to PubkeyAcceptedKeyTypes should suffice. It’s best practice to only use the cryptographic primitives that you need, and hopefully ones that are also modern. This isn’t a full-on SSH hardening guide, but you should take a look at the configuration file GrapheneOS uses for their servers to give you an idea on a few good practices.

    Restart the sshd service and try to connect to your server using your key handle (by passing -i ~/.ssh/id_ed25519_sk to ssh for instance). If that works for you (your FIDO2 security key should be needed to derive the real secret), feel free to remove your previous keys from .ssh/authorized_keys on your server.

    That’s cool, right?

    If you don’t have a security key, you can buy one from YubiKey (I’m very happy with my 5C NFC by the way), Nitrokey, SoloKeys or OnlyKey (to name a few). If you have an Android device with a hardware security module (HSM), such as the Google Pixels equipped with Titan M (Pixel 3+), you could even use them as bluetooth security keys.

    No reason to miss out on the party if you can afford it!

    \ No newline at end of file diff --git a/public/providers/index.html b/public/providers/index.html deleted file mode 100644 index 67bfd0d..0000000 --- a/public/providers/index.html +++ /dev/null @@ -1,5 +0,0 @@ -Providers | PrivSec.dev
    \ No newline at end of file diff --git a/public/providers/index.xml b/public/providers/index.xml deleted file mode 100644 index 2be4f01..0000000 --- a/public/providers/index.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - Providers on PrivSec.dev - https://privsec.dev/providers/ - Recent content in Providers on PrivSec.dev - Hugo -- gohugo.io - - diff --git a/public/providers/page/1/index.html b/public/providers/page/1/index.html deleted file mode 100644 index 827350a..0000000 --- a/public/providers/page/1/index.html +++ /dev/null @@ -1 +0,0 @@ -https://privsec.dev/providers/ \ No newline at end of file diff --git a/public/robots.txt b/public/robots.txt deleted file mode 100644 index ef476ff..0000000 --- a/public/robots.txt +++ /dev/null @@ -1,3 +0,0 @@ -User-agent: * -Disallow: -Sitemap: https://privsec.dev/sitemap.xml diff --git a/public/sitemap.xml b/public/sitemap.xml deleted file mode 100644 index c8896e6..0000000 --- a/public/sitemap.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - - https://privsec.dev/about/ - - https://privsec.dev/tags/android/ - - https://privsec.dev/apps/ - - https://privsec.dev/categories/ - - https://privsec.dev/os/choosing-your-desktop-linux-distribution/ - - https://privsec.dev/tags/container/ - - https://privsec.dev/os/docker-and-oci-hardening/ - - https://privsec.dev/donate/ - - https://privsec.dev/apps/f-droid-security-analysis/ - - https://privsec.dev/tags/knowledge-base/ - - https://privsec.dev/knowledge/ - - https://privsec.dev/tags/linux/ - - https://privsec.dev/os/linux-insecurities/ - - https://privsec.dev/knowledge/multi-factor-authentication/ - - https://privsec.dev/tags/operating-system/ - - https://privsec.dev/tags/operating-systems/ - - https://privsec.dev/os/ - - https://privsec.dev/ - - https://privsec.dev/providers/ - - https://privsec.dev/os/securing-openssh-with-fido2/ - - https://privsec.dev/tags/security/ - - https://privsec.dev/tags/software/ - - https://privsec.dev/tags/ - - diff --git a/public/software/f-droid-security-analysis/index.html b/public/software/f-droid-security-analysis/index.html deleted file mode 100644 index 11e66dd..0000000 --- a/public/software/f-droid-security-analysis/index.html +++ /dev/null @@ -1,41 +0,0 @@ -F-Droid Security Analysis | PrivSec.dev -

    F-Droid Security Analysis

    F-Droid is a popular alternative app repository for Android, especially known for its main repository dedicated to free and open-source software. F-Droid is often recommended among security and privacy enthusiasts, but how does it stack up against Play Store in practice? This write-up will attempt to emphasize major security issues with F-Droid that you should consider.

    Before we start, a few things to keep in mind:

    • The main goal of this write-up was to inform users so they can make responsible choices, not to trash someone else’s work. I have respect for any work done in the name of good intentions. Likewise, please don’t misinterpret the intentions of this article.
    • You have your own reasons for using open-source or free/libre/whatever software which won’t be discussed here. A development model shouldn’t be an excuse for bad practices and shouldn’t lure you into believing that it can provide strong guarantees it cannot.
    • A lot of information in this article is sourced from official and trusted sources, but you’re welcome to do your own research.
    • These analyses do not account for threat models and personal preferences. As the author of this article, I’m only interested in facts and not ideologies.

    This is not an in-depth security review, nor is it exhaustive.

    1. The trusted party problem

    To understand why this is a problem, you’ll have to understand a bit about F-Droid’s architecture, the things it does very differently from other app repositories, and the Android platform security model (some of the issues listed in this article are somewhat out of the scope of the OS security model, but the majority is).

    Unlike other repositories, F-Droid signs all the apps in the main repository with its own signing keys (unique per app) at the exception of the very few reproducible builds. A signature is a mathematical scheme that guarantees the authenticity of the applications you download. Upon the installation of an app, Android pins the signature across the entire OS (including user profiles): that’s what we call a trust-on-first-use model since all subsequent updates of the app must have the corresponding signature to be installed.

    Normally, the developer is supposed to sign their own app prior to its upload on a distribution channel, whether that is a website or a traditional repository (or both). You don’t have to trust the source (usually recommended by the developer) except for the first installation: future updates will have their authenticity cryptographically guaranteed. The issue with F-Droid is that all apps are signed by the same party (F-Droid) which is also not the developer. You’re now adding another party you’ll have to trust since you still have to trust the developer anyway, which isn’t ideal: the fewer parties, the better.

    On the other hand, Play Store now manages the app signing keys too, as Play App Signing is required for app bundles which are required for new apps since August 2021. These signing keys can be uploaded or automatically generated, and are securely stored by Google Cloud Key Management Service. It should be noted that the developer still has to sign the app with an upload key so that Google can verify its authenticity before signing it with the app signing key. For apps created before August 2021 that may have not opted in Play App Signing yet, the developer still manages the private key and is responsible for its security, as a compromised private key can allow a third party to sign and distribute malicious code.

    F-Droid requires that the source code of the app is exempt from any proprietary library or ad service, according to their inclusion policy. Usually, that means that some developers will have to maintain a slightly different version of their codebase that should comply with F-Droid’s requirements. Besides, their “quality control” offers close to no guarantees as having access to the source code doesn’t mean it can be easily proofread. Saying Play Store is filled with malicious apps is beyond the point: the false sense of security is a real issue. Users should not think of the F-Droid main repository as free of malicious apps, yet unfortunately many are inclined to believe this.

    But… can’t I just trust F-Droid and be done with it?

    You don’t have to take my word for it: they openly admit themselves it’s a very basic process relying on badness enumeration (this doesn’t work by the way) which consists in a few scripts scanning the code for proprietary blobs and known trackers. You are therefore not exempted from trusting upstream developers and it goes for any repository.

    A tempting idea would be to compare F-Droid to the desktop Linux model where users trust their distribution maintainers out-of-the-box (this can be sane if you’re already trusting the OS anyway), but the desktop platform is intrinsically chaotic and heterogeneous for better and for worse. It really shouldn’t be compared to the Android platform in any way.

    While we’ve seen that F-Droid controls the signing servers (much like Play App Signing), F-Droid also fully controls the build servers that run the disposable VMs used for building apps. And as of July 2022, their guest VM image officially runs a version of Debian which reached EOL. Undoubtedly, this raises questions about their whole infrastructure security.

    How can you be sure that the app repository can be held to account for the code it delivers?

    F-Droid’s answer, interesting yet largely unused, is build reproducibility. While deterministic builds are a neat idea in theory, it requires the developer to make their toolchain match with what F-Droid provides. It’s additional work on both ends sometimes resulting in apps severely lagging behind in updates, so reproducible builds are not as common as we would have wanted. It should be noted that reproducible builds in the main repository can be exclusively developer-signed.

    Google’s approach is code transparency for app bundles, which is a simple idea addressing some of the concerns with Play App Signing. A JSON Web Token (JWT) signed by a key private to the developer is included in the app bundle before its upload to Play Store. This token contains a list of DEX files and native .so libraries and their hashes, allowing end-users to verify that the running code was built and signed by the app developer. Code transparency has known limitations, however: not all resources can be verified, and this verification can only be done manually since it’s not part of the Android platform itself (so requiring a code transparency file cannot be enforced by the OS right now). Despite its incompleteness, code transparency is still helpful, easy to implement, and thus something we should see more often as time goes by.

    What about other app repositories such as Amazon?

    To my current knowledge, the Amazon Appstore has always been wrapping APKs with their own code (including their own trackers), and this means they were effectively resigning submitted APKs.

    If you understood correctly the information above, Google can’t do this for apps that haven’t opted in Play App Signing. As for apps concerned by Play App Signing, while Google could technically introduce their own code like Amazon, they wouldn’t do that without telling about it since this will be easily noticeable by the developer and more globally researchers. They have other means on the Android app development platform to do so. Believing they won’t do that based on this principle is not a strong guarantee, however: hence the above paragraph about code transparency for app bundles.

    Huawei AppGallery seems to have a similar approach to Google, where submitted apps could be developer-signed, but newer apps will be resigned by Huawei.

    2. Slow and irregular updates

    Since you’re adding one more party to the mix, that party is now responsible for delivering proper builds of the app: it’s a common thing among traditional Linux distributions and their packaging system. They have to catch up with upstream on a regular basis, but very few do it well (Arch Linux comes to my mind). Others, like Debian, prefer making extensive downstream changes and delivering security fixes for a subset of vulnerabilities assigned to a CVE (yeah, it’s as bad as it sounds, but that’s another topic).

    Not only does F-Droid require specific changes for the app to comply with its inclusion policy, which often leads to more maintenance work, it also has a rather strange way of triggering new builds. Part of its build process seems to be automated, which is the least you could expect. Now here’s the thing: app signing keys are on an air-gapped server (meaning it’s disconnected from any network, at least that’s what they claim: see their recommendations for reference), which forces an irregular update cycle where a human has to manually trigger the signing process. It is far from an ideal situation, and you may argue it’s the least to be expected since by entrusting all the signing keys to one party, you could also introduce a single point of failure. Should their system be compromised (whether from the inside or the outside), this could lead to serious security issues affecting plenty of users.

    This is one of the main reasons why Signal refused to support the inclusion of a third-party build in the F-Droid official repository. While this GitHub issue is quite old, many points still hold true today.

    Considering all this, and the fact that their build process is often broken using outdated tools, you have to expect far slower updates compared to a traditional distribution system. Slow updates mean that you will be exposed to security vulnerabilities more often than you should’ve been. It would be unwise to have a full browser updated through the F-Droid official repository, for instance. F-Droid third-party repositories somewhat mitigate the issue of slow updates since they can be managed directly by the developer. It isn’t ideal either as you will see below.

    3. Low target API level (SDK) for client & apps

    SDK stands for Software Development Kit and is the collection of software to build apps for a given platform. On Android, a higher SDK level means you’ll be able to make use of modern API levels of which each iteration brings security and privacy improvements. For instance, API level 31 makes use of all these improvements on Android 12.

    As you may already know, Android has a strong sandboxing model where each application is sandboxed. You could say that an app compiled with the highest API level benefits from all the latest improvements brought to the app sandbox; as opposed to outdated apps compiled with older API levels, which have a weaker sandbox.

    # b/35917228 - /proc/misc access
    -# This will go away in a future Android release
    -allow untrusted_app_25 proc_misc:file r_file_perms;
    -
    -# Access to /proc/tty/drivers, to allow apps to determine if they
    -# are running in an emulated environment.
    -# b/33214085 b/33814662 b/33791054 b/33211769
    -# https://github.com/strazzere/anti-emulator/blob/master/AntiEmulator/src/diff/strazzere/anti/emulator/FindEmulator.java
    -# This will go away in a future Android release
    -allow untrusted_app_25 proc_tty_drivers:file r_file_perms;
    -

    This is a mere sample of the SELinux exceptions that have to be made on older API levels so that you can understand why it matters.

    It turns out the official F-Droid client doesn’t care much about this since it lags behind quite a bit, targeting the API level 25 (Android 7.1) of which some SELinux exceptions were shown above. As a workaround, some users recommended third-party clients such as Foxy Droid or Aurora Droid. While these clients might be technically better, they’re poorly maintained for some, and they also introduce yet another party to the mix. Droid-ify (recently rebreanded to Neo-Store) seems to be a better option than the official client in most aspects.

    Furthermore, F-Droid doesn’t enforce a minimum target SDK for the official repository. Play Store does that quite aggressively for new apps and app updates:

    • Since August 2021, Play Store requires new apps to target at least API level 30.
    • Since November 2021, existing apps must at least target API level 30 for updates to be submitted.

    While it may seem bothersome, it’s a necessity to keep the app ecosystem modern and healthy. Here, F-Droid sends the wrong message to developers (and even users) because they should care about it, and this is why many of us think it may be even harmful to the FOSS ecosystem. Backward compatibility is often the enemy of security, and while there’s a middle-ground for convenience and obsolescence, it shouldn’t be exaggerated. As a result of this philosophy, the main repository of F-Droid is filled with obsolete apps from another era, just for these apps to be able to run on the more than ten years old Android 4.0 Ice Cream Sandwich. Let’s not make the same mistake as the desktop platforms: instead, complain to your vendors for selling devices with no decent OS/firmware support.

    There is little practical reason for developers not to increase the target SDK version (targetSdkVersion) along with each Android release. This attribute matches the version of the platform an app is targeting, and allows access to modern improvements, rules and features on a modern OS. The app can still ensure backwards compatibility in such a way that it can run on older platforms: the minSdkVersion attribute informs the system about the minimum API level required for the application to run. Setting it too low isn’t practical though, because this requires having a lot of fallback code (most of it is handled by common libraries) and separate code paths.

    At the time of writing:

    Overall statistics do not reflect real-world usage of a given app (people using old devices are not necessarily using your app). If anything, it should be viewed as an underestimation.

    4. General lack of good practices

    The F-Droid client allows multiple repositories to coexist within the same app. Many of the issues highlighted above were focused on the main official repository which most of the F-Droid users will use anyway. However, having other repositories in a single app also violates the security model of Android which was not designed for this at all. The OS expects you to trust an app repository as a single source of apps, yet F-Droid isn’t that by design as it mixes several repositories in one single app. This is important because the OS management APIs and features (such as UserManager) are not meant for this and see F-Droid as a single source, so you’re trusting the app client to not mess up far more than you should, especially when the privileged extension comes into the picture. This is also a problem with the OS first-party source feature.

    On that note, it is also worth noting the repository metadata format isn’t properly signed by lacking whole-file signing and key rotation. Their index v1 format uses JAR signing with jarsigner, which has serious security flaws. It seems that work is in progress on a v2 format with support for apksigner, although the final implementation remains to be seen. This just seems to be an over-engineered and flawed approach since better suited tools such as signify could be used to sign the metadata JSON.

    As a matter of fact, the new unattended update API added in API level 31 (Android 12) that allows seamless app updates for app repositories without privileged access to the system (such an approach is not compatible with the security model) won’t work with F-Droid “as is”. It should be mentioned that the aforementioned third-party client Neo-Store supports this API, although the underlying issues about the F-Droid infrastructure largely remain. Indeed, this secure API allowing for unprivileged unattended updates not only requires for the app repository client to target API level 31, but the apps to be updated also have to at least target API level 29.

    Their client also lacks TLS certificate pinning, unlike Play Store which improves security for all connections to Google (they generally use a limited set of root CAs including their own). Certificate pinning is a way for apps to increase the security of their connection to services by providing a set of public key hashes of known-good certificates for these services instead of trusting pre-installed CAs. This can avoid some cases where an interception (man-in-the-middle attack) could be possible and lead to various security issues considering you’re trusting the app to deliver you other apps.

    It is an important security feature that is also straightforward to implement using the declarative network security configuration available since Android 7.0 (API level 24). See how GrapheneOS pins both root and CA certificates in their app repository client:

    <!-- res/xml/network_security_config.xml -->
    -<network-security-config>
    -    <base-config cleartextTrafficPermitted="false"/>
    -    <domain-config>
    -        <domain includeSubdomains="true">apps.grapheneos.org</domain>
    -        <pin-set>
    -            <!-- ISRG Root X1 -->
    -            <pin digest="SHA-256">C5+lpZ7tcVwmwQIMcRtPbsQtWLABXhQzejna0wHFr8M=</pin>
    -            <!-- ISRG Root X2 -->
    -            <pin digest="SHA-256">diGVwiVYbubAI3RW4hB9xU8e/CH2GnkuvVFZE8zmgzI=</pin>
    -            <!-- Let's Encrypt R3 -->
    -            <pin digest="SHA-256">jQJTbIh0grw0/1TkHSumWb+Fs0Ggogr621gT3PvPKG0=</pin>
    -            <!-- Let's Encrypt E1 -->
    -            <pin digest="SHA-256">J2/oqMTsdhFWW/n85tys6b4yDBtb6idZayIEBx7QTxA=</pin>
    -            ...
    -        </pin-set>
    -    </domain-config>
    -</network-security-config>
    -

    To be fair, they’ve thought several times about adding certificate pinning to their client at least for the default repositories. Relics of preliminary work can even be found in their current codebase, but it’s unfortunate that they haven’t been able to find any working implementation so far. Given the overly complex nature of F-Droid, that’s largely understandable.

    F-Droid also has a problem regarding the adoption of new signature schemes as they held out on the v1 signature scheme (which was horrible and deprecated since 2017) until they were forced by Android 11 requirements to support the newer v2/v3 schemes (v2 was introduced in Android 7.0). Quite frankly, this is straight-up bad, and signing APKs with GPG is no better considering how bad PGP and its reference implementation GPG are (even Debian is trying to move away from it). Ideally, F-Droid should fully move on to newer signature schemes, and should completely phase out the legacy signature schemes which are still being used for some apps and metadata.

    5. Confusing UX

    It is worth mentioning that their website has (for some reason) always been hosting an outdated APK of F-Droid, and this is still the case today, leading to many users wondering why they can’t install F-Droid on their secondary user profile (due to the downgrade prevention enforced by Android). “Stability” seems to be the main reason mentioned on their part, which doesn’t make sense: either your version isn’t ready to be published in a stable channel, or it is and new users should be able to access it easily.

    F-Droid should enforce the approach of prefixing the package name of their alternate builds with org.f-droid for instance (or add a .fdroid suffix as some already have). Building and signing while reusing the package name (application ID) is bad practice as it causes signature verification errors when some users try to update/install these apps from other sources, even directly from the developer. That is again due to the security model of Android which enforces a signature check when installing app updates (or installing them again in a secondary user profile). Note that this is going to be an issue with Play App Signing as well, and developers are encouraged to follow this approach should they intend to distribute their apps through different distribution channels.

    This results in a confusing user experience where it’s hard to keep track of who signs each app, and from which repository the app should be downloaded or updated.

    6. Misleading permissions approach

    F-Droid shows a list of the low-level permissions for each app: these low-level permissions are usually grouped in the standard high-level permissions (Location, Microphone, Camera, etc.) and special toggles (nearby Wi-Fi networks, Bluetooth devices, etc.) that are explicitly based on a type of sensitive data. While showing a list of low-level permissions could be useful information for a developer, it’s often a misleading and inaccurate approach for the end-user. Since Android 6, apps have to request the standard permissions at runtime and do not get them simply by being installed, so showing all the “under the hood” permissions without proper context is not useful and makes the permission model unnecessarily confusing.

    F-Droid claims that these low-level permissions are relevant because they support Android 5.1+, meaning they support very outdated versions of Android where apps could have install-time permissions. Anyway, if a technical user wants to see all the manifest permissions for some reason, then they can access the app manifest pretty easily (in fact, exposing the raw manifest would be less misleading). But this is already beyond the scope of this article because anyone who cares about privacy and security wouldn’t run a 8 years old version of Android that has not received security updates for years.

    To clear up confusion: even apps targeting an API level below 23 (Android 5.1 or older) do not have permissions granted at install time on modern Android, which instead displays a legacy permission grant dialog. Whether or not permissions are granted at install time does not just depend on the app’s targetSdkVersion. And even if this were the case, the OS package installer on modern Android would’ve been designed to show the requested permissions for those legacy apps.

    For example, the low-level permission RECEIVE_BOOT_COMPLETED is referred to in F-Droid as the run at startup description, when in fact this permission is not needed to start at boot and just refers to a specific time broadcasted by the system once it finishes booting, and is not about background usage (though power usage may be a valid concern). To be fair, these short summaries used to be provided by the Android documentation years ago, but the permission model has drastically evolved since then and most of them aren’t accurate anymore.

    Allows the app to have itself started as soon as the system has finished booting. This can make it take longer to start the phone and allow the app to slow down the overall phone by always running.

    In modern Android, the background restriction toggle is what really provides the ability for apps to run in the background. Some low-level permissions don’t even have a security/privacy impact and shouldn’t be misinterpreted as having one. Anyhow, you can be sure that each dangerous low-level permission has a high-level representation that is disabled by default and needs to be granted dynamically to the app (by a toggle or explicit user consent in general).

    Another example to illustrate the shortcomings of this approach would be the QUERY_ALL_PACKAGES low-level permission, which is referred to as the query all packages permission that “allows an app to see all installed packages”. While this is somewhat correct, this can also be misleading: apps do not need QUERY_ALL_PACKAGES to list other apps within the same user profile. Even without this permission, some apps are visible automatically (visibility is restricted by default since Android 11). If an app needs more visibility, it will declare a <queries> element in its manifest file: in other words, QUERY_ALL_PACKAGES is only one way to achieve visibility. Again, this goes to show low-level manifest permissions are not intended to be interpreted as high-level permissions the user should fully comprehend.

    Play Store for instance conveys the permissions in a way less misleading way: the main low-level permissions are first grouped in their high-level user-facing toggles, and the rest is shown under “Other”. This permission list can only be accessed by taping “About this app” then “App permissions - See more” at the bottom of the page. Play Store will tell the app may request access to the following permissions: this kind of wording is more important than it seems. Update: since July 2022, Play Store doesn’t offer a way to display low-level permissions anymore.

    Moreover, Play Store restricts the use of highly invasive permissions such as MANAGE_EXTERNAL_STORAGE which allows apps to opt out of scoped storage if they can’t work with more privacy friendly approaches (like a file explorer). Apps that can’t justify their use of this permission (which again has to be granted dynamically) may be removed from Play Store. This is where an app repository can actually be useful in their review process to protect end-users from installing poorly made apps that might compromise their privacy. Not that it matters much if these apps target very old API levels that are inclined to require invasive permissions in the first place…

    Conclusion: what should you do?

    So far, you have been presented with referenced facts that are easily verifiable. In the next part, I’ll allow myself to express my own thoughts and opinions. You’re free to disagree with them, but don’t let that overshadow the rest.

    While some improvements could easily be made, I don’t think F-Droid is in an ideal situation to solve all of these issues because some of them are inherent flaws in their architecture. I’d also argue that their core philosophy is not aligned with some security principles expressed in this article. In any case, I can only wish for them to improve since they’re one of the most popular alternatives to commercial app repositories, and are therefore trusted by a large userbase.

    F-Droid is often seen as the only way to get and support open-source apps: that is not the case. Sure, F-Droid could help you in finding FOSS apps that you wouldn’t otherwise have known existed. Many developers also publish their FOSS apps on the Play Store or their website directly. Most of the time, releases are available on GitHub, which is great since each GitHub releases page has an Atom feed. If downloading APKs from regular websites, you can use apksigner to validate the authenticity by comparing the certificate fingerprint against the fingerprint from another source (it wouldn’t matter otherwise).

    This is how you may proceed to get the app certificate:

    apksigner verify --print-certs --verbose myCoolApp.apk
    -

    Also, as written above: the OS pins the app signature (for all profiles) upon installation, and enforces signature check for app updates. In practice, this means the source doesn’t matter as much after the initial installation.

    For most people, I’d recommend just sticking with Play Store. Play Store isn’t quite flawless, but emphasises the adoption of modern security standards which in turn encourages better privacy practices; as strange as it may sound, Google is not always doing bad things in that regard.

    Note: this article obviously can’t address all the flaws related to Play Store itself. Again, the main topic of this article is about F-Droid and should not be seen as an exhaustive comparison between different app repositories.

    Should I really care?

    It’s up to your threat model, and of course your personal preferences. Most likely, your phone won’t turn into a nuclear weapon if you install F-Droid on it - and this is far from the point that this article is trying to make. Still, I believe the information presented will be valuable for anyone who values a practical approach to privacy (rather than an ideological one). Such an approach is partially described below.

    But there is more malware in Play Store! How can you say that it’s more secure?

    As explained above, it doesn’t matter as you shouldn’t really rely on any quality control to be the sole guarantee that a software is free of malicious or exploitable code. Play Store and even the Apple App Store may have a considerable amount of malware because a full reverse-engineering of any uploaded app isn’t feasible realistically. However, they fulfill their role quite well, and that is all that is expected of them.

    With Play App Signing being effectively enforced for new apps, isn’t Play Store as “flawed” as F-Droid?

    I’ve seen this comment repeatedly, and it would be dismissing all the other points made in this article. Also, I strongly suggest that you carefully read the sections related to Play App Signing, and preferably the official documentation on this matter. It’s not a black and white question and there are many more nuances to it.

    Aren’t open-source apps more secure? Doesn’t it make F-Droid safer?

    You can still find and get your open-source apps elsewhere. And no, open-source apps aren’t necessarily more private or secure. Instead, you should rely on the strong security and privacy guarantees provided by a modern operating system with a robust sandboxing/permission model, namely modern Android, GrapheneOS and iOS. Pay close attention to the permissions you grant, and avoid legacy apps as they could require invasive permissions to run.

    When it comes to trackers (this really comes up a lot), you shouldn’t believe in the flawed idea that you can enumerate all of them. The enumerating badness approach is known to be flawed in the security field, and the same applies to privacy. You shouldn’t believe that a random script can detect every single line of code that can be used for data exfiltration. Data exfiltration can be properly prevented in the first place by the permission model, which again denies access to sensitive data by default: this is a simple, yet rigorous and effective approach.

    No app should be unnecessarily entrusted with any kind of permission. It is only if you deem it necessary that you should allow access to a type of data, and this access should be as fine-grained as possible. That’s the way the Android platform works (regular apps run in the explicit untrusted_app domain) and continues evolving. Contrary to some popular beliefs, usability and most productivity tasks can still be achieved in a secure and private way.

    Isn’t Google evil? Isn’t Play Store spyware?

    Some people tend to exaggerate the importance of Google in their threat model, at the cost of pragmatism and security/privacy good practices. Play Store isn’t spyware and can run unprivileged like it does on GrapheneOS (including with unattended updates support). On the vast majority of devices though, Google Play is a privileged app and a core part of the OS that provides low-level system modules. In that case, the trust issues involved with Play App Signing could be considered less important since Google Play is already trusted as a privileged component.

    Play Store evidently has some privacy issues given it’s a proprietary service which requires an account (this cannot be circumvented), and Google services have a history of nagging users to enable privacy-invasive features. Again, some of these privacy issues can be mitigated by setting up the Play services compatibility layer from GrapheneOS which runs Play services and Play Store in the regular app sandbox (the untrusted_app domain). ProtonAOSP also shares that feature. This solution could very well be ported to other Android-based operating systems. If you want to go further, consider using a properly configured account with the least amount of personally indentifiable information possible (note that the phone number requirement appears to be region-dependent).

    If you don’t have Play services installed, you can use a third-party Play Store client called Aurora Store. Aurora Store has some issues of its own, and some of them overlap in fact with F-Droid. Aurora Store somehow still requires the legacy storage permission, has yet to implement certificate pinning, has been known to sometimes retrieve wrong versions of apps, and distributed account tokens over cleartext HTTP until fairly recently; not that it matters much since tokens were designed to be shared between users, which is already concerning. I’d recommend against using the shared “anonymous” accounts feature: you should make your own throwaway account with minimal information.

    You should also keep an eye on the great work GrapheneOS does on their future app repository. It will be a simple, secure, modern app repository for a curated list of high-quality apps, some of which will have their own builds (for instance, Signal still uses their original 1024-bits RSA key that has never been rotated since then). Inspired by this work, a GrapheneOS community member is developing a more generic app repository called Accrescent. Hopefully, we’ll see well-made alternatives like these flourish.

    Thanks to the GrapheneOS community for proofreading this article. Bear in mind that these are not official recommendations from the GrapheneOS project.

    Post-publication note: it’s unfortunate that the release of this article mostly triggered a negative response from the F-Droid team which prefers to dismiss this article on several occasions rather than bringing relevant counterpoints. Some of their core members are also involved in a harassment campaign towards projects and security researchers that do not share their views. While this article remains a technical one, there are definitely ethical concerns to take into consideration.

    \ No newline at end of file diff --git a/public/software/index.html b/public/software/index.html deleted file mode 100644 index bb553cf..0000000 --- a/public/software/index.html +++ /dev/null @@ -1,7 +0,0 @@ -Software | PrivSec.dev

    F-Droid Security Analysis

    F-Droid is a popular alternative app repository for Android, especially known for its main repository dedicated to free and open-source software. F-Droid is often recommended among security and privacy enthusiasts, but how does it stack up against Play Store in practice? This write-up will attempt to emphasize major security issues with F-Droid that you should consider. -Before we start, a few things to keep in mind: -The main goal of this write-up was to inform users so they can make responsible choices, not to trash someone else’s work....

    January 2, 2022 · 26 min · 5392 words · Wonderfall
    \ No newline at end of file diff --git a/public/software/index.xml b/public/software/index.xml deleted file mode 100644 index 859b928..0000000 --- a/public/software/index.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - Software on PrivSec.dev - https://privsec.dev/software/ - Recent content in Software on PrivSec.dev - Hugo -- gohugo.io - Sun, 02 Jan 2022 21:28:31 +0000 - - F-Droid Security Analysis - https://privsec.dev/software/f-droid-security-analysis/ - Sun, 02 Jan 2022 21:28:31 +0000 - - https://privsec.dev/software/f-droid-security-analysis/ - F-Droid is a popular alternative app repository for Android, especially known for its main repository dedicated to free and open-source software. F-Droid is often recommended among security and privacy enthusiasts, but how does it stack up against Play Store in practice? This write-up will attempt to emphasize major security issues with F-Droid that you should consider. -Before we start, a few things to keep in mind: -The main goal of this write-up was to inform users so they can make responsible choices, not to trash someone else&rsquo;s work. - - - - diff --git a/public/software/page/1/index.html b/public/software/page/1/index.html deleted file mode 100644 index af50056..0000000 --- a/public/software/page/1/index.html +++ /dev/null @@ -1 +0,0 @@ -https://privsec.dev/software/ \ No newline at end of file diff --git a/public/tags/android/index.html b/public/tags/android/index.html deleted file mode 100644 index 3198296..0000000 --- a/public/tags/android/index.html +++ /dev/null @@ -1,7 +0,0 @@ -android | PrivSec.dev

    F-Droid Security Analysis

    F-Droid is a popular alternative app repository for Android, especially known for its main repository dedicated to free and open-source software. F-Droid is often recommended among security and privacy enthusiasts, but how does it stack up against Play Store in practice? This write-up will attempt to emphasize major security issues with F-Droid that you should consider. -Before we start, a few things to keep in mind: -The main goal of this write-up was to inform users so they can make responsible choices, not to trash someone else’s work....

    25 min · 5298 words · Wonderfall
    \ No newline at end of file diff --git a/public/tags/android/index.xml b/public/tags/android/index.xml deleted file mode 100644 index e71b12f..0000000 --- a/public/tags/android/index.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - android on PrivSec.dev - https://privsec.dev/tags/android/ - Recent content in android on PrivSec.dev - Hugo -- gohugo.io - - F-Droid Security Analysis - https://privsec.dev/apps/f-droid-security-analysis/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://privsec.dev/apps/f-droid-security-analysis/ - F-Droid is a popular alternative app repository for Android, especially known for its main repository dedicated to free and open-source software. F-Droid is often recommended among security and privacy enthusiasts, but how does it stack up against Play Store in practice? This write-up will attempt to emphasize major security issues with F-Droid that you should consider. -Before we start, a few things to keep in mind: -The main goal of this write-up was to inform users so they can make responsible choices, not to trash someone else&rsquo;s work. - - - - diff --git a/public/tags/android/page/1/index.html b/public/tags/android/page/1/index.html deleted file mode 100644 index b33a3f9..0000000 --- a/public/tags/android/page/1/index.html +++ /dev/null @@ -1 +0,0 @@ -https://privsec.dev/tags/android/ \ No newline at end of file diff --git a/public/tags/container/index.html b/public/tags/container/index.html deleted file mode 100644 index 8af549e..0000000 --- a/public/tags/container/index.html +++ /dev/null @@ -1,8 +0,0 @@ -container | PrivSec.dev

    Docker and OCI Hardening

    Containers aren’t that new fancy thing anymore, but they were a big deal. And they still are. They are a concrete solution to the following problem: -- Hey, your software doesn’t work… -- Sorry, it works on my computer! Can’t help you. -Whether we like them or not, containers are here to stay. Their expressiveness and semantics allow for an abstraction of the OS dependencies that a software has, the latter being often dynamically linked against certain libraries....

    19 min · 3925 words · Wonderfall
    \ No newline at end of file diff --git a/public/tags/container/index.xml b/public/tags/container/index.xml deleted file mode 100644 index 84a4284..0000000 --- a/public/tags/container/index.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - container on PrivSec.dev - https://privsec.dev/tags/container/ - Recent content in container on PrivSec.dev - Hugo -- gohugo.io - - Docker and OCI Hardening - https://privsec.dev/os/docker-and-oci-hardening/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://privsec.dev/os/docker-and-oci-hardening/ - Containers aren&rsquo;t that new fancy thing anymore, but they were a big deal. And they still are. They are a concrete solution to the following problem: -- Hey, your software doesn&rsquo;t work&hellip; -- Sorry, it works on my computer! Can&rsquo;t help you. -Whether we like them or not, containers are here to stay. Their expressiveness and semantics allow for an abstraction of the OS dependencies that a software has, the latter being often dynamically linked against certain libraries. - - - - diff --git a/public/tags/container/page/1/index.html b/public/tags/container/page/1/index.html deleted file mode 100644 index b879394..0000000 --- a/public/tags/container/page/1/index.html +++ /dev/null @@ -1 +0,0 @@ -https://privsec.dev/tags/container/ \ No newline at end of file diff --git a/public/tags/index.html b/public/tags/index.html deleted file mode 100644 index 7908d06..0000000 --- a/public/tags/index.html +++ /dev/null @@ -1,4 +0,0 @@ -Tags | PrivSec.dev
    \ No newline at end of file diff --git a/public/tags/index.xml b/public/tags/index.xml deleted file mode 100644 index 7887fe8..0000000 --- a/public/tags/index.xml +++ /dev/null @@ -1,81 +0,0 @@ - - - - Tags on PrivSec.dev - https://privsec.dev/tags/ - Recent content in Tags on PrivSec.dev - Hugo -- gohugo.io - - android - https://privsec.dev/tags/android/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://privsec.dev/tags/android/ - - - - - container - https://privsec.dev/tags/container/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://privsec.dev/tags/container/ - - - - - knowledge base - https://privsec.dev/tags/knowledge-base/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://privsec.dev/tags/knowledge-base/ - - - - - linux - https://privsec.dev/tags/linux/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://privsec.dev/tags/linux/ - - - - - operating system - https://privsec.dev/tags/operating-system/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://privsec.dev/tags/operating-system/ - - - - - operating systems - https://privsec.dev/tags/operating-systems/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://privsec.dev/tags/operating-systems/ - - - - - security - https://privsec.dev/tags/security/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://privsec.dev/tags/security/ - - - - - software - https://privsec.dev/tags/software/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://privsec.dev/tags/software/ - - - - - diff --git a/public/tags/knowledge-base/index.html b/public/tags/knowledge-base/index.html deleted file mode 100644 index f4fdd94..0000000 --- a/public/tags/knowledge-base/index.html +++ /dev/null @@ -1,6 +0,0 @@ -knowledge base | PrivSec.dev

    Multi-factor Authentication

    Multi-factor authentication is a security mechanism that requires additional verification beyond your username (or email) and password. This usually comes in the form of a one time passcode, a push notification, or plugging in and tapping a hardware security key. -Common protocols Email and SMS MFA Email and SMS MFA are examples of the weaker MFA protocols. Email MFA is not great as whoever controls your email account can typically both reset your password and recieve your MFA verification....

    6 min · 1223 words · Tommy
    \ No newline at end of file diff --git a/public/tags/knowledge-base/index.xml b/public/tags/knowledge-base/index.xml deleted file mode 100644 index e6dda11..0000000 --- a/public/tags/knowledge-base/index.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - knowledge base on PrivSec.dev - https://privsec.dev/tags/knowledge-base/ - Recent content in knowledge base on PrivSec.dev - Hugo -- gohugo.io - - Multi-factor Authentication - https://privsec.dev/knowledge/multi-factor-authentication/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://privsec.dev/knowledge/multi-factor-authentication/ - Multi-factor authentication is a security mechanism that requires additional verification beyond your username (or email) and password. This usually comes in the form of a one time passcode, a push notification, or plugging in and tapping a hardware security key. -Common protocols Email and SMS MFA Email and SMS MFA are examples of the weaker MFA protocols. Email MFA is not great as whoever controls your email account can typically both reset your password and recieve your MFA verification. - - - - diff --git a/public/tags/knowledge-base/page/1/index.html b/public/tags/knowledge-base/page/1/index.html deleted file mode 100644 index 1820a40..0000000 --- a/public/tags/knowledge-base/page/1/index.html +++ /dev/null @@ -1 +0,0 @@ -https://privsec.dev/tags/knowledge-base/ \ No newline at end of file diff --git a/public/tags/linux/index.html b/public/tags/linux/index.html deleted file mode 100644 index 1d4193e..0000000 --- a/public/tags/linux/index.html +++ /dev/null @@ -1,12 +0,0 @@ -linux | PrivSec.dev

    Choosing Your Desktop Linux Distribution

    Not all Linux distributions are created equal. When choosing a Linux distribution, there are several things you need to keep in mind. -Release cycle You should choose a distribution which stays close to the stable upstream software releases, typically rolling release distributions. This is because frozen release cycle distributions often don’t update package versions and fall behind on security updates. -For frozen distributions, package maintainers are expected to backport patches to fix vulnerabilities (Debian is one such example) rather than bump the software to the “next version” released by the upstream developer....

    7 min · 1431 words · Tommy

    Docker and OCI Hardening

    Containers aren’t that new fancy thing anymore, but they were a big deal. And they still are. They are a concrete solution to the following problem: -- Hey, your software doesn’t work… -- Sorry, it works on my computer! Can’t help you. -Whether we like them or not, containers are here to stay. Their expressiveness and semantics allow for an abstraction of the OS dependencies that a software has, the latter being often dynamically linked against certain libraries....

    19 min · 3925 words · Wonderfall

    Linux Insecurities

    There is a common misconception among privacy communities that Linux is one of the more secure operating systems, either because it is open source or because it is widely used in the cloud. This is however, a far cry from reality. -There is already a very indepth technical blog explaning the various security weaknesses of Linux by Madaidan, Whonix’s Security Researcher. This page will attempt to address some of the questions commonly raised in reaction to his blog post....

    3 min · 589 words · Tommy

    Securing OpenSSH with FIDO2

    Passwordless authentication with OpenSSH keys has been the de facto security standard for years. SSH keys are more robust since they’re cryptographically sane by default, and are therefore resilient to most bruteforce atacks. They’re also easier to manage while enabling a form of decentralized authentication (it’s easy and painless to revoke them). So, what’s the next step? And more exactly, why would one need something even better? -Why? The main problem with SSH keys is that they’re not magic: they consist of a key pair, of which the private key is stored on your disk....

    5 min · 863 words · Wonderfall
    \ No newline at end of file diff --git a/public/tags/linux/index.xml b/public/tags/linux/index.xml deleted file mode 100644 index 43c61ca..0000000 --- a/public/tags/linux/index.xml +++ /dev/null @@ -1,52 +0,0 @@ - - - - linux on PrivSec.dev - https://privsec.dev/tags/linux/ - Recent content in linux on PrivSec.dev - Hugo -- gohugo.io - - Choosing Your Desktop Linux Distribution - https://privsec.dev/os/choosing-your-desktop-linux-distribution/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://privsec.dev/os/choosing-your-desktop-linux-distribution/ - Not all Linux distributions are created equal. When choosing a Linux distribution, there are several things you need to keep in mind. -Release cycle You should choose a distribution which stays close to the stable upstream software releases, typically rolling release distributions. This is because frozen release cycle distributions often don’t update package versions and fall behind on security updates. -For frozen distributions, package maintainers are expected to backport patches to fix vulnerabilities (Debian is one such example) rather than bump the software to the “next version” released by the upstream developer. - - - - Docker and OCI Hardening - https://privsec.dev/os/docker-and-oci-hardening/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://privsec.dev/os/docker-and-oci-hardening/ - Containers aren&rsquo;t that new fancy thing anymore, but they were a big deal. And they still are. They are a concrete solution to the following problem: -- Hey, your software doesn&rsquo;t work&hellip; -- Sorry, it works on my computer! Can&rsquo;t help you. -Whether we like them or not, containers are here to stay. Their expressiveness and semantics allow for an abstraction of the OS dependencies that a software has, the latter being often dynamically linked against certain libraries. - - - - Linux Insecurities - https://privsec.dev/os/linux-insecurities/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://privsec.dev/os/linux-insecurities/ - There is a common misconception among privacy communities that Linux is one of the more secure operating systems, either because it is open source or because it is widely used in the cloud. This is however, a far cry from reality. -There is already a very indepth technical blog explaning the various security weaknesses of Linux by Madaidan, Whonix&rsquo;s Security Researcher. This page will attempt to address some of the questions commonly raised in reaction to his blog post. - - - - Securing OpenSSH with FIDO2 - https://privsec.dev/os/securing-openssh-with-fido2/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://privsec.dev/os/securing-openssh-with-fido2/ - Passwordless authentication with OpenSSH keys has been the de facto security standard for years. SSH keys are more robust since they&rsquo;re cryptographically sane by default, and are therefore resilient to most bruteforce atacks. They&rsquo;re also easier to manage while enabling a form of decentralized authentication (it&rsquo;s easy and painless to revoke them). So, what&rsquo;s the next step? And more exactly, why would one need something even better? -Why? The main problem with SSH keys is that they&rsquo;re not magic: they consist of a key pair, of which the private key is stored on your disk. - - - - diff --git a/public/tags/linux/page/1/index.html b/public/tags/linux/page/1/index.html deleted file mode 100644 index 6d7fbd9..0000000 --- a/public/tags/linux/page/1/index.html +++ /dev/null @@ -1 +0,0 @@ -https://privsec.dev/tags/linux/ \ No newline at end of file diff --git a/public/tags/operating-system/index.html b/public/tags/operating-system/index.html deleted file mode 100644 index 28c2d13..0000000 --- a/public/tags/operating-system/index.html +++ /dev/null @@ -1,8 +0,0 @@ -operating system | PrivSec.dev

    Choosing Your Desktop Linux Distribution

    Not all Linux distributions are created equal. When choosing a Linux distribution, there are several things you need to keep in mind. -Release cycle You should choose a distribution which stays close to the stable upstream software releases, typically rolling release distributions. This is because frozen release cycle distributions often don’t update package versions and fall behind on security updates. -For frozen distributions, package maintainers are expected to backport patches to fix vulnerabilities (Debian is one such example) rather than bump the software to the “next version” released by the upstream developer....

    7 min · 1431 words · Tommy

    Linux Insecurities

    There is a common misconception among privacy communities that Linux is one of the more secure operating systems, either because it is open source or because it is widely used in the cloud. This is however, a far cry from reality. -There is already a very indepth technical blog explaning the various security weaknesses of Linux by Madaidan, Whonix’s Security Researcher. This page will attempt to address some of the questions commonly raised in reaction to his blog post....

    3 min · 589 words · Tommy
    \ No newline at end of file diff --git a/public/tags/operating-system/index.xml b/public/tags/operating-system/index.xml deleted file mode 100644 index c3ce661..0000000 --- a/public/tags/operating-system/index.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - operating system on PrivSec.dev - https://privsec.dev/tags/operating-system/ - Recent content in operating system on PrivSec.dev - Hugo -- gohugo.io - - Choosing Your Desktop Linux Distribution - https://privsec.dev/os/choosing-your-desktop-linux-distribution/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://privsec.dev/os/choosing-your-desktop-linux-distribution/ - Not all Linux distributions are created equal. When choosing a Linux distribution, there are several things you need to keep in mind. -Release cycle You should choose a distribution which stays close to the stable upstream software releases, typically rolling release distributions. This is because frozen release cycle distributions often don’t update package versions and fall behind on security updates. -For frozen distributions, package maintainers are expected to backport patches to fix vulnerabilities (Debian is one such example) rather than bump the software to the “next version” released by the upstream developer. - - - - Linux Insecurities - https://privsec.dev/os/linux-insecurities/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://privsec.dev/os/linux-insecurities/ - There is a common misconception among privacy communities that Linux is one of the more secure operating systems, either because it is open source or because it is widely used in the cloud. This is however, a far cry from reality. -There is already a very indepth technical blog explaning the various security weaknesses of Linux by Madaidan, Whonix&rsquo;s Security Researcher. This page will attempt to address some of the questions commonly raised in reaction to his blog post. - - - - diff --git a/public/tags/operating-system/page/1/index.html b/public/tags/operating-system/page/1/index.html deleted file mode 100644 index f89178f..0000000 --- a/public/tags/operating-system/page/1/index.html +++ /dev/null @@ -1 +0,0 @@ -https://privsec.dev/tags/operating-system/ \ No newline at end of file diff --git a/public/tags/operating-systems/index.html b/public/tags/operating-systems/index.html deleted file mode 100644 index a54fe98..0000000 --- a/public/tags/operating-systems/index.html +++ /dev/null @@ -1,9 +0,0 @@ -operating systems | PrivSec.dev

    Docker and OCI Hardening

    Containers aren’t that new fancy thing anymore, but they were a big deal. And they still are. They are a concrete solution to the following problem: -- Hey, your software doesn’t work… -- Sorry, it works on my computer! Can’t help you. -Whether we like them or not, containers are here to stay. Their expressiveness and semantics allow for an abstraction of the OS dependencies that a software has, the latter being often dynamically linked against certain libraries....

    19 min · 3925 words · Wonderfall

    Securing OpenSSH with FIDO2

    Passwordless authentication with OpenSSH keys has been the de facto security standard for years. SSH keys are more robust since they’re cryptographically sane by default, and are therefore resilient to most bruteforce atacks. They’re also easier to manage while enabling a form of decentralized authentication (it’s easy and painless to revoke them). So, what’s the next step? And more exactly, why would one need something even better? -Why? The main problem with SSH keys is that they’re not magic: they consist of a key pair, of which the private key is stored on your disk....

    5 min · 863 words · Wonderfall
    \ No newline at end of file diff --git a/public/tags/operating-systems/index.xml b/public/tags/operating-systems/index.xml deleted file mode 100644 index 0506e8c..0000000 --- a/public/tags/operating-systems/index.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - operating systems on PrivSec.dev - https://privsec.dev/tags/operating-systems/ - Recent content in operating systems on PrivSec.dev - Hugo -- gohugo.io - - Docker and OCI Hardening - https://privsec.dev/os/docker-and-oci-hardening/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://privsec.dev/os/docker-and-oci-hardening/ - Containers aren&rsquo;t that new fancy thing anymore, but they were a big deal. And they still are. They are a concrete solution to the following problem: -- Hey, your software doesn&rsquo;t work&hellip; -- Sorry, it works on my computer! Can&rsquo;t help you. -Whether we like them or not, containers are here to stay. Their expressiveness and semantics allow for an abstraction of the OS dependencies that a software has, the latter being often dynamically linked against certain libraries. - - - - Securing OpenSSH with FIDO2 - https://privsec.dev/os/securing-openssh-with-fido2/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://privsec.dev/os/securing-openssh-with-fido2/ - Passwordless authentication with OpenSSH keys has been the de facto security standard for years. SSH keys are more robust since they&rsquo;re cryptographically sane by default, and are therefore resilient to most bruteforce atacks. They&rsquo;re also easier to manage while enabling a form of decentralized authentication (it&rsquo;s easy and painless to revoke them). So, what&rsquo;s the next step? And more exactly, why would one need something even better? -Why? The main problem with SSH keys is that they&rsquo;re not magic: they consist of a key pair, of which the private key is stored on your disk. - - - - diff --git a/public/tags/operating-systems/page/1/index.html b/public/tags/operating-systems/page/1/index.html deleted file mode 100644 index 1aab475..0000000 --- a/public/tags/operating-systems/page/1/index.html +++ /dev/null @@ -1 +0,0 @@ -https://privsec.dev/tags/operating-systems/ \ No newline at end of file diff --git a/public/tags/security/index.html b/public/tags/security/index.html deleted file mode 100644 index fcedd59..0000000 --- a/public/tags/security/index.html +++ /dev/null @@ -1,14 +0,0 @@ -security | PrivSec.dev

    Choosing Your Desktop Linux Distribution

    Not all Linux distributions are created equal. When choosing a Linux distribution, there are several things you need to keep in mind. -Release cycle You should choose a distribution which stays close to the stable upstream software releases, typically rolling release distributions. This is because frozen release cycle distributions often don’t update package versions and fall behind on security updates. -For frozen distributions, package maintainers are expected to backport patches to fix vulnerabilities (Debian is one such example) rather than bump the software to the “next version” released by the upstream developer....

    7 min · 1431 words · Tommy

    Docker and OCI Hardening

    Containers aren’t that new fancy thing anymore, but they were a big deal. And they still are. They are a concrete solution to the following problem: -- Hey, your software doesn’t work… -- Sorry, it works on my computer! Can’t help you. -Whether we like them or not, containers are here to stay. Their expressiveness and semantics allow for an abstraction of the OS dependencies that a software has, the latter being often dynamically linked against certain libraries....

    19 min · 3925 words · Wonderfall

    F-Droid Security Analysis

    F-Droid is a popular alternative app repository for Android, especially known for its main repository dedicated to free and open-source software. F-Droid is often recommended among security and privacy enthusiasts, but how does it stack up against Play Store in practice? This write-up will attempt to emphasize major security issues with F-Droid that you should consider. -Before we start, a few things to keep in mind: -The main goal of this write-up was to inform users so they can make responsible choices, not to trash someone else’s work....

    25 min · 5298 words · Wonderfall

    Linux Insecurities

    There is a common misconception among privacy communities that Linux is one of the more secure operating systems, either because it is open source or because it is widely used in the cloud. This is however, a far cry from reality. -There is already a very indepth technical blog explaning the various security weaknesses of Linux by Madaidan, Whonix’s Security Researcher. This page will attempt to address some of the questions commonly raised in reaction to his blog post....

    3 min · 589 words · Tommy

    Multi-factor Authentication

    Multi-factor authentication is a security mechanism that requires additional verification beyond your username (or email) and password. This usually comes in the form of a one time passcode, a push notification, or plugging in and tapping a hardware security key. -Common protocols Email and SMS MFA Email and SMS MFA are examples of the weaker MFA protocols. Email MFA is not great as whoever controls your email account can typically both reset your password and recieve your MFA verification....

    6 min · 1223 words · Tommy
    \ No newline at end of file diff --git a/public/tags/security/index.xml b/public/tags/security/index.xml deleted file mode 100644 index f70e1f0..0000000 --- a/public/tags/security/index.xml +++ /dev/null @@ -1,73 +0,0 @@ - - - - security on PrivSec.dev - https://privsec.dev/tags/security/ - Recent content in security on PrivSec.dev - Hugo -- gohugo.io - - Choosing Your Desktop Linux Distribution - https://privsec.dev/os/choosing-your-desktop-linux-distribution/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://privsec.dev/os/choosing-your-desktop-linux-distribution/ - Not all Linux distributions are created equal. When choosing a Linux distribution, there are several things you need to keep in mind. -Release cycle You should choose a distribution which stays close to the stable upstream software releases, typically rolling release distributions. This is because frozen release cycle distributions often don’t update package versions and fall behind on security updates. -For frozen distributions, package maintainers are expected to backport patches to fix vulnerabilities (Debian is one such example) rather than bump the software to the “next version” released by the upstream developer. - - - - Docker and OCI Hardening - https://privsec.dev/os/docker-and-oci-hardening/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://privsec.dev/os/docker-and-oci-hardening/ - Containers aren&rsquo;t that new fancy thing anymore, but they were a big deal. And they still are. They are a concrete solution to the following problem: -- Hey, your software doesn&rsquo;t work&hellip; -- Sorry, it works on my computer! Can&rsquo;t help you. -Whether we like them or not, containers are here to stay. Their expressiveness and semantics allow for an abstraction of the OS dependencies that a software has, the latter being often dynamically linked against certain libraries. - - - - F-Droid Security Analysis - https://privsec.dev/apps/f-droid-security-analysis/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://privsec.dev/apps/f-droid-security-analysis/ - F-Droid is a popular alternative app repository for Android, especially known for its main repository dedicated to free and open-source software. F-Droid is often recommended among security and privacy enthusiasts, but how does it stack up against Play Store in practice? This write-up will attempt to emphasize major security issues with F-Droid that you should consider. -Before we start, a few things to keep in mind: -The main goal of this write-up was to inform users so they can make responsible choices, not to trash someone else&rsquo;s work. - - - - Linux Insecurities - https://privsec.dev/os/linux-insecurities/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://privsec.dev/os/linux-insecurities/ - There is a common misconception among privacy communities that Linux is one of the more secure operating systems, either because it is open source or because it is widely used in the cloud. This is however, a far cry from reality. -There is already a very indepth technical blog explaning the various security weaknesses of Linux by Madaidan, Whonix&rsquo;s Security Researcher. This page will attempt to address some of the questions commonly raised in reaction to his blog post. - - - - Multi-factor Authentication - https://privsec.dev/knowledge/multi-factor-authentication/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://privsec.dev/knowledge/multi-factor-authentication/ - Multi-factor authentication is a security mechanism that requires additional verification beyond your username (or email) and password. This usually comes in the form of a one time passcode, a push notification, or plugging in and tapping a hardware security key. -Common protocols Email and SMS MFA Email and SMS MFA are examples of the weaker MFA protocols. Email MFA is not great as whoever controls your email account can typically both reset your password and recieve your MFA verification. - - - - Securing OpenSSH with FIDO2 - https://privsec.dev/os/securing-openssh-with-fido2/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://privsec.dev/os/securing-openssh-with-fido2/ - Passwordless authentication with OpenSSH keys has been the de facto security standard for years. SSH keys are more robust since they&rsquo;re cryptographically sane by default, and are therefore resilient to most bruteforce atacks. They&rsquo;re also easier to manage while enabling a form of decentralized authentication (it&rsquo;s easy and painless to revoke them). So, what&rsquo;s the next step? And more exactly, why would one need something even better? -Why? The main problem with SSH keys is that they&rsquo;re not magic: they consist of a key pair, of which the private key is stored on your disk. - - - - diff --git a/public/tags/security/page/1/index.html b/public/tags/security/page/1/index.html deleted file mode 100644 index e579248..0000000 --- a/public/tags/security/page/1/index.html +++ /dev/null @@ -1 +0,0 @@ -https://privsec.dev/tags/security/ \ No newline at end of file diff --git a/public/tags/security/page/2/index.html b/public/tags/security/page/2/index.html deleted file mode 100644 index 1817e77..0000000 --- a/public/tags/security/page/2/index.html +++ /dev/null @@ -1,6 +0,0 @@ -security | PrivSec.dev

    Securing OpenSSH with FIDO2

    Passwordless authentication with OpenSSH keys has been the de facto security standard for years. SSH keys are more robust since they’re cryptographically sane by default, and are therefore resilient to most bruteforce atacks. They’re also easier to manage while enabling a form of decentralized authentication (it’s easy and painless to revoke them). So, what’s the next step? And more exactly, why would one need something even better? -Why? The main problem with SSH keys is that they’re not magic: they consist of a key pair, of which the private key is stored on your disk....

    5 min · 863 words · Wonderfall
    \ No newline at end of file diff --git a/public/tags/software/index.html b/public/tags/software/index.html deleted file mode 100644 index b1ab8b4..0000000 --- a/public/tags/software/index.html +++ /dev/null @@ -1,7 +0,0 @@ -software | PrivSec.dev

    F-Droid Security Analysis

    F-Droid is a popular alternative app repository for Android, especially known for its main repository dedicated to free and open-source software. F-Droid is often recommended among security and privacy enthusiasts, but how does it stack up against Play Store in practice? This write-up will attempt to emphasize major security issues with F-Droid that you should consider. -Before we start, a few things to keep in mind: -The main goal of this write-up was to inform users so they can make responsible choices, not to trash someone else’s work....

    25 min · 5298 words · Wonderfall
    \ No newline at end of file diff --git a/public/tags/software/index.xml b/public/tags/software/index.xml deleted file mode 100644 index 870fcc7..0000000 --- a/public/tags/software/index.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - software on PrivSec.dev - https://privsec.dev/tags/software/ - Recent content in software on PrivSec.dev - Hugo -- gohugo.io - - F-Droid Security Analysis - https://privsec.dev/apps/f-droid-security-analysis/ - Mon, 01 Jan 0001 00:00:00 +0000 - - https://privsec.dev/apps/f-droid-security-analysis/ - F-Droid is a popular alternative app repository for Android, especially known for its main repository dedicated to free and open-source software. F-Droid is often recommended among security and privacy enthusiasts, but how does it stack up against Play Store in practice? This write-up will attempt to emphasize major security issues with F-Droid that you should consider. -Before we start, a few things to keep in mind: -The main goal of this write-up was to inform users so they can make responsible choices, not to trash someone else&rsquo;s work. - - - - diff --git a/public/tags/software/page/1/index.html b/public/tags/software/page/1/index.html deleted file mode 100644 index 572fe17..0000000 --- a/public/tags/software/page/1/index.html +++ /dev/null @@ -1 +0,0 @@ -https://privsec.dev/tags/software/ \ No newline at end of file diff --git a/public/yubico-otp.png b/public/yubico-otp.png deleted file mode 100644 index 92e4b14..0000000 Binary files a/public/yubico-otp.png and /dev/null differ