Security practices and incident response
This document describes how we actually build and release our Atlassian Marketplace apps, and what we do when someone reports a security problem. It is written to be checked, not to reassure: where a control does not exist, it says so.
Winnow Tools is a one-person shop. That fact shapes everything below — some controls are stronger for it, others are absent because of it, and we would rather you knew which is which before you install anything.
1. Design: keeping the blast radius small
The strongest security control we have is not a process, it is the shape of the app. Our apps run entirely on Atlassian Forge, inside Atlassian's own infrastructure. We operate no servers, so there is no backend of ours to breach and no copy of customer data outside Atlassian.
Concretely, for Notify for Google Chat:
| Permissions | Exactly three: read:jira-work, read:jira-user,
storage:app. The app holds no write access to Jira at all —
it cannot create, edit or delete anything in your site. |
| Outbound network | One declared host, chat.googleapis.com. Forge blocks every
other destination at the platform level. This is enforced by Atlassian,
not by our code, so a bug in our code cannot widen it. |
| Inbound network | None. There are no web triggers, no public endpoints and no API of our own. Nothing outside Atlassian can invoke our code. |
| Data at rest | Rules and a short delivery log, in Forge hosted storage scoped to your site. No issue summaries, descriptions or comment text is retained — those pass through and are discarded. |
| Personal data | Only Atlassian account identifiers, never names or email addresses. Display names are read from Jira each time a page is opened and are not stored. |
A weekly job removes account identifiers belonging to deleted users from saved rules. If removing them would empty a rule's people filter, the rule is disabled rather than silently widened — quietly turning "notify about these three people" into "notify about everyone" would be a data-exposure bug, not a convenience.
2. Authorisation
Every function that the settings page can call begins by verifying that the
caller holds the Jira ADMINISTER permission. This is not
cosmetic: Forge resolvers can be invoked outside the page that normally
displays them, so a check that lives only in the user interface is not a
check at all.
Beyond that, reads made on behalf of a person run as that person
(asUser), so Jira itself decides what they may see. App-level
access (asApp) is used only where no human is present: handling
an incoming event and running the weekly cleanup job.
We shipped this as a correction. An earlier version performed privileged reads without a permission check, which would have let any logged-in user enumerate projects and people and rewrite notification rules. Atlassian's automated review caught it before release, and it was right to.
3. Handling untrusted input
Issue titles, comments and user names are written by people we do not control, and they end up inside a message we compose. All such text is HTML- escaped before it reaches Google Chat, so a summary containing angle brackets or an ampersand cannot alter the structure of the message or inject markup into someone else's chat client. Comment bodies are parsed structurally from Atlassian Document Format rather than string-matched, which is also why mentions resolve to names instead of raw identifiers.
Both behaviours are covered by automated tests that run before every release, specifically so a future refactor cannot quietly remove them.
4. Secrets
A Google Chat webhook address is a bearer credential: anyone holding it can post into that room. We treat it as one.
- Webhook addresses are written to Forge encrypted secret storage, not to ordinary app storage.
- Once saved, an address is never sent back to the browser. The settings page shows that a room is configured, not what its address is.
- Addresses are never written to logs, and never included in error messages.
- Deleting a rule deletes its stored secret.
Before every save we verify that the address is a Google Chat webhook — the
host must be chat.googleapis.com. This prevents a
misconfiguration, or a malicious administrator, from turning the app into a
generic outbound data channel.
5. Logging
Platform logs contain counters and outcomes — how many rules were evaluated, how many matched, whether a delivery succeeded — and issue keys. They do not contain issue content, comment text, webhook addresses or any other secret. The delivery log shown on the settings page records the rule name, the issue key, the time and the result.
6. How changes reach you
Every release passes the same gate, run as a single command before any deployment:
- Static analysis. ESLint with
eslint-plugin-security, which flags the patterns that turn ordinary bugs into vulnerabilities: dynamic evaluation, unsafe regular expressions, object-injection sinks. The build fails on any error. - Automated tests. 54 of them at the time of writing, covering rule matching, message construction, escaping of third-party text, webhook address validation and the cleanup job. Several are built from event payloads captured from a live Jira site, because the documented shape and the real one differ.
- Staged release. Changes go to a development environment on a real Jira site first and are exercised against live Jira and live Google Chat. Only then do they reach production.
What is missing, stated plainly: there is no second pair of eyes. With one maintainer there are no pull requests and no peer review, and no automated gate fully replaces that. The compensating controls are the ones above plus the narrow design in section 1 — a mistake in our code cannot reach a host we never declared, or write to a Jira we have no permission to write to.
7. Dependencies
Runtime dependencies are checked with npm audit --omit=dev before
release. At the time of writing this reports nine advisories, all of them
inside Atlassian's own @forge and @atlaskit
packages, which we consume as published and cannot patch independently. None
is reachable from a code path our app executes. We re-check on every release
and take Atlassian's updates as they ship.
We do not publish a software bill of materials. The standard tooling
(npm sbom) currently fails to resolve the dependency graph of
Atlassian's own packages, and we would rather publish nothing than publish a
manifest we know to be incomplete.
8. Reporting a problem
Email security@winnowtools.com. Please include enough detail to reproduce the issue, and please give us time to fix it before disclosing it publicly. Our security page sets out response times and the safe-harbour terms for good-faith research.
9. Incident response
What counts as an incident
Any of the following: a vulnerability reported in our app; evidence that data reached somewhere it should not have; loss or suspected loss of a credential we hold, including an Atlassian API token; or notice from Atlassian or Google of a problem affecting our app.
Who responds
One named person, reachable at security@winnowtools.com, which is also the security contact registered with the Atlassian Marketplace. We keep that address monitored; an unreachable security contact is treated as an incident in itself, because it is one.
Steps
- Acknowledge — within 2 business days, to whoever reported it.
- Contain — if a credential may be exposed, rotate it immediately. If a released version is actively harmful, we ask Atlassian to pull it rather than wait for a fix.
- Assess — reproduce it, score it with CVSS, and determine which customers and what data are affected. Written down as we go, not reconstructed afterwards.
- Fix — with a regression test that fails before the fix and passes after it, so the same defect cannot return unnoticed.
- Notify — Atlassian, through the process in their app security incident guidelines, and affected customers directly at the contact we have for them. If we are unsure whether you were affected, we will tell you that rather than stay silent.
- Review — after the fix ships, write down what allowed it and what changes. If the answer is a new automated check, it is added to the release gate.
Timelines
We work to Atlassian's Marketplace Security Bug Fix Policy for cloud apps, which sets these deadlines from the point a vulnerability is reported or triaged:
| Critical (CVSS ≥ 9.0) | fixed within 10 days |
| High (CVSS ≥ 7.0) | fixed within 4 weeks |
| Medium (CVSS ≥ 4.0) | fixed within 12 weeks |
| Low (CVSS < 4.0) | fixed within 25 weeks |
These are ceilings set by Atlassian, not targets of ours. A small app with a small codebase has no good reason to use the whole window, and we do not plan to.
10. What we do not claim
No external penetration test. No SOC 2 or ISO 27001 certification. No paid bug bounty. No 24/7 rota — we are one person in one time zone. No anomaly monitoring beyond the platform's own logs, and no twelve-month log retention.
If your procurement process requires any of these, write to security@winnowtools.com and we will tell you honestly where we stand. A vendor who answers "yes" to everything on a questionnaire is telling you something, and it is not what you hoped.