Security
How Triage for Jira is built, what it can reach, and how to report a problem. Everything here is checked against the source rather than described from memory.
Reporting a vulnerability
Email support@voussoirsoftware.com with "security" in the subject. Please do not open a public issue.
Tell us what you found and how to reproduce it. You will get a reply from the person who wrote the app, not a queue. We will tell you what we intend to do and when, and we will say so plainly if we disagree that something is a vulnerability.
There is no bug bounty. We are a very small company and would rather be honest about that than imply a programme we cannot fund.
The shape of the thing
The app runs entirely on Atlassian's Forge platform, inside the customer's own site. There is no Voussoir server anywhere in the picture.
That is enforced by the platform rather than promised by us. The app's manifest declares no
permissions.external block, which is what allows a Forge app to make outbound network calls.
Without it the runtime refuses them. Adding one would require a new version, a visible manifest
change, and Atlassian's review, and it would cost the app its Runs on Atlassian certification.
Practically, this means there is nothing of ours to breach, no vendor outage that can take the app down, and nothing to put through a third-party security review.
Inbound webhooks
The app's only public surface is one Forge web trigger, the URL you paste into Sentry. It accepts POST and nothing else.
Signatures are verified before the body is parsed. The app computes HMAC-SHA256 over the exact
bytes received and compares it against Sentry's sentry-hook-signature header using a
constant-time comparison. A request that fails is refused without its payload being deserialised,
so unauthenticated input is never fed to a parser.
Verifying the raw bytes matters. Re-serialising a payload and signing that instead is a known way to let a modified body pass a signature check, and it is what Sentry's own sample code does.
Bodies are size-limited before anything else happens, so an oversized request is dropped rather than buffered.
A connection binds to the first Sentry installation that posts to it and refuses every other one afterwards. Two Sentry organisations therefore cannot read each other's linked work items, counters or rules through a shared URL, even if one obtained the other's webhook address.
The endpoint returns a fixed set of static responses. It cannot be used to read anything back out: the possible replies are a small list of status codes declared in the manifest.
Secrets
The Sentry Client Secret is held in Forge's encrypted secret store. It is never returned to the browser, never written to a log, and never displayed back once saved. The admin page shows only whether a secret exists.
It is used for one thing: verifying signatures on inbound webhooks. It grants no access to Sentry.
The app asks for no Atlassian credentials of any kind. No personal access token, no password, no API key. It acts through Forge's own app authentication, scoped by the permissions below.
What the app can reach
Five scopes, and nothing else:
| Scope | Why |
|---|---|
storage:app |
The key-value store and the encrypted secret store |
read:jira-work |
Read projects, issue types, fields, work items and transitions |
write:jira-work |
Create work items, comment, transition |
manage:jira-project |
The per-project enablement property |
write:app-data:jira |
Write values of this app's own two custom fields |
There is no scope for reading users, for administration, or for any Atlassian product other than Jira.
Data minimisation
Sentry payloads carry more than the app needs, and some of it is dangerous to store.
vars, cookies, headers and extra are dropped unconditionally, at any depth they appear.
Those hold whatever was in scope when the error happened, which in a real application means API
keys, session tokens and customer data.
Stack frames are rebuilt from an allow list rather than filtered: only filename, function, line number and source line survive. Anything Sentry adds to a frame in future is therefore discarded by default rather than passed through by default.
Query strings are stripped from request URLs, and the remaining text is scanned for credential-shaped content and redacted: credentials inside URLs, bearer and basic authentication, JSON web tokens, AWS access key ids, GitHub, Slack and OpenAI token prefixes, key=value secrets, PEM private key blocks and long runs of hex.
The test suite plants known values in each of those fields on every simulated webhook and asserts that none of them reaches the work item, its comments or its fields.
The browser
The admin interface and the issue panel are served from Atlassian's own infrastructure. The bundle contains no third-party script, stylesheet, font, image host or analytics endpoint, and a test asserts that on every build.
There is no tracking of any kind, and nothing for a blocker or a privacy extension to intercept, which is why the app still loads when one is running.
What we do not claim
No compliance certifications. No SOC 2, no ISO 27001, no CAIQ. A certification we have not earned would be worse than silence.
The app's security rests on where it runs and what it cannot do, both of which you can verify from the manifest rather than taking our word for.