Skip to content

feat(procore): Track A batch 2 — audit retention, JSONL gate, SBOM/vuln scan (T5,T6,T9) - #25

Merged
mcxl merged 5 commits into
mainfrom
procore/track-a-batch2
Jun 17, 2026
Merged

feat(procore): Track A batch 2 — audit retention, JSONL gate, SBOM/vuln scan (T5,T6,T9)#25
mcxl merged 5 commits into
mainfrom
procore/track-a-batch2

Conversation

@mcxl

@mcxl mcxl commented Jun 17, 2026

Copy link
Copy Markdown
Owner

Summary

Track A batch 2 — retention/audit + supply-chain hardening (P1 certification items). Follows PR #24 (T1–T4,T10).

Tickets

  • T6 feat(procore): append-only audit table with retention + deletion — migration 008: private.procore_audit (append-only, RLS), metadata-only (hashes/versions/counts, never raw text), SECURITY DEFINER RPCs (service-role only) for record / retention-purge / customer-deletion. Contract test guards the invariants (migration not applied in CI).
  • T5 feat(procore): gate local JSONL persistence off by defaultlog_payload/log_review/store_artifact gated behind PROCORE_LOCAL_JSONL_ENABLED (default false), making the "minimal audit metadata retained" statement true. Durable record is the Supabase audit trail.
  • T9 ci(security): add SBOM + pip-audit vulnerability gate — CycloneDX SBOM artifact + strict pip-audit on every push/PR.
  • Plus a follow-up enabling the JSONL flag for the review-store roundtrip tests (caught by the pre-push full suite).

Heads-up on CI

This PR introduces the pip-audit vulnerability gate. It could not be run locally (pip install restricted), so this CI run is the first authoritative scan. If the pinned deps carry known advisories, the Vulnerability scan step will go red — that's the gate working. We then triage: bump the dep if a fix exists, or add a documented --ignore-vuln <id> with a reason (no blanket-ignore).

Deployment prerequisites (not done here)

  • Apply migration 008_procore_audit.sql to the Supabase project; the audit RPCs require service_role.

Tests

pytest tests/test_procore_webhook.py → 71 passed; audit-migration contract test → 6 passed; full suite green (pre-push gate). flake8 clean.

🤖 Generated with Claude Code

mcxl and others added 4 commits June 17, 2026 21:04
Add migration 008: private.procore_audit, an append-only (UPDATE blocked,
DELETE owner-only) audit trail storing minimal metadata only — hashes,
rule-pack versions, status, finding counts, write-back metadata, and an
append-only human-override trail. Never stores raw SWMS text.

SECURITY DEFINER RPCs in public (service_role EXECUTE only):
record_procore_audit, purge_procore_audit (retention), and
delete_procore_audit_for_company (customer deletion request). Reuses the
004 immutability trigger pattern; table lives in the private schema with RLS.

Migration not applied (verify project first). A contract test guards the
invariants since the migration can't run in CI.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Local JSONL writes under src/data made the 'minimal audit metadata
retained' data-handling statement false and aren't durable on an ephemeral
container. Gate log_payload, log_review (webhook_handler) and store_artifact
(review_store) behind PROCORE_LOCAL_JSONL_ENABLED (default false). When
disabled they no-op; the durable record is the Supabase audit trail (T6).
Local JSONL stays available for dev/tests via the flag.

Tests: TestLocalJsonlGate covers disabled -> no write, enabled -> write, for
payload/review/artifact; test_log_creates_file sets the flag.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CI ran flake8 + pytest only, with no SBOM or vulnerability scanning. Add:
- Generate SBOM (CycloneDX) via pip-audit, uploaded as an artifact (always).
- Vulnerability scan: pip-audit -r requirements.txt, strict — fails CI on
  known-vuln pinned deps. Unavoidable no-fix advisories must be documented
  with --ignore-vuln and a reason, never blanket-ignored.
- pip-audit[cyclonedx] added to requirements-dev.txt.

Note: could not run pip-audit locally (pip install restricted), so CI is the
first authoritative scan.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Follow-up to T5: TestReviewStore exercises the local store/find roundtrip,
which is gated off by default now. Enable PROCORE_LOCAL_JSONL_ENABLED for
that class so the roundtrip assertions still run. Caught by the pre-push
full-suite gate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 37f68747c5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +81 to +84
create or replace function public.record_procore_audit(p_record jsonb)
returns bigint
language plpgsql
security definer

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Move audit definer RPCs out of public schema

The certification plan's Supabase RPC hardening section says T6 must not create security definer functions in exposed/public schemas (docs/procore/STAGE3_CERTIFICATION_PLAN_V2.md:281-282). This creates public.record_procore_audit as security definer (and repeats the pattern for purge/delete below), so the audit migration fails the hardening requirement it is meant to satisfy even though EXECUTE is restricted to service_role; put the definer implementation in a private/internal schema instead.

Useful? React with 👍 / 👎.

Comment on lines +31 to +33
if not _local_jsonl_enabled():
log.debug("Local JSONL disabled; skipping artifact store write")
return

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve review artifacts when JSONL is disabled

With the default environment this early return prevents store_artifact() from persisting the full review artifact, but _process_procore_v1_webhook still relies on this same store for resubmission comparison via find_previous_artifact() (api/main.py:1412-1429). Since the new Supabase audit migration stores only metadata and there is no Supabase reader/writer for comparison artifacts, normal deployments without PROCORE_LOCAL_JSONL_ENABLED=true will never retain reviews for later resubmission comparisons.

Useful? React with 👍 / 👎.

The new pip-audit gate flagged 19 known vulnerabilities (all with fixes):
- cryptography 46.0.7 -> 48.0.1 (GHSA-537c-gmf6-5ccf)
- pypdf 6.9.2 -> 6.13.0 (12 CVEs)
- python-dotenv 1.1.0 -> 1.2.2 (CVE-2026-28684)
- python-multipart 0.0.22 -> 0.0.31 (4 CVEs)

All have fix versions, so bump rather than ignore. Bumps validated by CI
(could not pip-install locally).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mcxl
mcxl merged commit 1447c27 into main Jun 17, 2026
1 check passed
@mcxl
mcxl deleted the procore/track-a-batch2 branch June 17, 2026 11:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant