Summary
The Amplenote note-sharing API is vulnerable to account enumeration because its response differs depending on whether the email address supplied as a sharing target belongs to an existing Amplenote account.
An authenticated user with permission to share a note can submit arbitrary email addresses to:
POST /v4/notes/{note_uuid}/shares
The endpoint produces three reliably distinguishable responses:
Target email Response Meaning
Existing Amplenote account HTTP 201 Created Account exists
Requester's own account HTTP 403 Forbidden Account exists and is the requester
Non-existent account HTTP 404 Not Found Account does not exist
For existing accounts, the 201 response additionally exposes an internal account-related UUID.
This creates a deterministic account-existence oracle that can be automated to validate whether arbitrary email addresses are registered with Amplenote.
Affected Endpoint
POST https:
Authentication: OAuth Bearer token required
Required privilege: Authenticated account with permission to share the specified note
Steps to Reproduce
1. Authenticate
Obtain an OAuth Bearer token for an Amplenote account through the normal OAuth authorization flow.
Use the token in subsequent requests:
Authorization: Bearer <OAUTH_TOKEN>
2. Use a shareable note
Use a note for which the authenticated account has share permission.
3. Test an email belonging to an existing Amplenote account
curl -i -X POST \
"https://api.amplenote.com/v4/notes/d8351a84-a5fb-11f1-b652-e58698769b69/shares" \
-H "Authorization: Bearer <OAUTH_TOKEN>" \
-H "Content-Type: application/json" \
-d '{"message":"test","permissions":{"share":false,"edit":false},"email":"aligoodluck427@gmail.com"}'
The server responds:
HTTP/1.1 201 Created
Content-Type: application/json
{
"uuid":"f482b752-33f8-11f1-a055-4f0c5ac8536e",
"sharer":"b8c0b9ae-33fc-11f1-84e1-854afadf3235",
"created":1788265309,
"permissions":{
"edit":false,
"share":false
},
"sources":[
{
"account":"b8c0b9ae-33fc-11f1-84e1-854afadf3235",
"tag":null
}
]
}
The 201 Created response confirms that the supplied email corresponds to an Amplenote account.
The response also contains an internal UUID associated with the created share/target relationship.
4. Test the requester's own account
curl -i -X POST \
"https://api.amplenote.com/v4/notes/d8351a84-a5fb-11f1-b652-e58698769b69/shares" \
-H "Authorization: Bearer <OAUTH_TOKEN>" \
-H "Content-Type: application/json" \
-d '{"message":"test","permissions":{"share":false,"edit":false},"email":"memedypk@gmail.com"}'
Response:
HTTP/1.1 403 Forbidden
{"error":"forbidden"}
This is another distinguishable response indicating that the email belongs to the authenticated account.
5. Test an email that does not have an Amplenote account
curl -i -X POST \
"https://api.amplenote.com/v4/notes/d8351a84-a5fb-11f1-b652-e58698769b69/shares" \
-H "Authorization: Bearer <OAUTH_TOKEN>" \
-H "Content-Type: application/json" \
-d '{"message":"test","permissions":{"share":false,"edit":false},"email":"thisuserdoesnotexist9f3k2@nowhere.invalid"}'
Response:
HTTP/1.1 404 Not Found
{"error":"not_found"}
The difference between 201 and 404 provides a reliable account-existence oracle.
Security Impact
An attacker only needs a normal authenticated Amplenote account and a shareable note to query arbitrary email addresses.
For example:
attacker@example.com → 404 → likely not registered
victim@example.com → 201 → registered Amplenote account
Because the responses are deterministic, the behavior can be automated against large email lists.
1. User/account enumeration
An attacker can determine whether specific individuals or email addresses have Amplenote accounts.
This can be useful for:
validating leaked or externally obtained email lists;
identifying which employees use Amplenote;
correlating email addresses with Amplenote accounts;
preparing targeted phishing or social-engineering campaigns.
2. Internal identifier disclosure
Successful requests return UUID information in the API response.
Although the UUID alone does not appear to provide authentication or direct access to private notes, exposing internal account/share identifiers unnecessarily gives an attacker additional information for account-bound reconnaissance and potentially makes future vulnerabilities easier to exploit.
3. Share-abuse potential
A successful request creates a real share.
Consequently, enumeration is not purely a passive lookup: when the target exists, the operation can create a share and potentially generate a notification to the target.
This makes the endpoint potentially useful for targeted share spam or phishing pretexts in addition to enumeration.
4. Lack of observed rate limiting
During testing, repeated requests against non-existent addresses returned 404 Not Found without an apparent rate-limit response.
This could make automated enumeration more practical.
No attempt was made to perform high-volume or disruptive testing.
Proof of Concept
The vulnerability can be demonstrated with three requests:
Existing account
│
▼
POST /v4/notes/{note_uuid}/shares
│
▼
HTTP 201 Created
│
└── Account exists
Non-existing account
│
▼
POST /v4/notes/{note_uuid}/shares
│
▼
HTTP 404 Not Found
│
└── Account does not exist
Authenticated user's own account
│
▼
POST /v4/notes/{note_uuid}/shares
│
▼
HTTP 403 Forbidden
│
└── Account exists / self-share
The response differences are sufficient to distinguish the states without needing access to the target user's account or private data.
Additional Verification
The oracle was also tested against other known addresses, including:
test@test.com
a@a.com
These addresses produced the successful 201 Created behavior, confirming that the behavior is not specific to a single account.
Testing was performed using authorized accounts and without accessing third-party private content.
Severity Assessment
Suggested severity: Low
The issue requires an authenticated Amplenote account with permission to share a note and does not, by itself, provide access to the target user's notes, password, session, or other private account data.
However, the endpoint provides a deterministic mechanism for confirming Amplenote account existence and additionally performs an actual share operation for existing accounts.
The absence of an apparent rate limit increases the practicality of automated enumeration.
Remediation
I recommend normalizing the behavior of the endpoint so that the existence of the target account cannot be inferred.
Possible approaches include:
Return the same externally observable response regardless of whether the email belongs to an existing account.
Avoid returning target/account-specific identifiers where they are not required by the client.
Consider making the self-share response indistinguishable from other invalid/unsupported targets.
Apply appropriate rate limiting and abuse detection to repeated share attempts.
Consider requiring additional confirmation before repeatedly creating shares to arbitrary email addresses.
The key requirement is that an attacker should not be able to distinguish:
registered email
vs.
unregistered email
based solely on the API's response.