Describe the bug
On every provider node, each incoming session produces one ERR core/quality/mysterium_morqa.go:212 > Failed to sign metrics event error="failed to sign metrics event: authentication needed: password or unlock".
The identity is unlocked. The node is asked to sign a metrics batch as the consumer's address, whose private key it does not hold.
The trace stage "Session validation" is the only one of 22 trace stages without a Provider/Consumer prefix. The MORQA mapper decides who owns an event from the stage-name prefix, so it attributes this provider-side stage to the consumer. The filesystem keystore answers ErrLocked for any address that is not in its unlocked map, which produces the misleading "password or unlock" message.
Previously reported as #6002 (March 2024, same error at mysterium_morqa.go:212). It was closed by the stale bot without investigation. This report provides the root cause.
Affected version: 1.39.6 (commit c45527af). The code path has been present since 1.21.0 (May 2023).
Component: core/quality (MORQA metrics) × core/service (session tracing)
Severity: Low (functional impact) / Medium (log noise and misleading error for every provider operator)
To Reproduce
Steps to reproduce the behavior:
- Run a provider node (any version since
1.21.0) as the mysterium-node systemd service, with an unlocked identity.
- Wait for any consumer to establish a session.
- Run
journalctl -u mysterium-node | grep "Failed to sign metrics event". One error appears per incoming session.
- Optional: compare per-day counts with
grep "session ref incr for" (one line per provider session). They match 1:1.
Expected behavior
Provider-side trace metrics, including Session validation, are attributed to and signed with the provider's own (unlocked) identity, with IsProvider: true. No Failed to sign metrics event error is logged.
Screenshots
N/A. Log excerpts are included under Additional context.
Environment (please complete the following information):
- Node version: 1.39.6 (build 35291316599, commit c45527a). The code path is unchanged since 1.21.0.
- OS: Debian 13 (trixie) x86_64 (VPS); Parrot Security 7.3 x86_64 (laptop); Debian 13 (trixie) aarch64 (Raspberry Pi). All run the
mysterium-node systemd service.
- Desktop app version (if applicable): N/A
- Docker (if applicable): N/A
- Your identity (if applicable):
0x45dbb4bf16ddeaf3ed7eb32fbeb5cff28faa5ef7 (VPS), 0xaf447d38e8a51975d37d19ff9bf477ba36128663 (laptop), 0x31e1c280033fa5b37d412a85c0f1ea271d29c0a5 (Pi)
- Payment (top-op) reference (if applicable): N/A
Additional context
Root cause
1. A provider-side stage without the Provider prefix
core/service/session_manager.go#L178
go func() {
trace := session.tracer.StartStage("Session validation")
validationError = manager.validateSession(session, prices)
session.tracer.EndStage(trace)
validationWG.Done()
}()
This runs in the provider's session manager, next to "Provider session create", "Provider session create (start)", (payment) and (configure).
All trace stages in the repository (non-test code, 1.39.6):
| Prefix |
Stages |
Consumer … |
11 |
Provider … |
10 |
| none |
1: Session validation |
2. The owner is chosen by stage-name prefix
core/quality/morqa_transport.go#L278-L283
func traceEventToMetricsEvent(ctx sessionTraceContext) (string, *metrics.Event) {
sender, target, isProvider, country := ctx.Consumer, ctx.Provider, false, ctx.ProviderCountry
// TODO Remove this workaround by generating&signing&publishing `metrics.Event` in same place
if strings.HasPrefix(ctx.Stage, "Provider") {
sender, target, isProvider, country = ctx.Provider, ctx.Consumer, true, ctx.ConsumerCountry
}
return sender, &metrics.Event{ ... }
}
For "Session validation":
sender is ctx.Consumer, which becomes the batch owner.
IsProvider is false and TargetId is the provider, so the event is reported to MORQA as if the consumer had sent it.
3. The node signs as the owner
core/quality/mysterium_morqa.go#L139
signature, err := m.signer(identity.FromAddress(owner)).Sign(bin)
4. The keystore returns ErrLocked for any address it hasn't unlocked, including unknown ones
identity/keystore_filesystem.go#L218-L229
unlockedKey, found := ks.unlocked[a.Address]
if !found {
return nil, ethKs.ErrLocked // "authentication needed: password or unlock"
}
The consumer's address is never in the provider's keystore, so the result is always ErrLocked. The message makes operators look for a passphrase or unlock problem that does not exist.
5. The batch is sent anyway, unsigned
core/quality/mysterium_morqa.go#L210-L217
signature, err := m.signBatch(owner, batch)
if err != nil {
log.Error().Err(err).Msg("Failed to sign metrics event") // no return
}
sb := &metrics.SignedBatch{
Signature: signature, // "" on error
Batch: batch,
}
The request is still sent with an empty signature. On all three nodes below, Failed to sent batch metrics request never appears, so the MORQA server accepts these unsigned batches without returning an error.
Call chain
core/service/session_manager.go:178 StartStage("Session validation") [provider]
→ trace.AppTopicTraceEvent
→ core/quality/sender.go:400 sendTraceEvent()
→ core/quality/morqa_transport.go:278 traceEventToMetricsEvent() owner = ctx.Consumer
→ core/quality/mysterium_morqa.go:139 signer(consumer).Sign()
→ identity/keystore_filesystem.go:225 ErrLocked
→ core/quality/mysterium_morqa.go:212 ERR "Failed to sign metrics event"
Why the existing tests don't catch it
| Test |
What it does |
Why it misses the bug |
core/quality/morqa_transport_test.go#L44 |
Uses identity.SignerFake{} as signer factory |
SignerFake signs successfully for any identity, so a wrong owner can never fail |
identity/keystore_mock.go#L81-L92 |
Mock keystore returns ErrNoMatch for unknown addresses |
Production returns ErrLocked for the same case, so mock and real keystore disagree |
core/service/session_manager_test.go#L142, #L189 |
Asserts the provider trace sequence includes "Session validation" |
Confirms the stage is provider-side, but nothing checks how the mapper attributes it |
| — |
No test for traceEventToMetricsEvent |
The prefix rule is untested |
The session manager test treats the stage as part of the provider flow, and the mapper's own TODO calls the prefix check a workaround. Both indicate the behaviour is not intentional. The stage was added three years after the prefix convention without following it.
Timeline
| Date |
Commit |
Change |
| 2020-05-05 |
03df488c8 |
"Unlock decrypts keystore once": SignHash returns ErrLocked for any address not in the unlocked map |
| 2020-06-29 |
4104f0c88 |
"Send session trace from the consumer": prefix-based owner workaround introduced |
| 2023-05-16 |
3d4676ac |
"Replace policy fetching with specific identity check request": adds StartStage("Session validation") without prefix |
| 2023-05-22 |
tag 1.21.0 |
First release containing the bug |
| 2026-08-17 |
tag 1.39.6 |
Still present |
Evidence from three independent provider nodes
Node details
| Host |
Arch |
OS |
Version |
Identities in keystore |
--identity.passphrase |
Log TZ |
| project-vps |
x86_64 |
Debian 13 (trixie), 6.12.107 cloud |
1.39.6 / c45527a |
1 |
not set |
UTC |
| parrot |
x86_64 |
Parrot Security 7.3 |
1.39.6 / c45527a |
1 |
set |
CEST |
| crypto (Raspberry Pi) |
aarch64 |
Debian 13 (trixie), 6.18.50+rpt-rpi-v8 |
1.39.6 / c45527a |
1 |
set |
CEST |
All nodes are installed as the mysterium-node.service systemd unit, with persistent journald. Each keystore holds exactly one UTC--… key file (owner mysterium-node, mode 0600), and it matches the unlocked address.
One error per incoming session
The per-day count of Failed to sign metrics event is compared with session ref incr for (logged once per provider session, right after the Session validation stage starts at session_manager.go#L190).
| Host |
Date |
Sign errors |
New sessions |
| project-vps |
2026-09-16 |
10 |
10 |
| project-vps |
2026-09-17 |
14 |
14 |
| project-vps |
2026-09-18 |
19 |
20 |
| project-vps |
2026-09-19 |
12 |
11 |
| project-vps |
2026-09-20 |
10 |
9 |
| project-vps |
2026-09-21 |
8 |
6 |
| parrot |
2026-09-19 |
10 |
10 |
| parrot |
2026-09-20 |
60 |
60 |
| parrot |
2026-09-21 |
26 |
27 |
| crypto |
2026-09-21 |
9 |
10 |
The counts track 1:1. The small ±1–2 offsets come from batching across midnight: sessions near day-end are flushed after 00:00, and the current day was still in progress.
Present since day one on every node
The first error in each journal appears within minutes of the oldest retained node entry: VPS +7 s, parrot +8.5 min, Pi +3 min. No error-free period precedes it.
Older logs are gone. On the VPS and the laptop, journald is at its size cap (SystemMaxUse=500M, usage 500.5M and 470.6M) and continuously rotates old entries out. On the Pi, the persistent journal only starts at the current boot. /var/log/mysterium-node/ exists on all three nodes but is empty.
The missing logs do not leave the start date open. Every identity was created long after the bug shipped in 1.21.0 (2023-05-22), as shown by the key file names:
| Host |
Identity created (keystore file name) |
| parrot |
2026-01-05 |
| project-vps |
2026-03-28 |
| crypto (Pi) |
2026-05-18 |
Every node has therefore run an affected version since its first session. The combination of the code path (unchanged since 2023), the 1:1 session correlation, and the error being present from the first retained log line confirms the error has occurred from day one on each node. It only became noticeable recently because earlier logs had already been rotated away.
Identity is unlocked when the errors occur
project-vps (UTC)
Sep 21 12:27:55 project-vps systemd[1]: Started mysterium-node.service - Server for Mysterium - decentralised VPN Network.
Sep 21 12:28:01 project-vps myst[1184]: 2026-09-21T12:28:01.303 INF cmd/commands/service/command.go:135 > Unlocked identity: 0x45dbb4bf16ddeaf3ed7eb32fbeb5cff28faa5ef7
Sep 21 12:28:28 project-vps myst[1184]: 2026-09-21T12:28:28.863 ERR core/quality/mysterium_morqa.go:212 > Failed to sign metrics event error="failed to sign metrics event: authentication needed: password or unlock"
Sep 21 12:38:29 project-vps myst[1184]: 2026-09-21T12:38:29.781 ERR core/quality/mysterium_morqa.go:212 > Failed to sign metrics event error="failed to sign metrics event: authentication needed: password or unlock"
parrot (CEST)
Sep 21 08:37:57 parrot systemd[1]: Started mysterium-node.service - Server for Mysterium - decentralised VPN Network.
Sep 21 08:38:27 parrot myst[3316]: 2026-09-21T08:38:27.935 DBG identity/selector/handler.go:108 > Unlocked identity: 0xaf447d38e8a51975d37d19ff9bf477ba36128663
Sep 21 08:38:27 parrot myst[3316]: 2026-09-21T08:38:27.938 INF cmd/commands/service/command.go:135 > Unlocked identity: 0xaf447d38e8a51975d37d19ff9bf477ba36128663
Sep 21 14:41:52 parrot myst[3316]: 2026-09-21T14:41:52.728 ERR core/quality/mysterium_morqa.go:212 > Failed to sign metrics event error="failed to sign metrics event: authentication needed: password or unlock"
Sep 21 14:42:53 parrot myst[3316]: 2026-09-21T14:42:53.036 ERR core/quality/mysterium_morqa.go:212 > Failed to sign metrics event error="failed to sign metrics event: authentication needed: password or unlock"
Sep 21 14:47:24 parrot myst[3316]: 2026-09-21T14:47:24.140 ERR core/quality/mysterium_morqa.go:212 > Failed to sign metrics event error="failed to sign metrics event: authentication needed: password or unlock"
crypto / Raspberry Pi (CEST)
Sep 21 12:54:12 crypto systemd[1]: Started mysterium-node.service - Server for Mysterium - decentralised VPN Network.
Sep 21 12:54:17 crypto myst[2208]: 2026-09-21T12:54:17.440 DBG identity/selector/handler.go:108 > Unlocked identity: 0x31e1c280033fa5b37d412a85c0f1ea271d29c0a5
Sep 21 12:54:17 crypto myst[2208]: 2026-09-21T12:54:17.442 INF cmd/commands/service/command.go:135 > Unlocked identity: 0x31e1c280033fa5b37d412a85c0f1ea271d29c0a5
Sep 21 14:11:05 crypto myst[2208]: 2026-09-21T14:11:05.372 ERR core/quality/mysterium_morqa.go:212 > Failed to sign metrics event error="failed to sign metrics event: authentication needed: password or unlock"
Sep 21 14:17:39 crypto myst[2208]: 2026-09-21T14:17:39.963 ERR core/quality/mysterium_morqa.go:212 > Failed to sign metrics event error="failed to sign metrics event: authentication needed: password or unlock"
Sep 21 14:26:19 crypto myst[2208]: 2026-09-21T14:26:19.407 ERR core/quality/mysterium_morqa.go:212 > Failed to sign metrics event error="failed to sign metrics event: authentication needed: password or unlock"
Local causes ruled out
| Hypothesis |
Check |
Result |
| Stale or multiple identities |
ls -la /var/lib/mysterium-node/keystore/ |
Exactly 1 key file per node, matches unlocked address |
| Passphrase / unlock problem |
--identity.passphrase presence + Unlocked identity log line |
Occurs both with and without passphrase; unlock succeeds on all nodes |
| Startup race |
Error timestamps vs. unlock |
Errors up to 6+ h after unlock, same PID |
| Platform-specific |
Arch / distro |
x86_64 + aarch64, Debian 13 + Parrot 7.3 |
| MORQA server blocked |
curl https://quality.mysterium.network/api/v3/, iptables/ufw, nftables, CrowdSec, fail2ban |
Reachable from all nodes (HTTP 404 on base path); both resolved IPs (188.245.149.150, 116.203.130.98) have 0 matches in every block list |
| Batch rejected by server |
grep -c "Failed to sent batch metrics request" |
0 on all nodes |
Signing happens entirely locally before any network call, so network conditions cannot cause this error. They are listed for completeness.
Impact
- Log noise: one
ERR per session on every provider node, with a message that points operators to a non-existent passphrase or unlock problem.
- Misattributed metrics: the provider's validation timing is reported with
IsProvider: false, TargetId: <provider> and the consumer's ID as sender, so MORQA stores provider-side data as consumer-reported.
- Unsigned batches accepted: these batches reach MORQA without a signature, and the server does not reject them. Whether the server should accept unsigned batches is a separate question.
- Earnings and sessions: not affected.
Suggested fix
Minimal: prefix the stage so the existing convention holds.
- trace := session.tracer.StartStage("Session validation")
+ trace := session.tracer.StartStage("Provider session validation")
This also requires updating the expected sequences in core/service/session_manager_test.go (L142, L189). Check any MORQA-side dashboards or queries keyed on the old stage name.
Proper: resolve the existing TODO. Carry IsProvider (or the owner identity) explicitly in trace.Event / sessionTraceContext instead of deriving it from the stage name. A future stage name can then no longer silently flip ownership.
Hardening (optional):
- In
sendMetrics, return or skip on signing failure instead of posting an empty signature.
- Add a test for
traceEventToMetricsEvent covering every stage name, or asserting that every StartStage literal carries a Provider/Consumer prefix.
- Align
keystore_mock.SignHash with production (ErrLocked vs ErrNoMatch), or use a signer factory in MORQA tests that fails for identities other than the expected owner.
Reported by Ian Johnsons (Telegram: @IanJohnsons). Analysis based on tag 1.39.6 source and journald output from three independent provider nodes.
Describe the bug
On every provider node, each incoming session produces one
ERR core/quality/mysterium_morqa.go:212 > Failed to sign metrics event error="failed to sign metrics event: authentication needed: password or unlock".The identity is unlocked. The node is asked to sign a metrics batch as the consumer's address, whose private key it does not hold.
The trace stage
"Session validation"is the only one of 22 trace stages without aProvider/Consumerprefix. The MORQA mapper decides who owns an event from the stage-name prefix, so it attributes this provider-side stage to the consumer. The filesystem keystore answersErrLockedfor any address that is not in its unlocked map, which produces the misleading "password or unlock" message.Affected version: 1.39.6 (commit
c45527af). The code path has been present since 1.21.0 (May 2023).Component:
core/quality(MORQA metrics) ×core/service(session tracing)Severity: Low (functional impact) / Medium (log noise and misleading error for every provider operator)
To Reproduce
Steps to reproduce the behavior:
1.21.0) as themysterium-nodesystemd service, with an unlocked identity.journalctl -u mysterium-node | grep "Failed to sign metrics event". One error appears per incoming session.grep "session ref incr for"(one line per provider session). They match 1:1.Expected behavior
Provider-side trace metrics, including
Session validation, are attributed to and signed with the provider's own (unlocked) identity, withIsProvider: true. NoFailed to sign metrics eventerror is logged.Screenshots
N/A. Log excerpts are included under Additional context.
Environment (please complete the following information):
mysterium-nodesystemd service.0x45dbb4bf16ddeaf3ed7eb32fbeb5cff28faa5ef7(VPS),0xaf447d38e8a51975d37d19ff9bf477ba36128663(laptop),0x31e1c280033fa5b37d412a85c0f1ea271d29c0a5(Pi)Additional context
Root cause
1. A provider-side stage without the
Providerprefixcore/service/session_manager.go#L178This runs in the provider's session manager, next to
"Provider session create","Provider session create (start)",(payment)and(configure).All trace stages in the repository (non-test code, 1.39.6):
Consumer …Provider …Session validation2. The owner is chosen by stage-name prefix
core/quality/morqa_transport.go#L278-L283For
"Session validation":senderisctx.Consumer, which becomes the batch owner.IsProviderisfalseandTargetIdis the provider, so the event is reported to MORQA as if the consumer had sent it.3. The node signs as the owner
core/quality/mysterium_morqa.go#L1394. The keystore returns
ErrLockedfor any address it hasn't unlocked, including unknown onesidentity/keystore_filesystem.go#L218-L229The consumer's address is never in the provider's keystore, so the result is always
ErrLocked. The message makes operators look for a passphrase or unlock problem that does not exist.5. The batch is sent anyway, unsigned
core/quality/mysterium_morqa.go#L210-L217The request is still sent with an empty signature. On all three nodes below,
Failed to sent batch metrics requestnever appears, so the MORQA server accepts these unsigned batches without returning an error.Call chain
Why the existing tests don't catch it
core/quality/morqa_transport_test.go#L44identity.SignerFake{}as signer factorySignerFakesigns successfully for any identity, so a wrong owner can never failidentity/keystore_mock.go#L81-L92ErrNoMatchfor unknown addressesErrLockedfor the same case, so mock and real keystore disagreecore/service/session_manager_test.go#L142,#L189"Session validation"traceEventToMetricsEventThe session manager test treats the stage as part of the provider flow, and the mapper's own
TODOcalls the prefix check a workaround. Both indicate the behaviour is not intentional. The stage was added three years after the prefix convention without following it.Timeline
03df488c8SignHashreturnsErrLockedfor any address not in the unlocked map4104f0c883d4676acStartStage("Session validation")without prefix1.21.01.39.6Evidence from three independent provider nodes
Node details
--identity.passphraseAll nodes are installed as the
mysterium-node.servicesystemd unit, with persistent journald. Each keystore holds exactly oneUTC--…key file (ownermysterium-node, mode0600), and it matches the unlocked address.One error per incoming session
The per-day count of
Failed to sign metrics eventis compared withsession ref incr for(logged once per provider session, right after theSession validationstage starts atsession_manager.go#L190).The counts track 1:1. The small ±1–2 offsets come from batching across midnight: sessions near day-end are flushed after 00:00, and the current day was still in progress.
Present since day one on every node
The first error in each journal appears within minutes of the oldest retained node entry: VPS +7 s, parrot +8.5 min, Pi +3 min. No error-free period precedes it.
Older logs are gone. On the VPS and the laptop, journald is at its size cap (
SystemMaxUse=500M, usage 500.5M and 470.6M) and continuously rotates old entries out. On the Pi, the persistent journal only starts at the current boot./var/log/mysterium-node/exists on all three nodes but is empty.The missing logs do not leave the start date open. Every identity was created long after the bug shipped in
1.21.0(2023-05-22), as shown by the key file names:Every node has therefore run an affected version since its first session. The combination of the code path (unchanged since 2023), the 1:1 session correlation, and the error being present from the first retained log line confirms the error has occurred from day one on each node. It only became noticeable recently because earlier logs had already been rotated away.
Identity is unlocked when the errors occur
project-vps (UTC)
parrot (CEST)
crypto / Raspberry Pi (CEST)
Local causes ruled out
ls -la /var/lib/mysterium-node/keystore/--identity.passphrasepresence +Unlocked identitylog linecurl https://quality.mysterium.network/api/v3/, iptables/ufw, nftables, CrowdSec, fail2ban188.245.149.150,116.203.130.98) have 0 matches in every block listgrep -c "Failed to sent batch metrics request"Signing happens entirely locally before any network call, so network conditions cannot cause this error. They are listed for completeness.
Impact
ERRper session on every provider node, with a message that points operators to a non-existent passphrase or unlock problem.IsProvider: false,TargetId: <provider>and the consumer's ID as sender, so MORQA stores provider-side data as consumer-reported.Suggested fix
Minimal: prefix the stage so the existing convention holds.
This also requires updating the expected sequences in
core/service/session_manager_test.go(L142, L189). Check any MORQA-side dashboards or queries keyed on the old stage name.Proper: resolve the existing
TODO. CarryIsProvider(or the owner identity) explicitly intrace.Event/sessionTraceContextinstead of deriving it from the stage name. A future stage name can then no longer silently flip ownership.Hardening (optional):
sendMetrics, return or skip on signing failure instead of posting an empty signature.traceEventToMetricsEventcovering every stage name, or asserting that everyStartStageliteral carries aProvider/Consumerprefix.keystore_mock.SignHashwith production (ErrLockedvsErrNoMatch), or use a signer factory in MORQA tests that fails for identities other than the expected owner.Reported by Ian Johnsons (Telegram: @IanJohnsons). Analysis based on tag
1.39.6source and journald output from three independent provider nodes.