fix(security): log every scan attempt blocked before execution#1698
Open
anshul23102 wants to merge 1 commit into
Open
fix(security): log every scan attempt blocked before execution#1698anshul23102 wants to merge 1 commit into
anshul23102 wants to merge 1 commit into
Conversation
Previously, the audit log only recorded scans that passed every gate and
began executing, via executor.create_task's "task_created" event. Any
attempt rejected earlier -- missing consent, unknown plugin, invalid
preset, unauthorised target/credential/session policy, invalid input,
or a target blocked by safe mode -- raised its HTTPException and exited
the code path before ever reaching a log write. An administrator had no
way to determine, post-incident, whether a user had attempted to scan an
unauthorised target: the misuse simply left no trace.
Fix: added _log_blocked_scan_attempt, a thin wrapper around the
existing db.log_audit with severity="warning", and called it at every
rejection point in POST /task/start before its raise:
- task_blocked_invalid_payload (payload size/field-length guard)
- task_blocked_consent (consent not granted)
- task_blocked_plugin_not_found (unknown plugin_id)
- task_blocked_invalid_preset (preset not valid for the plugin)
- task_blocked_policy (target/credential/session policy
missing or insufficiently permissive)
- task_blocked_invalid_input (timeout/max_scan_time out of bounds)
- task_blocked_safe_mode (target rejected or validation timed
out under safe mode -- the scenario
the issue's repro steps describe)
- task_blocked_rate_limit (per-client-per-plugin quota exceeded)
Moved db = await get_db() to the top of start_task so it's available
to every rejection branch, including ones that previously ran before
the database handle was fetched.
Testing:
- testing/backend/integration/test_task_start_audit_log.py: 5 new tests
covering missing consent, unknown plugin, invalid preset, and safe-mode
target rejection all producing an audit_log row with severity
"warning", plus a regression guard confirming the existing successful-
path 'task_created' entry is unaffected (exactly one entry, not zero
or duplicated).
- pytest testing/backend/unit -q -m "not benchmark" -- 2210 passed (6
pre-existing failures unrelated to this change, confirmed identical on
unmodified main -- sandbox/subprocess tests failing in this sandbox)
- pytest testing/backend/integration -q -m "not benchmark" -- 289 passed,
9 skipped (284 baseline + 5 new)
- ruff check backend testing/backend -- all checks passed
- scripts/check-artifacts.sh origin/main -- clean
Fixes utksh1#1623
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Fixes #1623
Previously, the audit log only recorded scans that passed every gate and began executing, via
executor.create_task's"task_created"event. Any attempt rejected earlier — missing consent, unknown plugin, invalid preset, unauthorised target/credential/session policy, invalid input, or a target blocked by safe mode — raised itsHTTPExceptionand exited the code path before ever reaching a log write. An administrator had no way to determine, post-incident, whether a user had attempted to scan an unauthorised target: the misuse simply left no trace.Approach
Added
_log_blocked_scan_attempt, a thin wrapper around the existingdb.log_auditwithseverity="warning", and called it at every rejection point inPOST /task/startbefore its raise:task_blocked_invalid_payload— payload size/field-length guardtask_blocked_consent— consent not grantedtask_blocked_plugin_not_found— unknownplugin_idtask_blocked_invalid_preset— preset not valid for the plugintask_blocked_policy— target/credential/session policy missing or insufficiently permissivetask_blocked_invalid_input— timeout/max_scan_time out of boundstask_blocked_safe_mode— target rejected or validation timed out under safe mode (the exact scenario the issue's reproduction steps describe)task_blocked_rate_limit— per-client-per-plugin quota exceededMoved
db = await get_db()to the top ofstart_taskso it's available to every rejection branch, including ones that previously ran before the database handle was fetched.Related Issues
Closes #1623
Type of Change
How Has This Been Tested?
New test file
testing/backend/integration/test_task_start_audit_log.py(5 tests):task_blocked_consentaudit entry, severitywarningtask_blocked_plugin_not_foundentrytask_blocked_invalid_presetentrytask_blocked_safe_modeentry with the target in contexttask_createdentry still fires exactly once, unaffected by the new instrumentationFull suite run locally:
Checklist
Additional Notes
I'm contributing this through GSSoC. While building the test for the safe-mode rejection path, I found a separate, more serious issue:
start_task's safe-mode target check only looks at the literal"target"input key (if target := effective_inputs.get("target")), but plugins can declare their field under a different name —http_inspector's schema uses"url". For any plugin whose field isn't literally named"target", safe-mode validation is silently skipped entirely (confirmed:validate_target("http://8.8.8.8", True)correctly rejects it, but the request-level check never reaches it forhttp_inspector). I've flagged that separately rather than expanding this PR's scope, since it's a distinct bug from the audit-log gap this PR fixes.