-
Notifications
You must be signed in to change notification settings - Fork 4
refactor(SDK-1127): extract sub flows from PaymentFlow #2445
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
4fbc390
723a67b
30515b2
b7c2120
f624da9
234929d
5b6e3b3
6d63f9c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -58,6 +58,51 @@ | |
| demoType: string | ||
| } | ||
|
|
||
| /** | ||
| * Records a past-dated "Historical Payment" for the given contractor so freshly | ||
| * provisioned demos have at least one contractor payment group to inspect | ||
| * (e.g. via ContractorManagement.ViewHistoryFlow). Historical payments are | ||
| * record-only -- no funding or ACH lead time -- so they post synchronously. | ||
| */ | ||
| async function seedHistoricalContractorPayment( | ||
| proxyBase: string, | ||
| companyId: string, | ||
| contractorId: string, | ||
| ): Promise<string> { | ||
| const contractorRes = await fetch(`${proxyBase}/v1/contractors/${contractorId}`, { | ||
|
Check failure on line 72 in sdk-app/scripts/demo-provisioner.ts
|
||
| signal: AbortSignal.timeout(10000), | ||
| }) | ||
| if (!contractorRes.ok) return '' | ||
| const contractor = (await contractorRes.json()) as { wage_type?: string } | ||
|
|
||
| const checkDate = new Date() | ||
| checkDate.setDate(checkDate.getDate() - 14) | ||
|
|
||
| const paymentRes = await fetch( | ||
| `${proxyBase}/v1/companies/${companyId}/contractor_payment_groups`, | ||
|
Check failure on line 82 in sdk-app/scripts/demo-provisioner.ts
|
||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. CWE-918: Server-Side Request Forgery (SSRF) DetailsThe web server receives a URL or similar request from an upstream component and retrieves the contents of this URL, but it does not sufficiently ensure that the request is being sent to the expected destination. This rule detected user-controlled URLs being passed to Node.js HTTP client libraries including axios.get(), axios.post(), fetch(), http.get(),http.request(), needle(), request(), urllib.request(),superagent.get(), bent(), got.get(), net.connect(), andsocket.io-client.io(). When user input controls the destination URL of HTTPrequests without validation, Server-Side Request Forgery (SSRF) vulnerabilities arise. SSRF allows attackers to force the server to make requests to internal systems, cloud metadata endpoints (such as 169.254.169.254), or other unauthorized destinations. This can expose internal APIs, databases, administrative panels, or enable network scanning and pivoting attacks that bypass firewall rules and network segmentation. 📘 Learn More AI Remediation At line 69, do the following changes: companyId: string,
contractorId: string,
): Promise<string> {
- const contractorRes = await fetch(`${proxyBase}/v1/contractors/${contractorId}`, {
- signal: AbortSignal.timeout(10000),
- })
+ // Guard against path/URL injection: IDs are interpolated into request URLs,
+ // so restrict them to opaque identifier characters before use.
+ const ID_PATTERN = /^[a-zA-Z0-9_-]+$/
+ if (!ID_PATTERN.test(companyId) || !ID_PATTERN.test(contractorId)) {
+ throw new Error('Invalid company or contractor ID')
+ }
+
+ const contractorRes = await fetch(
+ `${proxyBase}/v1/contractors/${encodeURIComponent(contractorId)}`,
+ {
+ signal: AbortSignal.timeout(10000),
+ },
+ )
if (!contractorRes.ok) return ''
const contractor = (await contractorRes.json()) as { wage_type?: string }
At line 79, do the following changes: checkDate.setDate(checkDate.getDate() - 14)
const paymentRes = await fetch(
- `${proxyBase}/v1/companies/${companyId}/contractor_payment_groups`,
+ `${proxyBase}/v1/companies/${encodeURIComponent(companyId)}/contractor_payment_groups`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' }, |
||
| { | ||
| method: 'POST', | ||
| headers: { 'Content-Type': 'application/json' }, | ||
| signal: AbortSignal.timeout(10000), | ||
| body: JSON.stringify({ | ||
| check_date: checkDate.toISOString().slice(0, 10), | ||
| creation_token: `sdk-app-seed-${companyId}-${contractorId}`, | ||
| contractor_payments: [ | ||
| { | ||
| contractor_uuid: contractorId, | ||
| payment_method: 'Historical Payment', | ||
| ...(contractor.wage_type === 'Hourly' ? { hours: '10' } : { wage: '500.00' }), | ||
| }, | ||
| ], | ||
| }), | ||
| }, | ||
| ) | ||
| if (!paymentRes.ok) return '' | ||
|
|
||
| const created = (await paymentRes.json()) as { contractor_payment_group?: { uuid?: string } } | ||
| return created.contractor_payment_group?.uuid || '' | ||
| } | ||
|
|
||
| export async function createDemoAndProvision( | ||
| gwsFlowsHost: string, | ||
| demoType: string, | ||
|
|
@@ -150,6 +195,20 @@ | |
| if (companyId) { | ||
| onProgress?.('Fetching entity IDs...') | ||
| entities = await fetchEntityIds(proxyBase, companyId) | ||
|
|
||
| if (entities.contractorId && !entities.paymentId) { | ||
| onProgress?.('Seeding a historical contractor payment...') | ||
| try { | ||
| const paymentId = await seedHistoricalContractorPayment( | ||
| proxyBase, | ||
| companyId, | ||
| entities.contractorId, | ||
| ) | ||
| if (paymentId) entities.paymentId = paymentId | ||
| } catch { | ||
| // Best-effort seed; demo creation should not fail if this errors. | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return { flowToken, companyId, entities, demoType } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CWE-918: Server-Side Request Forgery (SSRF)
Original Rule ID: rules_lgpl_javascript_ssrf_rule-node-ssrf
Details
The web server receives a URL or similar request from an upstream component and retrieves the contents of this URL, but it does not sufficiently ensure that the request is being sent to the expected destination.
This rule detected user-controlled URLs being passed to Node.js HTTP client
libraries including
axios.get(),axios.post(),fetch(),http.get(),http.request(),needle(),request(),urllib.request(),superagent.get(),bent(),got.get(),net.connect(), andsocket.io-client.io(). When user input controls the destination URL of HTTPrequests without validation, Server-Side Request Forgery (SSRF) vulnerabilities
arise. SSRF allows attackers to force the server to make requests to internal
systems, cloud metadata endpoints (such as 169.254.169.254), or other
unauthorized destinations. This can expose internal APIs, databases,
administrative panels, or enable network scanning and pivoting attacks that
bypass firewall rules and network segmentation.
📘 Learn More
AI Remediation
The fix adds an origin-allowlist re-validation (
validateHost) at the start ofseedHistoricalContractorPaymentbefore anyfetchcalls, ensuring the request destination is confined to the pre-approved gws-flows origins and cannot be redirected toward internal systems or cloud metadata endpoints. This closes the SSRF (CWE-918) gap whereproxyBasewas consumed as an HTTP client URL without validation at the request site.