Skip to content

Commit c17f07b

Browse files
committed
fix: address code review — bug fixes, CI hardening, dedup helper
* Extract config/fetch-gh-release.sh helper; 6 Dockerfile RUN blocks (opengrep, cutter, ghidra, retdec, imhex, pwninit) now call it instead of repeating curl+jq+wget+empty-check inline. * MOTD: echo '\n...' → printf so newlines render in /etc/motd. * IDA Free: replace dead regex check with `file ... | grep ELF` and add post-install ida64 binary check. * Ghidra: replace `mv /opt/ghidra_*` glob with explicit `find` plus ghidraRun executable check. * Binary Ninja: add existence check before symlink. * retdec: validate /opt/retdec/bin exists and only symlink actual executables; error if none found. * zsh_history: COPY moved after oh-my-zsh install with --chown so the installer can't overwrite the pre-populated history. * MOTD: rephrase decomp2dbg note (not installed; user installs). * Set PWNDBG_NO_AUTOUPDATE=1 — /opt/pwndbg is root-owned, so the ctf user otherwise sees a "Permission denied" wall on every gdb-pwndbg launch. CI: * New shellcheck step via ludeeus/action-shellcheck. * Trivy vuln scan (HIGH/CRITICAL, ignore-unfixed) after smoke test. * Smoke test now functionally invokes each tool (r2 -qv, pwn checksec, pwntools/capstone/keystone/unicorn import via the pipx venv python, gdb-multiarch -nx --batch, gdb-pwndbg --batch). Docs: * README: new "Security & Trade-offs" section covering passwordless sudo, unpinned git plugins, and the Wayback IDA snapshot. * README: fix checksec example (apt pkg not installed) → pwn checksec; same fix in config/zsh_history. * README: remove duplicate pwninit row; add ImHex to GUI tools list. * config/gdbinit: one-line purpose comment.
1 parent a58461d commit c17f07b

6 files changed

Lines changed: 134 additions & 57 deletions

File tree

.github/workflows/build.yml

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ jobs:
3232
steps:
3333
- uses: actions/checkout@v4
3434

35+
- name: Lint shell scripts
36+
uses: ludeeus/action-shellcheck@2.0.0
37+
with:
38+
scandir: config
39+
3540
- name: Set up Docker Buildx
3641
uses: docker/setup-buildx-action@v3
3742

@@ -74,19 +79,49 @@ jobs:
7479
cache-to: type=gha,mode=max
7580

7681
- name: Smoke test
82+
# Verify tools are present AND actually invokable. A corrupted ELF
83+
# would pass `command -v` but fail `--version`; this catches that.
7784
run: |
7885
set -euo pipefail
7986
docker run --rm smoke-test:local bash -c '
8087
set -eo pipefail
8188
for bin in pwn gdb-pwndbg gdb-gef gdb-peda r2 rizin ghidra \
8289
ROPgadget ropper afl-fuzz frida one_gadget \
8390
opengrep pwninit cutter ida64 binaryninja \
84-
pycdc jd-gui binwalk; do
91+
pycdc jd-gui binwalk imhex; do
8592
command -v "$bin" >/dev/null || { echo "MISSING: $bin"; exit 1; }
8693
done
94+
# Functional checks (only quick, non-interactive invocations)
95+
r2 -qv >/dev/null
96+
rizin -qv >/dev/null
97+
opengrep --version >/dev/null
98+
pwninit --help >/dev/null
99+
one_gadget --version >/dev/null
100+
ROPgadget --version >/dev/null
101+
# afl-fuzz -h exits non-zero AND grep -q closes the pipe early
102+
# (SIGPIPE), so wrap to satisfy `set -eo pipefail`.
103+
( afl-fuzz -h 2>&1 || true ) | grep -qi afl
104+
pwn version >/dev/null
105+
pwn checksec /bin/ls >/dev/null
106+
# pwntools lives in its pipx venv; bare python3 cannot import it.
107+
/opt/pipx/venvs/pwntools/bin/python -c "from pwn import *; from capstone import *; from keystone import *; from unicorn import *"
108+
# -nx skips ~/.gdbinit so we test gdb itself, not the pwndbg plugin.
109+
gdb-multiarch -nx --batch -ex quit >/dev/null
110+
# Loads the pwndbg plugin to confirm it imports cleanly.
111+
gdb-pwndbg --batch -ex quit >/dev/null
87112
echo "Smoke test passed"
88113
'
89114
115+
- name: Trivy vulnerability scan
116+
uses: aquasecurity/trivy-action@0.24.0
117+
with:
118+
image-ref: smoke-test:local
119+
format: table
120+
exit-code: "1"
121+
ignore-unfixed: true
122+
severity: HIGH,CRITICAL
123+
vuln-type: os,library
124+
90125
# Only pushes after smoke test succeeds. Cache hit from build_local means
91126
# this is near-instant even though build-push-action re-runs buildx.
92127
- name: Push to GHCR

