Skip to content
megapromotingLet's talk

Expertise · Cybersecurity

The security of the applications we build and operate — controls written in code, externally verifiable, without certifications we do not have.

The work covers application security and privacy engineering: headers and content policy, rate limiting, the default closed position of checks, secret encryption, logs that cannot be rewritten, data anonymisation and retention periods set in code, not only in policy. We are not certified auditors and we do not issue certificates.

Already builtI chose “delivered” for a deliberately narrow domain, and we write the limit before the list of capabilities. The controls described below run on in-house systems and some can already be verified from outside right now: rate limiting is demonstrated with twelve consecutive requests (the tenth gets 429, verified in production on 06.09.2026), the health probe returns only logical values, and the security headers are read from the response. The second and third cited implementations are the reporting platform — content policy with a single value per response, whitelist of network destinations verified in continuous integration, mandatory two-step authentication for staff — and the electronic signature client written without any new dependencies, with defence against XML signature wrapping. What the evidence does NOT support: we have no certification, we do not provide offensive testing as a service, we do not operate a monitoring centre, and we do not issue compliance certificates.

Most of the issues we found in our own systems were not sophisticated exploits. They were defaults. A route that initiated a phone call to any number, without authentication, which no interface component had called for a long time. An origin check that passed when the list was empty. A full IP address sent in a notification message while the public page promised anonymisation. All three were written in our code, and none would have shown up in an automated scanner.

This is where the shape of the service comes from. We do not sell a scan report. We read the code with a precise question: when this check cannot decide, what does it do — allow or deny? The default position is the difference between a gate and a decoration. In our systems, the consumption gate returns “not allowed” on exception, and the phone call route responds 404 if the secret is not configured — that is, the function is off if someone has not deliberately turned it on.

The second half of the work is privacy engineering, because in practice the two are inseparable. The publicly stated retention period means nothing if it is not enforced by code. In our case, the request log deletes files older than the declared term, with the number of days taken from the same value that appears in the published policy. In another system, the retention table keeps, alongside each term, the published text word for word, and the legal basis — so that the policy and the database cannot silently diverge from one another.

What we publish about ourselves is part of the method. The content policy of this site allows `unsafe-inline` and `unsafe-eval` in scripts. It is a real weakness and we write it here, not in an internal report — it was even the mechanism by which our own accessibility audit could inject a verification tool directly into the production page. A provider that does not publish its own known weaknesses cannot find yours.

What it covers

The work, by component

We read the default position of every check

The central question of the analysis: when the check cannot decide, does it allow or deny? Real example, from our own code, marked as such: the allowlist of origins for a widget lets the check pass if the list is empty — a deliberate compatibility choice for legacy installations, written in a comment, but which means that domain restriction must be configured explicitly for each implementation. The counterexample: the consumption gate that returns “not allowed” on any exception.

Proper rate limiting behind a proxy

Ten requests per minute per address, with a subtlety that decides whether the limit works or not: the key is taken from the last hop of the forwarding header, not from the first. Our proxy adds the real address at the end of what the client sent, so the first entry is controlled by the attacker — whoever uses it as the key can exceed their limit by sending random values. It can be checked from outside: twelve consecutive requests give nine successful responses, then 429.

Headers and content policy, with a unique value per response where possible

On this site: strict transport for two years with subdomains and preload registration, non-inferable content type, restricted referrer policy, permissions policy that turns off camera and leaves microphone and location only for its own page. On the complaints platform, stricter: the content policy receives a unique value for each response, generated in the proxy, without third-party sources for scripts, connections or fonts, and framing is completely refused.

Allowlist of network destinations, automatically verified

In a public project, which addresses the application is allowed to contact is an allowlist verified by a command run in continuous integration, both statically and at runtime. A new package that starts phoning home fails verification, not production. It is the control that catches exactly the class of supply-chain incidents that nobody protects against with a vulnerability scanner.

Secrets do not end up in responses, logs or notifications

The contact form health probe returns only logical values — whether it is configured, whether the credential is valid, whether the log can be written — never the token, conversation identifier or bot name. The platform messaging channel tokens are kept encrypted. Alert logs pass through a personal data redaction module before writing.

Anonymisation applied before writing, not after

The IP address is truncated to the network prefix — /24 for IPv4, /48 for IPv6 — before entering the log or a notification. With two details that matter: the last hop is taken, not the first, for the same reason as with rate limiting; and IPv4 addresses reported in IPv6-mapped form are recognised and treated as IPv4, otherwise they would be kept in full, that is, exactly the opposite of the purpose.

