Infrastructure audit

Every VPN provider says it keeps no logs. Almost none of them give you a way to check. This page is the way to check ours: monthly reports describing the verifiable state of the VPN infrastructure, published as machine-readable documents signed by the host that produced them, with the keys and the full configuration published alongside.

Nothing on this page asks you to trust YMRTECH. Everything on it is either fetchable, signed, or published as source code you can read.

What this is, and what it is not. These reports are produced by the infrastructure they describe, not by an independent auditor. They are self-reported, cryptographically signed, dated, and published — which makes them checkable and hard to quietly revise, not independently verified. We do not hold a third-party audit, an accredited certification, or a SOC 2 report, and we do not claim one. See Honest limits — it is the most important section on this page.


1. The claims, and the artifact that backs each one

What we claim What you can check Where
We keep no activity logs — no sites, no DNS lookups, no connection times A daily signed report re-asserting the no-log invariants on the host itself, with the raw evidence for each check logging-audit.json
The no-log setting cannot silently revert The check re-applies the setting, sends a real query, and injects a canary query whose name cannot occur by accident, then requires that nothing was written logging-audit.jsonchecks[].detail
A violation is published, not hidden A failing check still writes and signs the report first, then the audit unit fails — so a violated report exists at this URL checks[].ok
The claim held all month, not just today A monthly roll-up recounting every dated daily report in the period, re-verifying each signature monthly/<YYYY-MM>.json
Reports were not rewritten after the fact A closed month is frozen once published; its SHA-256 is recorded in the index monthly/index.json
You are reading the same bytes we published Every report is signed with ed25519 (ssh-keygen -Y verify); the signer key is published *.sig, logging-audit.allowed_signers
The infrastructure is what we say it is The large majority of the OS configuration is public declarative source, with PR review, CI, a deploy health gate, and auto-rollback. The product-perimeter modules and the audit’s own generator are not public — see the boundary below git.ymrtech.com/ymrtech/nix-config

The policy behind these claims is at What We Log and Privacy. This page is the evidence.


1a. The other half: how this is actually run

Everything above is technical proof — signed artifacts a stranger can fetch and verify. That answers “does the claim hold?” It does not by itself answer “is anyone actually running this, or is it a clever script nobody maintains?”

There is a second half, and it is published too: the management system behind the infrastructure. Not a certificate — a written, dated, inspectable record of how the service is governed.

What the management system establishes Document
The primary policy: commitments, the owner of each part, the review cycle, and a dated approval record Information security policy
What the system covers, and what it deliberately excludes ISMS scope statement
Fifteen identified risks with assessed likelihood, impact, treatment and residual — including the ones we accept rather than mitigate Risk register
All 93 ISO/IEC 27001:2022 Annex A controls, assessed one by one with the basis for each Statement of applicability
How a change reaches production, and how a bad one is reverted automatically Change management statement
Severity levels, the response process, and the post-incident review Incident response plan
The six providers we depend on — what each receives, where it goes, and what breaks without it Supplier review
The dated review cycle for the management system itself, and how a third party checks a review happened Internal audit programme

Start at the management system index — it opens with what the documents are for and states the position in one paragraph.

The honest relationship between the two halves. The technical audit is evidence; the management system is process. Neither substitutes for the other. A signed daily report proves the logging posture held; it does not prove anyone would respond correctly to an incident. A written incident-response plan says what would happen; until an incident exercises it, it is a plan and not a record — and the statement of applicability marks it partial for exactly that reason rather than claiming it as met.

We publish the awkward parts of both. The audit page names a check that is currently degenerate and admits the audit is self-reported. The management system records that one person operates the service, that there is no separation of duties, and that the whole thing is self-assessed — no external auditor has looked at any of it. What we refuse to say, and why, is set out in Standards & Claims.


2. Fetch it yourself

Two hosts each sign their own report — there is deliberately no central audit that could speak for both.

