feat(security/ci): SBOM + ZAP DAST + GDPR export/deletion - #470
Merged
Conversation
tayebmokni
enabled auto-merge (squash)
May 25, 2026 21:32
|
Heads up — this PR touches strings that often signal a security disclosure ( If this PR fixes or describes a real vulnerability that has not yet been publicly disclosed, please stop and use the private path:
See If this is a false positive (test fixture, doc update, release notes, etc.) please ignore this comment — the check is advisory only and does not block the PR. Matched files:
|
tayebmokni
force-pushed
the
feat/security-scanners-sbom-gdpr
branch
from
May 26, 2026 17:32
c5a8704 to
e35cf12
Compare
…aks (#190, #196) Single security-scan job in ci.yml runs five fail-fast scanners on every PR plus a weekly Sunday cron: - govulncheck (Go vuln DB, reachability-aware) - osv-scanner (cross-language: Go, npm, ...) - semgrep with p/owasp-top-ten + p/security-audit packs - gosec (G-rules, per workspace module) - gitleaks (secret scan with --redact, JSON report uploaded as artifact) The aggregate ci gate now depends on security-scan so branch protection picks it up automatically. Closes #190. Closes #196. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Signed-off-by: Tayeb Mokni <tayeb.mokni@gmail.com>
) #142 — .github/workflows/sbom.yml Matrix-generated CycloneDX (and SPDX) SBOMs via anchore/syft for apps/api, apps/web, cli/gonext, examples/plugins/seo. On PR the SBOMs ship as workflow artifacts (30d retention); on tag push the release job attaches them all to the GitHub Release. #204 — .github/workflows/zap-dast.yml Nightly 03:00 UTC + workflow_dispatch. Boots the compose stack via `make up`, waits for /readyz, then runs zap-baseline.py against http://api:8080 from inside the compose network. HTML + JSON reports upload as artifacts; the job does not fail on warnings (DAST has FPs — triage is owned by the security team off-line). Closes #142. Closes #204. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Signed-off-by: Tayeb Mokni <tayeb.mokni@gmail.com>
User-facing surface (apps/admin /settings/privacy):
- Download data: emerald CTA → POST queues an async export job.
- Delete account: destructive card with double-confirm password
form; on success shows the 30-day purge deadline.
API surface (apps/api /api/v1/account/data):
- GET /export — enqueues gdpr.export.run via Asynq, returns
job id + polling URL. (1/day rate limit applied
at mount time in a follow-up.)
- POST /delete — verifies the password, runs the anonymisation
transaction (zero PII on users + user_passwords,
re-own posts/comments to a sentinel id, wipe
audit_log.ip/user_agent), stamps anonymized_at
+ scheduled_purge_at = now()+30d.
Both endpoints behind RequireSession; every call emits a distinct
audit event (account.data.export.requested, .delete.{succeeded,failed}).
Worker (apps/worker/internal/tasks/gdpr):
- gdpr.export.run handler: assembles the ZIP via an injected
ExportStore and writes the artifact URL back through
MarkExportReady.
- gdpr.purge.tick handler + cron entry (@every 10m): batches up
to 100 users whose scheduled_purge_at <= now() and hard-deletes
them through HardDelete. Dry-run support for operator runbooks.
Schema (migrations/000033_gdpr_anonymization.up.sql):
- users.anonymized_at TIMESTAMPTZ (NULL = live)
- users.scheduled_purge_at TIMESTAMPTZ (NULL = live)
- Partial index on scheduled_purge_at WHERE NOT NULL so the cron
sweep is a B-tree range scan, not a sequential scan of users.
Tests:
- handler_test.go covers the happy path + 401/400/503 paths.
- tasks_test.go covers happy path, dry run, partial failure, and
cron spec wiring.
Closes #216.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: Tayeb Mokni <tayeb.mokni@gmail.com>
tayebmokni
force-pushed
the
feat/security-scanners-sbom-gdpr
branch
from
May 26, 2026 17:57
e35cf12 to
8d73c24
Compare
The keep-both rebase resolver concatenated the two halves of an upstream-merge cleanly but the HEAD-side else-block closing braces got dropped — the build failed on writeJSON / parseLogLevel etc being parsed at the wrong scope. Restore the two closing braces. Signed-off-by: Tayeb Mokni <tayeb.mokni@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This branch ships the Q4 security & compliance batch (five issues):
security-scanjob inci.ymlruns govulncheck, osv-scanner, semgrep, gosec, and gitleaks fail-fast on every PR plus a weekly Sunday cron..github/workflows/sbom.yml. Matrix-generates CycloneDX (+ SPDX) SBOMs via anchore/syft forapps/api,apps/web,cli/gonext,examples/plugins/seo. PR runs upload SBOMs as workflow artifacts (30d retention); tag pushes (v*) attach them all to the GitHub Release..github/workflows/zap-dast.yml. Cron nightly 03:00 UTC +workflow_dispatch. Boots the compose stack viamake up, waits for/readyz, then runszap-baseline.pyagainsthttp://api:8080from inside the compose network. HTML + JSON reports upload as artifacts; the job does not fail on warnings (DAST has FPs).000033_gdpr_anonymization:users.anonymized_at+users.scheduled_purge_atcolumns + partial index on the purge timestamp.GET /api/v1/account/data/export(async Asynq job → ZIP of profile/posts/comments/media/audit-rows) andPOST /api/v1/account/data/delete(double-confirm password → in-place anonymisation + 30-day purge schedule). Both behind RequireSession; every call emits a distinct audit event.apps/worker/internal/tasks/gdpr:gdpr.export.run+gdpr.purge.tick(cron@every 10m).apps/admin/src/app/(authenticated)/settings/privacy/page.tsx: emerald "Download my data" card + destructive "Delete my account" card with double-confirm password form.Test plan
security-scan, SBOM matrix (api/web/cli-gonext/plugin-seo), ZAP nightly all green.v*.*.*-rcand confirm SBOMs land on the GitHub Release.make up+gh workflow run zap-dast.yml; download artifact + spot-checkzap-report.html.000033, run new Go tests (apps/api/internal/account/data/...,apps/worker/internal/tasks/gdpr/...)./settings/privacy, fire an export (expect 202 + job id + poll URL), fire a delete with mismatched passwords (expect 400), then matching passwords (expect 200 + 30-day purge timestamp).account.data.export.requested,account.data.delete.succeeded,account.data.delete.failed.Closes #142.
Closes #190.
Closes #196.
Closes #204.
Closes #216.
Generated with Claude Code