Skip to content

Commit a7ae174

Browse files
fix(oracle-fusion): harden numeric and origin parsing
1 parent abb64b8 commit a7ae174

5 files changed

Lines changed: 35 additions & 8 deletions

File tree

apps/sim/lib/credentials/client-credential-accounts/descriptors.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,9 @@ describe('normalizeOracleFusionApplicationOrigin', () => {
140140
it.each([
141141
'http://vision.fa.us2.oraclecloud.com',
142142
'https://vision.fa.us2.oraclecloud.com/path',
143+
'https://vision.fa.us2.oraclecloud.com/path/..',
144+
'https://vision.fa.us2.oraclecloud.com/./',
145+
'https://vision.fa.us2.oraclecloud.com/%2e%2e/',
143146
'https://vision.fa.us2.oraclecloud.com:443',
144147
'https://vision.fa.us2.oraclecloud.com:8443',
145148
'https://user@vision.fa.us2.oraclecloud.com',

apps/sim/lib/credentials/client-credential-accounts/descriptors.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,8 @@ export function normalizeNetSuiteSuiteTalkOrigin(rawUrl: string): string | undef
159159
/** Canonical Oracle-assigned Fusion Applications origin used by product REST APIs. */
160160
export const ORACLE_FUSION_APPLICATION_ORIGIN_REGEX =
161161
/^https:\/\/[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.fa\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.oraclecloud\.com$/
162+
const ORACLE_FUSION_APPLICATION_INPUT_REGEX =
163+
/^https:\/\/[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.fa\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.oraclecloud\.com\/?$/i
162164

163165
/**
164166
* Normalizes a Fusion Applications URL to its authoritative HTTPS origin.
@@ -168,8 +170,7 @@ export const ORACLE_FUSION_APPLICATION_ORIGIN_REGEX =
168170
export function normalizeOracleFusionApplicationOrigin(rawUrl: string): string | undefined {
169171
try {
170172
const trimmed = rawUrl.trim()
171-
const authority = /^https:\/\/([^/?#]+)/i.exec(trimmed)?.[1]
172-
if (!authority || authority.includes(':')) return undefined
173+
if (!ORACLE_FUSION_APPLICATION_INPUT_REGEX.test(trimmed)) return undefined
173174
const parsed = new URL(trimmed)
174175
if (
175176
parsed.protocol !== 'https:' ||

apps/sim/lib/credentials/client-credential-accounts/minters/oracle-fusion.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ describe('mintOracleFusionServiceAccountToken', () => {
4949
it.each([
5050
'http://vision.fa.us2.oraclecloud.com',
5151
'https://vision.fa.us2.oraclecloud.com/path',
52+
'https://vision.fa.us2.oraclecloud.com/path/..',
53+
'https://vision.fa.us2.oraclecloud.com/%2e%2e/',
5254
'https://vision.fa.us2.oraclecloud.com:443',
5355
'https://user:password@vision.fa.us2.oraclecloud.com',
5456
'https://vision.fa.us2.oraclecloud.com?tenant=other',

apps/sim/lib/internal/oracle-fusion/client.test.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,14 +170,17 @@ describe('requestOracleFusionJson', () => {
170170
mockSecureFetch.mockResolvedValueOnce(
171171
response(
172172
200,
173-
'{"id":9007199254740993,"negative":-9007199254740993,"safe":9007199254740991,"decimal":9007199254740993.5}'
173+
'{"id":9007199254740993,"negative":-9007199254740993,"zeroFraction":9007199254740993.0,"exponent":9.007199254740993e15,"hugeExponent":1e999,"safe":9007199254740991,"decimal":9007199254740993.5}'
174174
)
175175
)
176176
await expect(
177177
requestOracleFusionJson(CREDENTIAL, { family: 'hcm', path: 'workers' })
178178
).resolves.toEqual({
179179
id: '9007199254740993',
180180
negative: '-9007199254740993',
181+
zeroFraction: '9007199254740993.0',
182+
exponent: '9.007199254740993e15',
183+
hugeExponent: '1e999',
181184
safe: 9007199254740991,
182185
decimal: 9007199254740994,
183186
})

apps/sim/lib/internal/oracle-fusion/client.ts

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const API_ROOTS = {
2121
const UNSAFE_PATH_ENCODING = /%(?:2e|2f|5c|3f|23)/i
2222
const ABSOLUTE_PATH = /^[a-z][a-z0-9+.-]*:/i
2323
const CANONICAL_BASE64 = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/
24-
const DECIMAL_INTEGER_TOKEN = /^-?\d+$/
24+
const JSON_NUMBER_TOKEN = /^-?(0|[1-9]\d*)(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/
2525

2626
interface JsonParseContext {
2727
source?: string
@@ -97,13 +97,31 @@ function buildRequestUrl(origin: string, request: OracleFusionRequest): string {
9797
return url.toString()
9898
}
9999

100+
function isIntegralJsonNumberToken(source: string): boolean {
101+
const match = JSON_NUMBER_TOKEN.exec(source)
102+
if (!match) return false
103+
const coefficient = `${match[1]}${match[2] ?? ''}`
104+
if (/^0+$/.test(coefficient)) return true
105+
106+
const fractionDigits = match[2]?.length ?? 0
107+
const exponentSource = match[3] ?? '0'
108+
const exponentDigits = exponentSource.replace(/^[+-]/, '').replace(/^0+/, '') || '0'
109+
if (exponentDigits.length > 6) return !exponentSource.startsWith('-')
110+
const exponent = Number(exponentSource)
111+
const remainingFractionDigits = fractionDigits - exponent
112+
if (remainingFractionDigits <= 0) return true
113+
if (remainingFractionDigits > coefficient.length) return false
114+
return coefficient
115+
.slice(-remainingFractionDigits)
116+
.split('')
117+
.every((digit) => digit === '0')
118+
}
119+
100120
function parseOracleFusionJson(body: string): unknown {
101121
return jsonParseWithSource(body, (_key, value, context) => {
102-
if (typeof value !== 'number' || !Number.isInteger(value) || Number.isSafeInteger(value)) {
103-
return value
104-
}
122+
if (typeof value !== 'number' || Number.isSafeInteger(value)) return value
105123
const source = context?.source
106-
return source && DECIMAL_INTEGER_TOKEN.test(source) ? source : value
124+
return source && isIntegralJsonNumberToken(source) ? source : value
107125
})
108126
}
109127

0 commit comments

Comments
 (0)