Technical information about our security practices
This document describes the technical controls in place across the Loco service. It's written for security, procurement and risk assessment teams. Running a secure and robust web service is ongoing work at Loco, so this document is revised as our systems change.
Password hashing
We don't store passwords. We store bcrypt hashes. This allows us to verify any password presented to us (such as when logging in) but the original password cannot be reversed from the hash other than through an infeasibly large number of guesses.
Each hash has a random salt and a "cost" factor of 10. The following is a hash of a password comprising three words in the English dictionary:
$2y$10$Am7tsxy2qygsNej.U718kOSuvcpFYYzYl/khr3bq6nvx6Y9cTgo2W
A list of ten thousand words yields a trillion three-word combinations, but bcrypt is deliberately slow to compute, so searching that space is not workable even with very fast computers.
Future upgrades
From time to time we will upgrade the strength of this hash. This could be due to the emergence of vulnerabilities, or simply because computers have become faster. In such an event we double hash every password in our database instead of waiting for you to log in again.
Example: When Loco first launched we were using sha256 hashes before upgrading to bcrypt in Oct 2016.
Users that haven't logged in since then will now have a hash that's a bcrypt of their old hash. Effectively: bcrypt(sha256(text)).
This method ensures weak legacy passwords are never left lying around.
Password strength
Passwords are validated when set or changed, for length and form, and against a corpus of breached passwords.
The minimum length is 8 characters, although we encourage longer phrases in the UI and user-facing help pages.
A password may not be entirely numeric, but beyond this we impose no composition rules (cases, numbers or symbols). Such rules produce short passwords of a predictable shape that are no harder to guess than a plain phrase of the same length. This follows the guidance in NIST SP 800-63B, which sets the same eight character floor.
There's also an upper bound of 72 bytes. This isn't a storage limitation; we don't store passwords! The reason is that a longer password would be silently truncated by bcrypt and give a false impression of strength. We reject rather than truncate.
Breached password screening
Passwords are rejected if they appear in a corpus compiled from public breach data. Matching is case-insensitive, and the corpus is refreshed periodically.
The corpus is held on our own servers as truncated SHA-1 digests. No part of a password is ever sent to a third party.
All these factors may change from time to time, and this section will be revised to match.
Staying signed in
Our auto-login feature is opt-in at the point of logging in with a password. The client is issued a 64 character token comprising a lookup key and a 24 byte random secret that we never store. Only a bcrypt hash of the combined token is held on our servers, using the same algorithm as password hashing.
The token is single use. It's deleted from our database the moment it's presented, and a replacement is issued and written back to the cookie. The cookie expires one year after each use, and is deleted and invalidated server-side when the user signs out.
Note that an auth key authenticates the session outright: it bypasses any second factor configured on the account.
Both the session cookie and the auto-login cookie are set Secure and HttpOnly, meaning they're never sent over an unencrypted connection and can never be read by JavaScript, even if a page on our site were somehow compromised. The session cookie is SameSite=Lax; the auto-login cookie is SameSite=Strict. See browser security headers.
Two-factor authentication
A second authentication factor can be enrolled by the account holder from their profile (instructions). Two methods are supported:
- Authenticator app (TOTP) — RFC 6238, HMAC-SHA1 over a 30 second time step with a tolerance of ±1 step. The shared secret is 20 random bytes generated on our server and displayed once, at enrolment. Codes are verified locally; no third party is involved.
- SMS — one-time codes delivered via Twilio Verify. Phone numbers are encrypted at rest with AES-256-CTR under a random IV, and the database row is keyed by an HMAC-SHA256 of the number rather than the number itself. Code requests are rate limited per account and per /24 network.
Each factor is issued a single-use recovery code, stored as a bcrypt hash. Redeeming it revokes the factor and disables 2FA on the account, so the holder must re-enrol.
Enrolment is per user. There is no setting yet to require 2FA of every member of a team; policy enforcement of this kind is planned as part of SAML support for corporate teams, and is on our roadmap.
Login protection
Repeated failed login attempts are rate limited, both per account and across the originating network, so brute-forcing a password by guesswork isn't a practical attack against Loco. The per-account lockout schedule is published in our help pages; network level thresholds are not.
Failed attempts against an address that isn't registered with us accumulate the same lockout state, with the same messages and timings, so login responses can't be used to find out who our customers are.
Password reset and one-time passcode requests are rate limited separately, per email address and per /24 network, over tiered time windows. Rejections return HTTP 429 with a Retry-After header.
API keys
A full access API key allows anyone in possession of it to modify and delete your translations. Stolen keys are worthless outside of the Loco API, but they still shouldn't fall into the wrong hands. For this reason we don't store writeable keys on our servers. Once a generated key appears on your screen, that's the last time we'll see it and it cannot be recovered.
Export keys are an exception. These read-only keys are generated automatically when you download translation files from the Loco interface. Export keys are stored in a recoverable form on purpose (so we can generate export URLs) but this key type cannot modify or delete anything.
Secret keys are held only as a digest — currently SHA-256 — computed over the key and a server-side pepper. As with passwords, we may upgrade this from time to time, but its strength lies in the wide range of random keys rather than the hashing algorithm.
All keys are 24 bytes taken from a cryptographic random source, giving 256^24 possible combinations — approximately 6.3 × 10^57.
This is an example key encoded as plain text characters: pvfPG0zYZUkbbM5RXS2QOgAKN4U6r9H8b
24 random bytes is 192 bits of entropy, so the ~6.3 × 10^57 combinations above is the actual search space an attacker faces. Even at an implausibly generous 200 billion digests computed per second, exhausting that space would take on the order of 10^39 years. The live keys in our database are a negligible fraction of that space, so testing candidates against all of them at once does not bring the figure into any practical range.
For identification purposes, the interface displays a short masked form of a key (e.g. pv∗∗∗∗∗∗8b), revealing 3 of the unencoded 24 bytes. This narrows the search space to 256^21 ≈ 3.7 × 10^50 combinations — still on the order of 10^31 years to exhaust at the same generous guess rate.
Browser security headers
Every page served by Loco carries a set of HTTP response headers that instruct your browser to lock down what a page is allowed to do, even in the event that some other part of our defences were to fail:
- Content-Security-Policy — script execution is restricted to a narrow list of origins (our domains + trusted vendors) plus specific inline snippets approved by a SHA-256 hash; no other inline script can run.
- Strict-Transport-Security (HSTS) — tells your browser to only ever talk to Loco over HTTPS, even if you type a plain
http://address. - X-Frame-Options — prevents our pages being embedded in a frame on someone else's site (a "click-jacking" defence).
- X-Content-Type-Options — stops browsers from guessing (or "sniffing") a file's type, which closes off a class of content-type confusion attacks.
- Referrer-Policy — limits how much of a Loco URL is leaked to third-party sites when you click a link away from us.
Data in transit (SSL certificates)
The Loco website and API are only available over SSL. (Actually it's TLS only). We've disabled various features known to be weak or vulnerable, and have a score of A+ on Qualsys SSL labs.
From time to time we may change our certificate. It's currently provided by Sectigo and expires on 3rd March 2027.
Data at rest (Disk encryption)
It's important to note that although security between your data and the web is strong, our disks are NOT encrypted at rest. Disk encryption may be implemented in future, but currently it would make our servers incompatible with our hosting provider's backup system.
Please note that no credit card numbers are stored on our disks, not even temporarily. See card transactions below.
Servers and databases
Our main servers are hosted with Linode and located in the UK. You can read about their physical security here. Take particular note of the high level of certification awarded to their London data centre.
Our back end database and application servers are on separate hardware from our front end web servers and have no Internet-facing services except on port 22. (see next paragraph). Access to database servers from application servers requires a strong password in addition to firewall access over the local network.
None of our servers can be accessed over SSH without a certificate. SSH access using a password is disabled, and SSH login by the root user is completely disabled.
In addition to our main servers in the UK, we also use Amazon S3 to store uploaded files and backups. These buckets are located in Ireland. All files stored on S3 are protected from public access with authorized access being granted via AWS Signature Version 4, and have server-side encryption (SSE-S3) enabled.
Card transactions
Our credit and debit card payment provider is Stripe, which means that we don't have access to card numbers. All card transactions are performed using Stripe's pre-authorized tokens. Our access to the Stripe dashboard is protected by two-factor authentication. Even when logged in to Stripe, we still don't have access to card numbers. Please see our privacy policy for more information on payment data storage.
We generate card tokens using the Stripe.js v2 API. Although this all takes place over SSL this API is no longer automatically PCI compliant. We are working on upgrading our systems to use newer Stripe APIs and/or provide a PCI self-attestation document (SAQ A).
Third party security
The full list of our third party providers is available on our GDPR Compliance page under Third Party Processors.
Access to all third party provider accounts is protected by MFA via an authenticator app (TOTP).
Bounty hunters
Loco does not operate a bug bounty programme.
If you've not been invited to test our systems for vulnerabilities then please refrain from doing so. Users or IP addresses that appear to be probing our servers, (or using our services in any suspicious or potentially harmful manner) tend to be blocked.
Feel free to tell us about any flaws or bugs you discover, but no remuneration will be forthcoming unless arranged prior to your commencement of any work resulting in such findings.
A machine-readable version of this contact information is also available at /.well-known/security.txt.
Security roadmap
We are currently conducting a security audit to better provide assurances to procurement and risk assessment teams. The items below are current WIP:
- Encryption at rest - We are preparing to migrate all our VPS nodes and backup volumes to encrypted storage at Linode.
- We are investigating PCI compliance for our usage of Stripe which currently uses their V2 API.
- We plan to support SAML for corporate teams, so organisations can enforce their own authentication policies separately from the consumer-facing self-service features.