Skip to content

feat(gusto): add Gusto Embedded Payroll integration#4346

Open
waleedlatif1 wants to merge 1 commit intostagingfrom
waleedlatif1/gusto-integration
Open

feat(gusto): add Gusto Embedded Payroll integration#4346
waleedlatif1 wants to merge 1 commit intostagingfrom
waleedlatif1/gusto-integration

Conversation

@waleedlatif1
Copy link
Copy Markdown
Collaborator

Summary

  • Adds Gusto Embedded Payroll integration with 32 tools: company/employee/contractor CRUD, payroll lifecycle (calculate/submit/cancel/off-cycle), contractor payments, pay stubs, time off activities, benefits, forms, departments, locations, pay schedules
  • OAuth provider wired with refresh tokens; scopes managed app-level per Gusto Embedded conventions
  • Block validated against the Gusto Embedded Python SDK; output property records mirror SDK models

Type of Change

  • New feature

Testing

Tested manually. Typecheck and lint clean.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 29, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
docs Ready Ready Preview, Comment Apr 29, 2026 10:35pm

Request Review

@cursor
Copy link
Copy Markdown

cursor Bot commented Apr 29, 2026

PR Summary

Medium Risk
Adds a new OAuth provider and many new HR/payroll API calls; while largely additive, failures in token handling or request/response shaping could impact integration auth and data operations.

Overview
Adds a new Gusto integration end-to-end: new SVG icon, integration metadata for landing/docs, and a full docs page at tools/gusto.

Introduces a GustoBlock with an operation dropdown and parameter validation/mapping for 32 Gusto tools (company/employee/contractor management, payroll lifecycle including off-cycle/calculate/submit/cancel, contractor payments, pay stubs, time off activities, benefits, forms, departments, locations, and pay schedules).

Wires up Gusto OAuth across the platform: adds provider config in auth.ts, environment variables for client credentials, registers the service in OAuth provider/types/config, and plugs the new block into the block registry.

Reviewed by Cursor Bugbot for commit 5470392. Configure here.

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 5470392. Configure here.

title: 'Check Date',
type: 'short-input',
placeholder: 'YYYY-MM-DD',
required: { field: 'operation', value: 'gusto_create_off_cycle_payroll' },
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Block config incorrectly requires optional checkDate field

Medium Severity

The checkDate sub-block is marked as required for the gusto_create_off_cycle_payroll operation, but the underlying tool definition declares it as required: false, and the documentation states it defaults to the next available payday. This forces users to provide a check date in the UI even though the Gusto API has a sensible default, preventing them from leveraging that default behavior.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 5470392. Configure here.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 29, 2026

Greptile Summary

This PR adds a full Gusto Embedded Payroll integration with 32 tools covering employees, contractors, payroll lifecycle, pay stubs, benefits, departments, and locations, wired to a new OAuth provider with refresh token support.

  • SSN/EIN PII visible to LLM: ssn (create_employee.ts, update_employee.ts) and ein (create_contractor.ts) use visibility: 'user-or-llm' — these should be user-only to prevent sensitive government IDs from being processed by the LLM.
  • Unguarded response.json() on error paths: Several tools (create_contractor, create_employee, create_off_cycle_payroll, list_payrolls, rehire_employee, terminate_employee, update_employee) call response.json() unconditionally before checking response.ok, which throws an unhandled rejection on non-JSON error bodies.
  • Empty email fallback in getUserInfo: The Gusto auth provider falls back to '' when no email is found, which may break better-auth account creation.

Confidence Score: 3/5

Not safe to merge as-is — SSN/EIN PII are exposed to the LLM and several tools can throw unhandled rejections on non-JSON error responses.

Three P1 findings: a security-relevant PII visibility misconfiguration (SSN/EIN marked user-or-llm), a robustness issue where unconditional response.json() calls can throw unhandled rejections on non-JSON error bodies across 7 tools, and a potential empty-email issue in the OAuth getUserInfo handler. Multiple P1s pull the score below the 4/5 P1 ceiling.

apps/sim/tools/gusto/create_employee.ts, apps/sim/tools/gusto/update_employee.ts, apps/sim/tools/gusto/create_contractor.ts, apps/sim/lib/auth/auth.ts

Security Review

  • SSN/EIN PII exposure: ssn in create_employee.ts and update_employee.ts, and ein in create_contractor.ts are marked visibility: 'user-or-llm', allowing the LLM to process Social Security and Employer Identification Numbers. These fields must be user-only.
  • No secrets hardcoded; client credentials flow through env vars correctly.
  • OAuth accessToken correctly uses hidden visibility in all tool definitions.

