diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..6e3ed42e --- /dev/null +++ b/.clang-format @@ -0,0 +1,15 @@ +# ©AngelaMos | 2026 +# .clang-format +BasedOnStyle: LLVM +IndentWidth: 4 +TabWidth: 4 +UseTab: Never +ColumnLimit: 100 +AccessModifierOffset: -4 +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false +SortIncludes: CaseSensitive +PointerAlignment: Left +SpaceBeforeParens: ControlStatements +BreakBeforeBraces: Attach diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index bffb1595..767fb596 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -68,6 +68,16 @@ jobs: - name: dlp-scanner type: ruff path: PROJECTS/intermediate/dlp-scanner + # Python (ruff) - Foundations + - name: hash-identifier + type: ruff + path: PROJECTS/foundations/hash-identifier + - name: http-headers-scanner + type: ruff + path: PROJECTS/foundations/http-headers-scanner + - name: password-manager + type: ruff + path: PROJECTS/foundations/password-manager # Biome (frontend) - name: bug-bounty-platform-frontend type: biome @@ -84,6 +94,21 @@ jobs: - name: encrypted-p2p-chat-frontend type: biome path: PROJECTS/advanced/encrypted-p2p-chat/frontend + - name: canary-token-generator-frontend + type: biome + path: PROJECTS/beginner/canary-token-generator/frontend + - name: ai-threat-detection-frontend + type: biome + path: PROJECTS/advanced/ai-threat-detection/frontend + - name: binary-analysis-tool-frontend + type: biome + path: PROJECTS/intermediate/binary-analysis-tool/frontend + - name: honeypot-network-frontend + type: biome + path: PROJECTS/advanced/honeypot-network/frontend + - name: monitor-the-situation-dashboard-frontend + type: biome + path: PROJECTS/advanced/monitor-the-situation-dashboard/frontend # Go - name: simple-vulnerability-scanner type: go @@ -94,10 +119,51 @@ jobs: - name: secrets-scanner type: go path: PROJECTS/intermediate/secrets-scanner + - name: canary-token-generator-backend + type: go + path: PROJECTS/beginner/canary-token-generator/backend + - name: systemd-persistence-scanner + type: go + path: PROJECTS/beginner/systemd-persistence-scanner + - name: sbom-generator-vulnerability-matcher + type: go + path: PROJECTS/intermediate/sbom-generator-vulnerability-matcher + - name: honeypot-network + type: go + path: PROJECTS/advanced/honeypot-network + - name: monitor-the-situation-dashboard-backend + type: go + path: PROJECTS/advanced/monitor-the-situation-dashboard/backend # Nim - name: credential-enumeration type: nim path: PROJECTS/intermediate/credential-enumeration + # Rust + - name: binary-analysis-tool-backend + type: rust + path: PROJECTS/intermediate/binary-analysis-tool/backend + # Crystal + - name: credential-rotation-enforcer + type: crystal + path: PROJECTS/intermediate/credential-rotation-enforcer + # V (Vlang) + - name: firewall-rule-engine + type: v + path: PROJECTS/beginner/firewall-rule-engine + # C++ + - name: hash-cracker + type: cpp + path: PROJECTS/beginner/hash-cracker + - name: simple-port-scanner + type: cpp + path: PROJECTS/beginner/simple-port-scanner + - name: network-traffic-analyzer-cpp + type: cpp + path: PROJECTS/beginner/network-traffic-analyzer/cpp + # Bash (shellcheck) + - name: linux-cis-hardening-auditor + type: bash + path: PROJECTS/beginner/linux-cis-hardening-auditor defaults: run: @@ -155,7 +221,7 @@ jobs: - name: Install golangci-lint if: matrix.type == 'go' - run: go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest + run: go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.10.1 # Nim Setup - name: Setup Nim @@ -168,6 +234,73 @@ jobs: if: matrix.type == 'nim' run: nimble install -y nph + # Rust Setup (pinned to match locally-verified clippy version) + - name: Install Rust 1.92.0 + if: matrix.type == 'rust' + uses: dtolnay/rust-toolchain@1.92.0 + with: + components: rustfmt, clippy + + - name: Cache Cargo registry and target + if: matrix.type == 'rust' + uses: Swatinem/rust-cache@v2 + with: + workspaces: "${{ matrix.path }} -> target" + + # Crystal Setup + - name: Install libyaml-dev (ameba dep) + if: matrix.type == 'crystal' + run: | + sudo apt-get update -qq + sudo apt-get install -y libyaml-dev + + - name: Install Crystal + if: matrix.type == 'crystal' + uses: crystal-lang/install-crystal@v1 + with: + crystal: 1.20.0 + + - name: Cache shards + if: matrix.type == 'crystal' + uses: actions/cache@v4 + with: + path: ${{ matrix.path }}/lib + key: ${{ runner.os }}-shards-${{ matrix.name }}-${{ hashFiles(format('{0}/shard.lock', matrix.path)) }} + restore-keys: | + ${{ runner.os }}-shards-${{ matrix.name }}- + ${{ runner.os }}-shards- + + - name: Install shards + if: matrix.type == 'crystal' + run: shards install + + - name: Build ameba binary + if: matrix.type == 'crystal' + run: | + mkdir -p bin + crystal build lib/ameba/bin/ameba.cr -o bin/ameba + + # V (Vlang) Setup (no check-latest — master drift breaks fmt parity) + - name: Install V + if: matrix.type == 'v' + uses: vlang/setup-v@v1.4 + + # C++ Setup (pinned clang-format via PyPI wheel — apt patch versions drift) + - name: Install clang-format and cppcheck + if: matrix.type == 'cpp' + run: | + sudo apt-get update -qq + sudo apt-get install -y cppcheck python3-pip + pip install --user --break-system-packages clang-format==19.1.7 + echo "$HOME/.local/bin" >> $GITHUB_PATH + + # Bash Setup (shellcheck pre-installed on ubuntu-latest, but ensure latest) + - name: Install shellcheck + if: matrix.type == 'bash' + run: | + sudo apt-get update -qq + sudo apt-get install -y shellcheck + # Ruff Linting - name: Run ruff if: matrix.type == 'ruff' @@ -247,6 +380,153 @@ jobs: cat nim-output.txt continue-on-error: true + # Rust Linting + - name: Run cargo fmt and clippy + if: matrix.type == 'rust' + id: rust + run: | + RUST_OK=true + echo "Running cargo fmt --check..." + if cargo fmt -- --check > rust-output.txt 2>&1; then + echo "cargo fmt: no issues" + else + RUST_OK=false + echo "cargo fmt: formatting issues found" + fi + echo "Running cargo clippy..." + if cargo clippy --all-targets --all-features -- -D warnings >> rust-output.txt 2>&1; then + echo "cargo clippy: no issues" + else + RUST_OK=false + echo "cargo clippy: issues found" + fi + if [[ "$RUST_OK" == "true" ]]; then + echo "RUST_PASSED=true" >> $GITHUB_ENV + else + echo "RUST_PASSED=false" >> $GITHUB_ENV + fi + cat rust-output.txt + continue-on-error: true + + # Crystal Linting + - name: Run crystal format + ameba + compile check + if: matrix.type == 'crystal' + id: crystal + run: | + CRYSTAL_OK=true + echo "Running crystal tool format --check..." + if crystal tool format --check src/ spec/ > crystal-output.txt 2>&1; then + echo "crystal format: no issues" + else + CRYSTAL_OK=false + echo "crystal format: formatting issues found" + fi + echo "Running ameba..." + if bin/ameba src/ >> crystal-output.txt 2>&1; then + echo "ameba: no issues" + else + CRYSTAL_OK=false + echo "ameba: issues found" + fi + echo "Running crystal build --no-codegen..." + if crystal build --no-codegen src/cre.cr >> crystal-output.txt 2>&1; then + echo "crystal build: passed" + else + CRYSTAL_OK=false + echo "crystal build: failed" + fi + if [[ "$CRYSTAL_OK" == "true" ]]; then + echo "CRYSTAL_PASSED=true" >> $GITHUB_ENV + else + echo "CRYSTAL_PASSED=false" >> $GITHUB_ENV + fi + cat crystal-output.txt + continue-on-error: true + + # V Linting (v fmt is advisory — formatter rules drift between V builds even + # within the same version string, making strict fmt parity impractical. v vet + # is stable across builds and remains the pass/fail gate.) + - name: Run v fmt (advisory) and v vet + if: matrix.type == 'v' + id: v + run: | + V_OK=true + echo "Running v fmt -diff src/ (advisory)..." + v fmt -diff src/ > v-output.txt 2>&1 || true + echo "Running v fmt -verify src/ (advisory)..." + if v fmt -verify src/ >> v-output.txt 2>&1; then + echo "v fmt: no issues" + else + echo "v fmt: formatting suggestions found (advisory — not failing the job)" + fi + echo "Running v vet src/..." + if v vet src/ >> v-output.txt 2>&1; then + echo "v vet: no issues" + else + V_OK=false + echo "v vet: issues found" + fi + if [[ "$V_OK" == "true" ]]; then + echo "V_PASSED=true" >> $GITHUB_ENV + else + echo "V_PASSED=false" >> $GITHUB_ENV + fi + cat v-output.txt + continue-on-error: true + + # C++ Linting + - name: Run clang-format and cppcheck + if: matrix.type == 'cpp' + id: cpp + run: | + CPP_OK=true + echo "Running clang-format check..." + FILES=$(find . \( -path ./build -o -path ./cmake-build -o -path ./node_modules -o -path ./.git \) -prune -o \( -name '*.cpp' -o -name '*.hpp' -o -name '*.h' \) -print 2>/dev/null) + if [ -n "$FILES" ]; then + if echo "$FILES" | xargs clang-format --dry-run --Werror > cpp-output.txt 2>&1; then + echo "clang-format: no issues" + else + CPP_OK=false + echo "clang-format: formatting issues found" + fi + else + echo "clang-format: no C++ files found" > cpp-output.txt + fi + echo "Running cppcheck..." + if cppcheck --enable=warning,style,performance,portability --error-exitcode=1 --suppress=missingIncludeSystem --suppress=normalCheckLevelMaxBranches -i build -i cmake-build -q . >> cpp-output.txt 2>&1; then + echo "cppcheck: no issues" + else + CPP_OK=false + echo "cppcheck: issues found" + fi + if [[ "$CPP_OK" == "true" ]]; then + echo "CPP_PASSED=true" >> $GITHUB_ENV + else + echo "CPP_PASSED=false" >> $GITHUB_ENV + fi + cat cpp-output.txt + continue-on-error: true + + # Bash Linting + - name: Run shellcheck + if: matrix.type == 'bash' + id: bash + run: | + echo "Running shellcheck..." + FILES=$(find . -name '*.sh' -not -path '*/.venv/*' -not -path '*/node_modules/*' 2>/dev/null || true) + if [ -z "$FILES" ]; then + echo "shellcheck: no .sh files found" > bash-output.txt + echo "BASH_PASSED=true" >> $GITHUB_ENV + elif echo "$FILES" | xargs shellcheck > bash-output.txt 2>&1; then + echo "BASH_PASSED=true" >> $GITHUB_ENV + echo "shellcheck: no issues" + else + echo "BASH_PASSED=false" >> $GITHUB_ENV + echo "shellcheck: issues found" + fi + cat bash-output.txt + continue-on-error: true + # Create Summary for Ruff - name: Create Ruff Lint Summary if: matrix.type == 'ruff' @@ -371,6 +651,151 @@ jobs: fi } >> $GITHUB_STEP_SUMMARY + # Create Summary for Rust + - name: Create Rust Lint Summary + if: matrix.type == 'rust' + run: | + { + echo "## Lint Results: ${{ matrix.name }}" + echo '' + if [[ "${{ env.RUST_PASSED }}" == "true" ]]; then + echo '### cargo fmt + clippy: **Passed**' + echo 'No Rust issues found.' + else + echo '### cargo fmt + clippy: **Issues Found**' + echo '
View output' + echo '' + echo '```' + head -100 rust-output.txt + echo '```' + echo '
' + fi + echo '' + if [[ "${{ env.RUST_PASSED }}" == "true" ]]; then + echo '---' + echo '### All checks passed!' + else + echo '---' + echo '### Review the issues above' + fi + } >> $GITHUB_STEP_SUMMARY + + # Create Summary for Crystal + - name: Create Crystal Lint Summary + if: matrix.type == 'crystal' + run: | + { + echo "## Lint Results: ${{ matrix.name }}" + echo '' + if [[ "${{ env.CRYSTAL_PASSED }}" == "true" ]]; then + echo '### crystal format + ameba + build: **Passed**' + echo 'No Crystal issues found.' + else + echo '### crystal format + ameba + build: **Issues Found**' + echo '
View output' + echo '' + echo '```' + head -100 crystal-output.txt + echo '```' + echo '
' + fi + echo '' + if [[ "${{ env.CRYSTAL_PASSED }}" == "true" ]]; then + echo '---' + echo '### All checks passed!' + else + echo '---' + echo '### Review the issues above' + fi + } >> $GITHUB_STEP_SUMMARY + + # Create Summary for V + - name: Create V Lint Summary + if: matrix.type == 'v' + run: | + { + echo "## Lint Results: ${{ matrix.name }}" + echo '' + if [[ "${{ env.V_PASSED }}" == "true" ]]; then + echo '### v vet: **Passed** (v fmt advisory)' + echo 'No V vet issues found. See output for v fmt suggestions.' + else + echo '### v vet: **Issues Found**' + echo '
View output' + echo '' + echo '```' + head -100 v-output.txt + echo '```' + echo '
' + fi + echo '' + if [[ "${{ env.V_PASSED }}" == "true" ]]; then + echo '---' + echo '### All checks passed!' + else + echo '---' + echo '### Review the issues above' + fi + } >> $GITHUB_STEP_SUMMARY + + # Create Summary for C++ + - name: Create C++ Lint Summary + if: matrix.type == 'cpp' + run: | + { + echo "## Lint Results: ${{ matrix.name }}" + echo '' + if [[ "${{ env.CPP_PASSED }}" == "true" ]]; then + echo '### clang-format + cppcheck: **Passed**' + echo 'No C++ issues found.' + else + echo '### clang-format + cppcheck: **Issues Found**' + echo '
View output' + echo '' + echo '```' + head -100 cpp-output.txt + echo '```' + echo '
' + fi + echo '' + if [[ "${{ env.CPP_PASSED }}" == "true" ]]; then + echo '---' + echo '### All checks passed!' + else + echo '---' + echo '### Review the issues above' + fi + } >> $GITHUB_STEP_SUMMARY + + # Create Summary for Bash + - name: Create Bash Lint Summary + if: matrix.type == 'bash' + run: | + { + echo "## Lint Results: ${{ matrix.name }}" + echo '' + if [[ "${{ env.BASH_PASSED }}" == "true" ]]; then + echo '### shellcheck: **Passed**' + echo 'No shell issues found.' + else + echo '### shellcheck: **Issues Found**' + echo '
View output' + echo '' + echo '```' + head -100 bash-output.txt + echo '```' + echo '
' + fi + echo '' + if [[ "${{ env.BASH_PASSED }}" == "true" ]]; then + echo '---' + echo '### All checks passed!' + else + echo '---' + echo '### Review the issues above' + fi + } >> $GITHUB_STEP_SUMMARY + # Exit with proper status - name: Check lint status run: | @@ -394,5 +819,30 @@ jobs: echo "Nim lint checks failed" exit 1 fi + elif [[ "${{ matrix.type }}" == "rust" ]]; then + if [[ "${{ env.RUST_PASSED }}" == "false" ]]; then + echo "Rust lint checks failed" + exit 1 + fi + elif [[ "${{ matrix.type }}" == "crystal" ]]; then + if [[ "${{ env.CRYSTAL_PASSED }}" == "false" ]]; then + echo "Crystal lint checks failed" + exit 1 + fi + elif [[ "${{ matrix.type }}" == "v" ]]; then + if [[ "${{ env.V_PASSED }}" == "false" ]]; then + echo "V lint checks failed" + exit 1 + fi + elif [[ "${{ matrix.type }}" == "cpp" ]]; then + if [[ "${{ env.CPP_PASSED }}" == "false" ]]; then + echo "C++ lint checks failed" + exit 1 + fi + elif [[ "${{ matrix.type }}" == "bash" ]]; then + if [[ "${{ env.BASH_PASSED }}" == "false" ]]; then + echo "Bash lint checks failed" + exit 1 + fi fi echo "All lint checks passed" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 20efe34a..ec12e31e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -84,6 +84,37 @@ repos: files: ^PROJECTS/advanced/ai-threat-detection/backend/ exclude: (\.venv|__pycache__|\.pytest_cache)/ + - id: ruff + name: ruff check (dlp-scanner) + args: [--fix, --exit-non-zero-on-fix] + files: ^PROJECTS/intermediate/dlp-scanner/ + exclude: (\.venv|__pycache__|\.pytest_cache)/ + + - id: ruff + name: ruff check (linux-ebpf-security-tracer) + args: [--fix, --exit-non-zero-on-fix] + files: ^PROJECTS/beginner/linux-ebpf-security-tracer/ + exclude: (\.venv|__pycache__|\.pytest_cache)/ + + # Foundations projects + - id: ruff + name: ruff check (hash-identifier) + args: [--fix, --exit-non-zero-on-fix] + files: ^PROJECTS/foundations/hash-identifier/ + exclude: (\.venv|__pycache__|\.pytest_cache)/ + + - id: ruff + name: ruff check (http-headers-scanner) + args: [--fix, --exit-non-zero-on-fix] + files: ^PROJECTS/foundations/http-headers-scanner/ + exclude: (\.venv|__pycache__|\.pytest_cache)/ + + - id: ruff + name: ruff check (password-manager) + args: [--fix, --exit-non-zero-on-fix] + files: ^PROJECTS/foundations/password-manager/ + exclude: (\.venv|__pycache__|\.pytest_cache)/ + # Go golangci-lint Checks - repo: local @@ -112,6 +143,48 @@ repos: types: [go] pass_filenames: false + - id: golangci-lint-canary-token-generator + name: golangci-lint (canary-token-generator backend) + entry: bash -c 'cd PROJECTS/beginner/canary-token-generator/backend && golangci-lint run --fix' + language: system + files: ^PROJECTS/beginner/canary-token-generator/backend/ + types: [go] + pass_filenames: false + + - id: golangci-lint-systemd-persistence-scanner + name: golangci-lint (systemd-persistence-scanner) + entry: bash -c 'cd PROJECTS/beginner/systemd-persistence-scanner && golangci-lint run --fix' + language: system + files: ^PROJECTS/beginner/systemd-persistence-scanner/ + types: [go] + pass_filenames: false + + - id: golangci-lint-sbom-generator + name: golangci-lint (sbom-generator-vulnerability-matcher) + entry: bash -c 'cd PROJECTS/intermediate/sbom-generator-vulnerability-matcher && golangci-lint run --fix' + language: system + files: ^PROJECTS/intermediate/sbom-generator-vulnerability-matcher/ + exclude: ^PROJECTS/intermediate/sbom-generator-vulnerability-matcher/testdata/ + types: [go] + pass_filenames: false + + - id: golangci-lint-honeypot-network + name: golangci-lint (honeypot-network) + entry: bash -c 'cd PROJECTS/advanced/honeypot-network && golangci-lint run --fix' + language: system + files: ^PROJECTS/advanced/honeypot-network/ + exclude: ^PROJECTS/advanced/honeypot-network/frontend/ + types: [go] + pass_filenames: false + + - id: golangci-lint-monitor-the-situation-dashboard + name: golangci-lint (monitor-the-situation-dashboard backend) + entry: bash -c 'cd PROJECTS/advanced/monitor-the-situation-dashboard/backend && golangci-lint run --fix' + language: system + files: ^PROJECTS/advanced/monitor-the-situation-dashboard/backend/ + types: [go] + pass_filenames: false + # Biome Frontend Checks - repo: local hooks: @@ -150,6 +223,41 @@ repos: files: ^PROJECTS/advanced/encrypted-p2p-chat/frontend/src/ pass_filenames: false + - id: biome-canary-token-generator + name: biome check (canary-token-generator frontend) + entry: bash -c 'cd PROJECTS/beginner/canary-token-generator/frontend && npx @biomejs/biome check .' + language: system + files: ^PROJECTS/beginner/canary-token-generator/frontend/src/ + pass_filenames: false + + - id: biome-ai-threat-detection + name: biome check (ai-threat-detection frontend) + entry: bash -c 'cd PROJECTS/advanced/ai-threat-detection/frontend && npx @biomejs/biome check .' + language: system + files: ^PROJECTS/advanced/ai-threat-detection/frontend/src/ + pass_filenames: false + + - id: biome-binary-analysis-tool + name: biome check (binary-analysis-tool frontend) + entry: bash -c 'cd PROJECTS/intermediate/binary-analysis-tool/frontend && npx @biomejs/biome check .' + language: system + files: ^PROJECTS/intermediate/binary-analysis-tool/frontend/src/ + pass_filenames: false + + - id: biome-honeypot-network + name: biome check (honeypot-network frontend) + entry: bash -c 'cd PROJECTS/advanced/honeypot-network/frontend && npx @biomejs/biome check .' + language: system + files: ^PROJECTS/advanced/honeypot-network/frontend/src/ + pass_filenames: false + + - id: biome-monitor-the-situation-dashboard + name: biome check (monitor-the-situation-dashboard frontend) + entry: bash -c 'cd PROJECTS/advanced/monitor-the-situation-dashboard/frontend && npx @biomejs/biome check .' + language: system + files: ^PROJECTS/advanced/monitor-the-situation-dashboard/frontend/src/ + pass_filenames: false + # Nim nph Checks - repo: local hooks: @@ -160,6 +268,130 @@ repos: files: ^PROJECTS/intermediate/credential-enumeration/src/ pass_filenames: false + # Rust Checks (cargo fmt + clippy) + - repo: local + hooks: + - id: cargo-fmt-binary-analysis-tool + name: cargo fmt (binary-analysis-tool backend) + entry: bash -c 'cd PROJECTS/intermediate/binary-analysis-tool/backend && cargo fmt -- --check' + language: system + files: ^PROJECTS/intermediate/binary-analysis-tool/backend/ + types: [rust] + pass_filenames: false + + - id: cargo-clippy-binary-analysis-tool + name: cargo clippy (binary-analysis-tool backend) + entry: bash -c 'cd PROJECTS/intermediate/binary-analysis-tool/backend && cargo clippy --all-targets --all-features -- -D warnings' + language: system + files: ^PROJECTS/intermediate/binary-analysis-tool/backend/ + types: [rust] + pass_filenames: false + + # Crystal Checks (format + ameba + compile) + - repo: local + hooks: + - id: crystal-format-credential-rotation-enforcer + name: crystal format check (credential-rotation-enforcer) + entry: bash -c 'cd PROJECTS/intermediate/credential-rotation-enforcer && crystal tool format --check src/ spec/' + language: system + files: ^PROJECTS/intermediate/credential-rotation-enforcer/ + types_or: [crystal] + pass_filenames: false + + - id: ameba-credential-rotation-enforcer + name: ameba (credential-rotation-enforcer) + entry: bash -c 'cd PROJECTS/intermediate/credential-rotation-enforcer && bin/ameba src/' + language: system + files: ^PROJECTS/intermediate/credential-rotation-enforcer/ + types_or: [crystal] + pass_filenames: false + + - id: crystal-build-check-credential-rotation-enforcer + name: crystal compile check (credential-rotation-enforcer) + entry: bash -c 'cd PROJECTS/intermediate/credential-rotation-enforcer && crystal build --no-codegen src/cre.cr' + language: system + files: ^PROJECTS/intermediate/credential-rotation-enforcer/ + types_or: [crystal] + pass_filenames: false + + # V (Vlang) Checks + - repo: local + hooks: + - id: v-fmt-firewall-rule-engine + name: v fmt (firewall-rule-engine) + entry: bash -c 'cd PROJECTS/beginner/firewall-rule-engine && v fmt -verify src/' + language: system + files: ^PROJECTS/beginner/firewall-rule-engine/src/ + pass_filenames: false + + - id: v-vet-firewall-rule-engine + name: v vet (firewall-rule-engine) + entry: bash -c 'cd PROJECTS/beginner/firewall-rule-engine && v vet src/' + language: system + files: ^PROJECTS/beginner/firewall-rule-engine/src/ + pass_filenames: false + + # C++ Checks (clang-format + cppcheck) + - repo: local + hooks: + - id: clang-format-hash-cracker + name: clang-format (hash-cracker) + entry: bash -c 'cd PROJECTS/beginner/hash-cracker && find . \( -path ./build -o -path ./cmake-build -o -path ./.git \) -prune -o \( -name "*.cpp" -o -name "*.hpp" -o -name "*.h" \) -print 2>/dev/null | xargs -r clang-format --dry-run --Werror' + language: system + files: ^PROJECTS/beginner/hash-cracker/ + types_or: [c++, c] + pass_filenames: false + + - id: cppcheck-hash-cracker + name: cppcheck (hash-cracker) + entry: bash -c 'cd PROJECTS/beginner/hash-cracker && cppcheck --enable=warning,style,performance,portability --error-exitcode=1 --suppress=missingIncludeSystem --suppress=normalCheckLevelMaxBranches -i build -i cmake-build -q .' + language: system + files: ^PROJECTS/beginner/hash-cracker/ + types_or: [c++, c] + pass_filenames: false + + - id: clang-format-simple-port-scanner + name: clang-format (simple-port-scanner) + entry: bash -c 'cd PROJECTS/beginner/simple-port-scanner && find . \( -path ./build -o -path ./cmake-build -o -path ./.git \) -prune -o \( -name "*.cpp" -o -name "*.hpp" -o -name "*.h" \) -print 2>/dev/null | xargs -r clang-format --dry-run --Werror' + language: system + files: ^PROJECTS/beginner/simple-port-scanner/ + types_or: [c++, c] + pass_filenames: false + + - id: cppcheck-simple-port-scanner + name: cppcheck (simple-port-scanner) + entry: bash -c 'cd PROJECTS/beginner/simple-port-scanner && cppcheck --enable=warning,style,performance,portability --error-exitcode=1 --suppress=missingIncludeSystem --suppress=normalCheckLevelMaxBranches -i build -i cmake-build -q .' + language: system + files: ^PROJECTS/beginner/simple-port-scanner/ + types_or: [c++, c] + pass_filenames: false + + - id: clang-format-network-traffic-analyzer-cpp + name: clang-format (network-traffic-analyzer/cpp) + entry: bash -c 'cd PROJECTS/beginner/network-traffic-analyzer/cpp && find . \( -path ./build -o -path ./cmake-build -o -path ./.git \) -prune -o \( -name "*.cpp" -o -name "*.hpp" -o -name "*.h" \) -print 2>/dev/null | xargs -r clang-format --dry-run --Werror' + language: system + files: ^PROJECTS/beginner/network-traffic-analyzer/cpp/ + types_or: [c++, c] + pass_filenames: false + + - id: cppcheck-network-traffic-analyzer-cpp + name: cppcheck (network-traffic-analyzer/cpp) + entry: bash -c 'cd PROJECTS/beginner/network-traffic-analyzer/cpp && cppcheck --enable=warning,style,performance,portability --error-exitcode=1 --suppress=missingIncludeSystem --suppress=normalCheckLevelMaxBranches -i build -i cmake-build -q .' + language: system + files: ^PROJECTS/beginner/network-traffic-analyzer/cpp/ + types_or: [c++, c] + pass_filenames: false + + # Bash (shellcheck) + - repo: local + hooks: + - id: shellcheck-linux-cis-hardening-auditor + name: shellcheck (linux-cis-hardening-auditor) + entry: bash -c 'find PROJECTS/beginner/linux-cis-hardening-auditor -name "*.sh" -not -path "*/.venv/*" | xargs -r shellcheck' + language: system + files: ^PROJECTS/beginner/linux-cis-hardening-auditor/.*\.sh$ + pass_filenames: false + - repo: https://github.com/pre-commit/pre-commit-hooks rev: v6.0.0 hooks: diff --git a/PROJECTS/advanced/honeypot-network/frontend/src/components/event-feed.tsx b/PROJECTS/advanced/honeypot-network/frontend/src/components/event-feed.tsx index c7799aaf..cacfc6af 100644 --- a/PROJECTS/advanced/honeypot-network/frontend/src/components/event-feed.tsx +++ b/PROJECTS/advanced/honeypot-network/frontend/src/components/event-feed.tsx @@ -2,8 +2,8 @@ // event-feed.tsx import { useWebSocketStore } from '@/core/lib/websocket.store' -import { ServiceBadge } from './service-badge' import styles from './event-feed.module.scss' +import { ServiceBadge } from './service-badge' const MAX_VISIBLE = 15 diff --git a/PROJECTS/advanced/honeypot-network/frontend/src/components/service-badge.tsx b/PROJECTS/advanced/honeypot-network/frontend/src/components/service-badge.tsx index 04d20088..72bba415 100644 --- a/PROJECTS/advanced/honeypot-network/frontend/src/components/service-badge.tsx +++ b/PROJECTS/advanced/honeypot-network/frontend/src/components/service-badge.tsx @@ -9,9 +9,5 @@ interface ServiceBadgeProps { } export function ServiceBadge({ service }: ServiceBadgeProps) { - return ( - - {service.toUpperCase()} - - ) + return {service.toUpperCase()} } diff --git a/PROJECTS/advanced/honeypot-network/frontend/src/core/app/shell.tsx b/PROJECTS/advanced/honeypot-network/frontend/src/core/app/shell.tsx index c2fbefca..4e976a1e 100644 --- a/PROJECTS/advanced/honeypot-network/frontend/src/core/app/shell.tsx +++ b/PROJECTS/advanced/honeypot-network/frontend/src/core/app/shell.tsx @@ -52,9 +52,7 @@ export function Shell() {
LINK - + {connected ? 'ACTIVE' : 'DOWN'}
diff --git a/PROJECTS/advanced/honeypot-network/frontend/src/pages/attackers/detail.tsx b/PROJECTS/advanced/honeypot-network/frontend/src/pages/attackers/detail.tsx index 904c99e2..32c1d76d 100644 --- a/PROJECTS/advanced/honeypot-network/frontend/src/pages/attackers/detail.tsx +++ b/PROJECTS/advanced/honeypot-network/frontend/src/pages/attackers/detail.tsx @@ -57,9 +57,7 @@ export function AttackerDetailPage() {
THREAT SCORE - + {attacker.threat_score}
diff --git a/PROJECTS/advanced/honeypot-network/frontend/src/pages/attackers/index.tsx b/PROJECTS/advanced/honeypot-network/frontend/src/pages/attackers/index.tsx index b5823ea3..69cdacee 100644 --- a/PROJECTS/advanced/honeypot-network/frontend/src/pages/attackers/index.tsx +++ b/PROJECTS/advanced/honeypot-network/frontend/src/pages/attackers/index.tsx @@ -34,10 +34,7 @@ export function AttackersPage() { {attackers?.map((a) => ( - + {a.ip} diff --git a/PROJECTS/advanced/honeypot-network/frontend/src/pages/dashboard/index.tsx b/PROJECTS/advanced/honeypot-network/frontend/src/pages/dashboard/index.tsx index 772921c2..67188e5b 100644 --- a/PROJECTS/advanced/honeypot-network/frontend/src/pages/dashboard/index.tsx +++ b/PROJECTS/advanced/honeypot-network/frontend/src/pages/dashboard/index.tsx @@ -113,11 +113,7 @@ export function DashboardPage() { axisLine={false} /> - + @@ -143,28 +139,24 @@ export function DashboardPage() {

Top Usernames

- {credentials.top_usernames - .slice(0, TOP_CREDS_LIMIT) - .map((u) => ( -
- {u.value} - {u.count} -
- ))} + {credentials.top_usernames.slice(0, TOP_CREDS_LIMIT).map((u) => ( +
+ {u.value} + {u.count} +
+ ))}

Top Passwords

- {credentials.top_passwords - .slice(0, TOP_CREDS_LIMIT) - .map((p) => ( -
- {p.value} - {p.count} -
- ))} + {credentials.top_passwords.slice(0, TOP_CREDS_LIMIT).map((p) => ( +
+ {p.value} + {p.count} +
+ ))}
diff --git a/PROJECTS/advanced/honeypot-network/frontend/src/pages/events/index.tsx b/PROJECTS/advanced/honeypot-network/frontend/src/pages/events/index.tsx index 4d15a83a..c752a4b0 100644 --- a/PROJECTS/advanced/honeypot-network/frontend/src/pages/events/index.tsx +++ b/PROJECTS/advanced/honeypot-network/frontend/src/pages/events/index.tsx @@ -10,10 +10,7 @@ const PAGE_SIZE = 50 export function EventsPage() { const [ipFilter, setIpFilter] = useState('') - const { data: events, isLoading } = useEvents( - PAGE_SIZE, - ipFilter || undefined - ) + const { data: events, isLoading } = useEvents(PAGE_SIZE, ipFilter || undefined) return (
diff --git a/PROJECTS/advanced/honeypot-network/frontend/src/pages/intel/index.tsx b/PROJECTS/advanced/honeypot-network/frontend/src/pages/intel/index.tsx index 98b73645..23d2ec21 100644 --- a/PROJECTS/advanced/honeypot-network/frontend/src/pages/intel/index.tsx +++ b/PROJECTS/advanced/honeypot-network/frontend/src/pages/intel/index.tsx @@ -27,24 +27,16 @@ export function IntelPage() { const res = await apiClient.get(API_ENDPOINTS.IOCS.EXPORT_STIX, { responseType: 'text', }) - downloadBlob( - res.data as string, - 'hive-iocs.stix.json', - 'application/json' - ) + downloadBlob(res.data as string, 'hive-iocs.stix.json', 'application/json') } async function exportBlocklist(format: string) { - const res = await apiClient.get( - API_ENDPOINTS.IOCS.EXPORT_BLOCKLIST, - { params: { format }, responseType: 'text' } - ) + const res = await apiClient.get(API_ENDPOINTS.IOCS.EXPORT_BLOCKLIST, { + params: { format }, + responseType: 'text', + }) const ext = format === 'csv' ? '.csv' : '.txt' - downloadBlob( - res.data as string, - `hive-blocklist${ext}`, - 'text/plain' - ) + downloadBlob(res.data as string, `hive-blocklist${ext}`, 'text/plain') } return ( @@ -52,18 +44,12 @@ export function IntelPage() {

Intel

- - THREAT INTELLIGENCE PRODUCTS - + THREAT INTELLIGENCE PRODUCTS
EXPORT - {BLOCKLIST_FORMATS.map((fmt) => ( @@ -103,12 +89,8 @@ export function IntelPage() { {ioc.confidence}% {ioc.sight_count} {ioc.source} - - {new Date(ioc.first_seen).toLocaleDateString()} - - - {new Date(ioc.last_seen).toLocaleDateString()} - + {new Date(ioc.first_seen).toLocaleDateString()} + {new Date(ioc.last_seen).toLocaleDateString()} ))} @@ -120,25 +102,20 @@ export function IntelPage() { className={styles.pageBtn} disabled={offset === 0} onClick={() => - setOffset( - Math.max(0, offset - PAGINATION.DEFAULT_LIMIT) - ) + setOffset(Math.max(0, offset - PAGINATION.DEFAULT_LIMIT)) } > ◀ PREV {offset + 1}– - {Math.min(offset + PAGINATION.DEFAULT_LIMIT, total)} OF{' '} - {total} + {Math.min(offset + PAGINATION.DEFAULT_LIMIT, total)} OF {total} diff --git a/PROJECTS/advanced/honeypot-network/frontend/src/pages/mitre/index.tsx b/PROJECTS/advanced/honeypot-network/frontend/src/pages/mitre/index.tsx index 86e7b81e..ab8768dc 100644 --- a/PROJECTS/advanced/honeypot-network/frontend/src/pages/mitre/index.tsx +++ b/PROJECTS/advanced/honeypot-network/frontend/src/pages/mitre/index.tsx @@ -29,9 +29,7 @@ export function MitrePage() { const { data: techniques } = useMitreTechniques() const { data: heatmap } = useMitreHeatmap() - const countMap = new Map( - heatmap?.map((h) => [h.technique_id, h.count]) - ) + const countMap = new Map(heatmap?.map((h) => [h.technique_id, h.count])) const byTactic = new Map() for (const t of techniques ?? []) { @@ -51,9 +49,7 @@ export function MitrePage() { INTENSITY {HEAT_THRESHOLDS.map((t, i) => ( - + {t}+ ))} @@ -66,9 +62,7 @@ export function MitrePage() { return (
-

- {tactic.replace(/-/g, ' ')} -

+

{tactic.replace(/-/g, ' ')}

{techs.map((t) => { const count = countMap.get(t.id) ?? 0 @@ -83,9 +77,7 @@ export function MitrePage() { {t.id} {t.name} {count > 0 && ( - - {count} - + {count} )}
) diff --git a/PROJECTS/advanced/honeypot-network/frontend/src/pages/sessions/detail.tsx b/PROJECTS/advanced/honeypot-network/frontend/src/pages/sessions/detail.tsx index 8ddb14a6..3c2c541b 100644 --- a/PROJECTS/advanced/honeypot-network/frontend/src/pages/sessions/detail.tsx +++ b/PROJECTS/advanced/honeypot-network/frontend/src/pages/sessions/detail.tsx @@ -44,9 +44,7 @@ export function SessionDetailPage() { ← SESSIONS -

- SESSION {session.id.slice(0, 8)} -

+

SESSION {session.id.slice(0, 8)}

{rows.map((row) => ( diff --git a/PROJECTS/advanced/honeypot-network/frontend/src/pages/sessions/index.tsx b/PROJECTS/advanced/honeypot-network/frontend/src/pages/sessions/index.tsx index cc30599c..9d1829f0 100644 --- a/PROJECTS/advanced/honeypot-network/frontend/src/pages/sessions/index.tsx +++ b/PROJECTS/advanced/honeypot-network/frontend/src/pages/sessions/index.tsx @@ -52,10 +52,7 @@ export function SessionsPage() { return ( - + {s.id.slice(0, 8)} @@ -67,9 +64,7 @@ export function SessionsPage() { {s.command_count} {s.threat_score} {new Date(s.started_at).toLocaleString()} - - {duration !== null ? `${duration}s` : 'active'} - + {duration !== null ? `${duration}s` : 'active'} ) })} @@ -81,15 +76,12 @@ export function SessionsPage() { type="button" className={styles.pageBtn} disabled={offset === 0} - onClick={() => - setOffset(Math.max(0, offset - PAGE_SIZE)) - } + onClick={() => setOffset(Math.max(0, offset - PAGE_SIZE))} > ◀ PREV - {offset + 1}–{Math.min(offset + PAGE_SIZE, total)}{' '} - OF {total} + {offset + 1}–{Math.min(offset + PAGE_SIZE, total)} OF {total}
diff --git a/PROJECTS/intermediate/credential-rotation-enforcer/.ameba.yml b/PROJECTS/intermediate/credential-rotation-enforcer/.ameba.yml new file mode 100644 index 00000000..d8a14688 --- /dev/null +++ b/PROJECTS/intermediate/credential-rotation-enforcer/.ameba.yml @@ -0,0 +1,16 @@ +# ©AngelaMos | 2026 +# .ameba.yml +# +# Disabled rules document intentional patterns in this codebase +# - Naming/BlockParameterName: idiomatic Crystal uses short block params like |c|, |b| +# - Lint/NotNil: .not_nil! used as explicit runtime contract assertion after upstream +# validation; raising NilAssertionError is the desired crash mode for misconfiguration +# Raised thresholds match the natural shape of dispatch/subscriber handlers +Naming/BlockParameterName: + Enabled: false + +Lint/NotNil: + Enabled: false + +Metrics/CyclomaticComplexity: + MaxComplexity: 20 diff --git a/PROJECTS/intermediate/credential-rotation-enforcer/shard.lock b/PROJECTS/intermediate/credential-rotation-enforcer/shard.lock index 4da0ce31..5c86ac70 100644 --- a/PROJECTS/intermediate/credential-rotation-enforcer/shard.lock +++ b/PROJECTS/intermediate/credential-rotation-enforcer/shard.lock @@ -1,5 +1,9 @@ version: 2.0 shards: + ameba: + git: https://github.com/crystal-ameba/ameba.git + version: 1.6.4 + db: git: https://github.com/crystal-lang/crystal-db.git version: 0.11.0 diff --git a/PROJECTS/intermediate/credential-rotation-enforcer/shard.yml b/PROJECTS/intermediate/credential-rotation-enforcer/shard.yml index 0afcc30e..56f75bbc 100644 --- a/PROJECTS/intermediate/credential-rotation-enforcer/shard.yml +++ b/PROJECTS/intermediate/credential-rotation-enforcer/shard.yml @@ -33,3 +33,6 @@ development_dependencies: webmock: github: manastech/webmock.cr version: ~> 0.9 + ameba: + github: crystal-ameba/ameba + version: ~> 1.6.4 diff --git a/PROJECTS/intermediate/credential-rotation-enforcer/src/cre/aws/signer.cr b/PROJECTS/intermediate/credential-rotation-enforcer/src/cre/aws/signer.cr index 5e4c8bdd..8d17acd8 100644 --- a/PROJECTS/intermediate/credential-rotation-enforcer/src/cre/aws/signer.cr +++ b/PROJECTS/intermediate/credential-rotation-enforcer/src/cre/aws/signer.cr @@ -79,7 +79,7 @@ module CRE::Aws end private def canonical_query(query : String?) : String - return "" unless query && !query.empty? + return "" if query.nil? || query.empty? params = [] of {String, String} query.split('&') do |pair| eq = pair.index('=') @@ -99,7 +99,7 @@ module CRE::Aws private def canonical_headers_and_list(headers : HTTP::Headers) : {String, String} sorted = headers.to_a.map { |name, values| {name.downcase, values.first.strip.gsub(/\s+/, " ")} - }.sort_by { |entry| entry[0] } + }.sort_by! { |entry| entry[0] } canonical = sorted.map { |k, v| "#{k}:#{v}\n" }.join list = sorted.map(&.[0]).join(';') diff --git a/PROJECTS/intermediate/credential-rotation-enforcer/src/cre/cli/commands/check.cr b/PROJECTS/intermediate/credential-rotation-enforcer/src/cre/cli/commands/check.cr index 2dc78c15..c8078f6b 100644 --- a/PROJECTS/intermediate/credential-rotation-enforcer/src/cre/cli/commands/check.cr +++ b/PROJECTS/intermediate/credential-rotation-enforcer/src/cre/cli/commands/check.cr @@ -32,7 +32,7 @@ module CRE::Cli::Commands CRE::Policy::Evaluator.new(bus, persist).evaluate_all sleep 0.1.seconds - violations = drain(ch).select(&.is_a?(CRE::Events::PolicyViolation)).map(&.as(CRE::Events::PolicyViolation)) + violations = drain(ch).select(CRE::Events::PolicyViolation).map(&.as(CRE::Events::PolicyViolation)) bus.stop persist.close diff --git a/PROJECTS/intermediate/credential-rotation-enforcer/src/cre/demo/tui_demo.cr b/PROJECTS/intermediate/credential-rotation-enforcer/src/cre/demo/tui_demo.cr index dc0d0212..77b46b80 100644 --- a/PROJECTS/intermediate/credential-rotation-enforcer/src/cre/demo/tui_demo.cr +++ b/PROJECTS/intermediate/credential-rotation-enforcer/src/cre/demo/tui_demo.cr @@ -19,7 +19,7 @@ module CRE::Demo bus.run tui.start - script_fiber = spawn do + spawn do run_script(bus, seconds) end diff --git a/PROJECTS/intermediate/credential-rotation-enforcer/src/cre/engine/rotation_orchestrator.cr b/PROJECTS/intermediate/credential-rotation-enforcer/src/cre/engine/rotation_orchestrator.cr index 9fa682d9..bc489b9c 100644 --- a/PROJECTS/intermediate/credential-rotation-enforcer/src/cre/engine/rotation_orchestrator.cr +++ b/PROJECTS/intermediate/credential-rotation-enforcer/src/cre/engine/rotation_orchestrator.cr @@ -59,7 +59,7 @@ module CRE::Engine @persistence.rotations.update_state(rotation_id, Persistence::RotationState::Applying) @bus.publish Events::RotationStepCompleted.new(c.id, rotation_id, :generate) - current_step = :apply + current_step = :apply # ameba:disable Lint/UselessAssign — read in rescue @bus.publish Events::RotationStepStarted.new(c.id, rotation_id, :apply) rotator.apply(c, new_secret) @persistence.rotations.update_state(rotation_id, Persistence::RotationState::Verifying) @@ -83,8 +83,8 @@ module CRE::Engine if (ns = new_secret) && (current_step == :apply || current_step == :verify) begin rotator.rollback_apply(c, ns) - rescue rb - Log.error(exception: rb) { "rollback_apply failed for credential #{c.id}" } + rescue error + Log.error(exception: error) { "rollback_apply failed for credential #{c.id}" } end end finalize_failure(c, rotation_id, current_step, ex) diff --git a/PROJECTS/intermediate/credential-rotation-enforcer/src/cre/persistence/postgres/postgres_persistence.cr b/PROJECTS/intermediate/credential-rotation-enforcer/src/cre/persistence/postgres/postgres_persistence.cr index 661dc7a9..213adbe3 100644 --- a/PROJECTS/intermediate/credential-rotation-enforcer/src/cre/persistence/postgres/postgres_persistence.cr +++ b/PROJECTS/intermediate/credential-rotation-enforcer/src/cre/persistence/postgres/postgres_persistence.cr @@ -40,11 +40,11 @@ module CRE::Persistence::Postgres @audit ||= AuditRepo.new(@db) end - def transaction(&block : ->) : Nil + def transaction(& : ->) : Nil @db.transaction { yield } end - def with_advisory_lock(key : Int64, &block : ->) : Nil + def with_advisory_lock(key : Int64, & : ->) : Nil @db.transaction do |tx| tx.connection.exec("SELECT pg_advisory_xact_lock($1)", key) yield diff --git a/PROJECTS/intermediate/credential-rotation-enforcer/src/cre/persistence/sqlite/sqlite_persistence.cr b/PROJECTS/intermediate/credential-rotation-enforcer/src/cre/persistence/sqlite/sqlite_persistence.cr index 58c61c69..076459b1 100644 --- a/PROJECTS/intermediate/credential-rotation-enforcer/src/cre/persistence/sqlite/sqlite_persistence.cr +++ b/PROJECTS/intermediate/credential-rotation-enforcer/src/cre/persistence/sqlite/sqlite_persistence.cr @@ -48,11 +48,11 @@ module CRE::Persistence::Sqlite @audit ||= AuditRepo.new(@db) end - def transaction(&block : ->) : Nil + def transaction(& : ->) : Nil @db.transaction { yield } end - def with_advisory_lock(key : Int64, &block : ->) : Nil + def with_advisory_lock(key : Int64, & : ->) : Nil _ = key # SQLite is single-process; the mutex is sufficient @mutex.synchronize { yield } end diff --git a/PROJECTS/intermediate/credential-rotation-enforcer/src/cre/policy/dsl.cr b/PROJECTS/intermediate/credential-rotation-enforcer/src/cre/policy/dsl.cr index ff7c4b71..47adc99e 100644 --- a/PROJECTS/intermediate/credential-rotation-enforcer/src/cre/policy/dsl.cr +++ b/PROJECTS/intermediate/credential-rotation-enforcer/src/cre/policy/dsl.cr @@ -25,7 +25,7 @@ module CRE::Policy::Dsl # `enforce :rotate_immediatly` fail at compile time. Splat-symbol args # (`notify_via :telegram, :structured_log`) are validated at policy # registration time and raise `BuilderError` on typos. - def policy(name : String, &block) + def policy(name : String, &) builder = CRE::Policy::Builder.new(name) with builder yield CRE::Policy::REGISTRY << builder.build diff --git a/PROJECTS/intermediate/credential-rotation-enforcer/src/cre/policy/evaluator.cr b/PROJECTS/intermediate/credential-rotation-enforcer/src/cre/policy/evaluator.cr index 11cc25cc..1d4d2d43 100644 --- a/PROJECTS/intermediate/credential-rotation-enforcer/src/cre/policy/evaluator.cr +++ b/PROJECTS/intermediate/credential-rotation-enforcer/src/cre/policy/evaluator.cr @@ -54,7 +54,7 @@ module CRE::Policy return if @policies.empty? seen = Set(UUID).new - @policies.group_by(&.max_age).each do |max_age, policies_with_age| + @policies.group_by(&.max_age).each do |max_age, _| @persistence.credentials.overdue(now, max_age).each do |c| next if seen.includes?(c.id) seen << c.id diff --git a/PROJECTS/intermediate/credential-rotation-enforcer/src/cre/rotators/env_file.cr b/PROJECTS/intermediate/credential-rotation-enforcer/src/cre/rotators/env_file.cr index e33a015a..8ec6ee3b 100644 --- a/PROJECTS/intermediate/credential-rotation-enforcer/src/cre/rotators/env_file.cr +++ b/PROJECTS/intermediate/credential-rotation-enforcer/src/cre/rotators/env_file.cr @@ -50,7 +50,7 @@ module CRE::Rotators pending = pending_path(path) existing = File.exists?(path) ? File.read(path) : "" - lines = existing.lines(chomp: true).reject { |line| line.strip.starts_with?("#{key}=") } + lines = existing.lines(chomp: true).reject(&.strip.starts_with?("#{key}=")) new_value = String.new(s.ciphertext) lines << "#{key}=#{new_value}" diff --git a/PROJECTS/intermediate/sbom-generator-vulnerability-matcher/.golangci.yml b/PROJECTS/intermediate/sbom-generator-vulnerability-matcher/.golangci.yml index 794b0097..c438ea7c 100644 --- a/PROJECTS/intermediate/sbom-generator-vulnerability-matcher/.golangci.yml +++ b/PROJECTS/intermediate/sbom-generator-vulnerability-matcher/.golangci.yml @@ -1,31 +1,39 @@ # ©AngelaMos | 2026 # .golangci.yml +version: "2" run: timeout: 5m linters: + default: none enable: - errcheck - govet - staticcheck - unused - - gosimple - ineffassign - - typecheck - - gofmt - gocritic - gosec - misspell - unconvert - unparam - prealloc - -linters-settings: - gocritic: - enabled-tags: - - diagnostic - - performance - gosec: - excludes: - - G304 + settings: + errcheck: + exclude-functions: + - fmt.Fprint + - fmt.Fprintf + - fmt.Fprintln + - (io.Closer).Close + gocritic: + enabled-tags: + - diagnostic + - performance + disabled-checks: + - hugeParam + - rangeValCopy + gosec: + excludes: + - G304 + - G704 diff --git a/PROJECTS/intermediate/sbom-generator-vulnerability-matcher/internal/cli/generate.go b/PROJECTS/intermediate/sbom-generator-vulnerability-matcher/internal/cli/generate.go index e6b54e5d..8d64f35a 100644 --- a/PROJECTS/intermediate/sbom-generator-vulnerability-matcher/internal/cli/generate.go +++ b/PROJECTS/intermediate/sbom-generator-vulnerability-matcher/internal/cli/generate.go @@ -73,7 +73,7 @@ func runGenerate(_ *cobra.Command, args []string) error { } if outputFile != "" { - if err := os.WriteFile(outputFile, data, 0o644); err != nil { + if err := os.WriteFile(outputFile, data, 0o600); err != nil { return fmt.Errorf("write output: %w", err) } fmt.Fprintf(os.Stderr, " %s SBOM written to %s (%s)\n", diff --git a/PROJECTS/intermediate/sbom-generator-vulnerability-matcher/internal/cli/vuln.go b/PROJECTS/intermediate/sbom-generator-vulnerability-matcher/internal/cli/vuln.go index b9490a78..ad00ebc3 100644 --- a/PROJECTS/intermediate/sbom-generator-vulnerability-matcher/internal/cli/vuln.go +++ b/PROJECTS/intermediate/sbom-generator-vulnerability-matcher/internal/cli/vuln.go @@ -75,7 +75,7 @@ func runVuln(cmd *cobra.Command, args []string) error { } func queryVulns(ctx context.Context, result *types.ScanResult) (*types.VulnReport, error) { - var allPkgs []types.Package + allPkgs := make([]types.Package, 0, len(result.Graphs)) for _, g := range result.Graphs { allPkgs = append(allPkgs, graph.AllPackages(g)...) } @@ -85,7 +85,7 @@ func queryVulns(ctx context.Context, result *types.ScanResult) (*types.VulnRepor c, err := vuln.NewCache(vuln.DefaultCachePath(), 24*time.Hour) if err == nil { cache = c - defer cache.Close() + defer func() { _ = cache.Close() }() } } diff --git a/PROJECTS/intermediate/sbom-generator-vulnerability-matcher/internal/parser/gomod.go b/PROJECTS/intermediate/sbom-generator-vulnerability-matcher/internal/parser/gomod.go index 584a7647..f9fcb3b4 100644 --- a/PROJECTS/intermediate/sbom-generator-vulnerability-matcher/internal/parser/gomod.go +++ b/PROJECTS/intermediate/sbom-generator-vulnerability-matcher/internal/parser/gomod.go @@ -38,7 +38,7 @@ func (p *GoModParser) Parse(dir string) (*types.DependencyGraph, error) { if err != nil { return nil, fmt.Errorf("open go.mod: %w", err) } - defer modFile.Close() + defer func() { _ = modFile.Close() }() var moduleName string directDeps := make(map[string]string) @@ -120,13 +120,13 @@ func (p *GoModParser) Parse(dir string) (*types.DependencyGraph, error) { graph.AddPackage(pkg) } - parseGoModGraph(dir, moduleName, graph) + parseGoModGraph(dir, graph) computeDepthLevels(graph) return graph, nil } -func parseGoModGraph(dir, moduleName string, graph *types.DependencyGraph) { +func parseGoModGraph(dir string, graph *types.DependencyGraph) { cmd := exec.Command("go", "mod", "graph") cmd.Dir = dir out, err := cmd.Output() @@ -140,8 +140,8 @@ func parseGoModGraph(dir, moduleName string, graph *types.DependencyGraph) { if len(parts) != 2 { continue } - parentPURL := goDepToPURL(parts[0], moduleName) - childPURL := goDepToPURL(parts[1], moduleName) + parentPURL := goDepToPURL(parts[0]) + childPURL := goDepToPURL(parts[1]) if _, exists := graph.Nodes[childPURL]; !exists { continue @@ -154,7 +154,7 @@ func parseGoModGraph(dir, moduleName string, graph *types.DependencyGraph) { } } -func goDepToPURL(dep, moduleName string) string { +func goDepToPURL(dep string) string { if !strings.Contains(dep, "@") { return fmt.Sprintf("pkg:golang/%s", dep) } @@ -195,7 +195,7 @@ func parseGoSum(path string) map[string][]types.Checksum { if err != nil { return checksums } - defer f.Close() + defer func() { _ = f.Close() }() scanner := bufio.NewScanner(f) for scanner.Scan() { diff --git a/PROJECTS/intermediate/sbom-generator-vulnerability-matcher/internal/vuln/cache.go b/PROJECTS/intermediate/sbom-generator-vulnerability-matcher/internal/vuln/cache.go index 6a38a8b6..5e082772 100644 --- a/PROJECTS/intermediate/sbom-generator-vulnerability-matcher/internal/vuln/cache.go +++ b/PROJECTS/intermediate/sbom-generator-vulnerability-matcher/internal/vuln/cache.go @@ -23,7 +23,7 @@ type Cache struct { func NewCache(dbPath string, ttl time.Duration) (*Cache, error) { dir := filepath.Dir(dbPath) - if err := os.MkdirAll(dir, 0o755); err != nil { + if err := os.MkdirAll(dir, 0o750); err != nil { return nil, fmt.Errorf("create cache dir: %w", err) } @@ -40,7 +40,7 @@ func NewCache(dbPath string, ttl time.Duration) (*Cache, error) { PRIMARY KEY (purl, source) )` if _, err := db.Exec(createSQL); err != nil { - db.Close() + _ = db.Close() return nil, fmt.Errorf("create cache table: %w", err) } diff --git a/PROJECTS/intermediate/sbom-generator-vulnerability-matcher/internal/vuln/cache_test.go b/PROJECTS/intermediate/sbom-generator-vulnerability-matcher/internal/vuln/cache_test.go index 154e298a..fb87ba07 100644 --- a/PROJECTS/intermediate/sbom-generator-vulnerability-matcher/internal/vuln/cache_test.go +++ b/PROJECTS/intermediate/sbom-generator-vulnerability-matcher/internal/vuln/cache_test.go @@ -17,7 +17,7 @@ func TestCachePutGet(t *testing.T) { dbPath := filepath.Join(t.TempDir(), "test.db") cache, err := NewCache(dbPath, 24*time.Hour) require.NoError(t, err) - defer cache.Close() + defer func() { _ = cache.Close() }() matches := []types.VulnMatch{ { @@ -39,7 +39,7 @@ func TestCacheExpiry(t *testing.T) { dbPath := filepath.Join(t.TempDir(), "test.db") cache, err := NewCache(dbPath, 1*time.Millisecond) require.NoError(t, err) - defer cache.Close() + defer func() { _ = cache.Close() }() matches := []types.VulnMatch{ { @@ -60,7 +60,7 @@ func TestCacheMiss(t *testing.T) { dbPath := filepath.Join(t.TempDir(), "test.db") cache, err := NewCache(dbPath, 24*time.Hour) require.NoError(t, err) - defer cache.Close() + defer func() { _ = cache.Close() }() _, ok, err := cache.Get("pkg:golang/nonexistent@v1.0.0", "osv") require.NoError(t, err) @@ -71,7 +71,7 @@ func TestCacheOverwrite(t *testing.T) { dbPath := filepath.Join(t.TempDir(), "test.db") cache, err := NewCache(dbPath, 24*time.Hour) require.NoError(t, err) - defer cache.Close() + defer func() { _ = cache.Close() }() old := []types.VulnMatch{ {Vulnerability: types.Vulnerability{ID: "CVE-OLD"}}, diff --git a/issue-253-response.md b/issue-253-response.md new file mode 100644 index 00000000..bcaad2f4 --- /dev/null +++ b/issue-253-response.md @@ -0,0 +1,47 @@ +Hey, thanks for picking this up and for thinking it through before opening a PR — appreciate it. + +A few things upfront so you've got room to do this your way: + +**On the synopsis** — `SYNOPSES/beginner/Steganography.Multi.Tool.md` is a *suggestion*, not a spec. You don't have to follow it exactly. The only thing I really care about is that the **scale/complexity is at least equivalent** to what the synopsis implies. If you want to swap stacks entirely — different language, add a web frontend, whatever — go for it, as long as the end result isn't smaller in scope than what's described. + +**On the core idea** — the one thing that should stay is the "multi" part. This is a steganography *tool* (not a detector), and it should cover **at least 5 different stego formats/techniques**, ideally 6. They all need to be solid implementations — I'd rather have 5 good ones than 6 where one is half-baked. + +**On the 3-PR split** — fine by me. Each PR should ship with **tests for what it adds** — not exhaustive, just enough to confirm each technique actually round-trips correctly. Don't go overboard. You can save the lint pass for the end if that's easier. + +**On stack/tooling** — pick whatever you want, but a few hard requirements per language: + +- **Python**: `ruff` + `mypy` are required. `pylint` is optional but I'll probably run it during review, so I'd recommend it — you'll need to disable a lot of rules to make it sane; look at the `pyproject.toml` in any of my recent Python projects for what I ignore. Format with `yapf`, column limit ~90. +- **Go**: `golangci-lint` (+ `gofmt`/`goimports`). +- **TypeScript**: `Biome` for format + lint. (And yes — if you go JS, it has to be TypeScript, not plain JS.) +- **Any language**: a `justfile` for install / lint / test / run targets. I keep this consistent across the whole repo, so please get familiar with it. Think of it as a nicer `Makefile`. + +**On references** — the two you picked aren't the best choices: + +- `keylogger/` was one of the very first projects in the repo. It works, but a lot has improved since then. +- `metadata-scrubber-tool/` was an external contribution — useful as a "another contributor did this" example but not really written in my style. + +Better references for senior-level Python style in this repo: + +- `PROJECTS/intermediate/dlp-scanner/` — most recent intermediate Python project, closest in scale/feel to what you're proposing +- `PROJECTS/beginner/base64-tool/` and `PROJECTS/beginner/caesar-cipher/` +- `PROJECTS/foundations/password-manager/` — technically foundations, but it's the most complex one in that tier and pretty close to intermediate in style + +Look at those for code structure, `pyproject.toml`, `justfile`, and overall layout. + +**On the `learn/` folder and README** — I write/standardize those across every project to keep them consistent, so don't sweat matching the exact format. If you take a shot at them, great, but if not I'll handle them after merge. Same for `install.sh` and any other dev-tooling polish — I can clean up around you, the main thing is the code itself. + +**On AI usage** — I'm going to be direct because it's relevant to how I'll review: + +Using AI to write code is totally fine — even heavily. What I push back on is *unsupervised* AI work (the "let it rip and come back when it says it's done" approach). The distinction matters: you're the project manager, the AI is the dev. You make the big calls, you micromanage the scope, and you verify the output. + +A few things that meaningfully raise the quality bar when working with AI on a project this size: + +1. **Amplify the model with research before it codes.** AI training data is not omniscient — it's training data. Spend time gathering up-to-date info on the libraries you'll use, senior-level patterns specific to *this* stack and *this* domain, advanced/nuanced techniques in steganography itself, recent changes in the ecosystem. Feed that to the model before it writes anything. The gap between "AI that knows generic Python" and "AI that's been primed with senior patterns + current best practices in stego + the specific libraries you chose" is enormous. + +2. **The project scope is bigger than any single context window.** You will burn through multiple AI sessions before this is done. The seams *between* those sessions — what gets carried forward, what gets re-established, what assumptions silently drift — are where quality usually dies. Most of the time the drift is subtle and you don't catch it until the project is "finished" and inconsistent. Plan for those handoffs deliberately. + +3. **Senior dev with AI ≠ junior dev with AI.** The same model produces wildly different output depending on who's driving. I can spot low-effort AI output quickly, and I review strictly *because* AI can be excellent when used well — so the floor for what counts as "good enough" is higher, not lower. + +None of this is gatekeeping or meant to scare you off — but fair warning: there are ~70 projects in this repo and the bar I'm holding them all to is "role-model code people study," not "good enough and it works," so expect strict review feedback even when the work is solid. + +Go ahead and start on PR 1 whenever you're ready. Ping me if anything's unclear as you go.