@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.
- package/all/copy-overwrite/.claude/hooks/enforce-plan-rules.sh +15 -0
- package/all/copy-overwrite/.claude/hooks/track-plan-sessions.sh +155 -0
- package/all/copy-overwrite/.claude/rules/plan.md +29 -0
- package/all/copy-overwrite/.claude/settings.json +30 -0
- package/all/copy-overwrite/CLAUDE.md +2 -9
- package/cdk/copy-overwrite/tsconfig.eslint.json +2 -1
- package/cdk/create-only/cdk.json +23 -0
- package/expo/copy-overwrite/.claude/skills/owasp-zap/SKILL.md +56 -0
- package/expo/copy-overwrite/.claude/skills/testing-library/SKILL.md +5 -10
- package/expo/copy-overwrite/.github/workflows/zap-baseline.yml +107 -0
- package/expo/copy-overwrite/.zap/baseline.conf +36 -0
- package/expo/copy-overwrite/jest.expo.ts +59 -13
- package/expo/copy-overwrite/knip.json +1 -1
- package/expo/copy-overwrite/scripts/zap-baseline.sh +92 -0
- package/expo/copy-overwrite/tsconfig.eslint.json +2 -1
- package/expo/copy-overwrite/tsconfig.expo.json +7 -0
- package/expo/copy-overwrite/tsconfig.json +3 -1
- package/expo/create-only/.github/workflows/ci.yml +8 -0
- package/expo/create-only/babel.config.js +27 -0
- package/expo/create-only/jest.config.local.ts +34 -0
- package/expo/create-only/jest.config.react-native-mock.js +88 -0
- package/expo/create-only/jest.setup.pre.js +106 -0
- package/expo/create-only/jest.setup.ts +118 -0
- package/expo/create-only/tsconfig.local.json +1 -7
- package/expo/package-lisa/package.lisa.json +4 -2
- package/nestjs/copy-overwrite/.github/workflows/zap-baseline.yml +123 -0
- package/nestjs/copy-overwrite/.zap/baseline.conf +39 -0
- package/nestjs/copy-overwrite/scripts/zap-baseline.sh +99 -0
- package/nestjs/copy-overwrite/tsconfig.eslint.json +10 -0
- package/nestjs/copy-overwrite/tsconfig.nestjs.json +4 -1
- package/nestjs/create-only/.github/workflows/ci.yml +8 -0
- package/nestjs/package-lisa/package.lisa.json +2 -1
- package/package.json +1 -1
- package/typescript/copy-contents/.husky/pre-push +5 -1
- package/typescript/copy-overwrite/.claude/commands/security/zap-scan.md +12 -0
- package/typescript/copy-overwrite/.claude/settings.json +10 -0
- package/typescript/copy-overwrite/.github/workflows/quality.yml +100 -5
- package/typescript/copy-overwrite/eslint.base.ts +1 -1
- package/typescript/copy-overwrite/eslint.ignore.config.json +1 -0
- package/typescript/copy-overwrite/jest.base.ts +1 -0
- 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
|
-
###
|
|
193
|
+
### Manual React Native Resolution (No Preset)
|
|
194
194
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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
|
-
*
|
|
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
|
|
51
|
-
*
|
|
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
|
-
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
"
|
|
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", "
|
|
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",
|