Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@ Three separate GitHub identities are used so each role is cleanly separated:
| Account | Purpose | Credentials stored as |
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
| `gnovak` | Repo owner. Used for normal development and testing. | `RDB_PAT_TOKEN` (on both repos), or GitHub App (`RDB_APP_ID` variable + `RDB_APP_PRIVATE_KEY` secret) |
| `remote-dev-bot` | Dedicated bot account. Collaborator on `remote-dev-bot-test`. Posts authorized test comments that trigger agent runs without attributing activity to `gnovak`. | `RDB_TESTER_PAT_TOKEN` (on remote-dev-bot) |
| `remote-dev-bot` | Dedicated bot account. Collaborator on `remote-dev-bot-test`. Posts authorized test comments that trigger agent runs without attributing activity to `gnovak`. | `RDB_TESTER_PAT_TOKEN` (on remote-dev-bot) — but see the temporary-state note below |
| `remote-dev-bot-tester` | Simulates an unauthorized external user. NOT a collaborator on any repo. | `RDB_TESTER_UNAUTHORIZED_PAT_TOKEN` (on remote-dev-bot) |

### remote-dev-bot (bot account) details

The intended setup:

- Classic PAT with `public_repo` + `workflow` scopes, no expiration
- **`public_repo`** — create issues, post comments, open PRs in public repos
(standard e2e test flow)
Expand All @@ -40,6 +42,17 @@ Three separate GitHub identities are used so each role is cleanly separated:
its trigger comments
- Keeps test activity out of `gnovak`'s GitHub contribution stats

> **Temporary state (as of 2026-08):** the `remote-dev-bot` account is
> flagged by GitHub (its profile 404s on the public API), so tokens cannot
> be minted on it until that's resolved with GitHub support. Until then,
> `RDB_TESTER_PAT_TOKEN` holds a PAT minted on `gnovak` instead (verifiable:
> e2e test issues on rdb-test are authored by `gnovak`). If minting the
> stopgap on `gnovak`, prefer a fine-grained PAT scoped to
> `gnovak/remote-dev-bot-test` only (Contents/Issues/Pull requests
> read-write + Actions read-write) with a long expiry — the 2026-08-05
> full-suite failure was a 90-day stopgap token quietly expiring. Once the
> bot account is restored, move the token back per the intended setup above.

### remote-dev-bot-tester details

- Classic PAT with `public_repo` scope, no expiration
Expand Down Expand Up @@ -81,7 +94,7 @@ Secrets stored on `gnovak/remote-dev-bot`:
| `OPENAI_API_KEY` | OpenAI API key (for GPT models) |
| `GEMINI_API_KEY` | Google AI API key (for Gemini models) |
| `RDB_APP_PRIVATE_KEY` | GitHub App private key, for bot identity on comments/PRs. |
| `RDB_TESTER_PAT_TOKEN` | PAT for `remote-dev-bot` account (collaborator on rdb-test). Used by e2e tests to post authorized trigger comments. |
| `RDB_TESTER_PAT_TOKEN` | PAT for `remote-dev-bot` account (collaborator on rdb-test; temporarily a `gnovak` PAT — see account details). Used by e2e tests to post authorized trigger comments. |
| `RDB_TESTER_UNAUTHORIZED_PAT_TOKEN` | PAT for `remote-dev-bot-tester` (not a collaborator). Used by security e2e tests to verify unauthorized users are blocked. |

Variables stored on `gnovak/remote-dev-bot`:
Expand Down
29 changes: 29 additions & 0 deletions tests/e2e-security.sh
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ check_graphql_quota() {
fi
remaining=$(echo "$result" | cut -d' ' -f1)
reset_ts=$(echo "$result" | cut -d' ' -f2)
if ! [[ "$remaining" =~ ^[0-9]+$ ]]; then
echo "==> Warning: could not parse GraphQL rate limit response — proceeding anyway"
return
fi
reset_time=$(python3 -c "
import datetime
ts = ${reset_ts}
Expand All @@ -74,6 +78,31 @@ print(f'{local_t.strftime(\"%H:%M:%S\")} local / {utc_t.strftime(\"%H:%M:%S\")}
echo "==> GraphQL quota: ${remaining} points remaining (resets ${reset_time})."
}

# Fail fast with a clear message when GH_TOKEN is dead — an expired
# RDB_TESTER_PAT_TOKEN otherwise surfaces as confusing 401s on every
# issue-creation call after the suite has already started.
gh_login=$(gh api user --jq .login 2>/dev/null) || {
echo "ERROR: GH_TOKEN is invalid or expired (gh api user failed)." >&2
echo "ERROR: In CI this token comes from secrets.RDB_TESTER_PAT_TOKEN —" >&2
echo "ERROR: mint a new PAT with write access to the test repo and update the secret." >&2
exit 1
}
gh_exp=$(gh api user -i 2>/dev/null | grep -i "^github-authentication-token-expiration:" | cut -d" " -f2- | tr -d "\r")
echo "==> Authenticated to GitHub as: ${gh_login}${gh_exp:+ (token expires: ${gh_exp})}"

# Validate the unauthorized-user token too, when present — its failures
# otherwise surface only mid-gating-test.
if [[ -n "${UNAUTHORIZED_PAT:-}" ]]; then
ua_login=$(curl -fs -H "Authorization: token ${UNAUTHORIZED_PAT}" \
https://api.github.com/user 2>/dev/null | python3 -c "import json,sys; print(json.load(sys.stdin)['login'])" 2>/dev/null) || true
if [[ -z "${ua_login:-}" ]]; then
echo "ERROR: UNAUTHORIZED_PAT is invalid or expired (api.github.com/user failed)." >&2
echo "ERROR: In CI this is secrets.RDB_TESTER_UNAUTHORIZED_PAT_TOKEN (remote-dev-bot-tester account)." >&2
exit 1
fi
echo "==> Unauthorized-user token authenticates as: ${ua_login}"
fi

check_graphql_quota

# --- Helpers ---
Expand Down
16 changes: 16 additions & 0 deletions tests/e2e.sh
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ check_graphql_quota() {
fi
remaining=$(echo "$result" | cut -d' ' -f1)
reset_ts=$(echo "$result" | cut -d' ' -f2)
if ! [[ "$remaining" =~ ^[0-9]+$ ]]; then
echo "==> Warning: could not parse GraphQL rate limit response — proceeding anyway"
return
fi
reset_time=$(python3 -c "
import datetime
ts = ${reset_ts}
Expand All @@ -70,6 +74,18 @@ print(f'{local_t.strftime(\"%H:%M:%S\")} local / {utc_t.strftime(\"%H:%M:%S\")}
echo "==> GraphQL quota: ${remaining} points remaining (resets ${reset_time})."
}

# Fail fast with a clear message when GH_TOKEN is dead — an expired
# RDB_TESTER_PAT_TOKEN otherwise surfaces as confusing 401s on every
# issue-creation call after the suite has already started.
gh_login=$(gh api user --jq .login 2>/dev/null) || {
echo "ERROR: GH_TOKEN is invalid or expired (gh api user failed)." >&2
echo "ERROR: In CI this token comes from secrets.RDB_TESTER_PAT_TOKEN —" >&2
echo "ERROR: mint a new PAT with write access to the test repo and update the secret." >&2
exit 1
}
gh_exp=$(gh api user -i 2>/dev/null | grep -i "^github-authentication-token-expiration:" | cut -d" " -f2- | tr -d "\r")
echo "==> Authenticated to GitHub as: ${gh_login}${gh_exp:+ (token expires: ${gh_exp})}"

check_graphql_quota

# --- Test case definitions ---
Expand Down