@codyswann/lisa 1.13.0 → 1.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/cdk/copy-overwrite/jest.cdk.ts +71 -0
  2. package/cdk/copy-overwrite/jest.config.ts +28 -0
  3. package/cdk/copy-overwrite/tsconfig.cdk.json +14 -0
  4. package/cdk/copy-overwrite/tsconfig.eslint.json +2 -1
  5. package/cdk/copy-overwrite/tsconfig.json +3 -0
  6. package/cdk/create-only/cdk.json +23 -0
  7. package/cdk/create-only/tsconfig.local.json +4 -0
  8. package/expo/copy-overwrite/.claude/skills/owasp-zap/SKILL.md +56 -0
  9. package/expo/copy-overwrite/.claude/skills/testing-library/SKILL.md +5 -10
  10. package/expo/copy-overwrite/.github/workflows/zap-baseline.yml +107 -0
  11. package/expo/copy-overwrite/.zap/baseline.conf +36 -0
  12. package/expo/copy-overwrite/jest.config.ts +28 -0
  13. package/expo/copy-overwrite/jest.expo.ts +119 -0
  14. package/expo/copy-overwrite/scripts/zap-baseline.sh +92 -0
  15. package/expo/copy-overwrite/tsconfig.eslint.json +2 -1
  16. package/expo/copy-overwrite/tsconfig.expo.json +12 -0
  17. package/expo/copy-overwrite/tsconfig.json +5 -0
  18. package/expo/create-only/.github/workflows/ci.yml +8 -0
  19. package/expo/create-only/tsconfig.local.json +3 -0
  20. package/expo/package-lisa/package.lisa.json +4 -2
  21. package/nestjs/copy-overwrite/.github/workflows/zap-baseline.yml +123 -0
  22. package/nestjs/copy-overwrite/.zap/baseline.conf +39 -0
  23. package/nestjs/copy-overwrite/jest.config.ts +28 -0
  24. package/nestjs/copy-overwrite/jest.nestjs.ts +89 -0
  25. package/nestjs/copy-overwrite/scripts/zap-baseline.sh +99 -0
  26. package/nestjs/copy-overwrite/tsconfig.build.json +5 -0
  27. package/nestjs/copy-overwrite/tsconfig.eslint.json +10 -0
  28. package/nestjs/copy-overwrite/tsconfig.json +5 -0
  29. package/nestjs/copy-overwrite/tsconfig.nestjs.json +16 -0
  30. package/nestjs/copy-overwrite/tsconfig.spec.json +7 -0
  31. package/nestjs/create-only/.github/workflows/ci.yml +8 -0
  32. package/nestjs/create-only/tsconfig.local.json +6 -0
  33. package/nestjs/package-lisa/package.lisa.json +2 -1
  34. package/package.json +1 -1
  35. package/typescript/copy-overwrite/.claude/commands/security/zap-scan.md +12 -0
  36. package/typescript/copy-overwrite/.github/workflows/quality.yml +100 -5
  37. package/typescript/copy-overwrite/jest.base.ts +112 -0
  38. package/typescript/copy-overwrite/jest.config.ts +34 -0
  39. package/typescript/copy-overwrite/jest.typescript.ts +72 -0
  40. package/typescript/copy-overwrite/tsconfig.base.json +15 -0
  41. package/typescript/copy-overwrite/tsconfig.eslint.json +3 -2
  42. package/typescript/copy-overwrite/tsconfig.json +5 -0
  43. package/typescript/copy-overwrite/tsconfig.typescript.json +11 -0
  44. package/typescript/create-only/jest.config.local.ts +30 -0
  45. package/typescript/create-only/jest.thresholds.json +8 -0
  46. package/typescript/create-only/tsconfig.local.json +6 -0
@@ -18,7 +18,8 @@
18
18
  "fetch:graphql:schema:staging": "./scripts/fetch-graphql-schema.sh staging",
19
19
  "fetch:graphql:schema:production": "./scripts/fetch-graphql-schema.sh production",
20
20
  "export:web": "expo export --platform web --source-maps",
