Security overview
Runs on Atlassian
The app is built on Forge and runs entirely inside Atlassian's infrastructure. There is no external server, no external API, no external database and no data egress.
This is not a claim about our operational discipline; it is a property of how the app is built. There is nowhere for data to go.
Permissions: no access to your Jira data
permissions:
scopes:
- storage:app
The app declares zero Jira scopes. It cannot read a work item, cannot write to one, and cannot see your projects, users or fields.
The single scope it does declare, storage:app, is a Forge platform scope, not a
Jira one. It grants this app access to its own storage — the working calendars you
create — and nothing else. No app can read another app's storage, and the scope
confers no access whatsoever to Jira content. It is required by Forge's storage API;
without it, saving a calendar fails.
This is possible because the app is a calculator. It receives the values a rule passes in, returns an answer, and Jira's own Edit work item step writes that answer wherever the rule says. The app never touches Jira's API.
Licensing does not change this. Licence state arrives on the Forge invocation context, so checking whether the app is paid for requires no scope either.
If a future version ever needs a scope, that will be stated in the release notes and in the changelog before the version ships, together with the reason. Adding a scope also forces Atlassian to ask you to re-approve the upgrade, so it cannot happen quietly.
Data handling
| Stored | Working calendars: name, time zone, working weekdays, non-working dates, optional note |
| Not stored | Work item content, custom fields, user identities, project data, the dates passing through a calculation |
| Location | Forge hosted storage, scoped to your site's installation |
| Encryption in transit | Yes — platform-provided (TLS) |
| Encryption at rest | Yes — platform-provided |
| Retained after uninstall | No — every calendar is deleted automatically |
Detail: Privacy policy.
Logging
One structured line per operation carrying the operation name, success or failure, duration, and an anonymous error code. No dates, no names, no content, no identifiers.
An automated test asserts this and fails the build if a log line can be made to carry customer data, so it stays true as the code changes.
Dependencies
The dependency budget is enforced by CI, not by intention:
| Package | Third-party runtime dependencies |
|---|---|
| Calculation engine | zero |
| Error catalogue | zero |
| Jira adapter | The Forge SDK and React, which Forge itself requires |
npm run audit:runtime-deps fails the build if a runtime dependency is added
anywhere. Adding one requires editing an allow-list and writing down why.
The reason is specific. Atlassian scans published Forge versions continuously, and a high or critical finding arrives later as a ticket with a 14-to-30-day remediation window — on Atlassian's schedule, not ours, potentially triggered by a transitive dependency nobody chose. The smallest possible dependency surface is the only real defence.
Continuous checks
Every push, pull request and weekly schedule runs:
- full type-check and test suite, including an exhaustive sweep of the date arithmetic against the JavaScript engine's own calendar
npm auditagainst production dependencies, failing on high severity- CodeQL static analysis
- the dependency budget check
Dependabot opens grouped weekly pull requests so routine bumps stay small.
Input handling
Every value entering the engine is validated before use:
- Dates must be strictly
YYYY-MM-DD, with real month lengths. 30 February and a 29 February in a non-leap year are refused, not rolled forward. - Numbers must be whole and within range.
- Any value echoed back in an error message is stripped of control characters and truncated, so a large pasted value cannot flood a message.
- Every loop in the engine is bounded. A calendar that marks so many days non-working that no working day can be found errors with a clear message rather than running until the platform kills it.
The failure principle
The app never returns a date it is not certain of. Every unclear input stops the rule with a specific, actionable message.
This matters more than it sounds. A rule that fails is noticed and fixed within the hour. A rule that silently writes a plausible but wrong due date is not noticed at all, and by the time it is, it has been wrong for months.
Reporting a vulnerability
Please report privately to maxim@gordienok.com rather than in the public tracker. We will confirm receipt and keep you updated on the fix.
Public issue tracker, for ordinary bugs: https://plugins.gordienok.com/working-days/support