CUTLIST
N°00Straight answers

Security.The mechanisms, and the gaps.

Most security pages are a wall of badges and a paragraph about how seriously we take your trust. This one names the code. Every claim in the first half points at something implemented today. The second half is what is missing - written down here rather than discovered by you later.

Mechanism or nothing

If a sentence on this page does not name the thing that implements it, it is not a security claim, it is a mood.

404, not 403

Unauthenticated and cross-tenant requests get a not-found. Probing cannot confirm that a route, a job or an account exists.

Nothing to countersign

No SOC 2, no ISO 27001, no BAA, no pen test, no two-factor. That is section 09, on this page rather than left off it.

01Passwords

Never stored, only derived

A password you type here is turned into a scrypt key and thrown away. What sits in the database is a salt and a derived key, which is not a password and cannot be turned back into one.

scrypt, per-user salt

16 random bytes of salt per account, a 64-byte derived key, stored as salt:key. Two people with the same password get different rows.

Constant-time compare

Verification derives the key again and compares with timingSafeEqual, so how long a wrong password takes tells an attacker nothing about how close it was.

A missing account costs the same as a wrong password

Sign-in runs the scrypt work even when no such user exists, against a placeholder hash, and returns one message for both cases. Response timing cannot be used to enumerate who has an account.

Sign-up refuses without naming the reason

An address that already exists is turned away with "that email cannot be used", never "that email is taken". Weaker than the sign-in path, and we will not dress it up: someone determined can still learn an account exists by trying to create it. Sign-in is where it matters, and sign-in gives nothing away.

Ten characters, and something you do not reuse

Enforced at sign-up. That is the whole rule - no composition theatre, no forced rotation.

Length is the only requirement, there is no second factor, and nothing throttles a guess. scrypt makes every attempt cost real CPU, which is a price rather than a lock. Until two-factor and rate limiting ship, a long unique password is the entire defence on your account.

02Sessions

The cookie is a pointer, not a claim

A signed-in browser holds 32 random bytes and nothing else. No encoded user id, no expiry it could edit, no signature to forge. The session itself lives in our database, which is what makes signing out mean something.

Random token, hashed at rest

The cookie value is randomBytes(32); the row stores only its sha256. A leaked database dump does not hand anyone a working session.

httpOnly, sameSite lax, secure in production

Script on the page cannot read it, a cross-site form post cannot ride it, and it does not travel over plain http in production.

Sign out actually revokes

The row is deleted. Compare that with a self-contained token, where signing out means asking a cookie you can no longer see to please stop being valid.

Expiry is enforced on read

Sessions run 30 days. One found past its expiry is deleted on the spot and treated as absent, not merely refused.

03Tenant isolation

Another account's id looks like nothing

Every read and every write is scoped by the calling project, in the query itself rather than in a check after the fact. There is no code path that loads a row first and asks who owns it second.

Scoped at the where clause

A job is fetched with { id, projectId }. Guessing a valid id from another account returns exactly what a made-up id returns.

404, not 403

Unauthenticated and cross-tenant requests get a not-found. A 403 confirms the thing exists, which is half of what an attacker wanted.

Storage keys cannot walk out of the root

Keys are pattern-checked, then the resolved absolute path must still sit inside the storage directory. The pattern check is the one that gets outsmarted, so it is not the one we rely on.

An uploaded source has to prove it is ours

The browser uploads a file, gets a URL back and hands that URL to the job. It arrives from the client, so it is treated as hostile: same origin, under /f/, and a key that survives validation, or it is not an upload.

The studio never touches an API key

Browser uploads authenticate with the session cookie. The project key stays server-side and never reaches client JavaScript.

Isolation ends at the row, not at the file. A rendered clip is served from an unguessable key under /f/ with no session check, because that is how a video tag plays it and how your webhook consumer downloads it. Anyone holding the link holds the file. Treat a clip URL as the secret it is - short-lived signed URLs are on the list at the bottom of this page.

04The SSRF guard

Two URLs, checked twice

