Skip to content

Commit dea03fb

Browse files
Ilanlidoclaude
andauthored
CM-69233 ai guardrails respect ignores (#499)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 0eeb0f2 commit dea03fb

4 files changed

Lines changed: 178 additions & 272 deletions

File tree

cycode/cli/apps/ai_guardrails/scan/handlers.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from cycode.cli.apps.scan.code_scanner import _get_scan_documents_thread_func
2727
from cycode.cli.apps.scan.scan_parameters import get_scan_parameters
2828
from cycode.cli.cli_types import ScanTypeOption, SeverityOption
29+
from cycode.cli.files_collector.file_excluder import is_path_configured_in_exclusions
2930
from cycode.cli.models import Document
3031
from cycode.cli.utils.progress_bar import DummyProgressBar, ScanProgressBarSection
3132
from cycode.cli.utils.scan_utils import build_violation_summary
@@ -337,7 +338,7 @@ def _perform_scan(
337338

338339
scan_id = local_scan_result.scan_id
339340

340-
if local_scan_result.detections_count > 0:
341+
if local_scan_result.issue_detected:
341342
violation_summary = build_violation_summary([local_scan_result])
342343
return violation_summary, scan_id
343344

@@ -360,6 +361,10 @@ def _scan_path_for_secrets(ctx: typer.Context, file_path: str, policy: dict) ->
360361
if not file_path or not os.path.isfile(file_path):
361362
return None, None
362363

364+
if is_path_configured_in_exclusions(str(ScanTypeOption.SECRET), os.path.abspath(file_path)):
365+
logger.debug('Skipping scan; the path is in the ignore paths list, %s', {'file_path': file_path})
366+
return None, None
367+
363368
max_bytes = get_policy_value(policy, 'secrets', 'max_bytes', default=200000)
364369

365370
with open(file_path, encoding='utf-8', errors='replace') as f:

cycode/cli/files_collector/file_excluder.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def _is_subpath_of_cycode_configuration_folder(filename: str) -> bool:
2525
)
2626

2727

28-
def _is_path_configured_in_exclusions(scan_type: str, file_path: str) -> bool:
28+
def is_path_configured_in_exclusions(scan_type: str, file_path: str) -> bool:
2929
exclusions_by_path = configuration_manager.get_exclusions_by_scan_type(scan_type).get(
3030
consts.EXCLUSIONS_BY_PATH_SECTION_NAME, []
3131
)
@@ -106,7 +106,7 @@ def _is_relevant_file_to_scan_common(self, scan_type: str, filename: str) -> boo
106106
)
107107
return False
108108

109-
if _is_path_configured_in_exclusions(scan_type, filename):
109+
if is_path_configured_in_exclusions(scan_type, filename):
110110
logger.debug(
111111
'The document is irrelevant because its path is in the ignore paths list, %s', {'filename': filename}
112112
)

tests/cli/commands/ai_guardrails/scan/test_handlers.py

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Tests for AI guardrails handlers."""
22

3+
import os
34
from typing import Any
45
from unittest.mock import MagicMock, patch
56

@@ -8,12 +9,15 @@
89

910
from cycode.cli.apps.ai_guardrails.ides.base import DecisionAction, HookDecision
1011
from cycode.cli.apps.ai_guardrails.scan.handlers import (
12+
_perform_scan,
13+
_scan_path_for_secrets,
1114
handle_before_mcp_execution,
1215
handle_before_read_file,
1316
handle_before_submit_prompt,
1417
)
1518
from cycode.cli.apps.ai_guardrails.scan.payload import AIHookPayload
1619
from cycode.cli.apps.ai_guardrails.scan.types import AiHookEventType, AIHookOutcome, BlockReason
20+
from cycode.cli.models import Document, LocalScanResult
1721

1822

1923
@pytest.fixture
@@ -357,15 +361,56 @@ def test_handle_before_read_file_sensitive_path_scan_disabled_warns(
357361

358362
def test_scan_path_for_secrets_directory(mock_ctx: MagicMock, default_policy: dict[str, Any], fs: Any) -> None:
359363
"""Test that _scan_path_for_secrets returns (None, None) for directories."""
360-
from cycode.cli.apps.ai_guardrails.scan.handlers import _scan_path_for_secrets
361-
362364
fs.create_dir('/path/to/some_directory')
363365

364366
result = _scan_path_for_secrets(mock_ctx, '/path/to/some_directory', default_policy)
365367

366368
assert result == (None, None)
367369

368370

371+
@patch('cycode.cli.apps.ai_guardrails.scan.handlers._perform_scan')
372+
def test_scan_path_for_secrets_skips_path_configured_in_exclusions(
373+
mock_perform_scan: MagicMock, mock_ctx: MagicMock, default_policy: dict[str, Any], fs: Any
374+
) -> None:
375+
"""Test that a path ignored via `cycode ignore --by-path` is not scanned."""
376+
# `cycode ignore --by-path` stores absolute paths; on Windows that includes the drive prefix
377+
excluded_dir = os.path.abspath(os.path.join(os.sep, 'project', 'secrets'))
378+
file_path = os.path.join(excluded_dir, 'creds.env')
379+
fs.create_file(file_path, contents='password=hunter2')
380+
mock_perform_scan.return_value = ('Cycode found 1 violations', 'scan-id-123')
381+
382+
with patch(
383+
'cycode.cli.files_collector.file_excluder.configuration_manager.get_exclusions_by_scan_type',
384+
return_value={'paths': [excluded_dir]},
385+
):
386+
result = _scan_path_for_secrets(mock_ctx, file_path, default_policy)
387+
388+
assert result == (None, None)
389+
mock_perform_scan.assert_not_called()
390+
391+
392+
def test_perform_scan_no_violation_when_all_detections_excluded(mock_ctx: MagicMock) -> None:
393+
"""Test that detections filtered out by ignore rules do not produce a violation."""
394+
local_scan_result = LocalScanResult(
395+
scan_id='scan-id-123',
396+
report_url=None,
397+
document_detections=[],
398+
issue_detected=False,
399+
detections_count=1,
400+
relevant_detections_count=0,
401+
)
402+
document = Document(path='prompt-content.txt', content='some content', is_git_diff_format=False)
403+
404+
with patch(
405+
'cycode.cli.apps.ai_guardrails.scan.handlers._get_scan_documents_thread_func',
406+
return_value=lambda batch: ('scan-id-123', None, local_scan_result),
407+
):
408+
violation_summary, scan_id = _perform_scan(mock_ctx, [document], {}, timeout_seconds=5.0)
409+
410+
assert violation_summary is None
411+
assert scan_id == 'scan-id-123'
412+
413+
369414
# Tests for handle_before_mcp_execution
370415

371416

0 commit comments

Comments
 (0)