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 Limits — it is the most important section on this page.

Audit status — ALL CHECKS PASS — every published check passed on every host

vpn — VPN edge: 10/10 checks passed (6.0 h ago) · public — Public web / API: 7/7 checks passed (6.1 h ago)

Newest signed report dated 2026-10-09T09:44:29Z; every signature above was re-verified with ssh-keygen -Y verify against the key pinned in this site's repository when the page was built.

Section 5 — full results: every check, the evidence behind it, and how to fetch and verify the reports yourself →


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.json → checks[].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. Four things are not public: the product-perimeter modules, the fleet’s internal address roster, the deception layer, and the audit’s own generator — 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
What is backed up off-site, how long restore points survive, and the restore we have actually performed Backup and recovery statement
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 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 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-10-09 15:45 UTC: every signature and every SHA-256 below was checked with ssh-keygen -Y verify against a key pinned in this site's repository. A report whose checks did not all pass fails this build instead of being published here, so this page only ever shows a state the infrastructure signed and passed. Each host's own signed report — linked just below — is the authoritative live copy, and is published whether or not this site builds.

vpn — VPN edge

live reportALL CHECKS PASS — 10/10 checks passed
generated2026-10-09T09:44:29Z (6.0 h ago)
signatureverified — ssh-ed25519 SHA256:nLZOwc5NYNz4dWnTI6ZDOYi3IGsiFj9yhKEs/VzCpzY (matches the key pinned in this site's repository)
report SHA-25679c2178519e7d339c1ffb2cb28dc063ce32a0f06874c0d19033fc0672c1b2d46 (5345 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: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=87 (before the probe: 87); canary audit-canary-8ca6ae77b3.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
blocky:fleet:querylog-offpassthe effective config /nix/store/g6rywy56lddycq062008p6bvxpqpj4q8-blocky-fleet.yaml (path read from blocky-fleet, not declared here) has queryLog.type=none; no query logging is the fleet posture
blocky:fleet:no-querylog-filepassqueryLog.type=none target=None; 0 artifact(s) at the declared target; 0 query-log-shaped name(s) under state dir /var/lib/blocky-fleet; unit blocky-fleet ActiveState=active
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 130 line(s) of units blocky-fleet,adguardhome-client,unbound,unbound-client since 14 days ago; 0 non-client line(s) ignored (sudo audit trail, or a fleet address: this host's own gateway, its peer's tier resolver, or the tier VIP)
nolog:clientsv-nolog-primarypassre-ran the unit (rc=0); ActiveState=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.
  • blocky:<instance> checks read the EFFECTIVE config the running unit loaded (the path comes from the unit's own ExecStart, not from this module's configuration) and require queryLog.type: none plus no query-log artifact under the unit's state dir, with the unit ACTIVE. There is no canary query on this engine: with query logging off blocky's /api/query answers 405, so a pass here is a claim about configuration and disk, not an observation that a sent query left no log entry.
  • nolog:<unit> checks RE-RUN the unit's own behavioural assertion (restart, then require a fresh success plus its NO-LOG OK line) rather than re-reading the line a past deploy wrote: the local journal is size-capped and vacuums that line long before any declared retention.
  • journal:no-client-tier-address scans only the resolver units named in queryUnits, and counts only lines naming a CLIENT tier address. Every address the fleet owns inside the scanned ranges — this host's own gateways, its peer's tier resolvers, and the tier VIP that moves between the halves, declared once in nix-config-private/modules/fleet-tier-addresses.nix — is counted and reported as non-client rather than dropped.
  • 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-dateincomplete13/30 days (17 days with no report — all before 2026-09-18, when dated copies began to be retained)0c60b353c5462388976b9… sig verifiedjson · sig · readable
2026-10month-to-dateclean9/9 days0655e1c7edbb13e342e6a… 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-10-09T09:40:53Z (6.1 h ago)
signatureverified — ssh-ed25519 SHA256:NkQoFKJH/tiH+K+8fICk3nCukwo35G+P4BdZDVnA/vU (matches the key pinned in this site's repository)
report SHA-2566732656268092160d12394a9bd5ef584a261bbe9396ea68ce8e9124e0a5b6532 (4181 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=585 (before the probe: 585); canary audit-canary-4d2c25ffb2.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 1262 line(s) of units adguardhome-client-standby,unbound since 14 days ago; 0 non-client line(s) ignored (sudo audit trail, or a fleet address: this host's own gateway, its peer's tier resolver, or the tier VIP)
nolog:clientsv-nolog-standbypassre-ran the unit (rc=0); ActiveState=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.
  • blocky:<instance> checks read the EFFECTIVE config the running unit loaded (the path comes from the unit's own ExecStart, not from this module's configuration) and require queryLog.type: none plus no query-log artifact under the unit's state dir, with the unit ACTIVE. There is no canary query on this engine: with query logging off blocky's /api/query answers 405, so a pass here is a claim about configuration and disk, not an observation that a sent query left no log entry.
  • nolog:<unit> checks RE-RUN the unit's own behavioural assertion (restart, then require a fresh success plus its NO-LOG OK line) rather than re-reading the line a past deploy wrote: the local journal is size-capped and vacuums that line long before any declared retention.
  • journal:no-client-tier-address scans only the resolver units named in queryUnits, and counts only lines naming a CLIENT tier address. Every address the fleet owns inside the scanned ranges — this host's own gateways, its peer's tier resolvers, and the tier VIP that moves between the halves, declared once in nix-config-private/modules/fleet-tier-addresses.nix — is counted and reported as non-client rather than dropped.
  • 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-dateincomplete13/30 days (17 days with no report — all before 2026-09-18, when dated copies began to be retained)0d3e4d6d6a789d7c6da76… sig verifiedjson · sig · readable
2026-10month-to-dateclean9/9 days0beec5a3f1ec32fe9db5e… 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 (90 days — the fleet keeps nothing longer than 90 days, and 90 still covers the previous month in full plus two months of margin). 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 a fair 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 — the fleet’s internal address roster (which host answers on which address, in which role), the deception layer, and the audit’s own generator. The perimeter modules and the generator are withheld because they are the parts whose details would most directly help someone attack a customer or evade the audit. The roster and the deception layer are withheld because publishing either would hand over the map: which address is which role, and which of our hosts is a decoy.

What that costs you: 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. 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:


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 | 󰌠 NixOS | 󰍢 UTF-8