oc Outlier Crosstab Narrow tools for Jira
Crosstaban product Live demoInstall

Security — Crosstab

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

Last reviewed: 26 October 2026. This is version 3, in force since the app was listed on the Atlassian Marketplace. A change to how the app is built or operated takes the next version number; a correction that touches none of that moves this date and the digest at the foot of the page instead.

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

At a glance

The questionThe short answer
Where it runsInside Atlassian's platform. No backend, database or network of ours
Whose permissionsThe person looking at the dashboard; for a schedule, whoever switched it on; for a published grid, whoever published it
What it may ask forTwenty-four read scopes, one storage scope, one that checks a search and one that reports account ids, printed below
A person as an axisRefused, against the schema of every field your site returns
Data residencyStored only in Forge storage, which resides where your site does. The function usually runs there too, but Forge does not promise it; nothing leaves Atlassian either way
EncryptionAtlassian's, at rest and in transit; the app adds no cryptography of its own
Checked by AtlassianEligible for Runs on Atlassian by Atlassian's own check, run by the Forge CLI after a deploy. It passed on 8 October 2026 for version 2.0.0 as deployed to the production environment. The badge itself comes with the listing
Before it shipsnpm audit, lint, a build and the test suite, on the workstation before every push and again in Bitbucket Pipelines on every merge to main and every release, and the browser suite on main before "Tag a release" is offered
A vulnerabilitysupport@chmut.com or Atlassian; a critical one fixed within 10 days
Who stands behind itOne person, a sole trader, and no team. If the one person stops

For whoever approves the app

What you can check yourself

The source is not public, so every claim below is narrower than it sounds. These you can check yourself, before installing.

WhatWhere to check itWhat it shows
The manifestThe consent screenNo external host
The scopesThe consent screenWhat the app may ask Jira, line by line
What runs when nobody is lookingThe manifest's triggersOne thing, for the people who asked for it
Runs on AtlassianThe badge, which comes with the listingAtlassian's own check of where the code runs and the data stays

The manifest

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

The scopes

There are twenty-four read scopes, one storage scope, one that checks a search and one that reports account ids. 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. Among them are read:user:jira and read:group:jira, in a gadget that will not draw an axis by person. The same four person-shaped scopes are what Jira asks for a display name. That is the one thing about a person this app asks for. A worklog arrives with its author and comment, and each change in an issue's history with its author. The app uses only the time logged and the changes to the sprint, the status and the field being added up, and stores none of it.

Every scope is printed below, with why it is asked for.

What runs when nobody is looking

One thing, and only for the people who asked for it: Keep automatically, beside Keep this. It keeps a grid every week on a chosen day, on the 1st of each month, or whenever a sprint closes. Each is kept then, or as soon after as Jira allows.

Entry point in the manifestWhat it does
A scheduled trigger, once a dayPuts a run on the app's own queue
An event trigger, a sprint's closePuts a run on the app's own queue
That queue's consumerReads, one run at a time

The consumer does one narrow thing for each schedule on the site whose turn it is. It reads the grid as the person who switched that schedule on, by their account id, with their permissions and no one else's. Then it does one thing more: keep it into their own history, exactly as their own press of Keep this would.

What a scheduled read asks of Jira beyond a render is listed on the privacy page.

Everything else reads Jira only while somebody is looking at the gadget or pressing something on it. The privacy page lists each moment. One script runs beside the gadgets. It shares a first read between the ones on a dashboard asking the same question. It is part of that dashboard: it runs while the dashboard is open and reads as whoever opened it. It keeps nothing a gadget's own read would not.

That is a statement about the manifest in front of you, not about every release after it. If a release adds another trigger, this page and the privacy policy say so before that release ships.

How much of Jira's allowance it may spend

Jira meters every installation of this app out of one pool of 65,000 points an hour. So a read made while nobody is looking spends what readers on other sites need. One site's share is capped: at most three tenths of the pool, schedules and gadgets together, so three sites at their ceiling still leave a tenth for every other site.

Who spends, on one siteAt most, in any UTC hourPast that
The schedules6,500 points - a tenthA schedule that does not fit waits for the next hour rather than failing
The gadgets people open, their settings forms included13,000 points - a fifth, give or take 500 points for each gadget reading at the same momentA gadget asks Jira nothing until the hour turns, and says that the limit is this app's, not Jira's

Checked by Atlassian

The Runs on Atlassian programme asks three things of an app.

