@codyswann/lisa 1.14.0 → 1.16.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 (41) hide show
  1. package/all/copy-overwrite/.claude/hooks/enforce-plan-rules.sh +15 -0
  2. package/all/copy-overwrite/.claude/hooks/track-plan-sessions.sh +155 -0
  3. package/all/copy-overwrite/.claude/rules/plan.md +29 -0
  4. package/all/copy-overwrite/.claude/settings.json +30 -0
  5. package/all/copy-overwrite/CLAUDE.md +2 -9
  6. package/cdk/copy-overwrite/tsconfig.eslint.json +2 -1
  7. package/cdk/create-only/cdk.json +23 -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.expo.ts +59 -13
  13. package/expo/copy-overwrite/knip.json +1 -1
  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 +7 -0
  17. package/expo/copy-overwrite/tsconfig.json +3 -1
  18. package/expo/create-only/.github/workflows/ci.yml +8 -0
  19. package/expo/create-only/babel.config.js +27 -0
  20. package/expo/create-only/jest.config.local.ts +34 -0
  21. package/expo/create-only/jest.config.react-native-mock.js +88 -0
  22. package/expo/create-only/jest.setup.pre.js +106 -0
  23. package/expo/create-only/jest.setup.ts +118 -0
  24. package/expo/create-only/tsconfig.local.json +1 -7
  25. package/expo/package-lisa/package.lisa.json +4 -2
  26. package/nestjs/copy-overwrite/.github/workflows/zap-baseline.yml +123 -0
  27. package/nestjs/copy-overwrite/.zap/baseline.conf +39 -0
  28. package/nestjs/copy-overwrite/scripts/zap-baseline.sh +99 -0
  29. package/nestjs/copy-overwrite/tsconfig.eslint.json +10 -0
  30. package/nestjs/copy-overwrite/tsconfig.nestjs.json +4 -1
  31. package/nestjs/create-only/.github/workflows/ci.yml +8 -0
  32. package/nestjs/package-lisa/package.lisa.json +2 -1
  33. package/package.json +1 -1
  34. package/typescript/copy-contents/.husky/pre-push +5 -1
  35. package/typescript/copy-overwrite/.claude/commands/security/zap-scan.md +12 -0
  36. package/typescript/copy-overwrite/.claude/settings.json +10 -0
  37. package/typescript/copy-overwrite/.github/workflows/quality.yml +100 -5
  38. package/typescript/copy-overwrite/eslint.base.ts +1 -1
  39. package/typescript/copy-overwrite/eslint.ignore.config.json +1 -0
  40. package/typescript/copy-overwrite/jest.base.ts +1 -0
  41. package/typescript/copy-overwrite/tsconfig.eslint.json +3 -2
@@ -0,0 +1,15 @@
1
+ #!/bin/bash
2
+ # Reinjects plan-mode rules on every prompt when Claude is in plan mode.
3
+ # Wired as a UserPromptSubmit hook in .claude/settings.json.
4
+
5
+ INPUT=$(cat)
6
+ PERMISSION_MODE=$(echo "$INPUT" | jq -r '.permission_mode // "default"')
7
+
8
+ if [ "$PERMISSION_MODE" = "plan" ]; then
9
+ PLAN_RULES="$CLAUDE_PROJECT_DIR/.claude/rules/plan.md"
10
+ if [ -f "$PLAN_RULES" ]; then
11
+ echo "PLAN MODE RULES (reinforced):"
12
+ cat "$PLAN_RULES"
13
+ fi
14
+ fi
15
+ exit 0
@@ -0,0 +1,155 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # track-plan-sessions.sh - Tracks which sessions work on each plan file
4
+ #
5
+ # Triggered by two hooks:
6
+ # 1. PostToolUse (Write|Edit) - Detects plan file writes via absolute path comparison,
7
+ # stamps session ID into the plan's ## Sessions table, and saves a session-specific
8
+ # marker file so subsequent UserPromptSubmit events can reliably find the active plan.
9
+ # 2. UserPromptSubmit - Finds the active plan file by checking for a session-specific
10
+ # marker file first (set by PostToolUse), falling back to the most recently CREATED
11
+ # .md file in plans/ (ls -tU on macOS sorts by birth time). This avoids the mtime bug
12
+ # where another plan file's modification time (e.g., from a hook writing a session ID
13
+ # to it, or format-on-edit touching it) could cause the wrong file to appear "newest."
14
+ #
15
+ # Marker files: $PLANS_DIR/.active-plan-<session-id> contain the absolute path to the
16
+ # active plan file. Stale markers (>24h) are cleaned up on each invocation.
17
+ #
18
+ # Debug logging: All key decisions are logged to $PLANS_DIR/.track-plan-debug.log for
19
+ # diagnostics if session IDs land in the wrong plan file.
20
+ #
21
+ # Input (via stdin): JSON with session_id, permission_mode, hook_event_name, etc.
22
+ #
23
+
24
+ set -euo pipefail
25
+
26
+ INPUT=$(cat)
27
+
28
+ SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // empty')
29
+ PERMISSION_MODE=$(echo "$INPUT" | jq -r '.permission_mode // "default"')
30
+ HOOK_EVENT=$(echo "$INPUT" | jq -r '.hook_event_name // empty')
31
+
32
+ # Session ID is required
33
+ if [[ -z "$SESSION_ID" ]]; then
34
+ exit 0
35
+ fi
36
+
37
+ # Resolve plans directory from settings (default ./plans)
38
+ PLANS_DIR="./plans"
39
+ SETTINGS_FILE="${CLAUDE_PROJECT_DIR:-.}/.claude/settings.json"
40
+ if [[ -f "$SETTINGS_FILE" ]]; then
41
+ CONFIGURED_DIR=$(jq -r '.plansDirectory // empty' "$SETTINGS_FILE")
42
+ if [[ -n "$CONFIGURED_DIR" ]]; then
43
+ PLANS_DIR="$CONFIGURED_DIR"
44
+ fi
45
+ fi
46
+
47
+ # Debug logging
48
+ DEBUG_LOG="$PLANS_DIR/.track-plan-debug.log"
49
+
50
+ log_debug() {
51
+ printf '[%s] [%s] [%s] %s\n' \
52
+ "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
53
+ "$SESSION_ID" \
54
+ "$HOOK_EVENT" \
55
+ "$1" >> "$DEBUG_LOG" 2>/dev/null || true
56
+ }
57
+
58
+ # Session-specific marker file for reliable active-plan detection
59
+ MARKER_FILE="$PLANS_DIR/.active-plan-${SESSION_ID}"
60
+
61
+ # Clean stale marker files older than 24h
62
+ find "$PLANS_DIR" -name ".active-plan-*" -mmin +1440 -delete 2>/dev/null || true
63
+
64
+ PLAN_FILE=""
65
+ RESOLUTION_METHOD=""
66
+
67
+ if [[ "$HOOK_EVENT" == "PostToolUse" ]]; then
68
+ # Trigger A: Plan file was written/edited
69
+ FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
70
+
71
+ if [[ -z "$FILE_PATH" ]]; then
72
+ log_debug "PostToolUse: no file_path in tool_input, exiting"
73
+ exit 0
74
+ fi
75
+
76
+ # Resolve PLANS_DIR to absolute path for comparison
77
+ ABS_PLANS_DIR=$(cd "$PLANS_DIR" 2>/dev/null && pwd)
78
+
79
+ if [[ -z "$ABS_PLANS_DIR" ]]; then
80
+ log_debug "PostToolUse: could not resolve PLANS_DIR=$PLANS_DIR to absolute path, exiting"
81
+ exit 0
82
+ fi
83
+
84
+ # Check if the written file is in the plans directory
85
+ if [[ "$FILE_PATH" == "$ABS_PLANS_DIR"/* ]]; then
86
+ PLAN_FILE="$FILE_PATH"
87
+ RESOLUTION_METHOD="PostToolUse-direct"
88
+ log_debug "PostToolUse: matched plan file=$PLAN_FILE"
89
+
90
+ # Save marker so UserPromptSubmit can find this plan reliably
91
+ echo "$PLAN_FILE" > "$MARKER_FILE"
92
+ log_debug "PostToolUse: saved marker file=$MARKER_FILE"
93
+ else
94
+ log_debug "PostToolUse: file_path=$FILE_PATH not in plans dir=$ABS_PLANS_DIR, exiting"
95
+ exit 0
96
+ fi
97
+
98
+ elif [[ "$HOOK_EVENT" == "UserPromptSubmit" ]]; then
99
+ # Trigger B: Find the active plan file
100
+ # Priority 1: Session-specific marker file (reliable — set by PostToolUse when plan was written)
101
+ if [[ -f "$MARKER_FILE" ]]; then
102
+ PLAN_FILE=$(cat "$MARKER_FILE")
103
+ RESOLUTION_METHOD="marker-file"
104
+ log_debug "UserPromptSubmit: resolved via marker file=$PLAN_FILE"
105
+ else
106
+ # Priority 2: Most recently CREATED file (ls -tU on macOS sorts by birth time)
107
+ PLAN_FILE=$(ls -tU "$PLANS_DIR"/*.md 2>/dev/null | head -1)
108
+ RESOLUTION_METHOD="fallback-ls-tU"
109
+ log_debug "UserPromptSubmit: no marker file, fallback ls -tU resolved=$PLAN_FILE"
110
+ fi
111
+
112
+ if [[ -z "$PLAN_FILE" || ! -f "$PLAN_FILE" ]]; then
113
+ log_debug "UserPromptSubmit: no valid plan file found, exiting"
114
+ exit 0
115
+ fi
116
+ else
117
+ exit 0
118
+ fi
119
+
120
+ # Verify the plan file exists
121
+ if [[ ! -f "$PLAN_FILE" ]]; then
122
+ log_debug "plan file=$PLAN_FILE does not exist, exiting"
123
+ exit 0
124
+ fi
125
+
126
+ # Check if session ID already exists in the file (dedup)
127
+ if grep -qF "$SESSION_ID" "$PLAN_FILE" 2>/dev/null; then
128
+ log_debug "dedup: session ID already in $PLAN_FILE (resolved via $RESOLUTION_METHOD), skipping write"
129
+ exit 0
130
+ fi
131
+
132
+ # Determine phase from permission_mode
133
+ PHASE=$( [[ "$PERMISSION_MODE" == "plan" ]] && echo "plan" || echo "implement" )
134
+ TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
135
+
136
+ # Check if ## Sessions section exists
137
+ if grep -q "^## Sessions" "$PLAN_FILE" 2>/dev/null; then
138
+ # Append row to existing table
139
+ printf '| %s | %s | %s |\n' "$SESSION_ID" "$TIMESTAMP" "$PHASE" >> "$PLAN_FILE"
140
+ else
141
+ # Create ## Sessions section at end of file
142
+ {
143
+ echo ""
144
+ echo "## Sessions"
145
+ echo ""
146
+ echo "<!-- Auto-maintained by track-plan-sessions.sh -->"
147
+ echo "| Session ID | First Seen | Phase |"
148
+ echo "|------------|------------|-------|"
149
+ printf '| %s | %s | %s |\n' "$SESSION_ID" "$TIMESTAMP" "$PHASE"
150
+ } >> "$PLAN_FILE"
151
+ fi
152
+
153
+ log_debug "wrote session to $PLAN_FILE (resolved via $RESOLUTION_METHOD, phase=$PHASE)"
154
+
155
+ exit 0
@@ -0,0 +1,29 @@
1
+ # Plan Mode Rules
2
+
3
+ These rules are enforced whenever Claude is in plan mode. They are loaded at session start via `.claude/rules/` and reinforced on every prompt via the `enforce-plan-rules.sh` `UserPromptSubmit` hook.
4
+
5
+ ## Required Behaviors
6
+
7
+ When making a plan:
8
+
9
+ - Always determine which skills should be used during execution of the plan and include them in the plan
10
+ - Always make sure you understand the correct versions of third party libraries
11
+ - Always save the plan with a name befitting the actual plan contents
12
+ - Always look for code that can be reused for implementation
13
+ - The plan MUST including written instructions to create a task list using TaskCreate for each task. The list should contain items related to the plan and specify that subagents should handle as many in parallel as possible. The following should always be included in the task list
14
+ - update/add/remove tests, containing the tests that need to get updated, added or removed
15
+ - update/add/remove documentation (jsdocs, markdown files, etc), containing the documentation that need to get updated, added or removed
16
+ - archive the plan (to be completed after all other tasks have been completed). This task should explcitly say to:
17
+ - create a folder named <plan-name> in ./plans/completed
18
+ - rename this plan to a name befitting the actual plan contents
19
+ - move it into ./plans/completed/<plan-name>
20
+ - read the session ids from ./plans/completed/<plan-name>
21
+ - For each session id, move the ~/.claude/tasks/<session-id> directory to ./plans/completed/<plan-name>/tasks
22
+ - If you're on a protected branch (dev, staging, main), create a new branch named based on the nature of the project and include in the plan pull requests should go to the protected branch you bracnehd from.
23
+ - If you're on a non-protected branch with an open pull request, submit pushes to the open pull request
24
+ - If you're on a non-protected branch with no existing PR, clarify which protected branch to open the pull request to.
25
+ - If referencing a ticket (jira, linear, etc), always include the ticket url in your plan
26
+ - If referencing a ticket (jira, linear, etc), always update the ticket with the branch you're working off of
27
+ - If referencing a ticket (jira, linear, etc), always add a comment to the ticket with the finalized plan
28
+ - The `## Sessions` section in plan files is auto-maintained by the `track-plan-sessions.sh` hook — do not manually edit it
29
+
@@ -10,9 +10,39 @@
10
10
  "timeout": 1
11
11
  }
12
12
  ]
13
+ },
14
+ {
15
+ "matcher": "",
16
+ "hooks": [
17
+ {
18
+ "type": "command",
19
+ "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/enforce-plan-rules.sh",
20
+ "timeout": 5
21
+ }
22
+ ]
23
+ },
24
+ {
25
+ "matcher": "",
26
+ "hooks": [
27
+ {
28
+ "type": "command",
29
+ "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/track-plan-sessions.sh",
30
+ "timeout": 5
31
+ }
32
+ ]
13
33
  }
14
34
  ],
15
35
  "PostToolUse": [
36
+ {
37
+ "matcher": "Write|Edit",
38
+ "hooks": [
39
+ {
40
+ "type": "command",
41
+ "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/track-plan-sessions.sh",
42
+ "timeout": 5
43
+ }
44
+ ]
45
+ },
16
46
  {
17
47
  "matcher": "TaskCreate|TaskUpdate",
18
48
  "hooks": [
@@ -16,7 +16,7 @@ Always use project-relative paths rather than absolute paths in documentation an
16
16
  Always ignore build folders (dist, build, etc) unless specified otherwise
17
17
  Always delete and remove old code completely - no deprecation needed
18
18
  Always add `GIT_SSH_COMMAND="ssh -o ServerAliveInterval=30 -o ServerAliveCountMax=5" ` when running `git push`
19
-
19
+ Always err on the side of creating a plan before directly executing a coding task
20
20
 
21
21
  Never modify this file (CLAUDE.md) directly. To add a memory or learning, add it to .claude/rules/PROJECT_RULES.md or create a skill with /skill-creator.
22
22
  Never commit changes to an environment branch (dev, staging, main) directly. This is enforced by the pre-commit hook.
@@ -52,12 +52,5 @@ ONLY use ts-ignore as a last resort and confirm with human before doing so
52
52
  ONLY use ts-expect-error as a last resort and confirm with human before doing so
53
53
  ONLY use ts-nocheck as a last resort and confirm with human before doing so
54
54
 
55
+ Never update CHANGELOG
55
56
 
56
- When making a plan, always determine which skills should be used during execution of the plan and include them in the plan
57
- When making a plan, always make sure you understand the correct versions of third party libraries
58
- When making a plan, always create a task list of items related to the plan and specify that subagents should handle as many in parallel as possible
59
- When making a plan, always save the plan with a name befitting the actual plan contents.
60
- When making a plan, always look for code that can be reused for implementation
61
- When making a plan, always include a task to update/add/remove documentation
62
-
63
- Never update CHANGELOG
@@ -3,9 +3,10 @@
3
3
  "compilerOptions": {
4
4
  "rootDir": ".",
5
5
  "noEmit": true,
6
+ "allowImportingTsExtensions": true,
6
7
  "module": "NodeNext",
7
8
  "moduleResolution": "NodeNext"
8
9
  },
9
- "include": ["lib/**/*", "bin/**/*", "test/**/*", "*.config.ts", "eslint.*.ts"],
10
+ "include": ["lib/**/*", "bin/**/*", "test/**/*", "*.config.ts", "eslint.*.ts", "jest.*.ts"],
10
11
  "exclude": ["node_modules", "dist", "cdk.out"]
11
12
  }
@@ -0,0 +1,23 @@
1
+ {
2
+ "app": "npx tsx bin/infrastructure.ts",
3
+ "watch": {
4
+ "include": ["**"],
5
+ "exclude": [
6
+ "README.md",
7
+ "cdk*.json",
8
+ "**/*.d.ts",
9
+ "**/*.js",
10
+ "tsconfig.json",
11
+ "package.json",
12
+ "yarn.lock",
13
+ "node_modules",
14
+ "test"
15
+ ]
16
+ },
17
+ "context": {
18
+ "@aws-cdk/aws-lambda:recognizeLayerVersion": true,
19
+ "@aws-cdk/core:checkSecretUsage": true,
20
+ "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
21
+ "@aws-cdk/core:target-partitions": ["aws", "aws-cn"]
22
+ }
23
+ }
@@ -0,0 +1,56 @@
1
+ # OWASP ZAP Baseline Scanning
2
+
3
+ OWASP ZAP (Zed Attack Proxy) performs DAST (Dynamic Application Security Testing) by scanning a running application for common security vulnerabilities from the OWASP Top 10.
4
+
5
+ ## When to Use
6
+
7
+ - After making changes to HTTP headers, authentication, or security middleware
8
+ - Before deploying to staging or production
9
+ - When reviewing security scan results from CI
10
+ - When triaging ZAP findings from pull request checks
11
+
12
+ ## Running Locally
13
+
14
+ ```bash
15
+ # Requires Docker to be installed and running
16
+ bash scripts/zap-baseline.sh
17
+ ```
18
+
19
+ The scan builds the Expo web export, serves it locally, and runs ZAP against it. Reports are saved to `zap-report.html`, `zap-report.json`, and `zap-report.md`.
20
+
21
+ ## Interpreting Results
22
+
23
+ ZAP findings are categorized by risk level:
24
+
25
+ | Risk | Action |
26
+ |------|--------|
27
+ | **High** | Fix immediately — indicates exploitable vulnerability |
28
+ | **Medium** | Fix before deployment — security best practice violation |
29
+ | **Low** | Fix when convenient — minor security improvement |
30
+ | **Informational** | Review — may be false positive or acceptable risk |
31
+
32
+ ## Common Findings and Fixes
33
+
34
+ ### Infrastructure-Level (fix at CDN/hosting, not in code)
35
+
36
+ - **CSP Header Not Set**: Configure Content-Security-Policy at CDN or hosting platform. Expo web exports need `script-src 'self' 'unsafe-inline'` for hydration.
37
+ - **HSTS Not Set**: Configure Strict-Transport-Security at CDN/load balancer.
38
+ - **X-Frame-Options**: Use `frame-ancestors` in CSP at CDN level.
39
+
40
+ ### Application-Level (fix in code)
41
+
42
+ - **Cookie flags missing**: Ensure all cookies set `HttpOnly`, `Secure`, and `SameSite` attributes.
43
+ - **Debug error messages**: Ensure error boundaries don't leak stack traces in production.
44
+ - **Server version disclosure**: Remove or mask the `Server` response header.
45
+
46
+ ## Configuration
47
+
48
+ ZAP scan rules are configured in `.zap/baseline.conf`. Each line controls how ZAP treats a specific rule:
49
+
50
+ - `IGNORE`: Skip the rule entirely
51
+ - `WARN`: Report finding but don't fail the build
52
+ - `FAIL`: Fail the build if this finding is detected
53
+
54
+ ## CI Integration
55
+
56
+ ZAP runs automatically in CI via the `zap-baseline.yml` workflow. Results are uploaded as artifacts and the build fails on medium+ severity findings.
@@ -190,17 +190,12 @@ test("sets isSubmitting to true", () => {});
190
190
 
191
191
  ## Jest Configuration
192
192
 
193
- ### Use jest-expo Universal Preset
193
+ ### Manual React Native Resolution (No Preset)
194
194
 
195
- Configure Jest to test across all Expo platforms:
196
-
197
- ```json
198
- {
199
- "jest": {
200
- "preset": "jest-expo/universal"
201
- }
202
- }
203
- ```
195
+ Lisa configures Jest manually instead of using the `jest-expo` preset to avoid
196
+ jsdom incompatibility with `react-native/jest/setup.js`. The configuration in
197
+ `jest.expo.ts` provides haste, resolver, transform, and setupFiles that match
198
+ the preset's behavior without redefining `window`.
204
199
 
205
200
  ### Use Fake Timers with userEvent
206
201
 
@@ -0,0 +1,107 @@
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 (Expo)
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)'
21
+ required: false
22
+ default: 'http://localhost:3000'
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 web export
63
+ run: npx expo export --platform web
64
+
65
+ - name: Start static server
66
+ run: |
67
+ npx serve dist -l 3000 &
68
+ SERVER_PID=$!
69
+ echo "SERVER_PID=$SERVER_PID" >> $GITHUB_ENV
70
+ sleep 5
71
+ curl -sf http://localhost:3000 > /dev/null || (echo "Static server failed to start" && exit 1)
72
+
73
+ - name: Check for ZAP rules file
74
+ id: check_rules
75
+ run: |
76
+ if [ -f "${{ inputs.zap_rules_file }}" ]; then
77
+ echo "has_rules=true" >> $GITHUB_OUTPUT
78
+ else
79
+ echo "has_rules=false" >> $GITHUB_OUTPUT
80
+ fi
81
+
82
+ - name: Run ZAP baseline scan
83
+ uses: zaproxy/action-baseline@v0.14.0
84
+ with:
85
+ target: ${{ inputs.zap_target_url }}
86
+ rules_file_name: ${{ steps.check_rules.outputs.has_rules == 'true' && inputs.zap_rules_file || '' }}
87
+ fail_action: true
88
+ allow_issue_writing: false
89
+ artifact_name: 'zap-report-expo'
90
+
91
+ - name: Stop static server
92
+ if: always()
93
+ run: |
94
+ if [ -n "$SERVER_PID" ]; then
95
+ kill "$SERVER_PID" 2>/dev/null || true
96
+ fi
97
+
98
+ - name: Upload ZAP report
99
+ if: always()
100
+ uses: actions/upload-artifact@v4
101
+ with:
102
+ name: zap-baseline-report-expo-${{ github.run_id }}
103
+ path: |
104
+ zap-report.html
105
+ zap-report.json
106
+ zap-report.md
107
+ retention-days: 14
@@ -0,0 +1,36 @@
1
+ # OWASP ZAP Baseline Scan Configuration — Expo Web Apps
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 Expo static web exports served behind CDN/reverse proxy in production.
6
+ # Rules that are infrastructure-level (handled by CDN/hosting) are set to WARN.
7
+
8
+ # CSP header — Expo web exports typically need inline scripts for hydration;
9
+ # CSP is best enforced at the CDN/hosting layer with appropriate nonces.
10
+ 10038 WARN (Content Security Policy (CSP) Header Not Set)
11
+
12
+ # X-Content-Type-Options — should be set at CDN/hosting layer
13
+ 10021 WARN (X-Content-Type-Options Header Missing)
14
+
15
+ # Strict-Transport-Security — enforced at CDN/load balancer level
16
+ 10035 WARN (Strict-Transport-Security Header Not Set)
17
+
18
+ # X-Frame-Options — enforced at CDN/hosting layer; CSP frame-ancestors preferred
19
+ 10020 WARN (X-Frame-Options Header Not Set)
20
+
21
+ # Permissions-Policy — not critical for most Expo apps but good practice
22
+ 10063 WARN (Permissions Policy Header Not Set)
23
+
24
+ # Server header disclosure — static server in CI, not production concern
25
+ 10036 WARN (Server Leaks Version Information via "Server" HTTP Response Header Field)
26
+
27
+ # Cookie flags — Expo static sites typically don't set cookies
28
+ 10010 IGNORE (Cookie No HttpOnly Flag)
29
+ 10011 IGNORE (Cookie Without Secure Flag)
30
+ 10054 IGNORE (Cookie without SameSite Attribute)
31
+
32
+ # Information disclosure in URL — Expo router may use query params legitimately
33
+ 10024 WARN (Information Disclosure - Sensitive Information in URL)
34
+
35
+ # Cross-domain JavaScript source — Expo uses CDN-hosted scripts legitimately
36
+ 10017 WARN (Cross-Domain JavaScript Source File Inclusion)
@@ -6,14 +6,32 @@
6
6
  /**
7
7
  * Jest Configuration - Expo Stack
8
8
  *
9
- * Provides Expo/React Native-specific Jest configuration.
10
- * Extends the base Jest utilities for coverage thresholds and merging.
9
+ * Provides Expo/React Native-specific Jest configuration without using
10
+ * the `jest-expo` preset directly. The preset's `setupFiles` include
11
+ * `react-native/jest/setup.js` which redefines `window` via
12
+ * `Object.defineProperties` — incompatible with jsdom's non-configurable
13
+ * `window` property, causing "Cannot redefine property: window" errors.
14
+ *
15
+ * Instead, this config manually replicates the preset's resolution,
16
+ * transform, and haste settings without any preset setupFiles.
17
+ *
18
+ * `setupFiles` is intentionally empty because `jest-expo/src/preset/setup.js`
19
+ * requires `__DEV__` to be defined before it runs, and `mergeConfigs`
20
+ * concatenates arrays with base entries first — making it impossible for
21
+ * project-local setupFiles to prepend a `__DEV__` definition. Projects
22
+ * should add their own setupFiles in `jest.config.local.ts` with the
23
+ * correct ordering (define globals first, then load jest-expo setup).
24
+ *
25
+ * Coverage collection is scoped to standard Expo source directories
26
+ * rather than a catch-all glob, preventing config files, scripts, and
27
+ * plugins from distorting coverage numbers.
11
28
  *
12
29
  * Inheritance chain:
13
30
  * jest.expo.ts (this file)
14
31
  * └── jest.base.ts
15
32
  *
16
33
  * @see https://jestjs.io/docs/configuration
34
+ * @see https://github.com/expo/expo/issues/40184
17
35
  * @module jest.expo
18
36
  */
