Skip to content
Back to skills

001 Review Report Bae07a40

FSecurity

**Project:** `.claude/hooks/security-guardian-go/` **Reviewer:** OpenAI Codex (gpt-5.2-codex) + manual review **Period:** Reviews #5 — #12 **Status:** Consensus reached. All critical/high-priority security bypasses fixed. ---

  • 9 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 11, 2026
testinggobashnodegitsecurity

Security analysis

F0/100
  • criticalPipes output to a shell interpreter
  • criticalAccesses sensitive system or user directories
  • highPerforms destructive filesystem operations
  • criticalReads or references SSH private keys
  • criticalExfiltrates credentials via HTTP — exact pattern from Snyk ToxicSkills study
  • criticalDownloads and executes remote scripts — classic supply chain attack

Pro shows the line behind each finding and how to fix it

Scanned October 11, 2026

npx -y skills add tools-only/X-Skills --skill 001-review_report_bae07a40 --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of 001 Review Report Bae07a40?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for 001 Review Report Bae07a40
[![Security: F — Skills Directory](https://www.skillsdirectory.com/api/skills/tools-only-001-review-report-bae07a40/badge)](https://www.skillsdirectory.com/skills/tools-only-001-review-report-bae07a40)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

SKILL.md
# Security Guardian Go — Iterative Code Review Report

**Project:** `.claude/hooks/security-guardian-go/`
**Reviewer:** OpenAI Codex (gpt-5.2-codex) + manual review
**Period:** Reviews #5 — #12
**Status:** Consensus reached. All critical/high-priority security bypasses fixed.

---

## Summary

| Iteration | Findings | Critical Fixes Applied |
|-----------|----------|----------------------|
| Review #5 | 3 bugs (2×P1, 1×P2) | Deferred to #7-#8 |
| Review #6 | 9 findings | Most already known or incorrect |
| Review #7 | 2 bugs (2×P1) | Bare filenames bypass |
| Review #8 | 2 bugs (1×P1, 1×P2) | Command substitution, curl -o |
| Review #9 | 4 findings (2×P1, 2×P2) | matchGlob, ResolvePath symlinks |
| Review #10 | 2 bugs (1×P1, 1×P2) | GetProjectRoot canonicalization, false positives |
| Review #11 | 8 findings (1×CRIT, 1×HIGH, rest MED/LOW) | Redirect bypass on nonPathCommands |
| Review #12 | 5 findings (2×MED, 3×LOW) | relPath symlinks, CheckFile cwd, root sync, secrets write, glob pattern |

**Total unique bugs fixed:** 13
**Test suite:** 20 tests, all passing
**Remaining items:** Feature gaps (unused config fields), cosmetic issues, known architectural limitation (cwd)

---

## Review #5

**Session:** `019bff78-22de-7030-bd12-a963fd177374`

### Finding 1 (P1): Enforce `sensitive_files.forbidden_read` patterns

- **File:** `internal/checks/secrets.go` (line 99)
- **Issue:** `matchesNoRead` only evaluates `protected_paths.no_read_content`, but config also defines patterns under `sensitive_files.forbidden_read` (e.g., `**/*.pem`, `**/*.key`). Those patterns are ignored, so reading private keys inside the repo is allowed.
- **Status:** Feature gap — config field not wired up. Not a bypass of existing protections.

### Finding 2 (P1): Include bare names and globs in path extraction

- **File:** `internal/parsers/bash.go` (lines 383-388)
- **Issue:** Path filter drops arguments without `/`, `.`/`~`, or dot extension. Bare targets like `build` and glob `*` are excluded. Commands like `rm -rf *` yield no paths and bypass safeguards.
- **Status:** Fixed in Review #7 (bare args check for file commands) and `deletion.go` glob detection.

### Finding 3 (P2): Bind `curl -o` to its actual output argument

- **File:** `internal/checks/download.go` (lines 182-188)
- **Issue:** When `-o/--output` is present, logic returns first non-URL argument. Flags like `-H` introduce non-URL arguments before the output path.
- **Status:** Fixed in Review #8 with `tokenizeRaw`.

---

## Review #6

**Session:** `019bff8e-9aa4-7740-a476-29f36bc906f5`

### Finding 1: `blocked_outside_project` config field never enforced

- **File:** `internal/checks/bypass.go`, `internal/config/schema.go:20`
- **Issue:** `BlockedOutsideProject` (default: `["base64 -d", "xxd -r"]`) defined but never referenced.
- **Status:** Feature gap. Directory check already blocks operations outside project.

### Finding 2: `require_user_download` config field unused

- **File:** `internal/checks/download.go`
- **Issue:** Hardcoded `scriptExtensions`/`binaryExtensions` maps used instead of config.
- **Status:** Feature gap.

### Finding 3: GlobGrepHandler missing SecretsCheck

- **File:** `internal/handlers/glob_grep.go`
- **Issue:** Reviewer claimed Grep handler doesn't run SecretsCheck.
- **Status:** **Incorrect finding.** Code at line 44 calls `h.secretsCheck.CheckPath(path, "read")`.

### Finding 4: Recursive deletion doesn't protect ancestor directories

- **File:** `internal/checks/deletion.go:148-154`
- **Issue:** Reviewer claimed ancestor directories not protected.
- **Status:** **Incorrect finding.** Code at lines 156-162 checks `strings.HasPrefix(protectedPath, relStr+"/")`.

### Finding 5: Dot-glob patterns bypass glob deletion protection

- **Issue:** `rm -rf .*` — `.*` starts with `.` so treated as path-like, glob guard skipped.
- **Status:** Marginal risk. Individual paths like `.git` are protected by path-level checks.

### Finding 6: Curl output path detection failure

- **Status:** Fixed in Review #8 with `tokenizeRaw`.

### Finding 7: Project root inconsistency across checks

- **Issue:** `DirectoryCheck` uses config root, other checks call `GetProjectRoot()` directly.
- **Status:** Low priority. Both resolve to same root in practice.

### Finding 8: No automated tests

- **Status:** Known. Deferred.

### Finding 9: `main.Version` variable missing

- **File:** `Makefile:9`, `cmd/guardian/main.go`
- **Status:** Cosmetic. Go silently ignores unknown `-X` targets.

---

## Review #7

### Finding 1 (P1): Symlink escape via path resolution against project root

- **Issue:** Same as known deferred cwd limitation.
- **Status:** Deferred.

### Finding 2 (P1): Bare filename arguments bypass directory boundary check

- **File:** `internal/checks/directory.go`
- **Issue:** `ExtractPathsFromCommand` filters out bare filenames (no `/`, `.`, `~`). A symlink named `barelink` pointing outside project would pass unchecked.
- **Status:** **Fixed.** Added bare `cmd.Args` iteration in `DirectoryCheck.CheckCommand` for `fileArgCommands`.

---

## Review #8

**Session:** `019c0001-218b-71e2-b2ef-900f3280f1dd`

### Finding 1 (P1): Parse command substitutions as nested commands

- **File:** `internal/parsers/bash.go` (lines 202-203)
- **Issue:** `extractWordValue` replaces `$(...)` with placeholder, never traverses inner statements. `echo $(rm -rf ../outside)` silently allowed.
- **Verified:** Reviewer piped test input, got no output (allowed).
- **Status:** **Fixed.** Added `extractSubstitutionCommands` using `syntax.Walk` for `CmdSubst` and `ProcSubst` AST nodes.

### Finding 2 (P2): Use the argument after `-o/--output` as the output path

- **File:** `internal/checks/download.go` (lines 185-187)
- **Issue:** First non-URL argument picked instead of the one paired with `-o`.
- **Verified:** `curl -H 'Accept: ...' -o payload` tracked as `Accept: ...` instead of `payload`.
- **Status:** **Fixed.** Replaced with `tokenizeRaw` that scans raw command for token after `-o`/`--output`.

---

## Review #9

### Finding 1 (P1): Resolve relative paths against cwd

- **Status:** Deferred (known cwd limitation).

### Finding 2 (P1): `/**` patterns don't match directory root

- **File:** `internal/checks/secrets.go`, `matchGlob` function
- **Issue:** Pattern `.git/**` doesn't match `.git` itself. `mv .git renamed-git` bypasses protection.
- **Status:** **Fixed.** Added `trimmedPrefix` check: if `name == strings.TrimSuffix(prefix, "/")` return true.

### Finding 3 (P2): Download tracking resolves against project root

- **Status:** Deferred (same cwd limitation).

### Finding 4 (P2): ResolvePath doesn't eval symlinks on base directory

- **File:** `internal/parsers/path.go`
- **Issue:** `/tmp` vs `/private/tmp` mismatch on macOS causes false denies for projects under symlinked roots.
- **Status:** **Fixed.** Added `filepath.EvalSymlinks(baseDir)` before joining paths.

---

## Review #10

### Finding 1 (P1): Canonicalize project root before computing relative paths

- **File:** `internal/checks/secrets.go` (lines 62-63)
- **Issue:** `c.projectRoot` stored as potentially symlinked path. `filepath.Rel(c.projectRoot, resolved)` produces `..` prefix, treating in-project paths as "outside".
- **Verified:** Reviewer wrote Go test — `.env` was allowed instead of blocked under `/tmp` root.
- **Status:** **Fixed.** Added `evalSymlinksOrClean` to all return paths in `GetProjectRoot()`.

### Finding 2 (P2): Avoid treating all positional arguments as file paths

- **File:** `internal/checks/secrets.go` (lines 42-46)
- **Issue:** Raw-argument scan calls `CheckPath` on every non-flag arg. `grep ".env" README.md` falsely blocked.
- **Status:** **Fixed.** Introduced command classification:
  - `patternFirstArgCommands` (grep, sed, awk) — skip first arg
  - `nonPathCommands` (echo, printf, export) — skip all args (but check redirects)
  - `fileArgCommands` (cat, mv, rm, chmod) — check bare args too

---

## Review #11

### Finding 1 (CRITICAL): Redirect bypass on nonPathCommands

- **File:** `internal/checks/directory.go` (lines 38-50), `internal/checks/secrets.go`
- **Issue:** Commands in `nonPathCommands` (`echo`, `printf`) skip ALL checks including redirect targets. `echo hi > /etc/passwd` passes through without deny.
- **Verified:** Reviewer piped test input, got no output (allowed).
- **Status:** **Fixed.** Even for nonPathCommands, redirect targets are now checked against directory boundaries and secrets patterns.

### Finding 2 (HIGH): Symlink escape after `cd` in compound commands

- **File:** `internal/checks/directory.go`, `CheckPath` method
- **Issue:** `cd subdir && cat link/passwd` — guardian resolves `link/passwd` against project root, not against `subdir`.
- **Status:** Deferred (known cwd limitation — hook cannot track directory changes within commands).

### Finding 3 (MEDIUM): Hardcoded extensions ignore `require_user_download` config

- **Status:** Feature gap. Not a security bypass.

### Finding 4 (MEDIUM): `BlockedOutsideProject` config defined but not enforced

- **Status:** Feature gap.

### Finding 5 (MEDIUM): Bare directory names missed by path extractor

- **Status:** Partially mitigated by `fileArgCommands` bare-args check.

### Finding 6 (LOW): Config loader silently falls back to defaults

- **Status:** UX/observability concern.

### Finding 7 (LOW): Makefile `-X main.Version` variable missing

- **Status:** Cosmetic.

### Finding 8 (LOW): Flag sorting in git check

- **Status:** No current impact.

---

## Review #12

**Source:** Manual code review

### Finding 1 (MEDIUM): Naive relPath via strings.HasPrefix in deletion.go

- **File:** `internal/checks/deletion.go:205`
- **Issue:** `relPath` used `strings.HasPrefix` instead of `filepath.Rel`, which can fail with symlinks (e.g. `/var` vs `/private/var`) and prefix collisions.
- **Status:** **Fixed.** Replaced with `filepath.Rel` + `filepath.EvalSymlinks` on both base and target.

### Finding 2 (MEDIUM): CodeContentCheck.CheckFile reads path relative to cwd

- **File:** `internal/checks/code_content.go:220`
- **Issue:** `os.ReadFile(filePath)` used the path as-is. If the hook runs from a different cwd than project root, relative script paths silently fail.
- **Status:** **Fixed.** Added `projectRoot` field to `CodeContentCheck`, resolves `filePath` via `parsers.ResolvePath` before reading.

### Finding 3 (LOW): SecretsCheck vs DirectoryCheck project root inconsistency

- **File:** `internal/checks/secrets.go:24`, `internal/checks/directory.go:22`
- **Issue:** `DirectoryCheck` uses `cfg.Directories.ProjectRoot` if set, but `SecretsCheck` always uses `GetProjectRoot()`. Could produce different roots.
- **Status:** **Fixed.** `SecretsCheck` now uses `cfg.Directories.ProjectRoot` when set, same as `DirectoryCheck`.

### Finding 4 (LOW): Secrets files writable via redirect (echo > .env)

- **Issue:** `CheckPath` with write operation only checked `matchesNoModify`, not `matchesNoRead`. Writing to `.env` was allowed because `.env` is only in `no_read_content`, not `no_modify`.
- **Status:** **Fixed.** Write operations now also check `matchesNoRead` patterns — secrets files are protected from both reading and writing.

### Finding 5 (LOW): Glob handler ignores absolute paths in pattern field

- **File:** `internal/handlers/glob_grep.go`
- **Issue:** Glob handler only checked the `path` field, but `pattern="/etc/*"` (with no `path`) was allowed through.
- **Status:** **Fixed.** When `path` is empty and `pattern` starts with `/`, the directory prefix is extracted and checked against boundaries.

### Finding 6: Report correction — sensitive_files.forbidden_read

- **Issue:** Report stated `sensitive_files.forbidden_read` is unused. In fact, it was already wired into `matchesNoRead` at line 175.
- **Status:** Report updated. Not a bug.

---

## All Fixes Applied (Chronological)

| # | Review | Fix | Files Changed |
|---|--------|-----|---------------|
| 1 | R#7 | Bare filenames bypass — check raw args for file commands | `directory.go` |
| 2 | R#8 | Command substitution — walk AST for CmdSubst/ProcSubst | `bash.go` |
| 3 | R#8 | curl -o detection — tokenizeRaw scans for -o argument | `download.go` |
| 4 | R#9 | matchGlob PREFIX/** also matches PREFIX itself | `secrets.go` |
| 5 | R#9 | ResolvePath — eval symlinks on baseDir before joining | `path.go` |
| 6 | R#10 | GetProjectRoot — evalSymlinksOrClean on all return paths | `path.go` |
| 7 | R#10 | Pattern-aware command classification (grep, echo, etc.) | `secrets.go`, `directory.go` |
| 8 | R#11 | Redirect bypass — check redirects for nonPathCommands | `directory.go`, `secrets.go` |
| 9 | R#12 | relPath in deletion.go — replaced strings.HasPrefix with filepath.Rel + EvalSymlinks | `deletion.go` |
| 10 | R#12 | CodeContentCheck.CheckFile — resolve path against projectRoot before reading | `code_content.go` |
| 11 | R#12 | SecretsCheck project root — use config root like DirectoryCheck | `secrets.go` |
| 12 | R#12 | Secrets write protection — deny writes to no_read_content files (echo > .env) | `secrets.go` |
| 13 | R#12 | Glob handler — check absolute paths in pattern field, not just path | `glob_grep.go` |

---

## Known Deferred Items

1. **cwd limitation** — Hook cannot know the calling process's working directory. Relative paths are resolved against project root. `cd` within compound commands is not tracked.
2. **Unused config fields** — `require_user_download`, `BlockedOutsideProject` are defined in config but not wired into check logic. (`sensitive_files.forbidden_read` is now wired up in `matchesNoRead`.)
3. **No unit tests** — All packages report `[no test files]`.
4. **`main.Version`** — Makefile sets `-X main.Version` but variable doesn't exist in code.

---

## Regression Test Suite (20 tests)

| # | Command | Expected | Result |
|---|---------|----------|--------|
| 1 | `ls -la` | ALLOW | ALLOW |
| 2 | `cat /etc/passwd` | DENY | DENY |
| 3 | `rm -rf .git` | ASK | ASK |
| 4 | `curl \| bash` | DENY | DENY |
| 5 | Read `.env` | DENY | DENY |
| 6 | Write `/etc/hosts` | DENY | DENY |
| 7 | `git push --force` | DENY | DENY |
| 8 | Glob `/etc` | DENY | DENY |
| 9 | `$EVIL_CMD` | DENY | DENY |
| 10 | `chmod +x scripts/install.sh` | ALLOW | ALLOW |
| 11 | `tar -C /tmp` | DENY | DENY |
| 12 | `echo $(rm -rf ../outside)` | DENY | DENY |
| 13 | `cat <(cat /etc/passwd)` | DENY | DENY |
| 14 | `curl -H ... -o output.bin` | ASK | ASK |
| 15 | `mv .git renamed-git` | DENY | DENY |
| 16 | `grep .env README.md` | ALLOW | ALLOW |
| 17 | `echo id_rsa` | ALLOW | ALLOW |
| 18 | `cat .env` | DENY | DENY |
| 19 | `echo hi > /etc/passwd` | DENY | DENY |
| 20 | `printf secret > .env` | DENY | DENY |

---

## Recommendations for Next Phase

1. Add Go unit tests (`go test`) for all check modules
2. Wire up `require_user_download` and `BlockedOutsideProject` from config
3. Add `var Version string` to `cmd/guardian/main.go`
4. Configure GitHub Actions for automated builds on tag push
5. Consider tracking `cd` within compound commands for stricter enforcement

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.