The programme asks forThis app
Atlassian-hosted compute and storage onlyThe manifest declares no external host
The host's own data residencyStored only in Forge storage, which resides where your site does
A customer able to control external egress, such as analytics and logsWhat leaves the function is counts per minute into Atlassian's own counters and a line in Atlassian's own log. A site administrator can turn off Custom metrics access and Logs access

The counters and the log hold sizes, status codes and fixed phrases, never content. The privacy page describes both.

Atlassian's own check agrees. The Forge CLI runs it after a deploy, and it passed on 8 October 2026 for version 2.0.0, as deployed to the production environment. Atlassian applies that label to an eligible app; the vendor does not claim it. It is not an audit. It certifies nothing about how the code is written. It says where the code runs and where the data stays. That is what the manifest says, checked by the platform the app runs on.

The twenty-seven scopes, printed

Named here rather than left on a consent screen the reader has already clicked through. The grouping is the manifest's own. The right-hand column is why each group is asked for. Every read scope but read:sprint:jira-software and read:permission:jira carries allowImpersonation. That is what lets a grid kept automatically be read as its owner. The sprint scope reads nothing and is held for the event alone. The permission scope is asked only as the person at the administration page.

ScopesAsked for
read:field:jira · read:field-configuration:jira · read:project-category:jiraThe field description: what may be an axis, what may be a value, and the names behind saved settings
read:filter:jira · read:jql:jira · read:application-role:jira · read:group:jira · read:user:jira · read:project:jira · read:project-role:jira · read:issue-type-hierarchy:jira · read:avatar:jiraSaved filters: the picker, and naming the one already chosen. Nine scopes for two fields, id and name, because Jira counts them as a set. It reads no group. It reads a person only for a display name: the reader's own, a publisher's by account id, and a schedule owner's by account id for a Jira administrator on the administration page
read:issue-details:jira · read:priority:jira · read:field.default-value:jira · read:field.option:jiraThe issues themselves. The search is a POST rather than a GET. Atlassian gives the two verbs different sets for the same question, and GET's includes read:audit-log:jira
read:board-scope:jira-software · read:board-scope.admin:jira-softwareThe order a board draws its statuses in, which no issue carries and no Jira Platform endpoint has. Asked for only when an axis is status or board column
read:instance-configuration:jira · read:issue.time-tracking:jiraHow this site writes a length of time: how long a working day is, and which of Jira's three forms a duration is drawn in. Asked when the settings form opens, and when the metric is a length of time - one of the six fields held in seconds, time between two dates, or time logged
read:issue-worklog:jira · read:issue-worklog.property:jiraThe worklogs of one issue, for time logged in a month. The search carries at most twenty worklogs an issue, and the rest are asked for issue by issue. Asked for only when the metric is time logged
read:sprint:jira-softwareThe event a sprint's close arrives as, for a grid kept when a sprint closes. The platform delivers it only to an app holding this scope. Nothing is read with it. The sprint's issues come through the search above, as the schedule's owner
read:permission:jiraWhether whoever uses the administration page holds the Administer Jira global permission. Asked on every change made there and each time it lists the site's schedules, as the person at the page, and on a production site nowhere else. One yes or no crosses into the app
validate:jql:jiraChecking a search when the settings form stores it. Jira's strict parse is the one question that names a field it does not know. Not a read and not a write. Asked only by the person at the form. A site that refuses it has the search checked as before
storage:appKeeping a grid, by hand or automatically, saving a setup, holding a reader's last grid for five minutes, each reader's finished work as totals, and the other records the privacy page lists. The only scope here that writes anything
report:personal-dataSending Atlassian, once a week, the account ids the app stores - a schedule's owner, a published grid's publisher, a saved setup's author - so it can say which accounts are closed. A closed account's schedules and published grids are deleted, and its id comes off the setups it saved. The only scope asked as the app rather than as a person. Neither a read of Jira nor a write to it

validate:jql:jira exists because a misspelt field in a search answers with no issues rather than an error. It reads nothing and writes nothing.

Two of these are worth a second look rather than a skim:

Where the data lives

Nowhere it was not already. The code runs in a Forge function inside Atlassian's infrastructure. The interface runs in an iframe in the reader's own browser. The function turns the issues it reads into totals and hands them to that iframe. There is no server or database of ours for anything to be written to.

What is kept sits in your own site's app storage, inside Atlassian's infrastructure and under your agreement with them. None of it holds an issue key, a description or a comment. The one piece of ticket text the app keeps is the label on the Parent axis: a parent's own summary, usually an epic's.

