← Home

Technology and security

What runs underneath the systems we deliver, and what we do not do.

Every stack choice comes with the reason behind it. Every security claim comes from code that is running in production. And there is a whole section stating where we stop.

The default stack, and why each piece is there

Each piece is here for the reason written next to it.

It is the stack a small team can maintain, that the next developer can read, and that locks nobody into a tool of ours.

Application

TypeScript, Next.js, React and Tailwind

One language, from screen to server. Types catch in the editor the bug the client would find at the till. Server rendering, because the first screen has to appear on mobile data at a building site rather than on office wi-fi. Styling lives in Tailwind, next to the component, with no parallel stylesheet growing on the side.

Database

PostgreSQL, and SQLite where it fits

Postgres when there is a server: transactions, constraints inside the database itself and money calculated in decimal, never floating point. SQLite in WAL mode when the system runs inside the venue, or when the service is too small to justify another moving part that can fail.

Schema and migrations

Prisma

A structural change is a versioned file in the repository, not a command typed straight into production. Additive migrations with a default on every new column: old rows stay valid and whatever was already writing keeps writing.

Infrastructure

Docker and Traefik

Every service in a container: taking one down does not take the others with it. Traefik handles the TLS certificate that renews itself, domain routing and rate limiting before the application. The same image runs on your company’s server or on ours.

Working with no signal

PWA, IndexedDB and a local queue

The app installs on the phone without going through a store. When the signal drops, the change goes into a local queue and uploads itself on reconnect, with a device-generated identifier so the same submission never lands twice.

Payments

Stripe

Card details never touch our servers. The gateway takes the payment, the webhook arrives signed and is re-checked at source, and stock is only decremented once payment is approved.

Testing and integration

Playwright

It tests the screen the way a user uses it. It also drives the legacy system that has no API at all, always through a queue, so it never becomes a script that misfires at three in the morning.

Where it is the better tool

Python

Computer vision, images and audio. Face detection performed on the device itself, with the photo never sent anywhere, is Python with a specialised model, a job a language model does not do well.

Visual output as code

Remotion, three.js and rendered HTML

When the output is artwork or video, the source is still code. Change a date or a colour and the whole batch is regenerated. No step ever passes through a graphics editor.

Process with AI

The /agent-team skill, open on GitHub

The skill we use is published: agents by role, planning, architecture, UX, QA, security, red team, documentation, each in an isolated worktree, with a shared board. It is at github.com/Riicke/SkillAgentTeam and you can read the code before hiring us.

Reference architecture of a typical system

The path of one request, from the screen to the database and back.

  1. 1. The user’s device

    A browser or an app installed on the phone. It holds what is needed to keep working with no signal and keeps a queue of pending changes until the network returns.

  2. 2. The edge

    Traefik terminates TLS with an automatically renewed certificate, routes by domain and applies the rate limit. This is where a flood dies, before it ever touches the application.

  3. 3. The application

    Next.js with the session in a signed cookie. No screen and no server action trusts only what the previous layer checked, how that is done in detail is in the security section.

  4. 4. The business rules

    Where the price is recalculated, the instalment is split in decimal and an oversell is refused. None of it trusts the value that came from the browser.

  5. 5. The database

    PostgreSQL or SQLite, versioned schema, constraints in the database itself and sensitive fields encrypted at rest. The database is the last line of defence for the business rule.

  6. 6. Queue and workers

    A queue with a worker for whatever is slow or can fail: ERP integration, email, the legacy-portal robot, mirroring to the admin panel. If it fails it retries, and nobody’s screen freezes waiting.

  7. 7. Outside the critical path

    Backups, logs and lead capture sit deliberately outside the main flow. The public form keeps saving even while the admin panel is being deployed.

Where the system is hosted

And what changes when the data has to stay in Brazil or in Europe.

System with a database, operating in BrazilA VPS, yours or oursThe bespoke system goes up on a machine contracted in your company’s name, and we come in as administrators. When a client would rather not manage a server, we host it on ours, and that is written into the contract. Either way the setup is the same: Docker and Traefik, a closed ufw firewall, fail2ban on SSH and Let’s Encrypt TLS.
Data that must stay on Brazilian soilThe provider’s Brazilian regionThe same Docker image goes up on a machine contracted in that region. The address and the latency change. The code does not, and neither does the deployment process.
Data belonging to UK or EU residentsInstance and backup in the same regionThis is what we set up when a project requires data residency in the region: the backup stays where the database is, and the list of who has access is written down. Leaving the region becomes a recorded decision, with the reason written into the project.
An operation that cannot depend on the internetA server inside the venue itselfThe restaurant point of sale runs on the local network: order, ticket and kitchen in real time with no cloud in the path. The dining-room data never leaves the dining room; the internet is only there to update the software.
Corporate site or landing pageStatic export on ordinary hostingNo database and no application server. There is no admin to break into and no customer base to leak, because there is no base.
A client who already has infrastructureOn the client’s own serverWe deploy there, with the process documented step by step, and the system needs no proprietary tool of ours in order to run.

Security: what is implemented, by name

Every item below exists in code that is live.

The old site said "AES-256 encryption, the same as Google Cloud". That means nothing. Here is what does.

Edge

TLS and security headers

A Let’s Encrypt certificate renewed automatically by Traefik, with HTTP redirected to HTTPS. Restrictive content policy, frame denial, nosniff and referrer policy, reapplied in every configuration block, because nginx headers are not inherited.

Data

Encryption at rest

National ID numbers encrypted inside the database. Third-party access tokens stored with envelope encryption: one key encrypts the key that encrypts the data, so a copy of the database never becomes access to the client’s account.

Authentication

Sessions

A cookie signed with HMAC-SHA256, compared in constant time, httpOnly and scoped to the site. The secret is mandatory in production: without it the application refuses to start, with no fallback to a development value. And the token carries a fingerprint of the credential, changing the password instantly drops every old session on every device, with no session table in the database.

Authorisation

Three-layer access control

A middleware guard, per-request revalidation of the user and a check inside every server action. A user who is deleted, blocked or whose credential changed drops to logout on the next request, not at the next sign-in.

Brute force

Login attempt limits

Login and password recovery are rate-limited per email address and per IP, with a single generic error that never reveals whether an address exists. Single-use reset links, valid for 30 minutes and invalidated if the password changes.

Availability

Rate limiting at the edge

We counted the files the homepage requests, allowed for visitors on mobile behind carrier NAT, and picked thresholds that stopped a load test of 654 requests per second without stopping the legitimate traffic simulated in the same test. The public capture service accepts 10 requests per minute per IP.

Infrastructure

The server

A closed ufw firewall and fail2ban on SSH, installed after we counted 346 brute-force attempts in seven days in the log. Multi-stage containers running as a non-root user. The mail service on an internal network, with no port exposed on the host.

Secrets

Secrets kept out of the bundle

No secret ever ships in the bundle sent to the browser. Environment variables on the server, and the Stripe webhook verified by signature. In the system distributed as an installer, the session secret is generated on first boot on the client’s machine with restricted permissions, the executable leaves here with nothing inside it.

Validation

Untrusted input

Schemas validated at the application boundary. Price and shipping recalculated on the server, because a value arriving from the browser is not data, it is a suggestion. Uploads restricted to the administrator, with an allowlist of types, a size cap and a randomised filename.

Trail

Auditing and backups

Every sign-in records date, user and device, and every conflict resolved during offline sync is written down. The database backup leaves the machine that produced it; frequency and retention are agreed per project, and restoring is tested, a backup never restored is just a file.

Supply chain

Dependencies

Versions pinned in a lockfile, updates reviewed before they ship, and a deliberately small surface: one of the landing pages delivered has zero third-party JavaScript libraries, only the typeface.

Process

A checklist that travels

We keep a 24 KB SECURITY.md that moves from project to project, recording what was checked, what was left out and why. You can ask to read the one for your project.

What we do not do

The limits, stated, and what each one means in practice.

A supplier who lists only what it does is asking you to take its word for it. Here is where we stop.

In practice

We are not ISO 27001 or SOC 2 certified.

If your compliance team requires a certified supplier, we do not clear that bar. What we hand your auditors is the project’s SECURITY.md, the record of architecture decisions and access to the code.

In practice

