feat(gusto): add Gusto Embedded Payroll integration#4346
feat(gusto): add Gusto Embedded Payroll integration#4346waleedlatif1 wants to merge 1 commit intostagingfrom
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
PR SummaryMedium Risk Overview Introduces a Wires up Gusto OAuth across the platform: adds provider config in Reviewed by Cursor Bugbot for commit 5470392. Configure here. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ 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' }, |
There was a problem hiding this comment.
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)
Reviewed by Cursor Bugbot for commit 5470392. Configure here.
Greptile SummaryThis 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.
Confidence Score: 3/5Not 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
|
| 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]
Reviews (1): Last reviewed commit: "feat(gusto): add Gusto Embedded Payroll ..." | Re-trigger Greptile
| required: false, | ||
| visibility: 'user-or-llm', | ||
| description: 'Send self-onboarding invite to employee', | ||
| }, | ||
| accessToken: { | ||
| type: 'string', |
There was a problem hiding this comment.
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
| 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() |
There was a problem hiding this comment.
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.
| } | ||
|
|
||
| const data = await response.json() | ||
| const email = data.email || data.user?.email || '' |
There was a problem hiding this comment.
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.


Summary
Type of Change
Testing
Tested manually. Typecheck and lint clean.
Checklist