Security & architecture
Nothing leaves your Atlassian tenancy
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.
No external services
No third-party API, no analytics endpoint, no vendor database. There is no network path out of your tenancy to review, because there is not one.
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 project permission, checked against Jira on each request.
Permission scopes
What the app asks for, and why
These are the scopes declared in the app manifest. Each is there for a stated reason; if you remove the ability to create delivery issues from your workflow, the write scope is the only one that becomes idle.
| Scope | Grants | Used for |
|---|---|---|
read:jira-work | Read issues and projects | Reads the linked delivery issues shown on a record, and the projects a register can belong to. |
write:jira-work | Create and update issues | Reserved for creating a delivery issue from an improvement. That action is implemented in the app but is not yet reachable from the interface, so in the shipped build this scope goes unused. |
read:jira-user | Read user profiles | Resolves account ids to display names for owners and sponsors on records and in the CSV export. |
manage:jira-project | Read project permissions | Establishes whether the signed-in user may browse or administer the project. |
storage:app | Forge app storage | Where the register, audit events, comments, configuration and snapshots are stored — inside your Atlassian tenancy. |
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.
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.
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.
Jira reads run as the signed-in user
Linked issues are fetched with the caller’s own authority, so Jira’s permission model still applies and nothing is disclosed that the person could not already see.
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.
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.
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.
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.
- Cross-project issue links are not restricted. An issue key from another project can be linked. The issues are read with the caller's own authority, so nothing is disclosed that they could not already see — but the key can be stored. Linking an improvement to delivery work in another project is a legitimate case, so this is a deliberate trade-off rather than an oversight.
- 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.