feat: Add Linear action item syncing for incidents #139
4 issues
find-bugs: Found 4 issues (2 high, 1 medium, 1 low)
High
update_or_create can steal ActionItems from another incident - `src/firetower/incidents/services.py:274-285`
ActionItem.objects.update_or_create(linear_issue_id=issue["id"], defaults={"incident": incident, ...}) keys solely on linear_issue_id. If the same Linear issue is referenced as a related issue from a different incident's parent (or was previously associated with another incident), this sync will silently reassign the ActionItem to the current incident, mutating another incident's data. The subsequent delete step then removes ActionItems no longer matching by linear_issue_id, but the cross-incident hijack already occurred. This violates data isolation between incidents.
parse_incident_id uses re module without importing it - `src/firetower/incidents/views.py:67-69`
The new parse_incident_id function calls re.escape and re.match, but the import block shown does not include import re. If re is not imported elsewhere in the file, any call to parse_incident_id will raise NameError at runtime, breaking incident ID parsing for endpoints that depend on it.
Also found at:
src/firetower/integrations/services/linear.py:142-145
Medium
Token refresh has a TOCTOU race that can clobber concurrent tokens - `src/firetower/integrations/services/linear.py:73-78`
_request_new_token deletes all LinearOAuthToken rows and creates a new one inside transaction.atomic() without row-level locking. If two requests hit _get_access_token near expiry, both will call Linear's token endpoint and race to delete/insert, potentially invalidating a token another in-flight request just stored, and producing redundant token requests against Linear (rate-limit risk). Use select_for_update() or a database-level lock / cache lock so only one worker refreshes at a time.
Also found at:
src/firetower/integrations/services/linear.py:125-135
Low
ActionItemListView triggers Linear sync on every GET without throttling at view layer - `src/firetower/incidents/views.py:348-354`
The list() method calls sync_action_items_from_linear(incident) on every GET request. While the description claims throttling exists in the service layer, any unauthenticated-but-permitted user with access can repeatedly hit this endpoint to trigger external Linear API calls. If the service-level throttle is per-incident rather than per-user/IP, this could be abused for resource exhaustion or to amplify load on the Linear API. The broad except Exception swallows all errors silently, which can mask repeated failures.
Also found at:
src/firetower/incidents/views.py:380-396
Duration: 122.0s · Tokens: 521.2k in / 5.0k out · Cost: $2.75 (+merge: $0.00)
Annotations
Check failure on line 285 in src/firetower/incidents/services.py
github-actions / warden: find-bugs
update_or_create can steal ActionItems from another incident
`ActionItem.objects.update_or_create(linear_issue_id=issue["id"], defaults={"incident": incident, ...})` keys solely on `linear_issue_id`. If the same Linear issue is referenced as a related issue from a different incident's parent (or was previously associated with another incident), this sync will silently reassign the ActionItem to the current incident, mutating another incident's data. The subsequent delete step then removes ActionItems no longer matching by linear_issue_id, but the cross-incident hijack already occurred. This violates data isolation between incidents.
Check failure on line 69 in src/firetower/incidents/views.py
github-actions / warden: find-bugs
parse_incident_id uses re module without importing it
The new parse_incident_id function calls re.escape and re.match, but the import block shown does not include `import re`. If `re` is not imported elsewhere in the file, any call to parse_incident_id will raise NameError at runtime, breaking incident ID parsing for endpoints that depend on it.
Check failure on line 145 in src/firetower/integrations/services/linear.py
github-actions / warden: find-bugs
[EGV-DET] parse_incident_id uses re module without importing it (additional location)
The new parse_incident_id function calls re.escape and re.match, but the import block shown does not include `import re`. If `re` is not imported elsewhere in the file, any call to parse_incident_id will raise NameError at runtime, breaking incident ID parsing for endpoints that depend on it.
Check warning on line 78 in src/firetower/integrations/services/linear.py
github-actions / warden: find-bugs
Token refresh has a TOCTOU race that can clobber concurrent tokens
`_request_new_token` deletes all `LinearOAuthToken` rows and creates a new one inside `transaction.atomic()` without row-level locking. If two requests hit `_get_access_token` near expiry, both will call Linear's token endpoint and race to delete/insert, potentially invalidating a token another in-flight request just stored, and producing redundant token requests against Linear (rate-limit risk). Use `select_for_update()` or a database-level lock / cache lock so only one worker refreshes at a time.
Check warning on line 135 in src/firetower/integrations/services/linear.py
github-actions / warden: find-bugs
[U3P-SR3] Token refresh has a TOCTOU race that can clobber concurrent tokens (additional location)
`_request_new_token` deletes all `LinearOAuthToken` rows and creates a new one inside `transaction.atomic()` without row-level locking. If two requests hit `_get_access_token` near expiry, both will call Linear's token endpoint and race to delete/insert, potentially invalidating a token another in-flight request just stored, and producing redundant token requests against Linear (rate-limit risk). Use `select_for_update()` or a database-level lock / cache lock so only one worker refreshes at a time.