Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
*.sh text eol=lf
*.zsh text eol=lf
*.bash text eol=lf
*.py text eol=lf
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ npm run build
npx electron-builder --mac # or --win / --linux
```

> **Windows / WSL users** — see [docs/windows-setup.md](docs/windows-setup.md) for build prerequisites, PowerShell hooks, and WSL integration.

## Features

### Recording Engine
Expand Down
151 changes: 151 additions & 0 deletions docs/windows-setup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
# Windows & WSL Setup

Running RedLog on Windows — build environment, PowerShell hooks, WSL integration,
and operational isolation.

---

## 1. Prerequisites

RedLog uses `better-sqlite3` (native C++ module), which must be compiled for
Electron's ABI. That requires a C/C++ toolchain.

| Requirement | Notes |
|---|---|
| **Node.js 20 or 22 (LTS)** | Not 24+ — no prebuilt `better-sqlite3` binary yet. |
| **Visual Studio Build Tools** | Install the **"Desktop development with C++"** workload. |
| **Python 3** | Required by `node-gyp`. |

```powershell
winget install Microsoft.VisualStudio.2022.BuildTools
winget install --id OpenJS.NodeJS.22 -e
```

### PowerShell notes

- Windows PowerShell 5.1 does **not** support `&&`. Either install PowerShell 7
(`winget install Microsoft.PowerShell`, run as `pwsh`) or chain with
`cmd1; if ($?) { cmd2 }`.
- If `npm` reports *"running scripts is disabled"*, allow user scripts once:
`Set-ExecutionPolicy -Scope CurrentUser RemoteSigned`.

---

## 2. Build & Run

```powershell
npm install
npm run rebuild # compile better-sqlite3 for Electron's ABI
npm run dev # launch the app
```

---

## 3. Packaging

Installers are built by [electron-builder](https://www.electron.build). The
GitHub Actions workflow (`.github/workflows/release.yml`) produces releases on
tags. To build locally:

```powershell
npm run build
npx electron-builder --win # NSIS + portable → dist\
```

**First-run note:** electron-builder downloads `winCodeSign`, which may need
symlink privileges. Either enable **Developer Mode** (Settings → For developers)
or run the packaging command once from an elevated terminal.

---

## 4. PowerShell Shell Hook

The PowerShell hook (`hooks/shell-hook.ps1`) captures every command from
PowerShell 5.1+ and pwsh 7+ and sends it to RedLog's timeline.

### Quick start

```powershell
# Source in current session
. "C:\path\to\redlog\hooks\shell-hook.ps1"

# Or add to your profile for every session
Add-Content $PROFILE '. "C:\path\to\redlog\hooks\shell-hook.ps1"'
```

The hook:
- Overrides the `prompt` function to capture commands via `Get-History`
- Sends `command_start` + `command_end` events with exit code and duration
- Uses background runspaces so the prompt is never blocked
- No-ops silently when RedLog isn't running

### Event sender (scripting)

For custom scripts, use `hooks/redlog-send.ps1`:

```powershell
. ".\hooks\redlog-send.ps1"
Send-RedLogEvent "invoke-mimikatz" command_start
# ... run tool ...
Send-RedLogEvent "invoke-mimikatz" command_end @{ exit_code = 0 }
```

---

## 5. WSL Integration

Pentest tooling often runs in WSL. Two things must line up for WSL → RedLog.

### 5.1 Token path

RedLog writes `api-token` and `api-port` to `%USERPROFILE%\.redlog\`. The WSL
hook scripts resolve this via `cmd.exe /c 'echo %USERPROFILE%'` + `wslpath`.

### 5.2 Networking — mirrored mode

The API binds `127.0.0.1` on Windows. Under WSL2's default NAT mode, WSL's
`127.0.0.1` is a separate loopback. Enable **mirrored networking**:

1. Create `%USERPROFILE%\.wslconfig`:
```ini
[wsl2]
networkingMode=mirrored
```
2. `wsl --shutdown` (from Windows PowerShell), then reopen WSL.
3. Verify: `wslinfo --networking-mode` → `mirrored`.
Comment on lines +104 to +115

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== docs/windows-setup.md relevant section =="
sed -n '90,125p' docs/windows-setup.md 2>/dev/null || true

echo
echo "== search for WSL-networking fallback/mirroring references =="
rg -n "mirrored|networkingMode|NAT|gateway|wslinfo|127\.0\.0\.1|Windows 11|Windows 10|22H2" docs hooks . 2>/dev/null \
  | sed -n '1,200p'

echo
echo "== candidate hook files =="
git ls-files hooks/ 2>/dev/null | sed -n '1,120p'

Repository: guan4tou2/REDLOG

Length of output: 11458


Document mirrored mode as optional and version-gated.

These steps present mirrored networking as mandatory, but it requires Windows 11 22H2 or later. Since the WSL hooks already fall back to gateway.docker.internal for NAT mode, describe mirrored mode as recommended and mention the NAT fallback for Windows 10/unavailable mirrored setups.

🤖 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 `@docs/windows-setup.md` around lines 104 - 115, The “Networking — mirrored
mode” section should present mirrored networking as recommended rather than
required, state that it is supported only on Windows 11 22H2 or later, and
document that Windows 10 or unavailable mirrored setups should use the existing
NAT fallback through gateway.docker.internal.


### 5.3 WSL hooks

```bash
# Diagnose the WSL → RedLog link
bash /mnt/c/Users/<you>/Desktop/REDLOG/hooks/wsl-redlog-test.sh

