Ordinary Trip Members Can Retrieve share_manage-Protected Public Share Tokens
Summary
TREK v3.4.1 exposes a trip's public share token to any authenticated user who has ordinary read access to that trip. The GET /api/trips/:tripId/share-link handler verifies only trip membership and does not enforce the stronger share_manage permission used by the corresponding create and delete operations.
A low-privileged trip member can therefore retrieve a public bearer token created by the trip owner. The member can then use or distribute that token through the unauthenticated GET /api/shared/:token endpoint. Depending on the owner's sharing settings, this can expose reservations, accommodations, budget information, places, itinerary notes, and collaboration data outside the authenticated trip-membership boundary.
Details
The affected controller is protected by JwtAuthGuard, but that guard establishes only that the requester is authenticated. Authorization to manage or retrieve a public sharing credential must be checked separately.
The vulnerable handler at server/src/nest/share/share.controller.ts:51-57 accepts a user-controlled tripId and performs only the weaker trip-access check:
Core vulnerable code path:
@Get()
get(@CurrentUser() user: User, @Param('tripId') tripId: string) {
if (!this.share.verifyTripAccess(tripId, user.id)) {
throw new HttpException({ error: 'Trip not found' }, 404);
}
const info = this.share.get(tripId);
return info ? info : { token: null };
}
This check confirms that the requester may access the trip, but it does not establish that the requester has the share_manage permission. By contrast, the create and delete paths in the same controller use the stronger management authorization implemented by requireManage()/canManage() at server/src/nest/share/share.controller.ts:21-48,60-64.
The source-to-sink chain is:
- An authenticated ordinary member supplies the victim trip identifier to
GET /api/trips/:tripId/share-link. JwtAuthGuard at server/src/nest/share/share.controller.ts:16-18 verifies identity but not share_manage authorization.
- The handler at
server/src/nest/share/share.controller.ts:51-57 checks only verifyTripAccess(tripId, user.id) and then calls this.share.get(tripId).
- The underlying
getShareLink() logic at server/src/services/shareService.ts:80-91 queries share_tokens by tripId and returns the complete token and sharing flags. This service operation does not receive a user identifier and cannot independently enforce object-level authorization.
- The leaked token can be supplied to the intentionally unauthenticated public route at
server/src/nest/share/share.controller.ts:100-106, corresponding to GET /api/shared/:token.
- The public sharing service resolves the trip from the token and returns data enabled by the owner's sharing flags at
server/src/services/shareService.ts:105-108,239-254.
The trust-boundary failure is the substitution of ordinary trip-read authorization for permission to retrieve a transferable public access credential. Although a member may legitimately read a trip while authenticated, that does not imply authorization to obtain a bearer token that can be copied to arbitrary third parties or continue to work after the member is removed.
POC
Preconditions
- The target is a TREK v3.4.1 instance with two accounts: trip owner A and ordinary user B.
- B has been added to A's trip and can read the trip, but B does not have
share_manage. Under the documented default permission model, share_manage remains at the trip_owner level.
- A has created a public share link. To demonstrate the broader confidentiality impact, A can enable
share_bookings, share_budget, or share_collab.
- The trip ID
42 and hostname below are illustrative and should be replaced with values from the test environment.
Reproduction steps
- As owner A, create a public share link and enable sensitive sharing categories:
POST /api/trips/42/share-link HTTP/1.1
Host: trek.example
Cookie: trek_session=<OWNER_JWT>
Content-Type: application/json
{"share_map":true,"share_bookings":true,"share_budget":true,"share_collab":true}
Expected setup result: HTTP 200 or 201 and a response containing the token created by the owner.
- As ordinary member B, request the share-link management endpoint:
GET /api/trips/42/share-link HTTP/1.1
Host: trek.example
Cookie: trek_session=<MEMBER_JWT>
Accept: application/json
Expected vulnerable result: HTTP 200 with JSON containing the complete token and flags such as share_bookings, share_budget, and share_collab.
Expected secure result: HTTP 403 because B lacks share_manage.
- Remove all authentication information and use only the token disclosed in step 2:
GET /api/shared/<LEAKED_TOKEN> HTTP/1.1
Host: trek.example
Accept: application/json
Expected vulnerable result: HTTP 200 with the victim trip and the categories enabled by the owner, potentially including reservations, accommodations, budget data, collaboration data, days, and places. This demonstrates that the token is a complete anonymous access credential rather than harmless metadata.
Impact
An authenticated low-privileged trip member can convert the owner's public-sharing capability into a transferable anonymous read channel. The exposed bearer token can be used from an unrelated client without a TREK session and can be distributed to an unlimited number of third parties.
The exact confidentiality impact depends on the sharing flags selected by the owner. At maximum verified scope, exposed data can include hotel and transportation reservations, confirmation details, addresses, notes, budget information, itinerary locations, and collaboration messages. A disclosed token can remain valid for up to its configured lifetime, identified in the reviewed context as a default maximum of 90 days, and removing the member from the trip does not automatically invalidate a token already obtained. The token remains usable until it expires or the owner deletes or rotates it.
No direct modification or availability impact was established.
Why the confidentiality metric is Low
The attacker in this advisory is already a trip member, and TREK has no read-side
permissions at all: every key in PERMISSION_ACTIONS
(server/src/nest/permissions/permissions.service.ts) is a write or management
action, and read access is granted solely by membership via canAccessTrip
(server/src/db/database.ts). trip_members carries no role column, so the case
of "a member without budget access meeting a link with share_budget=1" cannot
arise.
The public payload is also a strict subset of what a member already sees:
getSharedTripData() (server/src/services/shareService.ts) returns budget rows
without payers or members, reservations without endpoints or travellers, packing
items filtered to is_private = 0, and collaboration messages without reply
context. The only field it adds is the link creator's default_currency.
The confidentiality metric is therefore scored C:L rather than C:H: what the
attacker gains is a bearer credential they are not authorised to hold, not access
to data that was previously out of reach. The substantive harm is the loss of the
owner's control over who may read the trip.
Remediation
- Change the affected GET handler to invoke the same
requireManage(tripId, user) authorization used by the create and delete operations before calling this.share.get(tripId).
- Do not return a public share token to users who have only ordinary trip-read access.
- Centralize all read, create, update, and delete operations involving public share credentials behind a server-side authorization function that enforces
share_manage.
- Add regression tests confirming that a default ordinary member receives HTTP 403 from
GET /api/trips/:tripId/share-link.
- Add a positive test confirming that access is allowed only when the user has the required management level, including deployments where an administrator has explicitly lowered
share_manage to trip_member.
- Consider rotating existing share tokens when deploying the fix because tokens might already have been disclosed through the vulnerable endpoint. Notify owners if automatic rotation is not feasible.
Disclosure Notes
This report is based on a completed static source-to-sink analysis. The controller authorization mismatch, token-returning service path, and unauthenticated token-consumption path were identified in source code. No request was executed against a live deployment during the recorded audit session, so dynamic confirmation remains recommended.
The reviewed version label is v3.4.1 on the main branch. A specific commit SHA and the status of any vendor fix are to be confirmed. The distinction relevant to disclosure is that ordinary members may legitimately read a trip while authenticated, but they should not receive the owner-controlled, transferable public bearer token.
Supplemental Information
Affected products
liketrek/TREK v3.4.1. Other affected versions are to be confirmed.
Severity
Medium — CVSS v3.1: 4.3 (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N). Exploitation requires an authenticated low-privileged trip member, and only confidentiality is affected. The metric is Low rather than High because that member can already read the same data while authenticated (see below); what the flaw grants is a transferable bearer credential, not new visibility.
- Scoring method: CVSS v3.1
- Score: 6.5
- Vector string: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N
Weaknesses
CWE-862 — Missing Authorization; CWE-639 — Authorization Bypass Through User-Controlled Key.
Ordinary Trip Members Can Retrieve
share_manage-Protected Public Share TokensSummary
TREK v3.4.1 exposes a trip's public share token to any authenticated user who has ordinary read access to that trip. The
GET /api/trips/:tripId/share-linkhandler verifies only trip membership and does not enforce the strongershare_managepermission used by the corresponding create and delete operations.A low-privileged trip member can therefore retrieve a public bearer token created by the trip owner. The member can then use or distribute that token through the unauthenticated
GET /api/shared/:tokenendpoint. Depending on the owner's sharing settings, this can expose reservations, accommodations, budget information, places, itinerary notes, and collaboration data outside the authenticated trip-membership boundary.Details
The affected controller is protected by
JwtAuthGuard, but that guard establishes only that the requester is authenticated. Authorization to manage or retrieve a public sharing credential must be checked separately.The vulnerable handler at
server/src/nest/share/share.controller.ts:51-57accepts a user-controlledtripIdand performs only the weaker trip-access check:Core vulnerable code path:
This check confirms that the requester may access the trip, but it does not establish that the requester has the
share_managepermission. By contrast, the create and delete paths in the same controller use the stronger management authorization implemented byrequireManage()/canManage()atserver/src/nest/share/share.controller.ts:21-48,60-64.The source-to-sink chain is:
GET /api/trips/:tripId/share-link.JwtAuthGuardatserver/src/nest/share/share.controller.ts:16-18verifies identity but notshare_manageauthorization.server/src/nest/share/share.controller.ts:51-57checks onlyverifyTripAccess(tripId, user.id)and then callsthis.share.get(tripId).getShareLink()logic atserver/src/services/shareService.ts:80-91queriesshare_tokensbytripIdand returns the complete token and sharing flags. This service operation does not receive a user identifier and cannot independently enforce object-level authorization.server/src/nest/share/share.controller.ts:100-106, corresponding toGET /api/shared/:token.server/src/services/shareService.ts:105-108,239-254.The trust-boundary failure is the substitution of ordinary trip-read authorization for permission to retrieve a transferable public access credential. Although a member may legitimately read a trip while authenticated, that does not imply authorization to obtain a bearer token that can be copied to arbitrary third parties or continue to work after the member is removed.
POC
Preconditions
share_manage. Under the documented default permission model,share_manageremains at thetrip_ownerlevel.share_bookings,share_budget, orshare_collab.42and hostname below are illustrative and should be replaced with values from the test environment.Reproduction steps
Expected setup result: HTTP 200 or 201 and a response containing the token created by the owner.
Expected vulnerable result: HTTP 200 with JSON containing the complete
tokenand flags such asshare_bookings,share_budget, andshare_collab.Expected secure result: HTTP 403 because B lacks
share_manage.Expected vulnerable result: HTTP 200 with the victim trip and the categories enabled by the owner, potentially including reservations, accommodations, budget data, collaboration data, days, and places. This demonstrates that the token is a complete anonymous access credential rather than harmless metadata.
Impact
An authenticated low-privileged trip member can convert the owner's public-sharing capability into a transferable anonymous read channel. The exposed bearer token can be used from an unrelated client without a TREK session and can be distributed to an unlimited number of third parties.
The exact confidentiality impact depends on the sharing flags selected by the owner. At maximum verified scope, exposed data can include hotel and transportation reservations, confirmation details, addresses, notes, budget information, itinerary locations, and collaboration messages. A disclosed token can remain valid for up to its configured lifetime, identified in the reviewed context as a default maximum of 90 days, and removing the member from the trip does not automatically invalidate a token already obtained. The token remains usable until it expires or the owner deletes or rotates it.
No direct modification or availability impact was established.
Why the confidentiality metric is Low
The attacker in this advisory is already a trip member, and TREK has no read-side
permissions at all: every key in
PERMISSION_ACTIONS(
server/src/nest/permissions/permissions.service.ts) is a write or managementaction, and read access is granted solely by membership via
canAccessTrip(
server/src/db/database.ts).trip_memberscarries no role column, so the caseof "a member without budget access meeting a link with
share_budget=1" cannotarise.
The public payload is also a strict subset of what a member already sees:
getSharedTripData()(server/src/services/shareService.ts) returns budget rowswithout payers or members, reservations without endpoints or travellers, packing
items filtered to
is_private = 0, and collaboration messages without replycontext. The only field it adds is the link creator's
default_currency.The confidentiality metric is therefore scored
C:Lrather thanC:H: what theattacker gains is a bearer credential they are not authorised to hold, not access
to data that was previously out of reach. The substantive harm is the loss of the
owner's control over who may read the trip.
Remediation
requireManage(tripId, user)authorization used by the create and delete operations before callingthis.share.get(tripId).share_manage.GET /api/trips/:tripId/share-link.share_managetotrip_member.Disclosure Notes
This report is based on a completed static source-to-sink analysis. The controller authorization mismatch, token-returning service path, and unauthenticated token-consumption path were identified in source code. No request was executed against a live deployment during the recorded audit session, so dynamic confirmation remains recommended.
The reviewed version label is v3.4.1 on the
mainbranch. A specific commit SHA and the status of any vendor fix are to be confirmed. The distinction relevant to disclosure is that ordinary members may legitimately read a trip while authenticated, but they should not receive the owner-controlled, transferable public bearer token.Supplemental Information
Affected products
liketrek/TREK v3.4.1. Other affected versions are to be confirmed.
Severity
Medium — CVSS v3.1: 4.3 (
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N). Exploitation requires an authenticated low-privileged trip member, and only confidentiality is affected. The metric is Low rather than High because that member can already read the same data while authenticated (see below); what the flaw grants is a transferable bearer credential, not new visibility.Weaknesses
CWE-862 — Missing Authorization; CWE-639 — Authorization Bypass Through User-Controlled Key.