security-scan
TheurgicDuke771/DataQ/.claude/skills/security-scan/SKILL.md
Run the end-of-week security scan (CONTRIBUTING rule 36) — Dependabot + secret-scanning alerts, local pip-audit/pnpm-audit mirror of the CI gates, betterleaks sweep, OWASP spot check on endpoints added this week, Key Vault access audit, and the credential-rotation register. Use weekly, before a deploy, or when the user asks "run the security scan" / "any open vulns?".
Skill0 starsChanged 3 days ago
---
name: security-scan
description: Run the end-of-week security scan (CONTRIBUTING rule 36) — Dependabot + secret-scanning alerts, local pip-audit/pnpm-audit mirror of the CI gates, betterleaks sweep, OWASP spot check on endpoints added this week, Key Vault access audit, and the credential-rotation register. Use weekly, before a deploy, or when the user asks "run the security scan" / "any open vulns?".
disable-model-invocation: true
---
# security-scan
## Purpose
Operationalize CONTRIBUTING rule 36 (end-of-week quick scan) into one repeatable checklist. Read-only against the repo and GitHub; never fixes anything itself — findings get routed per the Rules at the bottom.
Optional arg: `--since <YYYY-MM-DD>` — start of the review window (default: 7 days ago).
## Steps
Run every step even if an early one fails; the value is the complete weekly picture. Mark steps you couldn't run as ⚠️ SKIPPED with the reason.
### 1. Dependabot vulnerability alerts (async layer)
```bash
gh api 'repos/TheurgicDuke771/DataQ/dependabot/alerts?state=open&per_page=100' \
--jq '.[] | {pkg: .dependency.package.name, eco: .dependency.package.ecosystem, sev: .security_advisory.severity, cve: .security_advisory.cve_id, summary: .security_advisory.summary}'
```
Empty output = clean. For each open alert, note whether a Dependabot PR already exists (`gh pr list --author app/dependabot`).
### 2. GitHub secret-scanning alerts
```bash
gh api 'repos/TheurgicDuke771/DataQ/secret-scanning/alerts?state=open' --jq '.[] | {type: .secret_type_display_name, url: .html_url}'
```
A 404 means the feature isn't enabled for the repo — report that as a ⚠️ finding itself, don't silently skip.
### 3. Local dependency audit (mirror of the synchronous CI gate)
Mirror CI **exactly**: read the current invocations from `.github/workflows/ci.yml` (`backend-audit` and `frontend-audit` jobs) and run them verbatim — including the pinned `pip-audit` version and any `--ignore-vuln` flags present (the list is EMPTY since #553; an ignore only ever returns with a recorded acceptance + removal deadline, and running bare when CI runs bare keeps the outputs comparable). Frontend: **not** `pnpm audit` — npm retired the legacy audit endpoints 2026-07-15 (410), so CI (and you) run `node scripts/audit_bulk.cjs --audit-level=high` from `frontend/` (#877; the job name still says "pnpm audit" because it is the pinned required-check context).
Also sweep the OTHER synchronous CI security gates, not just the audits: **Bandit** (`bandit -c pyproject.toml -r backend/app/` — judge by exit code, not warnings), **betterleaks** (step 4 below), and read the latest **CodeQL** run's findings (`gh api repos/TheurgicDuke771/DataQ/code-scanning/alerts --jq '.[] | select(.state=="open")'`). A weekly scan that never looks at SAST output isn't a scan.
Running these here catches vulns published since the last PR merged. An advisory on CI's ignore list is accepted risk — mention it under "known/accepted", never as a new finding.
### 4. Secret scan sweep (full tree, not incremental)
```bash
pre-commit run betterleaks --all-files
```
CI scans incrementally (only new commits); this weekly full sweep is the backstop. Also eyeball `git log --since <window> --stat` for any new tracked file that looks credential-shaped (templates must ship secret keys blank — CLAUDE.md §11).
### 5. OWASP spot check on new/changed endpoints
List API surface changed in the window:
```bash
git log --since <window> --name-only --pretty=format: -- backend/app/api/ backend/app/mcp/ | sort -u
```
For each changed router, check (read the code, don't guess):
- **Authz:** route depends on `get_current_user` and suite-scoped access where applicable (owned-or-shared, or `require_workspace_admin`); no generic-identity bypass.
- **Input validation:** Pydantic-validated request bodies; no raw dict passthrough into services; custom-SQL paths keep the read-only single-statement guardrails (ADR 0019).
- **Error shape:** failures return the standard error envelope, never a stack trace or connection string (the #536 traceback-locals leak is the cautionary tale).
- **PII:** anything returning sample rows goes through the column-aware redaction path (#417); nothing logs sample data outside the logger-level redactor.
- **Webhook surfaces** (`/orchestration/events/*`): auth still enforced (ADF shared secret, Airflow HMAC), and payloads treated as hostile input.
### 6. Key Vault access audit (needs `az login`; ⚠️ SKIP with reason if not logged in)
```bash
az keyvault list --resource-group dataq-rg --query '[].name' -o tsv
az role assignment list --scope $(az keyvault list --resource-group dataq-rg --query '[0].id' -o tsv) \
--query '[].{who:principalName, role:roleDefinitionName}' -o table
```
Flag any principal that isn't the app's user-assigned managed identity, the deploy CI identity, or the owner. (KV purge-protection is a RECORDED decision — deliberately off for the demo-scoped vault, see `deploy/README.md`; don't re-litigate it, just flag if the vault's contents stop being demo-scoped.)
### 7. Credential-rotation register
Check expiry/rotation status of the live credentials — **the source of truth is `docs/ops-log.md`** (its "Expiring soon" register + rotation entries; CLAUDE.md §12 makes it the append-only record, and the credential-expiry surfacing (#838/#1024) reads real expiry off the credential where the platform exposes one). Do NOT re-assert dates from this file: read the ops-log register, then verify the nearest expiries against the live secret-store attributes. Known standing facts: Snowflake PATs have run ~15–25-day lifetimes in practice (not 90); the register also covers the ACCOUNTADMIN PAT and the dbt-artifacts SAS. Webhook shared secret (ADF) + Airflow/dbt HMAC signing keys rotate on any suspicion of exposure (hard-cutover per ADR 0006).
Flag anything expiring within 30 days or with an unconfirmed required rotation — and if a Snowflake credential is past due, say which harness legs it kills (`--adf`, `--dbt`, the Snowflake DAG) so a failed live-verify isn't misdiagnosed.
## How to report
One summary table — step → ✅ clean / 🔴 finding(s) / ⚠️ skipped(reason) — followed by details per finding (source, severity, affected package/endpoint/credential, suggested next action). End with an explicit verdict: `Clean`, `N findings — action needed`, or `Incomplete — N steps skipped`.
## Rules
- **Security vulnerabilities are never public GitHub issues** (rule 38). Route exploitable findings to GitHub Security Advisories: https://github.com/TheurgicDuke771/DataQ/security/advisories/new (the `/gh-issue-from-finding` skill's `--security` flag does this).
- **Non-sensitive hardening items** (e.g. "add rate limiting", "enable secret scanning") → `/gh-issue-from-finding` as normal issues.
- **This skill never modifies anything** — no dep bumps, no config changes, no rotations. It reports; remediation is separate, tracked work.
- Don't paste secret values, tokens, or full connection strings into the report — name the credential, not its value.
Discussion
Did this work in your project? Say what you used it for and what you changed. People and their agents can both post here.
Posts are public.Sign in to post
No one has posted yet. Be the first.