21
- "pre-build:eas": "cat .gitignore > .easignore && cat .easignore.extra >> .easignore"
21
+ "pre-build:eas": "cat .gitignore > .easignore && cat .easignore.extra >> .easignore",
22
+ "security:zap": "bash scripts/zap-baseline.sh"
22
23
  },
23
24
  "dependencies": {
24
25
  "@apollo/client": "^3.10.8",
@@ -117,7 +118,8 @@
117
118
  "expo-atlas": "^0.4.0",
118
119
  "globals": "^16.0.0",
119
120
  "jest-environment-jsdom": "^30.2.0",
120
- "jest-expo": "^54.0.12"
121
+ "jest-expo": "^54.0.12",
122
+ "serve": "^14.2.0"
121
123
  },
122
124
  "resolutions": {
123
125
  "eslint-plugin-react-hooks": "^7.0.0",
@@ -0,0 +1,123 @@
1
+ # This file is managed by Lisa.
2
+ # Do not edit directly — changes will be overwritten on the next `lisa` run.
3
+
4
+ name: ZAP Baseline Scan (NestJS)
5
+
6
+ on:
7
+ workflow_call:
8
+ inputs:
9
+ node_version:
10
+ description: 'Node.js version to use'
11
+ required: false
12
+ default: '22.21.1'
13
+ type: string
14
+ package_manager:
15
+ description: 'Package manager to use (npm, yarn, or bun)'
16
+ required: false
17
+ default: 'bun'
18
+ type: string
19
+ zap_target_url:
20
+ description: 'Override URL for ZAP to scan (default: http://localhost:3000/graphql)'
21
+ required: false
22
+ default: 'http://localhost:3000/graphql'
23
+ type: string
24
+ zap_rules_file:
25
+ description: 'Path to ZAP rules configuration file'
26
+ required: false
27
+ default: '.zap/baseline.conf'
28
+ type: string
29
+
30
+ jobs:
31
+ zap_baseline:
32
+ name: ZAP Baseline Scan
33
+ runs-on: ubuntu-latest
34
+ timeout-minutes: 20
35
+
36
+ steps:
37
+ - name: Checkout repository
38
+ uses: actions/checkout@v4
39
+
40
+ - name: Setup Node.js
41
+ uses: actions/setup-node@v4
42
+ with:
43
+ node-version: ${{ inputs.node_version }}
44
+ cache: ${{ inputs.package_manager != 'bun' && inputs.package_manager || '' }}
45
+
46
+ - name: Setup Bun
47
+ if: inputs.package_manager == 'bun'
48
+ uses: oven-sh/setup-bun@v2
49
+ with:
50
+ bun-version: '1.3.8'
51
+
52
+ - name: Install dependencies
53
+ run: |
54
+ if [ "${{ inputs.package_manager }}" = "npm" ]; then
55
+ npm ci
56
+ elif [ "${{ inputs.package_manager }}" = "yarn" ]; then
57
+ yarn install --frozen-lockfile
58
+ elif [ "${{ inputs.package_manager }}" = "bun" ]; then
59
+ bun install --frozen-lockfile
60
+ fi
61
+
62
+ - name: Build project
63
+ run: ${{ inputs.package_manager }} run build
64
+
65
+ - name: Start NestJS server
66
+ run: |
67
+ ${{ inputs.package_manager }} run start &
68
+ SERVER_PID=$!
69
+ echo "SERVER_PID=$SERVER_PID" >> $GITHUB_ENV
70
+ env:
71
+ NODE_ENV: test
72
+ PORT: 3000
73
+
74
+ - name: Wait for server ready
75
+ run: |
76
+ echo "Waiting for NestJS server to be ready..."
77
+ RETRIES=30
78
+ until curl -sf http://localhost:3000/health > /dev/null 2>&1 || [ $RETRIES -eq 0 ]; do
79
+ echo "Waiting for server... ($RETRIES retries left)"
80
+ RETRIES=$((RETRIES - 1))
81
+ sleep 2
82
+ done
83
+ if [ $RETRIES -eq 0 ]; then
84
+ echo "Server failed to start within timeout"
85
+ exit 1
86
+ fi
87
+ echo "Server is ready"
88
+
89
+ - name: Check for ZAP rules file
90
+ id: check_rules
91
+ run: |
92
+ if [ -f "${{ inputs.zap_rules_file }}" ]; then
93
+ echo "has_rules=true" >> $GITHUB_OUTPUT
94
+ else
95
+ echo "has_rules=false" >> $GITHUB_OUTPUT
96
+ fi
97
+
98
+ - name: Run ZAP baseline scan
99
+ uses: zaproxy/action-baseline@v0.14.0
100
+ with:
101
+ target: ${{ inputs.zap_target_url }}
102
+ rules_file_name: ${{ steps.check_rules.outputs.has_rules == 'true' && inputs.zap_rules_file || '' }}
103
+ fail_action: true
104
+ allow_issue_writing: false
105
+ artifact_name: 'zap-report-nestjs'
106
+
107
+ - name: Stop NestJS server
108
+ if: always()
109
+ run: |
110
+ if [ -n "$SERVER_PID" ]; then
111
+ kill "$SERVER_PID" 2>/dev/null || true
112
+ fi
113
+
114
+ - name: Upload ZAP report
115
+ if: always()
116
+ uses: actions/upload-artifact@v4
117
+ with:
118
+ name: zap-baseline-report-nestjs-${{ github.run_id }}
119
+ path: |
120
+ zap-report.html
121
+ zap-report.json
122
+ zap-report.md
123
+ retention-days: 14
@@ -0,0 +1,39 @@
1
+ # OWASP ZAP Baseline Scan Configuration — NestJS GraphQL APIs
2
+ # Format: <rule_id> <action> <description>
3
+ # Actions: IGNORE (skip rule), WARN (report but don't fail), FAIL (fail on finding)
4
+ #
5
+ # Tuned for NestJS APIs behind API Gateway/load balancer in production.
6
+ # Transport-level headers are enforced at infrastructure layer.
7
+
8
+ # CSP header — API responses don't serve HTML; CSP is a browser concern
9
+ 10038 WARN (Content Security Policy (CSP) Header Not Set)
10
+
11
+ # X-Content-Type-Options — should be set but is infrastructure-level
12
+ 10021 WARN (X-Content-Type-Options Header Missing)
13
+
14
+ # Strict-Transport-Security — enforced at API Gateway/load balancer
15
+ 10035 WARN (Strict-Transport-Security Header Not Set)
16
+
17
+ # X-Frame-Options — API responses are not rendered in browsers
18
+ 10020 IGNORE (X-Frame-Options Header Not Set)
19
+
20
+ # Permissions-Policy — not applicable to API responses
21
+ 10063 IGNORE (Permissions Policy Header Not Set)
22
+
23
+ # Server header disclosure — NestJS/Express may leak version info;
24
+ # should be suppressed in production via Helmet middleware
25
+ 10036 WARN (Server Leaks Version Information via "Server" HTTP Response Header Field)
26
+
27
+ # Cookie flags — GraphQL APIs may use session cookies for auth
28
+ 10010 FAIL (Cookie No HttpOnly Flag)
29
+ 10011 FAIL (Cookie Without Secure Flag)
30
+ 10054 WARN (Cookie without SameSite Attribute)
31
+
32
+ # Information disclosure in URL — GraphQL uses POST body, not URL params
33
+ 10024 IGNORE (Information Disclosure - Sensitive Information in URL)
34
+
35
+ # Cross-domain JavaScript source — not applicable to API
36
+ 10017 IGNORE (Cross-Domain JavaScript Source File Inclusion)
37
+
38
+ # Application error disclosure — NestJS should not leak stack traces
39
+ 10023 FAIL (Information Disclosure - Debug Error Messages)
@@ -0,0 +1,28 @@
1
+ /**
2
+ * This file is managed by Lisa.
3
+ * Do not edit directly — changes will be overwritten on the next `lisa` run.
4
+ */
5
+
6
+ /**
7
+ * Jest Configuration - Main Entry Point (NestJS)
8
+ *
9
+ * Imports the NestJS-specific configuration and project-local customizations.
10
+ * Do not modify this file directly - use jest.config.local.ts for project-specific settings.
11
+ *
12
+ * Inheritance chain:
13
+ * jest.config.ts (this file)
14
+ * └── jest.nestjs.ts
15
+ * └── jest.base.ts
16
+ *
17
+ * @see https://jestjs.io/docs/configuration
18
+ * @module jest.config
19
+ */
20
+ import { mergeConfigs, mergeThresholds } from "./jest.base.ts";
21
+ import { defaultThresholds, getNestjsJestConfig } from "./jest.nestjs.ts";
22
+
23
+ import localConfig from "./jest.config.local.ts";
24
+ import thresholdsOverrides from "./jest.thresholds.json" with { type: "json" };
25
+
26
+ const thresholds = mergeThresholds(defaultThresholds, thresholdsOverrides);
27
+
28
+ export default mergeConfigs(getNestjsJestConfig({ thresholds }), localConfig);
@@ -0,0 +1,89 @@
1
+ /**
2
+ * This file is managed by Lisa.
3
+ * Do not edit directly — changes will be overwritten on the next `lisa` run.
4
+ */
5
+
6
+ /**
7
+ * Jest Configuration - NestJS Stack
8
+ *
9
+ * Provides NestJS-specific Jest configuration with extensive coverage
10
+ * exclusions for generated files, DTOs, entities, and modules.
11
+ *
12
+ * Inheritance chain:
13
+ * jest.nestjs.ts (this file)
14
+ * └── jest.base.ts
15
+ *
16
+ * @see https://jestjs.io/docs/configuration
17
+ * @module jest.nestjs
18
+ */
19
+ import type { Config } from "jest";
20
+
21
+ import {
22
+ defaultCoverageExclusions,
23
+ defaultThresholds,
24
+ mergeConfigs,
25
+ mergeThresholds,
26
+ } from "./jest.base.ts";
27
+
28
+ // Re-export base utilities for entry-point configs
29
+ export {
30
+ defaultCoverageExclusions,
31
+ defaultThresholds,
32
+ mergeConfigs,
33
+ mergeThresholds,
34
+ };
35
+
36
+ /**
37
+ * Options for configuring the NestJS Jest config factory.
38
+ */
39
+ interface NestjsJestOptions {
40
+ /** Coverage thresholds (merged defaults + project overrides) */
41
+ readonly thresholds?: Config["coverageThreshold"];
42
+ }
43
+
44
+ /**
45
+ * NestJS-specific patterns excluded from coverage collection.
46
+ * These are generated or boilerplate files that don't benefit from coverage tracking.
47
+ */
48
+ const nestjsCoverageExclusions: readonly string[] = [
49
+ ...defaultCoverageExclusions,
50
+ "!**/*.entity.ts",
51
+ "!**/*.dto.ts",
52
+ "!**/*.input.ts",
53
+ "!**/*.args.ts",
54
+ "!**/*.model.ts",
55
+ "!**/*.module.ts",
56
+ "!**/*.factory.ts",
57
+ "!**/*.enum.ts",
58
+ "!**/*.interface.ts",
59
+ "!**/*.constants.ts",
60
+ "!**/database/migrations/**",
61
+ "!**/database/seeds/**",
62
+ "!**/graphql/**",
63
+ "!**/main.ts",
64
+ ];
65
+
66
+ /**
67
+ * Creates a Jest configuration for NestJS projects.
68
+ *
69
+ * @param options - Configuration options for threshold overrides
70
+ * @param options.thresholds - Coverage thresholds (merged defaults + project overrides)
71
+ * @returns Jest config object with ts-jest transform, node environment, and NestJS-specific exclusions
72
+ * @remarks NestJS projects use CommonJS modules and spec.ts test file convention.
73
+ * Coverage excludes entities, DTOs, modules, and other boilerplate files
74
+ * that are better validated through integration tests.
75
+ */
76
+ export const getNestjsJestConfig = ({
77
+ thresholds = defaultThresholds,
78
+ }: NestjsJestOptions = {}): Config => ({
79
+ testEnvironment: "node",
80
+ rootDir: "src",
81
+ testRegex: ".*\\.spec\\.ts$",
82
+ transform: {
83
+ "^.+\\.ts$": "ts-jest",
84
+ },
85
+ moduleFileExtensions: ["js", "json", "ts"],
86
+ collectCoverageFrom: ["**/*.ts", ...nestjsCoverageExclusions],
87
+ coverageThreshold: thresholds,
88
+ testTimeout: 10000,
89
+ });
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env bash
2
+ # OWASP ZAP Baseline Scan — NestJS GraphQL API
3
+ # Builds and starts the NestJS server, then runs a ZAP baseline scan via Docker.
4
+ # Outputs an HTML report to zap-report.html in the project root.
5
+ set -euo pipefail
6
+
7
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8
+ PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
9
+ TARGET_URL="${ZAP_TARGET_URL:-http://host.docker.internal:3000/graphql}"
10
+ ZAP_RULES_FILE="${ZAP_RULES_FILE:-.zap/baseline.conf}"
11
+ REPORT_FILE="zap-report.html"
12
+
13
+ cd "$PROJECT_ROOT"
14
+
15
+ # Verify Docker is available
16
+ if ! command -v docker &> /dev/null; then
17
+ echo "Error: Docker is required but not installed."
18
+ echo "Install Docker from https://docs.docker.com/get-docker/"
19
+ exit 1
20
+ fi
21
+
22
+ if ! docker info &> /dev/null 2>&1; then
23
+ echo "Error: Docker daemon is not running."
24
+ exit 1
25
+ fi
26
+
27
+ # Detect package manager
28
+ if [ -f "bun.lockb" ]; then
29
+ PKG_MGR="bun"
30
+ elif [ -f "yarn.lock" ]; then
31
+ PKG_MGR="yarn"
32
+ elif [ -f "pnpm-lock.yaml" ]; then
33
+ PKG_MGR="pnpm"
34
+ else
35
+ PKG_MGR="npm"
36
+ fi
37
+
38
+ echo "==> Building NestJS project..."
39
+ $PKG_MGR run build
40
+
41
+ echo "==> Starting NestJS server..."
42
+ NODE_ENV=test PORT=3000 $PKG_MGR run start &
43
+ SERVER_PID=$!
44
+
45
+ cleanup() {
46
+ echo "==> Cleaning up..."
47
+ if [ -n "${SERVER_PID:-}" ]; then
48
+ kill "$SERVER_PID" 2>/dev/null || true
49
+ fi
50
+ }
51
+ trap cleanup EXIT
52
+
53
+ echo "==> Waiting for server to be ready..."
54
+ RETRIES=30
55
+ until curl -sf http://localhost:3000/health > /dev/null 2>&1 || [ $RETRIES -eq 0 ]; do
56
+ RETRIES=$((RETRIES - 1))
57
+ sleep 2
58
+ done
59
+
60
+ if [ $RETRIES -eq 0 ]; then
61
+ echo "Error: Server failed to start within timeout"
62
+ exit 1
63
+ fi
64
+ echo " Server is ready"
65
+
66
+ echo "==> Running OWASP ZAP baseline scan..."
67
+ ZAP_ARGS="-t $TARGET_URL"
68
+
69
+ if [ -f "$ZAP_RULES_FILE" ]; then
70
+ echo " Using rules file: $ZAP_RULES_FILE"
71
+ ZAP_ARGS="$ZAP_ARGS -c /zap/wrk/$(basename "$ZAP_RULES_FILE")"
72
+ MOUNT_RULES="-v $(dirname "$(realpath "$ZAP_RULES_FILE")"):/zap/wrk:ro"
73
+ else
74
+ MOUNT_RULES=""
75
+ fi
76
+
77
+ docker run --rm \
78
+ --add-host=host.docker.internal:host-gateway \
79
+ -v "$(pwd)":/zap/wrk/:rw \
80
+ $MOUNT_RULES \
81
+ ghcr.io/zaproxy/zaproxy:stable \
82
+ zap-baseline.py $ZAP_ARGS \
83
+ -r "$REPORT_FILE" \
84
+ -J zap-report.json \
85
+ -w zap-report.md \
86
+ -l WARN || ZAP_EXIT=$?
87
+
88
+ echo ""
89
+ if [ -f "$REPORT_FILE" ]; then
90
+ echo "ZAP report saved to: $REPORT_FILE"
91
+ fi
92
+
93
+ if [ "${ZAP_EXIT:-0}" -ne 0 ]; then
94
+ echo "ZAP found medium+ severity findings (exit code: $ZAP_EXIT)"
95
+ echo "Review $REPORT_FILE for details."
96
+ exit "$ZAP_EXIT"
97
+ else
98
+ echo "ZAP baseline scan passed — no medium+ severity findings."
99
+ fi
@@ -0,0 +1,5 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "include": ["src/**/*"],
4
+ "exclude": ["node_modules", "**/*.spec.ts"]
5
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "rootDir": ".",
5
+ "noEmit": true,
6
+ "allowImportingTsExtensions": true
7
+ },
8
+ "include": ["src/**/*", "test/**/*", "*.config.ts", "eslint.*.ts", "jest.*.ts"],
9
+ "exclude": ["node_modules", ".build"]
10
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "extends": ["./tsconfig.nestjs.json", "./tsconfig.local.json"],
3
+ "include": ["src/**/*"],
4
+ "exclude": ["node_modules", ".build"]
5
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "extends": "./tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "module": "commonjs",
5
+ "target": "ES2021",
6
+ "emitDecoratorMetadata": true,
7
+ "experimentalDecorators": true,
8
+ "declaration": true,
9
+ "sourceMap": true,
10
+ "outDir": ".build",
11
+ "baseUrl": "./",
12
+ "paths": {
13
+ "@/*": ["./src/*"]
14
+ }
15
+ }
16
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "types": ["jest", "node"]
5
+ },
6
+ "include": ["**/*.spec.ts", "**/*.d.ts"]
7
+ }
@@ -18,6 +18,14 @@ jobs:
18
18
  skip_jobs: 'test,test:integration,test:e2e'