Dockerfile

Lines changed: 59 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,14 @@ RUN git clone --depth=1 https://github.com/blackploit/hash-identifier /opt/hash-
5858
&& ln -s /opt/hash-identifier/hash-id.py /usr/local/bin/hash-identifier \
5959
&& chmod +x /opt/hash-identifier/hash-id.py
6060

61+
# Helper script shared by GitHub release downloads (see config/fetch-gh-release.sh)
62+
COPY config/fetch-gh-release.sh /usr/local/bin/fetch-gh-release.sh
63+
RUN chmod +x /usr/local/bin/fetch-gh-release.sh
64+
6165
RUN --mount=type=secret,id=github_token \
62-
tok=$(cat /run/secrets/github_token 2>/dev/null || true); \
63-
curl_auth=(); [ -z "$tok" ] || curl_auth=(-H "Authorization: token ${tok}"); \
64-
url=$(curl -fsSL "${curl_auth[@]}" https://api.github.com/repos/opengrep/opengrep/releases/latest \
65-
| jq -er '.assets[] | select(.name == "opengrep_manylinux_x86") | .browser_download_url' | head -1); \
66-
[ -n "$url" ] || { echo "ERROR: opengrep asset URL not found"; exit 1; }; \
67-
wget -qO /usr/local/bin/opengrep "$url"; \
68-
[ -s /usr/local/bin/opengrep ] || { echo "ERROR: opengrep download empty"; exit 1; }; \
66+
fetch-gh-release.sh opengrep/opengrep \
67+
'.assets[] | select(.name == "opengrep_manylinux_x86") | .browser_download_url' \
68+
/usr/local/bin/opengrep; \
6969
chmod +x /usr/local/bin/opengrep; \
7070
/usr/local/bin/opengrep --version >/dev/null || { echo "ERROR: opengrep binary invalid"; exit 1; }
7171

@@ -103,13 +103,9 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
103103

104104
# Cutter (rizin GUI) — extracted AppImage for Docker compatibility
105105
RUN --mount=type=secret,id=github_token \
106-
tok=$(cat /run/secrets/github_token 2>/dev/null || true); \
107-
curl_auth=(); [ -z "$tok" ] || curl_auth=(-H "Authorization: token ${tok}"); \
108-
url=$(curl -fsSL "${curl_auth[@]}" https://api.github.com/repos/rizinorg/cutter/releases/latest \
109-
| jq -er '.assets[] | select(.name | test("Linux-x86_64\\.AppImage$")) | .browser_download_url' | head -1); \
110-
[ -n "$url" ] || { echo "ERROR: Cutter asset URL not found"; exit 1; }; \
111-
wget -qO /tmp/cutter.AppImage "$url"; \
112-
[ -s /tmp/cutter.AppImage ] || { echo "ERROR: Cutter download empty"; exit 1; }; \
106+
fetch-gh-release.sh rizinorg/cutter \
107+
'.assets[] | select(.name | test("Linux-x86_64\\.AppImage$")) | .browser_download_url' \
108+
/tmp/cutter.AppImage; \
113109
file /tmp/cutter.AppImage | grep -q ELF || { echo "ERROR: Cutter not an ELF"; exit 1; }; \
114110
chmod +x /tmp/cutter.AppImage; \
115111
cd /tmp && ./cutter.AppImage --appimage-extract; \
@@ -119,40 +115,45 @@ RUN --mount=type=secret,id=github_token \
119115

