Keelcroft / ServiceNow 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 ServiceNow instance; 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 ServiceNow 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 ServiceNow instance and one table.
- Linking re-reads the target issue and refuses it unless its project matches the connection, so a project administrator cannot reach into another project.
- Changing the instance or table is blocked while sync is enabled or any linked pair remains, so an old link cannot be reinterpreted against a new table.
- The instance URL is validated as an HTTPS origin on
service-now.comorservicenowservices.comwith no path, port, query, fragment or embedded credentials. That is also exactly the egress the Forge manifest allows, so the app cannot be pointed anywhere else. - Record numbers and table names are validated against strict patterns before they are placed in a ServiceNow query, and every query parameter is URL-encoded.
Credentials and inbound requests
- The customer supplies a dedicated ServiceNow user with the
itilrole. Its username and password are stored in Forge encrypted secret storage, sent only to that instance as Basic authentication over HTTPS, never returned to the user interface and never written to logs. - Change detection is a five-minute poll that reads only identifiers and modification counters, so the poll itself carries no record content.
- The optional push endpoint is a public Forge web trigger. Every request must present the per-connection token, compared in constant time before the body is parsed. A failure returns the same generic
404as an unknown connection, so the endpoint cannot be used to enumerate installations. A push is only ever a hint: it names a record and a counter, and the sync then re-reads both platforms itself, so a forged or mangled push cannot inject content. - The push token is 256 bits from a cryptographic random source, shown once, and discarded when sync is paused. Re-enabling issues a new one.
Data integrity
- Every write is followed by an independent read from the destination and a value comparison.
- A value ServiceNow silently trimmed to its field length is reported as a failure, not recorded as agreement.
- 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 ServiceNow record. A vanished record marks the pair failed instead.
- The event journal stores identifiers, outcomes and field names — never record 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 five paths: a poll examining a connection, a push 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 ServiceNow record number, 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. A failed poll is logged by HTTP status alone, never by ServiceNow’s message.
Record content is never logged. Short descriptions, descriptions and work notes belong to the customer and to their ServiceNow instance, and no log statement accepts them as an argument. Neither are credentials: not the username, not the password, not the Basic authorization header, and not the push token. The reason a push was refused is kept in the log and deliberately withheld from the caller, who always receives the same unrevealing 404.
This is asserted rather than merely intended: an automated test drives a real push whose payload carries a short description, a description and a work note, and a poll whose instance rejects the password in an error body, captures everything written to the log, and fails the build if any of those strings, or any form of the 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.