Skip to content

feat(security/ci): SBOM + ZAP DAST + GDPR export/deletion - #470

Merged
tayebmokni merged 4 commits into
mainfrom
feat/security-scanners-sbom-gdpr
May 26, 2026
Merged

feat(security/ci): SBOM + ZAP DAST + GDPR export/deletion#470
tayebmokni merged 4 commits into
mainfrom
feat/security-scanners-sbom-gdpr

Conversation

@tayebmokni

Copy link
Copy Markdown
Contributor

Summary

This branch ships the Q4 security & compliance batch (five issues):

  • govulncheck + osv-scanner + semgrep in CI #190 + Add gosec and gitleaks to CI #196 — already-landed first commit: aggregate security-scan job in ci.yml runs govulncheck, osv-scanner, semgrep, gosec, and gitleaks fail-fast on every PR plus a weekly Sunday cron.
  • SBOM generation (CycloneDX via syft) for every release #142 SBOM — new .github/workflows/sbom.yml. Matrix-generates CycloneDX (+ SPDX) SBOMs via anchore/syft for apps/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.
  • ZAP DAST in CI (nightly against staging) #204 ZAP DAST — new .github/workflows/zap-dast.yml. Cron 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).
  • GDPR export + deletion #216 GDPR export + deletion — full self-service flow:
    • Migration 000033_gdpr_anonymization: users.anonymized_at + users.scheduled_purge_at columns + partial index on the purge timestamp.
    • REST: GET /api/v1/account/data/export (async Asynq job → ZIP of profile/posts/comments/media/audit-rows) and POST /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.
    • Worker tasks apps/worker/internal/tasks/gdpr: gdpr.export.run + gdpr.purge.tick (cron @every 10m).
    • Admin page 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

  • CI: security-scan, SBOM matrix (api/web/cli-gonext/plugin-seo), ZAP nightly all green.
  • Tag a v*.*.*-rc and confirm SBOMs land on the GitHub Release.
  • Run make up + gh workflow run zap-dast.yml; download artifact + spot-check zap-report.html.
  • Locally: apply migration 000033, run new Go tests (apps/api/internal/account/data/..., apps/worker/internal/tasks/gdpr/...).
  • In the admin shell, hit /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).
  • Inspect the audit log for 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

@tayebmokni
tayebmokni enabled auto-merge (squash) May 25, 2026 21:32
@github-actions

Copy link
Copy Markdown

Heads up — this PR touches strings that often signal a security disclosure (vulnerab, CVE-, exploit, bypass, or auth bypass).

If this PR fixes or describes a real vulnerability that has not yet been publicly disclosed, please stop and use the private path:

  1. Open a private security advisory, or
  2. Email security@gonext.io with subject [SECURITY] GoNext - <summary>.

See /SECURITY.md for the full disclosure flow and /docs/16-bug-bounty.md for bounty terms.

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:

  • .github/workflows/ci.yml

@tayebmokni
tayebmokni force-pushed the feat/security-scanners-sbom-gdpr branch from c5a8704 to e35cf12 Compare May 26, 2026 17:32
tib0o0o and others added 3 commits May 26, 2026 19:56
…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
tayebmokni force-pushed the feat/security-scanners-sbom-gdpr branch from e35cf12 to 8d73c24 Compare May 26, 2026 17:57
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>
@tayebmokni
tayebmokni merged commit af96919 into main May 26, 2026
23 of 31 checks passed
@tayebmokni
tayebmokni deleted the feat/security-scanners-sbom-gdpr branch May 26, 2026 21:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants