I’m building a Forge app (Confluence Cloud, UI Kit) that audits spaces and helps
admins clean up empty ones. Two of its features have no public API, and I want to
check whether the workaround I’ve built is acceptable for a Marketplace listing
before I invest in the listing itself.
None of these are reachable with OAuth scopes; they authenticate with a browser
session.
What I’ve implemented
The admin pastes their own tenant.session.token, and the app calls those endpoints
server-side. Specifically:
- Stored with Forge’s encrypted secret storage (
storage.setSecret), keyed by the
saving user’s accountId taken from req.context — never from a payload, so no
caller can read another user’s.
- Never returned to the browser. Written once, read only by the backend resolver.
- Configurable TTL (default 12h), deleted automatically on expiry and immediately
when Confluence rejects the session.
- Egress declared as
external.fetch.backend: ['*.atlassian.net'], with a runtime
check that the target really is an https://*.atlassian.net origin before the
cookie is attached, and redirects followed manually so a response can’t replay it
to another host.
- Both features are opt-in toggles, off by default.
My questions
- Would an app that asks an admin to supply their own session cookie, handled as
above, pass Marketplace security review? I’ve read the
App approval guidelines
and the Security guidelines —
neither addresses user-supplied session credentials directly, so I’d rather ask
than assume.
- Separately from the credential question: does calling undocumented
.action
endpoints or internal GraphQL disqualify an app from listing, even where no
public alternative exists?
- If the answer to either is no, is there a supported route to this functionality
that I’ve missed?
I’m intentionally asking before investing further in these features because I don’t want to build something that can’t ultimately be listed. If the recommended guidance is that Marketplace apps should not rely on browser session credentials or undocumented endpoints, I’m happy to exclude these features from the listed version and only ship functionality backed by supported APIs. My goal is to understand whether there’s a supported approach I’ve overlooked.
Hi Alessio,
No on the first one, and it is not on either page you linked. The binding text is the security requirements, Privacy row of every table, item 15 in both Forge ones: an application “must not collect or store credentials belonging to Atlassian user accounts such as user passwords or user API tokens”. Its companion page drops the list and says “The application must not collect Atlassian user credentials”, with the check named as a manual source-code audit. My read is that a session cookie is squarely that, though no Atlassian page I found says the words. Your TTL and origin checks mitigate leakage, and collecting is the banned half.
Before you reach for approval: 89494 is the same design down to the *.atlassian.net egress, and Joe Clark pointed that asker at written approval. CHANGE-2449 closed that to new requests a month later.
Which two endpoints are they? Neither page uses the word undocumented, so I have nothing to show you on your second question, and the ECOHELP ask for app auth needs the names.
Thanks for the detail, it makes sense, and good to know about CHANGE-2449 before we wasted time chasing that.
The two endpoints:
/wiki/pages/listpermissionpages.action?key=... → restricted-pages report
/wiki/spaces/dotrashspace.action?key=... → space trash action
Both are legacy Server/DC .action pages, no OAuth, session-cookie-authenticated only, not in any Atlassian REST/GraphQL spec we’ve found. That’s the pair the ECOHELP app-auth ticket needs.
Given item 15 and CHANGE-2449, we’re treating this design as closed and not pursuing either the cookie approach or written approval further.
Appreciate you digging into it.
also, would it be possible to know if there is any “legit” way to at least count the number of restricted pages present in a space (even without having access to them and only under specific permissions)?
Hi @Alessio, no public API for it, but the product does have the screen. A space admin gets the list at Space settings > Content > Restricted, the closest thing to that .action report.
Over REST it is not exposed. Both v2 list endpoints return only pages the caller has permission to view, neither takes an include-restricted parameter, and restrictions are absent from the v2 spec entirely. The five restriction endpoints are still v1 and each needs view permission on the page you are asking about. A human space admin can see that count. An app identity cannot, and admin key is no way round it: /wiki/api/v2/admin-key says Forge, OAuth 2.0 and Connect apps cannot access the resource.
I’d try /pages/{id}/descendants next. It asks for view permission on the parent only, and omits the viewable-pages sentence both list endpoints carry. I’ve not run it against a restricted child. Have you?
Tested it live. Made a restricted page, nested it under a parent, called both /children and /descendants on the parent as a user excluded from the restriction: empty results on both. Called the same two as a permitted user: the page shows up on both. So the missing “only pages the user has permission to view” sentence on /descendants is a documentation gap, not an actual hole. The restriction is enforced there too.
Thanks for the pointer, was worth ruling out properly rather than assuming either way.
Hi @Alessio, that is the version I could not give you. I was reading the spec, you ran it.
/descendants enforces restrictions the same as /children, then, and the gap sits in the page rather than in the behaviour. I would raise a DOC ticket for it: the /children reference carries the “only pages the user has permission to view” sentence and /descendants does not, and that omission is what made it look like a hole in the first place.