That storage is Forge's own. Atlassian's rule is that an app keeping all end-user data in Forge hosted storage qualifies for PINNED data residency, with nothing to configure. This app keeps it nowhere else, so the storage sits where your site's data is pinned. Where the function runs is a separate question. Forge usually runs it in your site's region, but Atlassian does not guarantee that, and this page does not either. Either way, the app calls no host outside Atlassian's: the manifest declares none.

Every record is listed once, on the privacy page:

QuestionWhere it is answered
What is read, when, and whyWhat the app reads
What each record holdsWhat the app stores
How long each record livesRetention
What reaches the vendorWhat the vendor sees

Uninstalling the app removes all of it, 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. It does not run with an application account, nor with the permissions of whoever configured the gadget.

GridRead with the permissions ofWho can open it
On a dashboardWhoever is lookingThat person. Two people opening the same gadget can see two different grids
Kept by handWhoever kept it, when they pressedOnly them. The storage key is a hash of their account id and of the choices
Kept automaticallyThe one person who switched that schedule on, named by their account idOnly them. It is kept only into their history
PublishedWhoever published it, on the pressEverybody who opens that gadget

A kept grid is a picture

A kept grid is a picture, not a query, and so is a published one. It holds figures already worked out. So drawing it again does not re-check anybody's permissions against the issues it counted. That is the exception a reader should know about.

A published grid

A published grid is the one picture meant for other people. Publish this grid leaves one grid on that gadget for whoever opens it. A colleague then sees figures from the publisher's view of Jira. That includes issues their own permissions may not show them.

So it is never silent. The grid is drawn under a banner naming whose view it is. The gadget asks before each publish, except when the publisher's own published grid is the one showing on that gadget.

How a published grid is keyed

It is stored against the gadget rather than a person. The key is a hash of the dashboard and gadget ids. Those ids are taken from the platform, never from the page. So nobody can ask for the grid of a dashboard they were never shown.

The publishing switch

A site has to want this before anybody can publish.

How the switch is enforced

The resolver reads which module called it from the platform's context, not from the request. So a request from anywhere but the administration page cannot change the setting. And it asks Jira, as the person at the page, whether they hold the Administer Jira global permission. A no, or no answer, changes nothing. The same two checks guard the Keep automatically switch, the list of the site's schedules, switching one of them off and deleting a stopped one.

No person as an axis

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, not against a list of names. So a custom user picker called Reviewers is refused for the same reason Assignee is. So is one that holds several people at once.

The rule enforces the shape of a field. Three things fall outside a shape: a label named after a colleague, a custom select list whose options are typed-in names, and a line of JQL. All three are set out on the privacy page, rather than left for a reviewer to find.

Checks before release

The gate is npm run check, run on the workstation before every push. Its first step is npm audit. Bitbucket Pipelines runs the same checks on every merge to main, and again on every release before it offers a deploy. A known vulnerability in a dependency stops the change on the workstation, and a release that fails the audit offers no deploy. A pull request gets the same checks without the audit and the test suite. On main the browser suite, npm run check:ui, runs too, by its own manual step, and "Tag a release" is offered only once it has passed on that commit.

CheckDetail
npm audit at moderate and aboveA known vulnerability in a dependency stops the change, rather than appearing in a report nobody opens
npm audit of the deploy tools' own lockfilesReported, not a gate. What it found, and why it is accepted, is under who can deploy
Formatting, and lint at zero warnings-
Only the four files allowed to reach the platform do-
A build, before the suite rather than after itThe bundle assertions have nothing to look at otherwise
The test suiteOver 4,600 tests across more than a hundred files

Two of those tests are about this page's claims rather than about behaviour.

Who can deploy

A Forge deploy reaches every installation with no action by you. That is the app's most valuable asset and its largest risk. Every deploy runs in Bitbucket Pipelines, as bitbucket-pipelines.yml declares it, and only from a release tag.

