Crosstab

Security — Crosstab

A security policy for an app with no servers is a shorter document than the phrase suggests, and pretending otherwise would be the first thing wrong with it. Crosstab runs entirely inside Atlassian's platform. There is no backend of ours, no database of ours, no network of ours, and nothing to breach that is not Atlassian's own infrastructure.

What follows is what that means in practice, what is checked before anything ships, and what to do if you find something.

This page is about how the app is built and operated. The privacy policy covers what is read, stored and kept, and for how long, and the method page prints the whole arithmetic.

What you can verify without trusting us

The source is not public, so the honest version of every claim below is narrower than it sounds. Three things you can check yourself, before installing:

The manifest. Atlassian shows it to you on the consent screen. A Forge app declares every external host it may reach, and a host it did not declare it cannot reach — the platform refuses the request. This app declares none.

The scopes. There are twenty read scopes and one storage scope, and they are granular rather than one blanket read:jira-work. That is more lines to read, not fewer, and it is the point: a granular list can be checked against what the app claims to do, and read:jira-work cannot. Some of the length is Atlassian's bundling rather than ours — reading a saved filter's id and name costs nine scopes, six of which nothing else here asks for, and among them are read:user:jira and read:group:jira in a gadget that will not draw an axis by person.

That there is no scheduled trigger. The manifest declares no background job of any kind. This app runs when somebody opens a dashboard carrying the gadget, and at no other time.

Where the data lives

Nowhere it was not already.

The code runs in a Forge function inside Atlassian's infrastructure, and the interface runs in an iframe in the reader's own browser. What the function reads it turns into totals and hands to that iframe. It does not write those issues anywhere, and there is nowhere of ours for it to write them to.

The one thing that is stored is described below, and it is stored in your own site's app storage, which is inside Atlassian's infrastructure and under your agreement with them.

What is read, and what is kept

Read: the issues your configuration selects, and on each of them the two axis fields, the numeric field being added up, the issue type and — when an axis is status — the status. Nothing else on the issue. Not the summary, not the description, not the comments.

Kept: nothing at all until somebody presses Keep this. After that, this app's own totals and the four choices that produced them. No issue keys. No field values beyond the labels already drawn on the axes, which are things like Bug, In Progress and Payments.

A saved setup is a name and the same four choices, and nothing else.

Both are deleted by the same interface that made them, and uninstalling the app removes the storage that holds them, on Atlassian's schedule rather than ours.

Whose eyes the data is read with

The search runs with the permissions of whoever is looking at the dashboard, not with an application account and not with the permissions of whoever configured the gadget. Two people opening the same gadget can see two different grids. This app cannot widen anybody's view of Jira, and a reader who cannot see a project does not see its issues in a total.

The exception, stated because it is the one thing a reader should know about: a kept grid is a picture, not a query. It holds figures already worked out, so it draws the same numbers for everybody who can open the dashboard it is on — including somebody whose own permissions would have produced a smaller grid. Whoever keeps one is publishing it to that dashboard's audience, and the interface says so where the button is.

No axis can be a person

Not assignee, not reporter, not creator, not any user field. This is enforced against the schema of every field your site returns rather than against a list of names, so a custom multi-select called Reviewers is refused for the same reason Assignee is.

It follows that no label on a kept grid is about a person. Two things that rule does not cover are set out plainly on the privacy page rather than left for a reviewer to find.

What is checked before anything ships

Every push runs the same gate, and it is hard-failing rather than advisory:

Two of those tests are about this page's claims rather than about behaviour. One reads every built file and fails on any host the bundle reaches that is not this site's own — the handful it permits are strings inside error messages, checked by reading the characters around each rather than by trusting the name. The other holds the claims in these documents against the code that has to make them true.

Who can deploy, and what stops them

A Forge deploy reaches every installation with no action by you. That is the app's most valuable asset and its largest risk, so it is worth saying who can do it.

Production deploys are made by hand and are not automated from the repository. The pipeline has exactly one step that holds a deploy credential, it deploys to the development environment only, and it is run deliberately rather than on a push.

That step installs its dependencies with lifecycle scripts disabled — both the app's and the Forge CLI's. The CLI is 831 packages with three install scripts, one of which downloads a 38 MB executable during install, and running arbitrary code in the one environment holding a deploy token is the risk that flag exists for. The CLI version is pinned, so a major release cannot change what deploys on the day Atlassian ships it. The step that runs the tests installs normally and holds no credentials at all.

Reporting a vulnerability

Write to ⟨decide — the support address, see docs/listing.md⟩. Say what you found and how to reproduce it; you do not need a proof of concept for anybody to take it seriously.

You can also report it to Atlassian, who will raise it in their Marketplace Security project, where it arrives with a remediation due date. Either route reaches the same person.

These are the timeframes the app is held to, and they are Atlassian's rather than ours:

SeverityCVSSFixed within
Critical9.0 and above10 days
High7.0 to 8.94 weeks
Medium4.0 to 6.912 weeks
Lowbelow 4.025 weeks

If there is an incident

Containment here is unusually direct. A Forge deploy reaches every installation without any action by you, so rolling back to the last good revision is both the fastest response available and a complete one — there is no upgrade for an administrator to apply and no site left behind on a broken build.

What is not here yet is the written plan: the notification clocks this app commits to, and who does what while an incident is open. It has Atlassian's obligations either way, and stating them here before anybody has committed to them would be worse than saying plainly that the document does not exist. ⟨decide — write it, then replace this paragraph.⟩

What this page does not claim

It does not claim the code is correct. It claims the code is checked, by the gate above, and that the claims in these documents are held against it. Those are different things and the difference matters.

It does not claim Atlassian's platform is secure. Everything here rests on it, and your agreement about it is with them.

It does not claim a label is safe. A component named after a colleague is text your organisation chose, and no rule in this app can tell it from any other text. That is on the privacy page too, because it is the one place the axis rule stops short.

It does not claim independent review. Nobody outside this project has audited it. If that changes, it will say so here with a date.