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
56 changes: 50 additions & 6 deletions .claude/workflows/new-feature.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ Use this workflow when adding new functionality to Signalist.
4. APPROVE → Get user sign-off
5. IMPLEMENT → Build incrementally
6. TEST → Write and run tests
7. REVIEW → Code review
8. ACCEPT → Verify against original request
9. MERGE → Complete
7. SECURITY → Security checklist
8. REVIEW → Code review
9. ACCEPT → Verify against original request
10. MERGE → Complete
```

---
Expand Down Expand Up @@ -208,7 +209,50 @@ docker compose exec app vendor/bin/behat --suite=api # API tests

---

## Step 7: Review
## Step 7: Security

Run this checklist against every feature before code review. Tick only what is relevant — skip rows that don't apply.

### Input & Output
- [ ] All user-supplied input validated via InputDTO constraints (`#[Assert\*]`)
- [ ] No raw user content rendered as HTML without sanitization (DOMPurify / HTMLPurifier)
- [ ] URL fields validated against SSRF-safe constraint (`#[SsrfSafeUrl]`) if the app fetches the URL
- [ ] Article/external content stored sanitized; URLs validated as `http/https` only

### Authentication & Authorization
- [ ] All new endpoints are behind JWT firewall (no `PUBLIC_ACCESS` unless intentional)
- [ ] State processors/providers use `if (!$user instanceof User) throw new AccessDeniedException()` — never `assert()`
- [ ] No user can access another user's resources (ownerId scope enforced in queries)

### Sensitive Data & GDPR
- [ ] No secrets, tokens, or credentials hardcoded — use environment variables
- [ ] No personal data logged in plain text
- [ ] New entities containing personal data have `deletedAt` soft-delete column
- [ ] Data sent to external AI services is anonymized

### API Design
- [ ] New endpoints return RFC 7807 problem details on error
- [ ] No internal error messages exposed to API consumers (generic messages only)
- [ ] Rate limiting considered if endpoint is public or auth-related

### Dependency & Supply Chain
- [ ] Any new composer package checked: `composer audit`
- [ ] Any new npm package checked: `npm audit --audit-level=high`

### Quick Commands
```bash
# Check PHP dependencies for known vulnerabilities
docker compose exec app composer audit

# Check JS dependencies
cd frontend && npm audit --audit-level=high
```

If any checklist item raises a concern, fix it before proceeding to review.

---

## Step 8: Review

Use `/review` to run a self-contained code review against architecture, quality, security, and test criteria.

Expand All @@ -219,7 +263,7 @@ Use `/review` to run a self-contained code review against architecture, quality,

---

## Step 8: Acceptance Check
## Step 9: Acceptance Check

**Before marking complete, verify the implementation matches the original request.**

Expand Down Expand Up @@ -272,7 +316,7 @@ Present to user for final approval:

---

## Step 9: Merge
## Step 10: Merge

### Pre-Merge Checklist
- [ ] All tests passing
Expand Down
2 changes: 1 addition & 1 deletion .env
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ MESSENGER_TRANSPORT_DSN=doctrine://default?auto_setup=0
###> lexik/jwt-authentication-bundle ###
JWT_SECRET_KEY=%kernel.project_dir%/config/jwt/private.pem
JWT_PUBLIC_KEY=%kernel.project_dir%/config/jwt/public.pem
JWT_PASSPHRASE=change_me
JWT_PASSPHRASE=
###< lexik/jwt-authentication-bundle ###

###> symfony/mailer ###
Expand Down
2 changes: 1 addition & 1 deletion .env.dev
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@

###> symfony/framework-bundle ###
APP_SECRET=4416dca5875954bac65fe0f2cd218c89
APP_SECRET=
###< symfony/framework-bundle ###
29 changes: 26 additions & 3 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
uses: actions/checkout@v5

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v4

- name: Build Docker images
run: docker compose build --no-cache
Expand Down Expand Up @@ -88,6 +88,11 @@ jobs:
- name: Run Behat
run: docker compose exec -T app vendor/bin/behat --no-interaction --colors

- name: Audit PHP dependencies
# Exit 1 = vulnerabilities (blocking), exit 2 = abandoned packages (non-blocking)
run: |
docker compose exec -T app composer audit || { code=$?; [ "$code" -eq 2 ] || exit "$code"; }

- name: Run CS Fixer (dry-run)
run: docker compose exec -T app vendor/bin/php-cs-fixer fix --dry-run --diff

Expand All @@ -103,6 +108,20 @@ jobs:
- name: Doctrine Schema Validator
run: docker compose exec -T app php bin/console -e test doctrine:schema:validate --skip-sync || true

secret-scan:
name: Secret Scan
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v5
with:
fetch-depth: 0

- name: Scan for secrets
uses: trufflesecurity/trufflehog@main
with:
extra_args: --only-verified

lint:
name: Docker Lint
runs-on: ubuntu-latest
Expand Down Expand Up @@ -156,11 +175,15 @@ jobs:

- name: Type check
if: steps.check_frontend.outputs.exists == 'true'
run: npm run typecheck || true
run: npm run typecheck

- name: Lint
if: steps.check_frontend.outputs.exists == 'true'
run: npm run lint || true
run: npm run lint

- name: Audit JS dependencies
if: steps.check_frontend.outputs.exists == 'true'
run: npm audit --audit-level=high

- name: Run tests
if: steps.check_frontend.outputs.exists == 'true'
Expand Down
5 changes: 5 additions & 0 deletions config/packages/api_platform.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,8 @@ api_platform:
mapping:
paths:
- '%kernel.project_dir%/src/Infrastructure/ApiPlatform/Resource'

when@prod:
api_platform:
enable_swagger_ui: false
enable_docs: false
2 changes: 1 addition & 1 deletion docker/frankenphp/Caddyfile
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@
header {
X-Frame-Options "SAMEORIGIN"
X-Content-Type-Options "nosniff"
X-XSS-Protection "1; mode=block"
Referrer-Policy "strict-origin-when-cross-origin"
Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' https: data:; font-src 'self'; connect-src 'self'; object-src 'none'; frame-ancestors 'none';"
-Server
}

Expand Down
14 changes: 7 additions & 7 deletions features/api/register.feature
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,24 @@ Feature: Authentication - Register
"""
{
"email": "newuser@signalist.app",
"password": "securepassword"
"password": "Fixture_Only!NotAReal#Pw9"
}
"""
Then the response status code should be 201
And the response should be JSON
And the JSON response should contain "id"
And the JSON response should contain "message"

Scenario: Registration with duplicate email
Scenario: Registration with duplicate email returns same generic response
When I send a "POST" request to "/api/v1/auth/register" with body:
"""
{
"email": "admin@signalist.app",
"password": "securepassword"
"password": "Fixture_Only!NotAReal#Pw9"
}
"""
Then the response status code should be 409
Then the response status code should be 201
And the response should be JSON
And the JSON response should be a RFC 7807 problem
And the JSON response should contain "message"

Scenario: Registration with missing email
When I send a "POST" request to "/api/v1/auth/register" with body:
Expand Down Expand Up @@ -58,7 +58,7 @@ Feature: Authentication - Register
"""
{
"email": "not-an-email",
"password": "securepassword"
"password": "Fixture_Only!NotAReal#Pw9"
}
"""
Then the response status code should be 422
Expand Down
Loading