Skip to content

feat(skills): add GitHub password rotator#64

Merged
acking-you merged 1 commit into
masterfrom
feat/github-password-rotator-skill
Jul 4, 2026
Merged

feat(skills): add GitHub password rotator#64
acking-you merged 1 commit into
masterfrom
feat/github-password-rotator-skill

Conversation

@acking-you

Copy link
Copy Markdown
Owner

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

  • Add github-password-rotator with a Python launcher, Node DevTools browser driver, OpenAI skill metadata, and focused unit tests.
  • Support hidden password/2FA-secret input, standard GitHub login, app-code 2FA via 2fa.fun, sudo confirmation, no-flash password-change completion, and beginner learning repo creation.
  • Handle GitHub two-factor checkup pages and add a randomized 3-10 second pause after password changes before creating the learning repository.
  • Update the GitHub suspension appeal tracker with additional suspended/disabled accounts for later appeals.

Test Plan

  • node --test skills/github-password-rotator/tests/drive_github_password_change.test.mjs
  • python3 -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.mjs
  • python3 -m py_compile skills/github-password-rotator/scripts/rotate_github_password.py
  • python3 /home/ts_user/.codex/skills/.system/skill-creator/scripts/quick_validate.py skills/github-password-rotator
  • redacted sensitive-literal scan over the new skill and appeal tracker

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +199 to +209
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;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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.

Suggested change
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();
}

Comment on lines +418 to +430
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 "";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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();
    })`
  );

Comment on lines +582 to +598
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);
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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();
  }

@acking-you acking-you merged commit a78b9c2 into master Jul 4, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant