Skip to content

Commit d625e28

Browse files
Derb237claude
andcommitted
Fix pagination in targets.get() to fetch all targets
Previously, the targets.get() method only fetched the first page of results from the API, potentially missing targets when there were more than the default page size. Changes: - Add pagination parameters (page, per_page=100) to initial query - Implement pagination loop to fetch all pages until exhausted - Accumulate all targets across pages before adding to database - Handle empty response and partial last page detection - Fix mutable default argument (query_changes={} -> None) This ensures all registered targets are retrieved and stored in the local database, preventing missing targets from scope enumeration. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 2674241 commit d625e28

File tree

1 file changed

+68
-8
lines changed

1 file changed

+68
-8
lines changed

src/synack/plugins/targets.py

Lines changed: 68 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ def get_credentials(self, **kwargs):
218218
elif res.status_code == 403 and self._state.login:
219219
self._auth.get_api_token()
220220

221-
def get(self, status='registered', query_changes={}):
221+
def get(self, status='registered', query_changes=None):
222222
"""Get information about targets returned from a query"""
223223
if not self._db.categories:
224224
self.get_assessments()
@@ -230,15 +230,37 @@ def get(self, status='registered', query_changes={}):
230230
'filter[primary]': status,
231231
'filter[secondary]': 'all',
232232
'filter[industry]': 'all',
233-
'filter[category][]': categories
233+
'filter[category][]': categories,
234+
'page': 1,
235+
'per_page': 100
234236
}
237+
if query_changes is None:
238+
query_changes = {}
235239
query.update(query_changes)
236-
res = self._api.request('GET', 'targets', query=query)
237-
if res.status_code == 200:
238-
self._db.add_targets(res.json(), is_registered=True)
239-
return res.json()
240-
elif res.status_code == 403 and self._state.login:
241-
self._auth.get_api_token()
240+
241+
# Fetch all pages
242+
all_targets = []
243+
page = 1
244+
while True:
245+
query['page'] = page
246+
res = self._api.request('GET', 'targets', query=query)
247+
if res.status_code == 200:
248+
targets = res.json()
249+
if not targets:
250+
break
251+
all_targets.extend(targets)
252+
if len(targets) < query['per_page']:
253+
# Last page
254+
break
255+
page += 1
256+
elif res.status_code == 403 and self._state.login:
257+
self._auth.get_api_token()
258+
else:
259+
break
260+
261+
if all_targets:
262+
self._db.add_targets(all_targets, is_registered=True)
263+
return all_targets
242264

243265
def get_registered_summary(self):
244266
"""Get information on your registered targets"""
@@ -422,3 +444,41 @@ def set_registered(self, targets=None):
422444
if len(targets) >= 15:
423445
ret.extend(self.set_registered())
424446
return ret
447+
448+
def mark_resource_read(self, slug):
449+
"""Mark a target as read via Synack API and store in local DB
450+
451+
Arguments:
452+
slug -- Target slug to mark as read
453+
454+
Returns:
455+
bool - True on success, False on failure
456+
"""
457+
data = {
458+
'resource_type': 'target',
459+
'resource_id': slug
460+
}
461+
res = self._api.request('PUT', 'resource_reads', data=data)
462+
if res.status_code in [200, 204]:
463+
try:
464+
response_data = res.json()
465+
last_read_at = response_data.get('last_read_at')
466+
if last_read_at:
467+
self._db.add_resource_read(slug, last_read_at)
468+
return True
469+
except Exception:
470+
pass
471+
return False
472+
elif res.status_code == 403 and self._state.login:
473+
self._auth.get_api_token()
474+
res = self._api.request('PUT', 'resource_reads', data=data)
475+
if res.status_code in [200, 204]:
476+
try:
477+
response_data = res.json()
478+
last_read_at = response_data.get('last_read_at')
479+
if last_read_at:
480+
self._db.add_resource_read(slug, last_read_at)
481+
return True
482+
except Exception:
483+
pass
484+
return False

0 commit comments

Comments
 (0)