Skip to content

Commit a8b1f02

Browse files
committed
fix(serverless): plug SSR bugs, harden secret handling, document concerns
1 parent cc596cc commit a8b1f02

29 files changed

Lines changed: 3446 additions & 419 deletions

.githooks/pre-commit

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
#!/usr/bin/env bash
2+
#
3+
# NextDeploy pre-commit hook
4+
#
5+
# Blocks commits that contain:
6+
# - Compiled binaries (Mach-O, ELF, PE)
7+
# - Files larger than MAX_FILE_KB
8+
# - Obvious secrets (.env, AWS keys, private keys)
9+
# - Files matching ignored patterns
10+
#
11+
# Install with: make pre-commit-install
12+
# Bypass (use sparingly, with explicit reason): git commit --no-verify
13+
14+
set -euo pipefail
15+
16+
MAX_FILE_KB=512
17+
RED='\033[0;31m'
18+
YELLOW='\033[0;33m'
19+
GREEN='\033[0;32m'
20+
NC='\033[0m'
21+
22+
fail=0
23+
24+
# Collect staged files (added/copied/modified, not deleted)
25+
staged=$(git diff --cached --name-only --diff-filter=ACM)
26+
if [ -z "$staged" ]; then
27+
exit 0
28+
fi
29+
30+
err() { echo -e "${RED}$*${NC}" >&2; fail=1; }
31+
warn() { echo -e "${YELLOW}$*${NC}" >&2; }
32+
ok() { echo -e "${GREEN}$*${NC}"; }
33+
34+
# 1. Block compiled binaries by content sniffing
35+
for f in $staged; do
36+
[ -f "$f" ] || continue
37+
mime=$(file --mime-type -b "$f" 2>/dev/null || echo "unknown")
38+
case "$mime" in
39+
application/x-mach-binary|application/x-executable|application/x-sharedlib|application/x-pie-executable|application/x-dosexec)
40+
err "Binary file blocked: $f ($mime)"
41+
;;
42+
esac
43+
done
44+
45+
# 2. Block oversized files
46+
for f in $staged; do
47+
[ -f "$f" ] || continue
48+
size_kb=$(du -k "$f" | cut -f1)
49+
if [ "$size_kb" -gt "$MAX_FILE_KB" ]; then
50+
err "File too large: $f (${size_kb}KB > ${MAX_FILE_KB}KB). Use Git LFS or .gitignore."
51+
fi
52+
done
53+
54+
# 3. Block secrets by filename
55+
for f in $staged; do
56+
case "$f" in
57+
.env|.env.*|*.pem|*.key|.nextdeploy/.env|.secrets|credentials.json|service-account*.json)
58+
# allow examples and testdata
59+
case "$f" in
60+
.env.example|*/testdata/*) continue ;;
61+
esac
62+
err "Secret file blocked: $f"
63+
;;
64+
esac
65+
done
66+
67+
# 4. Block secret PATTERNS in staged content (text files only)
68+
secret_pattern='(AKIA[0-9A-Z]{16}|aws_secret_access_key\s*=\s*[A-Za-z0-9/+=]{40}|-----BEGIN [A-Z ]*PRIVATE KEY-----|ghp_[A-Za-z0-9]{36}|xox[baprs]-[A-Za-z0-9-]+)'
69+
for f in $staged; do
70+
[ -f "$f" ] || continue
71+
mime=$(file --mime-type -b "$f" 2>/dev/null || echo "unknown")
72+
case "$mime" in text/*|application/json|application/xml|application/yaml) ;; *) continue ;; esac
73+
if grep -EH "$secret_pattern" "$f" >/dev/null 2>&1; then
74+
err "Possible secret detected in $f"
75+
grep -EHn "$secret_pattern" "$f" | sed 's/^/ /' >&2 || true
76+
fi
77+
done
78+
79+
# 5. gofmt check on staged Go files
80+
go_files=$(echo "$staged" | grep '\.go$' || true)
81+
if [ -n "$go_files" ]; then
82+
if ! command -v gofmt >/dev/null 2>&1; then
83+
warn "gofmt not found — skipping format check"
84+
else
85+
bad=$(gofmt -s -l $go_files || true)
86+
if [ -n "$bad" ]; then
87+
err "Go files need gofmt:"
88+
echo "$bad" | sed 's/^/ /' >&2
89+
echo " Fix with: make fmt" >&2
90+
fi
91+
fi
92+
fi
93+
94+
# 6. go vet on changed packages (cheap, catches obvious bugs)
95+
if [ -n "$go_files" ] && command -v go >/dev/null 2>&1; then
96+
pkgs=$(echo "$go_files" | xargs -n1 dirname | sort -u | sed 's|^|./|')
97+
if ! go vet $pkgs >/dev/null 2>&1; then
98+
err "go vet failed on changed packages:"
99+
go vet $pkgs 2>&1 | sed 's/^/ /' >&2 || true
100+
fi
101+
fi
102+
103+
if [ "$fail" -ne 0 ]; then
104+
echo
105+
echo -e "${RED}Pre-commit hook blocked the commit.${NC}" >&2
106+
echo " - Fix the issues above, OR" >&2
107+
echo " - Bypass with --no-verify ONLY if you understand the risk and document why." >&2
108+
exit 1
109+
fi
110+
111+
ok "Pre-commit checks passed"

.github/workflows/ci.yml

Lines changed: 69 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,97 +16,141 @@ permissions:
1616
env:
1717
GO_VERSION: "1.25.x"
1818
GOTOOLCHAIN: local
19+
GOLANGCI_LINT_VERSION: "v2.5.0"
1920

2021
jobs:
21-
build:
22-
name: Build & Test
22+
modules:
23+
name: Modules
2324
runs-on: ubuntu-latest
2425
steps:
2526
- uses: actions/checkout@v4
26-
2727
- uses: actions/setup-go@v5
2828
with:
2929
go-version: ${{ env.GO_VERSION }}
3030
cache: true
3131
cache-dependency-path: go.sum
32-
3332
- name: Verify modules
3433
run: |
3534
go mod verify
3635
go mod tidy
3736
git diff --exit-code go.mod go.sum
3837
39-
- name: Build CLI
40-
run: go build -o /dev/null ./cli
38+
fmt:
39+
name: Format Check
40+
runs-on: ubuntu-latest
41+
steps:
42+
- uses: actions/checkout@v4
43+
- uses: actions/setup-go@v5
44+
with:
45+
go-version: ${{ env.GO_VERSION }}
46+
cache: true
47+
cache-dependency-path: go.sum
48+
- name: gofmt
49+
run: make fmt-check
4150

51+
build:
52+
name: Build (${{ matrix.os }})
53+
runs-on: ${{ matrix.os }}
54+
strategy:
55+
fail-fast: false
56+
matrix:
57+
os: [ubuntu-latest, macos-latest]
58+
steps:
59+
- uses: actions/checkout@v4
60+
- uses: actions/setup-go@v5
61+
with:
62+
go-version: ${{ env.GO_VERSION }}
63+
cache: true
64+
cache-dependency-path: go.sum
65+
- name: Build CLI
66+
run: make build-cli
4267
- name: Build Daemon
43-
run: go build -o /dev/null ./daemon/cmd/nextdeployd
44-
45-
- name: Test with coverage
46-
run: go test -race -timeout 5m -coverprofile=coverage.out -covermode=atomic ./...
68+
if: runner.os == 'Linux'
69+
run: make build-daemon
4770

71+
test:
72+
name: Unit Tests
73+
runs-on: ubuntu-latest
74+
needs: [modules]
75+
steps:
76+
- uses: actions/checkout@v4
77+
- uses: actions/setup-go@v5
78+
with:
79+
go-version: ${{ env.GO_VERSION }}
80+
cache: true
81+
cache-dependency-path: go.sum
82+
- name: Run unit tests
83+
run: make test-unit
4884
- name: Coverage summary
4985
run: go tool cover -func=coverage.out | tail -1
50-
5186
- name: Upload coverage
5287
uses: actions/upload-artifact@v4
5388
with:
5489
name: coverage
5590
path: coverage.out
91+
retention-days: 7
5692

5793
lint:
5894
name: Lint
5995
runs-on: ubuntu-latest
96+
needs: [modules]
6097
permissions:
6198
contents: read
6299
pull-requests: read
63100
steps:
64101
- uses: actions/checkout@v4
65-
66102
- uses: actions/setup-go@v5
67103
with:
68104
go-version: ${{ env.GO_VERSION }}
69105
cache: true
70106
cache-dependency-path: go.sum
71-
72107
- name: golangci-lint
73108
uses: golangci/golangci-lint-action@v6
74109
with:
75-
version: latest
110+
version: ${{ env.GOLANGCI_LINT_VERSION }}
76111
args: --timeout 10m
77112

78113
vuln:
79114
name: Vulnerability Check
80115
runs-on: ubuntu-latest
81116
steps:
82117
- uses: actions/checkout@v4
83-
84118
- name: govulncheck
85119
uses: golang/govulncheck-action@v1
86120
with:
87121
go-version-input: ${{ env.GO_VERSION }}
88122
check-latest: true
89123

124+
hook-smoke:
125+
name: Pre-commit Hook Smoke Test
126+
runs-on: ubuntu-latest
127+
steps:
128+
- uses: actions/checkout@v4
129+
- name: Validate hook syntax
130+
run: bash -n .githooks/pre-commit
131+
- name: Run hook against HEAD
132+
run: |
133+
# Stage everything from the last commit so the hook has something
134+
# to inspect, then run it. Catches accidental binaries on main.
135+
git -c user.email=ci@example.com -c user.name=ci \
136+
reset --soft HEAD~1 || true
137+
./.githooks/pre-commit || (echo "Hook failed on HEAD"; exit 1)
138+
90139
quality:
91-
name: Code Quality
140+
name: Code Quality (informational)
92141
runs-on: ubuntu-latest
93142
steps:
94143
- uses: actions/checkout@v4
95144
with:
96145
fetch-depth: 0
97-
98146
- uses: actions/setup-go@v5
99147
with:
100148
go-version: ${{ env.GO_VERSION }}
101149
cache: true
102150
cache-dependency-path: go.sum
103-
104-
- name: Install scc (LOC counter)
151+
- name: Install scc
105152
run: go install github.com/boyter/scc/v3@latest
106-
107-
- name: Lines of code report
153+
- name: Lines of code
108154
run: scc --format wide --exclude-dir vendor,test-serverless-app,.next
109-
110-
- name: Benchmark
111-
run: |
112-
go test -bench=. -benchmem -run='^$' ./... 2>/dev/null || echo "No benchmarks found"
155+
- name: Benchmarks (best-effort)
156+
run: go test -bench=. -benchmem -run='^$' ./... 2>/dev/null || echo "No benchmarks found"

.gitignore

Lines changed: 57 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,68 @@
1-
/bin/
2-
/.env
3-
./.keys
4-
.secrets
1+
# ── Build artifacts ──────────────────────────────────────────────
2+
/bin/
53
/dist/
4+
/nextdeploy
5+
/nextdeployd
6+
*.exe
7+
*.test
8+
*.out
9+
*.prof
10+
coverage.out
11+
coverage.html
612

7-
/daemon.md
13+
# ── Secrets and credentials ──────────────────────────────────────
14+
.env
15+
.env.*
16+
!.env.example
817
.secrets
18+
.keys
19+
.nextdeploy/.env
20+
.nextdeploy/secrets/
21+
*.pem
22+
*.key
23+
!**/testdata/**/*.pem
24+
!**/testdata/**/*.key
25+
credentials.json
26+
service-account*.json
927

