Security

Measured security, dated

Cyntech designs critical infrastructure. This page is our public record of how the systems that run our business and yours are protected. Every statement below carries the date it was measured, what it covers, and the evidence it rests on; a statement we have not re-measured within its window is shown as such rather than left standing.

Measured posture

What we can show

Each card is a measurement, not a promise. Where the number is not what we would like it to be, it is printed anyway.

Tenant isolation

Not measured since 2026-09-14 — this statement is withheld until it is re-measured.

Measured:
· GitHub Actions · CI run 34796640258 (suite) · operator:ashley@cyntech.co.za (table counts, build seat, ORDER B §4.2a)
Scope:
The production database, public schema, measured from pg_class and pg_policies. The isolation suite runs against a freshly migrated throwaway database in CI, seeding two tenants as ordinary members with no app role; it proves the policies, not production data. The one unforced table (github_connection_config) carries no tenant_id. Platform administrators hold a declared read authority over device data across workspaces (N-685); this is disclosed, not hidden by the count.
Evidence:
ci_run — CI run 34796640258 · DB Migrations & Tenant Isolation · bun run test:isolation · PASS: 123 probes, 0 cross-tenant leaks open
Reopened if:
A public base table is created without FORCE ROW LEVEL SECURITY (force-rls-coverage, daily); a tenant_id table is found unforced; the isolation suite fails on develop or main.
Dependencies

Not measured since 2026-09-13 — this statement is withheld until it is re-measured.

Measured:
· GitHub Actions · CI run 34786787610
Scope:
The lockfile this project resolves from (bun.lock), development and runtime dependencies together. Advisories below high are listed in the job log and are not zero.
Evidence:
ci_run — CI run 34786787610 · Dependency Audit (blocking, high/critical) open
Reopened if:
A new high or critical advisory is published against a pinned version (the job goes red); the audit step is removed from build.needs.
Second factor on privileged accounts

Not measured since 2026-09-14 — this statement is withheld until it is re-measured.

Measured:
· operator:ashley@cyntech.co.za (build seat, ORDER B §4.2c)
Scope:
Accounts holding the admin or editor role in user_roles. Workspace (tenant) users are not counted here. This is an enrolment count, not a login-time count.
Evidence:
query — user_roles ⨝ auth.mfa_factors (status = 'verified'), 14 Sep 2026 01:10Z; receipt AR4-mfa-enforcement
Reopened if:
A privileged role is granted or revoked; a factor is unenrolled; the enforcement mode changes. The figure is re-measured and the date advanced, never edited in place.
What runs, and when

Not measured since 2026-09-14 — this statement is withheld until it is re-measured.

Measured:
· operator:ashley@cyntech.co.za (build seat, ORDER B §4.2d, re-measured §6)
Scope:
The CI workflows in this repository and the pg_cron schedule for the backup. 'Completed' means status ok with a manifest row; 'verified' means an independent read-back matched the manifest byte for byte — the two dates differ and both are printed. Nothing described here runs daily except the backup attempt; there is no daily vulnerability scan and the page no longer says there is.
Evidence:
receipt — N-724 (28 Aug export read back through getObject, SHA-256 9cbbf77f…, 4 of 4 manifest fields match); offsite_backup_runs run_date 2026-09-10 (status ok, manifest present, total_rows 14659276, total_bytes 10504084499); watchdog_findings offsite-backup-artifact subject offsite-backup:run:2026-09-10 = run_not_reconciled, 14 Sep 01:07Z; .github/workflows/ci.yml; security-scan.yml; N-728
Reopened if:
A job is removed from build.needs or set continue-on-error; the Semgrep schedule changes; a backup run completes or the failures are dispositioned (the dates above are then re-measured).
Transport and browser defences

Every response carries HSTS (max-age 63072000; includeSubDomains; preload), X-Content-Type-Options nosniff, X-Frame-Options DENY, Referrer-Policy strict-origin-when-cross-origin, a Permissions-Policy denying powerful APIs, COOP same-origin, CORP same-site and a Content-Security-Policy whose script-src is a per-request nonce with 'strict-dynamic' and no 'unsafe-eval' or 'unsafe-inline'. These are set in the server entry; they are not currently asserted by an automated test.

Measured:
· operator:ashley@cyntech.co.za (build seat, ORDER A §3) · Good until 2026-12-12
Scope:
Responses served by src/server.ts. style-src still allows 'unsafe-inline' (component library inline styles) — stated in N-728. The hstspreload.org submission has not been made.
Evidence:
receipt — N-728 (CSP nonce, unsafe-eval removed, 13 Sep 2026); src/server.ts
Reopened if:
A directive is loosened in src/server.ts; a CSP violation report reaches /api/public/csp-report; a header test is added (the claim is then re-based on the test).
Telemetry ingest

