cycode 3.16.1.dev7__py3-none-any.whl → 3.16.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
cycode/__init__.py CHANGED
@@ -1 +1,8 @@
1
- __version__ = '3.16.1.dev7' # DON'T TOUCH. Placeholder. Will be filled automatically on poetry build from Git Tag
1
+ import time as _time
2
+
3
+ # Unix-epoch wall clock captured at the earliest possible moment of CLI
4
+ # startup. Sent as `scan_parameters.cli_start_time` so the server can compute
5
+ # end-to-end scan duration from the moment the user actually triggered it.
6
+ _BOOT_WALL: float = _time.time()
7
+
8
+ __version__ = '3.16.2' # DON'T TOUCH. Placeholder. Will be filled automatically on poetry build from Git Tag
cycode/cli/app.py CHANGED
@@ -1,3 +1,4 @@
1
+ import importlib
1
2
  import logging
2
3
  import sys
3
4
  from typing import Annotated, Optional
@@ -10,12 +11,7 @@ from typer._completion_shared import Shells
10
11
  from typer.completion import install_callback, show_callback
11
12
 
12
13
  from cycode import __version__
13
- from cycode.cli.apps import ai_guardrails, ai_remediation, auth, configure, ignore, report, report_import, scan, status
14
14
  from cycode.cli.apps.api import get_platform_group
15
-
16
- if sys.version_info >= (3, 10):
17
- from cycode.cli.apps import mcp
18
-
19
15
  from cycode.cli.cli_types import OutputTypeOption
20
16
  from cycode.cli.consts import CLI_CONTEXT_SETTINGS
21
17
  from cycode.cli.printers import ConsolePrinter
@@ -46,17 +42,88 @@ app = typer.Typer(
46
42
  add_completion=False, # we add it manually to control the rich help panel
47
43
  )
48
44
 
49
- app.add_typer(ai_guardrails.app)
50
- app.add_typer(ai_remediation.app)
51
- app.add_typer(auth.app)
52
- app.add_typer(configure.app)
53
- app.add_typer(ignore.app)
54
- app.add_typer(report.app)
55
- app.add_typer(report_import.app)
56
- app.add_typer(scan.app)
57
- app.add_typer(status.app)
45
+ # Top-level subcommand → module providing its Typer app. Peeking at sys.argv
46
+ # lets us import only the invoked subapp on the hot path (e.g.
47
+ # `cycode ai-guardrails scan`), skipping ~300ms of unrelated imports.
48
+ _SUBAPP_MODULES: dict[str, str] = {
49
+ 'ai-guardrails': 'cycode.cli.apps.ai_guardrails',
50
+ 'ai-remediation': 'cycode.cli.apps.ai_remediation',
51
+ 'auth': 'cycode.cli.apps.auth',
52
+ 'configure': 'cycode.cli.apps.configure',
53
+ 'ignore': 'cycode.cli.apps.ignore',
54
+ 'report': 'cycode.cli.apps.report',
55
+ 'import': 'cycode.cli.apps.report_import',
56
+ 'scan': 'cycode.cli.apps.scan',
57
+ 'status': 'cycode.cli.apps.status',
58
+ }
58
59
  if sys.version_info >= (3, 10):
59
- app.add_typer(mcp.app)
60
+ _SUBAPP_MODULES['mcp'] = 'cycode.cli.apps.mcp'
61
+
62
+ # Aliases: alternate spellings that resolve to a primary subcommand key.
63
+ _SUBAPP_ALIASES: dict[str, str] = {
64
+ 'ai_remediation': 'ai-remediation', # backward-compat underscore form
65
+ 'version': 'status',
66
+ }
67
+
68
+ # Root-level options that consume a following value; argv-peek must skip past
69
+ # both the option and its value when scanning for the first positional arg.
70
+ _ROOT_OPTS_WITH_VALUE = frozenset(
71
+ {
72
+ '--output',
73
+ '-o',
74
+ '--user-agent',
75
+ '--client-secret',
76
+ '--client-id',
77
+ '--id-token',
78
+ '--show-completion',
79
+ }
80
+ )
81
+
82
+
83
+ def _detect_invocation() -> tuple[Optional[str], Optional[str]]:
84
+ """Return (top-level-subapp, second-level-subcommand) parsed from sys.argv.
85
+
86
+ Both values may be None: when no positional arg matches a known subapp,
87
+ or when the user only provided a top-level subcommand.
88
+ """
89
+ positionals = []
90
+ args = sys.argv[1:]
91
+ i = 0
92
+ while i < len(args):
93
+ arg = args[i]
94
+ if arg in _ROOT_OPTS_WITH_VALUE:
95
+ i += 2
96
+ elif arg.startswith('-'):
97
+ # Any flag form: short, long, --key=value, or '--' marker. Skip the token only.
98
+ i += 1
99
+ else:
100
+ positionals.append(arg)
101
+ if len(positionals) >= 2:
102
+ break
103
+ i += 1
104
+ subapp = positionals[0] if positionals else None
105
+ subapp = _SUBAPP_ALIASES.get(subapp, subapp)
106
+ if subapp not in _SUBAPP_MODULES:
107
+ return None, None
108
+ subcommand = positionals[1] if len(positionals) >= 2 else None
109
+ return subapp, subcommand
110
+
111
+
112
+ # Computed once at import; reused by lazy registration and the version-checker skip.
113
+ _INVOKED_SUBAPP, _INVOKED_SUBCOMMAND = _detect_invocation()
114
+
115
+
116
+ def _register_subapps(only: Optional[str]) -> None:
117
+ if only is not None:
118
+ app.add_typer(importlib.import_module(_SUBAPP_MODULES[only]).app)
119
+ return
120
+ # Cold path (--help, completion, unknown subcommand): load all modules so
121
+ # root help lists everything. Deduplicate since aliases share modules.
122
+ for module_path in dict.fromkeys(_SUBAPP_MODULES.values()):
123
+ app.add_typer(importlib.import_module(module_path).app)
124
+
125
+
126
+ _register_subapps(_INVOKED_SUBAPP)
60
127
 
61
128
  # Register the `platform` command group (dynamically built from the OpenAPI spec).
62
129
  # The group itself is constructed cheaply at import time; the spec is only fetched
@@ -81,6 +148,12 @@ typer.main.get_group = _get_group_with_platform
81
148
 
82
149
 
83
150
  def check_latest_version_on_close(ctx: typer.Context) -> None:
151
+ # Skip on `cycode ai-guardrails scan` — it emits JSON to stdout, so an
152
+ # upgrade notice would corrupt the response. Human-driven sibling commands
153
+ # (install, uninstall, status, session-start) still get the notice.
154
+ if (_INVOKED_SUBAPP, _INVOKED_SUBCOMMAND) == ('ai-guardrails', 'scan'):
155
+ return
156
+
84
157
  output = ctx.obj.get('output')
85
158
  # don't print anything if the output is JSON
86
159
  if output == OutputTypeOption.JSON:
@@ -26,13 +26,26 @@ def load_plugin_json(path: Path) -> Optional[dict]:
26
26
  return None
27
27
 
28
28
 
29
+ def build_global_config_file(path: Path, mcp_servers: Optional[dict]) -> Optional[dict]:
30
+ """Wrap a global (non-plugin) MCP config into the session-context file shape.
31
+
32
+ Returns ``{"path": <full path>, "content": <{"mcpServers": ...} JSON>}`` when
33
+ there are servers, else ``None``. ``content`` is normalized to the canonical
34
+ ``{"mcpServers": {...}}`` shape, dropping everything else in the source file.
35
+ """
36
+ servers = mcp_servers or {}
37
+ if not servers:
38
+ return None
39
+ return {'path': str(path), 'content': json.dumps({'mcpServers': servers})}
40
+
41
+
29
42
  def walk_enabled_plugins(
30
43
  plugin_entries: dict[str, Any],
31
44
  is_enabled: Callable[[Any], bool],
32
45
  locate_dir: Callable[[str, str], Optional[Path]],
33
46
  read_plugin: Callable[[Path], tuple[dict, dict]],
34
- ) -> tuple[dict, dict]:
35
- """Iterate enabled plugins; merge their MCP servers and metadata.
47
+ ) -> dict:
48
+ """Iterate enabled plugins and build their inventory metadata.
36
49
 
37
50
  Args:
38
51
  plugin_entries: ``{<plugin>@<marketplace>: settings}`` map from the IDE config.
