Skip to content

Commit ebb34fa

Browse files
committed
Add CI pipeline: type checking, integration tests, security scanning
GitHub Actions workflow (.github/workflows/ci.yml) runs on every push and PR to main with four parallel jobs: 1. TypeScript compile (tsc --noEmit, strict mode) 2. Integration test suite against the live API 3. Security checks: secret pattern scanning, .gitignore verification, sensitive file tracking detection 4. Dependency audit (npm audit --production, high severity threshold) Integration test suite (tests/api.test.mjs): - Zero dependencies, native fetch + node:assert - 20 test cases covering auth, writes (all memory_type values), validation (empty title, missing content, invalid type), reads, browse, tag filtering, 404 handling, and response shape assertions - Verifies error responses do not leak stack traces or file paths - Each run uses a unique tag for test data isolation - Runnable standalone: REFLECT_TEST_API_KEY=key node tests/api.test.mjs Security scanner (.github/workflows/security.yml): - Scans for AWS, Stripe, OpenAI, GitHub, Slack, SendGrid, and Reflect Memory key patterns across all tracked files - Verifies .env, credentials.json, and similar files are not tracked Requires REFLECT_TEST_API_KEY repository secret for integration tests. REFLECT_TEST_BASE_URL configurable via repository variables (defaults to https://api.reflectmemory.com). Made-with: Cursor
1 parent f93a1c5 commit ebb34fa

3 files changed

Lines changed: 505 additions & 0 deletions

File tree

.github/workflows/ci.yml

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
env:
10+
NODE_VERSION: "20"
11+
12+
jobs:
13+
typecheck:
14+
name: TypeScript Compile
15+
runs-on: ubuntu-latest
16+
steps:
17+
- uses: actions/checkout@v4
18+
- uses: actions/setup-node@v4
19+
with:
20+
node-version: ${{ env.NODE_VERSION }}
21+
cache: npm
22+
- run: npm ci
23+
- run: npx tsc --noEmit
24+
25+
integration-tests:
26+
name: Integration Tests
27+
runs-on: ubuntu-latest
28+
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository
29+
env:
30+
REFLECT_TEST_API_KEY: ${{ secrets.REFLECT_TEST_API_KEY }}
31+
REFLECT_TEST_BASE_URL: ${{ vars.REFLECT_TEST_BASE_URL }}
32+
steps:
33+
- uses: actions/checkout@v4
34+
- uses: actions/setup-node@v4
35+
with:
36+
node-version: ${{ env.NODE_VERSION }}
37+
- name: Run API integration tests
38+
run: node tests/api.test.mjs
39+
40+
security:
41+
name: Security Checks
42+
runs-on: ubuntu-latest
43+
steps:
44+
- uses: actions/checkout@v4
45+
46+
- name: Verify .env files are gitignored
47+
run: |
48+
MISSING=0
49+
for pattern in '.env' '.env.local' '.env.production'; do
50+
if ! grep -q "^${pattern}$" .gitignore 2>/dev/null && \
51+
! grep -q "^\.env\*$" .gitignore 2>/dev/null; then
52+
echo "::warning::${pattern} not explicitly in .gitignore"
53+
fi
54+
done
55+
if ! grep -q '\.env' .gitignore 2>/dev/null; then
56+
echo "::error::.env is not in .gitignore"
57+
exit 1
58+
fi
59+
60+
- name: Verify no sensitive files are tracked
61+
run: |
62+
SENSITIVE=(.env .env.local .env.production credentials.json serviceAccountKey.json)
63+
for f in "${SENSITIVE[@]}"; do
64+
if git ls-files --error-unmatch "$f" 2>/dev/null; then
65+
echo "::error::Sensitive file '${f}' is tracked by git — remove it and rotate credentials"
66+
exit 1
67+
fi
68+
done
69+
70+
- name: Scan for hardcoded secrets
71+
run: |
72+
PATTERNS=(
73+
'AKIA[0-9A-Z]{16}'
74+
'sk-[a-zA-Z0-9]{20,}'
75+
'sk_live_[a-zA-Z0-9]{20,}'
76+
'rk_live_[a-zA-Z0-9]{20,}'
77+
'ghp_[a-zA-Z0-9]{36}'
78+
'gho_[a-zA-Z0-9]{36}'
79+
'github_pat_[a-zA-Z0-9]{22}_[a-zA-Z0-9]{59}'
80+
'glpat-[a-zA-Z0-9\-]{20,}'
81+
'xox[bpors]-[a-zA-Z0-9\-]+'
82+
'SG\.[a-zA-Z0-9_-]{22}\.[a-zA-Z0-9_-]{43}'
83+
'rm_live_[a-f0-9]{48}'
84+
)
85+
86+
FAILED=0
87+
for pattern in "${PATTERNS[@]}"; do
88+
MATCHES=$(grep -rEn "$pattern" \
89+
--include='*.ts' --include='*.js' --include='*.mjs' \
90+
--include='*.json' --include='*.yml' --include='*.yaml' \
91+
--include='*.md' --include='*.env.*' \
92+
--exclude-dir=node_modules --exclude-dir=dist \
93+
--exclude-dir=.git --exclude='package-lock.json' \
94+
--exclude='ci.yml' --exclude='security.yml' \
95+
. 2>/dev/null || true)
96+
97+
if [ -n "$MATCHES" ]; then
98+
echo "::error::Potential secret matching: ${pattern:0:20}..."
99+
echo "$MATCHES" | head -5
100+
FAILED=1
101+
fi
102+
done
103+
104+
if [ "$FAILED" -eq 1 ]; then
105+
exit 1
106+
fi
107+
108+
audit:
109+
name: Dependency Audit
110+
runs-on: ubuntu-latest
111+
steps:
112+
- uses: actions/checkout@v4
113+
- uses: actions/setup-node@v4
114+
with:
115+
node-version: ${{ env.NODE_VERSION }}
116+
cache: npm
117+
- run: npm ci
118+
- run: npm audit --production --audit-level=high

.github/workflows/security.yml

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# Reflect Memory — Security Scanner
2+
# Runs on the public repo. Scans all files for accidentally committed secrets.
3+
4+
name: Security Scan
5+
6+
on:
7+
push:
8+
branches: [main]
9+
pull_request:
10+
branches: [main]
11+
12+
jobs:
13+
secrets-scan:
14+
name: Scan for Secrets
15+
runs-on: ubuntu-latest
16+
steps:
17+
- uses: actions/checkout@v4
18+
with:
19+
fetch-depth: 0
20+
21+
- name: Scan all files for hardcoded secrets
22+
run: |
23+
echo "Scanning repository for hardcoded secrets..."
24+
25+
PATTERNS=(
26+
'AKIA[0-9A-Z]{16}' # AWS access key ID
27+
'sk-[a-zA-Z0-9]{20,}' # OpenAI / Stripe secret key
28+
'sk_live_[a-zA-Z0-9]{20,}' # Stripe live secret key
29+
'rk_live_[a-zA-Z0-9]{20,}' # Stripe restricted key
30+
'ghp_[a-zA-Z0-9]{36}' # GitHub PAT
31+
'gho_[a-zA-Z0-9]{36}' # GitHub OAuth
32+
'github_pat_[a-zA-Z0-9]{22}_[a-zA-Z0-9]{59}' # GitHub fine-grained PAT
33+
'glpat-[a-zA-Z0-9\-]{20,}' # GitLab PAT
34+
'xox[bpors]-[a-zA-Z0-9\-]+' # Slack token
35+
'hooks\.slack\.com/services/T[A-Z0-9]+/B[A-Z0-9]+/[a-zA-Z0-9]+' # Slack webhook
36+
'sq0atp-[a-zA-Z0-9\-_]{22}' # Square access token
37+
'eyJ[a-zA-Z0-9_-]{10,}\.[a-zA-Z0-9_-]{10,}\.[a-zA-Z0-9_-]{10,}' # JWT
38+
'SG\.[a-zA-Z0-9_-]{22}\.[a-zA-Z0-9_-]{43}' # SendGrid API key
39+
'key-[a-zA-Z0-9]{32}' # Mailgun key
40+
'rm_live_[a-f0-9]{48}' # Reflect Memory live key
41+
)
42+
43+
FAILED=0
44+
for pattern in "${PATTERNS[@]}"; do
45+
MATCHES=$(grep -rEn "$pattern" \
46+
--exclude-dir=node_modules --exclude-dir=dist \
47+
--exclude-dir=.git --exclude='package-lock.json' \
48+
--exclude='security.yml' --exclude='ci.yml' \
49+
. 2>/dev/null || true)
50+
51+
if [ -n "$MATCHES" ]; then
52+
echo "::error::Potential secret matching pattern: ${pattern:0:30}..."
53+
echo "$MATCHES" | head -20
54+
FAILED=1
55+
fi
56+
done
57+
58+
if [ "$FAILED" -eq 1 ]; then
59+
echo ""
60+
echo "::error::Secrets detected in repository. Remove them and rotate the credentials."
61+
exit 1
62+
fi
63+
64+
echo "✓ No hardcoded secrets detected"
65+
66+
- name: Verify sensitive files are gitignored
67+
run: |
68+
SENSITIVE_FILES=(.env .env.local .env.production credentials.json serviceAccountKey.json)
69+
FAILED=0
70+
71+
for f in "${SENSITIVE_FILES[@]}"; do
72+
if git ls-files --error-unmatch "$f" 2>/dev/null; then
73+
echo "::error::Sensitive file '$f' is tracked by git"
74+
FAILED=1
75+
fi
76+
done
77+
78+
if [ "$FAILED" -eq 1 ]; then
79+
exit 1
80+
fi
81+
82+
echo "✓ No sensitive files tracked"

0 commit comments

Comments
 (0)