WhatHow
A releaseA v tag, which the "Tag a release" step cuts from main after the checks there pass, the browser suite included. The tag's pipeline runs the checks again on its commit before it offers a deploy
Development, and these pagesDeployed from the tag, each by its own manual step, or again, after the checks, in the pipeline someone runs to approve a major version
ProductionA manual step too, offered only after development and the pages have deployed the same commit
A change of scopesA new major version: a site keeps the previous version until its admin accepts the new scopes. Development approves it by itself, and its installation is upgraded only when someone runs a custom pipeline for that release. Production approves it only when someone runs a custom pipeline for that release, which deploys development and these pages again first; the usual production step fails on it and deploys nothing. Until production has its first installation, Forge asks for no approval, and the usual step deploys it like any release. Either can be started from any branch or tag, but deploys only a commit on main that a release tag points at
Any branch or pull requestNothing deploys
The deploy credentialsSecured variables in Bitbucket, never in the repository. Each belongs to the deployment that uses it, so only that step can read it
The check stepsHold no credentials at all
Rolling backRedeploy of the previous release, from Bitbucket's deployment history. The step rebuilds from that release's own commit. A Redeploy of a production deploy that approved a major version approves it again
What the deploy steps do not run

Each deploy step installs its dependencies with lifecycle scripts disabled, both the app's and the tools'. The Forge CLI is 832 packages with three install scripts, one of which downloads a 38 MB executable during install. Running arbitrary install code in the one environment holding a deploy token is the risk that flag exists for.

Each deploy step takes its tools from a lockfile of its own, committed beside the deploy script, and restores no npm cache. The lockfile fixes the Forge CLI, the tool that deploys these pages and every npm package under them, so a release of any of them cannot change a deploy on the day it ships. The check steps install with scripts on and keep an npm cache; nothing they leave in it reaches a step that holds a credential.

The tools' own trees are audited by npm run check too, from their lockfiles, and it reports what it finds rather than failing on it. On 8 October 2026 that was 44 known vulnerabilities in the Forge CLI 14.1.0 (3 critical, 29 high, 8 moderate, 4 low) and none in wrangler 4.148.0: the three it had, all in the image library its local simulator pins, were cleared by taking that library's next patch release. That override comes off once wrangler pins that release or a later one itself. The Forge CLI's are accepted rather than fixed: 14.1.0 is the latest release, and it ships its own npm-shrinkwrap.json, so its tree changes only when Atlassian releases a new one. Overriding packages inside it would deploy with a tree Atlassian has not tested. The deploy steps move to each new Forge CLI release, and the count here is measured again when they do.

The build still runs the code of every build dependency, and what it builds is what is deployed. The step builds with no Forge or Cloudflare variable in the environment it hands the build, so a dependency that reads its own environment finds no token there. That is all it covers: code in the same container, run as the same user, can still read the step's own environment from /proc. A dependency that could do that could as well change the bundle it builds, which is why the app's dependencies are audited and locked.

The deploy steps run on one Node image, pinned by its digest. A change to the image's tag reaches them only through a commit that names a new digest.

A deploy runs the pipeline file of the commit it deploys. So all of this holds for releases tagged since the pipeline has done it, and a Redeploy of an older release runs that release's own steps.

The repository has one account with write access, the vendor's. On this Bitbucket plan a deployment cannot be tied to a branch or a tag by a setting. The pipeline file does that, and only that account can change it. A tag on a commit that is not on main deploys nothing when that commit's pipeline file carries the check, which each deploy step runs before it installs anything. It stops a mistaken tag. A commit whose pipeline file lacks the check, an older branch or one that removed it, is not stopped by it, so what remains is that only that account pushes tags and a person starts each deploy. That account could still push a release tag by hand on a commit of main, which skips the browser suite: tags are cut by the "Tag a release" step, not by hand.

The vendor's machine keeps an Atlassian token of its own, because reading the app's logs needs one. It could deploy, but is not used for that.

Reporting a vulnerability

Write to support@chmut.com. Say what you found and how to reproduce it. You do not need a proof of concept for anybody to take it seriously.

You will get a human reply within 48 hours, on the same working days as any other message to support. That is a commitment about acknowledgement rather than about a fix.

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

The app is held to these timeframes. 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

Response times

Two clocks, and it is easy to read one as the other.

WhatThe clock
An ordinary question: a figure that looks wrong, a setting that will not do what you want, a grid that came back partialAnswered within 48 hours, between 09:00 and 19:00 Europe/Kyiv, Monday to Friday, except on Ukrainian public holidays
A vulnerabilityThe table above. It is Atlassian's, and measures time to a fix rather than to a reply

Both reach the same person. That is the whole of the support arrangement. It is written down plainly, because an administrator deciding whether to depend on this is entitled to know how thin it is.

Who stands behind this

One person: Oleksandr Chmut, a sole trader, trading as oc. It is the same vendor as this studio's other Marketplace app. There is no team behind either of them.

That is a smaller answer than an administrator would like. It is given plainly, because the alternative is a page implying a company and a reviewer finding out otherwise.

WhatHeld by
The response times aboveOne person
The deploy credentialsOne person
The incident plan belowOne person, to run

It also means there is nothing between a reported problem and the person who can fix it. A vulnerability report reaches the author of the code the same day it is sent, on either route.

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 and a complete one. There is no upgrade for an administrator to apply, and no site left behind on a broken build.

What counts

IncidentWhy it is one
The app doing something to a site that it is not supposed to be able to doIt is what the rest of this page is about
Data of yours reaching somewhere it should notIt is what the rest of this page is about
The app unavailable, or drawing wrong figures, across installations rather than on one boardA grid that quietly adds up wrong is worse than a grid that does not draw. Only one of the two announces itself

How one is noticed

In almost every case, because somebody writes. This is the thin part.

So the clock below starts when a report arrives, not when the fault does.

What happens when

The clocks, and they are what this app commits to you.

WhenWhat happens
Within one working dayThe report is answered by the person who wrote the code, saying what is known
Within 72 hoursAffected sites are told, if data of yours is involved
The same working dayThe build is rolled back, once the fault is confirmed and where a rollback is the fix
Within 5 working daysA written account of what happened, to anybody who was told

The 72 hours is the one clock Atlassian recommends rather than one chosen here. The notification goes to the technical contact Atlassian holds for each licensed installation. That is the only address this vendor has for you. There is no other list: this app collects no email addresses of its own. A site that installed the app through a distribution link, before the app was listed, has no such contact and cannot be reached this way.

What is done, in order

  1. Roll back to the last good revision first, and diagnose afterwards. A rollback is one redeploy of the last good release, it reaches every site at once, and it needs nothing from you.
  2. Stop deploying anything else, including unrelated fixes. A second change during an incident is how the first one becomes unexplainable.
  3. Write down what is known and when it was known, as it is learnt rather than afterwards.
  4. Tell Atlassian within 24 hours of becoming aware of it, as a P1 ticket in the Marketplace Partner Portal, and update them at least every 6 hours until it is over. A vulnerability that nobody has exploited goes to their Marketplace Security project instead.
  5. Tell the affected sites on the clock above.
  6. When it is over, write one account of what changed, so that this section is not the same next time.

Who does what

The same person does all of it. That is the arrangement, and what it costs you is worth stating. Nobody is on call. An incident that begins at midnight on a Saturday is noticed on the Monday. One that begins during a week away is noticed when that week ends, and so is any report sent during it. The clocks above start from that.

The compensating fact is the one this section opened with. The fix is a rollback, and it needs no action from you. The person who can do it is the person who wrote the code.

What this plan does not cover

Atlassian's own platform. If Forge, Jira or Atlassian's storage is what failed, the incident is theirs. Your agreement about it is with them. What this vendor can do is tell you what it knows.

If the one person stops

An administrator told there is one person and no team will ask a question. What are they left holding on the day that person stops? A page that admits the first and does not answer the second is only half of an answer.

What stops if the one person stops is the part a person does. That is new versions, an answer to a support message, and eventually a platform change that nobody is here to follow. Nobody else publishes new versions: there is no second maintainer, and no escrow arrangement that would hand the app to one. That is the real loss: a product that stops improving. It is not a report that disappears, and not a number you cannot get back.

What is not claimed

This page does not claimWhat it says instead
That the code is correctIt 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
That Atlassian's platform is secureEverything here rests on it, and your agreement about it is with them
That a label is safeA component named after a colleague is text your organisation chose. No rule in this app can tell it from any other text. It is the one place the axis rule stops short, so the privacy page says it too
Independent reviewNobody outside this project has audited it. If that changes, this page will say so with a date

This edition

The file below is the authoritative text. This page renders it and adds navigation and this block, so the page and the file are not byte-identical — it is the file that governs, and the digest is over the file.

Version 3 — security.md, 57 KB

162c0b9f30ec2f5941e19a43d5296e22acf7d9f1f43488d8523ae77d390f2e54

Check it with shasum -a 256 security.md once the file is downloaded, which prints the digest above and nothing to compare by eye. On Windows, Get-FileHash security.md -Algorithm SHA256 in PowerShell, which prints in capitals — the comparison is case-insensitive. The digest is published by us: it shows the file has not drifted or been altered in transit, and it is not a third-party attestation.