ref-agents 1.0.0__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.
- ref_agents/__init__.py +9 -0
- ref_agents/api_keys.json.example +8 -0
- ref_agents/auth.py +129 -0
- ref_agents/codemap/..md +62 -0
- ref_agents/codemap/CODE_MAP.md +37 -0
- ref_agents/codemap/core.md +43 -0
- ref_agents/codemap/models.md +43 -0
- ref_agents/codemap/prompts.md +40 -0
- ref_agents/codemap/security.md +45 -0
- ref_agents/codemap/tools.md +94 -0
- ref_agents/codemap/tools_browser.md +44 -0
- ref_agents/codemap/utils.md +42 -0
- ref_agents/codemap/workflow.md +42 -0
- ref_agents/config/ai_patterns.yaml +101 -0
- ref_agents/config/frameworks/angular.yaml +104 -0
- ref_agents/config/frameworks/aspnet.yaml +84 -0
- ref_agents/config/frameworks/ef_core.yaml +81 -0
- ref_agents/config/frameworks/react.yaml +111 -0
- ref_agents/config/frameworks/spring_boot.yaml +117 -0
- ref_agents/config/languages/csharp.yaml +153 -0
- ref_agents/config/languages/java.yaml +188 -0
- ref_agents/config/languages/javascript.yaml +172 -0
- ref_agents/config/languages/python.yaml +153 -0
- ref_agents/config/languages/typescript.yaml +193 -0
- ref_agents/constants.py +553 -0
- ref_agents/core/__init__.py +15 -0
- ref_agents/core/config_loader.py +160 -0
- ref_agents/core/config_models.py +167 -0
- ref_agents/core/config_parsing.py +84 -0
- ref_agents/core/language_detector.py +388 -0
- ref_agents/core/validation_models.py +66 -0
- ref_agents/core/validation_primitives.py +176 -0
- ref_agents/errors.py +34 -0
- ref_agents/license_client.py +247 -0
- ref_agents/models/__init__.py +22 -0
- ref_agents/models/gherkin.py +45 -0
- ref_agents/models/hierarchy.py +80 -0
- ref_agents/models/invest.py +59 -0
- ref_agents/models/version.py +49 -0
- ref_agents/prompts/__init__.py +9 -0
- ref_agents/prompts/start_agent.py +772 -0
- ref_agents/rules/architecture/backend_patterns.md +43 -0
- ref_agents/rules/architecture/diagramming.md +100 -0
- ref_agents/rules/architecture/frontend_patterns.md +40 -0
- ref_agents/rules/architecture/impact_analysis.md +129 -0
- ref_agents/rules/architecture/migration_strategy.md +208 -0
- ref_agents/rules/architecture/regression_protocol.md +77 -0
- ref_agents/rules/architecture/system_design.md +97 -0
- ref_agents/rules/common/codemap_standard.md +97 -0
- ref_agents/rules/common/core_protocol.md +59 -0
- ref_agents/rules/common/prompt_engineering.md +294 -0
- ref_agents/rules/development/debugging.md +32 -0
- ref_agents/rules/development/implementation.md +205 -0
- ref_agents/rules/operations/completion.md +119 -0
- ref_agents/rules/operations/cutover_protocol.md +218 -0
- ref_agents/rules/operations/discovery.md +179 -0
- ref_agents/rules/operations/fix_workflow.md +87 -0
- ref_agents/rules/operations/forensics.md +278 -0
- ref_agents/rules/operations/platform.md +263 -0
- ref_agents/rules/operations/synchronous_flow.md +25 -0
- ref_agents/rules/product/ac_validation.md +25 -0
- ref_agents/rules/product/brainstorming.md +27 -0
- ref_agents/rules/product/ref_flow.md +101 -0
- ref_agents/rules/product/requirements_std.md +114 -0
- ref_agents/rules/product/spec_writing.md +235 -0
- ref_agents/rules/product/strategy.md +96 -0
- ref_agents/rules/quality/documentation_standards.md +46 -0
- ref_agents/rules/quality/parity_testing.md +234 -0
- ref_agents/rules/quality/project_documentation.md +56 -0
- ref_agents/rules/quality/qa_lead.md +111 -0
- ref_agents/rules/quality/test_design.md +146 -0
- ref_agents/rules/quality/testing_standards.md +293 -0
- ref_agents/rules/review/pr_review.md +116 -0
- ref_agents/rules/security/security_audit.md +83 -0
- ref_agents/security/__init__.py +22 -0
- ref_agents/security/dependency_audit.py +188 -0
- ref_agents/security/file_audit.py +208 -0
- ref_agents/security/network_scan.py +179 -0
- ref_agents/security/report_generator.py +313 -0
- ref_agents/security/secret_scan.py +252 -0
- ref_agents/security/url_scan.py +240 -0
- ref_agents/security_scan.py +236 -0
- ref_agents/server.py +1586 -0
- ref_agents/session.py +100 -0
- ref_agents/tool_names.py +55 -0
- ref_agents/tools/__init__.py +8 -0
- ref_agents/tools/agents_generator.py +315 -0
- ref_agents/tools/ai_pattern_detector.py +815 -0
- ref_agents/tools/brownfield_populator.py +529 -0
- ref_agents/tools/browser/__init__.py +50 -0
- ref_agents/tools/browser/evidence_verifier.py +302 -0
- ref_agents/tools/browser/execution_logger.py +249 -0
- ref_agents/tools/browser/playwright_mcp_client.py +259 -0
- ref_agents/tools/browser/screenshot_utils.py +184 -0
- ref_agents/tools/browser/test_executor.py +537 -0
- ref_agents/tools/code_quality_scanner.py +629 -0
- ref_agents/tools/codemap/..md +93 -0
- ref_agents/tools/codemap/CODE_MAP.md +30 -0
- ref_agents/tools/codemap/browser.md +44 -0
- ref_agents/tools/codemap.py +403 -0
- ref_agents/tools/codemap_freshness.py +234 -0
- ref_agents/tools/comment_smell_scanner.py +346 -0
- ref_agents/tools/complexity.py +436 -0
- ref_agents/tools/complexity_ast.py +333 -0
- ref_agents/tools/compliance.py +246 -0
- ref_agents/tools/compliance_remediation.py +846 -0
- ref_agents/tools/context_graph.py +839 -0
- ref_agents/tools/context_manager.py +550 -0
- ref_agents/tools/context_tools.py +121 -0
- ref_agents/tools/cross_repo_linker.py +393 -0
- ref_agents/tools/dead_code_scanner.py +637 -0
- ref_agents/tools/debt_scanner.py +1092 -0
- ref_agents/tools/dependency_graph.py +272 -0
- ref_agents/tools/discovery_audit.py +372 -0
- ref_agents/tools/docs_scanner.py +600 -0
- ref_agents/tools/evaluate_gate.py +119 -0
- ref_agents/tools/external_detector.py +524 -0
- ref_agents/tools/features_generator.py +282 -0
- ref_agents/tools/flow_gap_detector.py +373 -0
- ref_agents/tools/flow_mapper.py +327 -0
- ref_agents/tools/full_suite_runner.py +740 -0
- ref_agents/tools/gherkin_parser.py +227 -0
- ref_agents/tools/guard_tools.py +139 -0
- ref_agents/tools/handoff_tools.py +282 -0
- ref_agents/tools/health_scanner.py +1211 -0
- ref_agents/tools/hierarchy_manager.py +289 -0
- ref_agents/tools/invest_scorer.py +249 -0
- ref_agents/tools/jira_confluence_export.py +306 -0
- ref_agents/tools/json_output.py +76 -0
- ref_agents/tools/migration_mapper.py +946 -0
- ref_agents/tools/migration_readiness_scanner.py +209 -0
- ref_agents/tools/pattern_learner.py +522 -0
- ref_agents/tools/report_utils.py +155 -0
- ref_agents/tools/requirements_serializer.py +225 -0
- ref_agents/tools/security_audit_tool.py +106 -0
- ref_agents/tools/sequencing_engine.py +288 -0
- ref_agents/tools/summary_generator.py +275 -0
- ref_agents/tools/symbol_resolver.py +306 -0
- ref_agents/tools/symbol_smoke_runner.py +336 -0
- ref_agents/tools/test_plan_validator.py +189 -0
- ref_agents/tools/test_smell_walker.py +902 -0
- ref_agents/tools/tier1_fixer.py +502 -0
- ref_agents/tools/validators/__init__.py +419 -0
- ref_agents/tools/validators/architect.py +268 -0
- ref_agents/tools/validators/cutover_engineer.py +167 -0
- ref_agents/tools/validators/developer.py +180 -0
- ref_agents/tools/validators/discovery.py +150 -0
- ref_agents/tools/validators/forensic_engineer.py +191 -0
- ref_agents/tools/validators/impact_architect.py +181 -0
- ref_agents/tools/validators/migration_planner.py +166 -0
- ref_agents/tools/validators/parity_tester.py +155 -0
- ref_agents/tools/validators/platform_engineer.py +134 -0
- ref_agents/tools/validators/pr_reviewer.py +129 -0
- ref_agents/tools/validators/product_manager.py +291 -0
- ref_agents/tools/validators/qa_lead.py +172 -0
- ref_agents/tools/validators/scrum_master.py +212 -0
- ref_agents/tools/validators/security_owner.py +162 -0
- ref_agents/tools/validators/specifier.py +134 -0
- ref_agents/tools/validators/strategist.py +149 -0
- ref_agents/tools/validators/tester.py +121 -0
- ref_agents/tools/version_manager.py +202 -0
- ref_agents/tools/workflow_tools.py +1549 -0
- ref_agents/utils/__init__.py +21 -0
- ref_agents/utils/git_utils.py +351 -0
- ref_agents/utils/handoff_logger.py +368 -0
- ref_agents/utils/ignore_matcher.py +270 -0
- ref_agents/workflow/__init__.py +19 -0
- ref_agents/workflow/capabilities.py +328 -0
- ref_agents/workflow/state_machine.py +708 -0
- ref_agents/workflow/transitions.py +658 -0
- ref_agents-1.0.0.dist-info/METADATA +365 -0
- ref_agents-1.0.0.dist-info/RECORD +175 -0
- ref_agents-1.0.0.dist-info/WHEEL +4 -0
- ref_agents-1.0.0.dist-info/entry_points.txt +2 -0
- ref_agents-1.0.0.dist-info/licenses/LICENSE +115 -0
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
"""URL/Endpoint Scanner.
|
|
2
|
+
|
|
3
|
+
Scans files for URLs and API endpoints that could indicate
|
|
4
|
+
external data transmission or cloud service connections.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import re
|
|
8
|
+
import subprocess
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
import structlog
|
|
13
|
+
|
|
14
|
+
logger = structlog.get_logger(__name__)
|
|
15
|
+
|
|
16
|
+
# Directories to skip during scanning
|
|
17
|
+
IGNORED_DIRS: set[str] = {
|
|
18
|
+
".venv",
|
|
19
|
+
"venv",
|
|
20
|
+
".env",
|
|
21
|
+
"node_modules",
|
|
22
|
+
".git",
|
|
23
|
+
"dist",
|
|
24
|
+
"build",
|
|
25
|
+
".tox",
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _should_skip(path: Path) -> bool:
|
|
30
|
+
"""Check if path should be skipped."""
|
|
31
|
+
for part in path.parts:
|
|
32
|
+
if part in IGNORED_DIRS:
|
|
33
|
+
return True
|
|
34
|
+
if part.endswith("_cache") or part.endswith("cache__"):
|
|
35
|
+
return True
|
|
36
|
+
return False
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# Patterns to detect URLs and endpoints
|
|
40
|
+
URL_PATTERNS: list[tuple[str, re.Pattern[str]]] = [
|
|
41
|
+
("HTTP/HTTPS URL", re.compile(r'https?://[^\s\'"<>)}\]]+', re.IGNORECASE)),
|
|
42
|
+
("WebSocket URL", re.compile(r'wss?://[^\s\'"<>)}\]]+', re.IGNORECASE)),
|
|
43
|
+
("FTP URL", re.compile(r'ftp://[^\s\'"<>)}\]]+', re.IGNORECASE)),
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
# URLs that are safe and expected (documentation, localhost, etc.)
|
|
47
|
+
WHITELIST_PATTERNS: list[re.Pattern[str]] = [
|
|
48
|
+
re.compile(r"https?://localhost", re.IGNORECASE),
|
|
49
|
+
re.compile(r"https?://127\.0\.0\.1", re.IGNORECASE),
|
|
50
|
+
re.compile(r"https?://0\.0\.0\.0", re.IGNORECASE),
|
|
51
|
+
re.compile(r"https?://example\.com", re.IGNORECASE),
|
|
52
|
+
re.compile(r"https?://example\.org", re.IGNORECASE),
|
|
53
|
+
# Documentation and reference links
|
|
54
|
+
re.compile(r"https?://modelcontextprotocol\.io", re.IGNORECASE),
|
|
55
|
+
re.compile(r"https?://github\.com", re.IGNORECASE),
|
|
56
|
+
re.compile(r"https?://ghcr\.io", re.IGNORECASE),
|
|
57
|
+
re.compile(r"https?://files\.pythonhosted\.org", re.IGNORECASE),
|
|
58
|
+
re.compile(r"https?://pypi\.org", re.IGNORECASE),
|
|
59
|
+
re.compile(r"https?://docs\.pytest\.org", re.IGNORECASE),
|
|
60
|
+
# Schema references (not actual URLs called at runtime)
|
|
61
|
+
re.compile(r"https?://json-schema\.org", re.IGNORECASE),
|
|
62
|
+
re.compile(r"https?://www\.w3\.org", re.IGNORECASE),
|
|
63
|
+
re.compile(r"https?://docs\.astral\.sh", re.IGNORECASE),
|
|
64
|
+
re.compile(r"https?://blog\.crewai\.com", re.IGNORECASE),
|
|
65
|
+
re.compile(r"https?://api\.example\.com", re.IGNORECASE),
|
|
66
|
+
re.compile(r"https?://astral\.sh", re.IGNORECASE),
|
|
67
|
+
re.compile(r"https?://api\.stripe\.com", re.IGNORECASE),
|
|
68
|
+
re.compile(r"https?://stripe\.com", re.IGNORECASE),
|
|
69
|
+
re.compile(r"https?://boto3\.amazonaws\.com", re.IGNORECASE),
|
|
70
|
+
# Common Badges and Libraries
|
|
71
|
+
re.compile(r"https?://img\.shields\.io", re.IGNORECASE),
|
|
72
|
+
re.compile(r"https?://www\.structlog\.org", re.IGNORECASE),
|
|
73
|
+
# vis.js CDN — used in context_graph.to_html() for interactive graph viewer
|
|
74
|
+
re.compile(r"https?://cdn\.jsdelivr\.net", re.IGNORECASE),
|
|
75
|
+
# FastMCP Cloud schema references (not actual URLs called at runtime)
|
|
76
|
+
re.compile(r"https?://gofastmcp\.com", re.IGNORECASE),
|
|
77
|
+
# Microsoft XML schema references in .csproj / docs (not called at runtime)
|
|
78
|
+
re.compile(r"https?://schemas\.microsoft\.com", re.IGNORECASE),
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@dataclass
|
|
83
|
+
class URLScanResult:
|
|
84
|
+
"""Result of URL scan."""
|
|
85
|
+
|
|
86
|
+
passed: bool
|
|
87
|
+
files_scanned: int
|
|
88
|
+
external_urls: list[dict[str, str]] = field(default_factory=list)
|
|
89
|
+
whitelisted_urls: list[dict[str, str]] = field(default_factory=list)
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def summary(self) -> str:
|
|
93
|
+
"""One-line summary for CLI output."""
|
|
94
|
+
if self.passed:
|
|
95
|
+
return f"0 external URLs found in {self.files_scanned} files"
|
|
96
|
+
return f"{len(self.external_urls)} external URLs found"
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _is_whitelisted(url: str) -> bool:
|
|
100
|
+
"""Check if URL matches any whitelist pattern."""
|
|
101
|
+
# Skip regex pattern fragments (contain backslashes or regex metacharacters)
|
|
102
|
+
if "\\" in url or "[^" in url or "\\s" in url:
|
|
103
|
+
return True
|
|
104
|
+
return any(pattern.search(url) for pattern in WHITELIST_PATTERNS)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _should_scan_file(file_path: Path, include_extensions: set[str]) -> bool:
|
|
108
|
+
"""Check if file should be scanned."""
|
|
109
|
+
# Skip directories and non-matching extensions
|
|
110
|
+
if file_path.is_dir():
|
|
111
|
+
return False
|
|
112
|
+
if file_path.suffix not in include_extensions:
|
|
113
|
+
return False
|
|
114
|
+
if _should_skip(file_path):
|
|
115
|
+
return False
|
|
116
|
+
# Skip test files (they contain test data)
|
|
117
|
+
if "/tests/" in str(file_path) or "test_" in file_path.name:
|
|
118
|
+
return False
|
|
119
|
+
# Skip generated reports
|
|
120
|
+
if "ref-reports" in str(file_path):
|
|
121
|
+
return False
|
|
122
|
+
# Skip lock files (they contain many package URLs)
|
|
123
|
+
if file_path.name in {"uv.lock", "poetry.lock", "package-lock.json"}:
|
|
124
|
+
return False
|
|
125
|
+
# Skip generated JSON reports (bandit, pip-audit)
|
|
126
|
+
if file_path.name in {"bandit-report.json", "pip-audit-report.json"}:
|
|
127
|
+
return False
|
|
128
|
+
# User defined ignores
|
|
129
|
+
if "GAP_ANALYSIS.md" in file_path.name:
|
|
130
|
+
return False
|
|
131
|
+
if "docs/misc" in str(file_path):
|
|
132
|
+
return False
|
|
133
|
+
return True
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _process_file_urls(
|
|
137
|
+
file_path: Path,
|
|
138
|
+
directory: Path,
|
|
139
|
+
external_urls: list[dict[str, str]],
|
|
140
|
+
whitelisted_urls: list[dict[str, str]],
|
|
141
|
+
) -> None:
|
|
142
|
+
"""Process a single file for URLs."""
|
|
143
|
+
try:
|
|
144
|
+
content = file_path.read_text(encoding="utf-8")
|
|
145
|
+
except UnicodeDecodeError:
|
|
146
|
+
return
|
|
147
|
+
|
|
148
|
+
relative_path = str(file_path.relative_to(directory))
|
|
149
|
+
|
|
150
|
+
for pattern_name, pattern in URL_PATTERNS:
|
|
151
|
+
for match in pattern.finditer(content):
|
|
152
|
+
url = match.group(0)
|
|
153
|
+
# Clean up trailing punctuation
|
|
154
|
+
url = url.rstrip(".,;:\"')")
|
|
155
|
+
|
|
156
|
+
url_info = {
|
|
157
|
+
"file": relative_path,
|
|
158
|
+
"url": url,
|
|
159
|
+
"type": pattern_name,
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if _is_whitelisted(url):
|
|
163
|
+
whitelisted_urls.append(url_info)
|
|
164
|
+
else:
|
|
165
|
+
external_urls.append(url_info)
|
|
166
|
+
logger.info(
|
|
167
|
+
"external_url_found",
|
|
168
|
+
file=relative_path,
|
|
169
|
+
url=url,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _iter_scannable_files(directory: Path, include_extensions: set[str]) -> list[Path]:
|
|
174
|
+
"""Return files to scan, preferring git-tracked files when inside a repo.
|
|
175
|
+
|
|
176
|
+
Falls back to rglob when not in a git repository (e.g., CI without full
|
|
177
|
+
checkout).
|
|
178
|
+
"""
|
|
179
|
+
try:
|
|
180
|
+
result = subprocess.run(
|
|
181
|
+
["git", "ls-files", "--cached", "--others", "--exclude-standard"],
|
|
182
|
+
cwd=directory,
|
|
183
|
+
capture_output=True,
|
|
184
|
+
text=True,
|
|
185
|
+
timeout=10,
|
|
186
|
+
)
|
|
187
|
+
if result.returncode == 0:
|
|
188
|
+
paths = []
|
|
189
|
+
for line in result.stdout.splitlines():
|
|
190
|
+
p = directory / line.strip()
|
|
191
|
+
if p.suffix in include_extensions and p.exists():
|
|
192
|
+
paths.append(p)
|
|
193
|
+
return paths
|
|
194
|
+
except (subprocess.TimeoutExpired, FileNotFoundError):
|
|
195
|
+
pass
|
|
196
|
+
return list(directory.rglob("*"))
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def scan_urls(
|
|
200
|
+
directory: Path,
|
|
201
|
+
include_extensions: set[str] | None = None,
|
|
202
|
+
) -> URLScanResult:
|
|
203
|
+
"""
|
|
204
|
+
Scan files for external URLs and endpoints.
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
directory: Root directory to scan
|
|
208
|
+
include_extensions: File extensions to scan (default: .py, .md, .txt, .json)
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
URLScanResult with pass/fail status and found URLs
|
|
212
|
+
"""
|
|
213
|
+
include_extensions = include_extensions or {".py", ".md", ".txt", ".json", ".toml"}
|
|
214
|
+
external_urls: list[dict[str, str]] = []
|
|
215
|
+
whitelisted_urls: list[dict[str, str]] = []
|
|
216
|
+
files_scanned = 0
|
|
217
|
+
|
|
218
|
+
for file_path in _iter_scannable_files(directory, include_extensions):
|
|
219
|
+
if not _should_scan_file(file_path, include_extensions):
|
|
220
|
+
continue
|
|
221
|
+
|
|
222
|
+
files_scanned += 1
|
|
223
|
+
_process_file_urls(file_path, directory, external_urls, whitelisted_urls)
|
|
224
|
+
|
|
225
|
+
passed = len(external_urls) == 0
|
|
226
|
+
|
|
227
|
+
logger.info(
|
|
228
|
+
"url_scan_complete",
|
|
229
|
+
passed=passed,
|
|
230
|
+
files_scanned=files_scanned,
|
|
231
|
+
external_count=len(external_urls),
|
|
232
|
+
whitelisted_count=len(whitelisted_urls),
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
return URLScanResult(
|
|
236
|
+
passed=passed,
|
|
237
|
+
files_scanned=files_scanned,
|
|
238
|
+
external_urls=external_urls,
|
|
239
|
+
whitelisted_urls=whitelisted_urls,
|
|
240
|
+
)
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
"""REF Agents Security Scanner CLI.
|
|
2
|
+
|
|
3
|
+
A single command that runs all security checks and produces a professional
|
|
4
|
+
security audit report for enterprise customers.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
uv run python -m ref_agents.security_scan
|
|
8
|
+
uv run python -m ref_agents.security_scan --verbose
|
|
9
|
+
uv run python -m ref_agents.security_scan --format json
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import argparse
|
|
13
|
+
import json
|
|
14
|
+
import sys
|
|
15
|
+
from datetime import datetime
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
import structlog
|
|
19
|
+
|
|
20
|
+
from ref_agents.constants import Icons, Status
|
|
21
|
+
from ref_agents.security.dependency_audit import audit_dependencies
|
|
22
|
+
from ref_agents.security.file_audit import audit_file_operations
|
|
23
|
+
from ref_agents.security.network_scan import scan_network_imports
|
|
24
|
+
from ref_agents.security.report_generator import SecurityReport, save_report
|
|
25
|
+
from ref_agents.security.secret_scan import scan_secrets
|
|
26
|
+
from ref_agents.security.url_scan import scan_urls
|
|
27
|
+
|
|
28
|
+
# Configure structlog for CLI output
|
|
29
|
+
structlog.configure(
|
|
30
|
+
processors=[
|
|
31
|
+
structlog.processors.TimeStamper(fmt="iso"),
|
|
32
|
+
structlog.processors.JSONRenderer(),
|
|
33
|
+
]
|
|
34
|
+
)
|
|
35
|
+
logger = structlog.get_logger(__name__)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def echo(message: str = "") -> None:
|
|
39
|
+
"""Write message to stdout for CLI output.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
message: Text to display. Empty string prints blank line.
|
|
43
|
+
"""
|
|
44
|
+
sys.stdout.write(message + "\n")
|
|
45
|
+
sys.stdout.flush()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def print_status(check_num: int, total: int, name: str, passed: bool) -> None:
|
|
49
|
+
"""Print formatted status line to stdout."""
|
|
50
|
+
status = Status.PASS if passed else Status.FAIL
|
|
51
|
+
dots = "." * (30 - len(name))
|
|
52
|
+
echo(f"[{check_num}/{total}] {name}{dots} {status}")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def run_security_scan(
|
|
56
|
+
directory: Path,
|
|
57
|
+
verbose: bool = False,
|
|
58
|
+
output_format: str = "text",
|
|
59
|
+
no_report: bool = False,
|
|
60
|
+
) -> bool:
|
|
61
|
+
"""
|
|
62
|
+
Run all security scans and generate report.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
directory: Root directory to scan
|
|
66
|
+
verbose: Print detailed output
|
|
67
|
+
output_format: Output format (text, json, markdown)
|
|
68
|
+
no_report: If True, skip saving report to disk
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
True if all scans passed, False otherwise
|
|
72
|
+
"""
|
|
73
|
+
echo(Icons.HEAVY_LINE * 60)
|
|
74
|
+
echo(" REF-AGENTS SECURITY AUDIT")
|
|
75
|
+
echo(Icons.HEAVY_LINE * 60)
|
|
76
|
+
echo()
|
|
77
|
+
echo(f"Scanning: {directory}")
|
|
78
|
+
echo(f"Started: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
|
79
|
+
echo()
|
|
80
|
+
|
|
81
|
+
# Run all scans
|
|
82
|
+
src_dir = directory / "src"
|
|
83
|
+
if not src_dir.exists():
|
|
84
|
+
src_dir = directory
|
|
85
|
+
|
|
86
|
+
# 1. Network Import Scan
|
|
87
|
+
if verbose:
|
|
88
|
+
echo("Running network import scan...")
|
|
89
|
+
network_result = scan_network_imports(src_dir)
|
|
90
|
+
print_status(1, 6, "Network Import Scan", network_result.passed)
|
|
91
|
+
|
|
92
|
+
# 2. URL Scan
|
|
93
|
+
if verbose:
|
|
94
|
+
echo("Running URL scan...")
|
|
95
|
+
url_result = scan_urls(directory)
|
|
96
|
+
print_status(2, 6, "Outbound URL Scan", url_result.passed)
|
|
97
|
+
|
|
98
|
+
# 3. Secret Scan
|
|
99
|
+
if verbose:
|
|
100
|
+
echo("Running secret scan...")
|
|
101
|
+
secret_result = scan_secrets(directory)
|
|
102
|
+
print_status(3, 6, "Secret Detection", secret_result.passed)
|
|
103
|
+
|
|
104
|
+
# 4. Dependency Audit
|
|
105
|
+
if verbose:
|
|
106
|
+
echo("Running dependency audit...")
|
|
107
|
+
dependency_result = audit_dependencies(directory)
|
|
108
|
+
print_status(4, 6, "Dependency Audit", dependency_result.passed)
|
|
109
|
+
|
|
110
|
+
# 5. License Check (part of dependency audit)
|
|
111
|
+
license_passed = len(dependency_result.unsafe_licenses) == 0
|
|
112
|
+
print_status(5, 6, "License Check", license_passed)
|
|
113
|
+
|
|
114
|
+
# 6. File Operation Audit
|
|
115
|
+
if verbose:
|
|
116
|
+
echo("Running file operation audit...")
|
|
117
|
+
file_result = audit_file_operations(src_dir)
|
|
118
|
+
print_status(6, 6, "File Operation Audit", file_result.passed)
|
|
119
|
+
|
|
120
|
+
echo()
|
|
121
|
+
|
|
122
|
+
# Generate report
|
|
123
|
+
report = SecurityReport(
|
|
124
|
+
network_result=network_result,
|
|
125
|
+
url_result=url_result,
|
|
126
|
+
secret_result=secret_result,
|
|
127
|
+
dependency_result=dependency_result,
|
|
128
|
+
file_result=file_result,
|
|
129
|
+
generated_at=datetime.now(),
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
if output_format == "json":
|
|
133
|
+
# JSON output for CI
|
|
134
|
+
result = {
|
|
135
|
+
"passed": report.overall_passed,
|
|
136
|
+
"timestamp": report.generated_at.isoformat(),
|
|
137
|
+
"checks": {
|
|
138
|
+
"network_scan": {
|
|
139
|
+
"passed": network_result.passed,
|
|
140
|
+
"violations": len(network_result.violations),
|
|
141
|
+
},
|
|
142
|
+
"url_scan": {
|
|
143
|
+
"passed": url_result.passed,
|
|
144
|
+
"external_urls": len(url_result.external_urls),
|
|
145
|
+
},
|
|
146
|
+
"secret_scan": {
|
|
147
|
+
"passed": secret_result.passed,
|
|
148
|
+
"secrets_found": len(secret_result.secrets_found),
|
|
149
|
+
},
|
|
150
|
+
"dependency_audit": {
|
|
151
|
+
"passed": dependency_result.passed,
|
|
152
|
+
"dependencies": len(dependency_result.dependencies),
|
|
153
|
+
},
|
|
154
|
+
"file_audit": {
|
|
155
|
+
"passed": file_result.passed,
|
|
156
|
+
"dangerous_ops": len(file_result.dangerous_operations),
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
}
|
|
160
|
+
echo(json.dumps(result, indent=2))
|
|
161
|
+
else:
|
|
162
|
+
# Save markdown report (unless --no-report)
|
|
163
|
+
if not no_report:
|
|
164
|
+
report_path = save_report(report)
|
|
165
|
+
echo(f"Report saved: {report_path}")
|
|
166
|
+
echo()
|
|
167
|
+
|
|
168
|
+
# Overall status
|
|
169
|
+
if report.overall_passed:
|
|
170
|
+
echo(Icons.HEAVY_LINE * 60)
|
|
171
|
+
echo(f" {Status.SUCCESS} ALL CHECKS PASSED")
|
|
172
|
+
echo(Icons.HEAVY_LINE * 60)
|
|
173
|
+
else:
|
|
174
|
+
echo(Icons.HEAVY_LINE * 60)
|
|
175
|
+
echo(f" {Status.ERROR} SOME CHECKS FAILED")
|
|
176
|
+
echo(Icons.HEAVY_LINE * 60)
|
|
177
|
+
echo()
|
|
178
|
+
echo("Review the report for details on failures.")
|
|
179
|
+
|
|
180
|
+
return report.overall_passed
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def main() -> int:
|
|
184
|
+
"""CLI entry point."""
|
|
185
|
+
parser = argparse.ArgumentParser(
|
|
186
|
+
description="REF Agents Security Scanner - Enterprise Security Audit Tool"
|
|
187
|
+
)
|
|
188
|
+
parser.add_argument(
|
|
189
|
+
"--directory",
|
|
190
|
+
"-d",
|
|
191
|
+
type=Path,
|
|
192
|
+
default=Path.cwd(),
|
|
193
|
+
help="Directory to scan (default: current directory)",
|
|
194
|
+
)
|
|
195
|
+
parser.add_argument(
|
|
196
|
+
"--verbose",
|
|
197
|
+
"-v",
|
|
198
|
+
action="store_true",
|
|
199
|
+
help="Print detailed output",
|
|
200
|
+
)
|
|
201
|
+
parser.add_argument(
|
|
202
|
+
"--format",
|
|
203
|
+
"-f",
|
|
204
|
+
choices=["text", "json"],
|
|
205
|
+
default="text",
|
|
206
|
+
help="Output format (default: text)",
|
|
207
|
+
)
|
|
208
|
+
parser.add_argument(
|
|
209
|
+
"--no-report",
|
|
210
|
+
action="store_true",
|
|
211
|
+
help="Skip saving report to disk (for pre-commit hooks)",
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
args = parser.parse_args()
|
|
215
|
+
|
|
216
|
+
# Find project root (look for pyproject.toml)
|
|
217
|
+
directory = args.directory.resolve()
|
|
218
|
+
if not (directory / "pyproject.toml").exists():
|
|
219
|
+
# Try parent directories
|
|
220
|
+
for parent in directory.parents:
|
|
221
|
+
if (parent / "pyproject.toml").exists():
|
|
222
|
+
directory = parent
|
|
223
|
+
break
|
|
224
|
+
|
|
225
|
+
passed = run_security_scan(
|
|
226
|
+
directory=directory,
|
|
227
|
+
verbose=args.verbose,
|
|
228
|
+
output_format=args.format,
|
|
229
|
+
no_report=args.no_report,
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
return 0 if passed else 1
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
if __name__ == "__main__":
|
|
236
|
+
sys.exit(main())
|