Log that appends, not rewrites

Form submissions are written to an append-only log, one object per line, with 0600 permissions on the file and 0700 on the directory, before delivery is attempted. The reason is a real fault: when the notification channel went down, the endpoint returned 500 and every submission was lost without a trace. Now a broken channel means “we need to look in the file”, not “the request never existed”. The delivery result is written as the second line, with the same identifier.

Retention period enforced by code, not merely declared

The request log deletes files older than the publicly declared period, with the number of days taken from the same value. In another system, the retention table keeps next to each term the published text word for word and the legal basis, and the clean-up function reports by default what it would delete; actual deletion requires an explicit argument. Silent deletion is an error mode, not a function.

Identity and signature, when the project requires them

We have written, from scratch and without new dependencies, a service provider for federated authentication and a client for electronic signature. The XML parser does not process document type definitions, so the class of attacks on external entities does not apply; signature verification returns the node covered by the signature, not a boolean value, and the caller is obliged to compare object identity — the defence against signature wrapping. Canonicalisation was verified against the official specification vectors.

What it looks like

The path, step by step.

01

We define the scope and what we are not allowed to touch

In writing, before any command: which systems, which period, which types of verification are allowed and who is the contact person if something stops. We deliver: the signed scope document and the list of excluded systems.

02

We read the code and configuration, with emphasis on the default position

Checks that cannot decide, secrets that end up in responses or logs, routes that are no longer called by anyone but remain open, missing headers. We deliver: the findings with file and line, ordered by what can be done with them, not by theoretical severity.

03

We fix or describe the fix precisely

Where we have access, we put the control in place and write the test that keeps it there — a check that has no test is lost at the first refactor. Where we do not have access, we deliver the change described precisely enough for the client’s team to apply it without asking us again.

04

We link confidentiality to code

The retention period, anonymisation and list of recipients become values in code, with the published text alongside. We deliver: the completed processing register, with the “where it is implemented” column filled in, and the list of points still to be decided, with who decides each one.

05

We verify from the outside what can be verified from the outside

Headers, boundary behaviour, what the health probes return. We deliver: the exact verification commands, so anyone — including the client’s auditor — can repeat the check without us.

The layers we touch, from top to bottom1headers and content policy2rate limiting with the key taken from the last hop3authentication, roles and organisation-based separation applied inbase4encrypted secrets, never in responses or logs5log with append and anonymisation applied before writing6retention term applied by code, with the published text alongside
The layers we touch, from top to bottom

The data

What we touch, where it lives, and how long it stays

The questions anyone with a data protection officer would ask — asked here before they do.

What we cover in an analysis
The code, server configuration, response headers and, where applicable, the database schema and row-level access policies. We do not need the clients' real data to carry out the analysis and we prefer not to touch it at all.
What we do not do without a domain authorised in writing
No action that generates traffic against a live system. The production checks shown on this page are ordinary read requests, on our own systems. Any step beyond this limit requires written authorisation, with a period and addresses, signed by the person entitled to give it.
Where the findings go
Into a document with one finding per row: where it is, what can be done with it, what fixes it and how the fix is verified. Findings that describe an exploitation path do not circulate through unsecured channels and do not go into public materials before repair.
Processing register
For the confidentiality part, the result is a register with one row per activity: purpose, basis, categories of data, recipients, transfers, storage location, term, measures and the file in the code where it is implemented. What could not be verified is written as “unverified”, not filled in from a template.
The rule that keeps policy tied to code
If a code change alters what is collected, who receives it or how long it is kept, the published policy is changed in the same delivery step. The divergence between the published policy and the code was exactly the problem that made us write our own register.

A case

An overlooked route that could call anyone

The situation

During a review of our own site we came across an application programming interface route that initiated a phone call to any number sent in the request body. No authentication, at the company's expense and from the company's number. No component in the interface was calling it any more — the only caller was an element that was no longer displayed. In other words, it had been operating openly on the internet without anyone having a reason to look at it.

What we built

We treated it as two issues, not one. Technically: the route now requires a shared secret in a header, and if the secret is not configured at all it returns 404 — the capability is off unless someone deliberately turns it on, not on until someone turns it off. Legally and ethically: an unsolicited automated call is processing data that the called person did not request, so we wrote in the register what would be needed for any possible reactivation — documented basis with respect to the called person, record of consent and an opposition channel.

What came out

The capability is off by default and remains off until someone makes an explicit decision. The processing register row describes the state, what it was before, the risk and the conditions for reactivation — so the next person who finds the route does not have to reconstruct the reasoning.

