Skip to content

refactor!: adopt github-app-setup-go, update dependencies, and fix security and sync bugs - #28

Merged
sgtoj merged 3 commits into
mainfrom
refactor/v1
Nov 7, 2026
Merged

sgtoj merged 3 commits into
mainfrom
refactor/v1

Conversation

@sgtoj

@sgtoj sgtoj commented Nov 6, 2026

Copy link
Copy Markdown
Member

Summary

This is a full review of the app with every finding fixed. It also updates all Go modules and GitHub Actions, adopts github-app-setup-go v0.7.1, and brings CI, the Makefile, and releases in line with the other cruxstack Go repos. It contains breaking changes and will release as v1.0.0. docs/upgrading-to-v1.md lists every one.

The PR has three commits, and each builds and passes its tests on its own:

  • ci: pinned actions, lint, govulncheck, a tidy check, amd64 and arm64 builds, and an image build. It also fixes dependabot, which never ran because its config was at .github/.dependabot.yaml, and a release rule that turned breaking changes into minor versions. Releases now build binaries before tagging.
  • refactor!: the code, dependencies, and fixes below.
  • docs: README, setup guides, the Lambda guide, and AGENTS.md.

Dependencies

  • Go 1.24.4 to 1.27.3, go-github v79 to v93, okta-sdk-golang v6 to v8, slack-go v0.17.3 to v0.35.0, and every other module to its latest version.
  • cockroachdb/errors is replaced by the standard library, which also removes sentry-go and its dependencies.
  • govulncheck reported 11 reachable vulnerabilities before this change and none after.

github-app-setup-go

Both entry points now run on its runtime:

  • The server loads its configuration at startup and reloads it on SIGHUP or when the installer saves credentials.
  • Lambda loads on first use and retries a failed load on the next invocation. Before, one failed load (such as a brief SSM error) broke every invocation until AWS replaced the container.
  • An optional installer at /setup creates the GitHub App from a manifest and saves its credentials to an env file or SSM. Once an app is registered, it refuses new codes.

Security

  • Unsigned webhooks were accepted when no secret was set. They are now always rejected.
  • The admin and scheduled endpoints were open when no admin token was set. They are now disabled without one, and the token is compared in constant time.
  • The app manifest was public: true, so any account could install the app and send it signed events. It is now private, and events from other orgs are ignored.
  • PR titles, logins, and Okta group names went into Slack unescaped. They are now escaped.
  • Okta usernames are validated before they go into GitHub API paths.
  • Auth headers are sent only to the GitHub API host.
  • A data race in installation token refresh is fixed.

Fixes

  • Manifest permissions: the manifest used permission names GitHub doesn't know and lacked Administration and Commit statuses read access, so PR checks silently skipped branch protection and statuses.
  • PR compliance: a comment no longer cancels an approval, skipped and neutral checks pass as they do on GitHub, maintainers are recognized, and every page is read.
  • Security alerts: reports read every page. Before, they read only the first 100 alerts of each kind.
  • Okta sync, users:
    • Users with expired passwords are no longer removed.
    • Pending invitees aren't re-invited.
    • Child-team members aren't removed from the parent team.
    • Groups whose members can't be read are reported and not synced.
  • Okta sync, teams:
    • create_team_if_missing is honored.
    • Teams whose slug differs from their name are found.
    • Two groups can't sync into one team.
  • Okta sync, triggers:
    • Overlapping syncs are coalesced.
    • Webhooks trigger a sync only for teams a rule manages.
    • In server mode the sync runs after the webhook response.
  • Orphaned users: members of a team whose sync failed are no longer reported as orphans.
  • Configuration: invalid values used to fall back to defaults silently. Every problem is now reported at startup.
  • Lambda: query strings, cookies, and base64 bodies now pass through from API Gateway, and ping and unhandled events no longer return 400.

Testing

  • make check passes: golangci-lint reports 0 issues, the tests pass with the race detector, govulncheck is clean, and go mod tidy is clean.
  • New unit tests cover the GitHub client against a fake API, including pagination, token caching under concurrency, compliance rules, and team sync. They also cover the Okta client, Slack escaping and message limits, config validation, the Lambda adapter, and the installer flow: save, reload, and a restart that reads the env file.
  • e2e/ runs the 22 scenarios in fixtures/scenarios.json against the real service and mock GitHub, Okta, and Slack APIs, replacing cmd/verify.
  • The server binary was smoke-tested, and the container image builds and runs as a non-root user.

Follow-ups

  • terraform-aws-github-ops-app must be updated before the next release. It builds ./cmd/lambda with golang:1.24-alpine, which can't build a Go 1.27 module. It also sets APP_OKTA_KEY_ID, which the app never read; the correct name is APP_OKTA_PRIVATE_KEY_ID. Its 30-second default timeout is too short for a sync.
  • Existing GitHub Apps need Administration (read) and Commit statuses (read), should be made private, and need the new permissions approved on the installation.
  • Okta's githubUsername attribute should be read-only for users. Otherwise a user can point it at another GitHub account and give that account their team access.
  • Lambda webhooks: Lambda can't run work after responding, so webhook-triggered syncs there still run before the response and may exceed GitHub's 10-second wait.

🤖 Generated with Claude Code

