Sprint Verdict foooverse

Security

Written to answer the Marketplace security self-assessment and the questions a customer's IT team will ask. Most answers are short because the app's architecture removes the question.


Architecture in one line

A Forge app that runs entirely on Atlassian's infrastructure, reads Jira as the app while checking each viewer's own permissions before returning anything, writes one project property, and has no external network access.


Common security questions

Where is customer data stored? Inside the customer's own Atlassian Cloud site. Team configuration is a Jira project property (capacity-tracker.team). Display preferences use Forge storage on Atlassian's infrastructure. The partner operates no storage of any kind.

Does the app send data outside Atlassian? No. The app makes no outbound network requests. There are no external APIs, analytics, error-reporting services or CDNs. The UI bundle is fully self-contained, including its charts — no third-party script is loaded at runtime.

What authentication does the app use? Forge's built-in authentication. Jira calls run as the app (api.asApp()), and the app checks the viewer's own permissions before returning anything: boards are filtered to projects the user can BROWSE_PROJECTS, and every write requires ADMINISTER_PROJECTS on that project, both verified through Jira's bulk permissions API. asUser() is not used because it requires each individual to authorise the app before any call succeeds, which a Custom UI app has no way to prompt for. The viewer's own Jira permissions are enforced by the app on every request instead. The app holds no credentials, API tokens or secrets.

Can a user see data they aren't permitted to see? No, but the mechanism is worth stating accurately rather than reassuringly. Calls run as the app, so Jira is not filtering by the viewer's permissions on its own. The app asks instead: before returning anything, it checks the viewer's own BROWSE_PROJECTS against Jira's permissions API and drops every board they cannot open. A board with no identifiable project is hidden rather than shown — the check fails closed, and src/permissions.test.js pins that behaviour. A user who cannot open a board in Jira cannot see it in the app.

What are the app's permissions and why? See the table below.

Is data encrypted? In transit and at rest by Atlassian, as part of the Forge platform. The app adds no storage of its own to encrypt.

Third-party dependencies? React and React DOM in the UI bundle; @forge/api, @forge/resolver and @forge/bridge from Atlassian. No charting, analytics or utility libraries. The charts are hand-written SVG specifically to keep the dependency surface near zero.

Do you have a vulnerability disclosure process? Yes — see below.


Permissions, and why each one

ScopeWhyRisk if abused
read:jira-workIssues, fields, boards, sprints, project metadata, and the project property the roster lives inRead-only, filtered to projects the viewer can browse
write:jira-workOne of two write scopes. The team roster, leave and holidays, as a project propertyLimited to one named property
manage:jira-projectRequired by the project-property write. Scopes never override Jira's own permissions, so this grants nothing the signed-in user could not already doGated on ADMINISTER_PROJECTS, checked per request
read:jira-userAssignable users, for the "add someone to the team" pickerRead-only
read:project:jiraRequired by GET /rest/agile/1.0/board. Classic read:jira-work does not satisfy itRead-only
read:issue-details:jiraRequired by the Agile backlog and sprint-issue endpointsRead-only
read:jql:jiraRequired by the Agile sprint-issue endpointRead-only
read:board-scope:jira-softwareBoards and backlogs from the Agile APIRead-only
read:sprint:jira-softwareSprints and the issues in themRead-only
storage:appDisplay preferences per boardAtlassian-hosted

Classic scopes for the Jira platform API, granular for everything the Agile API touches. That split is not a style choice. Jira Software supports no classic scopes at all, and its endpoints additionally require granular Jira platform scopes that classic does not satisfy — the board list needs read:project:jira even when read:jira-work is granted. forge lint reports zero errors against manifests that Jira rejects on every call, so the per-endpoint OAuth 2.0 scopes required line in the REST reference is the only source of truth here.

Classic is broader than granular, and that is a real trade. The honest description is that the app reads issues, fields, boards and sprints across the projects a viewer may browse, and writes a single project property.

The narrowing that matters is not in the scope list anyway. It is in src/access.js: every read is filtered to projects the viewer holds BROWSE_PROJECTS on, and every write requires ADMINISTER_PROJECTS, both checked against Jira on each request.

On the write scope

This is the one a reviewer will ask about. The full answer:

> The app writes exactly one Jira project property, capacity-tracker.team, on the project > being viewed. It holds one team per board: the roster, per-person availability settings, > the leave-source configuration and the public holiday calendar. > > This is deliberate rather than convenient: storing that configuration in the customer's own > Jira means they can read it over the REST API, export it, and keep it if the app is > uninstalled. The alternative — Forge storage — would make the customer's configuration the > app's property rather than theirs. > > The app never creates, edits, transitions, comments on or deletes an issue, sprint, board > or project. > > Every write is authorised explicitly. Calls run as the app rather than as the viewer, > because a Custom UI app cannot prompt for the per-user consent asUser() requires. That > removes Jira's own 403 as a safety net, so the app reinstates it: each write resolver first > asks Jira's permissions API whether the requesting account holds ADMINISTER_PROJECTS on > that project, and refuses if not. The check is per request, against Jira, and is not > cacheable or skippable from the browser — requireProjectAdmin in src/index.js gates > every one of them.


Where injection could happen, and what stops it

JQL. The leave-source feature accepts a JQL query, and only from a project admin — the same ADMINISTER_PROJECTS gate as every other write. It is passed to Jira's search API as a parameter, so Jira parses it; the app never concatenates it into anything.

The query runs as the app, not as the viewer, which is worth being precise about: it is bounded by the app's scopes rather than the reader's permissions. Two things contain that. The query is set by a project admin, who could read those issues anyway. And the picker now generates the query from a chosen project rather than expecting one to be typed, so the common path involves no hand-written JQL at all. The worst outcome is a query returning nothing useful, which the Test it dry run surfaces before saving.

CSV. Exported cells beginning =, +, - or @ are prefixed with an apostrophe, so a crafted Jira summary cannot become a formula when the file opens in Excel. This is tested (src/csv.test.js).

Rendered content. The UI renders text as React children, never dangerouslySetInnerHTML. Issue summaries and user names are escaped by React.

Stored configuration. Numeric settings are clamped to sane ranges on save; leave dates are validated and rejected if malformed. Records read from Jira issues are never written back into storage.


Data minimisation


Vulnerability disclosure

Report to support@foooverse.com.

This app does not currently participate in a bug bounty programme. Reports come directly to the address above and are handled by the developer who wrote the code.


Incident response

If a security incident affects customer data:

  1. Take the affected version off the Marketplace
  2. Notify affected customers within 72 hours
  3. Ship a fix and publish what happened
  4. Post a summary on the listing's security tab

There is no escalation chain: the person who receives the report is the person who fixes it. Contact support@foooverse.com for anything security-related, including during an incident. Atlassian is notified in parallel with customers, through the Marketplace security process.


What the app is not

Stated plainly, because it saves a round of questions: