Skip to content

Add stop_at_first_success to Prevent Duplicate Results (Fixes #1513) - #1514

Open
Aarush289 wants to merge 24 commits into
OWASP:masterfrom
Aarush289:Add_module_and_flow
Open

Add stop_at_first_success to Prevent Duplicate Results (Fixes #1513)#1514
Aarush289 wants to merge 24 commits into
OWASP:masterfrom
Aarush289:Add_module_and_flow

Conversation

@Aarush289

Copy link
Copy Markdown
Contributor

Proposed change

Your PR description goes here:

This PR introduces a new feature stop_at_first_success to prevent duplicate entries in scan results. Currently, the same successful detection can be logged multiple times for a given target, module, and port, which degrades user experience and clutters the output.
Fixes #1513
Before

image

After
image

The feature can be used as
image

Type of change

  • New core framework functionality
  • Bugfix (non-breaking change that fixes an issue)
  • Code refactoring without any functionality changes
  • New or existing module/payload change
  • Documentation/localization improvement
  • Test coverage improvement
  • Dependency upgrade
  • Other improvement (best practice, cleanup, optimization, etc)

Checklist

  • I've followed the contributing guidelines
  • I've digitally signed all my commits in this PR
  • I've run make pre-commit and confirm it didn't generate any warnings/changes
  • I've run make test and I confirm all tests passed locally
  • I've added/updated any relevant documentation in the docs/ folder
  • I've linked this PR with an open issue
  • I've tested and verified that my code works as intended and resolves the issue as described
  • I've attached screenshots demonstrating that my code works as intended (if applicable)
  • I've checked all other open PRs to avoid submitting duplicate work
  • I confirm that the code and comments in this PR are not direct unreviewed outputs of AI
  • I confirm that I am the Sole Responsible Author for every line of code, comment, and design decision

@coderabbitai

coderabbitai Bot commented Apr 14, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Summary by CodeRabbit

  • Improvements
    • Faster scans through enhanced early termination after a successful condition is detected.
    • More accurate matching of prior successful results, including port-specific checks and supported connection formats.
    • Improved consistency when multiple scan tasks reach the same successful result.
  • Bug Fixes
    • Prevented duplicate processing and duplicate success records during concurrent scanning.
    • Improved reliability when recording temporary scan events and handling existing database installations.
  • Documentation
    • Updated HTTP response validation to support optional stop-at-first-success settings.

Walkthrough

Adds shared preflight checks and unique temporary-event claims for stop_at_first_success. Engine paths skip matching prior successes, resolve ports from event data or URLs, and database lookups support optional port filtering across APSW and SQLAlchemy.

Changes

Stop-at-first-success handling

Layer / File(s) Summary
Temporary-event uniqueness and claims
nettacker/database/models.py, nettacker/database/sqlite.py, nettacker/database/db.py, tests/database/test_db.py
TempEvents now has a composite uniqueness constraint. SQLite setup migrates an existing table when the constraint is absent. APSW and SQLAlchemy insertion paths return whether the event claim succeeded and handle conflicts with rollback. Tests validate affected-row counts and direct session commits.
Port-aware temporary-event lookup
nettacker/database/db.py
find_temp_events(...) accepts an optional port and applies the serialized port filter in both database paths.
Engine preflight and result claiming
nettacker/core/lib/base.py, nettacker/core/lib/http.py, tests/test_yaml_schema_and_regex.py
BaseEngine and HttpEngine check for matching prior successes before execution. process_conditions(...) claims successful events and records duplicate claims as unsuccessful results. Port lookup supports event fields and numeric URL ports. The HTTP response schema accepts an optional stop_at_first_success string.
Estimated code review effort: 4 (Complex) ~45 minutes

Suggested reviewers: arkid15r

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 57.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the primary change: adding stop_at_first_success to prevent duplicate scan results.
Description check ✅ Passed The description explains the duplicate-result problem, the stop_at_first_success feature, configuration use, and issue addressed.
Linked Issues check ✅ Passed The implementation prevents repeated successes by using pre-checks, atomic temporary-event claims, and uniqueness across target, module, scan, event, and port [#1513].
Out of Scope Changes check ✅ Passed The database, engine, HTTP, schema, migration, and test changes directly support duplicate-result prevention and remain within the linked issue scope.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@nettacker/core/lib/base.py`:
- Around line 294-299: The branch that returns early on finding an existing
event leaks a partially mutated sub_step because run() deletes
sub_step["method"] and sub_step["response"] before the check; move the
"stop_at_first_success" check to run before deleting those keys or,
alternatively, ensure you restore sub_step["method"] and sub_step["response"]
(the originals saved in backup_response) before returning; locate the logic
around run(), the sub_step dict, the deletions of "method" and "response", and
the find_temp_events call to apply the fix.
- Around line 126-127: The dedupe key is missing the port so
find_temp_events(...) currently suppresses successes across different ports;
update all calls to find_temp_events(target, module_name, scan_id, event_name)
to pass the port (use event["response"]["port"]) and change the find_temp_events
function signature/implementation to include port in its lookup key; also update
any temp-event creation/storage logic used by find_temp_events so the stored key
includes port (apply the same change to the other two call sites referenced
around the other ranges).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 326ef8c1-32d8-4965-9438-406456214e8b

📥 Commits

Reviewing files that changed from the base of the PR and between 06c9bdf and c49eaad.

📒 Files selected for processing (1)
  • nettacker/core/lib/base.py

Comment thread nettacker/core/lib/base.py Outdated
Comment thread nettacker/core/lib/base.py Outdated
Comment thread nettacker/core/lib/base.py Outdated
@pUrGe12

pUrGe12 commented Apr 15, 2026

Copy link
Copy Markdown
Contributor

Can you do a small benchmark to see how this will affect the scan efficieny and latencies? If we're making multiple database calls for deduplication then it might increase the number of I/O calls? @Aarush289

@Aarush289

Copy link
Copy Markdown
Contributor Author

Okay sure, will do that.

@Aarush289

Copy link
Copy Markdown
Contributor Author

@pUrGe12 I have done basic bench-marking, file I/O calls will be increased but there is negligible effect on system time and cpu usage.

@securestep9

Copy link
Copy Markdown
Collaborator

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 07d0385d26

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread nettacker/core/lib/base.py Outdated
"module_name": module_name,
"scan_id": scan_id,
"event_name": event["response"]["stop_at_first_success"],
"port": event.get("ports", ""),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve the actual port in success markers

For engines that inherit BaseEngine.run (such as FTP, SSH, and socket), run converts ports to the singular port before calling this method. Consequently this expression stores every success marker under "", and the lookup above also uses that blank port. After one port succeeds, a later success on a different port is discarded, causing false negatives despite the feature being scoped by target, module, and port. Read both ports and port, as the normal scan-result path already does.

Useful? React with 👍 / 👎.

Comment on lines +127 to +129
existing = find_temp_events(
target, module_name, scan_id, event_name, port=event.get("ports", "")
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Claim the success marker atomically

When multiple generated substeps for the same target/module/port finish concurrently, each thread can complete this lookup before either reaches the marker insertion below. Both then insert a marker and submit a normal scan result, so the duplicate-results regression remains under the default concurrent execution. Because temp_events has no uniqueness constraint for this key, the marker must be claimed atomically and the result logged only by the thread whose claim succeeds.

Useful? React with 👍 / 👎.

Comment thread nettacker/core/lib/base.py Outdated
Comment on lines +295 to +300
if "stop_at_first_success" in backup_response:
event_name = backup_response["stop_at_first_success"]
existing = find_temp_events(
target, module_name, scan_id, event_name, port=sub_step.get("ports", "")
)
if existing:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Apply the preflight check to HttpEngine

For HTTP payloads, HttpEngine.run overrides this method and never performs the new preflight lookup, so every generated HTTP request is still sent even after an earlier request has succeeded; suppression happens only afterward in process_conditions. This is especially costly for the WAF-style payloads this option is intended for, where many probes remain after the first match, and means stop_at_first_success does not actually stop HTTP scanning at the first success. Move the check into shared code or add the equivalent before send_request in HttpEngine.run.

Useful? React with 👍 / 👎.

Comment on lines +125 to +126
if "stop_at_first_success" in event["response"]:
event_name = event["response"]["stop_at_first_success"]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Register the new response key in the YAML schema

The repository's HTTP_RESPONSE_SCHEMA in tests/test_yaml_schema_and_regex.py sets ignore_extra_keys=False but does not allow stop_at_first_success. As soon as a bundled HTTP module adopts this new framework option, the module-validation test rejects its response block with an unexpected-key error, so the feature cannot be integrated into the shipped YAML modules while keeping the required test suite green. Add this string-valued option to the response schema.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (2)
nettacker/database/sqlite.py (1)

7-13: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Document and type the public initializer.

Add a docstring and -> None to sqlite_create_tables.

As per coding guidelines, “Keep functions small, use type hints where practical, and add docstrings for public APIs.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@nettacker/database/sqlite.py` around lines 7 - 13, Update the public
sqlite_create_tables initializer to declare a None return type and add a concise
docstring describing its table-creation behavior, while preserving the existing
database setup and metadata creation flow.

Source: Coding guidelines

tests/database/test_db.py (1)

526-526: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Test duplicate claim outcomes on both backends.

Add an APSW test with changes() returning 0. Add an ORM test where commit() raises IntegrityError. Assert that each path reports a duplicate claim and rolls back the ORM session. Also test that a non-conflict persistence failure is not handled as a duplicate.

Also applies to: 606-606, 616-616

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/database/test_db.py` at line 526, Extend the duplicate-claim tests
around mock_connection.changes and the ORM claim path to cover both backends:
assert APSW changes() returning 0 reports a duplicate claim, and assert ORM
commit() raising IntegrityError reports a duplicate claim and rolls back the
session. Add a non-conflict persistence failure case and verify it is not
classified as a duplicate claim.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@nettacker/core/lib/base.py`:
- Around line 192-209: Update process_conditions() in nettacker/core/lib/base.py
to suppress and log an unsuccessful result only when the claim status explicitly
indicates a duplicate; propagate non-conflict persistence failures instead of
treating them as duplicates. In nettacker/database/db.py, update the APSW
operation at lines 354-361 and the ORM operation at lines 397-420 to return
distinct claimed, duplicate, and failed statuses, ensuring APSW retry exhaustion
reports failure unless a claim was actually stored.
- Around line 69-73: Unify port resolution across check_prior_success() and the
claim/insert path so both use the same effective port value. The resolver must
account for sub_step["ports"], the explicit port argument, and a numeric port
parsed from the URL using the existing precedence rules. Pass that resolved
value to find_temp_events() and serialize the identical value when inserting
claims, preventing duplicate requests for URL-only or port-only steps.

In `@nettacker/database/models.py`:
- Around line 37-46: Add upgrade logic in mysql_create_tables() and
postgres_create_database() to detect existing temp_events tables missing
uq_temp_events_claim and create the constraint/index, matching the existing
SQLite rebuild behavior. Keep Base.metadata.create_all() for initial setup and
ensure the migration is safe for already-updated databases.

In `@nettacker/database/sqlite.py`:
- Around line 34-36: Update the unique-constraint migration around the
has_constraint check to preserve existing temp_events rows: copy legacy rows
into the new TempEvents schema with deduplication before applying
uq_temp_events_claim, or ensure initialization is exclusive before any drop.
Keep the migration invoked by app.py initialization from losing active
dependency state across processes.

---

Nitpick comments:
In `@nettacker/database/sqlite.py`:
- Around line 7-13: Update the public sqlite_create_tables initializer to
declare a None return type and add a concise docstring describing its
table-creation behavior, while preserving the existing database setup and
metadata creation flow.

In `@tests/database/test_db.py`:
- Line 526: Extend the duplicate-claim tests around mock_connection.changes and
the ORM claim path to cover both backends: assert APSW changes() returning 0
reports a duplicate claim, and assert ORM commit() raising IntegrityError
reports a duplicate claim and rolls back the session. Add a non-conflict
persistence failure case and verify it is not classified as a duplicate claim.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 4f1348b4-bb11-4e50-a108-bfbd6877cfb1

📥 Commits

Reviewing files that changed from the base of the PR and between 8e7df0b and 879861b.

📒 Files selected for processing (6)
  • nettacker/core/lib/base.py
  • nettacker/core/lib/http.py
  • nettacker/database/db.py
  • nettacker/database/models.py
  • nettacker/database/sqlite.py
  • tests/database/test_db.py

Comment thread nettacker/core/lib/base.py
Comment thread nettacker/core/lib/base.py Outdated
Comment thread nettacker/database/models.py
Comment thread nettacker/database/sqlite.py Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Duplicate Entries in Scan Results Degrading User Experience

3 participants