Skip to content

Recorded flow support - #1712

Open
Mzack9999 wants to merge 4 commits into
devfrom
1629-recorded-flow-support
Open

Recorded flow support#1712
Mzack9999 wants to merge 4 commits into
devfrom
1629-recorded-flow-support

Conversation

@Mzack9999

@Mzack9999 Mzack9999 commented Jul 15, 2026

Copy link
Copy Markdown
Member

Closes #1629

Adds -rf / -recorded-flow to replay Chrome DevTools Recorder (or explicit step) logins once before headless crawl. Includes AuthLab e2e + docs.

Summary by CodeRabbit

  • New Features
    • Added a headless-only --recorded-flow / -rf option to replay Chrome DevTools Recorder (or explicit step scripts) for authentication before crawling.
    • Added multi-step headless auth replay with support for credential placeholders across login styles.
  • Bug Fixes
    • Improved logout URL skipping so it applies when recorded-flow authentication is enabled.
  • Documentation
    • Expanded README with end-to-end authenticated crawling instructions, including -auto-login and -recorded-flow usage details.
  • Tests
    • Added an auth lab workflow plus unit and end-to-end coverage for recorded-flow login behavior.
  • Chores
    • Improved test command quoting and failure output clarity.

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e17f89f2-507f-4b94-8a76-ec43acbb3623

📥 Commits

Reviewing files that changed from the base of the PR and between 314e870 and 9a0be1d.

📒 Files selected for processing (1)
  • pkg/engine/headless/auth/recording.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • pkg/engine/headless/auth/recording.go

Walkthrough

Recorded Chrome DevTools flows and explicit login steps can now be validated, replayed in headless mode, and used before crawling. The change adds CLI configuration, authentication fixtures, functional tests, and browser-level coverage.

Changes

Recorded authentication