Host Base URL
VPN edge (vpn) https://vpn.ymrtech.com/logging-audit/
Public web / API (public) https://ymrtech.com/logging-audit/
1
2
3
4
5
6
7
8
9
10
# three files: the report, its detached signature, and the accepted signer
curl -O https://vpn.ymrtech.com/logging-audit/logging-audit.json
curl -O https://vpn.ymrtech.com/logging-audit/logging-audit.json.sig
curl -O https://vpn.ymrtech.com/logging-audit/logging-audit.allowed_signers

# verify — no YMRTECH software, no YMRTECH server, just OpenSSH
ssh-keygen -Y verify \
  -f logging-audit.allowed_signers \
  -I logging-audit@vpn -n logging-audit \
  -s logging-audit.json.sig < logging-audit.json

A pass prints Good "logging-audit" signature for logging-audit@vpn with ED25519 key SHA256:…. Each report also carries its own signing_key block with the exact command, the key fingerprint, and the principal — so the recipe travels with the artifact instead of living in this page.

The same three commands verify a dated copy (history/<host>-<date>.json) or a monthly report (monthly/<YYYY-MM>.json): each file is signed in its own right, so any single day or month can be verified on its own, detached from the host that published it and from any summary quoting it.

ssh-keygen -Y verify works on Linux, macOS, Windows (OpenSSH ≥ 8.0), and inside containers. It is a stock tool: no account, no API key, no callback to us.


3. What is published, and how often

Artifact Contents Cadence
logging-audit.json + .sig The live report: every check, its verdict, and the evidence string behind it daily
logging-audit.allowed_signers, logging-audit.pub The accepted signer and the bare key for fingerprinting daily
history/<host>-<YYYY-MM-DD>.json + .sig That day’s report, dated and signed separately — the audit’s memory daily
monthly/<YYYY-MM>.json + .sig The month’s summary (below) daily for the running month, then frozen
monthly/<YYYY-MM>.md The same summary, in readable form same
monthly/index.json + .sig Every month published, with the SHA-256 of each report same

A dated copy is what makes a missing day distinguishable from a passing day. Without it, “the audit has nothing to say about the 3rd” and “the audit passed on the 3rd” look identical from outside.

What a monthly report contains

Field Meaning
coverage expected_days vs. days_with_report; every unreported day is listed by name in days_missing — a coverage gap, not a pass, and never filled in silently. archive_starts says when dated copies began
signatures checked / verified and the list that did not verify — the roll-up re-verifies each dated report’s signature before summarising it
checks Per check id: days passed / days failed — so a check that failed once is visible for the whole month
violations count, plus three named buckets: check_failures (every failed check with its date and the evidence string the host recorded), signature_failures (a dated report whose signature did not verify), unreadable_reports (a report that could not be parsed). A zero is published as a zero, never omitted
system_generations[] The deployed configuration generation each check ran against, with first/last-seen dates — the reports are tied to specific reviewed, deployed revisions
retention The retention values declared by the configuration, recorded so that changing them shows up as a visible diff
status clean · violation · incomplete (gaps only)
state final for a closed month, month-to-date for the running one

The roll-up asserts nothing it did not read: before summarising a day it re-verifies that day’s signature against the published signer file. A month that does not verify is reported as a month that does not verify.


4. What the checks actually assert

These are behavioural assertions, not config-file comparisons. The full procedure for each is published in the source; in outline:

Check family Assertion
adguard:*:querylog_config, adguard:*:stats_config Enforce, then probe. The setting is re-applied, any persisted query log is purged, a real query is sent — and the audit requires that nothing was written
adguard:*:buffer The in-memory query buffer is judged by evidence: a canary query whose name cannot appear by accident, plus the count before and after, so residue from before the setting was applied is not mistaken for a live log
adguard:*:no-querylog-file No querylog.json exists on disk
unbound:<path> The effective resolver configuration contains no log-queries
nolog:<unit> The deploy-gated no-log unit is active and its journal shows a live lookup was performed and left nothing behind
journal:no-client-tier-address Over the retention window, the journal names no customer-tier tunnel address (the host’s own provisioning lines are excluded)

Each check reports the evidence string it based its verdict on, so a reader can see why a day passed — not just that it did.


5. The monthly report — published state

Re-verified at build time by tools/audit-report.rb on 2026-09-19 00:25 UTC: every signature and every SHA-256 below was checked with ssh-keygen -Y verify against a key pinned in this site's repository. If a check had failed, this page would say so — it is generated from the host's own reports, not typed by hand.

vpn — VPN edge

live reportALL CHECKS PASS — 13/13 checks passed
generated2026-09-19T00:20:45Z (5 min ago)
signatureverified — ssh-ed25519 SHA256:nLZOwc5NYNz4dWnTI6ZDOYi3IGsiFj9yhKEs/VzCpzY (matches the key pinned in this site's repository)
report SHA-25660ed13c30910efccf0654160dd546899c1e66f4c7ce2bdd9c15d1469c457d755 (4632 bytes)
fetch it yourselfvpn.ymrtech.com/logging-audit/logging-audit.json
Every check this report ran, and the evidence the host recorded
checkverdictevidence recorded by the host
adguard:fleet:querylog_configpassenforce POST accepted after 1 attempt(s); behavioural check below
adguard:fleet:stats_configpassenforce POST accepted after 1 attempt(s); behavioural check below
adguard:fleet:no-querylog-filepass/var/lib/AdGuardHome/data/querylog.json absent after live queries; purge=False; probe reached the resolver=True (rc=0 after 1 attempt(s): dig @127.0.0.1 example.com A +time=3 +tries=1)
adguard:fleet:bufferpassin-memory entries=0 (before the probe: 0); canary audit-canary-e0c7357a96.ymrtech.invalid present=False; grew=False — a present canary or growth means the resolver still logs, and that is a violation on every tier; a non-zero count that does neither is residue from before the disable, which only a restart clears
adguard:wg2:querylog_configpassenforce POST accepted after 1 attempt(s); behavioural check below
adguard:wg2:stats_configpassenforce POST accepted after 1 attempt(s); behavioural check below
adguard:wg2:no-querylog-filepass/var/lib/adguardhome-client/data/querylog.json absent after live queries; purge=False; probe reached the resolver=True (rc=0 after 1 attempt(s): dig -b 172.16.41.1 @172.16.41.1 example.com A +time=3 +tries=1)
adguard:wg2:bufferpassin-memory entries=0 (before the probe: 0); canary audit-canary-a694c3da83.ymrtech.invalid present=False; grew=False — a present canary or growth means the resolver still logs, and that is a violation on every tier; a non-zero count that does neither is residue from before the disable, which only a restart clears
unbound:/etc/unbound/unbound.confpasslog-queries lines=none
unbound:/etc/unbound-client/unbound.confpasslog-queries lines=none
journal:no-client-tier-addresspass0 client match(es) in 6723 line(s) of units adguardhome,adguardhome-client,unbound,unbound-client since 14 days ago; 371 non-client line(s) ignored (sudo audit trail, or this host's own gateway addresses)
nolog:adguardhome-nologpassActiveState=active ExecMainStatus=0 behavioural_ok=True
nolog:clientsv-nolog-primarypassActiveState=active ExecMainStatus=0 behavioural_ok=True
What these checks mean, as recorded by the host (verbatim)
  • adguard:* checks ENFORCE the setting (idempotent POST), purge any persisted querylog, then require that a real query wrote nothing: the API has no read endpoint for this setting and its querylog GET serves stale memory.
  • nolog:<unit> checks re-read the deploy-gated behavioural assertion (unit state + its NO-LOG OK journal line); they do not re-run the probe themselves.
  • journal:no-client-tier-address scans only the resolver units named in queryUnits, and counts only lines naming a tier address this host does not own: provisioning output (the sudo audit trail, `ip route`/`ip address` commands) is counted and reported as non-client. It has still never seen real client traffic (no wg1/wg2 client has connected yet).
  • retention values are DECLARED claims from the configuration, recorded here so a change to them is visible as a diff.
monthreport containsstatuscoverageviolationsSHA-256fetch
2026-09month-to-dateincomplete2/19 days (17 days with no report — all before 2026-09-18, when dated copies began to be retained)0346b791edfdc94a50efa… sig verifiedjson · sig · readable

A month-to-date row is a running summary and changes as the month progresses; a closed month is frozen, so its SHA-256 must keep matching whatever you download later. If it stops matching, that is itself the finding.

public — Public web / API

live reportALL CHECKS PASS — 7/7 checks passed
generated2026-09-19T00:22:30Z (3 min ago)
signatureverified — ssh-ed25519 SHA256:NkQoFKJH/tiH+K+8fICk3nCukwo35G+P4BdZDVnA/vU (matches the key pinned in this site's repository)
report SHA-25668d39c73a80a28d046f015813c5625aa0f6c3db590f53176e3d4c84e32657802 (3300 bytes)
fetch it yourselfymrtech.com/logging-audit/logging-audit.json
Every check this report ran, and the evidence the host recorded
checkverdictevidence recorded by the host
adguard:wg2-standby:querylog_configpassenforce POST accepted after 1 attempt(s); behavioural check below
adguard:wg2-standby:stats_configpassenforce POST accepted after 1 attempt(s); behavioural check below
adguard:wg2-standby:no-querylog-filepass/var/lib/adguardhome-client-standby/data/querylog.json absent after live queries; purge=False; probe reached the resolver=True (rc=0 after 1 attempt(s): dig @127.0.0.1 example.com A +time=3 +tries=1)
adguard:wg2-standby:bufferpassin-memory entries=1 (before the probe: 1); canary audit-canary-defbd18f28.ymrtech.invalid present=False; grew=False — a present canary or growth means the resolver still logs, and that is a violation on every tier; a non-zero count that does neither is residue from before the disable, which only a restart clears
unbound:/etc/unbound/unbound.confpasslog-queries lines=none
journal:no-client-tier-addresspass0 client match(es) in 1082 line(s) of units adguardhome-client-standby,unbound since 14 days ago; 58 non-client line(s) ignored (sudo audit trail, or this host's own gateway addresses)
nolog:clientsv-nolog-standbypassActiveState=active ExecMainStatus=0 behavioural_ok=True
What these checks mean, as recorded by the host (verbatim)
  • adguard:* checks ENFORCE the setting (idempotent POST), purge any persisted querylog, then require that a real query wrote nothing: the API has no read endpoint for this setting and its querylog GET serves stale memory.
  • nolog:<unit> checks re-read the deploy-gated behavioural assertion (unit state + its NO-LOG OK journal line); they do not re-run the probe themselves.
  • journal:no-client-tier-address scans only the resolver units named in queryUnits, and counts only lines naming a tier address this host does not own: provisioning output (the sudo audit trail, `ip route`/`ip address` commands) is counted and reported as non-client. It has still never seen real client traffic (no wg1/wg2 client has connected yet).
  • retention values are DECLARED claims from the configuration, recorded here so a change to them is visible as a diff.
monthreport containsstatuscoverageviolationsSHA-256fetch
2026-09month-to-dateincomplete2/19 days (17 days with no report — all before 2026-09-18, when dated copies began to be retained)04466b04f9fb32a5e0b25… sig verifiedjson · sig · readable

A month-to-date row is a running summary and changes as the month progresses; a closed month is frozen, so its SHA-256 must keep matching whatever you download later. If it stops matching, that is itself the finding.

The machine-readable version of this section — including the pinned signer fingerprints — is /audit/reports.json, published so the numbers above can be diffed by a script rather than trusted by eye.


6. Verifying without trusting this page

  1. Fetch the artifacts from the host, not from this page. This page is a summary; the reports are the evidence. A summary that disagrees with the artifact is our bug.
  2. Check the key fingerprint against two independent sources: the fingerprint printed inside the report, and the same key on the other host’s report. The fingerprints are published in monthly/index.json too, so a key swap is visible as a diff.
  3. Check the code you can, and know where the boundary is. The deployment that runs the audit, the host definitions, the firewall, and the publication path are all in the public configuration repository. The audit’s generator is not — it lives in a non-public repository — so its correctness is not something we ask you to take on faith either: every check, its verdict, and the raw evidence string behind it are published in the signed artifact itself, and the signer’s key is published so a substituted report is detectable. We would rather state that boundary plainly than imply a source you cannot read. See the public-source boundary.

  4. Check the dates you care about. Dated copies are kept for historyDays (400 days by default — always long enough to cover the previous month in full plus a year-over-year comparison). Ask for one from the month you care about and verify it directly.
  5. Verify a hash you kept. Because closed months are frozen and hashed, a copy you saved last month must still match what we serve today. If it does not, that is the finding.

The public-source boundary

We publish most of our configuration, and we are explicit about the part we do not, because “read our source” is only an honest invitation if the source is actually readable.

Public (git.ymrtech.com/ymrtech/nix-config): every host definition, the firewall and network configuration, the WireGuard and AmneziaWG setup, the resolver and DNS policy, the monitoring and alerting stack, the Caddy publication path that serves these artifacts, and the CI workflows that build, verify and deploy them.

Not public (nix-config-private): the modules that make up the product perimeter — the client-facing VPN service, its client-isolation ACLs, its provisioning — together with the audit’s own generator. These are withheld for one reason: they are the parts whose details would most directly help someone attack a customer or evade the audit.

What that costs you, stated honestly: you cannot audit our generator’s source, so you cannot independently confirm that it checks what we say it checks. What you can do is audit its behaviour — the published artifact names every check and prints the evidence behind each verdict, the results are signed with a key you can fingerprint, and the daily series is frozen per month and hash-chained through monthly/index.json. A generator that quietly stopped checking something would have to lie in a signed, dated, archived document in order to hide it. That is a weaker guarantee than open source, and we would rather you hear that from us than discover it.

Closing that gap is on the roadmap: the audit’s generator is separable from the product-perimeter modules, and publishing it alone would not expose anything about the client VPN design.


7. Honest limits

A verifiable claim that overstates itself is just a louder unverifiable claim. These are the limits, stated as plainly as the strongest claims above:

  • The audit is self-reported. It runs on the very hosts it audits. A full compromise of a host could forge its report. What the design buys is that the report is signed, dated, published, mirrored off-host, and reproducible from published configuration — so tampering is detectable and a rewrite is visible. It is not the same thing as an independent check, and we do not dress it up as one.
  • No third-party attestation, and no certification. YMRTECH has not been audited by an external party. We hold no ISO/IEC 27001 certificate, no SOC 2 report, and no accreditation of any kind, and we say so on purpose: claiming one we do not hold would be a misrepresentation, not marketing. For what we are working towards and the exact vocabulary we permit ourselves, see Standards & Claims.
  • Some checks are stronger than others. The resolver and query-log checks are enforced and probed; the journal check is a pattern assertion over a window. Where a check is weak, the report says what it actually tested.
  • One check is currently degenerate. journal:no-client-tier-address has never observed real customer traffic (no customer tunnel has connected yet on that host), so a pass today means “no tier address was logged” rather than “no address was logged under load”. It becomes meaningful the first day a customer connects — and the report will show that transition in its evidence.
  • Retention values are declared, not enforced by this audit. The report states what the configuration says. Enforcing it belongs to the components that own the data.
  • A gap is reported as a gap. If a timer does not fire or a host is down, the month records a coverage gap. We would rather publish an incomplete month than a complete-looking one.
  • A gap is attributed, not just counted. The report table reads the host’s own coverage.archive_starts and splits every missing day into the days before dated copies began to be retained (context, shown as a note) and the days after (a finding, flagged in red). September 2026 opens with a large gap for exactly this reason: dated retention began on 2026-09-18, so days 1–17 predate the archive rather than representing a host that failed to report.
  • Telemetry is aggregate. Per-interface byte and peer counts, with no per-peer labels: enough to run a network fairly, not enough to describe a person.

8. Monthly reporting cadence and change log

The report set is generated on the infrastructure and re-published here as the site is deployed, so this page cannot show a month the infrastructure has not published.

Date (UTC) Change
2026-09-18 Page created. Dated daily copies and the signed monthly roll-up introduced; this page and /audit/reports.json are generated from the published artifacts.

Corrections to this page are made in the public site repository, where every change carries a commit, an author, and a review.

󰣨 ymrtech@ymrtech 󰖣 DARK | 󰌠 NixOS | 󰍢 UTF-8