Device telemetry arrives over MQTT with TLS, on a per-integration token. Each message is idempotent on (integration_id, msg_id); a repeat with the same value is counted and dropped, a repeat with a different value is quarantined with both values kept. Nothing is derived from a device's data until a person has onboarded it and declared each metric's class.

Measured:
· operator:ashley@cyntech.co.za (basis of design Rev E) · Good until 2026-12-12
Scope:
The Nexus ingest path as designed and governed. Messages are not individually signed by the device; integrity rests on TLS and the broker token, not on a payload signature.
Evidence:
document — docs/00-basis-of-design-RevE.md — Payload and Onboarding sections; receipt N-402 (dedup and quarantine)
Reopened if:
The envelope, the idempotency key or the onboarding gate changes by migration; a token is found shared across purposes (check_token_purpose_drift).
Scheduled-job heartbeat

Not measured since 2026-09-14 — this statement is withheld until it is re-measured.

Measured:
· operator:ashley@cyntech.co.za (build seat, ORDER B §6)
Scope:
control_register_heartbeat (one row) and external_heartbeat_pings (all rows). A failed ping means a required scheduled job did not report an outcome, not that the site was down; no availability figure is derived from it.
Evidence:
query — select * from control_register_heartbeat; select outcome, min(sent_at), max(sent_at), count(*) from external_heartbeat_pings group by 1 — 14 Sep 2026 02:05Z
Reopened if:
A ping succeeds again (the figure is re-measured); the heartbeat cadence or the required-job list changes; a 90-day record accrues.
Telemetry arrival

Not measured since 2026-09-14 — this statement is withheld until it is re-measured.

Measured:
· operator:ashley@cyntech.co.za (build seat, ORDER B §6)
Scope:
ingest_deadman_subjects and the ingest-outcome-deadman check in watchdog_findings, a tenant-neutral aggregate. Producer liveness, not per-device completeness; a device silent behind a live bridge is a separate finding.
Evidence:
query — select expected_to_produce, state_reason, count(*) from ingest_deadman_subjects group by 1,2; watchdog_findings where check_name = 'ingest-outcome-deadman', latest run — 14 Sep 2026 02:05Z
Reopened if:
Any active subject reads other than declared_kind_rows_landing; a subject is added or retired.
Off-site backup

Not measured since 2026-09-14 — this statement is withheld until it is re-measured.

Measured:
· operator:ashley@cyntech.co.za (build seat, ORDER B §6)
Scope:
offsite_backup_runs and the offsite-backup-artifact / offsite_backup_lease_progress checks. The backup covers the five-year rollup record, configuration and tenancy — not the 72-hour raw telemetry window, which the rollups supersede (src/lib/backup/spec.ts).
Evidence:
query — select run_date, kind, status, started_at, finished_at, manifest is not null, completion_mode from offsite_backup_runs order by run_date desc limit 6; select count(*) from export_restore_scratch — 14 Sep 2026 02:05Z; N-693, N-724
Reopened if:
A run completes with a manifest (the date advances); a restore-verify is run (the restore time is then quoted); the schedule changes.
Browser-level defences

Security headers on every response

The headers below are set in the server entry for every response. They are read from the source, not from a test — an automated header check is not yet in place, and the claim above says so.

Strict-Transport-Security

max-age=63072000; includeSubDomains; preload

HTTPS is required for two years across all subdomains. Preload-eligible; the preload submission is not yet made.

X-Content-Type-Options

nosniff

Browsers cannot override the declared MIME type.

X-Frame-Options

DENY

Pages cannot be embedded in frames.

Referrer-Policy

strict-origin-when-cross-origin

Referrer data is reduced to the origin on cross-origin requests.

Permissions-Policy

accelerometer=(), camera=(), geolocation=(), microphone=(), payment=(), usb=() …

Powerful browser APIs are off unless a page needs them.

Cross-Origin-Opener-Policy

same-origin

Cross-origin windows are isolated from this one.

Cross-Origin-Resource-Policy

same-site

Resources are served to same-site requests only.

Content-Security-Policy

default-src 'self'; script-src 'nonce-…' 'strict-dynamic'; frame-ancestors 'none'; object-src 'none'; connect-src 'self' ws: wss: …

Scripts run only with the per-request nonce; no eval, no inline. Styles still allow inline (component library), stated in N-728.

Privacy choices

Cookie consent categories

Four categories; non-necessary scripts are blocked until you opt in, and each choice is timestamped. Governed by the Cookie Policy.

Strictly necessary

Authentication, security tokens and CSRF protection. Always on.

Preferences

Theme, dismissed banners and last-selected workspace. Set only after you opt in.

Analytics

Anonymous page-view and event data. No personal identifiers or advertising profiles.

