HSH Design
Security

Your artwork, your customer list, your invoice history.

A print shop's files are commercially sensitive, and handing them to somebody else's software is a real decision. This page is what we would want to read before making it.

Everything below describes a mechanism that exists in the software today, with the actual figure it uses. We have written it this way rather than as a list of adjectives because “enterprise-grade” and “bank-level” are not claims anyone can check, and the specific ones are.

Equally, there are things we do not claim. We hold no card details, because the platform issues invoices and does not take payments. Your customers have no accounts, because approving a proof never creates one. And we have not been through a formal external audit — if that is a requirement for you, say so early and we will tell you straight away whether we can meet it.

Signing in

01 / 04
Two-factor by email, on every account

Signing in from a new device sends a six-digit code to the address on the account. The code is stored hashed, never in plain text, and it is useless once used.

6 digits · expires in 10 minutes · 5 wrong tries
Devices you already trust stay trusted

Confirm a device once and it stops asking for a code on that browser. The token behind it is random, held server-side against one user, and expires on its own.

45 days · http-only cookie
Guessing is throttled, per account

Failed sign-in attempts are counted against the identifier being tried, not the whole system, so someone hammering one account cannot lock everyone else out and cannot keep guessing either.

8 failed attempts per 15 minutes
Passwords are hashed, and quietly upgraded

Stored with bcrypt. When the standard moves on, an older hash is re-hashed the next time that person signs in successfully — nobody has to be told to reset anything.

bcrypt · rehash on next sign-in
Resets are single use

A reset link carries a token that is stored hashed and consumed the moment it sets a new password. Sending yourself a second link invalidates the first.

One use, then dead
The session changes when your access does

The session identifier is regenerated at sign-in and again whenever an account's privileges change, so a session captured before a change cannot be replayed after one.

Regenerated on privilege change

Keeping shops apart

02 / 04
Separation happens in the query, not the page

Every listing is scoped to the company asking for it in the SQL itself. A page is not fetching everything and then hiding the rows you should not see — the rows never leave the database.

Enforced at the query layer
Access is three things, not one flag

A login has a scope (shared team, named staff, or a client), a role within it, and separate switches for the print and social sides of the platform. A print-only staffer does not get a social page rendered and gets refused on a direct call to a social endpoint.

Scope · role · per-track access
Client assignment fails closed

Staff scoped to particular clients see those clients and nothing else. If someone has no assignments recorded, that means no access — not, as the easier implementation would have it, access to everything.

No assignment means no access
There is a record of who did what

Actions are written to an activity log, with owner-only and internal-only entries kept separate so a client login cannot read your staff's trail or your commercial notes.

Owner-only and internal-only entries

The link your customer opens

03 / 04
No account, and that is the safe option

Your customer approving a proof never creates a login. That is deliberate: an account we do not create is a password we cannot leak, and it removes the single most common reason a proof sits unapproved for a week.

No password to lose
The link itself is the key, and it is not guessable

Each approval link carries a token of sixteen random bytes. There is no sequence to walk and no list to enumerate — a wrong token matches nothing at all.

16 random bytes · 128 bits
One link, one proof

The endpoint behind an approval link returns only the fields needed to render the one proof the token names. It never returns a list, another customer's job, or anything about your shop beyond the branding on the page.

Never a list
Client pages stay out of search engines

The approval page, the client portal, the sign-in page and the proof endpoint all tell crawlers not to index them, and they are left crawlable so that instruction is actually read rather than blocked at the door.

noindex on every client-facing page

Files, secrets and time

04 / 04
An uploaded file cannot be run as a program

Every folder that receives an upload carries a rule denying execution of anything script-shaped. The rule is re-written on every single request, not just at install — so a folder that loses it gets it straight back rather than sitting exposed until someone notices.

Re-asserted on every request · 9 folders
The free tools have nowhere to upload to

The five prepress tools on this site have no upload endpoint in the code at all. Your artwork is read by your own browser and discarded when the tab closes. That is a fact about how they are built, not a promise about how we behave.

No endpoint exists
Connected accounts are encrypted at rest

When you connect a Facebook or Instagram account, the access token is encrypted before it is stored, each with its own initialisation vector.

Encrypted, per-record IV
Timestamps mean one thing

The database is pinned to UTC and every time is converted in the viewer's own browser. An approval timestamped at sign-off reads correctly whether it is being checked from Islamabad, Dubai or Manchester — which matters when the timestamp is the evidence.

UTC stored · converted per viewer

Something here you need to dig into before you can commit?

Ask. We would rather answer it now than have it come up after you have moved your jobs across.

Ask a question