@@ -42,13 +55,13 @@ def walk_enabled_plugins(
42
55
  filesystem path or None if it can't be resolved.
43
56
  read_plugin: given the plugin path, returns ``(entry_fields, servers)``:
44
57
  ``entry_fields`` are extra metadata to attach to the inventory entry
45
- (name/version/description/...), ``servers`` are MCP servers contributed.
58
+ (name/version/description/...); ``servers`` are the plugin's MCP
59
+ servers, which ``read_plugin`` uses to derive that metadata.
46
60
 
47
- Returns ``(merged_mcp_servers, enriched_plugins)``. Plugin keys without
48
- ``@`` (or that fail to resolve to a directory) still appear in the
49
- inventory with just ``{'enabled': True}`` so we don't silently drop them.
61
+ Returns ``enriched_plugins``. Plugin keys without ``@`` (or that fail to
62
+ resolve to a directory) still appear in the inventory with just
63
+ ``{'enabled': True}`` so we don't silently drop them.
50
64
  """
51
- merged_mcp: dict = {}
52
65
  enriched: dict = {}
53
66
 
54
67
  for plugin_key, settings in plugin_entries.items():
@@ -66,8 +79,7 @@ def walk_enabled_plugins(
66
79
  if plugin_dir is None:
67
80
  continue
68
81
 
69
- plugin_fields, servers = read_plugin(plugin_dir)
82
+ plugin_fields, _ = read_plugin(plugin_dir)
70
83
  entry.update(plugin_fields)
71
- merged_mcp.update(servers)
72
84
 
73
- return merged_mcp, enriched
85
+ return enriched
@@ -167,10 +167,16 @@ class IDE(ABC):
167
167
  """
168
168
  return None
169
169
 
170
- def get_session_context(self) -> tuple[dict, dict]:
171
- """Return ``(mcp_servers, enabled_plugins)`` for session-context reporting.
170
+ def get_session_context(self) -> tuple[Optional[dict], dict]:
171
+ """Return ``(global_config_file, enabled_plugins)`` for session-context reporting.
172
172
 
173
- Default: empty dicts (no plugin system, no discoverable MCP config).
173
+ ``global_config_file`` is the IDE's global (non-plugin) MCP config as
174
+ ``{"path": <full path>, "content": <normalized {"mcpServers": ...} JSON>}``,
175
+ or ``None`` when there is no global MCP config. ``enabled_plugins`` maps each
176
+ enabled plugin key to its metadata (including its own ``mcp_config_file``
177
+ content and ``mcp_config_file_path``).
178
+
179
+ Default: ``(None, {})`` (no plugin system, no discoverable MCP config).
174
180
  Override to surface MCP/plugin inventory.
175
181
  """
176
- return {}, {}
182
+ return None, {}
@@ -7,7 +7,11 @@ from pathlib import Path
7
7
  from typing import ClassVar, Optional
8
8
 
9
9
  from cycode.cli.apps.ai_guardrails.consts import CYCODE_SCAN_PROMPT_COMMAND, CYCODE_SESSION_START_COMMAND
10
- from cycode.cli.apps.ai_guardrails.ides._plugin_utils import load_plugin_json, walk_enabled_plugins
10
+ from cycode.cli.apps.ai_guardrails.ides._plugin_utils import (
11
+ build_global_config_file,
12
+ load_plugin_json,
13
+ walk_enabled_plugins,
14
+ )
11
15
  from cycode.cli.apps.ai_guardrails.ides.base import IDE, DecisionAction, HookDecision
12
16
  from cycode.cli.apps.ai_guardrails.scan.payload import AIHookPayload
13
17
  from cycode.cli.apps.ai_guardrails.scan.types import AiHookEventType
@@ -184,14 +188,17 @@ def _read_claude_plugin(plugin_dir: Path) -> tuple[dict, dict]:
184
188
  if field in manifest:
185
189
  entry[field] = manifest[field]
186
190
 
187
- mcp_config = load_plugin_json(plugin_dir / '.mcp.json') or {}
191
+ mcp_config_path = plugin_dir / '.mcp.json'
192
+ mcp_config = load_plugin_json(mcp_config_path) or {}
188
193
  servers: dict = mcp_config.get('mcpServers') or {}
189
194
  if servers:
190
195
  entry['mcp_server_names'] = list(servers.keys())
196
+ entry['mcp_config_file_path'] = str(mcp_config_path)
197
+ entry['mcp_config_file'] = json.dumps(mcp_config)
191
198
  return entry, servers
192
199
 
193
200
 
194
- def resolve_plugins(settings: dict) -> tuple[dict, dict]:
201
+ def resolve_plugins(settings: dict) -> dict:
195
202
  """Walk Claude Code's ``enabledPlugins`` via the shared plugin walker.
196
203
 
197
204
  Each enabled plugin's marketplace is resolved through
@@ -354,15 +361,11 @@ class ClaudeCode(IDE):
354
361
  config = load_claude_config()
355
362
  return _email_from_config(config) if config else None
356
363
 
357
- def get_session_context(self) -> tuple[dict, dict]:
364
+ def get_session_context(self) -> tuple[Optional[dict], dict]:
358
365
  config = load_claude_config()
359
- mcp_servers: dict = dict(get_mcp_servers(config) or {}) if config else {}
366
+ global_config_file = build_global_config_file(_CLAUDE_CONFIG_PATH, get_mcp_servers(config)) if config else None
360
367
 
361
368
  settings = load_claude_settings()
362
- if settings:
363
- plugin_mcp, enriched_plugins = resolve_plugins(settings)
364
- mcp_servers.update(plugin_mcp)
365
- else:
366
- enriched_plugins = {}
369
+ enriched_plugins = resolve_plugins(settings) if settings else {}
367
370
 
368
- return mcp_servers, enriched_plugins
371
+ return global_config_file, enriched_plugins
@@ -14,7 +14,11 @@ else: # pragma: no cover - py<3.11 fallback
14
14
  import tomli as tomllib
15
15
 
16
16
  from cycode.cli.apps.ai_guardrails.consts import CYCODE_SCAN_PROMPT_COMMAND, CYCODE_SESSION_START_COMMAND
17
- from cycode.cli.apps.ai_guardrails.ides._plugin_utils import load_plugin_json, walk_enabled_plugins
17
+ from cycode.cli.apps.ai_guardrails.ides._plugin_utils import (
18
+ build_global_config_file,
19
+ load_plugin_json,
20
+ walk_enabled_plugins,
21
+ )
18
22
  from cycode.cli.apps.ai_guardrails.ides.base import IDE, DecisionAction, HookDecision
19
23
  from cycode.cli.apps.ai_guardrails.scan.payload import AIHookPayload
20
24
  from cycode.cli.apps.ai_guardrails.scan.types import AiHookEventType
@@ -129,16 +133,19 @@ def _read_codex_plugin(plugin_dir: Path) -> tuple[dict, dict]:
129
133
  mcp_ref = manifest.get('mcpServers')
130
134
  if not mcp_ref:
131
135
  return entry, {}
132
- mcp_doc = load_plugin_json(plugin_dir / mcp_ref) or {}
136
+ mcp_config_path = plugin_dir / mcp_ref
137
+ mcp_doc = load_plugin_json(mcp_config_path) or {}
133
138
  servers = mcp_doc.get('mcpServers', mcp_doc)
134
139
  if not isinstance(servers, dict):
135
140
  servers = {}
136
141
  if servers:
137
142
  entry['mcp_server_names'] = list(servers.keys())
143
+ entry['mcp_config_file_path'] = str(mcp_config_path)
144
+ entry['mcp_config_file'] = json.dumps(mcp_doc)
138
145
  return entry, servers
139
146
 
140
147
 
141
- def _resolve_codex_plugins(config: dict) -> tuple[dict, dict]:
148
+ def _resolve_codex_plugins(config: dict) -> dict:
142
149
  """Walk enabled ``[plugins."<plugin>@<marketplace>"]`` entries."""
143
150
  return walk_enabled_plugins(
144
151
  plugin_entries=config.get('plugins') or {},
@@ -297,13 +304,14 @@ class Codex(IDE):
297
304
  def get_user_email(self) -> Optional[str]:
298
305
  return _email_from_auth()
299
306
 
300
- def get_session_context(self) -> tuple[dict, dict]:
307
+ def get_session_context(self) -> tuple[Optional[dict], dict]:
301
308
  config = _load_codex_config()
302
309
  if not config:
303
- return {}, {}
304
- # Codex stores MCP servers under `[mcp_servers.<name>]`. Plugin-contributed
305
- # servers (via `[plugins."<plugin>@<marketplace>"]`) merge on top.
306
- mcp_servers: dict = dict(config.get('mcp_servers') or {})
307
- plugin_mcp, enriched_plugins = _resolve_codex_plugins(config)
308
- mcp_servers.update(plugin_mcp)
309
- return mcp_servers, enriched_plugins
310
+ return None, {}
311
+ # Codex stores MCP servers under `[mcp_servers.<name>]`; the global config
312
+ # file becomes its own session-context file. Plugins (via
313
+ # `[plugins."<plugin>@<marketplace>"]`) carry their own config files.
314
+ config_path = _codex_config_toml_path('user')
315
+ global_config_file = build_global_config_file(config_path, config.get('mcp_servers'))
316
+ enriched_plugins = _resolve_codex_plugins(config)
317
+ return global_config_file, enriched_plugins
@@ -6,6 +6,7 @@ from pathlib import Path
6
6
  from typing import ClassVar, Optional
7
7
 
8
8
  from cycode.cli.apps.ai_guardrails.consts import CYCODE_SCAN_PROMPT_COMMAND, CYCODE_SESSION_START_COMMAND
9
+ from cycode.cli.apps.ai_guardrails.ides._plugin_utils import build_global_config_file
9
10
  from cycode.cli.apps.ai_guardrails.ides.base import IDE, DecisionAction, HookDecision
10
11
  from cycode.cli.apps.ai_guardrails.scan.payload import AIHookPayload
11
12
  from cycode.cli.apps.ai_guardrails.scan.types import AiHookEventType
@@ -39,9 +40,14 @@ def _user_hooks_dir() -> Path:
39
40
  return Path.home() / '.config' / 'Cursor'
40
41
 
41
42
 
43
+ def _cursor_mcp_config_path() -> Path:
44
+ """User-scope Cursor MCP config path (``~/.cursor/mcp.json``, all platforms)."""
45
+ return Path.home() / '.cursor' / _MCP_CONFIG_FILENAME
46
+
47
+
42
48
  def _load_cursor_mcp_config(config_path: Optional[Path] = None) -> Optional[dict]:
43
49
  """Load and parse `~/.cursor/mcp.json`. Returns None if missing/invalid."""
44
- path = config_path or (Path.home() / '.cursor' / _MCP_CONFIG_FILENAME)
50
+ path = config_path or _cursor_mcp_config_path()
45
51
  if not path.exists():
46
52
  logger.debug('Cursor MCP config file not found, %s', {'path': str(path)})
47
53
  return None
@@ -113,7 +119,10 @@ class Cursor(IDE):
113
119
  ide_version=raw_payload.get('cursor_version'),
114
120
  )
115
121
 
116
- def get_session_context(self) -> tuple[dict, dict]:
122
+ def get_session_context(self) -> tuple[Optional[dict], dict]:
117
123
  config = _load_cursor_mcp_config()
118
- mcp_servers = dict((config or {}).get('mcpServers') or {}) if config else {}
119
- return mcp_servers, {}
124
+ if not config:
125
+ return None, {}
126
+ config_path = _cursor_mcp_config_path()
127
+ global_config_file = build_global_config_file(config_path, config.get('mcpServers'))
128
+ return global_config_file, {}
@@ -1,5 +1,8 @@
1
1
  """Handle AI guardrails session start: auth, conversation creation, session context."""
2
2
 
3
+ import os
4
+ import platform
5
+ import socket
3
6
  import sys
4
7
  from typing import TYPE_CHECKING, Annotated, Optional
5
8
 
@@ -20,14 +23,25 @@ if TYPE_CHECKING:
20
23
  logger = get_logger('AI Guardrails')
21
24
 
22
25
 
26
+ def _get_logged_in_user() -> Optional[str]:
27
+ """Best-effort OS account name (whoami). None if it can't be resolved."""
28
+ try:
29
+ return os.getlogin()
30
+ except Exception:
31
+ return None
32
+
33
+
23
34
  def _report_session_context(ai_client: 'AISecurityManagerClient', ide: IDE, user_email: Optional[str]) -> None:
24
35
  """Report IDE session context to the AI security manager. Never raises."""
25
36
  try:
26
- mcp_servers, enabled_plugins = ide.get_session_context()
27
- if not mcp_servers and not enabled_plugins:
37
+ global_config_file, enabled_plugins = ide.get_session_context()
38
+ if not global_config_file and not enabled_plugins:
28
39
  return
29
40
  ai_client.report_session_context(
30
- mcp_servers=mcp_servers,
41
+ hostname=socket.gethostname(),
42
+ platform=platform.system(),
43
+ logged_in_user=_get_logged_in_user(),
44
+ global_config_file=global_config_file,
31
45
  enabled_plugins=enabled_plugins,
32
46
  user_email=user_email,
33
47
  )
@@ -204,18 +204,21 @@ def _get_scan_documents_thread_func(
204
204
  'zip_file_size': zip_file_size,
205
205
  },
206
206
  )
