feat(skills): add GitHub password rotator#64
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces a new github-password-rotator skill that automates GitHub password rotation using Chrome DevTools Protocol and optionally creates a beginner learning repository. Key feedback includes wrapping DevTools commands in a try...finally block to prevent WebSocket leaks, optimizing the 2FA polling loop to avoid socket exhaustion, and replacing a single conditional check with a loop to robustly handle manual verification prompts during repository creation.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| await targetSend("Runtime.enable"); | ||
| const response = await targetSend("Runtime.evaluate", { | ||
| expression, | ||
| returnByValue: true, | ||
| awaitPromise: true, | ||
| }); | ||
| targetWs.close(); | ||
| if (response.exceptionDetails) { | ||
| throw new Error(JSON.stringify(response.exceptionDetails)); | ||
| } | ||
| return response.result?.result?.value; |
There was a problem hiding this comment.
If Runtime.evaluate or any other DevTools command throws an exception, the WebSocket connection to the target page will be leaked because targetWs.close() is bypassed. Wrap the commands in a try...finally block to ensure the connection is always closed.
| await targetSend("Runtime.enable"); | |
| const response = await targetSend("Runtime.evaluate", { | |
| expression, | |
| returnByValue: true, | |
| awaitPromise: true, | |
| }); | |
| targetWs.close(); | |
| if (response.exceptionDetails) { | |
| throw new Error(JSON.stringify(response.exceptionDetails)); | |
| } | |
| return response.result?.result?.value; | |
| try { | |
| await targetSend("Runtime.enable"); | |
| const response = await targetSend("Runtime.evaluate", { | |
| expression, | |
| returnByValue: true, | |
| awaitPromise: true, | |
| }); | |
| if (response.exceptionDetails) { | |
| throw new Error(JSON.stringify(response.exceptionDetails)); | |
| } | |
| return response.result?.result?.value; | |
| } finally { | |
| targetWs.close(); | |
| } |
| const deadline = Date.now() + 12_000; | ||
| while (Date.now() < deadline) { | ||
| await sleep(500); | ||
| const values = await evaluateInTarget( | ||
| target, | ||
| `(() => [...document.querySelectorAll('input.faotp')].map((e) => e.value || ''))()` | ||
| ); | ||
| const code = extractTotpCodeFrom2faFunValues(values || []); | ||
| if (code) { | ||
| return code; | ||
| } | ||
| } | ||
| return ""; |
There was a problem hiding this comment.
Polling with evaluateInTarget inside a loop opens and closes a new WebSocket connection every 500ms, which is highly inefficient and can lead to socket exhaustion. Since evaluateInTarget supports awaitPromise: true, you can evaluate a single Promise in the target page that polls internally and resolves when the code is found.
return await evaluateInTarget(
target,
`new Promise((resolve) => {
const start = Date.now();
const poll = () => {
const inputs = [...document.querySelectorAll('input.faotp')];
for (const input of inputs) {
const val = (input.value || "").trim();
const match = val.match(/^\\s*(\\d{6})\\s*$/) || val.match(/\\b(\\d{6})\\b/);
if (match) {
resolve(match[1]);
return;
}
}
if (Date.now() - start > 12000) {
resolve("");
} else {
setTimeout(poll, 500);
}
};
poll();
})`
);| let current = await state(); | ||
| const currentLower = (current.text || "").toLowerCase(); | ||
| if (isGithubTwoFactorPrompt(current.url, currentLower)) { | ||
| if (auto2faFun && totpSecret) { | ||
| await clickEnabledTextContaining("verify 2fa now"); | ||
| await sleep(3000); | ||
| const code = await codeFrom2faFun(); | ||
| if (await submitGithubTotpCode(code)) { | ||
| console.log("Browser helper: submitted GitHub 2FA code from 2fa.fun"); | ||
| await sleep(3500); | ||
| } | ||
| await navigate("https://github.com/new"); | ||
| await waitForPageSettle(3000); | ||
| } else if (await clickEnabledTextContaining("skip 2fa verification")) { | ||
| await waitForPageSettle(3000); | ||
| } | ||
| } |
There was a problem hiding this comment.
If GitHub prompts for manual verification (such as device verification or passkeys) during repository creation, the script will immediately fail because it lacks a wait loop for manual steps. Changing the if check to a while loop that checks requiresManualGithubStep allows the user to complete manual verification steps just like in the main loop.
let current = await state();
let currentLower = (current.text || "").toLowerCase();
const deadline = Date.now() + timeoutSeconds * 1000;
while (requiresManualGithubStep(current.url, currentLower) && Date.now() < deadline) {
if (auto2faFun && totpSecret && isGithubTwoFactorPrompt(current.url, currentLower)) {
await clickEnabledTextContaining("verify 2fa now");
await sleep(3000);
const code = await codeFrom2faFun();
if (await submitGithubTotpCode(code)) {
console.log("Browser helper: submitted GitHub 2FA code from 2fa.fun");
await sleep(3500);
}
await navigate("https://github.com/new");
await waitForPageSettle(3000);
} else if (await clickEnabledTextContaining("skip 2fa verification")) {
await waitForPageSettle(3000);
} else {
console.log("Browser helper: GitHub verification/restriction detected during repo creation; complete it manually");
await sleep(2000);
}
current = await state();
currentLower = (current.text || "").toLowerCase();
}
Summary
Adds a reusable GitHub password rotation skill and records the latest suspended-account appeal queue updates.
Root cause
Recent account recovery work needed repeatable browser automation for GitHub password changes, app-code 2FA through 2fa.fun, and post-change repository setup. The ad hoc flow also exposed GitHub timing and checkup-page variants that should be handled by the skill instead of repeated manual patches.
What changed
github-password-rotatorwith a Python launcher, Node DevTools browser driver, OpenAI skill metadata, and focused unit tests.2fa.fun, sudo confirmation, no-flash password-change completion, and beginner learning repo creation.Test Plan
node --test skills/github-password-rotator/tests/drive_github_password_change.test.mjspython3 -m unittest discover -s skills/github-password-rotator/tests -p 'test_rotate_github_password.py'node --check skills/github-password-rotator/scripts/drive_github_password_change.mjspython3 -m py_compile skills/github-password-rotator/scripts/rotate_github_password.pypython3 /home/ts_user/.codex/skills/.system/skill-creator/scripts/quick_validate.py skills/github-password-rotator