@js-recon/js-recon 1.4.1-alpha.9 → 1.4.1-beta.2

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 (78) hide show
  1. package/.claude-sync/capture-new.sh +61 -0
  2. package/.claude-sync/capture-scan.sh +55 -0
  3. package/.claude-sync/hooks/post-checkout +16 -0
  4. package/.claude-sync/hooks/pre-commit +3 -0
  5. package/.claude-sync/setup.sh +17 -0
  6. package/.claude-sync/sync.sh +64 -0
  7. package/.claude-sync/watch.sh +17 -0
  8. package/.github/workflows/build-and-prettify.yaml +42 -0
  9. package/.github/workflows/promote-js-recon.yml +299 -0
  10. package/.vscode/tasks.json +42 -0
  11. package/CHANGELOG.md +35 -0
  12. package/Dockerfile.w-rules +13 -0
  13. package/README.md +4 -2
  14. package/build/analyze/helpers/initRules.js +28 -6
  15. package/build/analyze/helpers/initRules.js.map +1 -1
  16. package/build/analyze/index.js +3 -2
  17. package/build/analyze/index.js.map +1 -1
  18. package/build/completion/index.js +17 -11
  19. package/build/completion/index.js.map +1 -1
  20. package/build/globalConfig.js +1 -1
  21. package/build/globalConfig.js.map +1 -1
  22. package/build/index.js +55 -23
  23. package/build/index.js.map +1 -1
  24. package/build/lazyLoad/generic/generic_jsMimeTypes.js +19 -0
  25. package/build/lazyLoad/generic/generic_jsMimeTypes.js.map +1 -0
  26. package/build/lazyLoad/index.js +1 -0
  27. package/build/lazyLoad/index.js.map +1 -1
  28. package/build/lazyLoad/next_js/next_GetLazyResourcesWebpackJs.js +8 -2
  29. package/build/lazyLoad/next_js/next_GetLazyResourcesWebpackJs.js.map +1 -1
  30. package/build/lazyLoad/techDetect/checkInterceptedEvidence.js +18 -0
  31. package/build/lazyLoad/techDetect/checkInterceptedEvidence.js.map +1 -0
  32. package/build/lazyLoad/techDetect/index.js +58 -13
  33. package/build/lazyLoad/techDetect/index.js.map +1 -1
  34. package/build/mcp/mcpServer.js +1 -2
  35. package/build/mcp/mcpServer.js.map +1 -1
  36. package/build/mcp/tools.js +1 -2
  37. package/build/mcp/tools.js.map +1 -1
  38. package/build/proxy/awsConfig.js +45 -0
  39. package/build/proxy/awsConfig.js.map +1 -0
  40. package/build/proxy/checkFeasibility.js.map +1 -0
  41. package/build/proxy/checkFireWallBlocking.js.map +1 -0
  42. package/build/proxy/configFile.js +31 -0
  43. package/build/proxy/configFile.js.map +1 -0
  44. package/build/{api_gateway → proxy}/genReq.js +4 -5
  45. package/build/proxy/genReq.js.map +1 -0
  46. package/build/proxy/genericProxy.js +21 -0
  47. package/build/proxy/genericProxy.js.map +1 -0
  48. package/build/proxy/index.js +421 -0
  49. package/build/proxy/index.js.map +1 -0
  50. package/build/proxy/oxylabsProxy.js +34 -0
  51. package/build/proxy/oxylabsProxy.js.map +1 -0
  52. package/build/proxy/resolveProxyConfig.js +38 -0
  53. package/build/proxy/resolveProxyConfig.js.map +1 -0
  54. package/build/report/utility/initGlobalReportDb.js +111 -0
  55. package/build/report/utility/initGlobalReportDb.js.map +1 -0
  56. package/build/report/utility/mergeDomainIntoGlobalDb.js +39 -0
  57. package/build/report/utility/mergeDomainIntoGlobalDb.js.map +1 -0
  58. package/build/run/index.js +110 -74
  59. package/build/run/index.js.map +1 -1
  60. package/build/run/interruptHandler.js +17 -1
  61. package/build/run/interruptHandler.js.map +1 -1
  62. package/build/utility/configureProxy.js +60 -0
  63. package/build/utility/configureProxy.js.map +1 -0
  64. package/build/utility/globals.js +108 -13
  65. package/build/utility/globals.js.map +1 -1
  66. package/build/utility/makeReq.js +103 -10
  67. package/build/utility/makeReq.js.map +1 -1
  68. package/package.json +5 -1
  69. package/.claude/settings.local.json +0 -10
  70. package/CLAUDE.md +0 -629
  71. package/build/api_gateway/checkFeasibility.js.map +0 -1
  72. package/build/api_gateway/checkFireWallBlocking.js.map +0 -1
  73. package/build/api_gateway/genReq.js.map +0 -1
  74. package/build/api_gateway/index.js +0 -291
  75. package/build/api_gateway/index.js.map +0 -1
  76. package/urls.txt +0 -5
  77. /package/build/{api_gateway → proxy}/checkFeasibility.js +0 -0
  78. /package/build/{api_gateway → proxy}/checkFireWallBlocking.js +0 -0
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env bash
2
+ # Intercepts newly-added real CLAUDE.md files at commit time, redirects their
3
+ # content into the sibling js-recon-agentic-files repo on the current branch,
4
+ # and replaces them with a symlink — so they never land in this repo's history.
5
+ set -euo pipefail
6
+
7
+ JS_RECON_ROOT="$(git rev-parse --show-toplevel)"
8
+ AGENTIC_REPO_PATH="${JSR_AGENTIC_FILES_PATH:-$JS_RECON_ROOT/../js-recon-agentic-files}"
9
+ CURRENT_BRANCH="$(git -C "$JS_RECON_ROOT" symbolic-ref --short HEAD)"
10
+
11
+ cd "$JS_RECON_ROOT"
12
+
13
+ staged_real_claude_md="$(git diff --cached --name-only --diff-filter=ACM | grep -E '(^|/)CLAUDE\.md$' || true)"
14
+
15
+ if [ -z "$staged_real_claude_md" ]; then
16
+ exit 0
17
+ fi
18
+
19
+ captured=0
20
+ while IFS= read -r f; do
21
+ [ -z "$f" ] && continue
22
+
23
+ if [ -L "$f" ]; then
24
+ continue
25
+ fi
26
+
27
+ dest="$AGENTIC_REPO_PATH/$f"
28
+ mkdir -p "$(dirname "$dest")"
29
+ cp "$JS_RECON_ROOT/$f" "$dest"
30
+
31
+ (
32
+ cd "$AGENTIC_REPO_PATH"
33
+ if ! git show-ref --verify --quiet "refs/heads/$CURRENT_BRANCH"; then
34
+ git fetch origin --quiet || true
35
+ if git show-ref --verify --quiet "refs/remotes/origin/$CURRENT_BRANCH"; then
36
+ git checkout -q "$CURRENT_BRANCH"
37
+ else
38
+ git checkout -q -b "$CURRENT_BRANCH"
39
+ fi
40
+ else
41
+ git checkout -q "$CURRENT_BRANCH"
42
+ fi
43
+ git add "$f"
44
+ git commit -q -m "sync: capture $f from js-recon@$CURRENT_BRANCH"
45
+ git push -q origin "$CURRENT_BRANCH"
46
+ )
47
+
48
+ git reset -q HEAD -- "$f" >/dev/null 2>&1 || true
49
+ rm -f "$JS_RECON_ROOT/$f"
50
+ ln -s "$dest" "$JS_RECON_ROOT/$f"
51
+ echo "[claude-sync] captured $f into js-recon-agentic-files (branch $CURRENT_BRANCH), replaced with symlink"
52
+ captured=1
53
+ done <<< "$staged_real_claude_md"
54
+
55
+ if [ "$captured" -eq 1 ]; then
56
+ echo "[claude-sync] commit aborted — CLAUDE.md file(s) were redirected to js-recon-agentic-files."
57
+ echo "[claude-sync] please 'git add' any remaining changes and re-run 'git commit'."
58
+ exit 1
59
+ fi
60
+
61
+ exit 0
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env bash
2
+ # Walks the working tree for real (non-symlink) CLAUDE.md / .claude/skills /
3
+ # .claude/agents files and captures each into the sibling js-recon-agentic-files
4
+ # repo on the current branch, replacing it with a symlink. This is the primary
5
+ # "new file" trigger — .gitignore refuses `git add` on these paths outright, so
6
+ # a git-staged-diff-based hook (see capture-new.sh) never sees them; a
7
+ # filesystem scan is required.
8
+ set -euo pipefail
9
+
10
+ JS_RECON_ROOT="$(git rev-parse --show-toplevel)"
11
+ AGENTIC_REPO_PATH="${JSR_AGENTIC_FILES_PATH:-$JS_RECON_ROOT/../js-recon-agentic-files}"
12
+
13
+ # Optional $1: attribute captures to this branch instead of the current HEAD.
14
+ # Used by the post-checkout hook, which only runs *after* HEAD has already
15
+ # moved to the new branch — git has no pre-checkout hook, so any real file
16
+ # left over from the branch being switched away from must be attributed to
17
+ # that branch explicitly, or it would be mis-captured onto the new branch.
18
+ CURRENT_BRANCH="${1:-$(git -C "$JS_RECON_ROOT" symbolic-ref --short HEAD 2>/dev/null || echo "")}"
19
+
20
+ if [ -z "$CURRENT_BRANCH" ] || [ ! -d "$AGENTIC_REPO_PATH/.git" ]; then
21
+ exit 0
22
+ fi
23
+
24
+ captured=0
25
+ while IFS= read -r f; do
26
+ [ -z "$f" ] && continue
27
+ rel="${f#$JS_RECON_ROOT/}"
28
+ dest="$AGENTIC_REPO_PATH/$rel"
29
+
30
+ mkdir -p "$(dirname "$dest")"
31
+ cp "$f" "$dest"
32
+
33
+ (
34
+ cd "$AGENTIC_REPO_PATH"
35
+ if git show-ref --verify --quiet "refs/heads/$CURRENT_BRANCH"; then
36
+ git checkout -q "$CURRENT_BRANCH"
37
+ elif git show-ref --verify --quiet "refs/remotes/origin/$CURRENT_BRANCH"; then
38
+ git checkout -q "$CURRENT_BRANCH"
39
+ else
40
+ git checkout -q -b "$CURRENT_BRANCH"
41
+ fi
42
+ git add "$rel"
43
+ git commit -q -m "sync: capture $rel from js-recon@$CURRENT_BRANCH"
44
+ git push -q origin "$CURRENT_BRANCH" 2>/dev/null || echo "[claude-sync] WARNING: push failed for $rel (branch may need 'git push -u origin $CURRENT_BRANCH' manually)"
45
+ )
46
+
47
+ rm -f "$f"
48
+ ln -s "$dest" "$f"
49
+ echo "[claude-sync] captured $rel into js-recon-agentic-files (branch $CURRENT_BRANCH), replaced with symlink"
50
+ captured=1
51
+ done < <(find "$JS_RECON_ROOT" \( -name "CLAUDE.md" -o -path "*/.claude/skills/*" -o -path "*/.claude/agents/*" \) -not -path "*/.git/*" -not -path "*/node_modules/*" -not -path "*/build/*" -not -path "$AGENTIC_REPO_PATH/*" -type f)
52
+
53
+ if [ "$captured" -eq 0 ]; then
54
+ exit 0
55
+ fi
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env bash
2
+ # git post-checkout hook: re-syncs CLAUDE.md symlinks whenever a branch switch happens.
3
+ prev_head="$1"
4
+ new_head="$2"
5
+ branch_checkout="$3"
6
+
7
+ if [ "$branch_checkout" = "1" ]; then
8
+ ROOT="$(git rev-parse --show-toplevel)"
9
+ # Resolve which branch prev_head belongs to (the branch we're leaving) so any
10
+ # leftover real CLAUDE.md file gets attributed correctly — HEAD has already
11
+ # moved to the new branch by the time this hook runs, and git has no
12
+ # pre-checkout hook to capture beforehand.
13
+ prev_branch="$(git for-each-ref --points-at="$prev_head" --format='%(refname:short)' refs/heads/ 2>/dev/null | head -1)"
14
+ "$ROOT/.claude-sync/capture-scan.sh" "$prev_branch"
15
+ "$ROOT/.claude-sync/sync.sh"
16
+ fi
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env bash
2
+ # git pre-commit hook: redirects newly-staged real CLAUDE.md files to js-recon-agentic-files.
3
+ exec "$(git rev-parse --show-toplevel)/.claude-sync/capture-new.sh"
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env bash
2
+ # One-time collaborator setup: installs the git hooks and runs an initial sync.
3
+ # Requires js-recon-agentic-files to already be cloned as a sibling directory
4
+ # (../js-recon-agentic-files) or JSR_AGENTIC_FILES_PATH to point at it.
5
+ set -euo pipefail
6
+
7
+ JS_RECON_ROOT="$(git rev-parse --show-toplevel)"
8
+
9
+ chmod +x "$JS_RECON_ROOT/.claude-sync/sync.sh"
10
+ chmod +x "$JS_RECON_ROOT/.claude-sync/capture-new.sh"
11
+ chmod +x "$JS_RECON_ROOT/.claude-sync/hooks/post-checkout"
12
+ chmod +x "$JS_RECON_ROOT/.claude-sync/hooks/pre-commit"
13
+
14
+ git -C "$JS_RECON_ROOT" config core.hooksPath .claude-sync/hooks
15
+
16
+ echo "[claude-sync] hooksPath configured, running initial sync..."
17
+ "$JS_RECON_ROOT/.claude-sync/sync.sh"
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env bash
2
+ # Syncs CLAUDE.md / .claude/skills / .claude/agents symlinks from the sibling
3
+ # js-recon-agentic-files checkout to match the currently checked-out branch of
4
+ # this repo.
5
+ set -euo pipefail
6
+
7
+ JS_RECON_ROOT="$(git rev-parse --show-toplevel)"
8
+ AGENTIC_REPO_PATH="${JSR_AGENTIC_FILES_PATH:-$JS_RECON_ROOT/../js-recon-agentic-files}"
9
+ FALLBACK_BRANCH="${JSR_AGENTIC_FALLBACK_BRANCH:-dev}"
10
+
11
+ CURRENT_BRANCH="$(git -C "$JS_RECON_ROOT" symbolic-ref --short HEAD 2>/dev/null || echo "")"
12
+
13
+ if [ -z "$CURRENT_BRANCH" ]; then
14
+ echo "[claude-sync] detached HEAD, skipping sync"
15
+ exit 0
16
+ fi
17
+
18
+ if [ ! -d "$AGENTIC_REPO_PATH/.git" ]; then
19
+ echo "[claude-sync] agentic-files repo not found at $AGENTIC_REPO_PATH — see js-recon-agentic-files/README.md for setup"
20
+ exit 0
21
+ fi
22
+
23
+ (
24
+ cd "$AGENTIC_REPO_PATH"
25
+ git fetch origin --quiet
26
+
27
+ if git show-ref --verify --quiet "refs/remotes/origin/$CURRENT_BRANCH"; then
28
+ git checkout -B "$CURRENT_BRANCH" "origin/$CURRENT_BRANCH" --quiet
29
+ else
30
+ echo "[claude-sync] WARNING: no 'origin/$CURRENT_BRANCH' branch in agentic-files repo — falling back to 'origin/$FALLBACK_BRANCH'"
31
+ git checkout -B "$CURRENT_BRANCH" "origin/$FALLBACK_BRANCH" --quiet
32
+ fi
33
+ )
34
+
35
+ # 1. Remove stale symlinks that point into the agentic checkout but whose target
36
+ # no longer exists on this branch (e.g. switched to a branch with fewer files).
37
+ while IFS= read -r link; do
38
+ target="$(readlink "$link")"
39
+ case "$target" in
40
+ "$AGENTIC_REPO_PATH"/*)
41
+ if [ ! -e "$target" ]; then
42
+ rm "$link"
43
+ echo "[claude-sync] removed stale symlink: ${link#$JS_RECON_ROOT/}"
44
+ fi
45
+ ;;
46
+ esac
47
+ done < <(find "$JS_RECON_ROOT" -type l \( -name "CLAUDE.md" -o -path "*/.claude/skills/*" -o -path "*/.claude/agents/*" \) -not -path "*/.git/*" -not -path "*/node_modules/*" -not -path "*/build/*")
48
+
49
+ # 2. Create/update symlinks for every CLAUDE.md (and .claude/skills / .claude/agents
50
+ # entry) present on this branch in the agentic checkout.
51
+ while IFS= read -r src; do
52
+ rel="${src#$AGENTIC_REPO_PATH/}"
53
+ dest="$JS_RECON_ROOT/$rel"
54
+
55
+ if [ -e "$dest" ] && [ ! -L "$dest" ]; then
56
+ echo "[claude-sync] WARNING: $rel exists as a real (non-symlink) file — not overwriting. Stage it to trigger capture, or remove it manually."
57
+ continue
58
+ fi
59
+
60
+ mkdir -p "$(dirname "$dest")"
61
+ ln -sf "$src" "$dest"
62
+ done < <(find "$AGENTIC_REPO_PATH" \( -name "CLAUDE.md" -o -path "*/.claude/skills/*" -o -path "*/.claude/agents/*" \) -type f -not -path "*/.git/*")
63
+
64
+ echo "[claude-sync] synced CLAUDE.md files for branch '$CURRENT_BRANCH'"
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env bash
2
+ # Background watcher intended to be run as a VS Code task (runOn: folderOpen,
3
+ # isBackground: true). Polls for new real CLAUDE.md files every few seconds and
4
+ # captures them immediately via capture-scan.sh, so a file created interactively
5
+ # (e.g. by an editor or an agent session) gets redirected without waiting for the
6
+ # next branch switch or commit.
7
+ set -uo pipefail
8
+
9
+ ROOT="$(git rev-parse --show-toplevel)"
10
+ INTERVAL="${JSR_WATCH_INTERVAL:-3}"
11
+
12
+ echo "[claude-sync] watching for new CLAUDE.md files (interval: ${INTERVAL}s)"
13
+
14
+ while true; do
15
+ "$ROOT/.claude-sync/capture-scan.sh"
16
+ sleep "$INTERVAL"
17
+ done
@@ -136,6 +136,8 @@ jobs:
136
136
  run: npm run build
137
137
  build-docker:
138
138
  needs: build
139
+ permissions:
140
+ contents: read
139
141
  runs-on: ubuntu-latest
140
142
  steps:
141
143
  - name: Checkout
@@ -167,6 +169,9 @@ jobs:
167
169
  with:
168
170
  repository: js-recon/js-recon-rules
169
171
  path: js-recon-rules
172
+ # Keep the checkout token out of js-recon-rules/.git/config — this
173
+ # context is consumed by later build/run steps (incl. Dockerfile.w-rules).
174
+ persist-credentials: false
170
175
 
171
176
  - name: Set up Node.js
172
177
  uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
@@ -223,6 +228,40 @@ jobs:
223
228
  path: output/localhost_3001/analyze.json
224
229
  if-no-files-found: ignore
225
230
 
231
+ - name: Build the rules-bundled (-w-rules) image
232
+ run: docker build -f Dockerfile.w-rules --build-arg BASE_IMAGE=js-recon/js-recon -t js-recon/js-recon-w-rules .
233
+
234
+ - name: Run js-recon-w-rules via Docker against lab app (no rules mount, no -r)
235
+ run: |
236
+ set -o pipefail
237
+ mkdir -p output-w-rules
238
+ docker run --name js-recon-w-rules-run \
239
+ --network host \
240
+ --entrypoint /bin/bash \
241
+ js-recon/js-recon-w-rules \
242
+ -c 'printf "http://localhost:3001\n" > /tmp/urls.txt && node build/index.js run -u /tmp/urls.txt --no-sandbox -y -k' \
243
+ | tee docker-w-rules-run.log
244
+ docker cp js-recon-w-rules-run:/home/pptruser/output/. ./output-w-rules/ 2>/dev/null || true
245
+ docker rm js-recon-w-rules-run
246
+
247
+ - name: Verify the version check was actually skipped
248
+ run: |
249
+ grep -q "Rules version check disabled" docker-w-rules-run.log || {
250
+ echo "Expected log line confirming JS_RECON_DISABLE_RULES_VERSION_CHECK was honored not found"
251
+ exit 1
252
+ }
253
+
254
+ - name: Verify all rules fired (w-rules image)
255
+ run: node scripts/smoke-test.js output-w-rules/localhost_3001/analyze.json
256
+
257
+ - name: Upload w-rules analyze.json on failure
258
+ if: failure()
259
+ uses: actions/upload-artifact@v4
260
+ with:
261
+ name: analyze-json-docker-w-rules
262
+ path: output-w-rules/localhost_3001/analyze.json
263
+ if-no-files-found: ignore
264
+
226
265
  integration-test-macos:
227
266
  needs: build
228
267
  runs-on: macos-latest
@@ -244,6 +283,9 @@ jobs:
244
283
  with:
245
284
  repository: js-recon/js-recon-rules
246
285
  path: js-recon-rules
286
+ # Keep the checkout token out of js-recon-rules/.git/config — this
287
+ # context is consumed by later build/run steps (incl. Dockerfile.w-rules).
288
+ persist-credentials: false
247
289
 
248
290
  - name: Set up Node.js
249
291
  uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
@@ -78,6 +78,15 @@ jobs:
78
78
  packages: write
79
79
  steps:
80
80
  - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
81
+ - name: Checkout js-recon-rules
82
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
83
+ with:
84
+ repository: js-recon/js-recon-rules
85
+ path: js-recon-rules
86
+ # Don't persist the checkout token into js-recon-rules/.git/config —
87
+ # Dockerfile.w-rules builds from this context and would otherwise bake
88
+ # the writable GHCR credential into an image layer.
89
+ persist-credentials: false
81
90
  - uses: docker/login-action@v3
82
91
  with:
83
92
  registry: ghcr.io
@@ -102,3 +111,293 @@ jobs:
102
111
 
103
112
  env:
104
113
  VERSION: ${{ inputs.version }}
114
+
115
+ - name: Build and push the rules-bundled (-w-rules) variant
116
+ run: |
117
+ # layer js-recon-rules on top of the image just built/pushed above
118
+ docker build -f Dockerfile.w-rules --build-arg BASE_IMAGE=ghcr.io/js-recon/js-recon:$VERSION -t ghcr.io/js-recon/js-recon:$VERSION-w-rules .
119
+
120
+ TAG="latest"
121
+ [[ "$VERSION" == *"beta"* ]] && TAG="beta"
122
+ [[ "$VERSION" == *"alpha"* ]] && TAG="alpha"
123
+
124
+ docker tag ghcr.io/js-recon/js-recon:$VERSION-w-rules ghcr.io/js-recon/js-recon:$TAG-w-rules
125
+
126
+ docker push ghcr.io/js-recon/js-recon:$VERSION-w-rules
127
+ docker push ghcr.io/js-recon/js-recon:$TAG-w-rules
128
+ env:
129
+ VERSION: ${{ inputs.version }}
130
+
131
+ verify-npm-install:
132
+ runs-on: ubuntu-latest
133
+ permissions:
134
+ contents: read
135
+ steps:
136
+ - name: Checkout js-recon-labs
137
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
138
+ with:
139
+ repository: js-recon/js-recon-labs
140
+ path: js-recon-labs
141
+ persist-credentials: false
142
+
143
+ - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
144
+ with:
145
+ node-version: "22"
146
+
147
+ - name: Install js-recon globally from npm
148
+ run: npm install -g "@js-recon/js-recon@${VERSION}"
149
+ env:
150
+ VERSION: ${{ inputs.version }}
151
+
152
+ - name: Verify installed version
153
+ run: |
154
+ INSTALLED=$(js-recon -V)
155
+ echo "Installed: ${INSTALLED}"
156
+ [[ "$INSTALLED" == *"$VERSION"* ]] || { echo "Version mismatch"; exit 1; }
157
+ env:
158
+ VERSION: ${{ inputs.version }}
159
+
160
+ - name: Install and start vulnerable app
161
+ working-directory: js-recon-labs/next_js/vuln-all-rules
162
+ run: |
163
+ npm ci
164
+ npm run build
165
+ npm start &
166
+
167
+ - name: Wait for app to be ready
168
+ run: timeout 60 bash -c 'until curl -sf --max-time 2 http://localhost:3001 > /dev/null; do sleep 2; done'
169
+
170
+ - name: Run installed js-recon against the lab app
171
+ run: |
172
+ echo "http://localhost:3001" > /tmp/npm-smoke-urls.txt
173
+ js-recon run -u /tmp/npm-smoke-urls.txt --no-sandbox -y -k
174
+ env:
175
+ PUPPETEER_EXECUTABLE_PATH: /usr/bin/google-chrome-stable
176
+
177
+ - name: Verify expected output files exist
178
+ run: |
179
+ test -n "$(find output -name analyze.json)" || { echo "analyze.json missing"; exit 1; }
180
+ test -n "$(find output -name js-recon.db)" || { echo "js-recon.db missing"; exit 1; }
181
+
182
+ verify-docker-install:
183
+ runs-on: ubuntu-latest
184
+ needs: publish-ghcr
185
+ permissions:
186
+ contents: read
187
+ packages: read
188
+ steps:
189
+ - name: Checkout js-recon-labs
190
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
191
+ with:
192
+ repository: js-recon/js-recon-labs
193
+ path: js-recon-labs
194
+ persist-credentials: false
195
+
196
+ - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
197
+ with:
198
+ node-version: "22"
199
+
200
+ - uses: docker/login-action@v3
201
+ with:
202
+ registry: ghcr.io
203
+ username: ${{ github.actor }}
204
+ password: ${{ secrets.GITHUB_TOKEN }}
205
+
206
+ - name: Verify installed version
207
+ run: |
208
+ INSTALLED=$(docker run --rm ghcr.io/js-recon/js-recon:${VERSION} -V)
209
+ echo "Installed: ${INSTALLED}"
210
+ [[ "$INSTALLED" == *"$VERSION"* ]] || { echo "Version mismatch"; exit 1; }
211
+ env:
212
+ VERSION: ${{ inputs.version }}
213
+
214
+ - name: Install and start vulnerable app
215
+ working-directory: js-recon-labs/next_js/vuln-all-rules
216
+ run: |
217
+ npm ci
218
+ npm run build
219
+ npm start &
220
+
221
+ - name: Wait for app to be ready
222
+ run: timeout 60 bash -c 'until curl -sf --max-time 2 http://localhost:3001 > /dev/null; do sleep 2; done'
223
+
224
+ - name: Run containerized js-recon against the lab app
225
+ run: |
226
+ # The release image runs as the non-root `pptruser` (see Dockerfile.release),
227
+ # so a bind-mounted host directory landing inside its home dir inherits the
228
+ # host-side owner/perms and can be unwritable to that user. Mount outside
229
+ # $HOME with permissive perms and point js-recon at it explicitly via `-o`
230
+ # rather than relying on its default CWD-relative output path.
231
+ mkdir -p /tmp/docker-output
232
+ chmod 777 /tmp/docker-output
233
+ echo "http://localhost:3001" > /tmp/docker-smoke-urls.txt
234
+ docker run --rm --network host \
235
+ -v /tmp/docker-output:/tmp/js-recon-output \
236
+ -v /tmp/docker-smoke-urls.txt:/tmp/urls.txt \
237
+ ghcr.io/js-recon/js-recon:${VERSION} \
238
+ run -u /tmp/urls.txt -o /tmp/js-recon-output --no-sandbox -y -k
239
+ env:
240
+ VERSION: ${{ inputs.version }}
241
+
242
+ - name: Verify expected output files exist
243
+ run: |
244
+ test -n "$(find /tmp/docker-output -name analyze.json)" || { echo "analyze.json missing"; exit 1; }
245
+ test -n "$(find /tmp/docker-output -name js-recon.db)" || { echo "js-recon.db missing"; exit 1; }
246
+
247
+ verify-docker-w-rules-install:
248
+ runs-on: ubuntu-latest
249
+ needs: publish-ghcr
250
+ permissions:
251
+ contents: read
252
+ packages: read
253
+ steps:
254
+ - name: Checkout js-recon
255
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
256
+
257
+ - name: Checkout js-recon-labs
258
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
259
+ with:
260
+ repository: js-recon/js-recon-labs
261
+ path: js-recon-labs
262
+ persist-credentials: false
263
+
264
+ - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
265
+ with:
266
+ node-version: "22"
267
+
268
+ - uses: docker/login-action@v3
269
+ with:
270
+ registry: ghcr.io
271
+ username: ${{ github.actor }}
272
+ password: ${{ secrets.GITHUB_TOKEN }}
273
+
274
+ - name: Install and start vulnerable app
275
+ working-directory: js-recon-labs/next_js/vuln-all-rules
276
+ run: |
277
+ npm ci
278
+ npm run build
279
+ npm start &
280
+
281
+ - name: Wait for app to be ready
282
+ run: timeout 60 bash -c 'until curl -sf --max-time 2 http://localhost:3001 > /dev/null; do sleep 2; done'
283
+
284
+ - name: Run the rules-bundled image against the lab app (no rules mount, no -r)
285
+ run: |
286
+ set -o pipefail
287
+ # Deliberately don't mount any rules directory and don't pass -r — the
288
+ # -w-rules image must be able to analyze using only what's baked in.
289
+ mkdir -p /tmp/docker-w-rules-output
290
+ chmod 777 /tmp/docker-w-rules-output
291
+ echo "http://localhost:3001" > /tmp/docker-w-rules-smoke-urls.txt
292
+ docker run --network host \
293
+ --name js-recon-w-rules-run \
294
+ -v /tmp/docker-w-rules-output:/tmp/js-recon-output \
295
+ -v /tmp/docker-w-rules-smoke-urls.txt:/tmp/urls.txt \
296
+ ghcr.io/js-recon/js-recon:${VERSION}-w-rules \
297
+ run -u /tmp/urls.txt -o /tmp/js-recon-output --no-sandbox -y -k \
298
+ | tee /tmp/docker-w-rules-run.log
299
+ docker rm js-recon-w-rules-run
300
+ env:
301
+ VERSION: ${{ inputs.version }}
302
+
303
+ - name: Verify the version check was actually skipped
304
+ run: |
305
+ grep -q "Rules version check disabled" /tmp/docker-w-rules-run.log || {
306
+ echo "Expected log line confirming JS_RECON_DISABLE_RULES_VERSION_CHECK was honored not found"
307
+ exit 1
308
+ }
309
+
310
+ - name: Verify all rules fired
311
+ run: node scripts/smoke-test.js "$(find /tmp/docker-w-rules-output -name analyze.json | head -n1)"
312
+
313
+ verify-brew-install:
314
+ runs-on: macos-latest
315
+ needs: update-homebrew-tap
316
+ permissions:
317
+ contents: read
318
+ strategy:
319
+ fail-fast: false
320
+ matrix:
321
+ formula: [js-recon, js-recon-alpha, js-recon-beta]
322
+ steps:
323
+ - name: Checkout js-recon-labs
324
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
325
+ with:
326
+ repository: js-recon/js-recon-labs
327
+ path: js-recon-labs
328
+ persist-credentials: false
329
+
330
+ - name: Tap and install
331
+ run: |
332
+ brew tap js-recon/tap
333
+ brew update
334
+ brew install "js-recon/tap/${{ matrix.formula }}"
335
+
336
+ - name: Determine expected formula for this version
337
+ id: expected
338
+ run: |
339
+ if [[ "$VERSION" == *"alpha"* ]]; then
340
+ echo "formula=js-recon-alpha" >> "$GITHUB_OUTPUT"
341
+ elif [[ "$VERSION" == *"beta"* ]]; then
342
+ echo "formula=js-recon-beta" >> "$GITHUB_OUTPUT"
343
+ else
344
+ echo "formula=js-recon" >> "$GITHUB_OUTPUT"
345
+ fi
346
+ env:
347
+ VERSION: ${{ inputs.version }}
348
+
349
+ - name: Resolve js-recon binary path
350
+ id: bin
351
+ run: |
352
+ # js-recon-alpha/js-recon-beta are keg-only (they share the `js-recon`
353
+ # binary name with the stable formula), so brew never symlinks them
354
+ # into PATH. Resolve the keg path directly instead of relying on PATH.
355
+ KEG_BIN="$(brew --prefix)/opt/${{ matrix.formula }}/bin/js-recon"
356
+ if [[ -x "$KEG_BIN" ]]; then
357
+ echo "bin=$KEG_BIN" >> "$GITHUB_OUTPUT"
358
+ else
359
+ echo "bin=js-recon" >> "$GITHUB_OUTPUT"
360
+ fi
361
+
362
+ - name: Verify installed version
363
+ run: |
364
+ INSTALLED=$("$BIN" -V)
365
+ echo "Installed: ${INSTALLED}"
366
+ if [[ "${{ matrix.formula }}" == "${{ steps.expected.outputs.formula }}" ]]; then
367
+ [[ "$INSTALLED" == *"$VERSION"* ]] || { echo "Version mismatch: expected ${VERSION} in ${{ matrix.formula }}"; exit 1; }
368
+ else
369
+ echo "Skipping version assertion — ${{ matrix.formula }} is not the channel updated by this promotion"
370
+ fi
371
+ env:
372
+ VERSION: ${{ inputs.version }}
373
+ BIN: ${{ steps.bin.outputs.bin }}
374
+
375
+ - name: Install and start vulnerable app
376
+ working-directory: js-recon-labs/next_js/vuln-all-rules
377
+ run: |
378
+ npm ci
379
+ npm run build
380
+ npm start &
381
+
382
+ - name: Wait for app to be ready
383
+ run: |
384
+ SECONDS=0
385
+ until curl -sf --max-time 2 http://localhost:3001 > /dev/null; do
386
+ if (( SECONDS > 60 )); then
387
+ echo "Timed out waiting for app to be ready"
388
+ exit 1
389
+ fi
390
+ sleep 2
391
+ done
392
+
393
+ - name: Run installed js-recon against the lab app
394
+ run: |
395
+ echo "http://localhost:3001" > /tmp/brew-smoke-urls.txt
396
+ "$BIN" run -u /tmp/brew-smoke-urls.txt -y -k
397
+ env:
398
+ BIN: ${{ steps.bin.outputs.bin }}
399
+
400
+ - name: Verify expected output files exist
401
+ run: |
402
+ test -n "$(find output -name analyze.json)" || { echo "analyze.json missing"; exit 1; }
403
+ test -n "$(find output -name js-recon.db)" || { echo "js-recon.db missing"; exit 1; }
@@ -0,0 +1,42 @@
1
+ {
2
+ "version": "2.0.0",
3
+ "tasks": [
4
+ {
5
+ "label": "claude-sync: sync CLAUDE.md files",
6
+ "type": "shell",
7
+ "command": "${workspaceFolder}/.claude-sync/sync.sh",
8
+ "runOptions": {
9
+ "runOn": "folderOpen"
10
+ },
11
+ "presentation": {
12
+ "reveal": "silent",
13
+ "panel": "dedicated"
14
+ },
15
+ "problemMatcher": []
16
+ },
17
+ {
18
+ "label": "claude-sync: watch for new CLAUDE.md files",
19
+ "type": "shell",
20
+ "command": "${workspaceFolder}/.claude-sync/watch.sh",
21
+ "isBackground": true,
22
+ "runOptions": {
23
+ "runOn": "folderOpen"
24
+ },
25
+ "presentation": {
26
+ "reveal": "silent",
27
+ "panel": "dedicated"
28
+ },
29
+ "problemMatcher": {
30
+ "pattern": {
31
+ "regexp": "^(.*)$",
32
+ "message": 1
33
+ },
34
+ "background": {
35
+ "activeOnStart": true,
36
+ "beginsPattern": "^\\[claude-sync\\] watching",
37
+ "endsPattern": "^\\[claude-sync\\] watching"
38
+ }
39
+ }
40
+ }
41
+ ]
42
+ }