120116
# Ghidra (platform-independent zip)
121117
RUN --mount=type=secret,id=github_token \
122-
tok=$(cat /run/secrets/github_token 2>/dev/null || true); \
123-
curl_auth=(); [ -z "$tok" ] || curl_auth=(-H "Authorization: token ${tok}"); \
124-
url=$(curl -fsSL "${curl_auth[@]}" https://api.github.com/repos/NationalSecurityAgency/ghidra/releases/latest \
125-
| jq -er '.assets[] | select(.name | endswith(".zip")) | select(.name | test("PUBLIC")) | .browser_download_url' | head -1); \
126-
[ -n "$url" ] || { echo "ERROR: Ghidra asset URL not found"; exit 1; }; \
127-
wget -qO /tmp/ghidra.zip "$url"; \
128-
[ -s /tmp/ghidra.zip ] || { echo "ERROR: Ghidra download empty"; exit 1; }; \
118+
fetch-gh-release.sh NationalSecurityAgency/ghidra \
119+
'.assets[] | select(.name | endswith(".zip")) | select(.name | test("PUBLIC")) | .browser_download_url' \
120+
/tmp/ghidra.zip; \
129121
unzip -tq /tmp/ghidra.zip || { echo "ERROR: Ghidra zip corrupt"; exit 1; }; \
130122
unzip -q /tmp/ghidra.zip -d /opt; \
131-
mv /opt/ghidra_* /opt/ghidra; \
123+
ghidra_dir=$(find /opt -maxdepth 1 -mindepth 1 -type d -name 'ghidra_*' -print -quit); \
124+
[ -n "$ghidra_dir" ] || { echo "ERROR: Ghidra extracted dir not found"; exit 1; }; \
125+
mv "$ghidra_dir" /opt/ghidra; \
126+
[ -x /opt/ghidra/ghidraRun ] || { echo "ERROR: ghidraRun missing or not executable"; exit 1; }; \
132127
ln -s /opt/ghidra/ghidraRun /usr/local/bin/ghidra; \
133128
rm /tmp/ghidra.zip
134129

