appsec-tool-kit 0.1.0__tar.gz → 0.2.0__tar.gz

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.
Files changed (23) hide show
  1. {appsec_tool_kit-0.1.0 → appsec_tool_kit-0.2.0}/PKG-INFO +1 -1
  2. {appsec_tool_kit-0.1.0 → appsec_tool_kit-0.2.0}/appsec_kit/cli.py +5 -5
  3. appsec_tool_kit-0.2.0/appsec_kit/updater.py +74 -0
  4. {appsec_tool_kit-0.1.0 → appsec_tool_kit-0.2.0}/appsec_kit/versions.py +3 -3
  5. {appsec_tool_kit-0.1.0 → appsec_tool_kit-0.2.0}/pyproject.toml +2 -1
  6. appsec_tool_kit-0.2.0/renovate.json +35 -0
  7. appsec_tool_kit-0.2.0/tests/test_cli.py +61 -0
  8. {appsec_tool_kit-0.1.0 → appsec_tool_kit-0.2.0}/.github/workflows/tests.yml +0 -0
  9. {appsec_tool_kit-0.1.0 → appsec_tool_kit-0.2.0}/.gitignore +0 -0
  10. {appsec_tool_kit-0.1.0 → appsec_tool_kit-0.2.0}/.python-version +0 -0
  11. {appsec_tool_kit-0.1.0 → appsec_tool_kit-0.2.0}/README.md +0 -0
  12. {appsec_tool_kit-0.1.0 → appsec_tool_kit-0.2.0}/appsec_kit/__init__.py +0 -0
  13. {appsec_tool_kit-0.1.0 → appsec_tool_kit-0.2.0}/appsec_kit/detector.py +0 -0
  14. {appsec_tool_kit-0.1.0 → appsec_tool_kit-0.2.0}/appsec_kit/templates/__init__.py +0 -0
  15. {appsec_tool_kit-0.1.0 → appsec_tool_kit-0.2.0}/appsec_kit/templates/node_templates.py +0 -0
  16. {appsec_tool_kit-0.1.0 → appsec_tool_kit-0.2.0}/appsec_kit/templates/python_templates.py +0 -0
  17. {appsec_tool_kit-0.1.0 → appsec_tool_kit-0.2.0}/appsec_kit/writer.py +0 -0
  18. {appsec_tool_kit-0.1.0 → appsec_tool_kit-0.2.0}/tests/__init__.py +0 -0
  19. {appsec_tool_kit-0.1.0 → appsec_tool_kit-0.2.0}/tests/test_detector.py +0 -0
  20. {appsec_tool_kit-0.1.0 → appsec_tool_kit-0.2.0}/tests/test_templates.py +0 -0
  21. {appsec_tool_kit-0.1.0 → appsec_tool_kit-0.2.0}/tests/test_versions.py +0 -0
  22. {appsec_tool_kit-0.1.0 → appsec_tool_kit-0.2.0}/tests/test_writer.py +0 -0
  23. {appsec_tool_kit-0.1.0 → appsec_tool_kit-0.2.0}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: appsec-tool-kit
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: Interactive CLI to configure security tools for CI/CD pipelines
5
5
  Requires-Python: >=3.12
6
6
  Requires-Dist: questionary>=2.0
@@ -33,11 +33,11 @@ def _print_next_steps(lang: str, layers: list[str]) -> None:
33
33
  lines: list[str] = []
34
34
 
35
35
  if "precommit" in layers:
36
- lines += [
37
- "pip install pre-commit detect-secrets",
38
- "detect-secrets scan > .secrets.baseline",
39
- "pre-commit install",
40
- ]
36
+ pip_pkgs = "pre-commit detect-secrets" if "secrets" in layers else "pre-commit"
37
+ lines.append(f"pip install {pip_pkgs}")
38
+ if "secrets" in layers:
39
+ lines.append("detect-secrets scan > .secrets.baseline")
40
+ lines.append("pre-commit install")
41
41
 
42
42
  if lang == "node" and "sast" in layers:
43
43
  lines += [
@@ -0,0 +1,74 @@
1
+ import json
2
+ import re
3
+ from pathlib import Path
4
+ from urllib.error import URLError
5
+ from urllib.request import Request, urlopen
6
+
7
+ from rich.console import Console
8
+
9
+ console = Console()
10
+
11
+ _VERSIONS_PATH = Path(__file__).parent / "versions.py"
12
+
13
+ # (github_repo, keep_v_prefix)
14
+ _TOOLS: dict[str, tuple[str, bool]] = {
15
+ "BANDIT": ("PyCQA/bandit", False),
16
+ "DETECT_SECRETS": ("Yelp/detect-secrets", True),
17
+ "PIP_AUDIT": ("pypa/pip-audit", True),
18
+ "GITLEAKS": ("gitleaks/gitleaks", True),
19
+ }
20
+
21
+
22
+ def _fetch_latest_tag(repo: str) -> str | None:
23
+ url = f"https://api.github.com/repos/{repo}/releases/latest"
24
+ req = Request(url, headers={
25
+ "Accept": "application/vnd.github+json",
26
+ "X-GitHub-Api-Version": "2022-11-28",
27
+ })
28
+ try:
29
+ with urlopen(req, timeout=10) as resp:
30
+ return json.loads(resp.read()).get("tag_name")
31
+ except (URLError, json.JSONDecodeError, KeyError):
32
+ return None
33
+
34
+
35
+ def bump_versions() -> None:
36
+ console.print()
37
+ console.print("[bold]Checking latest versions...[/bold]\n")
38
+
39
+ content = _VERSIONS_PATH.read_text()
40
+ updated = content
41
+ any_change = False
42
+
43
+ for var, (repo, keep_v) in _TOOLS.items():
44
+ tag = _fetch_latest_tag(repo)
45
+ if tag is None:
46
+ console.print(f" [yellow]⚠[/yellow] {var}: could not fetch from {repo}")
47
+ continue
48
+
49
+ version = tag if keep_v else tag.lstrip("v")
50
+
51
+ match = re.search(rf'^{var} = "([^"]+)"', content, re.MULTILINE)
52
+ if not match:
53
+ console.print(f" [yellow]⚠[/yellow] {var}: not found in versions.py")
54
+ continue
55
+
56
+ current = match.group(1)
57
+ if current == version:
58
+ console.print(f" [dim]–[/dim] {var}: [dim]{current} (up to date)[/dim]")
59
+ else:
60
+ updated = updated.replace(f'{var} = "{current}"', f'{var} = "{version}"')
61
+ console.print(f" [green]↑[/green] {var}: [dim]{current}[/dim] → [bold green]{version}[/bold green]")
62
+ any_change = True
63
+
64
+ console.print()
65
+
66
+ if any_change:
67
+ _VERSIONS_PATH.write_text(updated)
68
+ console.print("[green]versions.py updated.[/green] Commit when pronto:")
69
+ console.print(' [dim]git add appsec_kit/versions.py[/dim]')
70
+ console.print(' [dim]git commit -m "chore: bump pinned tool versions"[/dim]')
71
+ else:
72
+ console.print("[dim]All versions up to date.[/dim]")
73
+
74
+ console.print()
@@ -1,6 +1,6 @@
1
- BANDIT = "1.8.3"
1
+ BANDIT = "1.9.4"
2
2
  DETECT_SECRETS = "v1.5.0"
3
- PIP_AUDIT = "v2.7.3"
4
- GITLEAKS = "v8.22.1"
3
+ PIP_AUDIT = "v2.10.1"
4
+ GITLEAKS = "v8.30.1"
5
5
 
6
6
  CI_LAYERS = frozenset(("sast", "deps", "secrets"))
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "appsec-tool-kit"
7
- version = "0.1.0"
7
+ version = "0.2.0"
8
8
  description = "Interactive CLI to configure security tools for CI/CD pipelines"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
@@ -15,6 +15,7 @@ dependencies = [
15
15
 
16
16
  [project.scripts]
17
17
  appsec-kit = "appsec_kit.cli:run"
18
+ appsec-kit-bump = "appsec_kit.updater:bump_versions"
18
19
 
19
20
  [tool.hatch.build.targets.wheel]
20
21
  packages = ["appsec_kit"]
@@ -0,0 +1,35 @@
1
+ {
2
+ "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3
+ "extends": ["config:base"],
4
+ "customManagers": [
5
+ {
6
+ "customType": "regex",
7
+ "fileMatch": ["^appsec_kit/versions\\.py$"],
8
+ "matchStrings": ["BANDIT = \"(?<currentValue>[^\"]+)\""],
9
+ "depNameTemplate": "PyCQA/bandit",
10
+ "datasourceTemplate": "github-releases",
11
+ "extractVersionTemplate": "^v?(?<version>.+)$"
12
+ },
13
+ {
14
+ "customType": "regex",
15
+ "fileMatch": ["^appsec_kit/versions\\.py$"],
16
+ "matchStrings": ["DETECT_SECRETS = \"(?<currentValue>[^\"]+)\""],
17
+ "depNameTemplate": "Yelp/detect-secrets",
18
+ "datasourceTemplate": "github-releases"
19
+ },
20
+ {
21
+ "customType": "regex",
22
+ "fileMatch": ["^appsec_kit/versions\\.py$"],
23
+ "matchStrings": ["PIP_AUDIT = \"(?<currentValue>[^\"]+)\""],
24
+ "depNameTemplate": "pypa/pip-audit",
25
+ "datasourceTemplate": "github-releases"
26
+ },
27
+ {
28
+ "customType": "regex",
29
+ "fileMatch": ["^appsec_kit/versions\\.py$"],
30
+ "matchStrings": ["GITLEAKS = \"(?<currentValue>[^\"]+)\""],
31
+ "depNameTemplate": "gitleaks/gitleaks",
32
+ "datasourceTemplate": "github-releases"
33
+ }
34
+ ]
35
+ }
@@ -0,0 +1,61 @@
1
+ from unittest.mock import patch
2
+
3
+ from rich.panel import Panel
4
+
5
+ from appsec_kit.cli import _print_next_steps
6
+
7
+
8
+ def _capture(lang: str, layers: list[str]) -> str:
9
+ captured: list[str] = []
10
+
11
+ def _collect(*args, **_):
12
+ for arg in args:
13
+ if isinstance(arg, Panel):
14
+ captured.append(str(arg.renderable))
15
+ else:
16
+ captured.append(str(arg))
17
+
18
+ with patch("appsec_kit.cli.console") as mock_console:
19
+ mock_console.print.side_effect = _collect
20
+ _print_next_steps(lang, layers)
21
+ return "\n".join(captured)
22
+
23
+
24
+ class TestNextSteps:
25
+ def test_precommit_with_secrets_shows_baseline_and_detect_secrets(self):
26
+ out = _capture("python", ["precommit", "secrets"])
27
+ assert "detect-secrets scan" in out
28
+ assert "detect-secrets" in out
29
+ assert "pre-commit install" in out
30
+
31
+ def test_precommit_without_secrets_omits_baseline_and_detect_secrets_pkg(self):
32
+ out = _capture("python", ["precommit", "sast"])
33
+ assert "detect-secrets scan" not in out
34
+ assert "detect-secrets" not in out
35
+ assert "pre-commit install" in out
36
+
37
+ def test_precommit_only_omits_baseline(self):
38
+ out = _capture("python", ["precommit"])
39
+ assert "detect-secrets scan" not in out
40
+ assert "pre-commit install" in out
41
+
42
+ def test_no_precommit_shows_nothing(self):
43
+ out = _capture("python", ["sast", "deps", "secrets"])
44
+ assert "pre-commit install" not in out
45
+ assert "detect-secrets scan" not in out
46
+
47
+ def test_node_sast_shows_semgrep_token_instructions(self):
48
+ out = _capture("node", ["sast", "precommit"])
49
+ assert "SEMGREP_APP_TOKEN" in out
50
+
51
+ def test_python_sast_omits_semgrep_token_instructions(self):
52
+ out = _capture("python", ["sast", "precommit"])
53
+ assert "SEMGREP_APP_TOKEN" not in out
54
+
55
+ def test_ci_layers_show_git_push_instructions(self):
56
+ out = _capture("python", ["sast"])
57
+ assert "git push" in out
58
+
59
+ def test_no_layers_shows_nothing(self):
60
+ out = _capture("python", [])
61
+ assert out == ""
File without changes