207
- report_scan_status(
208
- cycode_client,
209
- scan_type,
210
- scan_id,
211
- scan_completed,
212
- relevant_detections_count,
213
- detections_count,
214
- len(batch),
215
- zip_file_size,
216
- command_scan_type,
217
- error_message,
218
- )
207
+ # Sync flows already received the full result inline; only async flows
208
+ # need a separate status report to signal polling completion.
209
+ if not should_use_sync_flow:
210
+ report_scan_status(
211
+ cycode_client,
212
+ scan_type,
213
+ scan_id,
214
+ scan_completed,
215
+ relevant_detections_count,
216
+ detections_count,
217
+ len(batch),
218
+ zip_file_size,
219
+ command_scan_type,
220
+ error_message,
221
+ )
219
222
 
220
223
  return scan_id, error, local_scan_result
221
224
 
@@ -2,6 +2,7 @@ from typing import Optional
2
2
 
3
3
  import typer
4
4
 
5
+ from cycode import _BOOT_WALL
5
6
  from cycode.cli.apps.scan.remote_url_resolver import get_remote_url_scan_parameter
6
7
  from cycode.cli.utils.scan_utils import generate_unique_scan_id
7
8
  from cycode.logger import get_logger
@@ -17,6 +18,7 @@ def _get_default_scan_parameters(ctx: typer.Context) -> dict:
17
18
  'license_compliance': ctx.obj.get('license-compliance'),
18
19
  'command_type': ctx.info_name.replace('-', '_'), # save backward compatibility
19
20
  'aggregation_id': str(generate_unique_scan_id()),
21
+ 'cli_start_time': _BOOT_WALL,
20
22
  }
21
23
 
22
24
 