10-
# Documentation
11-
*.md
12-
!README.md
13-
!TECH_DOCS.md
28+
# ── Generated / packaged ─────────────────────────────────────────
29+
*.tar.gz
30+
*.tgz
31+
*.zip
32+
!assets/**/*.zip
33+
node_modules/
34+
.next/
35+
out/
1436

15-
# Root-level binaries
16-
/nextdeploy
17-
/nextdeployd
37+
# ── Tooling / IDE / OS ───────────────────────────────────────────
38+
.idea/
39+
.vscode/
40+
*.swp
41+
*.swo
42+
*~
43+
.DS_Store
44+
Thumbs.db
45+
.air.toml
46+
.air.*.toml.bak
1847

19-
# Test artifacts
20-
test*.go
48+
# ── Test scratch ─────────────────────────────────────────────────
2149
testfile*
2250
testdir*/
2351
testsym*
2452
testwalk/
2553
test_src_dir/
54+
55+
# ── Documentation ────────────────────────────────────────────────
56+
# Track only the docs we curate. Anything else is scratch/draft.
57+
*.md
58+
!README.md
59+
!TECH_DOCS.md
60+
!CODE_QUALITY.md
61+
!CHANGELOG.md
62+
!CONTRIBUTING.md
63+
!cli/internal/serverless/REVIEW.md
64+
!**/README.md
65+
66+
# ── Misc ─────────────────────────────────────────────────────────
67+
/daemon.md
68+
vendor/

0 commit comments

Comments
 (0)