Marketing

Retargeting pixels for LinkedIn and search campaigns. Off by default; loads only after explicit consent.

Reject all is equal

“Reject all” has the same visual weight as “Accept all”.

Script gating

Analytics and marketing scripts are wrapped in consent checks and do not load before permission.

Granular control

Each category can be accepted or rejected on its own, and revisited from the Cookie Policy.

Attestations

Certifications, frameworks and where we are

What we hold, what is in flight and what is planned, with the status we would give an auditor. No certificate is held today.

ISO 27001:2022In progress

Controls are being mapped to Annex A. No audit date is set.

ISO 27701Roadmap

Privacy extension to ISO 27001, after certification.

SOC 2 Type IIRoadmap

Observation window planned alongside ISO 27001; not started.

IEC 62443-2-4Aligned

Service-provider controls used as the reference for industrial integration work; not certified.

NIST CSF 2.0Aligned

Used as the internal self-assessment frame; no external assessment.

POPIAPosition

Processing is conducted under the Privacy Policy and DPA. Evidence of the Information Officer registration is not yet published on this page.

Evidence behind any card is available under NDA — contact security@cyntech.co.za.

Regulatory map

Laws and standards in scope

The regimes our clients in South Africa, the EU/UK and the United States ask about, and the industrial standards specific to energy and OT. Listing a regime here means it is in scope for our programme, not that a certification is held.

South Africa
  • POPIA
  • Cybercrimes Act
  • ECT Act
  • PAIA
European Union & UK
  • GDPR
  • UK GDPR
  • ePrivacy / PECR
  • NIS2 (flow-down)
  • DORA (flow-down)
United States
  • CCPA / CPRA
  • CIRCIA reporting
  • State breach-notification matrix
Industrial
  • IEC 62443
  • ISO 27019 (roadmap)
  • IOGP 627 / 645
  • TSA / NERC CIP (flow-down)
Data stewardship

How we process personal information

Purpose-limited and rights-respecting, as set out in the Privacy Policy.

What we collect

Contact details, project and site data, portal account metadata, recruitment CVs, and technical logs (IP, browser, pages viewed).

Lawful basis

Contract for deliverables, legitimate interest for security and improvement, legal obligation for regulators, consent for marketing.

How we protect it

Transport security and browser defences as measured above; row-level security in the database; a second factor on administrator sessions. The measured cards are the record; this line adds nothing to them.

Cross-border transfers

Sub-processors outside South Africa are bound by EU Standard Contractual Clauses or an adequacy decision, per the sub-processor list.

Retention

As long as the purpose requires. Financial records are kept 5+ years per the Tax Administration Act; project data for the asset lifecycle plus the indemnity period.

Your rights

Access, correction, erasure, portability, objection and withdrawal of consent, via the Data Subject Request form or privacy@cyntech.co.za.

For the full description of processing, retention schedules and lawful basis, read our Privacy Policy and Cookie Policy.

Our commitments

What we put in writing

These are commitments in our DPA, Privacy Policy and Incident Response Notice — obligations we accept, not measurements. Whether we have met each is a question for the incident register and the sub-processor list, not this page.

  • We do not sell client data.
  • We do not use your data to train third-party AI models without written consent.
  • We notify you within 72 hours of confirming a security incident that affects you (Incident Response Notice).
  • We give 30 days' notice before adding a sub-processor.
  • Our DPA, sub-processor list and this security record are published on this site without a sales gate.
If something goes wrong

Incident response

A written runbook with named owners and regulator clocks. Incidents that affected a client are recorded in the incident register when it is published.

Detect

Centralised findings from the platform's own watchdog checks, database and auth logs, and reports through coordinated disclosure.

Contain and assess

Severity thresholds, escalation, and a snapshot of the affected systems before repair.

Notify

POPIA “as soon as reasonably possible” and the GDPR 72-hour clock are both tracked. Affected clients are contacted through their primary administrator.

Read the full Incident Response Notice or report a vulnerability through our coordinated disclosure programme (safe-harbour protected).

Data subject rights

Client request workflows

A no-cost process for exercising rights under POPIA, GDPR, UK GDPR and CCPA / CPRA.

Request types

  • Access — receive a copy of your data
  • Rectification — correct inaccurate information
  • Erasure — delete your data
  • Portability — export in a machine-readable format
  • Objection — stop processing for direct marketing
  • Restriction — pause processing while a dispute is resolved
  • Withdraw consent
  • Lodge a complaint

How to submit

Use our Data Subject Request form or email privacy@cyntech.co.za. For sensitive requests we may ask for proof of identity separately.

Talk to us

Building something where security matters?

Vendor questionnaires (SIG / CAIQ / bespoke) are answered within 5 business days, from the same evidence this page cites.