# Fire-and-forget event sender
hooks/redlog-send.sh "nmap -sV $TARGET" command_start
nmap -sV "$TARGET"
hooks/redlog-send.sh "nmap -sV $TARGET" command_end "{\"exit_code\":$?}"
```

The bash/zsh `shell-preexec-hook.sh` also works inside WSL — source it in your
WSL distro's `~/.bashrc` or `~/.zshrc`.

---

## 6. Operational Privacy

### 6.1 Workspace isolation (primary control)

Source hooks **only** in engagement shells. Commands in unhooked shells are never
logged. A clean pattern: do engagement work in a dedicated WSL distro with the
hook in `~/.bashrc`; keep personal work on the Windows host (unhooked).

### 6.2 Pause limitation

The status-bar recording toggle only hides events from the live timeline — it
does **not** stop database writes. For genuine isolation, stop the producer
(unsource the hook or close the engagement workspace).

### 6.3 Per-engagement isolation

Each project is a separate SQLite DB (`~/.redlog/projects/<id>/`). Close /
switch the project when you stop working an engagement.
63 changes: 63 additions & 0 deletions hooks/redlog-send.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# RedLog Event Sender for PowerShell
# Fire-and-forget event posting from any PowerShell script or tool.
#
# Usage:
# . .\hooks\redlog-send.ps1
# Send-RedLogEvent "nmap -sV 10.0.0.1" command_start
# nmap -sV 10.0.0.1
# Send-RedLogEvent "nmap -sV 10.0.0.1" command_end @{ exit_code = $LASTEXITCODE }

$script:_RedLogPortFile = Join-Path $env:USERPROFILE '.redlog\api-port'
$script:_RedLogTokenFile = Join-Path $env:USERPROFILE '.redlog\api-token'

function Send-RedLogEvent {
[CmdletBinding()]
param(
[Parameter(Mandatory, Position = 0)]
[string]$Command,

[Parameter(Mandatory, Position = 1)]
[ValidateSet('command_start', 'command_end', 'note', 'marker')]
[string]$Subtype,

[Parameter(Position = 2)]
[hashtable]$Extra = @{}
)

if (-not (Test-Path $script:_RedLogPortFile) -or
-not (Test-Path $script:_RedLogTokenFile)) {
return
}

try {
$port = (Get-Content $script:_RedLogPortFile -Raw).Trim()
$token = (Get-Content $script:_RedLogTokenFile -Raw).Trim()

$data = @{
subtype = $Subtype
command = $Command
shell = 'powershell'
pid = $PID
}
foreach ($k in $Extra.Keys) { $data[$k] = $Extra[$k] }

$body = @{ agent_type = 'shell'; data = $data } | ConvertTo-Json -Depth 4 -Compress
$uri = "http://127.0.0.1:${port}/api/events"
$headers = @{
'Authorization' = "Bearer $token"
'Content-Type' = 'application/json'
}

# Fire-and-forget via background runspace
$rs = [runspacefactory]::CreateRunspace()
$rs.Open()
$ps = [powershell]::Create().AddScript({
param($uri, $headers, $body)
try {
Invoke-RestMethod -Uri $uri -Method Post -Headers $headers -Body $body -TimeoutSec 2 | Out-Null
} catch {}
}).AddArgument($uri).AddArgument($headers).AddArgument($body)
$ps.Runspace = $rs
$null = $ps.BeginInvoke()
Comment on lines +52 to +61

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Dispose completed fire-and-forget runspaces.

Each event creates a runspace and PowerShell instance but never calls EndInvoke or disposes either object. Frequent shell activity can accumulate runspaces and degrade the interactive session.

  • hooks/redlog-send.ps1#L52-L61: arrange asynchronous completion to call EndInvoke(), then dispose $ps and $rs.
  • hooks/shell-hook.ps1#L47-L56: apply the same lifecycle cleanup, ideally through a shared sender implementation.
🧰 Tools
🪛 PSScriptAnalyzer (1.25.0)

[warning] 58-58: Empty catch block is used. Please use Write-Error or throw statements in catch blocks.

(PSAvoidUsingEmptyCatchBlock)

📍 Affects 2 files
  • hooks/redlog-send.ps1#L52-L61 (this comment)
  • hooks/shell-hook.ps1#L47-L56
🤖 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 `@hooks/redlog-send.ps1` around lines 52 - 61, The asynchronous senders in
hooks/redlog-send.ps1 lines 52-61 and hooks/shell-hook.ps1 lines 47-56 leak
their PowerShell and runspace resources. Update both implementations, preferably
via a shared sender helper, so completion invokes EndInvoke(), then disposes the
PowerShell instance and runspace while preserving fire-and-forget behavior.

} catch {}
}
93 changes: 93 additions & 0 deletions hooks/redlog-send.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
#!/usr/bin/env bash
# RedLog Event Sender — fire-and-forget from any shell / hook script.
# Works natively on macOS/Linux and inside WSL2 (auto-resolves Windows token path).
#
# Usage:
# hooks/redlog-send.sh "nmap -sV $TARGET" command_start
# nmap -sV "$TARGET"
# hooks/redlog-send.sh "nmap -sV $TARGET" command_end "{\"exit_code\":$?}"

set -u

COMMAND="${1:-}"
SUBTYPE="${2:-command_start}"
EXTRA="${3:-}"

[[ -z "$COMMAND" ]] && exit 0

# --- Locate token / port files ---
_resolve_redlog_dir() {
# Native (macOS / Linux / WSL with symlink)
if [[ -f "$HOME/.redlog/api-token" ]]; then
echo "$HOME/.redlog"
return
fi
# WSL: resolve from Windows %USERPROFILE%
if [[ -n "${WSL_DISTRO_NAME:-}" ]]; then
local win_profile
win_profile=$(cmd.exe /c 'echo %USERPROFILE%' 2>/dev/null | tr -d '\r')
if [[ -n "$win_profile" ]]; then
local wsl_path
wsl_path=$(wslpath "$win_profile" 2>/dev/null)
if [[ -f "$wsl_path/.redlog/api-token" ]]; then
echo "$wsl_path/.redlog"
return
fi
fi
fi
return 1
}

# Cache the resolved dir for the session
if [[ -z "${_REDLOG_DIR:-}" ]]; then
_REDLOG_DIR=$(_resolve_redlog_dir) || exit 0
export _REDLOG_DIR
fi

PORT_FILE="$_REDLOG_DIR/api-port"
TOKEN_FILE="$_REDLOG_DIR/api-token"

[[ -f "$PORT_FILE" ]] || exit 0
[[ -f "$TOKEN_FILE" ]] || exit 0

PORT=$(<"$PORT_FILE")
TOKEN=$(<"$TOKEN_FILE")

# --- Resolve reachable host ---
if [[ -z "${_REDLOG_HOST:-}" ]]; then
_REDLOG_HOST="127.0.0.1"
if ! curl -sf --connect-timeout 1 "http://127.0.0.1:${PORT}/api/health" >/dev/null 2>&1; then
# WSL2 NAT mode: try the Windows host gateway
if [[ -n "${WSL_DISTRO_NAME:-}" ]]; then
local gw
gw=$(ip route show default 2>/dev/null | awk '{print $3; exit}')
if [[ -n "$gw" ]] && curl -sf --connect-timeout 1 "http://${gw}:${PORT}/api/health" >/dev/null 2>&1; then
Comment on lines +61 to +64

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Files matching redlog-send.sh:\n'
fd -a 'redlog-send\.sh$' . || true

file="$(fd 'redlog-send\.sh$' . | head -n1 || true)"
if [[ -n "${file:-}" ]]; then
  echo
  printf 'Line count and relevant slice for %s:\n' "$file"
  wc -l "$file"
  sed -n '1,110p' "$file" | nl -ba
fi

printf '\nParsing/local context probe if file exists:\n'
python3 - <<'PY'
from pathlib import Path
files = list(Path('.').rglob('redlog-send.sh'))
print(files)
for p in files:
    text = p.read_text()
    lines = text.splitlines()
    for i,line in enumerate(lines, start=1):
        if 'if [[ -n "${WSL_DISTRO_NAME:-}"' in line:
            print(f"match at {p}:{i}")
            for j in range(max(1,i-4), min(len(lines),i+8)+1):
                print(f"{j}: {lines[j-1]}")
PY

Repository: guan4tou2/REDLOG

Length of output: 351


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo 'Relevant lines from hooks/redlog-send.sh:'
awk '1' hooks/redlog-send.sh | sed -n '1,110p' | cat -n

echo
echo 'Bash local-on-top-level behavior probe:'
bash -n <<'EOF'
set -euo pipefail
if [[ -n "${WSL_DISTRO_NAME:-}" ]]; then
  local gw
  gw='x'
fi
EOF
echo "bash -n exit code: $?" || true

bash -n <<'EOF'
set -euo pipefail
if [[ -n "${WSL_DISTRO_NAME:-}" ]]; then
  gw='x'
fi
EOF
echo "bash -n normal variable exit code: $?" || true

Repository: guan4tou2/REDLOG

Length of output: 3556


Remove the invalid local declaration.

This local gw is outside a function, so Bash emits an error when falling back to the WSL NAT gateway. Make gw a normal variable.

-      local gw
-      gw=$(ip route show default 2>/dev/null | awk '{print $3; exit}')
+      gw=$(ip route show default 2>/dev/null | awk '{print $3; exit}')
📝 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
if [[ -n "${WSL_DISTRO_NAME:-}" ]]; then
local gw
gw=$(ip route show default 2>/dev/null | awk '{print $3; exit}')
if [[ -n "$gw" ]] && curl -sf --connect-timeout 1 "http://${gw}:${PORT}/api/health" >/dev/null 2>&1; then
if [[ -n "${WSL_DISTRO_NAME:-}" ]]; then
gw=$(ip route show default 2>/dev/null | awk '{print $3; exit}')
if [[ -n "$gw" ]] && curl -sf --connect-timeout 1 "http://${gw}:${PORT}/api/health" >/dev/null 2>&1; then
🧰 Tools
🪛 Shellcheck (0.11.0)

[error] 62-62: 'local' is only valid in functions.

(SC2168)

🤖 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 `@hooks/redlog-send.sh` around lines 61 - 64, In the WSL gateway fallback block
of redlog-send.sh, remove the local declaration for gw and assign it as a normal
variable before the existing gateway health check. Preserve the current route
lookup and curl logic unchanged.

Source: Linters/SAST tools

_REDLOG_HOST="$gw"
fi
fi
fi
export _REDLOG_HOST
fi

# --- Build and send event ---
PAYLOAD=$(python3 -c "
import json, sys, os
d = {
'agent_type': 'shell',
'data': {
'subtype': sys.argv[1],
'command': sys.argv[2],
'shell': os.environ.get('SHELL', 'bash').rsplit('/', 1)[-1],
'pid': os.getppid()
}
}
if sys.argv[3]:
d['data'].update(json.loads(sys.argv[3]))
print(json.dumps(d))
" "$SUBTYPE" "$COMMAND" "$EXTRA" 2>/dev/null) || exit 0

curl -sf -X POST "http://${_REDLOG_HOST}:${PORT}/api/events" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "$PAYLOAD" \
--connect-timeout 1 --max-time 2 >/dev/null 2>&1 &
Loading