19
19
  secrets: inherit
20
20
 
21
+ zap:
22
+ name: 🕷️ ZAP Baseline Scan
23
+ needs: [quality]
24
+ uses: ./.github/workflows/zap-baseline.yml
25
+ with:
26
+ node_version: '22.21.1'
27
+ package_manager: 'bun'
28
+
21
29
  create_issue_on_failure:
22
30
  name: 📌 Create Issue on Failure
23
31
  needs: [quality]
@@ -0,0 +1,6 @@
1
+ {
2
+ "compilerOptions": {
3
+ "outDir": ".build",
4
+ "rootDir": "src"
5
+ }
6
+ }
@@ -26,7 +26,8 @@
26
26
  "fetch:graphql:schema:production": "./scripts/fetch-graphql-schema.sh production",
27
27
  "deploy:dev": "sls deploy --stage dev",
28
28
  "deploy:staging": "sls deploy --stage staging",
29
- "deploy:production": "sls deploy --stage production"
29
+ "deploy:production": "sls deploy --stage production",
30
+ "security:zap": "bash scripts/zap-baseline.sh"
30
31
  },
31
32
  "dependencies": {
32
33
  "@apollo/server": "^5.2.0",
package/package.json CHANGED
@@ -85,7 +85,7 @@
85
85
  },
86
86
  "resolutions": {},
87
87
  "name": "@codyswann/lisa",
88
- "version": "1.13.0",
88
+ "version": "1.15.0",
89
89
  "description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
90
90
  "main": "dist/index.js",
91
91
  "bin": {
@@ -0,0 +1,12 @@
1
+ Run an OWASP ZAP baseline security scan locally using Docker.
2
+
3
+ Steps:
4
+ 1. Check if Docker is installed and running: `docker info`
5
+ 2. Check if `scripts/zap-baseline.sh` exists in the project
6
+ 3. If it exists, run: `bash scripts/zap-baseline.sh`
7
+ 4. If it does not exist, inform the user that this project does not have a ZAP baseline scan configured
8
+ 5. After the scan completes, read `zap-report.html` (or `zap-report.md` for text) and summarize:
9
+ - Total number of alerts by risk level (High, Medium, Low, Informational)
10
+ - List each Medium+ finding with its rule ID, name, and recommended fix
11
+ - Categorize findings as "infrastructure-level" (fix at CDN/proxy) vs "application-level" (fix in code)
12
+ 6. If the scan failed, explain what failed and suggest concrete remediation steps