@@ -189,6 +189,10 @@ def enrich_scan_result_with_data_from_detection_rules(
189
189
  for detection in detections_per_file.detections:
190
190
  detection_rule_ids.add(detection.detection_rule_id)
191
191
 
192
+ if not detection_rule_ids:
193
+ logger.debug('No detections to enrich, skipping detection_rules fetch')
194
+ return
195
+
192
196
  detection_rules = cycode_client.get_detection_rules(detection_rule_ids)
193
197
  detection_rules_by_id = {detection_rule.detection_rule_id: detection_rule for detection_rule in detection_rules}
194
198
 
cycode/cli/consts.py CHANGED
@@ -53,6 +53,30 @@ SECRET_SCAN_FILE_EXTENSIONS_TO_IGNORE = (
53
53
  '.iso',
54
54
  )
55
55
 
56
+ # Fallback block-list used for SAST only when the server does not return scannable extensions
57
+ # (e.g. when the customer has custom rules, any text file is scannable). These are non-source
58
+ # data formats that can slip past binary detection (the EICAR test file and ClamAV signature
59
+ # databases are plain ASCII) and may be quarantined by object-storage antivirus after upload.
60
+ SAST_SCAN_FILE_EXTENSIONS_TO_IGNORE = (
61
+ '.bin',
62
+ '.cvd',
63
+ '.cld',
64
+ '.cud',
65
+ '.hdb',
66
+ '.hsb',
67
+ '.mdb',
68
+ '.msb',
69
+ '.ndb',
70
+ '.ndu',
71
+ '.ldb',
72
+ '.ldu',
73
+ '.idb',
74
+ '.fp',
75
+ '.sfp',
76
+ '.ign',
77
+ '.ign2',
78
+ )
79
+
56
80
  SCA_CONFIGURATION_SCAN_SUPPORTED_FILES = ( # keep in lowercase
57
81
  'cargo.lock',
58
82
  'cargo.toml',
@@ -63,7 +63,10 @@ class Excluder:
63
63
  }
64
64
  self._non_scannable_extensions: dict[str, tuple[str, ...]] = {
65
65
  consts.SECRET_SCAN_TYPE: consts.SECRET_SCAN_FILE_EXTENSIONS_TO_IGNORE,
66
+ consts.SAST_SCAN_TYPE: consts.SAST_SCAN_FILE_EXTENSIONS_TO_IGNORE,
66
67
  }
68
+ # Tracks scan types for which the SAST fallback log has already been emitted (log once, not per file)
69
+ self._logged_sast_fallback = False
67
70
 
68
71
  def apply_scan_config(self, scan_type: str, scan_config: 'models.ScanConfiguration') -> None:
69
72
  if scan_config.scannable_extensions:
@@ -86,6 +89,11 @@ class Excluder:
86
89
 
87
90
  non_scannable_extensions = self._non_scannable_extensions.get(scan_type)
88
91
  if non_scannable_extensions:
92
+ # For SAST, reaching the block-list means the server returned no scannable extensions
93
+ # (e.g. custom rules, or no remote config). Log once so this is diagnosable.
94
+ if scan_type == consts.SAST_SCAN_TYPE and not self._logged_sast_fallback:
95
+ self._logged_sast_fallback = True
96
+ logger.debug('No scannable extensions provided for SAST; falling back to the built-in ignore list')
89
97
  return not filename.endswith(non_scannable_extensions)
90
98
 
91
99
  return True
@@ -93,15 +93,21 @@ class AISecurityManagerClient:
93
93
 
94
94
  def report_session_context(
95
95
  self,
96
- mcp_servers: Optional[dict] = None,
96
+ hostname: Optional[str] = None,
97
+ platform: Optional[str] = None,
98
+ logged_in_user: Optional[str] = None,
99
+ global_config_file: Optional[dict] = None,
97
100
  enabled_plugins: Optional[dict] = None,
98
101
  user_email: Optional[str] = None,
99
102
  ) -> None:
100
103
  """Report session context to the backend."""
101
104
  body: dict = {
102
- 'mcp_servers': mcp_servers,
103
- 'enabled_plugins': enabled_plugins,
105
+ 'hostname': hostname,
106
+ 'platform': platform,
107
+ 'logged_in_user': logged_in_user,
104
108
  'user_email': user_email,
109
+ 'global_config_file': global_config_file,
110
+ 'enabled_plugins': enabled_plugins,
105
111
  }
106
112
 
107
113
  try:
@@ -24,19 +24,10 @@ class BaseTokenAuthClient(CycodeClient, ABC):
24
24
  self.client_id = client_id
25
25
 
26
26
  self._credentials_manager = CredentialsManager()
27
- # load cached access token
28
- access_token, expires_in, creator = self._credentials_manager.get_access_token()
29
-
30
- self._access_token = self._expires_in = None
31
- expected_creator = self._create_jwt_creator()
32
- if creator == expected_creator:
33
- # we must be sure that cached access token is created using the same client id and client secret.
34
- # because client id and client secret could be passed via command, via env vars or via config file.
35
- # we must not use cached access token if client id or client secret was changed.
36
- self._access_token = access_token
37
- self._expires_in = arrow.get(expires_in) if expires_in else None
38
-
27
+ self._access_token = None
28
+ self._expires_in = None
39
29
  self._lock = Lock()
30
+ self._load_token_from_disk()
40
31
 
41
32
  def get_access_token(self) -> str:
42
33
  with self._lock:
@@ -51,8 +42,30 @@ class BaseTokenAuthClient(CycodeClient, ABC):
51
42
  self._credentials_manager.update_access_token(None, None, None)
52
43
 
53
44
  def refresh_access_token_if_needed(self) -> None:
54
- if self._access_token is None or self._expires_in is None or arrow.utcnow() >= self._expires_in:
55
- self.refresh_access_token()
45
+ if self._has_valid_token():
46
+ return
47
+ # Re-check disk before doing the network refresh: another client instance
48
+ # in this process may have already refreshed and persisted a fresh token.
49
+ self._load_token_from_disk()
50
+ if self._has_valid_token():
51
+ return
52
+ self.refresh_access_token()
53
+
54
+ def _has_valid_token(self) -> bool:
55
+ return self._access_token is not None and self._expires_in is not None and arrow.utcnow() < self._expires_in
56
+
57
+ def _load_token_from_disk(self) -> None:
58
+ access_token, expires_in, creator = self._credentials_manager.get_access_token()
59
+ expected_creator = self._create_jwt_creator()
60
+ # We must be sure that cached access token is created using the same client id and client secret.
61
+ # Because client id and client secret could be passed via command, via env vars or via config file.
62
+ # We must not use cached access token if client id or client secret was changed.
63
+ if creator == expected_creator and access_token:
64
+ self._access_token = access_token
65
+ self._expires_in = arrow.get(expires_in) if expires_in else None
66
+ else:
67
+ self._access_token = None
68
+ self._expires_in = None
56
69
 
57
70
  def refresh_access_token(self) -> None:
58
71
  auth_response = self._request_new_access_token()
@@ -1,3 +1,4 @@
1
+ import functools
1
2
  import os
2
3
  import platform
3
4
  import ssl
@@ -39,16 +40,29 @@ class SystemStorageSslContext(HTTPAdapter):
39
40
  conn.ca_certs = None
40
41
 
41
42
 
43
+ @functools.cache
44
+ def _get_session() -> requests.Session:
45
+ """Process-wide Session so TCP+TLS connections are reused across all API calls."""
46
+ session = requests.Session()
47
+ # On Windows without an explicit CA bundle env var, fall back to the system
48
+ # trust store via a custom SSL context.
49
+ if platform.system() == 'Windows' and not (
50
+ os.environ.get('REQUESTS_CA_BUNDLE') or os.environ.get('CURL_CA_BUNDLE')
51
+ ):
52
+ session.mount('https://', SystemStorageSslContext())
53
+ return session
54
+
55
+
42
56
  def _get_request_function() -> Callable:
43
- if os.environ.get('REQUESTS_CA_BUNDLE') or os.environ.get('CURL_CA_BUNDLE'):
44
- return requests.request
57
+ return _get_session().request
45
58
 
46
- if platform.system() != 'Windows':
47
- return requests.request
48
59
 
49
- session = requests.Session()
50
- session.mount('https://', SystemStorageSslContext())
51
- return session.request
60
+ def _log_response(response: Response, url: str, hide_response_content_log: bool) -> None:
61
+ content = 'HIDDEN' if hide_response_content_log else response.text
62
+ logger.debug(
63
+ 'Receiving response, %s',
64
+ {'status_code': response.status_code, 'url': url, 'content': content},
65
+ )
52
66
 
53
67
 
54
68
  _REQUEST_ERRORS_TO_RETRY = (
@@ -182,12 +196,7 @@ class CycodeClientBase:
182
196
  response = _get_request_function()(
183
197
  method='post', url=url, data=tracker, headers=headers, timeout=self.timeout
184
198
  )
185
-
186
- content = 'HIDDEN' if hide_response_content_log else response.text
187
- logger.debug(
188
- 'Receiving response, %s',
189
- {'status_code': response.status_code, 'url': url, 'content': content},
190
- )
199
+ _log_response(response, url, hide_response_content_log)
191
200
 
192
201
  response.raise_for_status()
193
202
  return response
@@ -231,14 +240,8 @@ class CycodeClientBase:
231
240
 
232
241
  try:
233
242
  headers = self.get_request_headers(headers, without_auth=without_auth)
234
- request = _get_request_function()
235
- response = request(method=method, url=url, timeout=timeout, headers=headers, **kwargs)
236
-
237
- content = 'HIDDEN' if hide_response_content_log else response.text
238
- logger.debug(
239
- 'Receiving response, %s',
240
- {'status_code': response.status_code, 'url': url, 'content': content},
241
- )
243
+ response = _get_request_function()(method=method, url=url, timeout=timeout, headers=headers, **kwargs)
244
+ _log_response(response, url, hide_response_content_log)
242
245
 
243
246
  response.raise_for_status()
244
247
  return response
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cycode
3
- Version: 3.16.1.dev7
3
+ Version: 3.16.2
4
4
  Summary: Boost security in your dev lifecycle via SAST, SCA, Secrets & IaC scanning.
5
5
  License-Expression: MIT
6
6
  License-File: LICENCE
@@ -1,7 +1,7 @@
1
- cycode/__init__.py,sha256=5B0uQpQ89sImWJL007hvzWjchwgJBFIKiQtxtmYyQ6Y,115
1
+ cycode/__init__.py,sha256=TSHmzZsQlKEA-YAkhDK_FSH49HSJxp-E02fYuRoDKPc,391
2
2
  cycode/__main__.py,sha256=Z3bD5yrA7yPvAChcADQrqCaZd0ChGI1gdiwALwbWJ6U,104
3
3
  cycode/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- cycode/cli/app.py,sha256=7ReEcVkRX9IaQ2I7jAj7Sl9smbtvxiuK8-9bitMEQik,7491
4
+ cycode/cli/app.py,sha256=AlR2durAEbsa47PDfIj7JtMvJDWA_Dq6wPtVuMJYSCs,10250
5
5
  cycode/cli/apps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  cycode/cli/apps/activation_manager.py,sha256=Hz9PDJFB-ZmYi4HSG8iYC-fR8j5v25VuUU-l95Otsdk,1678
7
7
  cycode/cli/apps/ai_guardrails/__init__.py,sha256=NsqB1Ca83BIjJMcDSt6suec6Ed0iNnacC0gBqkuuTtI,1367
@@ -9,11 +9,11 @@ cycode/cli/apps/ai_guardrails/command_utils.py,sha256=NVwd0-2RGRKIqhsQ-4LNDR1D0g
9
9
  cycode/cli/apps/ai_guardrails/consts.py,sha256=Js2QtSNYG9Kt0eo3vepRd5TFciCeJHEC9NN18zqKvlE,620
10
10
  cycode/cli/apps/ai_guardrails/hooks_manager.py,sha256=c8okVel9KjeXWm5QTnvTraWsTwzrUTqqKd6C80C34Y4,9003
11
11
  cycode/cli/apps/ai_guardrails/ides/__init__.py,sha256=JMXbQlq-7q1429w3nQq9Z4FZ8K0zdiUK6zCbilmD3JU,1689
12
- cycode/cli/apps/ai_guardrails/ides/_plugin_utils.py,sha256=_wJfdUHeCJGHQIOdoXQLuMJ4YGaOq_aLjY0ZUyzpsxU,2747
13
- cycode/cli/apps/ai_guardrails/ides/base.py,sha256=tFWjkuTKBn-4IZUFXHThwKctB6CWOxnG9q9Yr9fscJA,6594
14
- cycode/cli/apps/ai_guardrails/ides/claude_code.py,sha256=29O6Mp2NkJ89bQ6Wu_vQ9YlfUnMqpasSGwigAAzhzyM,13500
15
- cycode/cli/apps/ai_guardrails/ides/codex.py,sha256=-YkJs0PwGSP1ExJKPlgiZeQmWaQ1LL4wOkLG86iMLBU,11887
16
- cycode/cli/apps/ai_guardrails/ides/cursor.py,sha256=_u-DS4Pdvu_UiNh-W5i2ViHPV0IDlDvFrpRnisL3ks8,5235
12
+ cycode/cli/apps/ai_guardrails/ides/_plugin_utils.py,sha256=sBiLZWAKL8M7fTSDLR09aPk7_2w7NJfN10tydwVszmA,3273
13
+ cycode/cli/apps/ai_guardrails/ides/base.py,sha256=siFgELUNn1YBXY-jbx3yvhCbbqMXZQG1mFlxTAnwqS8,6995
14
+ cycode/cli/apps/ai_guardrails/ides/claude_code.py,sha256=KkTpX285ofwAD8olOcXwZXy9KBerlG3haS7QDrizD3o,13634
15
+ cycode/cli/apps/ai_guardrails/ides/codex.py,sha256=n9aiFQ8vGJxZMxCViSz5K8t3GixxZhiIMeIBkbfjxmg,12189
16
+ cycode/cli/apps/ai_guardrails/ides/cursor.py,sha256=wyHbh0QaSjC0EbwcWi-kZR4SN5lgkOLJ5S55EgSO2sw,5606
17
17
  cycode/cli/apps/ai_guardrails/install_command.py,sha256=vGZSIvHHVMS9_zhV_6lEhxqtmr5H6uykSq4AS5nxQYw,4278
18
18
  cycode/cli/apps/ai_guardrails/scan/__init__.py,sha256=qJc82XiQGiAuc1sYY8Ij_A-qXpxgLPuayQq8xWlouMA,48
19
19
  cycode/cli/apps/ai_guardrails/scan/consts.py,sha256=drAslw6vW3kxmbCs2qPCUbUPR7PJouT2lsXtu5sD-lQ,1094
@@ -23,7 +23,7 @@ cycode/cli/apps/ai_guardrails/scan/policy.py,sha256=39s8hnxgjny1l6XAO59wsRcAlpW-
23
23
  cycode/cli/apps/ai_guardrails/scan/scan_command.py,sha256=-Gl7cHELF1wlwLGno6ZVukFtK6NI6Z3-dTpIOsz8ors,6079
24
24
  cycode/cli/apps/ai_guardrails/scan/types.py,sha256=lDttkYFBfOkdMEEaRbq1IT2QTK0R-7Ht3T8vZdyB3b4,1038
25
25
  cycode/cli/apps/ai_guardrails/scan/utils.py,sha256=KVfX-NrcM-QW4quLtoNqfmz4GF0FlDs-TkqUOu1hAWM,2057
26
- cycode/cli/apps/ai_guardrails/session_start_command.py,sha256=z-yClXkV-SAxh5E8zKGIyo_qbhkpujoTgnx-lUDGS0I,3279
26
+ cycode/cli/apps/ai_guardrails/session_start_command.py,sha256=WzFE_12sxDq2lrVmMN_G2q_d9pfbsEO3CvjJXBcbFI0,3684
27
27
  cycode/cli/apps/ai_guardrails/status_command.py,sha256=Uqss68TEPCYPXpLix6Bh-4J3g-khxWsAqlIGYH5x4bQ,3203
28
28
  cycode/cli/apps/ai_guardrails/uninstall_command.py,sha256=dOmePfZmlHAPy2zEJM1yMtSuDqvzDwtqgmLKYK-T9PI,2698
29
29
  cycode/cli/apps/ai_remediation/__init__.py,sha256=8vYthY9RQeJqEni3AIF5sryz8n-XJQ6VNqG4aEFBAdY,553
@@ -64,7 +64,7 @@ cycode/cli/apps/report_import/sbom/sbom_command.py,sha256=uWvBhVdROHcHsjoR3l44h3
64
64
  cycode/cli/apps/sca_options.py,sha256=-3iXoJV5qOkfjr-WGIWuAgaeNYeItJIbm2n6O2Kg5D8,1666
65
65
  cycode/cli/apps/scan/__init__.py,sha256=-q1AIBnrQ4GP0CVKFLr_2CdWf9TBQC90ejSL4I7rxuA,2444
66
66
  cycode/cli/apps/scan/aggregation_report.py,sha256=8f9kPfO7biNf5OsDZG6UhMPqG6ymoFrX5GBtlEIfFAg,1540
67
- cycode/cli/apps/scan/code_scanner.py,sha256=IoU3dAFSXa4o0W8FggTUrgEonI0OYqREuG34i0XBumI,16865
67
+ cycode/cli/apps/scan/code_scanner.py,sha256=jjUzi2yueS0TrW4lo_vkkz1KMoBJfwjCCsKeSmoPW2A,17099
68
68
  cycode/cli/apps/scan/commit_history/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
69
69
  cycode/cli/apps/scan/commit_history/commit_history_command.py,sha256=zTVmN8yeLXGAiCbyDL-EyEzSPNLzcRpP2q6Qq7p4uZA,1011
70
70
  cycode/cli/apps/scan/commit_range_scanner.py,sha256=axFs--Xcb3I7D_SJHtBNE810qyCsSzLqPn_rD_8Peb8,17172
@@ -84,8 +84,8 @@ cycode/cli/apps/scan/scan_ci/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NM
84
84
  cycode/cli/apps/scan/scan_ci/ci_integrations.py,sha256=3ZUv1uLsHC13KTNQ4erQKKDXAkmaSm5jow2Utwr4mCw,1634
85
85
  cycode/cli/apps/scan/scan_ci/scan_ci_command.py,sha256=37I6YTs5UWYtbnDe1EeYZnhV1twFTDUrniZ4Sf2_6Kk,562
86
86
  cycode/cli/apps/scan/scan_command.py,sha256=n3bzGjB5CSmLRfFn6-pLBhrlOCDTRz2Kfn2L_J1l0io,7916
87
- cycode/cli/apps/scan/scan_parameters.py,sha256=66Ft8c_L6_BxDvRgJoXP5ItUQfzSHGF_XJWBdQismrg,1341
88
- cycode/cli/apps/scan/scan_result.py,sha256=mIxALi_qUfXHoauSU7SknX0xLp8P9WdhV3oFc8jp_SA,8497
87
+ cycode/cli/apps/scan/scan_parameters.py,sha256=x0UPwLQx85SW-pdPG6bNkChmnBXhWA8lnUCdY08urq8,1409
88
+ cycode/cli/apps/scan/scan_result.py,sha256=hTPB2wnsbqSNqAxhWN4P7_ygDN_j5Mufs_shIb0FJU4,8624
89
89
  cycode/cli/apps/status/__init__.py,sha256=uxfkEBafO7Da0mPc1fZhwoO0RTtyXp2a5T3LJTZxubU,371
90
90
  cycode/cli/apps/status/get_cli_status.py,sha256=1UAVyKPpYk2S8nQYshKD1x5xgWR-hCTchLChxNU0JVA,2577
91
91
  cycode/cli/apps/status/models.py,sha256=2SBpJlh_MNCPxv8aXMV5D4GfK6-G-XB0GlMFZ3Nep_o,1907
@@ -94,7 +94,7 @@ cycode/cli/apps/status/version_command.py,sha256=c6Iko_rmZo9T_kQSd3HUloBi40Qv7cj
94
94
  cycode/cli/cli_types.py,sha256=QbFWJLtlsEnHGdqdHbLolJqT57RfhocvsPAhlcNcCRE,3354
95
95
  cycode/cli/config.py,sha256=Op-lX_neanJtvPvoOEx4ByBdveh5ygElIga1FdSHhOI,299
96
96
  cycode/cli/console.py,sha256=vp-DHwlkwpwdsPyfwGdjsPF-6-Bi3f8W7G-W_YXCMH8,1914
97
- cycode/cli/consts.py,sha256=YjP_aIOayJkGEc87hTCMZBmRAFtXgM-dspVF51nVSCs,9029
97
+ cycode/cli/consts.py,sha256=DA64POP-TWR4jTOkBzzN-paHX_S_9A_qPs6GCIW-U9s,9651
98
98
  cycode/cli/exceptions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
99
99
  cycode/cli/exceptions/custom_exceptions.py,sha256=mTPLPI6V5JrEM6IQ8f7An9P207oYWEgJr-l9UpieSWk,4232
100
100
  cycode/cli/exceptions/handle_ai_remediation_errors.py,sha256=mA70upSYXK3rL_fmanzKYeUzLENhpXdkW8k3aIHrKzU,785
@@ -105,7 +105,7 @@ cycode/cli/exceptions/handle_scan_errors.py,sha256=1KkBFb7LniflYRr0vMl1FPIZDALPZ
105
105
  cycode/cli/files_collector/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
106
106
  cycode/cli/files_collector/commit_range_documents.py,sha256=ZAU9er6m8_IF9y9KxZoiEaDOiZC35SEfv5VtqKp4AZc,20484
107
107
  cycode/cli/files_collector/documents_walk_ignore.py,sha256=G4e-3vfP4WZ7wa9-VbZ66xCKCioTXnPBfbrs4_hh8xY,4705
108
- cycode/cli/files_collector/file_excluder.py,sha256=5Y7MM6_4x4FRKCV47D_hOXIg9BzYLHqwoWkmtV7Lt4I,7562
108
+ cycode/cli/files_collector/file_excluder.py,sha256=YSMzmsv1qJFwOIWk6JzXLYPOd2YNHZGqf854n_DSnWI,8233
109
109
  cycode/cli/files_collector/iac/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
110
110
  cycode/cli/files_collector/iac/tf_content_generator.py,sha256=a65zA0Ejv_LSA5jac2omHck4IKoNS5MX6v6ltF2wo4E,2873
111
111
  cycode/cli/files_collector/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -186,16 +186,16 @@ cycode/cli/utils/version_checker.py,sha256=0f5PaTk02ZkDxzBqZOeMV9mU_CWcx6HKW80jU
186
186
  cycode/cli/utils/yaml_utils.py,sha256=R-tqzl0C-zoa42rS7nfWeHu3GJ0jpbQUyyqYYU2hleM,1818
187
187
  cycode/config.py,sha256=jHORGZQcAXkAGSf2XreC-RQoc8sdNWja69QKtPWTbWo,1044
188
188
  cycode/cyclient/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
189
- cycode/cyclient/ai_security_manager_client.py,sha256=aZKsq2yB7ZiptMGj0RBTuPYyjYiJ5FTjNTi5_i37t4Q,4282
189
+ cycode/cyclient/ai_security_manager_client.py,sha256=EhPzP_jia9c9hw98UgHBemwFKluuhg5AJVAfngXkWfM,4543
190
190
  cycode/cyclient/ai_security_manager_service_config.py,sha256=83pQzgOb93JW6E-dznJkI4c0NEXmQRlx9YZKMmjVwp8,808
191
191
  cycode/cyclient/auth_client.py,sha256=TwbmZ358Ancf-Q-IZolvfljZ8691_6botsqd0R0PLPk,2105
192
- cycode/cyclient/base_token_auth_client.py,sha256=3JIrSz0-ywVTIfxIs2zs5aGcE-x5GW3AgPHm9qA4ZDE,3857
192
+ cycode/cyclient/base_token_auth_client.py,sha256=mn5580d7A8Z2_zcdFKIJk78ADK7mViwTcV-4QCpRCGo,4369
193
193
  cycode/cyclient/cli_activation_client.py,sha256=QaATFVABf0vQ6cTU_dCN9UT6fUkJf5qciaK8W7gQDrc,419
194
194
  cycode/cyclient/client_creator.py,sha256=-bsOT7M28yx-WT1UhSzENY8N9EgoQIv48Smrr9izQTI,2849
195
195
  cycode/cyclient/config.py,sha256=Le3YYp7LyclTwS4vonuFipfRA1qhyC28_muUtxpnImc,1387
196
196
  cycode/cyclient/config_dev.py,sha256=GJ3w8Q-ow5SvXGBFA34eaeoI6GhitavAGy3UfcUh9OU,120
197
197
  cycode/cyclient/cycode_client.py,sha256=ifZMA4RlFw4QNHku5ZxmtUKglH2yd1479yYZGDtxVvw,257
198
- cycode/cyclient/cycode_client_base.py,sha256=N9awOsu0nP7yuRbYwFNbkhR99L2Z84k7sC-wiFaC8Pw,10018
198
+ cycode/cyclient/cycode_client_base.py,sha256=_53DmKG_RAEY9INP_27s9FPa4_StQI5IgWt1Pl_Coq8,10193
199
199
  cycode/cyclient/cycode_dev_based_client.py,sha256=8LxeUWizXzZ0ilpwb6Q0W4ZMLZyZdKPzgpl5xcRmT8c,664
200
200
  cycode/cyclient/cycode_oidc_based_client.py,sha256=AVKBlqFOLYUQVxyPquvFwqnYpD6xgU_R6GJiQCWEdJw,857
201
201
  cycode/cyclient/cycode_token_based_client.py,sha256=frbrv1jzF388SXqHNNkZ95Hbx7Vjd3UXwWnq7nVxYN8,848
@@ -207,8 +207,8 @@ cycode/cyclient/report_client.py,sha256=Scq30NeJPzgXv0hPLO1U05AdE9i_2iu6cIrSKpEJ
207
207
  cycode/cyclient/scan_client.py,sha256=6TK5FQkfrvV7PHqRnUzEn1PBNd2oPYVamvIixcUfe3c,16755
208
208
  cycode/cyclient/scan_config_base.py,sha256=mXsPZGYCtp85rv5GIige40yQZXuRcEKUW-VQJ0vgFzk,1201
209
209
  cycode/logger.py,sha256=EfZGRK6VC5rE_LAjIcRrHFiQCueylCDXoG6bvGkrIME,2111
210
- cycode-3.16.1.dev7.dist-info/METADATA,sha256=Y43jx9fdiLPcKksSYbryGOKCx5bTtFWzTYbmb5Qn5HI,89245
211
- cycode-3.16.1.dev7.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
212
- cycode-3.16.1.dev7.dist-info/entry_points.txt,sha256=iDcVJM8ByLElVgvBgtYxDjw1kT7O8Mo0LcWZIT5L3Ig,45
213
- cycode-3.16.1.dev7.dist-info/licenses/LICENCE,sha256=2Wx4N6mD_4xB7-E3hPkZ3MPhpJy__k_I8MaCSO-PDRo,1068
214
- cycode-3.16.1.dev7.dist-info/RECORD,,
210
+ cycode-3.16.2.dist-info/METADATA,sha256=Q_Nj8ZMYzcoZ-A5GR8HdYMlABqiGnVculq-0e3_OUbM,89240
211
+ cycode-3.16.2.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
212
+ cycode-3.16.2.dist-info/entry_points.txt,sha256=iDcVJM8ByLElVgvBgtYxDjw1kT7O8Mo0LcWZIT5L3Ig,45
213
+ cycode-3.16.2.dist-info/licenses/LICENCE,sha256=2Wx4N6mD_4xB7-E3hPkZ3MPhpJy__k_I8MaCSO-PDRo,1068
214
+ cycode-3.16.2.dist-info/RECORD,,