Skip to content

Claude/domain records 1van5c - #65

Open
chelstein wants to merge 4 commits into
Leonxlnx:mainfrom
chelstein:claude/domain-records-1van5c
Open

Claude/domain records 1van5c#65
chelstein wants to merge 4 commits into
Leonxlnx:mainfrom
chelstein:claude/domain-records-1van5c

Conversation

@chelstein

Copy link
Copy Markdown

No description provided.

claude added 3 commits June 29, 2026 21:44
…dashboard

- Next.js 14 app with TypeScript, Tailwind, dark ocean theme
- Prisma schema: Customer, Domain, ScanRun, Finding, ReputationCheck, DnsRecordSnapshot
- Real DNS scanners: SPF (multi-record, lookup count), DMARC, DKIM (13 selectors), DNSSEC, CAA
- Reputation module: Spamhaus DBL (live), Google Safe Browsing/VirusTotal/Talos/URLScan/SmartScreen (placeholders)
- Scoring engine: starts at 100, deductions per spec, posture: healthy/watch/needs_improvement/poor
- Dashboard, Customers, Domains, Findings, Reputation, Settings pages
- API routes: GET/POST /api/domains, /api/domains/[id], /api/scan/domain, /api/scan/all, /api/findings, PATCH /api/findings/[id]
- Slack alert abstractions: score drop, poor posture, DMARC disappear, SPF invalid, blacklist hit
- Scanner worker: npm run scan:all (tsx scripts/scan-all.ts)
- Seed data: Spinato's Pizza (score 35, poor) and Atlas Healthcare Partners (score 65, needs_improvement)
- DigitalOcean App Platform config: .do/app.yaml with web service + hourly scheduled scanner job
- GitHub Actions CI: lint, tsc, build

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Claude-Session: https://claude.ai/code/session_01RMiVydSVDjTrqBCeG8k8PJ

@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: fd48ad2a1a

ℹ️ 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 thread reefguard/.do/app.yaml
repo: chelstein/taste-skill
branch: main
deploy_on_push: true
build_command: npm install && npx prisma generate && npm run build

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 Apply the Prisma schema during deploy

In the DigitalOcean deploy path this provisions a fresh Postgres database, but the service build only runs prisma generate and next build (and the repo contains no checked-in prisma/migrations). prisma generate does not create tables, so a first App Platform deploy starts the web service and scanner against an empty database, causing runtime queries/scans to fail until someone manually runs db push/migrations. Add a migration/db-push step before startup or check in migrations and run prisma migrate deploy.

Useful? React with 👍 / 👎.

const hardfail = primary.includes('-all');

// Count DNS-lookup mechanisms (a, mx, include, exists, redirect)
const lookupMechanisms = (primary.match(/\b(include:|a:|mx:|exists:|redirect=)/gi) ?? []).length;

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 Count all SPF lookup mechanisms

For SPF records that use bare a or mx mechanisms, such as v=spf1 a mx include:... -all, this regex only counts a: and mx: forms, so records can exceed SPF's 10-DNS-lookup limit while tooManyLookups stays false and no finding/deduction is generated. Parse/count bare a/mx (and nested include/redirect lookups) before scoring this check.

Useful? React with 👍 / 👎.

}

const get = (tag: string) => {
const m = flat.match(new RegExp(`${tag}=([^;]+)`, 'i'));

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 Anchor DMARC tag parsing to tag boundaries

When a DMARC record contains sp= but omits p=, get('p') matches the p= inside sp= and treats an invalid record as if it had a domain policy. That lets malformed DMARC avoid the missing/weak-policy deduction and be stored as a valid policy; split on semicolons or require a start/semicolon boundary for each tag name.

Useful? React with 👍 / 👎.

if (!webhookUrl) return;

const color = payload.severity === 'critical' ? '#FF0000' : payload.severity === 'warning' ? '#FFA500' : '#0099FF';

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 Make Slack alerts best-effort

When SLACK_WEBHOOK_URL is set, a network error or invalid webhook URL makes this fetch reject, and /api/scan/domain awaits alerts before refreshing findings. In that environment a transient Slack failure turns an otherwise successful scan into a 500 and leaves findings stale or partially updated; catch/log alert delivery errors or move them out of the critical scan persistence path.

Useful? React with 👍 / 👎.

@chelstein
chelstein marked this pull request as draft June 29, 2026 21:53
@chelstein
chelstein marked this pull request as ready for review June 29, 2026 21:53

@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: 28c474e22e

ℹ️ 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".

const dblRecords = await resolve4(`${domain}.dbl.spamhaus.org`).catch(() => null);
results.push({
name: 'Spamhaus DBL',
listed: !!dblRecords,

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 Don't treat Spamhaus error codes as listings

When the scan runs through a public/open resolver or hits Spamhaus query limits, resolve4 can return 127.255.255.254/255; Spamhaus documents these 127.255.255.0/24 answers as query errors, not reputation data (Spamhaus guidance). Because any non-empty response sets listed: true, clean domains in those environments get critical blacklist findings, 30-point deductions, and Slack alerts; filter valid DBL listing codes and surface 127.255.255.* as error/unknown instead.

Useful? React with 👍 / 👎.

}

export async function checkDmarc(domain: string): Promise<DmarcResult> {
const records = await getTxtRecords(`_dmarc.${domain}`);

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 Reject duplicate DMARC records

When DNS returns two _dmarc TXT records that start with v=DMARC1, this find silently chooses one and treats the domain as protected. RFC 7489 policy discovery terminates when multiple DMARC records remain (section 6.6.3), so common migration overlaps can leave receivers with no applicable DMARC policy while the scanner skips the missing/invalid-policy finding; count matching records and report duplicates as invalid instead of picking one.

Useful? React with 👍 / 👎.

}

// Upsert findings
await prisma.finding.deleteMany({ where: { domainId } });

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 resolved finding state on rescan

The app exposes PATCH /api/findings/[id] to change a finding's status, but every rescan deletes all findings for the domain and recreates current deductions with the default open status (the scheduled worker repeats the same pattern in scripts/scan-all.ts). If an operator marks a finding resolved or suppressed while the DNS fix is still pending, the next scan loses that state and reopens it; delete only scanner-owned open findings or upsert by stable key while preserving status/history.

Useful? React with 👍 / 👎.

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.

2 participants