Skip to content
ImproveDesk logo: an open loop with a rising trend line on a teal tile ImproveDesk by ITSM Ltd

Security & architecture

Nothing leaves your Atlassian tenancy unless you choose it

ImproveDesk is a Forge app. Its code runs on Atlassian's compute and its data sits in Atlassian's storage, under your site. There is no vendor-operated service in the path, which removes most of the questions a security review would otherwise have to answer. The one way register content reaches anywhere else is the email digest, and only if a Jira administrator allows it: Jira's own email then sends a project's summary to the people chosen — counts and references by default.

No external services

No third-party API, no analytics endpoint, no vendor database. The app declares no egress, so it has no network path of its own out of your tenancy to review. An email digest, where allowed, is sent by Jira's own email, not by the app.

Data at rest in Forge storage

Improvements, audit events, comments, configuration and snapshots are held in Atlassian's app storage, inheriting your site's data residency.

Your Jira permissions

No separate access model to keep in step. Every action is gated on a Jira permission, checked against Jira on each request — a project permission for the register and its settings, and Jira administrator for the site settings page.

Permission scopes

What the app asks for, and why

The list is read straight from the app manifest at build time, so it cannot drift from what the app actually asks Atlassian for, and each description follows section 5.1 of the Cloud Security Statement. Four scopes, each here for a stated reason — the app asks for nothing it does not use, and a backend guard fails the build if that stops being true.

ScopeGrantsUsed for
read:jira-work Read issues and projects Reads the delivery issues a user has linked to an improvement, lists the project’s issue types, checks that an issue being linked belongs to the same project and is visible to the person linking it, and runs the /rest/api/3/mypermissions check that decides whether the signed-in user may browse or administer the project. Where a project administrator has switched publishing on, it also reads back the status record the app wrote to a linked issue, so the daily reconciliation can tell whether it is still correct. Where a project sends email digests, the issue a digest is sent from is read once, as the project administrator choosing it, to confirm it belongs to the project; before each send the app reads that issue’s project as the app, sending nothing if the issue has moved; and once per daily run in which a digest is sent, it reads the site’s address so the email can link back to the project.
write:jira-work Write issues Writes three things. A Jira delivery issue created from an improvement, posted as the user who asked for it, from the Create delivery issue button on the record. Where a project administrator has switched publishing on, a small status record on each linked issue, stored as a Jira issue entity property. And, where a Jira administrator has allowed email digests and a project administrator has switched them on, an email notification queued through Jira from one issue in the project, one per recipient, which writes nothing to that issue. The app does not edit an issue’s fields, comment on an issue or transition an issue.
read:jira-user Read user profiles Resolves the Jira account ids stored on improvements to display names and avatars, so owners, sponsors, comment authors and audit-trail actors read as people rather than opaque ids, and finds people who can browse the project by name when you choose an owner or sponsor. Both run as the signed-in user; only the account id of the person you choose is stored.
storage:app Forge app storage Stores all app data inside your Atlassian tenancy: the register, the append-only audit trail, comments, per-project configuration — including, where email digests are used, the account ids of up to ten recipients — a per-project record of the latest email digest, the site settings, weekly KPI snapshots and the scheduled-run log. There is no external data store. Where email digests are allowed, the email itself is delivered by Jira to the recipients’ mailboxes.

Design decisions

The choices behind the behaviour

Most of these exist because the obvious alternative fails quietly. A silent failure in a compliance register is worse than a loud one, because nobody goes looking.

01

The project key comes from the Forge extension context, never the request payload

A payload-supplied project key would let any authenticated user read another project’s register by editing a request in the browser. The single exception is the global portfolio page, which has no project in its context — and it re-checks browse access explicitly for every project it offers. An automated guard test enforces that no other module reads a project key from the payload.

02

Permission checks fail closed

A non-success response from the Jira permissions endpoint is treated as "no permission" and logged loudly, rather than defaulting to allow. An endpoint that quietly starts failing must not become an open door.

03

What a person asks for runs as that person

Reading and linking issues, creating a delivery issue, resolving names and choosing the issue an email digest is sent from all run with the caller’s own authority, so Jira’s permission model still applies and nothing is disclosed that the person could not already see. The daily job runs with nobody signed in, so two modules — and only two — call Jira as the app: one writes, reads back and removes the app’s own status record on linked issues, and the other asks Jira to email the attention digest where it is allowed. A check over the whole source tree fails the build if an app-level call appears in any other file.

04

Optimistic concurrency on every write

Two people editing the same improvement in different tabs is the normal case for a register reviewed in a monthly service meeting. A last-write-wins overwrite there silently loses somebody’s benefit numbers, so a stale write is refused and the client is told to reload.

05

Resolvers never throw across the bridge

Forge flattens a thrown error into an opaque string on the client — exactly the surface that hides a validation failure behind "something went wrong". Every call returns a structured result the interface can render field by field.

06

CSV export neutralises formula characters

A leading =, +, - or @ in a free-text field is a live spreadsheet injection the moment the export is opened in Excel. Those values are prefixed before the file is written.

07

Confirmation happens in the app, not with a browser dialog

A Forge Custom UI iframe is sandboxed, and a blocked confirm() returns false. Relying on one would turn Delete into a button that does nothing and explains nothing — so destructive actions use in-app panels that state what is lost.

Architecture

How it is put together

A pure domain layer

The lifecycle graph, validation, scoring and benefit arithmetic are pure functions. Time and identifiers are passed in rather than read from the environment, so every rule is testable without a platform — and behaves identically whichever surface calls it.

One storage boundary

Exactly one layer touches Forge storage. Everything above it works with plain objects, which is what makes the rules above provable rather than merely intended.

Structured logging

One JSON object per line through forge logs, with an optional debug level that traces entry and exit around every storage and service call. Nothing is caught without being logged.

Bounded reads

Filtering happens in memory against a project's own partition, with a hard scan ceiling that logs when it truncates. A register is a management artefact holding hundreds of records, not a transaction log holding millions.

Straight answers

Current limits

Stated plainly, because you will find them anyway and it is better that you hear them here.

  • Issue links stay inside the project. An improvement can link only an issue in its own project that the person linking can see, checked with their own authority before the key is stored. Delivery work tracked in another project cannot be linked: when an issue is deleted, the tidy-up looks only in that issue's own project, so a link reaching outside it would outlive the issue it names.
  • A very large register is truncated. Beyond a few thousand improvements in a single project, reads are capped and the truncation is logged. Index-side filtering would be the next step.
  • Reference allocation is not lock-based. Forge offers no atomic counter, so the app checks the live set for collisions and advances past them. Two creates in the same instant rely on that check rather than on a lock.
  • CSV export is paged. Export is capped per call to stay inside the Forge invocation budget.