-
Notifications
You must be signed in to change notification settings - Fork 56
rollup: integrate open fix PRs into a single review branch #432
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
98ee7a2
d38ef10
fcb5521
f15933a
22db44c
3226352
20c5f65
8ecb724
258d5b4
8895b76
6f06bc3
aabb652
eb5408d
06f2390
8aad2a7
ff4dff6
33ec2c6
f474aa2
afd1fff
d1f4caf
366e7e8
ccd1274
118b852
9b4306b
81824d7
6f2c1ec
a7bea8b
6931526
3279218
d582d0d
e6de13d
0ad6a25
2d8bfc2
e0a8a34
0e21c80
4c35972
d7acb80
7c90a6e
045a914
9f2442a
de1e179
098a17f
11e6f1d
25fc5b6
fd4d36d
47253a8
7f50455
056ad18
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 |
|---|---|---|
|
|
@@ -66,14 +66,18 @@ export function startLocalOAuthServer({ | |
| "default-src 'none'; style-src 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; script-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'", | ||
| ); | ||
| res.end(successHtml); | ||
| const trackedServer = server as http.Server & { _lastCode?: string }; | ||
| const trackedServer = server as http.Server & { | ||
| _lastCode?: string; | ||
| _lastState?: string; | ||
| }; | ||
| if (trackedServer._lastCode) { | ||
| logWarn( | ||
| "Duplicate OAuth callback received; preserving first authorization code", | ||
| ); | ||
| return; | ||
| } | ||
| trackedServer._lastCode = code; | ||
| trackedServer._lastState = state; | ||
|
Comment on lines
+69
to
+80
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. 🧹 Nitpick | 🔵 Trivial state pinning is correct but partially redundant; worth a comment.
that's still a reasonable defense-in-depth check for the rollup of also, one concurrency nit worth calling out for windows/EBUSY-prone environments per the repo guidelines: suggested tightening- const lastCode = trackedServer._lastCode;
- if (lastCode) {
- if (trackedServer._lastState !== expectedState) {
+ const { _lastCode: lastCode, _lastState: lastState } =
+ trackedServer;
+ if (lastCode) {
+ if (lastState !== expectedState) {
logWarn(
"Discarding OAuth callback due to state mismatch in waitForCode",
);
return null;
}
return { code: lastCode };
}As per coding guidelines: "focus on auth rotation, windows filesystem IO, and concurrency." Also applies to: 102-128, 148-148 🤖 Prompt for AI Agents |
||
| } catch (err) { | ||
| logError( | ||
| `Request handler error: ${(err as Error)?.message ?? String(err)}`, | ||
|
|
@@ -95,17 +99,28 @@ export function startLocalOAuthServer({ | |
| pollAborted = true; | ||
| server.close(); | ||
| }, | ||
| waitForCode: async () => { | ||
| waitForCode: async (expectedState: string) => { | ||
| const POLL_INTERVAL_MS = 100; | ||
| const TIMEOUT_MS = 5 * 60 * 1000; | ||
| const maxIterations = Math.floor(TIMEOUT_MS / POLL_INTERVAL_MS); | ||
| const poll = () => | ||
| new Promise<void>((r) => setTimeout(r, POLL_INTERVAL_MS)); | ||
| for (let i = 0; i < maxIterations; i++) { | ||
| if (pollAborted) return null; | ||
| const lastCode = (server as http.Server & { _lastCode?: string }) | ||
| ._lastCode; | ||
| if (lastCode) return { code: lastCode }; | ||
| const trackedServer = server as http.Server & { | ||
| _lastCode?: string; | ||
| _lastState?: string; | ||
| }; | ||
| const lastCode = trackedServer._lastCode; | ||
| if (lastCode) { | ||
| if (trackedServer._lastState !== expectedState) { | ||
| logWarn( | ||
| "Discarding OAuth callback due to state mismatch in waitForCode", | ||
| ); | ||
| return null; | ||
| } | ||
| return { code: lastCode }; | ||
| } | ||
| await poll(); | ||
| } | ||
| logWarn("OAuth poll timeout after 5 minutes"); | ||
|
|
@@ -130,7 +145,7 @@ export function startLocalOAuthServer({ | |
| ); | ||
| } | ||
| }, | ||
| waitForCode: () => Promise.resolve(null), | ||
| waitForCode: (_expectedState: string) => Promise.resolve(null), | ||
| }); | ||
| }); | ||
| }); | ||
|
|
||
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.
sanitizer looks solid; one subtle gap worth covering.
lib/auth/auth.ts:134-159does the right thing: structured json → recursive key redaction, otherwise a targeted regex scrub. two notes:lib/auth/auth.ts:152andlib/auth/auth.ts:155is a false positive —OAUTH_SENSITIVE_BODY_KEYSis a frozen const array of ascii identifiers, not user input, so no regex injection surface here. no action."upstream error: refresh_token=RT_ch_...; trace=..."with leading plain text), the json path is skipped (doesn't start with{/[) and the urlencoded regex atlib/auth/auth.ts:155requires a^|[?&\s]boundary beforekey=. a body likeupstream error refresh_token=RT_ch_xxxx...with a space beforerefresh_tokenis covered by\s, butupstream:refresh_token=...(colon boundary) is not. thescrubTokenLikeSubstringshelper atlib/auth/auth.ts:82-92would save you here for theRT_ch_/AT_ch_shape, but it is only invoked fromredactSensitiveFieldson parsed-json strings, not on the raw-text fallback path.please either (a) pipe the fallback output through
scrubTokenLikeSubstringsas a final pass, or (b) loosen the boundary to also accept[:]. recommend (a) since it also catches opaque tokens that appear without akey=prefix.proposed fix
let scrubbed = rawBody; for (const key of OAUTH_SENSITIVE_BODY_KEYS) { const jsonPattern = new RegExp(`("${key}"\\s*:\\s*)"[^"]*"`, "g"); scrubbed = scrubbed.replace(jsonPattern, `$1"***REDACTED***"`); const urlPattern = new RegExp(`(^|[?&\\s])(${key}=)[^&\\s]+`, "g"); scrubbed = scrubbed.replace(urlPattern, `$1$2***REDACTED***`); } - return scrubbed; + return scrubTokenLikeSubstrings(scrubbed); }and add a test in
test/auth.test.tsalongside theLIB-HIGH-001 regressionsuite covering a non-json/non-urlencoded body carrying an opaqueRT_ch_...substring.As per coding guidelines: "check for logging that leaks tokens or emails."
🧰 Tools
🪛 ast-grep (0.42.1)
[warning] 151-151: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(
("${key}"\\s*:\\s*)"[^"]*", "g")Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html
(regexp-from-variable)
[warning] 154-154: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(
(^|[?&\\s])(${key}=)[^&\\s]+, "g")Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html
(regexp-from-variable)
🤖 Prompt for AI Agents