What the case does not say

We did not realise this through a tool. We found it by reading the routes one by one and asking, for each one, who calls it. An automatic scanner would have seen a route that returns 400 to an empty request and would have moved on. That is also the limit of the method: it covers what we read, and what we do not read remains uncovered — that is why the domain is set in writing.

Questions

What people ask us before they call

Do you have security certifications?

No. Neither ISO 27001, nor SOC 2, nor any offensive testing certification, nor auditor accreditation. We do not issue certificates and do not sign conformity attestations. What we can show is our own practice, with file and line, and the controls we have put in live systems. If you need a certificate for a file, you need an accredited body, not us — and it is better to know that now.

Do you do penetration testing?

Not as a service. We do not have an offensive testing team, licence or certified methodology, and we do not pretend that we do. What we do is code and configuration analysis, plus non-invasive checks from the outside — headers, rate-limit behaviour, what error responses leak. If the project needs real offensive testing, you need a specialised firm; we can work alongside it on the remediation side.

What did you find in your own systems?

Three things, in one day of analysis, all written publicly in our register. A route that initiated a phone call to any number in the request body, anonymously, without authentication, which no component in the interface was calling any more — now it returns 404 if the secret is not configured, that is, it is stopped unless someone deliberately starts it. A conversation widget that loaded on every view and wrote the session into the browser memory before the visitor asked for anything — now it loads on button press. And the full IP address sent in a notification, while the public page promised anonymisation — now it is truncated before writing.

Your content policy allows `unsafe-eval`. Why should I believe you?

Because we told you, not you found out. Yes, this site's policy allows `unsafe-inline` and `unsafe-eval` in scripts — it is a real weakness, inherited from the way certain third-party scripts are loaded, and it is even the mechanism through which our own accessibility audit injected a verification tool into the production page. On a project where we set the constraint from the start, the correct form is the one from the reporting platform: a single value per response generated in the proxy and no third-party source for scripts. The difference between the two is exactly the discussion we should have at the start of a project, not at the end.

How do I verify, without taking your word for it, that the rate limit works?

By sending twelve consecutive requests to an application interface route on this site and counting the responses: the first nine go through, the tenth receives 429 with the header that says when it can be tried again. The window is one minute. The command is in the source list of this page and you can run it now. We apply the same principle to what we deliver: if a control cannot be checked from the outside by the client, it is not delivered, it is declared.

What happens to personal data from a form?

It is written to an append-only log, one object per line, with restricted file and directory permissions, before delivery is attempted — so a broken notification channel does not make the request disappear. The IP address is truncated to the network prefix before writing. Files older than the declared term are deleted, with the number of days taken from the same value that appears in the published policy. And the row in the processing register says, for each item, in which file in the code it is implemented.

Can you do authentication with state electronic identity or electronic signature?

The code exists and is tested, but it is not enabled anywhere in production, and the difference matters. We have written a provider service for federated authentication and a signature client, both without new dependencies, with the specific defences implemented and with tests. What is missing is not code: contract with the authority, system certificate and registration of the production address. Until then, the version that works today is the one where the citizen signs on the official portal and uploads the signed document back.

What controls do you normally put in a new application?

Headers and content policy with a single value per response; rate limiting with the key taken correctly from behind the proxy; organisation separation applied in the database, not only in the application; encrypted secrets and never in responses or logs; idempotency keys on third-party events; mandatory two-factor authentication for staff accounts; audit log with separate visibility for internal and public; and, for public projects, a whitelist of network destinations checked automatically at every delivery.

What does this service not cover?

Corporate network security, user equipment, continuous monitoring by an operations centre, 24/7 incident response, forensic investigation and anti-phishing training. These are not things we do badly — they are things we do not do. Our scope is the application we build or take over, plus the data that passes through it.

What the statements above rest on (24 sources)
  1. Verified in production: 12 consecutive requests give 9 200 responses, then 429https://www.megapromoting.com/api/health/contact · 2026-09-06
  2. Headers in production: strict transport for 63072000 s with subdomains and preloading, non-deducible content type, restricted referrer policy, permissions policy that closes the camera; framing is fully denied on API routes and limited to same origin on pageshttps://www.megapromoting.com/api/health/contact · 2026-09-06

22 of them are code and files from our repositories. We don't publish their name or line: together, on a single page, they would describe too precisely how systems that aren't only ours are built. We go through them with you, in the repository, on request — verification stays possible, it just happens in a conversation.

What would you want to work better?

Tell us about your process. Together we decide what is worth building, what we can connect, and how we check the result.

Let's talk