We do not run a formal penetration test.

We test what we wrote, run automated checks on headers, TLS, redirects and exposed files, and put the code through an adversarial review inside our own process. That is not a signed report from a penetration-testing firm. If your contract requires one, commission it, we will fix the findings.

In practice

We do not run a 24-hour support desk.

There is nobody on call at three in the morning. We agree the support window and the response time in writing, and automate what can be automated: health checks, container restarts and alerts.

In practice

We do not promise a numerical uptime.

We do not sell a 99.9% SLA. What goes into the contract is the support window, the agreed response time and the list of what is monitored automatically. An availability figure we cannot guarantee on our own is one we will not sign.

In practice

We do not take on someone else’s codebase without a paid audit first.

Inheriting a system written by someone else begins with a short, paid audit: what exists, what is broken, what can be kept. Only after that do we say whether we take it on, and for how much. Without that step any date would be a guess, and the bill would land in your lap.

In practice

We do not do branding, graphic design or paid media.

We do not create visual identities, design logos or run advertising campaigns. We build the system and the page that converts, applying the brand that already exists. If you still need the brand, hire someone who does that, we are happy to work alongside them.

In practice

We do not store card details.

Payments go through a gateway. The card number never passes through our servers, so there is no card data here to leak. We also do not issue PCI compliance, because we are not the ones processing it.

In practice

We are not your company’s data protection officer.

We make the architecture meet Brazilian and UK data protection law, and we document what the system collects, for how long and who can see it. The policy, the contract with data subjects and the officer role belong to the company, and your legal team signs them off.

In practice

We are not a large team.

Engineering is one developer. The schedule runs in sequence, not in parallel. And the right question to ask is what happens if H&I disappears: the code sits in your repository, the deployment is documented, and nothing depends on a proprietary tool of ours.

In practice

We do not ship code we have not read.

We use AI in the process, by role, with a record of what each one did. It speeds up review, testing and documentation. It does not decide architecture and it does not replace reading the diff, whoever signs off what went live has a name and a phone number.

In practice

We do not quote a price or a date before seeing the operation.

A fixed figure given without looking at the current system is a guess, and a guess becomes a delay halfway through. First the diagnosis and a written scope; then the number.

Privacy by design

Brazilian and UK data protection settled in the schema, not on a policy page.

The privacy policy is the document. The decision happens before it, when you choose which column is going to exist.

What is never collected

Minimisation

A field that will not be used never reaches the form. In an image generator we delivered, the person’s photo is processed inside the browser and never leaves the device, and the page’s content policy technically blocks the connection that would upload it. The promise is enforced by the browser, and you can check it in the page’s own headers.

What stays encrypted

Sensitive data kept apart

Anything sensitive is encrypted at rest and never appears in full on screen. Integration keys entered by the client sit in a table with row-level access policies in the database itself, and the panel only reads a masked view returning "configured" plus the last four digits.

How it is served

Right of access

An endpoint that hands each person their own data as JSON. An employee exports what the company holds about them without opening a ticket with anyone.

Decided before the screen exists

Right to erasure

Erasure is designed into the system: what is deleted, what is anonymised and what remains. A record with accounting or legal effect does not vanish, it loses its link to the person and keeps the history. That is written down before any screen exists.

A deadline per table

Retention

Every table holding personal data has a retention period agreed in the project, and access logs are the first to expire. When it can be avoided, nothing is stored: a usage counter we wrote saves one integer and nothing else, no IP address, no cookie, no log.

Where data crosses a border

International transfer

When a project requires the data subject’s data to stay in the UK or the EU, the instance and the backup stay in that region, and anything leaving it becomes a decision recorded in the project, with the reason written down.

Who else receives what

Third parties and AI models

Every external service goes on a list with what it receives: payment gateway, email delivery, language model. And what reaches the model is chosen field by field, in a finance assistant we maintain, the money figures never go back to the model: the answer comes from a deterministic layer reading the database directly.

Bring your technical team to the conversation

A 30-minute diagnostic, free and with no obligation: you leave with a written summary of your problem, and it is yours even if we never work together. Tell us three things: where the data has to live, what is already running today and what cannot stop even for an hour.

Book the diagnostic