From 71681740061fa58aca7c7bcbf6f0805593115e1e Mon Sep 17 00:00:00 2001 From: CarterPerez-dev Date: Wed, 18 Feb 2026 20:08:20 -0500 Subject: [PATCH] issue 77 pt3 golangci --- .github/workflows/lint.yml | 13 +- .pre-commit-config.yaml | 18 +- .../base64-tool/src/base64_tool/cli.py | 86 ++++----- .../base64-tool/src/base64_tool/constants.py | 2 +- .../base64-tool/src/base64_tool/detector.py | 33 ++-- .../base64-tool/src/base64_tool/formatter.py | 83 +++++---- .../base64-tool/src/base64_tool/peeler.py | 28 ++- .../src/caesar_cipher/__init__.py | 1 + .../beginner/dns-lookup/dnslookup/output.py | 1 - .../internal/cli/update.go | 4 +- .../internal/analyzer/container.go | 20 +-- .../internal/analyzer/container_test.go | 24 ++- .../internal/analyzer/image.go | 12 +- .../internal/docker/client.go | 14 +- .../tests/e2e/e2e_test.go | 166 +++++++++++++++--- .../tests/integration/compose_test.go | 2 +- TEMPLATES/fullstack-template | 2 +- 17 files changed, 330 insertions(+), 179 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 2e6d18f1..79d47a7f 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -87,9 +87,12 @@ jobs: type: biome path: TEMPLATES/fullstack-template/frontend # Go + - name: simple-vulnerability-scanner + type: go + path: PROJECTS/beginner/simple-vulnerability-scanner - name: docker-security-audit type: go - path: PROJECTS/beginner/docker-security-audit + path: PROJECTS/intermediate/docker-security-audit defaults: run: @@ -142,9 +145,13 @@ jobs: if: matrix.type == 'go' uses: actions/setup-go@v5 with: - go-version: '1.21' + go-version-file: ${{ matrix.path }}/go.mod cache-dependency-path: ${{ matrix.path }}/go.sum + - name: Install golangci-lint + if: matrix.type == 'go' + run: go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest + # Ruff Linting - name: Run ruff if: matrix.type == 'ruff' @@ -183,7 +190,7 @@ jobs: id: golangci run: | echo "Running golangci-lint..." - if golangci-lint run --out-format=colored-line-number > golangci-output.txt 2>&1; then + if golangci-lint run > golangci-output.txt 2>&1; then echo "GOLANGCI_PASSED=true" >> $GITHUB_ENV echo "No golangci-lint errors found!" else diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8ee84f61..3d079ca0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -92,8 +92,24 @@ repos: exclude: (\.venv|__pycache__|\.pytest_cache)/ - # TODO: add golangci checks for GO projects + # Go golangci-lint Checks + - repo: local + hooks: + - id: golangci-lint-simple-vulnerability-scanner + name: golangci-lint (simple-vulnerability-scanner) + entry: bash -c 'cd PROJECTS/beginner/simple-vulnerability-scanner && golangci-lint run --fix' + language: system + files: ^PROJECTS/beginner/simple-vulnerability-scanner/ + types: [go] + pass_filenames: false + - id: golangci-lint-docker-security-audit + name: golangci-lint (docker-security-audit) + entry: bash -c 'cd PROJECTS/intermediate/docker-security-audit && golangci-lint run --fix' + language: system + files: ^PROJECTS/intermediate/docker-security-audit/ + types: [go] + pass_filenames: false # Biome Frontend Checks - repo: local diff --git a/PROJECTS/beginner/base64-tool/src/base64_tool/cli.py b/PROJECTS/beginner/base64-tool/src/base64_tool/cli.py index e0cd3039..dc03a8fc 100644 --- a/PROJECTS/beginner/base64-tool/src/base64_tool/cli.py +++ b/PROJECTS/beginner/base64-tool/src/base64_tool/cli.py @@ -37,14 +37,14 @@ from base64_tool.utils import ( app = typer.Typer( - name="b64tool", - help=("Multi-format encoding/decoding CLI " - "with recursive layer detection"), - no_args_is_help=True, - pretty_exceptions_show_locals=False, + name = "b64tool", + help = ("Multi-format encoding/decoding CLI " + "with recursive layer detection"), + no_args_is_help = True, + pretty_exceptions_show_locals = False, ) -_console = Console(stderr=True) +_console = Console(stderr = True) def _version_callback(value: bool) -> None: @@ -60,27 +60,27 @@ def main( typer.Option( "--version", "-v", - help="Show version and exit.", - callback=_version_callback, - is_eager=True, + help = "Show version and exit.", + callback = _version_callback, + is_eager = True, ), ] = False, ) -> None: pass -@app.command(name="encode") +@app.command(name = "encode") def encode_cmd( data: Annotated[ str | None, - typer.Argument(help="Data to encode."), + typer.Argument(help = "Data to encode."), ] = None, fmt: Annotated[ EncodingFormat, typer.Option( "--format", "-f", - help="Target encoding format.", + help = "Target encoding format.", ), ] = EncodingFormat.BASE64, file: Annotated[ @@ -88,21 +88,21 @@ def encode_cmd( typer.Option( "--file", "-i", - help="Read input from file.", + help = "Read input from file.", ), ] = None, form: Annotated[ bool, typer.Option( "--form", - help="Use form-encoding for URL (space becomes +).", + help = "Use form-encoding for URL (space becomes +).", ), ] = False, ) -> None: try: raw = resolve_input_bytes(data, file) if fmt == EncodingFormat.URL and form: - result = encode_url(raw, form=True) + result = encode_url(raw, form = True) else: result = encode(raw, fmt) print_encoded(result, fmt) @@ -110,21 +110,21 @@ def encode_cmd( raise except Exception as exc: _console.print(f"[red]Error:[/red] {exc}") - raise typer.Exit(code=ExitCode.ERROR) from None + raise typer.Exit(code = ExitCode.ERROR) from None -@app.command(name="decode") +@app.command(name = "decode") def decode_cmd( data: Annotated[ str | None, - typer.Argument(help="Data to decode."), + typer.Argument(help = "Data to decode."), ] = None, fmt: Annotated[ EncodingFormat, typer.Option( "--format", "-f", - help="Source encoding format.", + help = "Source encoding format.", ), ] = EncodingFormat.BASE64, file: Annotated[ @@ -132,21 +132,21 @@ def decode_cmd( typer.Option( "--file", "-i", - help="Read input from file.", + help = "Read input from file.", ), ] = None, form: Annotated[ bool, typer.Option( "--form", - help="Use form-decoding for URL (+ becomes space).", + help = "Use form-decoding for URL (+ becomes space).", ), ] = False, ) -> None: try: text = resolve_input_text(data, file) if fmt == EncodingFormat.URL and form: - result = decode_url(text, form=True) + result = decode_url(text, form = True) else: result = decode(text, fmt) print_decoded(result) @@ -154,21 +154,21 @@ def decode_cmd( raise except Exception as exc: _console.print(f"[red]Error:[/red] {exc}") - raise typer.Exit(code=ExitCode.ERROR) from None + raise typer.Exit(code = ExitCode.ERROR) from None -@app.command(name="detect") +@app.command(name = "detect") def detect_cmd( data: Annotated[ str | None, - typer.Argument(help="Data to analyze."), + typer.Argument(help = "Data to analyze."), ] = None, file: Annotated[ Path | None, typer.Option( "--file", "-i", - help="Read input from file.", + help = "Read input from file.", ), ] = None, verbose: Annotated[ @@ -176,7 +176,7 @@ def detect_cmd( typer.Option( "--verbose", "-V", - help="Show per-format score breakdown.", + help = "Show per-format score breakdown.", ), ] = False, ) -> None: @@ -184,26 +184,26 @@ def detect_cmd( text = resolve_input_text(data, file) results = detect_encoding(text) scores = score_all_formats(text) if verbose else None - print_detection(results, verbose_scores=scores) + print_detection(results, verbose_scores = scores) except typer.BadParameter: raise except Exception as exc: _console.print(f"[red]Error:[/red] {exc}") - raise typer.Exit(code=ExitCode.ERROR) from None + raise typer.Exit(code = ExitCode.ERROR) from None -@app.command(name="peel") +@app.command(name = "peel") def peel_cmd( data: Annotated[ str | None, - typer.Argument(help="Data to recursively decode."), + typer.Argument(help = "Data to recursively decode."), ] = None, file: Annotated[ Path | None, typer.Option( "--file", "-i", - help="Read input from file.", + help = "Read input from file.", ), ] = None, max_depth: Annotated[ @@ -211,7 +211,7 @@ def peel_cmd( typer.Option( "--max-depth", "-d", - help="Maximum decoding layers.", + help = "Maximum decoding layers.", ), ] = PEEL_MAX_DEPTH, verbose: Annotated[ @@ -219,34 +219,34 @@ def peel_cmd( typer.Option( "--verbose", "-V", - help="Show per-format score breakdown at each layer.", + help = "Show per-format score breakdown at each layer.", ), ] = False, ) -> None: try: text = resolve_input_text(data, file) - result = peel(text, max_depth=max_depth, verbose=verbose) - print_peel_result(result, verbose=verbose) + result = peel(text, max_depth = max_depth, verbose = verbose) + print_peel_result(result, verbose = verbose) except typer.BadParameter: raise except Exception as exc: _console.print(f"[red]Error:[/red] {exc}") - raise typer.Exit(code=ExitCode.ERROR) from None + raise typer.Exit(code = ExitCode.ERROR) from None -@app.command(name="chain") +@app.command(name = "chain") def chain_cmd( data: Annotated[ str | None, - typer.Argument(help="Data to encode through chain."), + typer.Argument(help = "Data to encode through chain."), ] = None, steps: Annotated[ str, typer.Option( "--steps", "-s", - help=("Comma-separated encoding formats " - "(e.g. base64,hex,url)."), + help = ("Comma-separated encoding formats " + "(e.g. base64,hex,url)."), ), ] = "base64", file: Annotated[ @@ -254,7 +254,7 @@ def chain_cmd( typer.Option( "--file", "-i", - help="Read input from file.", + help = "Read input from file.", ), ] = None, ) -> None: @@ -275,7 +275,7 @@ def chain_cmd( raise except Exception as exc: _console.print(f"[red]Error:[/red] {exc}") - raise typer.Exit(code=ExitCode.ERROR) from None + raise typer.Exit(code = ExitCode.ERROR) from None def _parse_chain_steps(raw: str) -> list[EncodingFormat]: diff --git a/PROJECTS/beginner/base64-tool/src/base64_tool/constants.py b/PROJECTS/beginner/base64-tool/src/base64_tool/constants.py index 1b37fdfe..77741d4d 100644 --- a/PROJECTS/beginner/base64-tool/src/base64_tool/constants.py +++ b/PROJECTS/beginner/base64-tool/src/base64_tool/constants.py @@ -62,6 +62,7 @@ class ScoreWeight: URL_RATIO_CAP: Final[float] = 0.35 URL_DECODE_CHANGED: Final[float] = 0.15 + BASE64_CHARSET: Final[ frozenset[str] ] = frozenset("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=") @@ -75,4 +76,3 @@ BASE32_CHARSET: Final[frozenset[str]] = frozenset("ABCDEFGHIJKLMNOPQRSTUVWXYZ234 HEX_CHARSET: Final[frozenset[str]] = frozenset("0123456789abcdefABCDEF") HEX_SEPARATORS: Final[frozenset[str]] = frozenset(" :.-") - diff --git a/PROJECTS/beginner/base64-tool/src/base64_tool/detector.py b/PROJECTS/beginner/base64-tool/src/base64_tool/detector.py index 4733aaa5..df8c6ea8 100644 --- a/PROJECTS/beginner/base64-tool/src/base64_tool/detector.py +++ b/PROJECTS/beginner/base64-tool/src/base64_tool/detector.py @@ -22,7 +22,7 @@ from base64_tool.encoders import try_decode from base64_tool.utils import is_printable_text -@dataclass(frozen=True, slots=True) +@dataclass(frozen = True, slots = True) class DetectionResult: format: EncodingFormat confidence: float @@ -152,9 +152,7 @@ def _score_hex(data: str) -> float: else: score -= W.HEX_NO_ALPHA_PENALTY - is_consistent_case = ( - hex_only == hex_only.lower() or hex_only == hex_only.upper() - ) + is_consistent_case = (hex_only == hex_only.lower() or hex_only == hex_only.upper()) if is_consistent_case: score += W.HEX_CONSISTENT_CASE @@ -188,20 +186,22 @@ def _score_url(data: str) -> float: decoded = try_decode(data, EncodingFormat.URL) if decoded is not None: - decoded_text = decoded.decode("utf-8", errors="replace") + decoded_text = decoded.decode("utf-8", errors = "replace") if decoded_text != data: score += W.URL_DECODE_CHANGED return min(score, 1.0) -_SCORERS: dict[EncodingFormat, Callable[[str], float]] = { - EncodingFormat.BASE64: _score_base64, - EncodingFormat.BASE64URL: _score_base64url, - EncodingFormat.BASE32: _score_base32, - EncodingFormat.HEX: _score_hex, - EncodingFormat.URL: _score_url, -} +_SCORERS: dict[EncodingFormat, + Callable[[str], + float]] = { + EncodingFormat.BASE64: _score_base64, + EncodingFormat.BASE64URL: _score_base64url, + EncodingFormat.BASE32: _score_base32, + EncodingFormat.HEX: _score_hex, + EncodingFormat.URL: _score_url, + } def score_all_formats(data: str) -> dict[EncodingFormat, float]: @@ -216,13 +216,14 @@ def detect_encoding(data: str) -> list[DetectionResult]: decoded = try_decode(data, fmt) results.append( DetectionResult( - format=fmt, - confidence=round(confidence, 2), - decoded=decoded, + format = fmt, + confidence = round(confidence, + 2), + decoded = decoded, ) ) - results.sort(key=lambda r: r.confidence, reverse=True) + results.sort(key = lambda r: r.confidence, reverse = True) return results diff --git a/PROJECTS/beginner/base64-tool/src/base64_tool/formatter.py b/PROJECTS/beginner/base64-tool/src/base64_tool/formatter.py index 76eef9f2..da18ec3f 100644 --- a/PROJECTS/beginner/base64-tool/src/base64_tool/formatter.py +++ b/PROJECTS/beginner/base64-tool/src/base64_tool/formatter.py @@ -20,7 +20,7 @@ from base64_tool.peeler import PeelResult from base64_tool.utils import safe_bytes_preview -console = Console(stderr=True) +console = Console(stderr = True) def is_piped() -> bool: @@ -37,44 +37,49 @@ def print_encoded(result: str, fmt: EncodingFormat) -> None: write_raw(result) return panel = Panel( - Text(result, style="green"), - title=f"[bold cyan]{fmt.value}[/bold cyan] encoded", - border_style="cyan", + Text(result, + style = "green"), + title = f"[bold cyan]{fmt.value}[/bold cyan] encoded", + border_style = "cyan", ) console.print(panel) def print_decoded(result: bytes) -> None: - preview = safe_bytes_preview(result, length=4096) + preview = safe_bytes_preview(result, length = 4096) if is_piped(): write_raw(preview) return panel = Panel( - Text(preview, style="green"), - title="[bold cyan]Decoded[/bold cyan]", - border_style="cyan", + Text(preview, + style = "green"), + title = "[bold cyan]Decoded[/bold cyan]", + border_style = "cyan", ) console.print(panel) def print_score_breakdown( - scores: dict[EncodingFormat, float], + scores: dict[EncodingFormat, + float], ) -> None: table = Table( - title="Score Breakdown", - show_header=True, - header_style="bold magenta", + title = "Score Breakdown", + show_header = True, + header_style = "bold magenta", ) - table.add_column("Format", style="cyan", min_width=10) + table.add_column("Format", style = "cyan", min_width = 10) table.add_column( "Score", - justify="right", - min_width=8, + justify = "right", + min_width = 8, ) - table.add_column("Status", min_width=10) + table.add_column("Status", min_width = 10) sorted_scores = sorted( - scores.items(), key=lambda x: x[1], reverse=True, + scores.items(), + key = lambda x: x[1], + reverse = True, ) for fmt, score in sorted_scores: color = _confidence_color(score) @@ -96,7 +101,8 @@ def print_score_breakdown( def print_detection( results: list[DetectionResult], *, - verbose_scores: dict[EncodingFormat, float] | None = None, + verbose_scores: dict[EncodingFormat, + float] | None = None, ) -> None: if verbose_scores is not None: print_score_breakdown(verbose_scores) @@ -107,18 +113,18 @@ def print_detection( return table = Table( - title="Detection Results", - show_header=True, - header_style="bold magenta", + title = "Detection Results", + show_header = True, + header_style = "bold magenta", ) - table.add_column("Format", style="cyan", min_width=10) + table.add_column("Format", style = "cyan", min_width = 10) table.add_column( "Confidence", - justify="right", - style="green", - min_width=12, + justify = "right", + style = "green", + min_width = 12, ) - table.add_column("Decoded Preview", style="dim") + table.add_column("Decoded Preview", style = "dim") for result in results: confidence_str = f"{result.confidence:.0%}" @@ -171,18 +177,20 @@ def print_peel_result( console.print() - preview = safe_bytes_preview(result.final_output, length=4096) + preview = safe_bytes_preview(result.final_output, length = 4096) panel = Panel( - Text(preview, style="bold green"), - title="[bold]Final Output[/bold]", - border_style="green", - subtitle=(f"[dim]{layer_count} layer{suffix} peeled[/dim]"), + Text(preview, + style = "bold green"), + title = "[bold]Final Output[/bold]", + border_style = "green", + subtitle = (f"[dim]{layer_count} layer{suffix} peeled[/dim]"), ) console.print(panel) def print_chain_result( - steps: list[tuple[EncodingFormat, str]], + steps: list[tuple[EncodingFormat, + str]], final: str, ) -> None: if is_piped(): @@ -196,7 +204,7 @@ def print_chain_result( for i, (fmt, intermediate) in enumerate(steps): marker = "start" if i == 0 else "step" arrow = f" [{marker}] " if i == 0 else " -> " - truncated = intermediate[:PREVIEW_LENGTH] + truncated = intermediate[: PREVIEW_LENGTH] ellipsis = "..." if len(intermediate) > PREVIEW_LENGTH else "" console.print( f"{arrow}[cyan]{fmt.value}[/cyan] " @@ -205,10 +213,11 @@ def print_chain_result( console.print() panel = Panel( - Text(final, style="green"), - title="[bold]Chain Result[/bold]", - border_style="cyan", - subtitle=f"[dim]{len(steps)} steps[/dim]", + Text(final, + style = "green"), + title = "[bold]Chain Result[/bold]", + border_style = "cyan", + subtitle = f"[dim]{len(steps)} steps[/dim]", ) console.print(panel) diff --git a/PROJECTS/beginner/base64-tool/src/base64_tool/peeler.py b/PROJECTS/beginner/base64-tool/src/base64_tool/peeler.py index 4e385b9d..35b9b7c3 100644 --- a/PROJECTS/beginner/base64-tool/src/base64_tool/peeler.py +++ b/PROJECTS/beginner/base64-tool/src/base64_tool/peeler.py @@ -14,7 +14,7 @@ from base64_tool.detector import detect_best, score_all_formats from base64_tool.utils import safe_bytes_preview, truncate -@dataclass(frozen=True, slots=True) +@dataclass(frozen = True, slots = True) class PeelLayer: depth: int format: EncodingFormat @@ -24,7 +24,7 @@ class PeelLayer: all_scores: tuple[tuple[EncodingFormat, float], ...] = () -@dataclass(frozen=True, slots=True) +@dataclass(frozen = True, slots = True) class PeelResult: layers: tuple[PeelLayer, ...] final_output: bytes @@ -52,20 +52,16 @@ def peel( if detection.decoded is None: break - scores = ( - tuple(score_all_formats(current_text).items()) - if verbose - else () - ) + scores = (tuple(score_all_formats(current_text).items()) if verbose else ()) decoded_bytes = detection.decoded layer = PeelLayer( - depth=depth + 1, - format=detection.format, - confidence=detection.confidence, - encoded_preview=truncate(current_text), - decoded_preview=safe_bytes_preview(decoded_bytes), - all_scores=scores, + depth = depth + 1, + format = detection.format, + confidence = detection.confidence, + encoded_preview = truncate(current_text), + decoded_preview = safe_bytes_preview(decoded_bytes), + all_scores = scores, ) layers.append(layer) current_bytes = decoded_bytes @@ -76,7 +72,7 @@ def peel( break return PeelResult( - layers=tuple(layers), - final_output=current_bytes, - success=len(layers) > 0, + layers = tuple(layers), + final_output = current_bytes, + success = len(layers) > 0, ) diff --git a/PROJECTS/beginner/caesar-cipher/src/caesar_cipher/__init__.py b/PROJECTS/beginner/caesar-cipher/src/caesar_cipher/__init__.py index a042a8bd..97a588c4 100644 --- a/PROJECTS/beginner/caesar-cipher/src/caesar_cipher/__init__.py +++ b/PROJECTS/beginner/caesar-cipher/src/caesar_cipher/__init__.py @@ -26,5 +26,6 @@ from caesar_cipher.cipher import CaesarCipher from caesar_cipher.analyzer import FrequencyAnalyzer + __version__ = "0.1.0" __all__ = ["CaesarCipher", "FrequencyAnalyzer"] diff --git a/PROJECTS/beginner/dns-lookup/dnslookup/output.py b/PROJECTS/beginner/dns-lookup/dnslookup/output.py index 5a18970e..ce545aa3 100644 --- a/PROJECTS/beginner/dns-lookup/dnslookup/output.py +++ b/PROJECTS/beginner/dns-lookup/dnslookup/output.py @@ -24,7 +24,6 @@ from dnslookup.resolver import ( console = Console() - RECORD_COLORS: dict[RecordType, str] = { RecordType.A: "green", diff --git a/PROJECTS/beginner/simple-vulnerability-scanner/internal/cli/update.go b/PROJECTS/beginner/simple-vulnerability-scanner/internal/cli/update.go index 8f91283e..b12783fa 100644 --- a/PROJECTS/beginner/simple-vulnerability-scanner/internal/cli/update.go +++ b/PROJECTS/beginner/simple-vulnerability-scanner/internal/cli/update.go @@ -134,11 +134,11 @@ func runInit() error { name := filepath.Base(dir) content := fmt.Sprintf(pyprojectTemplate, name) - if err := os.WriteFile( + if err := os.WriteFile( //nolint:gosec // pyproject.toml needs 0644 for uv/pip reads "pyproject.toml", []byte(content), 0o644, - ); err != nil { //nolint:gosec + ); err != nil { return fmt.Errorf("write pyproject.toml: %w", err) } diff --git a/PROJECTS/intermediate/docker-security-audit/internal/analyzer/container.go b/PROJECTS/intermediate/docker-security-audit/internal/analyzer/container.go index 99e9c781..2f2d756b 100644 --- a/PROJECTS/intermediate/docker-security-audit/internal/analyzer/container.go +++ b/PROJECTS/intermediate/docker-security-audit/internal/analyzer/container.go @@ -1,5 +1,5 @@ /* -AngelaMos | 2026 +© AngelaMos | 2026 container.go */ @@ -13,7 +13,7 @@ import ( "github.com/CarterPerez-dev/docksec/internal/docker" "github.com/CarterPerez-dev/docksec/internal/finding" "github.com/CarterPerez-dev/docksec/internal/rules" - "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" ) type ContainerAnalyzer struct { @@ -49,7 +49,7 @@ func (a *ContainerAnalyzer) Analyze( } func (a *ContainerAnalyzer) analyzeContainer( - info types.ContainerJSON, + info container.InspectResponse, ) finding.Collection { var findings finding.Collection target := finding.Target{ @@ -75,7 +75,7 @@ func (a *ContainerAnalyzer) analyzeContainer( func (a *ContainerAnalyzer) checkPrivileged( target finding.Target, - info types.ContainerJSON, + info container.InspectResponse, ) finding.Collection { var findings finding.Collection @@ -95,7 +95,7 @@ func (a *ContainerAnalyzer) checkPrivileged( func (a *ContainerAnalyzer) checkCapabilities( target finding.Target, - info types.ContainerJSON, + info container.InspectResponse, ) finding.Collection { var findings finding.Collection @@ -127,7 +127,7 @@ func (a *ContainerAnalyzer) checkCapabilities( func (a *ContainerAnalyzer) checkMounts( target finding.Target, - info types.ContainerJSON, + info container.InspectResponse, ) finding.Collection { var findings finding.Collection @@ -172,7 +172,7 @@ func (a *ContainerAnalyzer) checkMounts( func (a *ContainerAnalyzer) checkNamespaces( target finding.Target, - info types.ContainerJSON, + info container.InspectResponse, ) finding.Collection { var findings finding.Collection @@ -225,7 +225,7 @@ func (a *ContainerAnalyzer) checkNamespaces( func (a *ContainerAnalyzer) checkSecurityOptions( target finding.Target, - info types.ContainerJSON, + info container.InspectResponse, ) finding.Collection { var findings finding.Collection @@ -298,7 +298,7 @@ func (a *ContainerAnalyzer) checkSecurityOptions( func (a *ContainerAnalyzer) checkResourceLimits( target finding.Target, - info types.ContainerJSON, + info container.InspectResponse, ) finding.Collection { var findings finding.Collection @@ -342,7 +342,7 @@ func (a *ContainerAnalyzer) checkResourceLimits( func (a *ContainerAnalyzer) checkReadonlyRootfs( target finding.Target, - info types.ContainerJSON, + info container.InspectResponse, ) finding.Collection { var findings finding.Collection diff --git a/PROJECTS/intermediate/docker-security-audit/internal/analyzer/container_test.go b/PROJECTS/intermediate/docker-security-audit/internal/analyzer/container_test.go index 2f4f9e07..fef08640 100644 --- a/PROJECTS/intermediate/docker-security-audit/internal/analyzer/container_test.go +++ b/PROJECTS/intermediate/docker-security-audit/internal/analyzer/container_test.go @@ -1,5 +1,5 @@ /* -AngelaMos | 2026 +© AngelaMos | 2026 container_test.go */ @@ -12,23 +12,33 @@ import ( "testing" "github.com/CarterPerez-dev/docksec/internal/finding" - "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func loadContainerJSON(t *testing.T, filename string) types.ContainerJSON { +func loadContainerJSON( + t *testing.T, + filename string, +) container.InspectResponse { t.Helper() - path := filepath.Join("..", "..", "tests", "testdata", "containers", filename) + path := filepath.Join( + "..", + "..", + "tests", + "testdata", + "containers", + filename, + ) data, err := os.ReadFile(path) require.NoError(t, err, "Failed to read container JSON file") - var container types.ContainerJSON - err = json.Unmarshal(data, &container) + var ctr container.InspectResponse + err = json.Unmarshal(data, &ctr) require.NoError(t, err, "Failed to unmarshal container JSON") - return container + return ctr } func TestContainerAnalyzer_PrivilegedContainer(t *testing.T) { diff --git a/PROJECTS/intermediate/docker-security-audit/internal/analyzer/image.go b/PROJECTS/intermediate/docker-security-audit/internal/analyzer/image.go index 2270eedf..f135b68a 100644 --- a/PROJECTS/intermediate/docker-security-audit/internal/analyzer/image.go +++ b/PROJECTS/intermediate/docker-security-audit/internal/analyzer/image.go @@ -1,5 +1,5 @@ /* -AngelaMos | 2026 +© AngelaMos | 2026 image.go */ @@ -13,7 +13,7 @@ import ( "github.com/CarterPerez-dev/docksec/internal/benchmark" "github.com/CarterPerez-dev/docksec/internal/docker" "github.com/CarterPerez-dev/docksec/internal/finding" - "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/image" ) type ImageAnalyzer struct { @@ -62,7 +62,7 @@ func (a *ImageAnalyzer) Analyze( func (a *ImageAnalyzer) analyzeImage( target finding.Target, - info types.ImageInspect, + info image.InspectResponse, ) finding.Collection { var findings finding.Collection @@ -75,7 +75,7 @@ func (a *ImageAnalyzer) analyzeImage( func (a *ImageAnalyzer) checkRootUser( target finding.Target, - info types.ImageInspect, + info image.InspectResponse, ) finding.Collection { var findings finding.Collection @@ -100,7 +100,7 @@ func (a *ImageAnalyzer) checkRootUser( func (a *ImageAnalyzer) checkHealthcheck( target finding.Target, - info types.ImageInspect, + info image.InspectResponse, ) finding.Collection { var findings finding.Collection @@ -139,7 +139,7 @@ func (a *ImageAnalyzer) checkHealthcheck( func (a *ImageAnalyzer) checkExposedPorts( target finding.Target, - info types.ImageInspect, + info image.InspectResponse, ) finding.Collection { var findings finding.Collection diff --git a/PROJECTS/intermediate/docker-security-audit/internal/docker/client.go b/PROJECTS/intermediate/docker-security-audit/internal/docker/client.go index 26c38d06..0c453a02 100644 --- a/PROJECTS/intermediate/docker-security-audit/internal/docker/client.go +++ b/PROJECTS/intermediate/docker-security-audit/internal/docker/client.go @@ -1,5 +1,5 @@ /* -CarterPerez-dev | 2026 +© AngelaMos | 2026 client.go */ @@ -90,7 +90,7 @@ func (c *Client) ServerVersion(ctx context.Context) (types.Version, error) { func (c *Client) ListContainers( ctx context.Context, all bool, -) ([]types.Container, error) { +) ([]container.Summary, error) { listCtx, cancel := context.WithTimeout(ctx, config.DefaultTimeout) defer cancel() @@ -107,13 +107,13 @@ func (c *Client) ListContainers( func (c *Client) InspectContainer( ctx context.Context, containerID string, -) (types.ContainerJSON, error) { +) (container.InspectResponse, error) { inspectCtx, cancel := context.WithTimeout(ctx, config.InspectTimeout) defer cancel() info, err := c.api.ContainerInspect(inspectCtx, containerID) if err != nil { - return types.ContainerJSON{}, fmt.Errorf( + return container.InspectResponse{}, fmt.Errorf( "inspecting container %s: %w", containerID, err, @@ -136,13 +136,13 @@ func (c *Client) ListImages(ctx context.Context) ([]image.Summary, error) { func (c *Client) InspectImage( ctx context.Context, imageID string, -) (types.ImageInspect, error) { +) (image.InspectResponse, error) { inspectCtx, cancel := context.WithTimeout(ctx, config.InspectTimeout) defer cancel() - info, _, err := c.api.ImageInspectWithRaw(inspectCtx, imageID) + info, err := c.api.ImageInspect(inspectCtx, imageID) if err != nil { - return types.ImageInspect{}, fmt.Errorf( + return image.InspectResponse{}, fmt.Errorf( "inspecting image %s: %w", imageID, err, diff --git a/PROJECTS/intermediate/docker-security-audit/tests/e2e/e2e_test.go b/PROJECTS/intermediate/docker-security-audit/tests/e2e/e2e_test.go index 1b4f23ab..483aa337 100644 --- a/PROJECTS/intermediate/docker-security-audit/tests/e2e/e2e_test.go +++ b/PROJECTS/intermediate/docker-security-audit/tests/e2e/e2e_test.go @@ -25,7 +25,12 @@ func TestE2E_DockerfileAnalysis(t *testing.T) { ctx := context.Background() t.Run("analyze bad-secrets.Dockerfile end-to-end", func(t *testing.T) { - path := filepath.Join("..", "testdata", "dockerfiles", "bad-secrets.Dockerfile") + path := filepath.Join( + "..", + "testdata", + "dockerfiles", + "bad-secrets.Dockerfile", + ) require.FileExists(t, path, "Test file should exist") @@ -47,7 +52,12 @@ func TestE2E_DockerfileAnalysis(t *testing.T) { }) t.Run("analyze good-security.Dockerfile end-to-end", func(t *testing.T) { - path := filepath.Join("..", "testdata", "dockerfiles", "good-security.Dockerfile") + path := filepath.Join( + "..", + "testdata", + "dockerfiles", + "good-security.Dockerfile", + ) require.FileExists(t, path, "Test file should exist") @@ -73,7 +83,12 @@ func TestE2E_ComposeAnalysis(t *testing.T) { ctx := context.Background() t.Run("analyze bad-docker-socket.yml end-to-end", func(t *testing.T) { - path := filepath.Join("..", "testdata", "compose", "bad-docker-socket.yml") + path := filepath.Join( + "..", + "testdata", + "compose", + "bad-docker-socket.yml", + ) require.FileExists(t, path, "Test file should exist") @@ -83,8 +98,11 @@ func TestE2E_ComposeAnalysis(t *testing.T) { require.NoError(t, err, "Analyze should not return error") require.NotEmpty(t, findings, "Should have findings") - assert.True(t, findings.HasSeverityAtOrAbove(finding.SeverityCritical), - "Should detect CRITICAL issues") + assert.True( + t, + findings.HasSeverityAtOrAbove(finding.SeverityCritical), + "Should detect CRITICAL issues", + ) counts := findings.CountBySeverity() t.Logf("Findings by severity: %+v", counts) @@ -95,7 +113,12 @@ func TestE2E_ComposeAnalysis(t *testing.T) { }) t.Run("analyze good-production.yml end-to-end", func(t *testing.T) { - path := filepath.Join("..", "testdata", "compose", "good-production.yml") + path := filepath.Join( + "..", + "testdata", + "compose", + "good-production.yml", + ) require.FileExists(t, path, "Test file should exist") @@ -104,8 +127,11 @@ func TestE2E_ComposeAnalysis(t *testing.T) { findings, err := a.Analyze(ctx) require.NoError(t, err, "Analyze should not return error") - assert.False(t, findings.HasSeverityAtOrAbove(finding.SeverityCritical), - "Production compose should have no CRITICAL findings") + assert.False( + t, + findings.HasSeverityAtOrAbove(finding.SeverityCritical), + "Production compose should have no CRITICAL findings", + ) counts := findings.CountBySeverity() t.Logf("Findings by severity: %+v", counts) @@ -126,22 +152,42 @@ func TestE2E_MultipleFiles(t *testing.T) { wantIssues bool }{ { - path: filepath.Join("..", "testdata", "dockerfiles", "bad-secrets.Dockerfile"), + path: filepath.Join( + "..", + "testdata", + "dockerfiles", + "bad-secrets.Dockerfile", + ), analyzer: func(p string) analyzer.Analyzer { return analyzer.NewDockerfileAnalyzer(p) }, wantIssues: true, }, { - path: filepath.Join("..", "testdata", "dockerfiles", "good-minimal.Dockerfile"), + path: filepath.Join( + "..", + "testdata", + "dockerfiles", + "good-minimal.Dockerfile", + ), analyzer: func(p string) analyzer.Analyzer { return analyzer.NewDockerfileAnalyzer(p) }, wantIssues: false, }, { - path: filepath.Join("..", "testdata", "compose", "bad-privileged.yml"), + path: filepath.Join( + "..", + "testdata", + "compose", + "bad-privileged.yml", + ), analyzer: func(p string) analyzer.Analyzer { return analyzer.NewComposeAnalyzer(p) }, wantIssues: true, }, { - path: filepath.Join("..", "testdata", "compose", "good-production.yml"), + path: filepath.Join( + "..", + "testdata", + "compose", + "good-production.yml", + ), analyzer: func(p string) analyzer.Analyzer { return analyzer.NewComposeAnalyzer(p) }, wantIssues: false, }, @@ -158,7 +204,12 @@ func TestE2E_MultipleFiles(t *testing.T) { require.NoError(t, err, "Analyze should not error for %s", f.path) if f.wantIssues { - assert.NotEmpty(t, findings, "File %s should have findings", f.path) + assert.NotEmpty( + t, + findings, + "File %s should have findings", + f.path, + ) } allFindings = append(allFindings, findings...) @@ -166,7 +217,11 @@ func TestE2E_MultipleFiles(t *testing.T) { } t.Logf("Total findings across all files: %d", allFindings.Total()) - assert.NotEmpty(t, allFindings, "Should have findings across all files") + assert.NotEmpty( + t, + allFindings, + "Should have findings across all files", + ) counts := allFindings.CountBySeverity() t.Logf("Overall severity distribution: %+v", counts) @@ -191,12 +246,28 @@ func TestE2E_FindingProperties(t *testing.T) { assert.NotEmpty(t, f.ID, "Finding should have ID") assert.NotEmpty(t, f.RuleID, "Finding should have RuleID") assert.NotEmpty(t, f.Title, "Finding should have Title") - assert.NotEmpty(t, f.Description, "Finding should have Description") + assert.NotEmpty( + t, + f.Description, + "Finding should have Description", + ) assert.NotEmpty(t, f.Category, "Finding should have Category") - assert.NotEmpty(t, f.Remediation, "Finding should have Remediation") + assert.NotEmpty( + t, + f.Remediation, + "Finding should have Remediation", + ) assert.NotZero(t, f.Severity, "Finding should have Severity") - assert.NotEmpty(t, f.Target.Type, "Finding should have Target.Type") - assert.NotEmpty(t, f.Target.Name, "Finding should have Target.Name") + assert.NotEmpty( + t, + f.Target.Type, + "Finding should have Target.Type", + ) + assert.NotEmpty( + t, + f.Target.Name, + "Finding should have Target.Name", + ) } }) @@ -205,12 +276,25 @@ func TestE2E_FindingProperties(t *testing.T) { for _, f := range findings { if f.Location != nil { hasLocation = true - assert.NotEmpty(t, f.Location.Path, "Location should have Path") - assert.Greater(t, f.Location.Line, 0, "Location should have Line > 0") + assert.NotEmpty( + t, + f.Location.Path, + "Location should have Path", + ) + assert.Greater( + t, + f.Location.Line, + 0, + "Location should have Line > 0", + ) break } } - assert.True(t, hasLocation, "At least one finding should have location info") + assert.True( + t, + hasLocation, + "At least one finding should have location info", + ) }) t.Run("CIS findings have CIS control info", func(t *testing.T) { @@ -219,8 +303,16 @@ func TestE2E_FindingProperties(t *testing.T) { if len(f.RuleID) >= 4 && f.RuleID[:4] == "CIS-" { hasCIS = true if f.CISControl != nil { - assert.NotEmpty(t, f.CISControl.ID, "CISControl should have ID") - assert.NotEmpty(t, f.CISControl.Title, "CISControl should have Title") + assert.NotEmpty( + t, + f.CISControl.ID, + "CISControl should have ID", + ) + assert.NotEmpty( + t, + f.CISControl.Title, + "CISControl should have Title", + ) } break } @@ -235,7 +327,12 @@ func TestE2E_SeverityFiltering(t *testing.T) { } ctx := context.Background() - path := filepath.Join("..", "testdata", "compose", "bad-docker-socket.yml") + path := filepath.Join( + "..", + "testdata", + "compose", + "bad-docker-socket.yml", + ) a := analyzer.NewComposeAnalyzer(path) findings, err := a.Analyze(ctx) @@ -247,7 +344,12 @@ func TestE2E_SeverityFiltering(t *testing.T) { high := findings.BySeverity(finding.SeverityHigh) medium := findings.BySeverity(finding.SeverityMedium) - t.Logf("CRITICAL: %d, HIGH: %d, MEDIUM: %d", len(critical), len(high), len(medium)) + t.Logf( + "CRITICAL: %d, HIGH: %d, MEDIUM: %d", + len(critical), + len(high), + len(medium), + ) assert.NotEmpty(t, critical, "Should have CRITICAL findings") @@ -278,7 +380,12 @@ func TestE2E_FileNotFound(t *testing.T) { ctx := context.Background() t.Run("nonexistent Dockerfile", func(t *testing.T) { - path := filepath.Join("..", "testdata", "dockerfiles", "does-not-exist.Dockerfile") + path := filepath.Join( + "..", + "testdata", + "dockerfiles", + "does-not-exist.Dockerfile", + ) a := analyzer.NewDockerfileAnalyzer(path) _, err := a.Analyze(ctx) @@ -288,7 +395,12 @@ func TestE2E_FileNotFound(t *testing.T) { }) t.Run("nonexistent compose file", func(t *testing.T) { - path := filepath.Join("..", "testdata", "compose", "does-not-exist.yml") + path := filepath.Join( + "..", + "testdata", + "compose", + "does-not-exist.yml", + ) a := analyzer.NewComposeAnalyzer(path) _, err := a.Analyze(ctx) diff --git a/PROJECTS/intermediate/docker-security-audit/tests/integration/compose_test.go b/PROJECTS/intermediate/docker-security-audit/tests/integration/compose_test.go index dd2ebbf4..5f0fd853 100644 --- a/PROJECTS/intermediate/docker-security-audit/tests/integration/compose_test.go +++ b/PROJECTS/intermediate/docker-security-audit/tests/integration/compose_test.go @@ -9,8 +9,8 @@ import ( "context" "path/filepath" "testing" - "github.com/CarterPerez-dev/docksec/internal/analyzer" + "github.com/CarterPerez-dev/docksec/internal/analyzer" "github.com/CarterPerez-dev/docksec/internal/finding" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/TEMPLATES/fullstack-template b/TEMPLATES/fullstack-template index 0eec274f..ecbb534e 160000 --- a/TEMPLATES/fullstack-template +++ b/TEMPLATES/fullstack-template @@ -1 +1 @@ -Subproject commit 0eec274fba59ddd373080ae961dd4497eaf2e454 +Subproject commit ecbb534e85e8e381e6e89aece4b786db8f7ad172