plugin-scanner 1.4.15__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.
- codex_plugin_scanner/__init__.py +29 -0
- codex_plugin_scanner/action_runner.py +470 -0
- codex_plugin_scanner/checks/__init__.py +0 -0
- codex_plugin_scanner/checks/best_practices.py +238 -0
- codex_plugin_scanner/checks/claude.py +285 -0
- codex_plugin_scanner/checks/code_quality.py +115 -0
- codex_plugin_scanner/checks/ecosystem_common.py +34 -0
- codex_plugin_scanner/checks/gemini.py +196 -0
- codex_plugin_scanner/checks/manifest.py +501 -0
- codex_plugin_scanner/checks/manifest_support.py +61 -0
- codex_plugin_scanner/checks/marketplace.py +334 -0
- codex_plugin_scanner/checks/opencode.py +223 -0
- codex_plugin_scanner/checks/operational_security.py +346 -0
- codex_plugin_scanner/checks/security.py +447 -0
- codex_plugin_scanner/checks/skill_security.py +241 -0
- codex_plugin_scanner/cli.py +467 -0
- codex_plugin_scanner/config.py +76 -0
- codex_plugin_scanner/ecosystems/__init__.py +15 -0
- codex_plugin_scanner/ecosystems/base.py +20 -0
- codex_plugin_scanner/ecosystems/claude.py +112 -0
- codex_plugin_scanner/ecosystems/codex.py +94 -0
- codex_plugin_scanner/ecosystems/detect.py +46 -0
- codex_plugin_scanner/ecosystems/gemini.py +80 -0
- codex_plugin_scanner/ecosystems/opencode.py +184 -0
- codex_plugin_scanner/ecosystems/registry.py +41 -0
- codex_plugin_scanner/ecosystems/types.py +45 -0
- codex_plugin_scanner/integrations/__init__.py +5 -0
- codex_plugin_scanner/integrations/cisco_skill_scanner.py +200 -0
- codex_plugin_scanner/lint_fixes.py +105 -0
- codex_plugin_scanner/marketplace_support.py +100 -0
- codex_plugin_scanner/models.py +177 -0
- codex_plugin_scanner/path_support.py +46 -0
- codex_plugin_scanner/policy.py +140 -0
- codex_plugin_scanner/quality_artifact.py +91 -0
- codex_plugin_scanner/repo_detect.py +137 -0
- codex_plugin_scanner/reporting.py +376 -0
- codex_plugin_scanner/rules/__init__.py +6 -0
- codex_plugin_scanner/rules/registry.py +101 -0
- codex_plugin_scanner/rules/specs.py +26 -0
- codex_plugin_scanner/scanner.py +557 -0
- codex_plugin_scanner/submission.py +284 -0
- codex_plugin_scanner/suppressions.py +87 -0
- codex_plugin_scanner/trust_domain_scoring.py +22 -0
- codex_plugin_scanner/trust_helpers.py +207 -0
- codex_plugin_scanner/trust_mcp_scoring.py +116 -0
- codex_plugin_scanner/trust_models.py +85 -0
- codex_plugin_scanner/trust_plugin_scoring.py +180 -0
- codex_plugin_scanner/trust_scoring.py +52 -0
- codex_plugin_scanner/trust_skill_scoring.py +296 -0
- codex_plugin_scanner/trust_specs.py +286 -0
- codex_plugin_scanner/verification.py +964 -0
- codex_plugin_scanner/version.py +3 -0
- plugin_scanner-1.4.15.dist-info/METADATA +596 -0
- plugin_scanner-1.4.15.dist-info/RECORD +57 -0
- plugin_scanner-1.4.15.dist-info/WHEEL +4 -0
- plugin_scanner-1.4.15.dist-info/entry_points.txt +4 -0
- plugin_scanner-1.4.15.dist-info/licenses/LICENSE +120 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Codex Plugin Scanner - multi-ecosystem scanner for agent plugin packages."""
|
|
2
|
+
|
|
3
|
+
from .models import (
|
|
4
|
+
GRADE_LABELS,
|
|
5
|
+
CategoryResult,
|
|
6
|
+
CheckResult,
|
|
7
|
+
Finding,
|
|
8
|
+
PackageSummary,
|
|
9
|
+
ScanOptions,
|
|
10
|
+
ScanResult,
|
|
11
|
+
Severity,
|
|
12
|
+
get_grade,
|
|
13
|
+
)
|
|
14
|
+
from .scanner import scan_plugin
|
|
15
|
+
from .version import __version__
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"GRADE_LABELS",
|
|
19
|
+
"CategoryResult",
|
|
20
|
+
"CheckResult",
|
|
21
|
+
"Finding",
|
|
22
|
+
"PackageSummary",
|
|
23
|
+
"ScanOptions",
|
|
24
|
+
"ScanResult",
|
|
25
|
+
"Severity",
|
|
26
|
+
"__version__",
|
|
27
|
+
"get_grade",
|
|
28
|
+
"scan_plugin",
|
|
29
|
+
]
|
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
"""GitHub Action entry point for scan and submission workflows."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from . import __version__
|
|
12
|
+
from .cli import _build_plain_text, _build_verification_text, _scan_with_policy
|
|
13
|
+
from .models import GRADE_LABELS, max_severity
|
|
14
|
+
from .quality_artifact import build_quality_artifact, write_quality_artifact
|
|
15
|
+
from .reporting import build_json_payload, format_markdown, format_sarif, should_fail_for_severity
|
|
16
|
+
from .submission import (
|
|
17
|
+
SubmissionIssue,
|
|
18
|
+
build_submission_issue_body,
|
|
19
|
+
build_submission_issue_title,
|
|
20
|
+
build_submission_payload,
|
|
21
|
+
create_submission_issue,
|
|
22
|
+
find_existing_submission_issue,
|
|
23
|
+
resolve_submission_metadata,
|
|
24
|
+
)
|
|
25
|
+
from .verification import build_verification_payload, verify_plugin
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _parse_csv(value: str) -> tuple[str, ...]:
|
|
29
|
+
return tuple(item.strip() for item in value.split(",") if item.strip())
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _read_bool_env(name: str, *, default: bool = False) -> bool:
|
|
33
|
+
raw = os.environ.get(name)
|
|
34
|
+
if raw is None:
|
|
35
|
+
return default
|
|
36
|
+
return raw.strip().lower() == "true"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _read_env(name: str, default: str = "") -> str:
|
|
40
|
+
return os.environ.get(name, default)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _write_outputs(path: str, values: dict[str, str]) -> None:
|
|
44
|
+
with Path(path).open("a", encoding="utf-8") as handle:
|
|
45
|
+
for key, value in values.items():
|
|
46
|
+
handle.write(f"{key}={value}\n")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _write_step_summary(path: str, lines: tuple[str, ...]) -> None:
|
|
50
|
+
with Path(path).open("a", encoding="utf-8") as handle:
|
|
51
|
+
handle.write("\n".join(lines))
|
|
52
|
+
handle.write("\n")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _build_scan_args(
|
|
56
|
+
*,
|
|
57
|
+
plugin_dir: str,
|
|
58
|
+
profile: str,
|
|
59
|
+
config: str,
|
|
60
|
+
baseline: str,
|
|
61
|
+
min_score: int,
|
|
62
|
+
fail_on_severity: str,
|
|
63
|
+
cisco_scan: str,
|
|
64
|
+
cisco_policy: str,
|
|
65
|
+
) -> argparse.Namespace:
|
|
66
|
+
return argparse.Namespace(
|
|
67
|
+
plugin_dir=plugin_dir,
|
|
68
|
+
profile=profile or None,
|
|
69
|
+
config=config or None,
|
|
70
|
+
baseline=baseline or None,
|
|
71
|
+
strict=False,
|
|
72
|
+
diff_base=None,
|
|
73
|
+
min_score=min_score,
|
|
74
|
+
fail_on_severity=fail_on_severity,
|
|
75
|
+
cisco_skill_scan=cisco_scan,
|
|
76
|
+
cisco_policy=cisco_policy,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _render_scan_output(result, *, output_format: str, profile: str, policy_pass: bool, raw_score: int) -> str:
|
|
81
|
+
if output_format == "json":
|
|
82
|
+
return json.dumps(
|
|
83
|
+
build_json_payload(
|
|
84
|
+
result,
|
|
85
|
+
profile=profile,
|
|
86
|
+
policy_pass=policy_pass,
|
|
87
|
+
verify_pass=True,
|
|
88
|
+
raw_score=raw_score,
|
|
89
|
+
effective_score=result.score,
|
|
90
|
+
),
|
|
91
|
+
indent=2,
|
|
92
|
+
)
|
|
93
|
+
if output_format == "markdown":
|
|
94
|
+
return format_markdown(result)
|
|
95
|
+
if output_format == "sarif":
|
|
96
|
+
return format_sarif(result)
|
|
97
|
+
return _build_plain_text(result)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _render_verify_output(verification, *, output_format: str) -> str:
|
|
101
|
+
payload = build_verification_payload(verification)
|
|
102
|
+
if output_format == "json":
|
|
103
|
+
return json.dumps(payload, indent=2)
|
|
104
|
+
return _build_verification_text(payload)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _render_lint_output(result, *, output_format: str, profile: str, policy_pass: bool) -> str:
|
|
108
|
+
if output_format == "json":
|
|
109
|
+
payload = {
|
|
110
|
+
"profile": profile,
|
|
111
|
+
"policy_pass": policy_pass,
|
|
112
|
+
"effective_score": result.score,
|
|
113
|
+
"findings": [
|
|
114
|
+
{
|
|
115
|
+
"rule_id": finding.rule_id,
|
|
116
|
+
"severity": finding.severity.value,
|
|
117
|
+
"category": finding.category,
|
|
118
|
+
"title": finding.title,
|
|
119
|
+
"description": finding.description,
|
|
120
|
+
}
|
|
121
|
+
for finding in result.findings
|
|
122
|
+
],
|
|
123
|
+
}
|
|
124
|
+
return json.dumps(payload, indent=2)
|
|
125
|
+
lines = [f"Lint profile: {profile} | policy_pass={policy_pass} | effective_score={result.score}"]
|
|
126
|
+
for finding in result.findings:
|
|
127
|
+
lines.append(f"- {finding.rule_id} [{finding.severity.value}] {finding.title}")
|
|
128
|
+
return "\n".join(lines)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _build_step_summary_lines(
|
|
132
|
+
*,
|
|
133
|
+
mode: str,
|
|
134
|
+
score: str,
|
|
135
|
+
grade: str,
|
|
136
|
+
grade_label: str,
|
|
137
|
+
max_severity: str,
|
|
138
|
+
findings_total: str,
|
|
139
|
+
report_path: str,
|
|
140
|
+
registry_payload_path: str,
|
|
141
|
+
submission_issues: list[SubmissionIssue],
|
|
142
|
+
submission_eligible: bool,
|
|
143
|
+
verify_pass: bool | None = None,
|
|
144
|
+
scope: str = "plugin",
|
|
145
|
+
local_plugin_count: int | None = None,
|
|
146
|
+
skipped_target_count: int | None = None,
|
|
147
|
+
) -> tuple[str, ...]:
|
|
148
|
+
lines = ["## HOL Codex Plugin Scanner", "", f"- Mode: {mode}"]
|
|
149
|
+
lines.append(f"- Scope: {scope}")
|
|
150
|
+
if local_plugin_count is not None:
|
|
151
|
+
lines.append(f"- Local plugins scanned: {local_plugin_count}")
|
|
152
|
+
if skipped_target_count is not None:
|
|
153
|
+
lines.append(f"- Skipped marketplace entries: {skipped_target_count}")
|
|
154
|
+
if score:
|
|
155
|
+
lines.append(f"- Score: {score}/100")
|
|
156
|
+
if grade:
|
|
157
|
+
lines.append(f"- Grade: {grade} - {grade_label}")
|
|
158
|
+
if max_severity:
|
|
159
|
+
lines.append(f"- Max severity: {max_severity}")
|
|
160
|
+
if findings_total:
|
|
161
|
+
lines.append(f"- Findings: {findings_total}")
|
|
162
|
+
if verify_pass is not None:
|
|
163
|
+
lines.append(f"- Verification pass: {'yes' if verify_pass else 'no'}")
|
|
164
|
+
lines.append(f"- Submission eligible: {'yes' if submission_eligible else 'no'}")
|
|
165
|
+
if report_path:
|
|
166
|
+
lines.append(f"- Report: `{report_path}`")
|
|
167
|
+
if registry_payload_path:
|
|
168
|
+
lines.append(f"- Registry payload: `{registry_payload_path}`")
|
|
169
|
+
if submission_issues:
|
|
170
|
+
lines.append(f"- Submission issues: {', '.join(issue.url for issue in submission_issues)}")
|
|
171
|
+
return tuple(lines)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def main() -> int:
|
|
175
|
+
mode = _read_env("MODE", "scan")
|
|
176
|
+
plugin_dir = _read_env("PLUGIN_DIR", ".")
|
|
177
|
+
output_format = _read_env("FORMAT", "text")
|
|
178
|
+
output_path = _read_env("OUTPUT")
|
|
179
|
+
write_step_summary = _read_bool_env("WRITE_STEP_SUMMARY", default=True)
|
|
180
|
+
registry_payload_output = _read_env("REGISTRY_PAYLOAD_OUTPUT")
|
|
181
|
+
upload_sarif = _read_bool_env("UPLOAD_SARIF")
|
|
182
|
+
profile = _read_env("PROFILE", "default")
|
|
183
|
+
config = _read_env("CONFIG")
|
|
184
|
+
baseline = _read_env("BASELINE")
|
|
185
|
+
online = _read_bool_env("ONLINE")
|
|
186
|
+
min_score = int(_read_env("MIN_SCORE", "0"))
|
|
187
|
+
fail_on = _read_env("FAIL_ON", "none")
|
|
188
|
+
cisco_scan = _read_env("CISCO_SCAN", "auto")
|
|
189
|
+
cisco_policy = _read_env("CISCO_POLICY", "balanced")
|
|
190
|
+
submission_enabled = _read_bool_env("SUBMISSION_ENABLED")
|
|
191
|
+
submission_threshold = int(_read_env("SUBMISSION_SCORE_THRESHOLD", "80"))
|
|
192
|
+
submission_repos = _parse_csv(_read_env("SUBMISSION_REPOS"))
|
|
193
|
+
submission_token = _read_env("SUBMISSION_TOKEN").strip()
|
|
194
|
+
submission_labels = _parse_csv(_read_env("SUBMISSION_LABELS"))
|
|
195
|
+
submission_category = _read_env("SUBMISSION_CATEGORY", "Community Plugins")
|
|
196
|
+
submission_plugin_name = _read_env("SUBMISSION_PLUGIN_NAME")
|
|
197
|
+
submission_plugin_url = _read_env("SUBMISSION_PLUGIN_URL")
|
|
198
|
+
submission_plugin_description = _read_env("SUBMISSION_PLUGIN_DESCRIPTION")
|
|
199
|
+
submission_author = _read_env("SUBMISSION_AUTHOR")
|
|
200
|
+
github_repository = _read_env("GITHUB_REPOSITORY")
|
|
201
|
+
github_server_url = _read_env("GITHUB_SERVER_URL", "https://github.com")
|
|
202
|
+
github_sha = _read_env("GITHUB_SHA")
|
|
203
|
+
github_run_id = _read_env("GITHUB_RUN_ID")
|
|
204
|
+
github_api_url = _read_env("GITHUB_API_URL", "https://api.github.com")
|
|
205
|
+
|
|
206
|
+
workflow_url = ""
|
|
207
|
+
if github_repository and github_run_id:
|
|
208
|
+
workflow_url = f"{github_server_url.rstrip('/')}/{github_repository}/actions/runs/{github_run_id}"
|
|
209
|
+
|
|
210
|
+
report_path_value = ""
|
|
211
|
+
registry_payload_path_value = ""
|
|
212
|
+
submission_issues: list[SubmissionIssue] = []
|
|
213
|
+
submission_eligible = False
|
|
214
|
+
output_values = {
|
|
215
|
+
"mode": mode,
|
|
216
|
+
"score": "",
|
|
217
|
+
"grade": "",
|
|
218
|
+
"grade_label": "",
|
|
219
|
+
"policy_pass": "",
|
|
220
|
+
"verify_pass": "",
|
|
221
|
+
"max_severity": "",
|
|
222
|
+
"findings_total": "",
|
|
223
|
+
"report_path": "",
|
|
224
|
+
"registry_payload_path": "",
|
|
225
|
+
"submission_eligible": "false",
|
|
226
|
+
"submission_performed": "false",
|
|
227
|
+
"submission_issue_urls": "",
|
|
228
|
+
"submission_issue_numbers": "",
|
|
229
|
+
}
|
|
230
|
+
verify_pass_for_summary: bool | None = None
|
|
231
|
+
scan_scope = "plugin"
|
|
232
|
+
local_plugin_count: int | None = None
|
|
233
|
+
skipped_target_count: int | None = None
|
|
234
|
+
|
|
235
|
+
if mode in {"scan", "lint", "submit"}:
|
|
236
|
+
args = _build_scan_args(
|
|
237
|
+
plugin_dir=plugin_dir,
|
|
238
|
+
profile=profile,
|
|
239
|
+
config=config,
|
|
240
|
+
baseline=baseline,
|
|
241
|
+
min_score=min_score,
|
|
242
|
+
fail_on_severity=fail_on,
|
|
243
|
+
cisco_scan=cisco_scan,
|
|
244
|
+
cisco_policy=cisco_policy,
|
|
245
|
+
)
|
|
246
|
+
raw_result, result, resolved_profile, policy_eval, _effective_score = _scan_with_policy(
|
|
247
|
+
args,
|
|
248
|
+
Path(plugin_dir).resolve(),
|
|
249
|
+
)
|
|
250
|
+
scan_scope = getattr(result, "scope", "plugin")
|
|
251
|
+
if scan_scope == "repository":
|
|
252
|
+
local_plugin_count = len(result.plugin_results)
|
|
253
|
+
skipped_target_count = len(result.skipped_targets)
|
|
254
|
+
rendered = ""
|
|
255
|
+
artifact_path = ""
|
|
256
|
+
verification = None
|
|
257
|
+
if mode == "scan":
|
|
258
|
+
if upload_sarif:
|
|
259
|
+
if output_format != "sarif":
|
|
260
|
+
print("upload_sarif requires format=sarif.", file=sys.stderr)
|
|
261
|
+
return 1
|
|
262
|
+
if not output_path:
|
|
263
|
+
output_path = "codex-plugin-scanner.sarif"
|
|
264
|
+
rendered = _render_scan_output(
|
|
265
|
+
result,
|
|
266
|
+
output_format=output_format,
|
|
267
|
+
profile=resolved_profile,
|
|
268
|
+
policy_pass=policy_eval.policy_pass,
|
|
269
|
+
raw_score=raw_result.score,
|
|
270
|
+
)
|
|
271
|
+
elif mode == "lint":
|
|
272
|
+
rendered = _render_lint_output(
|
|
273
|
+
result,
|
|
274
|
+
output_format="json" if output_format not in {"json", "text"} else output_format,
|
|
275
|
+
profile=resolved_profile,
|
|
276
|
+
policy_pass=policy_eval.policy_pass,
|
|
277
|
+
)
|
|
278
|
+
else:
|
|
279
|
+
if scan_scope != "plugin":
|
|
280
|
+
print(
|
|
281
|
+
"Submission mode requires a single plugin directory. "
|
|
282
|
+
"Point plugin_dir at one plugin instead of a repo marketplace root.",
|
|
283
|
+
file=sys.stderr,
|
|
284
|
+
)
|
|
285
|
+
return 1
|
|
286
|
+
verification = verify_plugin(Path(plugin_dir).resolve(), online=online)
|
|
287
|
+
artifact_path = output_path or "plugin-quality.json"
|
|
288
|
+
artifact = build_quality_artifact(
|
|
289
|
+
Path(plugin_dir).resolve(),
|
|
290
|
+
result,
|
|
291
|
+
verification,
|
|
292
|
+
policy_eval,
|
|
293
|
+
resolved_profile,
|
|
294
|
+
raw_score=raw_result.score,
|
|
295
|
+
)
|
|
296
|
+
write_quality_artifact(Path(artifact_path), artifact)
|
|
297
|
+
rendered = json.dumps(artifact, indent=2)
|
|
298
|
+
print(f"Submission artifact written to {artifact_path}")
|
|
299
|
+
verify_pass_for_summary = verification.verify_pass
|
|
300
|
+
|
|
301
|
+
if output_path and mode != "submit":
|
|
302
|
+
target = Path(output_path)
|
|
303
|
+
target.write_text(rendered, encoding="utf-8")
|
|
304
|
+
print(f"Report written to {target}")
|
|
305
|
+
report_path_value = str(target)
|
|
306
|
+
elif mode == "submit":
|
|
307
|
+
report_path_value = artifact_path
|
|
308
|
+
else:
|
|
309
|
+
print(rendered)
|
|
310
|
+
|
|
311
|
+
severity_failed = should_fail_for_severity(result, fail_on)
|
|
312
|
+
output_values.update(
|
|
313
|
+
{
|
|
314
|
+
"score": str(result.score),
|
|
315
|
+
"grade": result.grade,
|
|
316
|
+
"grade_label": GRADE_LABELS.get(result.grade, "Unknown"),
|
|
317
|
+
"policy_pass": "true" if policy_eval.policy_pass else "false",
|
|
318
|
+
"verify_pass": "true" if verification is not None and verification.verify_pass else "",
|
|
319
|
+
"max_severity": max_severity(result.findings).value if result.findings else "none",
|
|
320
|
+
"findings_total": str(sum(result.severity_counts.values())),
|
|
321
|
+
}
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
if submission_enabled or registry_payload_output:
|
|
325
|
+
metadata = resolve_submission_metadata(
|
|
326
|
+
Path(plugin_dir).resolve(),
|
|
327
|
+
result,
|
|
328
|
+
plugin_name=submission_plugin_name,
|
|
329
|
+
plugin_url=submission_plugin_url,
|
|
330
|
+
description=submission_plugin_description,
|
|
331
|
+
author=submission_author,
|
|
332
|
+
category=submission_category,
|
|
333
|
+
github_repository=github_repository or None,
|
|
334
|
+
github_server_url=github_server_url,
|
|
335
|
+
)
|
|
336
|
+
registry_payload = build_submission_payload(
|
|
337
|
+
metadata,
|
|
338
|
+
result,
|
|
339
|
+
source_repository=github_repository,
|
|
340
|
+
source_sha=github_sha,
|
|
341
|
+
workflow_url=workflow_url,
|
|
342
|
+
scanner_version=__version__,
|
|
343
|
+
)
|
|
344
|
+
if registry_payload_output:
|
|
345
|
+
registry_path = Path(registry_payload_output)
|
|
346
|
+
registry_path.write_text(json.dumps(registry_payload, indent=2), encoding="utf-8")
|
|
347
|
+
registry_payload_path_value = str(registry_path)
|
|
348
|
+
|
|
349
|
+
verify_for_submission = verification.verify_pass if verification is not None else True
|
|
350
|
+
submission_eligible = (
|
|
351
|
+
submission_enabled
|
|
352
|
+
and result.score >= submission_threshold
|
|
353
|
+
and not severity_failed
|
|
354
|
+
and policy_eval.policy_pass
|
|
355
|
+
and verify_for_submission
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
if submission_eligible:
|
|
359
|
+
if not submission_repos:
|
|
360
|
+
print("Submission is enabled but no submission repositories were configured.", file=sys.stderr)
|
|
361
|
+
return 1
|
|
362
|
+
if not submission_token:
|
|
363
|
+
print("Submission is enabled but no submission token was provided.", file=sys.stderr)
|
|
364
|
+
return 1
|
|
365
|
+
if not metadata.plugin_url:
|
|
366
|
+
print("Submission metadata is missing a plugin repository URL.", file=sys.stderr)
|
|
367
|
+
return 1
|
|
368
|
+
title = build_submission_issue_title(metadata)
|
|
369
|
+
body = build_submission_issue_body(
|
|
370
|
+
metadata,
|
|
371
|
+
result,
|
|
372
|
+
payload=registry_payload,
|
|
373
|
+
workflow_url=workflow_url,
|
|
374
|
+
)
|
|
375
|
+
for submission_repo in submission_repos:
|
|
376
|
+
existing = find_existing_submission_issue(
|
|
377
|
+
submission_repo,
|
|
378
|
+
metadata.plugin_url,
|
|
379
|
+
submission_token,
|
|
380
|
+
api_base_url=github_api_url,
|
|
381
|
+
)
|
|
382
|
+
if existing is not None:
|
|
383
|
+
submission_issues.append(existing)
|
|
384
|
+
continue
|
|
385
|
+
submission_issues.append(
|
|
386
|
+
create_submission_issue(
|
|
387
|
+
submission_repo,
|
|
388
|
+
title,
|
|
389
|
+
body,
|
|
390
|
+
submission_token,
|
|
391
|
+
labels=submission_labels,
|
|
392
|
+
api_base_url=github_api_url,
|
|
393
|
+
)
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
output_values["submission_eligible"] = "true" if submission_eligible else "false"
|
|
397
|
+
output_values["submission_performed"] = "true" if submission_issues else "false"
|
|
398
|
+
output_values["submission_issue_urls"] = ",".join(issue.url for issue in submission_issues)
|
|
399
|
+
output_values["submission_issue_numbers"] = ",".join(str(issue.number) for issue in submission_issues)
|
|
400
|
+
|
|
401
|
+
if result.score < min_score:
|
|
402
|
+
print(f"Score {result.score} is below minimum threshold {min_score}", file=sys.stderr)
|
|
403
|
+
return 1
|
|
404
|
+
if should_fail_for_severity(result, fail_on):
|
|
405
|
+
print(f'Findings met or exceeded the "{fail_on}" severity threshold.', file=sys.stderr)
|
|
406
|
+
return 1
|
|
407
|
+
if not policy_eval.policy_pass:
|
|
408
|
+
print(f'Policy profile "{resolved_profile}" failed.', file=sys.stderr)
|
|
409
|
+
return 1
|
|
410
|
+
if mode == "submit" and verification is not None and not verification.verify_pass:
|
|
411
|
+
print("Submission blocked: runtime verification failed.", file=sys.stderr)
|
|
412
|
+
return 1
|
|
413
|
+
|
|
414
|
+
elif mode == "verify":
|
|
415
|
+
verification = verify_plugin(Path(plugin_dir).resolve(), online=online)
|
|
416
|
+
scan_scope = getattr(verification, "scope", "plugin")
|
|
417
|
+
if scan_scope == "repository":
|
|
418
|
+
local_plugin_count = len(verification.plugin_results)
|
|
419
|
+
skipped_target_count = len(verification.skipped_targets)
|
|
420
|
+
rendered = _render_verify_output(verification, output_format=output_format)
|
|
421
|
+
verify_pass_for_summary = verification.verify_pass
|
|
422
|
+
if output_path:
|
|
423
|
+
target = Path(output_path)
|
|
424
|
+
target.write_text(rendered, encoding="utf-8")
|
|
425
|
+
print(f"Report written to {target}")
|
|
426
|
+
report_path_value = str(target)
|
|
427
|
+
else:
|
|
428
|
+
print(rendered)
|
|
429
|
+
return_code = 1 if not verification.verify_pass else 0
|
|
430
|
+
output_values["verify_pass"] = "true" if verification.verify_pass else "false"
|
|
431
|
+
else:
|
|
432
|
+
print(f"Unsupported mode: {mode}", file=sys.stderr)
|
|
433
|
+
return 1
|
|
434
|
+
|
|
435
|
+
output_values["report_path"] = report_path_value
|
|
436
|
+
output_values["registry_payload_path"] = registry_payload_path_value
|
|
437
|
+
|
|
438
|
+
step_summary_path = _read_env("GITHUB_STEP_SUMMARY")
|
|
439
|
+
if write_step_summary and step_summary_path:
|
|
440
|
+
_write_step_summary(
|
|
441
|
+
step_summary_path,
|
|
442
|
+
_build_step_summary_lines(
|
|
443
|
+
mode=mode,
|
|
444
|
+
score=output_values["score"],
|
|
445
|
+
grade=output_values["grade"],
|
|
446
|
+
grade_label=output_values["grade_label"],
|
|
447
|
+
max_severity=output_values["max_severity"] or "none",
|
|
448
|
+
findings_total=output_values["findings_total"],
|
|
449
|
+
report_path=report_path_value,
|
|
450
|
+
registry_payload_path=registry_payload_path_value,
|
|
451
|
+
submission_issues=submission_issues,
|
|
452
|
+
submission_eligible=submission_eligible,
|
|
453
|
+
verify_pass=verify_pass_for_summary,
|
|
454
|
+
scope=scan_scope,
|
|
455
|
+
local_plugin_count=local_plugin_count,
|
|
456
|
+
skipped_target_count=skipped_target_count,
|
|
457
|
+
),
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
github_output = _read_env("GITHUB_OUTPUT")
|
|
461
|
+
if github_output:
|
|
462
|
+
_write_outputs(github_output, output_values)
|
|
463
|
+
|
|
464
|
+
if mode == "verify":
|
|
465
|
+
return return_code
|
|
466
|
+
return 0
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
if __name__ == "__main__":
|
|
470
|
+
raise SystemExit(main())
|
|
File without changes
|