T09 · Insecure Skill Coding Practices
- Location
infra/tools/inbound-form.ts:118- Finding
Unauthenticated Form Submissions Can Overwrite Shared Contact Records
- Content
View full analysis
Vulnerability Details
File Location:
infra/tools/inbound-form.ts, lines 118–146
Vulnerability Type: Unauthenticated record overwrite
Risk Level: MediumVulnerable Code
ts model.upsert({ modelUuid: gtmContacts.uuid, matchingColumnSlug: "email", matchingValue: input.email, mappings: [ { columnSlug: "email", value: input.email }, { columnSlug: "first_name", value: input.first_name }, { columnSlug: "last_name", value: input.last_name }, { columnSlug: "name", value: `${input.first_name} ${input.last_name}`, }, { columnSlug: "account_id", value: accounts[0].id }, { columnSlug: "lead_source", value: "website" }, ], customMappings: [ { columnSlug: "inbound_status", value: company.company_name ? qualified ? "qualified" : "not_qualified" : "unknown_company", }, { columnSlug: "inbound_message", value: input.message }, { columnSlug: "inbound_page_url", value: input.page_url }, { columnSlug: "utm_source", value: input.utm_source }, { columnSlug: "utm_campaign", value: input.utm_campaign }, { columnSlug: "marketing_consent", value: input.consent }, ], });Technical Analysis
The publicly accessible form accepts an email address without verifying that the submitter controls it. The workflow then uses that address as the sole matching key for an upsert into the shared
gtm_contactsmodel.If the supplied address already belongs to a contact, the operation updates that existing record with unauthenticated values. The affected fields include the contact's name, account association, lead source, inbound qualification state, message, attribution data, and marketing-consent status.
The origin allowlist, honeypot, minimum-fill delay, and per-IP rate limit provide anti-automation controls, but they do not authenticate the identity of the submitter or prove ownership of the supplied address. Consequently, an ordinary e ...[truncated 1348 chars]
- Remediation
View remediation
Remediation Suggestions
- Do not update authoritative existing-contact fields directly from an unverified public submission.
- Store each request in a separate immutable submission or staging model, then merge it into a contact only after appropriate verification or review.
- Require email ownership verification before changing an existing contact.
- If direct upsert is necessary, restrict public submissions to dedicated inbound fields and preserve existing identity, account association, lead source, and consent data.
- Treat consent as submission-specific evidence rather than allowing an unverified request to replace an existing contact-level consent value.
- Add a server-side rule that distinguishes creation from update and requires stronger authorization for updates.
- Add contract tests proving that a submission using an existing email cannot overwrite protected fields.