You give us a source to fetch and, if you are a partner, a callback to POST. Both are fetched by our own infrastructure, which sits inside a private network. Without a host check, either one is a port scanner pointed at us, using error text and response timing as the readout.

Blocked before the job is queued

Loopback, RFC1918, carrier-grade NAT, link-local (which is where cloud metadata lives at 169.254.169.254), multicast and reserved space. Plus localhost, metadata.google.internal, and anything ending .local, .internal or .home.arpa.

http and https only, no embedded credentials

file://, gopher:// and data: are refused outright, and a URL carrying user:pass is refused before anything resolves it.

A bare name is an intranet name

A hostname with no dot in it means something inside the network, not something on the internet. Refused.

Re-resolved at the worker, immediately before connecting

The control-plane check catches literals. The worker resolves the name again at connect time and every answer must be public - a name with one private A record is still a way in.

Redirects are checked too

A public URL that 302s to 169.254.169.254 is the oldest version of this attack. Each hop goes through the same check.

DNS rebinding between that final lookup and the connect itself is a residual risk. Only a client that resolves and then connects by IP removes it, and ours does not do that yet.

05Webhooks

Signed, so you can refuse the rest

When a job finishes we POST to your endpoint. Your endpoint is on the internet, so anyone can POST to it. The signature is what separates us from anyone.

HMAC-SHA256 over the exact body

X-Cutlist-Signature: sha256=... computed with your project's own secret. Compute it over the raw body you received and compare in constant time. If it does not match, it was not us.

A delivery id you can deduplicate on

X-Cutlist-Delivery carries the job id, so a repeat delivery is recognisable rather than a second copy of the work.

https, and never aimed back at us

In production a callback URL has to be https, and it runs through the same host guard a source does. You cannot point a callback at our internals and read the answer off the delivery.

Redirects are not followed

redirect: manual. A 3xx from a webhook endpoint is a misconfiguration, not a delivery target.

A broken endpoint cannot fail a finished job

Delivery has a 10-second timeout and never throws. The render already succeeded, and GET /api/v1/jobs/:id returns the identical payload whenever you ask for it.

There is no automatic retry and no delivery log yet. If your endpoint was down, poll the job - the payload is the same one the webhook would have carried.

06Partner API keys

Shown once, stored as a hash

A partner project gets one key at creation. We keep the sha256 of it and a short non-secret prefix for display, which means we cannot tell you what your key is - only whether the one you just sent matches.

clk_live_ plus 24 random bytes

Generated with randomBytes and base64url-encoded. The prefix, clk_live_ and four characters, is what the interface shows you so you can tell two keys apart.

Authentication is an indexed lookup

The bearer token is hashed and looked up by hash. Constant work, no scan, no compare loop over stored secrets.

The worker token is compared on digests

Both sides are hashed before timingSafeEqual, so the compared buffers are always the same length. A raw compare would need a length check first, and that check leaks the expected token's length through timing.

A key opens the API and nothing else

A partner project is a key with no person behind it. Presenting a key authenticates a project against /api/v1 - it never mints a session, and there is no studio to walk into.

One key per project, no scopes, and no rotation without us doing it by hand. If a key is exposed, mail us and we will cut a new one.

07What we keep

Everything has a clock on it

Clips are working output, not an archive. Holding your footage forever would cost us money and make us a target, so nothing here is open-ended. The numbers below are the ones the code enforces.

Your account

An email address, a name, and a scrypt hash. That is the whole record of you.

Source uploads

Removed within 2 days of a finished render. Once the cut exists there is no reason to keep the original.

Clips, posters and SRT files

30 days, then swept. The studio counts each clip down to its expiry so nothing vanishes unannounced.

Transcripts and word timings

Stored on the clip row, because that is what makes text editing work. They go when the clip goes.

Sessions

One row per signed-in browser, 30 days, deleted the moment you sign out.

Credit ledger

Append-only, every movement recorded with the job that caused it. A balance you cannot explain is a support ticket.

Storage total