19
37
  import type { Config } from "jest";
@@ -47,27 +65,55 @@ interface ExpoJestOptions {
47
65
  * @param options - Configuration options for threshold overrides
48
66
  * @param options.thresholds - Coverage thresholds (merged defaults + project overrides)
49
67
  * @returns Jest config object with jsdom environment, babel-jest transform, and React Native resolver
50
- * @remarks Uses jest-expo preset which provides platform-specific test resolution
51
- * and proper React Native module mocking out of the box.
68
+ * @remarks Avoids `jest-expo` preset to prevent jsdom + `react-native/jest/setup.js`
69
+ * incompatibility. Manually configures haste, resolver, and transform to match the
70
+ * preset's behavior without the problematic window redefinition. `setupFiles` is
71
+ * empty — projects must provide their own in `jest.config.local.ts` with correct
72
+ * ordering (define `__DEV__` before loading `jest-expo/src/preset/setup.js`).
52
73
  */
53
74
  export const getExpoJestConfig = ({
54
75
  thresholds = defaultThresholds,
55
76
  }: ExpoJestOptions = {}): Config => ({
56
- preset: "jest-expo",
57
77
  testEnvironment: "jsdom",
58
- testMatch: [
59
- "<rootDir>/**/*.test.ts",
60
- "<rootDir>/**/*.test.tsx",
61
- "<rootDir>/**/__tests__/**/*.ts",
62
- "<rootDir>/**/__tests__/**/*.tsx",
63
- ],
78
+ haste: {
79
+ defaultPlatform: "ios",
80
+ platforms: ["android", "ios", "native"],
81
+ },
82
+ resolver: "react-native/jest/resolver.js",
83
+ setupFiles: [],
84
+ transform: {
85
+ "\\.[jt]sx?$": [
86
+ "babel-jest",
87
+ {
88
+ caller: {
89
+ name: "metro",
90
+ bundler: "metro",
91
+ platform: "ios",
92
+ },
93
+ },
94
+ ],
95
+ "^.+\\.(bmp|gif|jpg|jpeg|mp4|png|psd|svg|webp|ttf|otf|woff|woff2)$":
96
+ "jest-expo/src/preset/assetFileTransformer.js",
97
+ },
98
+ testMatch: ["<rootDir>/**/*.test.ts", "<rootDir>/**/*.test.tsx"],
64
99
  testPathIgnorePatterns: ["/node_modules/", "/dist/", "/.expo/"],
65
100
  transformIgnorePatterns: [
66
- "node_modules/(?!((jest-)?react-native|@react-native(-community)?)|expo(nent)?|@expo(nent)?/.*|@expo-google-fonts/.*|react-navigation|@react-navigation/.*|@sentry/react-native|native-base|react-native-svg|@gluestack-ui/.*|@gluestack-style/.*|nativewind|react-native-css-interop)",
101
+ "node_modules/(?!((jest-)?react-native|@react-native(-community)?)|expo(nent)?|@expo(nent)?/.*|@expo-google-fonts/.*|react-navigation|@react-navigation/.*|@sentry/react-native|native-base|react-native-svg|@gluestack-ui/.*|@gluestack-style/.*|nativewind|react-native-css-interop|react-native-reanimated|react-native-worklets|lucide-react-native|@gorhom|@shopify)",
67
102
  ],
68
103
  moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"],
69
104
  collectCoverageFrom: [
70
- "**/*.{ts,tsx}",
105
+ "app/**/*.{ts,tsx}",
106
+ "components/**/*.{ts,tsx}",
107
+ "config/**/*.{ts,tsx}",
108
+ "constants/**/*.{ts,tsx}",
109
+ "features/**/*.{ts,tsx}",
110
+ "hooks/**/*.{ts,tsx}",
111
+ "lib/**/*.{ts,tsx}",
112
+ "providers/**/*.{ts,tsx}",
113
+ "shared/**/*.{ts,tsx}",
114
+ "stores/**/*.{ts,tsx}",
115
+ "types/**/*.{ts,tsx}",
116
+ "utils/**/*.{ts,tsx}",
71
117
  "!**/*.d.ts",
72
118
  ...defaultCoverageExclusions,
73
119
  ],
@@ -53,7 +53,7 @@
53
53
  "**/metro.config.js",
54
54
  "**/babel.config.js"
55
55
  ],
56
- "ignoreBinaries": ["audit", "ast-grep", "maestro", "eas", "source-map-explorer"],
56
+ "ignoreBinaries": ["ast-grep", "audit", "eas", "maestro", "serve", "source-map-explorer"],
57
57
  "ignoreDependencies": [
58
58
  "@dnd-kit/modifiers",
59
59
  "@expo-google-fonts/inter",