Important Files Changed

Filename Overview
apps/sim/tools/gusto/create_employee.ts Creates a new Gusto employee; SSN field uses user-or-llm visibility (should be user-only) and response.json() is called unconditionally before response.ok check
apps/sim/tools/gusto/update_employee.ts Updates a Gusto employee; same SSN visibility issue and unconditional response.json() as create_employee.ts
apps/sim/tools/gusto/create_contractor.ts Creates a Gusto contractor; EIN uses user-or-llm visibility and response.json() called before response.ok check
apps/sim/lib/auth/auth.ts Adds Gusto OAuth provider; getUserInfo can produce an empty email string on fallback, which may break account linking in better-auth
apps/sim/lib/oauth/oauth.ts Registers Gusto as an OAuth provider with correct token endpoint, refresh token rotation, and icon
apps/sim/tools/gusto/utils.ts Shared Gusto utilities: headers builder with access token guard and error message extractor — looks correct
apps/sim/tools/gusto/submit_payroll.ts Submits a payroll; uses safe response pattern (checks ok before json parse), handles 202/204 async acceptance
apps/sim/tools/gusto/calculate_payroll.ts Calculates a payroll preview; uses safe error-handling pattern correctly
apps/sim/tools/gusto/terminate_employee.ts Terminates a Gusto employee; unconditional response.json() before ok-check is a minor robustness concern
apps/sim/blocks/blocks/gusto.ts Defines the Gusto block with 32 operations; conditional input visibility logic is well-structured

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[User configures Gusto Block] --> B{Select Operation}
    B --> C[Company / Payroll ops require companyId]
    B --> D[Employee ops require employeeId]
    B --> E[Contractor ops require contractorId]
    C --> F[Payroll lifecycle: calculate to submit to cancel]
    D --> G[CRUD + terminate + rehire + benefits]
    E --> H[CRUD + payments + contractor forms]
    F --> I[Tool Executor calls Gusto REST API v1]
    G --> I
    H --> I
    I --> J[OAuth refresh handled automatically]
    J --> K[Response mapped to typed output properties]
Loading

Reviews (1): Last reviewed commit: "feat(gusto): add Gusto Embedded Payroll ..." | Re-trigger Greptile

Comment on lines +65 to +70
required: false,
visibility: 'user-or-llm',
description: 'Send self-onboarding invite to employee',
},
accessToken: {
type: 'string',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 security SSN exposed to LLM via user-or-llm visibility

The ssn field has visibility: 'user-or-llm', which allows the LLM to read and process the employee's Social Security Number. SSNs are highly sensitive PII and should be restricted to human input only (user-only), consistent with the project's rule that user-provided sensitive credentials must use user-only visibility. The same issue exists in update_employee.ts (line 94) and the ein field in create_contractor.ts (line 90).

Rule Used: API keys and other user-provided credentials shoul... (source)

Learned From
simstudioai/sim#2133

Comment on lines +117 to +125
if (params.selfOnboarding !== undefined) body.self_onboarding = params.selfOnboarding
if (params.ein) body.ein = params.ein
if (params.hourlyRate) body.hourly_rate = params.hourlyRate
return body
},
},

transformResponse: async (response) => {
const data = await response.json()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 response.json() called before !response.ok check

response.json() is called unconditionally before checking response.ok. If the API returns a non-JSON error body (e.g., a plain-text 5xx, rate-limit response, or empty body), this throws an unhandled rejection instead of returning { success: false, error: "..." }. The safer pattern used in calculate_payroll.ts and submit_payroll.ts checks response.ok first and only calls response.json().catch(() => ({})) in the error branch. The same issue exists in create_employee.ts, create_off_cycle_payroll.ts, list_payrolls.ts, rehire_employee.ts, terminate_employee.ts, and update_employee.ts.

Comment thread apps/sim/lib/auth/auth.ts
Comment on lines +2266 to +2269
}

const data = await response.json()
const email = data.email || data.user?.email || ''
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 getUserInfo falls back to empty string for email

When the Gusto /v1/me endpoint returns a response where neither data.email nor data.user?.email is set, email falls back to ''. better-auth may reject this or create a user record with a blank email, breaking sign-in. Other providers in this file throw when no email is found. Consider throwing an error or falling back to something identifiable when no email can be extracted.

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