Keelcroft / Azure DevOps Sync for Jira / Security
Security
Effective 30 September 2026. This page describes the controls in the current release.
Architecture
The app runs entirely on Atlassian Forge. Jira data is sent directly from Forge to the customer’s own Azure DevOps organization; Keelcroft operates no relay and no vendor-hosted backend. Persistent records use Forge storage, and credentials use Forge encrypted secret storage. The sync engine itself holds no Forge or Azure DevOps imports.
The app is not eligible for the Runs on Atlassian programme, and will not be: reaching a third-party API requires egress, which disqualifies that badge by definition.
Access controls
- Creating, changing, enabling, pausing, linking, unlinking and replaying all re-check
ADMINISTER_PROJECTSagainst Jira as the calling user. Rendering inside a project settings page is not treated as a permission check. - The Jira project is always taken from the signed resolver context, never from the client payload.
- Each connection is scoped to one numeric Jira project ID, one Azure DevOps organization and one Azure DevOps project.
- Linking re-reads the target issue and refuses it unless its project matches the connection, so a project administrator cannot reach into another project.
- The organization URL is validated as an HTTPS organization root with no embedded credentials, port, query or fragment. Egress is limited by the Forge manifest to
dev.azure.com,vssps.dev.azure.comandlogin.microsoftonline.com; a self-hosted Azure DevOps Server host can only be added by the customer’s own Atlassian organization admin through Atlassian’s egress controls, never by the app. - Work item ids and project names are validated before they are placed in a request, and every path segment and query parameter is URL-encoded.
Credentials and inbound requests
- The customer supplies either an Azure DevOps personal access token scoped to Work Items (read, write) and Service Hooks (read, write, manage), or a Microsoft Entra service principal. The credential is stored in Forge encrypted secret storage, sent only to the Microsoft endpoints above over HTTPS, never returned to the user interface and never written to logs.
- Change detection on the Azure DevOps side uses a service hook subscription the app creates. Azure DevOps authenticates service hooks with HTTP Basic credentials, so the app generates a random username and a 256-bit password for each connection, stores them as a secret, and compares them in constant time before the body is parsed. A failure returns the same generic response as an unknown connection, so the endpoint cannot be used to enumerate installations.
- A delivery is only ever a hint: it names a work item, and the sync then re-reads both platforms itself, so a forged or mangled delivery cannot inject content.
- Pausing sync clears the hook credentials and removes the subscription when Azure DevOps permits; re-enabling issues new ones.
Data integrity
- Every write is followed by an independent read from the destination and a value comparison.
- Azure DevOps updates carry the revision the app read (
op: teston/rev), so a concurrent edit by somebody else makes the update fail rather than overwrite their work. - Forge delivers events at least once, so deliveries are durably claimed, processed serially per connection, and safe to replay.
- Conflicting edits are held or resolved by the configured policy, never silently discarded.
- The app never deletes a Jira issue or a work item. A vanished record marks the pair failed instead.
- The event journal stores identifiers, outcomes and field names — never issue or work item content. That is why a replay re-reads both platforms rather than reusing a stored copy.
What the app logs
One structured line is written per decision on four paths: a service hook arriving, a Jira change being considered, an administrator linking or replaying a pair, and a sync step finishing. Each line carries identifiers and an outcome only — the connection id, the work item id, the Jira issue id, the delivery id, the app’s own event id, the resulting status, the attempt count and, where something was skipped or refused, the reason.
Work item content is never logged. Titles, descriptions, tags and comments belong to the customer and to their Azure DevOps project, and no log statement accepts them as an argument. Neither are credentials: not the personal access token, not the service principal secret, and not the Basic credentials Azure DevOps presents on a service hook. The reason a delivery was refused is kept in the log and deliberately withheld from the caller.
This is asserted rather than merely intended: an automated test drives a real service hook whose payload carries a title, a description and a tag, captures everything written to the log, and fails the build if any of those strings, or either form of the hook credential, appears in the output.
Vulnerability reporting
Report suspected vulnerabilities privately to support@keelcroft.com with the subject line “Security”. Include reproducible steps and impact, but do not access data that does not belong to you and do not send credentials. Critical security reports receive an initial response within one business day.
The app does not claim SOC 2, Cloud Fortified, an independent penetration test, or participation in the Marketplace Security Bug Bounty Program.