Skip to content

Commit d07e242

Browse files
committed
fix: ruff issues, modernize CLI, stabilize CI
1 parent 11d0570 commit d07e242

File tree

12 files changed

+138
-44
lines changed

12 files changed

+138
-44
lines changed

.secretscoutignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,5 @@ build/**
99
*.min.js
1010
*.map
1111
tests/**
12+
CONTRIBUTING.md
13+
README.md

pyproject.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "secretscout"
7-
version = "0.2.0"
7+
version = "0.2.1"
88
description = "Defensive secret scanner for git repos and folders (pretty report + JSON/SARIF/HTML + pre-commit helper)."
99
readme = "README.md"
1010
requires-python = ">=3.10"
@@ -23,9 +23,9 @@ dev = [
2323
]
2424

2525
[project.urls]
26-
Homepage = "https://github.com/vazor-code/secretscout"
27-
Repository = "https://github.com/vazor-code/secretscout"
28-
Issues = "https://github.com/vazor-code/secretscout/issues"
26+
Homepage = "https://github.com/vazor-code/SecretScout"
27+
Repository = "https://github.com/vazor-code/SecretScout"
28+
Issues = "https://github.com/vazor-code/SecretScout/issues"
2929

3030
[project.scripts]
3131
secretscout = "secretscout.cli:app"

src/secretscout/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
__all__ = ['__version__']
2-
__version__ = '0.2.0'
2+
__version__ = '0.2.1'

src/secretscout/cli.py

Lines changed: 112 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,94 @@
11
from __future__ import annotations
22

33
from pathlib import Path
4-
from typing import Optional
4+
from typing import Annotated
55

66
import typer
77

88
from .commands import cmd_baseline, cmd_init, cmd_scan, cmd_stats
99
from .reporting import Format
1010
from .rules_cmd import list_rules, show_rule
1111

12-
app = typer.Typer(add_completion=False, help="SecretScout: defensive secret scanner for repos and folders.")
12+
app = typer.Typer(
13+
add_completion=False,
14+
help="SecretScout: defensive secret scanner for repos and folders.",
15+
)
16+
17+
# ---- Reusable annotations (Ruff B008-safe) ----
18+
19+
PathArg = Annotated[
20+
Path,
21+
typer.Argument(exists=True, file_okay=False, dir_okay=True),
22+
]
23+
24+
FormatOpt = Annotated[
25+
Format,
26+
typer.Option("--format", help="table|minimal|json|sarif|html"),
27+
]
28+
29+
OutputOpt = Annotated[
30+
Path | None,
31+
typer.Option("--output", "-o", help="Write report to file (else stdout)."),
32+
]
33+
34+
FailOnOpt = Annotated[
35+
str,
36+
typer.Option("--fail-on", help="Exit 1 if findings severity >= this."),
37+
]
38+
39+
BaselineOpt = Annotated[
40+
Path | None,
41+
typer.Option("--baseline", help="Baseline JSON file to ignore known findings."),
42+
]
43+
44+
StagedOpt = Annotated[
45+
bool,
46+
typer.Option("--staged", help="Scan staged changes (git index)."),
47+
]
48+
49+
TrackedOpt = Annotated[
50+
bool,
51+
typer.Option("--tracked", help="Scan only git tracked files."),
52+
]
53+
54+
AllFilesOpt = Annotated[
55+
bool,
56+
typer.Option("--all", help="Scan all files under PATH (ignores git)."),
57+
]
58+
59+
ExcludeOpt = Annotated[
60+
list[str] | None,
61+
typer.Option("--exclude", help="Extra exclude glob patterns (repeatable)."),
62+
]
63+
64+
NoCacheOpt = Annotated[
65+
bool,
66+
typer.Option("--no-cache", help="Disable cache."),
67+
]
68+
69+
MaxFindingsOpt = Annotated[
70+
int | None,
71+
typer.Option("--max-findings", help="Limit output findings."),
72+
]
73+
74+
# ---- Commands ----
1375

1476

1577
@app.command()
1678
def scan(
17-
path: Path = typer.Argument(Path("."), exists=True, file_okay=False, dir_okay=True),
18-
format: Format = typer.Option("table", "--format", help="table|minimal|json|sarif|html"),
19-
output: Optional[Path] = typer.Option(None, "--output", "-o", help="Write report to file (else stdout)."),
20-
fail_on: str = typer.Option("high", "--fail-on", help="Exit 1 if findings severity >= this."),
21-
baseline: Optional[Path] = typer.Option(None, "--baseline", help="Baseline JSON file to ignore known findings."),
22-
staged: bool = typer.Option(False, "--staged", help="Scan staged changes (git index)."),
23-
tracked: bool = typer.Option(False, "--tracked", help="Scan only git tracked files."),
24-
all_files: bool = typer.Option(False, "--all", help="Scan all files under PATH (ignores git)."),
25-
exclude: list[str] = typer.Option(None, "--exclude", help="Extra exclude glob patterns (repeatable)."),
26-
no_cache: bool = typer.Option(False, "--no-cache", help="Disable cache."),
27-
max_findings: Optional[int] = typer.Option(None, "--max-findings", help="Limit output findings."),
79+
path: PathArg = Path("."),
80+
format: FormatOpt = "table",
81+
output: OutputOpt = None,
82+
fail_on: FailOnOpt = "high",
83+
baseline: BaselineOpt = None,
84+
staged: StagedOpt = False,
85+
tracked: TrackedOpt = False,
86+
all_files: AllFilesOpt = False,
87+
exclude: ExcludeOpt = None,
88+
no_cache: NoCacheOpt = False,
89+
max_findings: MaxFindingsOpt = None,
2890
) -> None:
91+
"""Scan a path for potential secrets."""
2992
code = cmd_scan(
3093
path=path,
3194
fmt=format,
@@ -43,44 +106,68 @@ def scan(
43106

44107

45108
@app.command()
46-
def init(path: Path = typer.Argument(Path("."), exists=True, file_okay=False, dir_okay=True)) -> None:
109+
def init(path: PathArg = Path(".")) -> None:
110+
"""Generate default config and ignore files."""
47111
raise typer.Exit(code=cmd_init(path))
48112

49113

50114
@app.command()
51-
def fix(path: Path = typer.Argument(Path("."), exists=True, file_okay=False, dir_okay=True)) -> None:
115+
def fix(path: PathArg = Path(".")) -> None:
116+
"""Alias for init (kept for convenience)."""
52117
raise typer.Exit(code=cmd_init(path))
53118

54119

55120
@app.command()
56121
def baseline(
57-
path: Path = typer.Argument(Path("."), exists=True, file_okay=False, dir_okay=True),
58-
output: Path = typer.Option(Path(".secretscout.baseline.json"), "--output", "-o", help="Baseline output file."),
59-
tracked: bool = typer.Option(True, "--tracked/--all", help="By default, baseline git-tracked files."),
122+
path: PathArg = Path("."),
123+
output: Annotated[
124+
Path,
125+
typer.Option("--output", "-o", help="Baseline output file."),
126+
] = Path(".secretscout.baseline.json"),
127+
tracked: Annotated[
128+
bool,
129+
typer.Option("--tracked/--all", help="By default, baseline git-tracked files."),
130+
] = True,
60131
) -> None:
132+
"""Create a baseline file to ignore known findings."""
61133
raise typer.Exit(code=cmd_baseline(path, output, tracked))
62134

63135

64136
@app.command()
65137
def stats(
66-
path: Path = typer.Argument(Path("."), exists=True, file_okay=False, dir_okay=True),
67-
staged: bool = typer.Option(False, "--staged"),
68-
tracked: bool = typer.Option(False, "--tracked"),
69-
all_files: bool = typer.Option(False, "--all"),
70-
baseline: Optional[Path] = typer.Option(None, "--baseline"),
138+
path: PathArg = Path("."),
139+
staged: StagedOpt = False,
140+
tracked: TrackedOpt = False,
141+
all_files: AllFilesOpt = False,
142+
baseline: BaselineOpt = None,
71143
) -> None:
72-
raise typer.Exit(code=cmd_stats(path, staged=staged, tracked=tracked, all_files=all_files, baseline=baseline))
144+
"""Show a summary of findings."""
145+
raise typer.Exit(
146+
code=cmd_stats(
147+
path,
148+
staged=staged,
149+
tracked=tracked,
150+
all_files=all_files,
151+
baseline=baseline,
152+
)
153+
)
73154

74155

156+
# ---- Rules subcommands ----
157+
75158
rules_app = typer.Typer(help="Manage and inspect rules.")
76159
app.add_typer(rules_app, name="rules")
77160

78161

79162
@rules_app.command("list")
80163
def rules_list() -> None:
164+
"""List available rules."""
81165
list_rules()
82166

83167

84168
@rules_app.command("show")
85-
def rules_show(rule_id: str) -> None:
169+
def rules_show(
170+
rule_id: Annotated[str, typer.Argument(help="Rule id (e.g. github-token).")],
171+
) -> None:
172+
"""Show details for a single rule."""
86173
show_rule(rule_id)

src/secretscout/commands.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import json
44
from pathlib import Path
5-
from typing import Optional
65

76
import typer
87
from rich.console import Console
@@ -17,22 +16,25 @@
1716
def to_severity(value: str) -> Severity:
1817
try:
1918
return Severity(value.lower())
20-
except Exception:
21-
raise typer.BadParameter("Severity must be one of: low, medium, high, critical")
19+
except Exception as err:
20+
raise typer.BadParameter(
21+
"Severity must be one of: low, medium, high, critical"
22+
) from err
23+
2224

2325

2426
def cmd_scan(
2527
path: Path,
2628
fmt: Format,
27-
output: Optional[Path],
29+
output: Path | None,
2830
fail_on: str,
29-
baseline: Optional[Path],
31+
baseline: Path | None,
3032
staged: bool,
3133
tracked: bool,
3234
all_files: bool,
3335
exclude: list[str],
3436
no_cache: bool,
35-
max_findings: Optional[int],
37+
max_findings: int | None,
3638
) -> int:
3739
root = path.resolve()
3840

@@ -86,7 +88,7 @@ def cmd_init(path: Path) -> int:
8688
return 0
8789

8890

89-
def cmd_stats(path: Path, staged: bool, tracked: bool, all_files: bool, baseline: Optional[Path]) -> int:
91+
def cmd_stats(path: Path, staged: bool, tracked: bool, all_files: bool, baseline: Path | None) -> int:
9092
root = path.resolve()
9193
mode = "tracked"
9294
if staged:

src/secretscout/git.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@ def _run_git(args: list[str], cwd: Path) -> subprocess.CompletedProcess:
88
return subprocess.run(
99
["git", *args],
1010
cwd=str(cwd),
11-
stdout=subprocess.PIPE,
12-
stderr=subprocess.PIPE,
11+
capture_output=True,
1312
text=False,
1413
check=False,
1514
)
1615

1716

17+
1818
def is_git_repo(root: Path) -> bool:
1919
p = _run_git(["rev-parse", "--is-inside-work-tree"], cwd=root)
2020
return p.returncode == 0 and p.stdout.strip() == b"true"

src/secretscout/models.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ class Severity(str, Enum):
1212
critical = "critical"
1313

1414
@classmethod
15-
def order(cls) -> dict["Severity", int]:
15+
def order(cls) -> dict[Severity, int]:
1616
return {cls.low: 0, cls.medium: 1, cls.high: 2, cls.critical: 3}
1717

18-
def ge(self, other: "Severity") -> bool:
18+
def ge(self, other: Severity) -> bool:
1919
return self.order()[self] >= self.order()[other]
2020

2121

src/secretscout/reporting.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
import html
44
import json
55
from collections import Counter
6+
from collections.abc import Iterable
67
from datetime import datetime
7-
from typing import Iterable, Literal
8+
from typing import Literal
89

910
from rich.console import Console
1011
from rich.table import Table

src/secretscout/scanner.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
import json
44
import re
5+
from collections.abc import Iterable
56
from concurrent.futures import ThreadPoolExecutor, as_completed
67
from pathlib import Path
7-
from typing import Iterable
88

99
from .cache import Cache
1010
from .config import is_excluded, load_config, load_ignore_file

src/secretscout/util.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import hashlib
44
import math
55
import re
6-
from typing import Iterable
6+
from collections.abc import Iterable
77

88

99
def shannon_entropy(s: str) -> float:

0 commit comments

Comments
 (0)