Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions backend/api_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -903,6 +903,32 @@ def clear_login_failures(email):
'untraceable', 'anonymous task',
]

PAYMENT_CIRCUMVENTION_PATTERNS = [
r"\bpaypal\.me/\S+",
r"\bpaypal\b",
r"\bvenmo\b",
r"\bcash\s*app\b",
r"\bcashapp\b",
r"\bzelle\b",
r"\bcrypto\b",
r"\bbitcoin\b",
r"\bethereum\b",
r"\bsolana\b",
r"\bevm\b",
r"\bwallet\b",
r"\bdirect\s+payment\b",
r"\boff[-\s]?platform\s+payment\b",
r"\bpay\s+me\s+direct\b",
r"\bsend\s+payment\s+to\b",
]


def check_payment_circumvention(text):
for pattern in PAYMENT_CIRCUMVENTION_PATTERNS:
if re.search(pattern, text or "", re.IGNORECASE):
return False, "Payment instructions must stay on-platform. Do not include direct payment links, wallet addresses, or off-platform payment instructions."
return True, None

VALID_CATEGORIES = [
'web_development', 'mobile_development', 'software_development',
'graphic_design', 'ui_ux_design', 'video_editing', 'photography',
Expand Down Expand Up @@ -2026,6 +2052,15 @@ def _handle_routes(db):
return error_response(f"Invalid category. Must be one of: {', '.join(VALID_CATEGORIES)}")

safe, msg = check_content_safety(body['title'] + " " + body['description'])
if not safe:
return error_response(f"Service rejected: {msg}", 422)
service_text = " ".join([
str(body.get('title') or ''),
str(body.get('description') or ''),
str(body.get('includes') or ''),
" ".join(body.get('tags') or []) if isinstance(body.get('tags'), list) else str(body.get('tags') or ''),
])
safe, msg = check_payment_circumvention(service_text)
if not safe:
return error_response(f"Service rejected: {msg}", 422)

Expand Down Expand Up @@ -2099,6 +2134,15 @@ def _handle_routes(db):
safe, msg = check_content_safety(txt)
if not safe:
return error_response(f"Service update rejected: {msg}", 422)
merged_service_text = " ".join([
str(body.get('title') if 'title' in body else svc['title'] or ''),
str(body.get('description') if 'description' in body else svc['description'] or ''),
str(body.get('includes') if 'includes' in body else svc['includes'] or ''),
" ".join(body.get('tags') or []) if isinstance(body.get('tags'), list) else str(body.get('tags') if 'tags' in body else svc['tags'] or ''),
])
safe, msg = check_payment_circumvention(merged_service_text)
if not safe:
return error_response(f"Service update rejected: {msg}", 422)

updates = []
vals = []
Expand Down
71 changes: 67 additions & 4 deletions backend/test_deep_audit_regressions.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,68 @@ def test_api_key_header_authenticates_protected_profile_route(self):
finally:
db.close()

def test_service_creation_rejects_off_platform_payment_instructions(self):
db = self.module.get_db()
token = "tok-worker"
try:
db.execute("INSERT INTO users (id,email,password_hash,name) VALUES (1,'worker@example.com','x','Worker')")
db.execute("INSERT INTO sessions (user_id,token,expires_at) VALUES (1,?,datetime('now','+1 day'))", [token])
db.commit()
finally:
db.close()

payload = {
"title": "Website QA pass",
"description": "I can test your website. Direct payment via PayPal is available.",
"category": "testing",
"pricing_type": "fixed",
"price": 25,
"includes": "Send payment to a crypto wallet before work starts",
}
body = json.dumps(payload)
self.module._request_ctx.request_method = "POST"
self.module._request_ctx.path_info = "/api/v1/services"
self.module._request_ctx.query_string = ""
self.module._request_ctx.http_authorization = f"Bearer {token}"
self.module._request_ctx.http_x_api_key = ""
self.module._request_ctx.stdin_data = body
self.module._request_ctx.content_type = "application/json"
self.module._request_ctx.content_length = str(len(body))
self.module._request_ctx.remote_addr = "127.0.0.1"
with contextlib.redirect_stdout(io.StringIO()) as out:
self.module.handle_request()
status, response = parse_cgi_output(out.getvalue())
self.assertEqual(status, 422, response)
self.assertIn("Payment instructions must stay on-platform", response.get("error", ""))

def test_service_update_rejects_off_platform_payment_instructions_in_tags(self):
db = self.module.get_db()
token = "tok-worker"
try:
db.execute("INSERT INTO users (id,email,password_hash,name) VALUES (1,'worker@example.com','x','Worker')")
db.execute("INSERT INTO sessions (user_id,token,expires_at) VALUES (1,?,datetime('now','+1 day'))", [token])
db.execute("INSERT INTO services (id,worker_id,title,description,category,pricing_type,price,status) VALUES (1,1,'Testing Svc','Clean QA scope','testing','fixed',25,'active')")
db.commit()
finally:
db.close()

payload = {"tags": ["qa", "solana wallet accepted"]}
body = json.dumps(payload)
self.module._request_ctx.request_method = "PUT"
self.module._request_ctx.path_info = "/api/v1/services/1"
self.module._request_ctx.query_string = ""
self.module._request_ctx.http_authorization = f"Bearer {token}"
self.module._request_ctx.http_x_api_key = ""
self.module._request_ctx.stdin_data = body
self.module._request_ctx.content_type = "application/json"
self.module._request_ctx.content_length = str(len(body))
self.module._request_ctx.remote_addr = "127.0.0.1"
with contextlib.redirect_stdout(io.StringIO()) as out:
self.module.handle_request()
status, response = parse_cgi_output(out.getvalue())
self.assertEqual(status, 422, response)
self.assertIn("Payment instructions must stay on-platform", response.get("error", ""))

def test_job_creation_notifies_matching_service_workers(self):
db = self.module.get_db()
token = "tok-employer"
Expand Down Expand Up @@ -1347,13 +1409,13 @@ def test_public_nav_active_state_uses_light_pill_for_all_tabs(self):
def test_sitemapped_html_pages_use_single_canonical_public_nav(self):
expected_labels = [
"GoHireHumans",
"Starter QA Offers",
"Marketplace",
"Open Jobs",
"Open Jobs for Workers",
"For Agents",
"Agent Guide",
"Use Cases",
"About",
"FAQ",
]
failures = []
for rel in self._sitemapped_html_pages():
Expand Down Expand Up @@ -1448,10 +1510,11 @@ def _assert_shared_landing_nav(self, pages):
'<link rel="stylesheet" href="/style.css?v=20260526-nav-consistency">',
'<div class="lp-nav-wrap">',
'<nav class="lp-nav" aria-label="Main navigation">',
'<a class="lp-nav-link" href="/starter-offers.html">Starter QA Offers</a>',
'<a class="lp-nav-link" href="/#/services">Marketplace</a>',
'<a class="lp-nav-link" href="/#/jobs">Open Jobs</a>',
'<a class="lp-nav-link" href="/#/jobs">Open Jobs for Workers</a>',
'<a class="lp-nav-link" href="/#/ai-employers">For Agents</a>',
'<a class="btn btn-primary btn-sm" href="/#/register">Get started</a>',
'<a class="btn btn-primary btn-sm" href="/starter-offers.html">Request QA</a>',
'function toggleMobileMenu()',
]
failures = []
Expand Down
6 changes: 3 additions & 3 deletions frontend/404.html
Original file line number Diff line number Diff line change
Expand Up @@ -155,11 +155,11 @@
<div class="lp-nav-wrap">
<nav class="lp-nav" aria-label="Main navigation">
<a class="lp-nav-logo" href="/"><svg width="28" height="28" viewBox="0 0 32 32" fill="none" aria-label="GoHireHumans"><rect width="32" height="32" rx="7" fill="#0d7377"/><circle cx="11" cy="10" r="3.5" fill="white"/><path d="M4.5 22c0-3.5 2.9-6.5 6.5-6.5s6.5 2.9 6.5 6.5" stroke="white" stroke-width="2.2" fill="none" stroke-linecap="round"/><circle cx="22" cy="12" r="2.5" fill="white" opacity="0.75"/><path d="M17.5 22c0-2.8 2-5 4.5-5.5" stroke="white" stroke-width="1.8" fill="none" stroke-linecap="round" opacity="0.75"/></svg>GoHireHumans</a>
<div class="lp-nav-links"><a class="lp-nav-link" href="/#/services">Marketplace</a><a class="lp-nav-link" href="/#/jobs">Open Jobs</a><a class="lp-nav-link" href="/#/ai-employers">For Agents</a><a class="lp-nav-link" href="/ai-integration.html">Agent Guide</a><a class="lp-nav-link" href="/use-cases/">Use Cases</a><a class="lp-nav-link" href="/about.html">About</a><a class="lp-nav-link" href="/faq.html">FAQ</a></div>
<div class="lp-nav-actions"><a class="btn btn-ghost btn-sm" href="/#/login">Sign in</a><a class="btn btn-primary btn-sm" href="/#/register">Get started</a></div>
<div class="lp-nav-links"><a class="lp-nav-link" href="/starter-offers.html">Starter QA Offers</a><a class="lp-nav-link" href="/#/services">Marketplace</a><a class="lp-nav-link" href="/#/jobs">Open Jobs for Workers</a><a class="lp-nav-link" href="/#/ai-employers">For Agents</a><a class="lp-nav-link" href="/ai-integration.html">Agent Guide</a><a class="lp-nav-link" href="/use-cases/">Use Cases</a><a class="lp-nav-link" href="/about.html">About</a><a class="lp-nav-link" href="/faq.html">FAQ</a></div>
<div class="lp-nav-actions"><a class="btn btn-ghost btn-sm" href="/#/login">Sign in</a><a class="btn btn-primary btn-sm" href="/starter-offers.html">Request QA</a></div>
<button class="lp-hamburger" onclick="toggleMobileMenu()" aria-label="Menu"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="18" x2="21" y2="18"/></svg></button>
</nav>
<div class="lp-mobile-menu" id="mobileMenu" style="display:none"><a class="lp-mobile-link" href="/#/services">Marketplace</a><a class="lp-mobile-link" href="/#/jobs">Open Jobs</a><a class="lp-mobile-link" href="/#/ai-employers">For Agents</a><a class="lp-mobile-link" href="/ai-integration.html">Agent Guide</a><a class="lp-mobile-link" href="/use-cases/">Use Cases</a><a class="lp-mobile-link" href="/about.html">About</a><a class="lp-mobile-link" href="/faq.html">FAQ</a><div style="padding:var(--space-3) var(--space-4);display:flex;gap:var(--space-2)"><a class="btn btn-ghost btn-sm" href="/#/login">Sign in</a><a class="btn btn-primary btn-sm" href="/#/register">Get started</a></div></div>
<div class="lp-mobile-menu" id="mobileMenu" style="display:none"><a class="lp-mobile-link" href="/starter-offers.html">Starter QA Offers</a><a class="lp-mobile-link" href="/#/services">Marketplace</a><a class="lp-mobile-link" href="/#/jobs">Open Jobs for Workers</a><a class="lp-mobile-link" href="/#/ai-employers">For Agents</a><a class="lp-mobile-link" href="/ai-integration.html">Agent Guide</a><a class="lp-mobile-link" href="/use-cases/">Use Cases</a><a class="lp-mobile-link" href="/about.html">About</a><a class="lp-mobile-link" href="/faq.html">FAQ</a><div style="padding:var(--space-3) var(--space-4);display:flex;gap:var(--space-2)"><a class="btn btn-ghost btn-sm" href="/#/login">Sign in</a><a class="btn btn-primary btn-sm" href="/starter-offers.html">Request QA</a></div></div>
</div>

<script>
Expand Down
6 changes: 3 additions & 3 deletions frontend/about.html
Original file line number Diff line number Diff line change
Expand Up @@ -407,11 +407,11 @@
<div class="lp-nav-wrap">
<nav class="lp-nav" aria-label="Main navigation">
<a class="lp-nav-logo" href="/"><svg width="28" height="28" viewBox="0 0 32 32" fill="none" aria-label="GoHireHumans"><rect width="32" height="32" rx="7" fill="#0d7377"/><circle cx="11" cy="10" r="3.5" fill="white"/><path d="M4.5 22c0-3.5 2.9-6.5 6.5-6.5s6.5 2.9 6.5 6.5" stroke="white" stroke-width="2.2" fill="none" stroke-linecap="round"/><circle cx="22" cy="12" r="2.5" fill="white" opacity="0.75"/><path d="M17.5 22c0-2.8 2-5 4.5-5.5" stroke="white" stroke-width="1.8" fill="none" stroke-linecap="round" opacity="0.75"/></svg>GoHireHumans</a>
<div class="lp-nav-links"><a class="lp-nav-link" href="/#/services">Marketplace</a><a class="lp-nav-link" href="/#/jobs">Open Jobs</a><a class="lp-nav-link" href="/#/ai-employers">For Agents</a><a class="lp-nav-link" href="/ai-integration.html">Agent Guide</a><a class="lp-nav-link" href="/use-cases/">Use Cases</a><a class="lp-nav-link lp-nav-link-active" href="/about.html">About</a><a class="lp-nav-link" href="/faq.html">FAQ</a></div>
<div class="lp-nav-actions"><a class="btn btn-ghost btn-sm" href="/#/login">Sign in</a><a class="btn btn-primary btn-sm" href="/#/register">Get started</a></div>
<div class="lp-nav-links"><a class="lp-nav-link" href="/starter-offers.html">Starter QA Offers</a><a class="lp-nav-link" href="/#/services">Marketplace</a><a class="lp-nav-link" href="/#/jobs">Open Jobs for Workers</a><a class="lp-nav-link" href="/#/ai-employers">For Agents</a><a class="lp-nav-link" href="/ai-integration.html">Agent Guide</a><a class="lp-nav-link" href="/use-cases/">Use Cases</a><a class="lp-nav-link lp-nav-link-active" href="/about.html">About</a><a class="lp-nav-link" href="/faq.html">FAQ</a></div>
<div class="lp-nav-actions"><a class="btn btn-ghost btn-sm" href="/#/login">Sign in</a><a class="btn btn-primary btn-sm" href="/starter-offers.html">Request QA</a></div>
<button class="lp-hamburger" onclick="toggleMobileMenu()" aria-label="Menu"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="18" x2="21" y2="18"/></svg></button>
</nav>
<div class="lp-mobile-menu" id="mobileMenu" style="display:none"><a class="lp-mobile-link" href="/#/services">Marketplace</a><a class="lp-mobile-link" href="/#/jobs">Open Jobs</a><a class="lp-mobile-link" href="/#/ai-employers">For Agents</a><a class="lp-mobile-link" href="/ai-integration.html">Agent Guide</a><a class="lp-mobile-link" href="/use-cases/">Use Cases</a><a class="lp-mobile-link lp-nav-link-active" href="/about.html">About</a><a class="lp-mobile-link" href="/faq.html">FAQ</a><div style="padding:var(--space-3) var(--space-4);display:flex;gap:var(--space-2)"><a class="btn btn-ghost btn-sm" href="/#/login">Sign in</a><a class="btn btn-primary btn-sm" href="/#/register">Get started</a></div></div>
<div class="lp-mobile-menu" id="mobileMenu" style="display:none"><a class="lp-mobile-link" href="/starter-offers.html">Starter QA Offers</a><a class="lp-mobile-link" href="/#/services">Marketplace</a><a class="lp-mobile-link" href="/#/jobs">Open Jobs for Workers</a><a class="lp-mobile-link" href="/#/ai-employers">For Agents</a><a class="lp-mobile-link" href="/ai-integration.html">Agent Guide</a><a class="lp-mobile-link" href="/use-cases/">Use Cases</a><a class="lp-mobile-link lp-nav-link-active" href="/about.html">About</a><a class="lp-mobile-link" href="/faq.html">FAQ</a><div style="padding:var(--space-3) var(--space-4);display:flex;gap:var(--space-2)"><a class="btn btn-ghost btn-sm" href="/#/login">Sign in</a><a class="btn btn-primary btn-sm" href="/starter-offers.html">Request QA</a></div></div>
</div>

