You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit d8fd2a9
Browse filesBrowse the repository at this point in the historyBrowse files
Write the documents a VPN-class app carries, and stop the README lying
The README's status table said "not yet usable as a VPN: there is no VpnService
and no TUN layer, so nothing routes device traffic". That stopped being true
four runs ago. A status section is the first thing anyone reads and the last
thing anyone updates, and this one had drifted far enough to describe a
different project.
It now says what is true, including the parts that are not flattering: nothing
has ever run on physical hardware, the QUIC carrier does no certificate
verification, no release has been published and no signing key exists. The build
section names what a first build actually needs — Go and perl for aws-lc, a
network step because the QUIC stack is fetched rather than vendored — and how to
produce the corresponding source a GPL binary that links it must carry.
privacy.md is written the way an app of this kind should write one: the absences
are the part worth reading, so they are listed individually rather than summed
up as "we respect your privacy". Nothing is collected, and the claim is made
checkable — the dependency list contains no analytics library and a reader can
verify that in one pass, which is worth more than a promise. What the app stores
is named file by file, including that a shared key is a credential kept in plain
text in private storage, which is the same protection the platform gives every
app and weaker than it sounds on a rooted device.
store-policy.md records what a VPN listing requires against what this app
already does, and separates that from what is still someone's decision: the
package identifier, which D-02b makes irreversible at first publication and
which the first signed build has now made due; a signing key, which is the app's
identity for life; export compliance; and the distribution channel.
Two things it says the listing must not: this app does not claim to resist
censorship — upstream does not claim it either, and the words appear zero times
in its own security document — and it is not a free VPN, because it carries no
service and does nothing without a Portal the user brings.
Gates green.
Copy file name to clipboardExpand all lines: README.md
+76-25Lines changed: 76 additions & 25 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -8,20 +8,34 @@ engine is hand-written in Kotlin rather than wrapping an existing core.
8
8
9
9
## Status
10
10
11
-
Pre-release, and **not yet usable as a VPN**: there is no `VpnService` and no TUN
12
-
layer, so nothing routes device traffic. What exists:
11
+
Pre-release, and **not published anywhere**. The protocol is complete: L1, L2 and
12
+
L3 all work against a live upstream Portal, and a node URL with no parameters —
13
+
which is a QUIC node, because the specification's default for both directions is
14
+
`udp` — connects and carries traffic.
13
15
14
16
|||
15
17
|---|---|
16
-
| Protocol, L1 | Authentication, flow setup, the dedicated TLS lane, the seven setup outcomes. Verified against a live upstream Portal, not only against fixtures |
17
-
| Transport | TLS dialing with all three certificate modes — skipped, `sni`, `pin` — each tested against a live Portal |
18
-
| Nodes | Stored on device, imported from a `nowhere://` link, probed for reachability |
19
-
| Subscription | Fetching and parsing, including the quota semantics upstream actually has |
20
-
| UI | All eight screens, both themes, three locales |
21
-
| Not yet | TUN / lwIP, `VpnService`, Mux (L2), QUIC (L3) |
22
-
23
-
Delivery is layered: L1 TLS/TCP, then L2 Mux, then L3 QUIC. Each layer ships to
24
-
internal testing before the next one starts.
18
+
| Protocol, L1 | Authentication, flow setup, the dedicated TLS lane, the seven setup outcomes |
19
+
| Protocol, L2 | TLS Mux. Sixteen concurrent flows measured over four connections, at upstream's stated shard density |
20
+
| Protocol, L3 | QUIC: authentication, TCP flows over reliable streams, UDP over DATAGRAM with fragmentation, split flows across two carriers |
21
+
| Carriers | All four combinations — `tcp/tcp`, `udp/udp`, `udp/tcp`, `tcp/udp` — compared case by case against the reference implementation, which agrees on every one |
22
+
| Tunnel |`VpnService` + lwIP, TCP with back-pressure, UDP, DNS interception with fake-IP, live throughput |
23
+
| Routing | A rule matcher, an import path, a bundled network-structural rule set, a direct path, a reject path |
24
+
| Per-app | A real installed list, a persisted selection, and this client structurally outside its own tunnel |
25
+
| Nodes | Stored, imported from a `nowhere://` link, probed for reachability |
26
+
| Subscription | Fetching and parsing, with upstream's actual quota semantics |
27
+
| UI | Nine screens, both themes, three locales |
28
+
29
+
**What is not done**, stated as plainly as the rest:
30
+
31
+
-**Nothing has ever run on physical hardware.** Every device result is from an
32
+
emulator. Private DNS over DoT, IPv6-only and NAT64 networks, a real Wi-Fi to
33
+
cellular change, Doze and a path MTU below 1500 are all things an emulator
34
+
cannot represent, and all things a VPN meets on the first real device.
35
+
-**The QUIC carrier does no certificate verification.** A node carrying `sni`
36
+
or `pin` is refused rather than carried without it. The TLS carrier implements
37
+
both.
38
+
-**No release has been published**, and no signing key exists.
25
39
26
40
## Protocol baseline
27
41
@@ -39,14 +53,22 @@ interop checks need it, and community bug reports need it to be meaningful.
> Applies to Somewhere for Android, package `eu.nodepass.somewhere`.
4
+
> Last reviewed 2026-08-29, against the source at that date.
5
+
6
+
## The short version
7
+
8
+
**This app sends nothing to its authors.** There is no analytics, no crash
9
+
reporting and no telemetry of any kind — not disabled by default, not
10
+
opt-out: absent. The dependency list in
11
+
[`gradle/libs.versions.toml`](../gradle/libs.versions.toml) contains no
12
+
analytics or reporting library, and a reader can check that in one pass, which
13
+
is the point of saying it this way rather than promising it.
14
+
15
+
Everything the app stores stays on the device. Everything it sends goes to a
16
+
server **you configured**, and nowhere else.
17
+
18
+
## What is stored on the device
19
+
20
+
All of it in the app's private storage, which no other app can read, and all of
21
+
it removed when the app is uninstalled.
22
+
23
+
| What | Why | Where |
24
+
|---|---|---|
25
+
| Nodes, including their shared keys | A node is unusable without its key |`NodeStore`|
26
+
| A subscription URL and its title | To refresh the node list |`SubscriptionStore`|
27
+
| Routing rules you imported | To decide what goes through the tunnel |`RuleStore`|
28
+
| Which applications the tunnel carries | Applied when the tunnel is built |`AppSelectionStore`|
29
+
| Settings | Preferences |`RoutingPreferences`|
30
+
31
+
**A shared key and a subscription URL are credentials.** They are stored in
32
+
plain text in private storage, which is the same protection the platform gives
33
+
every app's own files. On a rooted device, or one with an unlocked bootloader,
34
+
that protection is weaker than it sounds — this is stated because it is true,
35
+
not because it is unusual.
36
+
37
+
## What leaves the device, and to where
38
+
39
+
**To the Portal you configured.** Your tunnelled traffic, plus a 32-byte
40
+
authentication frame per connection. The Portal necessarily sees the addresses
41
+
and names you connect to — that is what a tunnel is. What it does with them is
42
+
its operator's policy, not this app's, and this app has no way to constrain it.
43
+
44
+
**To the subscription URL you entered**, when you add or refresh a
45
+
subscription. An ordinary HTTPS `GET`, carrying three query parameters that
46
+
describe this client so a dashboard can generate a node list it can actually
47
+
use:
48
+
49
+
-`type` — that this is a Somewhere client
50
+
-`ver` — the client version
51
+
-`caps` — which protocol features this build supports
52
+
53
+
No identifier of you, your device or your installation is sent. There is none
54
+
to send: this app generates no installation id.
55
+
56
+
**To the addresses you connect to**, for traffic your routing rules send
57
+
*direct* rather than through the tunnel. That traffic does not reach the Portal
58
+
at all, which is the point of a direct rule, and it is not otherwise treated
59
+
differently.
60
+
61
+
**Nowhere else.** No third party receives anything.
62
+
63
+
## What is not collected
64
+
65
+
Named individually, because for an app of this kind the absences are the part
66
+
worth reading:
67
+
68
+
- no advertising identifier, and no advertising
69
+
- no installation id, device id, IMEI, MAC address or serial number
70
+
- no location, coarse or fine
71
+
- no contacts, calendar, photos, files or clipboard
72
+
- no list of installed applications **leaves the device** — the per-app screen
73
+
reads one to draw itself and it is never transmitted
74
+
- no browsing history: the app does not keep one, and the connection log is
75
+
in-memory, capped at 200 entries, and gone when the process ends
76
+
- no crash reports, no usage statistics, no "improve the product" data
77
+
78
+
## The connection log
79
+
80
+
The Diagnostics screen shows what the Portal answered for recent flows: the
81
+
result, the time, a destination as host and port, a flow id, and which carrier
82
+
carried it. It exists in memory only, holds the most recent 200 entries, and is
83
+
lost when the app's process ends.
84
+
85
+
It is **built so that a credential cannot appear in it**. An entry has five
86
+
fields and none of them can hold a shared key, a subscription URL or a token —
87
+
there is no room for one — and a test asserts that field list so a future entry
88
+
that grew one fails rather than shipping. A destination is reduced to a host and
89
+
a port before it is stored. This matters because people paste logs into public
90
+
issue trackers, and helpfulness is how credentials get published.
91
+
92
+
## Permissions, and what each is for
93
+
94
+
| Permission | Why |
95
+
|---|---|
96
+
|`INTERNET`| To reach the Portal and the subscription URL |
97
+
|`ACCESS_NETWORK_STATE`| To notice when the network changes under a live tunnel |
98
+
|`BIND_VPN_SERVICE`| To establish the tunnel. Android shows its own consent dialog and the tunnel cannot start without it |
99
+
|`FOREGROUND_SERVICE`, `FOREGROUND_SERVICE_SPECIAL_USE`| A tunnel must survive the app being backgrounded, which is the normal case: nobody watches a VPN |
100
+
|`POST_NOTIFICATIONS`| To show the ongoing notification that says a tunnel is running. Without it the tunnel runs with nothing on screen saying so |
101
+
102
+
The app declares a `<queries>` entry for applications with a launcher icon so
103
+
the per-app screen can list them. It does **not** request
104
+
`QUERY_ALL_PACKAGES`, and the screen says its list is partial rather than
105
+
pretending otherwise.
106
+
107
+
## Children
108
+
109
+
This app is not directed at children and collects nothing from anyone.
110
+
111
+
## Changes
112
+
113
+
This document lives in the repository and changes with it. Its history is the
114
+
change log: `git log docs/privacy.md`.
115
+
116
+
## Contact
117
+
118
+
Security reports: see [`SECURITY.md`](../SECURITY.md). Anything else: the issue
> Checked 2026-08-29, before any submission. **Nothing has been submitted**, and
4
+
> this is a reading of published policy rather than advice or an approval.
5
+
6
+
A VPN app is one of the most heavily conditioned categories a store has, and
7
+
every condition below is one that gets an app removed rather than merely
8
+
delayed. They are recorded now because the expensive ones are architectural: a
9
+
policy discovered at submission that requires a different design is a rewrite,
10
+
and a policy discovered after publication is a removal.
11
+
12
+
## What this app already satisfies
13
+
14
+
| Requirement | Where it stands |
15
+
|---|---|
16
+
| Declare `VpnService` and use the platform's own consent flow |`SomewhereVpnService` extends `VpnService`; Android shows its dialog and the tunnel cannot start without it |
17
+
| A foreground service with a visible ongoing notification | Present, with `specialUse` and its subtype declared — Android has no VPN foreground type, which is why |
18
+
| Do not use `VpnService` to collect, monitor or redirect user traffic for undisclosed purposes | The app is a transport to a server the user configured. It has no upload path of its own |
19
+
| Do not use the VPN to circumvent store policy or bill outside it | No purchases, no billing, no ads |
20
+
| A privacy policy, linked from the listing |[`privacy.md`](privacy.md)|
21
+
| Prominent disclosure of what a sensitive permission is used for | The consent dialog is the platform's; the notification says a tunnel is running; the privacy policy names every permission |
22
+
| No undisclosed reading of installed applications |`<queries>` for launcher-visible apps only, never `QUERY_ALL_PACKAGES`, and the screen says the list is partial |
23
+
| Data safety declaration matching behaviour | Answerable directly from `privacy.md`: nothing is collected, nothing is shared |
24
+
25
+
## What is still open, and who owns it
26
+
27
+
**The package identifier — the requester's, and the only one that cannot be
28
+
undone.**`eu.nodepass.somewhere` uses the organisation's domain rather than one
29
+
this project controls. It is baked into the signing identity, the listing and
30
+
the user data directory, and changing it after publication ships a different app
31
+
with installs and ratings at zero. D-02b says it is free to change until the
32
+
first signed, published build; the first signed build now exists and nothing has
33
+
been published, so this is the moment.
34
+
35
+
**A signing key, and where it lives.** There is none. Whoever creates it holds
36
+
the app's identity for its lifetime: a lost key means no further updates, ever,
37
+
under that identifier.
38
+
39
+
**Encryption export compliance.** The app uses TLS and QUIC and statically links
40
+
aws-lc. Most jurisdictions exempt an application whose cryptography is limited to
41
+
protecting the user's own communications, and store declarations usually ask a
42
+
single yes/no. It is listed here because "we forgot to answer the export
43
+
question" is a common cause of a rejected first submission, not because it is
44
+
expected to be difficult.
45
+
46
+
**A distribution channel (D-05).** Store track, direct APK, or both. Not decided,
47
+
and the answer changes what else is needed: a direct APK needs an update path of
48
+
its own, and a store track needs a developer account, a listing and screenshots.
49
+
50
+
**Whether `QUERY_ALL_PACKAGES` is ever wanted (D-16).** Currently not requested.
51
+
If the launcher-visible list turns out to be materially incomplete on real
52
+
hardware, the fallback is a build flavour split, and a store build would be the
53
+
one without it.
54
+
55
+
## What is deliberately not claimed
56
+
57
+
**This app does not claim to resist censorship, and its listing must not.**
58
+
Nowhere is not an anti-censorship protocol and does not present itself as one:
59
+
the words obfuscation, censorship, active probing and DPI appear zero times in
60
+
upstream's own `docs/protocol.md` and `docs/security.md`. A listing that implied
61
+
otherwise would be both a false claim and, in several jurisdictions, a
62
+
materially riskier one to make.
63
+
64
+
**It is not a "free VPN".** It carries no service. A user brings their own
65
+
Portal, and without one the app does nothing at all. That is worth saying
66
+
plainly on any listing, because the category's reputation is built on apps that
0 commit comments