Skiplink is in pre-release: the hosted API is still being deployed, so scanning and sign-up are not live yet. The docs and the reference are complete.

Skiplink

Security

Skiplink loads pages that strangers control, in a browser, on our infrastructure. The design starts from the assumption that one of those pages will eventually win.

Reporting a vulnerability

Email security@skiplink.dev. Include enough for us to reproduce it. You will get a human acknowledgement within 2 working days and an assessment within 5.

Safe harbour

If you research in good faith, stay within your own accounts and test data, avoid degrading the service for anyone else, do not access or exfiltrate other people's data, and give us reasonable time before publishing, then: we will not pursue legal action, we will not report you, and we will credit you if you would like us to.

If you accidentally reach data that is not yours, stop, tell us, and delete it. That is a finding, not an offence, and we will treat it as one.

We have no bug bounty budget yet. We will say so honestly rather than imply a reward that does not exist.

Out of scope: reports generated only by a scanner with no demonstrated impact, missing headers with no exploit path, social engineering of our staff, denial of service, and anything requiring a compromised device belonging to the victim.

The parts of the design that are security decisions

Separate services, and the split is the point

The API holds the database credentials, the queue and the payment keys, and never fetches a customer-supplied URL. A separate service runs the browsers, and holds no database or payment credentials — no database access, no API keys, nothing but the job it was handed. Every request its browser makes passes through an egress guard that refuses private address space.

So the machine most likely to be attacked is the one with nothing on it, and everything it knows arrives in the job and leaves in the result.

The scanning browser runs without Chromium's own sandbox

Chromium normally confines each page it renders inside a sandbox. Our hosting provider's container runtime does not allow that sandbox to start, so on our hosting the scanning browser runs without it. We would rather say that here than have you find it. It means code that exploits a bug in the browser runs inside the scanning service itself, not inside a further box within it. That service was built on the assumption that this happens:

Turning the sandbox off is an explicit setting on that one service. Anywhere the sandbox can start, it is on, and if it is expected and fails, the service stops rather than carrying on without it.

Two independent layers against server-side request forgery

A scanning API is an arbitrary server-side fetch, which is the most abusable primitive a service can offer. Every URL is resolved before it is fetched and refused if any answer points at loopback, RFC1918, link-local, CGNAT, unique-local, multicast or a cloud metadata address — including via IPv6 transition addresses, and re-checked at every hop of a redirect chain rather than only on the first request.

That layer cannot be complete: DNS rebinding beats any check made before the connection, because the name can resolve differently between the check and the connection. So the second layer sits where the connection is made: the browser's only route to the network is a guard that resolves each name itself, refuses private and internal answers, and connects to exactly the address it checked — one answer for both, so there is nothing to rebind.

Secrets are stored as hashes

Passwords are argon2id. API keys, session tokens, email verification tokens and password reset tokens are stored as hashes with an indexed prefix for lookup. A dump of our database does not yield a working credential.

Backups that have been restored

Nightly, and each one is restored into a scratch database with its rows counted before it counts as a backup. The copy kept off-site is encrypted before it leaves our hosting. This caught a real problem the first time it ran, which is the argument for doing it.

Content Security Policy on the website

default-src 'none', then only what the site uses. No CDN, no external fonts, no analytics — a tracker added by accident would fail to load. Inline scripts are allowed by SHA-256 hash rather than by 'unsafe-inline', and a script that checks every page under the real policy in a real browser runs before release, because a wrong hash silently breaks the page rather than warning.

What we do not have

No ISO 27001, no SOC 2, no penetration test report. We are small, and those cost money we have not yet made. If your procurement requires one, we will tell you we do not have it rather than gesture at a roadmap.

There is no bug bounty. There is no 24/7 on-call rota. There is one person, who reads security@skiplink.dev.

If something happens

We will tell affected customers within 48 hours of becoming aware of a breach involving their data, with what we know at the time, including the parts we do not yet understand. Regulator notification follows the statutory 72 hours where it applies. The commitment is in the data processing addendum, which makes it contractual rather than a good intention.