135-
# retdec (pre-built, no wrapper dir in archive)
130+
# retdec — ships bare binaries in bin/; extract and symlink each into PATH
136131
RUN --mount=type=secret,id=github_token \
137132
mkdir -p /opt/retdec; \
138-
tok=$(cat /run/secrets/github_token 2>/dev/null || true); \
139-
curl_auth=(); [ -z "$tok" ] || curl_auth=(-H "Authorization: token ${tok}"); \
140-
url=$(curl -fsSL "${curl_auth[@]}" https://api.github.com/repos/avast/retdec/releases/latest \
141-
| jq -er '.assets[] | select(.name | test("[Ll]inux.*tar")) | .browser_download_url' | head -1); \
142-
[ -n "$url" ] || { echo "ERROR: retdec asset URL not found"; exit 1; }; \
143-
wget -qO /tmp/retdec.tar.xz "$url"; \
144-
[ -s /tmp/retdec.tar.xz ] || { echo "ERROR: retdec download empty"; exit 1; }; \
133+
fetch-gh-release.sh avast/retdec \
134+
'.assets[] | select(.name | test("[Ll]inux.*tar")) | .browser_download_url' \
135+
/tmp/retdec.tar.xz; \
145136
tar tf /tmp/retdec.tar.xz >/dev/null || { echo "ERROR: retdec archive corrupt"; exit 1; }; \
146137
tar xf /tmp/retdec.tar.xz -C /opt/retdec; \
147-
ln -s /opt/retdec/bin/* /usr/local/bin/; \
138+
[ -d /opt/retdec/bin ] || { echo "ERROR: retdec bin/ dir missing"; exit 1; }; \
139+
found=0; for f in /opt/retdec/bin/*; do \
140+
[ -f "$f" ] && [ -x "$f" ] || continue; \
141+
ln -s "$f" "/usr/local/bin/$(basename "$f")"; \
142+
found=1; \
143+
done; \
144+
[ "$found" = "1" ] || { echo "ERROR: no executable retdec binaries to symlink"; exit 1; }; \
148145
rm /tmp/retdec.tar.xz
149146

150147
# IDA Free (Wayback archive — original URL removed by Hex-Rays)
148+
# Installer ships as a bare ELF executable. Validate ELF + size so we fail
149+
# loudly if Wayback returns an HTML error page instead of the binary.
151150
RUN wget -qO /tmp/idafree_linux.run "https://web.archive.org/web/20240921184600if_/https://out7.hex-rays.com/files/idafree84_linux.run"; \
152151
[ -s /tmp/idafree_linux.run ] || { echo "ERROR: IDA Free download empty (Wayback may be unavailable)"; exit 1; }; \
153-
head -c4 /tmp/idafree_linux.run | grep -q '^.ELF' || [ "$(stat -c%s /tmp/idafree_linux.run)" -gt 10000000 ] || { echo "ERROR: IDA Free installer looks invalid"; exit 1; }; \
152+
file /tmp/idafree_linux.run | grep -q 'ELF .* executable' || { echo "ERROR: IDA Free installer is not an ELF (Wayback returned HTML?)"; exit 1; }; \
153+
[ "$(stat -c%s /tmp/idafree_linux.run)" -gt 10000000 ] || { echo "ERROR: IDA Free installer too small"; exit 1; }; \
154154
chmod +x /tmp/idafree_linux.run; \
155155
/tmp/idafree_linux.run --mode unattended --prefix /opt/idafree; \
156+
[ -x /opt/idafree/ida64 ] || { echo "ERROR: IDA Free ida64 missing after install"; exit 1; }; \
156157
ln -s /opt/idafree/ida64 /usr/local/bin/ida64; \
157158
rm /tmp/idafree_linux.run
158159

@@ -161,20 +162,17 @@ RUN wget -qO /tmp/binaryninja_free.zip "https://cdn.binary.ninja/installers/bina
161162
[ -s /tmp/binaryninja_free.zip ] || { echo "ERROR: Binary Ninja download empty"; exit 1; }; \
162163
unzip -tq /tmp/binaryninja_free.zip || { echo "ERROR: Binary Ninja zip corrupt"; exit 1; }; \
163164
unzip -q /tmp/binaryninja_free.zip -d /opt; \
165+
[ -x /opt/binaryninja/binaryninja ] || { echo "ERROR: Binary Ninja binary missing at /opt/binaryninja/binaryninja (archive layout changed?)"; exit 1; }; \
164166
ln -s /opt/binaryninja/binaryninja /usr/local/bin/binaryninja; \
165167
rm /tmp/binaryninja_free.zip
166168

167169
# ImHex (hex editor for RE)
168170
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
169171
--mount=type=cache,target=/var/lib/apt/lists,sharing=locked \
170172
--mount=type=secret,id=github_token \
171-
tok=$(cat /run/secrets/github_token 2>/dev/null || true); \
172-
curl_auth=(); [ -z "$tok" ] || curl_auth=(-H "Authorization: token ${tok}"); \
173-
url=$(curl -fsSL "${curl_auth[@]}" https://api.github.com/repos/WerWolv/ImHex/releases/latest \
174-
| jq -er '.assets[] | select(.name | test("Ubuntu.*24\\.04.*\\.deb$")) | .browser_download_url' | head -1); \
175-
[ -n "$url" ] || { echo "ERROR: ImHex asset URL not found"; exit 1; }; \
176-
wget -qO /tmp/imhex.deb "$url"; \
177-
[ -s /tmp/imhex.deb ] || { echo "ERROR: ImHex download empty"; exit 1; }; \
173+
fetch-gh-release.sh WerWolv/ImHex \
174+
'.assets[] | select(.name | test("Ubuntu.*24\\.04.*\\.deb$")) | .browser_download_url' \
175+
/tmp/imhex.deb; \
178176
dpkg-deb -I /tmp/imhex.deb >/dev/null || { echo "ERROR: ImHex deb corrupt"; exit 1; }; \
179177
apt-get update && apt-get install -y --no-install-recommends /tmp/imhex.deb; \
180178
rm /tmp/imhex.deb
@@ -224,13 +222,9 @@ RUN git clone --depth=1 https://github.com/niklasb/libc-database /opt/libc-datab
224222

225223
# pwninit
226224
RUN --mount=type=secret,id=github_token \
227-
tok=$(cat /run/secrets/github_token 2>/dev/null || true); \
228-
curl_auth=(); [ -z "$tok" ] || curl_auth=(-H "Authorization: token ${tok}"); \
229-
url=$(curl -fsSL "${curl_auth[@]}" https://api.github.com/repos/io12/pwninit/releases/latest \
230-
| jq -er '.assets[] | select(.name == "pwninit") | .browser_download_url' | head -1); \
231-
[ -n "$url" ] || { echo "ERROR: pwninit asset URL not found"; exit 1; }; \
232-
wget -qO /usr/local/bin/pwninit "$url"; \
233-
[ -s /usr/local/bin/pwninit ] || { echo "ERROR: pwninit download empty"; exit 1; }; \
225+
fetch-gh-release.sh io12/pwninit \
226+
'.assets[] | select(.name == "pwninit") | .browser_download_url' \
227+
/usr/local/bin/pwninit; \
234228
file /usr/local/bin/pwninit | grep -q ELF || { echo "ERROR: pwninit not an ELF"; exit 1; }; \
235229
chmod +x /usr/local/bin/pwninit
236230

@@ -245,19 +239,32 @@ RUN { userdel -r ubuntu 2>/dev/null || true; } \
245239
&& mkdir -p /ctf && chown ctf:ctf /ctf
246240

247241
# MOTD with post-build notes
248-
RUN echo '\n=== pwndocker-reverse ===\nNote: Run "decomp2dbg --install" for Ghidra/Cutter GDB integration.\nUse "gdb-switch {pwndbg|gef|peda}" to change default GDB plugin.\n' > /etc/motd
249-
250-
# Layer 12: Config files and helper scripts
251-
COPY config/zsh_history /home/ctf/.zsh_history
242+
RUN printf '%s\n' \
243+
'' \
244+
'=== pwndocker-reverse ===' \
245+
'Note: install decomp2dbg yourself ("pipx install decomp2dbg && decomp2dbg --install") for Ghidra/Cutter GDB integration.' \
246+
'Use "gdb-switch {pwndbg|gef|peda}" to change default GDB plugin.' \
247+
'' > /etc/motd
248+
249+
# Layer 12: GUI launchers
252250
COPY config/start-cutter.sh config/start-ghidra.sh /usr/local/bin/
253-
RUN chown ctf:ctf /home/ctf/.zsh_history \
254-
&& chmod +x /usr/local/bin/start-cutter.sh /usr/local/bin/start-ghidra.sh
251+
RUN chmod +x /usr/local/bin/start-cutter.sh /usr/local/bin/start-ghidra.sh
252+
253+
# pwndbg's gdbinit.py runs `uv pip install --reinstall` on every launch
254+
# when the install dir is read-only for the calling user, which prints a
255+
# scary "Permission denied" error. /opt/pwndbg is owned by root (built in
256+
# layer 7), so the ctf user always hits this. Disable the auto-update.
257+
ENV PWNDBG_NO_AUTOUPDATE=1
255258

256259
USER ctf
257260

258261
# oh-my-zsh
259262
RUN sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended
260263

264+
# Pre-populated history (copied AFTER oh-my-zsh install so the installer
265+
# cannot overwrite it). --chown avoids a separate root chown step.
266+
COPY --chown=ctf:ctf config/zsh_history /home/ctf/.zsh_history
267+
261268
# Set default GDB plugin to pwndbg
262269
RUN gdb-switch pwndbg
263270

README.md

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ docker buildx imagetools inspect ghcr.io/gl0bal01/pwndocker-reverse:latest --for
7272
| **Hex Editors** | ImHex, hexedit |
7373
| **Fuzzing** | AFL++ |
7474
| **Dynamic** | frida, strace, ltrace, villoc |
75-
| **Workflow** | libc-database, pwninit, patchelf, binwalk, unblob, upx |
75+
| **Workflow** | libc-database, patchelf, binwalk, unblob, upx |
7676
| **Analysis** | binary-refinery, opengrep, hash-identifier |
7777
| **Networking** | socat, ncat, tcpdump, tshark, nmap |
7878
| **Editors** | vim, neovim |
@@ -87,7 +87,7 @@ docker buildx imagetools inspect ghcr.io/gl0bal01/pwndocker-reverse:latest --for
8787
docker run -it --rm --cap-add=SYS_PTRACE -v $(pwd):/ctf pwndocker-reverse
8888

8989
# Analyze the binary
90-
checksec --file=./challenge
90+
pwn checksec ./challenge
9191
file ./challenge
9292
strings -n 8 ./challenge
9393
r2 -A ./challenge
@@ -116,7 +116,7 @@ gdb-peda ./binary # launch with PEDA
116116
gdb-switch pwndbg # set default for plain `gdb`
117117
```
118118

119-
### GUI tools (Ghidra, IDA, Cutter, Binary Ninja, jd-gui)
119+
### GUI tools (Ghidra, IDA, Cutter, Binary Ninja, jd-gui, ImHex)
120120

121121
Requires X11 forwarding. On Linux this works natively; on macOS install [XQuartz](https://www.xquartz.org/), on Windows install [VcXsrv](https://sourceforge.net/projects/vcxsrv/) or use WSLg.
122122

@@ -132,6 +132,7 @@ ida64 ./binary # launch IDA Free
132132
binaryninja ./binary # launch Binary Ninja Free
133133
start-cutter.sh ./binary # launch Cutter (checks $DISPLAY)
134134
jd-gui ./app.jar # launch Java decompiler
135+
imhex ./binary # launch ImHex (hex editor)
135136
```
136137

137138
### Disassemblers (CLI)
@@ -204,6 +205,16 @@ tshark -r capture.pcap # analyze pcap
204205

205206
Hit Ctrl+R and search. 78 commands covering all installed tools are already in your history -- no need to remember syntax.
206207

208+
## Security & Trade-offs
209+
210+
This image is built for **local CTF use**, not for hosting as a shared service. Three deliberate trade-offs to be aware of:
211+
212+
- **Passwordless sudo for `ctf`.** The `ctf` user has `NOPASSWD:ALL` so contestants can `apt-get install` extra packages mid-CTF. There is no privilege boundary inside the container — only the Docker isolation around it. Do **not** expose this container as a network service (e.g., via `socat`/`ncat`) without dropping sudo first.
213+
- **GDB plugins (pwndbg, GEF, PEDA) and a few other git-cloned tools track the latest default branch.** This keeps the image fresh as CTF tooling evolves; the **weekly CI rebuild + smoke test** catches upstream breakage. The remaining risk is a malicious upstream commit shipping straight into a rebuild — pin a specific image digest (see *Reproducibility & Verification* above) if that's a concern.
214+
- **IDA Free is fetched from a Wayback Machine snapshot** because Hex-Rays removed v8.4 from their CDN. The build validates that the downloaded blob is an ELF executable of plausible size, but cannot SHA-pin without access to the original asset. If the snapshot ever disappears the build fails loudly rather than silently producing a broken image.
215+
216+
CI runs **Trivy** against every build (fails on HIGH/CRITICAL OS/library CVEs with fixes available) and **shellcheck** on every shipped shell script, so regressions in either dimension block the push.
217+
207218
## Project Structure
208219

209220
```
@@ -213,6 +224,8 @@ config/
213224
gdb-gef # GDB launcher (GEF)
214225
gdb-peda # GDB launcher (PEDA)
215226
gdb-switch # Set default GDB plugin
227+
gdbinit # Shared GDB plugin init (loaded by wrappers)
228+
fetch-gh-release.sh # GitHub release asset fetcher (shared in Dockerfile)
216229
jd-gui # java -jar wrapper
217230
start-cutter.sh # GUI launcher with $DISPLAY check
218231
start-ghidra.sh # GUI launcher with $DISPLAY check

config/fetch-gh-release.sh

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#!/bin/bash
2+
# Fetch a GitHub release asset URL through the API and download it.
3+
# Reads optional auth token from /run/secrets/github_token (BuildKit secret).
4+
# Usage: fetch-gh-release.sh <owner/repo> <jq-filter> <output-path>
5+
# <jq-filter> must yield browser_download_url string(s); first match used.
6+
set -eo pipefail
7+
8+
repo=${1:?repo required}
9+
filter=${2:?jq filter required}
10+
out=${3:?output path required}
11+
12+
tok=$(cat /run/secrets/github_token 2>/dev/null || true)
13+
auth=()
14+
[ -z "$tok" ] || auth=(-H "Authorization: token ${tok}")
15+
16+
url=$(curl -fsSL "${auth[@]}" "https://api.github.com/repos/${repo}/releases/latest" \
17+
| jq -er "${filter}" | head -1)
18+
[ -n "$url" ] || { echo "ERROR: ${repo} asset URL not found"; exit 1; }
19+
20+
wget -qO "$out" "$url"
21+
[ -s "$out" ] || { echo "ERROR: ${repo} download empty"; exit 1; }

config/gdbinit

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# GDB plugin init — each define loads one plugin on demand via init-<name>.
12
define init-pwndbg
23
source /opt/pwndbg/gdbinit.py
34
end

config/zsh_history

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# === Reconnaissance & Analysis ===
2-
checksec --file=./binary
2+
pwn checksec ./binary
33
file ./binary
44
strings -n 8 ./binary
55
strings -e l ./binary

0 commit comments

Comments
 (0)