sgtoj and others added 3 commits November 6, 2026 16:30
CI runs golangci-lint, a go mod tidy check, the tests with the race
detector, govulncheck, linux amd64 and arm64 builds of the server and the
lambda function, and a container image build, on every pull request
instead of only those targeting main. Every action is pinned to a commit
of its latest release, the go version comes from go.mod, and checkouts
don't persist credentials.

Dependabot never ran: its config was at .github/.dependabot.yaml, a path
GitHub doesn't read. It moves to .github/dependabot.yaml, groups its
updates, and also watches the Dockerfile's base images.

The release rules listed feat:minor and refactor:minor. A custom rule that
matches a commit's type wins over the commit analyzer's "breaking change
-> major" rule, so a feat! or refactor! commit was released as a minor
version. Those rules are removed. The release now computes the version
first (dry_run), builds the server and lambda binaries for amd64 and arm64
and the container image, and only then creates the tag, so a failed build
leaves no tag behind.

The semantic check no longer checks out code. It runs on
pull_request_target, with a token that can write to the repository, and
the action doesn't need the code.

The Makefile gains help, lint, fmt, vuln, tidy, check, cover, image, and
local-run targets, runs tests with the race detector, and stamps the
version into the binaries. build-server is renamed build and writes
dist/github-ops-app, and the lambda binary is built with the lambda.norpc
tag for the provided.al2023 runtime.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rework the service around github-app-setup-go, update every dependency,
and fix the security and correctness problems a full review found.

Dependencies:
- go 1.24.4 to 1.27.3; go-github v79 to v93, okta-sdk-golang v6 to v8,
  slack-go v0.17.3 to v0.35.0, and every other module to its latest
- adopt github-app-setup-go v0.7.1, and drop cockroachdb/errors for the
  standard library, which removes sentry-go and its dependencies;
  govulncheck reported 11 reachable vulnerabilities before and none after

Structure:
- internal/service runs both entry points on a github-app-setup-go
  runtime: the server loads at startup and reloads on SIGHUP or when the
  installer saves credentials, and lambda loads on first use and retries
  a failed load on the next invocation, instead of failing every
  invocation until the container is recycled
- an optional manifest installer at /setup creates the GitHub App and
  saves its credentials to an env file or SSM; once an app is registered
  it refuses new codes
- internal/config validates everything at startup and reports every
  problem at once; any APP_ value may be an SSM ARN, in any partition
- the installation is looked up from the org when no id is set
- /healthz and /readyz; JSON responses; the scenarios in
  fixtures/scenarios.json run as go tests in e2e/, replacing cmd/verify
- the server waits for running syncs on shutdown; the image runs as a
  non-root user

Security:
- webhooks must be signed; the admin endpoints are disabled without
  APP_ADMIN_TOKEN, which is compared in constant time; EventBridge
  invocations run scheduled actions without it
- the app manifest is private, and events from other orgs are ignored
- slack text from GitHub and Okta is escaped, and okta usernames are
  validated as GitHub logins before they are used in API paths
- auth headers are sent only to the GitHub API host, and the installation
  token cache no longer races

Fixes:
- the manifest used permission names GitHub doesn't know and lacked
  administration and statuses read, so branch protection and commit
  statuses were silently skipped; read errors now fail the check
- PR compliance: read every page of reviews, statuses, and check runs;
  comments don't revoke approvals; neutral and skipped checks pass;
  maintainers are recognized; every bypass is reported, not only admins'
- security alerts: read every page, not only the first 100 of each kind
- okta sync: keep PASSWORD_EXPIRED, LOCKED_OUT, and RECOVERY users; honor
  create_team_if_missing; find teams whose slug differs from their name;
  don't re-invite pending invitees or remove child-team members, and
  don't report a 404 as a removal; report groups whose members can't be
  read; don't sync two groups into one team; retry rate-limited requests
- syncs requested while one runs are coalesced and the follow-up always
  runs; webhook-triggered syncs run only for teams a rule manages, only
  for renames among edits, and after the response in server mode
- orphaned users: reuse the synced member lists, and skip detection
  rather than report the members of a team that couldn't be found
- slack: fit reports to Slack's block and text limits, and time out
  requests
- lambda: pass query strings, cookies, and base64 bodies through from API
  Gateway; ping and unhandled events no longer return 400

BREAKING CHANGE: APP_DEBUG_ENABLED, APP_PORT, and APP_OKTA_KEY_ID are
replaced by APP_LOG_LEVEL, APP_LISTEN_ADDR, and APP_OKTA_PRIVATE_KEY_ID,
and setting an old name stops the service. The webhook secret is
required, the admin API is disabled without a token, invalid or partial
configuration stops the service, create_team_if_missing defaults to
false, and the GitHub App needs administration and commit statuses read.
See docs/upgrading-to-v1.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- README: the endpoints and who may call them, every variable with its
  default, the sync rule fields, sync safety, and how each feature works
- github app setup: the installer, the permissions the app needs and what
  each is for, and the optional installation id
- okta setup: make the github username attribute read only for users, the
  synced user statuses, rule validation, and create_team_if_missing
- slack setup: drop scopes the app doesn't use, and describe escaping,
  message limits, and the slack-test action
- lambda: the events, retried configuration loads, timeouts, and the
  installer with SSM storage
- AGENTS.md: the current packages, make targets, standard library errors,
  slog key-value pairs, configuration rules, and security rules

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sgtoj
sgtoj merged commit 21b85d5 into main Nov 7, 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