Skip to content

Turn anti-spam back on for the hire form - #177

Merged
ralyodio merged 1 commit into
masterfrom
worktree-form-guard
Sep 7, 2026
Merged

Turn anti-spam back on for the hire form#177
ralyodio merged 1 commit into
masterfrom
worktree-form-guard

Conversation

@ralyodio

@ralyodio ralyodio commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Why

Contact-form spam POSTs straight at /api/contact without loading the page. A hidden honeypot field therefore ends up absent from the body rather than filled, and "is it empty?" answers yes, so the check passes. A honeypot only catches a bot that renders your page.

Part of a fleet-wide rollout after a submission got through a working honeypot on another property.

This route had anti-spam switched off

honeypot: false, with nothing in its place. /api/contact accepted whatever anyone sent it.

The honeypot is back on and, unlike several sibling repos in this rollout, the field is actually rendered so it can fire. On its own that still misses the common case, which is what the token is for.

What

Bumps @profullstack/stack to 0.2.0 and passes its new guard. The page mints a signed proof-of-render token and the route requires it back, so a request that never loaded the form has nothing to present. The token carries its issue time, which gives a fill-time floor for free.

Layer Catches On failure
Proof-of-render token Direct-to-endpoint bots dropped, answered as success
Fill-time floor (3s) Instant submits asked to resend
Honeypot Bots that do render dropped
Rate limit (5/hr/IP) Floods 429
Content scoring Low-effort lead bait delivered, subject tagged [spam? N]

Only the first four block. Scoring can tag a message but never drop one — every signal it reads has an innocent explanation.

The guard runs before field validation on purpose, so a bot that gets a response has not learned which fields the route wants. Delivered mail gains a provenance block: IP, user-agent, fill time, and which signals fired.

Wiring

The page was a client component, so there was nowhere on the server to mint a token, and the secret must never reach the browser. HireForm gains three props and renders the honeypot; /hire mints the token and passes them down.

/hire becomes force-dynamic — a cached page would hand every visitor the same dead token.

Config

No new env var needed. The secret falls back to RESEND_API_KEY, which sending already cannot work without. If neither that nor FORM_GUARD_SECRET is set the guard disables itself and says so in the logs rather than silently. FORM_GUARD_ENFORCE=0 drops back to scoring-only if a real sender ever reports being turned away.

🤖 Generated with Claude Code

https://claude.ai/code/session_01H5K2AxX2QZ98JW1Exe5wup

The route had anti-spam switched off outright (`honeypot: false`) with
nothing in its place, so /api/contact accepted anything anyone sent it.

The honeypot is back on and, unlike several sibling repos, the field is
actually rendered so it can fire. On its own that still misses the common
case: contact-form spam POSTs straight at /api/contact without loading
the page, which leaves a hidden field absent from the body rather than
filled, and the check passes.

So this also bumps @profullstack/stack to 0.2.0 and passes its new
`guard`. The hire page mints a signed token at render time and the route
requires it back, so a request that never loaded the form has nothing to
present. The token carries its issue time, giving a fill-time floor, and
the guard adds a per-IP rate limit.

The guard runs before field validation on purpose, so a bot that gets a
response has not learned which fields the route wants.

/hire becomes force-dynamic, since a cached page would hand every visitor
the same dead token. Nothing else changes render mode.

Content scoring only tags: a suspicious message still arrives and is
still persisted, with [spam? N] in the subject and a provenance block
naming the sender's IP, user-agent, fill time and the signals that fired.
It can never drop one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H5K2AxX2QZ98JW1Exe5wup
@socket-security

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updated@​profullstack/​stack@​0.1.3 ⏵ 0.2.0721008492 +5100

View full report

@socket-security

Copy link
Copy Markdown

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

Action Severity Alert  (click "▶" to expand/collapse)
Warn High
Publisher changed: npm @profullstack/stack is now published by moshcoder

Author: moshcoder

From: apps/web/package.jsonnpm/@profullstack/stack@0.2.0

ℹ Read more on: This package | This alert | What is unstable ownership?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Try to reduce the number of authors you depend on to reduce the risk to malicious actors gaining access to your supply chain. Packages should remove inactive collaborators with publishing rights from packages on npm.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/@profullstack/stack@0.2.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn Medium
Low adoption: npm @profullstack/stack

Location: Package overview

From: apps/web/package.jsonnpm/@profullstack/stack@0.2.0

ℹ Read more on: This package | This alert | What are unpopular packages?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Unpopular packages may have less maintenance and contain other problems.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/@profullstack/stack@0.2.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown

ThreatCrush Security Scan

12 finding(s)

HIGH/CRITICAL: 1 | MEDIUM: 6 | LOW: 5

Severity Rule Location
HIGH secret-aws-access-key prd/0003-detect-hardcoded-secrets-before-they-are-committed-or-served.md:126
MEDIUM js-open-redirect apps/web/src/app/auth/login/page.tsx:50
MEDIUM js-unescaped-html-sink apps/web/src/app/hire/page.tsx:104
MEDIUM js-unescaped-html-sink apps/web/src/app/hire/page.tsx:108
MEDIUM js-open-redirect apps/web/src/components/funding/FundingClient.tsx:97
MEDIUM js-unescaped-html-sink apps/web/src/components/GuideReader.tsx:265
MEDIUM js-uninitialized-buffer packages/scan/src/node-rules.ts:456
LOW secret-generic-credential PRD.md:269
LOW tls-verification-disabled prd/0004-find-dangerous-code-patterns-without-pretending-to-be-a-compiler.md:121
LOW tls-verification-disabled prd/0004-find-dangerous-code-patterns-without-pretending-to-be-a-compiler.md:122
LOW sh-remote-script-execution scripts/smoke-test.sh:47
LOW secret-aws-access-key scripts/smoke-test.sh:112

Snippets are redacted; ThreatCrush never prints matched credential material.

@ralyodio
ralyodio merged commit 7540c14 into master Sep 7, 2026
11 checks passed
@ralyodio
ralyodio deleted the worktree-form-guard branch September 7, 2026 00:29
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