<script>
Expand Down
6 changes: 3 additions & 3 deletions frontend/agent-onboarding.html
Original file line number Diff line number Diff line change
Expand Up @@ -800,11 +800,11 @@
<div class="lp-nav-wrap">
<nav class="lp-nav" aria-label="Main navigation">
<a class="lp-nav-logo" href="/"><svg width="28" height="28" viewBox="0 0 32 32" fill="none" aria-label="GoHireHumans"><rect width="32" height="32" rx="7" fill="#0d7377"/><circle cx="11" cy="10" r="3.5" fill="white"/><path d="M4.5 22c0-3.5 2.9-6.5 6.5-6.5s6.5 2.9 6.5 6.5" stroke="white" stroke-width="2.2" fill="none" stroke-linecap="round"/><circle cx="22" cy="12" r="2.5" fill="white" opacity="0.75"/><path d="M17.5 22c0-2.8 2-5 4.5-5.5" stroke="white" stroke-width="1.8" fill="none" stroke-linecap="round" opacity="0.75"/></svg>GoHireHumans</a>
<div class="lp-nav-links"><a class="lp-nav-link" href="/#/services">Marketplace</a><a class="lp-nav-link" href="/#/jobs">Open Jobs</a><a class="lp-nav-link" href="/#/ai-employers">For Agents</a><a class="lp-nav-link" href="/ai-integration.html">Agent Guide</a><a class="lp-nav-link" href="/use-cases/">Use Cases</a><a class="lp-nav-link" href="/about.html">About</a><a class="lp-nav-link" href="/faq.html">FAQ</a></div>
<div class="lp-nav-actions"><a class="btn btn-ghost btn-sm" href="/#/login">Sign in</a><a class="btn btn-primary btn-sm" href="/#/register">Get started</a></div>
<div class="lp-nav-links"><a class="lp-nav-link" href="/starter-offers.html">Starter QA Offers</a><a class="lp-nav-link" href="/#/services">Marketplace</a><a class="lp-nav-link" href="/#/jobs">Open Jobs for Workers</a><a class="lp-nav-link" href="/#/ai-employers">For Agents</a><a class="lp-nav-link" href="/ai-integration.html">Agent Guide</a><a class="lp-nav-link" href="/use-cases/">Use Cases</a><a class="lp-nav-link" href="/about.html">About</a><a class="lp-nav-link" href="/faq.html">FAQ</a></div>
<div class="lp-nav-actions"><a class="btn btn-ghost btn-sm" href="/#/login">Sign in</a><a class="btn btn-primary btn-sm" href="/starter-offers.html">Request QA</a></div>
<button class="lp-hamburger" onclick="toggleMobileMenu()" aria-label="Menu"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="18" x2="21" y2="18"/></svg></button>
</nav>
<div class="lp-mobile-menu" id="mobileMenu" style="display:none"><a class="lp-mobile-link" href="/#/services">Marketplace</a><a class="lp-mobile-link" href="/#/jobs">Open Jobs</a><a class="lp-mobile-link" href="/#/ai-employers">For Agents</a><a class="lp-mobile-link" href="/ai-integration.html">Agent Guide</a><a class="lp-mobile-link" href="/use-cases/">Use Cases</a><a class="lp-mobile-link" href="/about.html">About</a><a class="lp-mobile-link" href="/faq.html">FAQ</a><div style="padding:var(--space-3) var(--space-4);display:flex;gap:var(--space-2)"><a class="btn btn-ghost btn-sm" href="/#/login">Sign in</a><a class="btn btn-primary btn-sm" href="/#/register">Get started</a></div></div>
<div class="lp-mobile-menu" id="mobileMenu" style="display:none"><a class="lp-mobile-link" href="/starter-offers.html">Starter QA Offers</a><a class="lp-mobile-link" href="/#/services">Marketplace</a><a class="lp-mobile-link" href="/#/jobs">Open Jobs for Workers</a><a class="lp-mobile-link" href="/#/ai-employers">For Agents</a><a class="lp-mobile-link" href="/ai-integration.html">Agent Guide</a><a class="lp-mobile-link" href="/use-cases/">Use Cases</a><a class="lp-mobile-link" href="/about.html">About</a><a class="lp-mobile-link" href="/faq.html">FAQ</a><div style="padding:var(--space-3) var(--space-4);display:flex;gap:var(--space-2)"><a class="btn btn-ghost btn-sm" href="/#/login">Sign in</a><a class="btn btn-primary btn-sm" href="/starter-offers.html">Request QA</a></div></div>
</div>

<script>
Expand Down
Loading
Loading