100 GB per account, shared across every project you own.

What leaves our infrastructure

Sampled frames, transcript text and on-screen text go to the model that reviews them. Your clips go to your webhook if you configured one. Nothing goes anywhere else, and nothing is sold.

The credit ledger is append-only on purpose, so deleting a job still leaves the line saying it was billed. That is the one record that outlives the work it describes.

08Getting your data out and gone

Delete means the file, not the row

Plenty of products mark a record deleted and leave the media sitting in a bucket. The order matters here: files first, quota returned, rows last - because once the clip rows are gone we no longer know what to delete.

Delete a job

From the studio. Every video, poster and SRT is unlinked from disk, your storage allowance is given back, and the rows drop after. Clips cascade with the job.

Cancel a running job

The held credits are refunded and the job stops. A cancel is not a silent charge.

The expiry sweep

Runs on a schedule, claims each row by deleting it before touching its files, and is safe to run twice at once. It is authenticated with a secret, and a wrong secret gets the same 404 as everything else here.

The whole account

Mail us and we remove the account, its projects, jobs, clips and files. We answer within two working days and tell you when it is done. There is no self-serve button yet, which is why it is on the list below.

09The part everyone leaves off

What we do not have

Everything above is code you could point at. Here is the other half. Cutlist is a young product and none of the following exists. If your procurement process needs any of it, we are not your vendor yet - and you should know that now rather than three weeks into a questionnaire.

SOC 2

No Type I, no Type II. We are not audited, and an unaudited badge is just a picture.

ISO 27001

Not certified. Not in progress.

HIPAA and a BAA

We will not sign one. Do not put protected health information through this product.

Rate limiting

None. Sign-in and the API take requests as fast as you can send them. scrypt puts a real CPU price on every password guess, which slows an attacker down without ever stopping one.

Two-factor authentication

A password and a server-side session, and that is all there is between someone and your account.

A penetration test report

None yet. The code has been read carefully by the people who wrote it, which is a different and much weaker thing.

A bug bounty programme

No payouts. The reporting route below is real and answered; the cheque does not exist.

SSO, roles and audit logs

No SAML, no per-seat trail, no shared workspaces. One account, one set of projects.

Encryption at rest

Artifacts sit on disk inside our infrastructure. We do not run application-level encryption over them, and claiming otherwise would be the easiest lie on this page.

A wall of certifications nobody checked tells you only what a vendor is willing to imply. This list will get shorter. It will not get quieter.

10Found something

Reporting a vulnerability

One address, read by a person who can fix the thing. No form, no portal, no triage queue that eats your report and returns a reference number.

Where to send it

hello@heckraiser.com with “security” in the subject. Include the URL or endpoint, what you sent, and what came back. A short proof of concept is worth more than a scanner export.

What we commit to

We acknowledge within two working days, reply as a person rather than a template, tell you what we found and tell you when it is fixed. Credit in the changelog if you want it, silence if you do not.

Good faith, no lawyers

Research in good faith against your own account is welcome and we will not come after you for it. Stop as soon as you have proof, and tell us before you tell anyone else.

Out of bounds

Do not touch other people’s accounts or footage, do not run denial of service, do not social-engineer anyone. And to say it before you spend the evening: we do not pay bounties today.

11In roughly this order

What we are working on next

Two-factor sign-in

The largest single gap on this page, and the first one closing.

Rate limiting and lockout

So a password guess costs an attacker time as well as CPU.

Signed artifact URLs

Short-lived, per object, so a clip link stops working when it should.

Self-serve account deletion

A button, so the deletion path above stops being an email.

Webhook retry and a delivery log

Backoff on failure, and a record you can look at.

Multiple keys and rotation

Issue a second key, move traffic, revoke the first. No downtime.

An independent penetration test

Before we take anyone's enterprise money, not after.

No dates. A date on this list would be the invented part.

You read the gaps and you are still here. Upload half an hour of footage on the trial and judge the cut the same way you just judged the page.

Found a hole? hello@heckraiser.com