Layer / File(s) Summary
Flow model, parsing, and replay
pkg/engine/headless/auth/*
Defines login steps, parses Chrome Recorder and explicit JSON formats, expands credentials, selects selectors, and replays browser actions.
CLI validation and crawler wiring
pkg/types/options.go, cmd/katana/main.go, internal/runner/*, pkg/engine/headless/{headless.go,crawler/*}, pkg/engine/common/base.go, README.md
Adds --recorded-flow, validates flow files and credentials, forces pure headless mode, and runs authentication before crawling.
Authentication lab and functional validation
internal/testutils/authlab/*, internal/testutils/auth_cases.go, cmd/functional-test/main.go, internal/testutils/integration.go
Adds local authentication endpoints, recorded-flow fixtures, functional test cases, and safer functional-test command execution.
Browser and crawler end-to-end coverage
pkg/engine/headless/*e2e_test.go
Tests successful flow replay, explicit steps, SPA and username-first login, incorrect credentials, gated-page discovery, and unauthenticated crawling.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CLI
  participant Runner
  participant Headless
  participant Crawler
  participant AuthLab
  CLI->>Runner: pass --recorded-flow and credentials
  Runner->>Headless: load validated login steps
  Headless->>Crawler: configure AuthSteps
  Crawler->>AuthLab: replay login actions
  AuthLab-->>Crawler: session cookie
  Crawler->>AuthLab: crawl protected routes
Loading

Poem

A bunny hops through steps so neat,
Fills in passwords, quick and sweet.
Chrome records the login dance,
Headless paws now get a chance.
Secret pages bloom in sight! 🐇

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Most changes fit the feature, but the shell-quoting/error-handling refactor in internal/testutils/integration.go is unrelated to recorded flow support. Split the integration test harness refactor into a separate PR unless it is required for the recorded-flow feature.
Docstring Coverage ⚠️ Warning Docstring coverage is 27.78% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately reflects the main change: recorded flow support.
Linked Issues check ✅ Passed The PR implements recorded flow support with CLI, replay, tests, and docs as requested by #1629.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 1629-recorded-flow-support

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Comment on lines +94 to +100
http.SetCookie(w, &http.Cookie{
Name: CookieName,
Value: CookieValue,
Path: "/",
HttpOnly: true,
SameSite: http.SameSiteLaxMode,
})
Comment on lines +104 to +110
http.SetCookie(w, &http.Cookie{
Name: CookieName,
Value: "",
Path: "/",
MaxAge: -1,
HttpOnly: true,
})

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (1)
pkg/engine/headless/auth/e2e_test.go (1)

18-25: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Simplify the browser check.

You can avoid the dummy variable assignment by adopting the cleaner style used in recorded_flow_e2e_test.go.

♻️ Proposed refactor
 func skipIfNoBrowser(t *testing.T) {
 	t.Helper()
-	path, found := launcher.LookPath()
-	if !found {
+	if path, found := launcher.LookPath(); !found || path == "" {
 		t.Skip("chrome/chromium not found, skipping recorded-flow e2e")
 	}
-	_ = path
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/engine/headless/auth/e2e_test.go` around lines 18 - 25, Update
skipIfNoBrowser to use the browser lookup only for its found-status check,
avoiding the unused path variable and dummy assignment; match the cleaner
browser-check style already used in recorded_flow_e2e_test.go.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/testutils/auth_cases.go`:
- Around line 32-38: Update the explicit flow test’s CompareFunc in the
authentication cases to reuse the simple flow’s validation logic for detecting
discovered gated pages, rather than only checking that got is non-empty.
Preserve the existing authenticated-output error behavior while ensuring a
/app/dashboard URL alone cannot satisfy the assertion.

In `@internal/testutils/authlab/lab.go`:
- Around line 64-73: Update Lab.Close to return l.server.Close() directly when
l.server is non-nil, and only close and return l.listener when no server exists.
Preserve the nil fallback returning nil and avoid closing the listener after the
server has already closed it.

In `@pkg/engine/headless/auth/e2e_test.go`:
- Line 156: Update the page-load setup in the affected test to assert that
page.WaitLoad() succeeds instead of discarding its error. Use the test’s
existing assertion style and test context, ensuring failures from timeouts or
browser crashes stop the test before page.HTML() is evaluated.

In `@pkg/engine/headless/auth/replay.go`:
- Around line 93-96: Update the "submit" branch in the replay flow to honor
step.Selector when it is non-empty, passing the matching element to submitForm;
only use findVisible(page, `input[type="password"]`) for form discovery when no
selector was supplied. Preserve the existing error wrapping and return behavior.

In `@pkg/engine/headless/recorded_flow_e2e_test.go`:
- Line 134: Update the negative test around c.Crawl to assert that crawling
succeeds before checking SecretHits, using the test’s existing assertion
mechanism. Do not discard the Crawl error; fail the test when c.Crawl returns an
error while preserving the existing SecretHits validation.

---

Nitpick comments:
In `@pkg/engine/headless/auth/e2e_test.go`:
- Around line 18-25: Update skipIfNoBrowser to use the browser lookup only for
its found-status check, avoiding the unused path variable and dummy assignment;
match the cleaner browser-check style already used in recorded_flow_e2e_test.go.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ca1f319b-4345-49db-a564-c6072d1a4b04

📥 Commits

Reviewing files that changed from the base of the PR and between 4b15e7f and f287ec4.

📒 Files selected for processing (18)
  • README.md
  • cmd/functional-test/main.go
  • cmd/katana/main.go
  • internal/runner/options.go
  • internal/runner/options_test.go
  • internal/testutils/auth_cases.go
  • internal/testutils/authlab/lab.go
  • internal/testutils/authlab/lab_test.go
  • pkg/engine/common/base.go
  • pkg/engine/headless/auth/e2e_test.go
  • pkg/engine/headless/auth/recording.go
  • pkg/engine/headless/auth/recording_test.go
  • pkg/engine/headless/auth/replay.go
  • pkg/engine/headless/auth/step.go
  • pkg/engine/headless/crawler/crawler.go
  • pkg/engine/headless/headless.go
  • pkg/engine/headless/recorded_flow_e2e_test.go
  • pkg/types/options.go

Comment thread internal/testutils/auth_cases.go Outdated
Comment thread internal/testutils/authlab/lab.go
Comment thread pkg/engine/headless/auth/e2e_test.go Outdated
Comment thread pkg/engine/headless/auth/replay.go
Comment thread pkg/engine/headless/recorded_flow_e2e_test.go Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/testutils/integration.go`:
- Around line 11-40: Replace the shell-based command construction in
RunKatanaBinaryAndGetResults with exec.Command(katanaBinary, args...) and assign
strings.NewReader(target+"\n") to cmd.Stdin. Remove shellQuote, cmdLine, and the
bash -c invocation while preserving debug output, stdout/stderr capture, and
error reporting.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c9eb3085-9d80-4130-8823-2848c5b57cdf

📥 Commits

Reviewing files that changed from the base of the PR and between f287ec4 and 61d6b30.

📒 Files selected for processing (6)
  • internal/testutils/auth_cases.go
  • internal/testutils/authlab/lab.go
  • internal/testutils/integration.go
  • pkg/engine/headless/auth/e2e_test.go
  • pkg/engine/headless/auth/replay.go
  • pkg/engine/headless/recorded_flow_e2e_test.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • internal/testutils/authlab/lab.go
  • pkg/engine/headless/auth/replay.go
  • pkg/engine/headless/auth/e2e_test.go

Comment on lines +11 to +40
// shellQuote quotes s for bash so Windows paths with backslashes and values
// containing @/: don't get mangled by bash -c.
func shellQuote(s string) string {
s = filepath.ToSlash(s)
return "'" + strings.ReplaceAll(s, "'", `'\''`) + "'"
}

func RunKatanaBinaryAndGetResults(target string, katanaBinary string, debug bool, args []string) ([]string, error) {
cmd := exec.Command("bash", "-c")
cmdLine := fmt.Sprintf(`echo %s | %s `, target, katanaBinary)
cmdLine += strings.Join(args, " ")
quotedArgs := make([]string, 0, len(args))
for _, a := range args {
quotedArgs = append(quotedArgs, shellQuote(a))
}

cmd.Args = append(cmd.Args, cmdLine)
data, err := cmd.Output()
if err != nil {
return nil, err
cmdLine := fmt.Sprintf(`echo %s | %s %s`,
shellQuote(target),
shellQuote(katanaBinary),
strings.Join(quotedArgs, " "),
)
if debug {
fmt.Printf("cmd: %s\n", cmdLine)
}

cmd := exec.Command("bash", "-c", cmdLine)
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
return nil, fmt.Errorf("katana failed: %w\nstderr:\n%s\nstdout:\n%s", err, stderr.String(), stdout.String())
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Avoid using bash -c to pipe input; use cmd.Stdin directly instead.

Using bash -c with dynamically built strings is an anti-pattern that can lead to command injection (as flagged by static analysis tools), even when custom shell-quoting functions are used. Additionally, it makes the code less cross-platform.

Since the only shell feature being used here is piping echo <target> into the binary's standard input, you can achieve the same result safely and cleanly in native Go by assigning a string reader to cmd.Stdin and passing arguments directly to exec.Command.

🛡️ Proposed fix
-// shellQuote quotes s for bash so Windows paths with backslashes and values
-// containing `@/`: don't get mangled by bash -c.
-func shellQuote(s string) string {
-	s = filepath.ToSlash(s)
-	return "'" + strings.ReplaceAll(s, "'", `'\''`) + "'"
-}
-
 func RunKatanaBinaryAndGetResults(target string, katanaBinary string, debug bool, args []string) ([]string, error) {
-	quotedArgs := make([]string, 0, len(args))
-	for _, a := range args {
-		quotedArgs = append(quotedArgs, shellQuote(a))
-	}
-
-	cmdLine := fmt.Sprintf(`echo %s | %s %s`,
-		shellQuote(target),
-		shellQuote(katanaBinary),
-		strings.Join(quotedArgs, " "),
-	)
 	if debug {
-		fmt.Printf("cmd: %s\n", cmdLine)
+		fmt.Printf("cmd: echo %s | %s %s\n", target, katanaBinary, strings.Join(args, " "))
 	}
 
-	cmd := exec.Command("bash", "-c", cmdLine)
+	cmd := exec.Command(katanaBinary, args...)
+	cmd.Stdin = strings.NewReader(target + "\n")
 	var stdout, stderr bytes.Buffer
 	cmd.Stdout = &stdout
 	cmd.Stderr = &stderr
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// shellQuote quotes s for bash so Windows paths with backslashes and values
// containing @/: don't get mangled by bash -c.
func shellQuote(s string) string {
s = filepath.ToSlash(s)
return "'" + strings.ReplaceAll(s, "'", `'\''`) + "'"
}
func RunKatanaBinaryAndGetResults(target string, katanaBinary string, debug bool, args []string) ([]string, error) {
cmd := exec.Command("bash", "-c")
cmdLine := fmt.Sprintf(`echo %s | %s `, target, katanaBinary)
cmdLine += strings.Join(args, " ")
quotedArgs := make([]string, 0, len(args))
for _, a := range args {
quotedArgs = append(quotedArgs, shellQuote(a))
}
cmd.Args = append(cmd.Args, cmdLine)
data, err := cmd.Output()
if err != nil {
return nil, err
cmdLine := fmt.Sprintf(`echo %s | %s %s`,
shellQuote(target),
shellQuote(katanaBinary),
strings.Join(quotedArgs, " "),
)
if debug {
fmt.Printf("cmd: %s\n", cmdLine)
}
cmd := exec.Command("bash", "-c", cmdLine)
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
return nil, fmt.Errorf("katana failed: %w\nstderr:\n%s\nstdout:\n%s", err, stderr.String(), stdout.String())
}
func RunKatanaBinaryAndGetResults(target string, katanaBinary string, debug bool, args []string) ([]string, error) {
if debug {
fmt.Printf("cmd: echo %s | %s %s\n", target, katanaBinary, strings.Join(args, " "))
}
cmd := exec.Command(katanaBinary, args...)
cmd.Stdin = strings.NewReader(target + "\n")
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
return nil, fmt.Errorf("katana failed: %w\nstderr:\n%s\nstdout:\n%s", err, stderr.String(), stdout.String())
}
🧰 Tools
🪛 ast-grep (0.44.1)

[error] 32-32: A shell (sh/bash) is invoked with -c and a dynamically built command string (string concatenation, fmt.Sprintf, or a variable holding the command) passed to exec.Command / exec.CommandContext. Untrusted input embedded in the command lets an attacker inject arbitrary shell commands. Avoid the shell: call the target binary directly with exec.Command(name, arg1, arg2, ...) so each argument is passed as a separate, non-interpreted token, and never build a shell command string from external input.
Context: exec.Command("bash", "-c", cmdLine)
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(command-injection-exec-sh-c-go)

🪛 OpenGrep (1.25.0)

[ERROR] 33-33: Dynamic command passed to exec.Command with a shell invocation. Pass arguments directly to exec.Command without a shell wrapper.

(coderabbit.command-injection.go-exec-command)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/testutils/integration.go` around lines 11 - 40, Replace the
shell-based command construction in RunKatanaBinaryAndGetResults with
exec.Command(katanaBinary, args...) and assign strings.NewReader(target+"\n") to
cmd.Stdin. Remove shellQuote, cmdLine, and the bash -c invocation while
preserving debug output, stdout/stderr capture, and error reporting.

Source: Linters/SAST tools

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

add recorded flow support

2 participants