Repository navigation
Fix critical deployment and security issues from 2026-09 codebase review - #27
Merged
Merged
Conversation
- Install git-core in the runtime image: internal/gitcmd shells out to git for merge, rebase, and every carry-patch operation, and ubi-minimal ships without it, so those features failed with "failed to run git --version". - Point XDG_CONFIG_HOME/XDG_DATA_HOME at /config/af-hub and /data/af-hub so the bundled config.toml is found by default and the image matches deploy/deployment.yaml. Adjust `make hub-runc` mounts and the docs. - Exempt /git/ routes from apikit's instance-wide request body limit via a Pre middleware that stashes the original body, and stream the receive-pack body instead of buffering it. A push larger than max_body_size (1MB by default) was rejected with 413 before reaching the git server. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gsa6jQ2Du2qW3PX2HFc88o
Authorization: - Add workspace.AuthorizeWorkspace and apply the owner-or-admin rule (404 for non-owners, matching workspace CRUD) to sync, reclone, all patch endpoints, every carry-patch endpoint (rebuild submit/list/get/cancel/ requeue/rollback, preview, patch-status, rerere list/forget, the sync hook), and every merge endpoint (submit/list/get/cancel/requeue, batch rebase). Any authenticated API key could previously act on any workspace. - Audit: POST /sessions requires access to the target workspace and no longer returns another tenant's session on an id collision; per-run read endpoints and the transcript require workspace ownership; GET /audit and the SSE stream restrict non-admin callers to workspaces they own. - Git server: reject credentials of blocked users, as apikit's REST middleware already does. - Secrets/vars: admin tokens are rejected on user-scoped endpoints instead of operating on a phantom empty-owner scope; the resolved-vars endpoint only includes the org tier when the workspace owner is an org member. Input hardening: - gitcmd: pass --end-of-options (or "--") in every typed helper, make RevParse use --verify, add ValidateRefName, and redact URL userinfo in GitError messages. A ref such as "--detach" or "--exec=<cmd>" previously passed the branch-exists check and reached git checkout/rebase as an option. - Validate ref names at the API boundary: merge target/source, batch rebase refs, patch branch names, and integration_branch. - Merge CHECK_COMMAND no longer inherits the hub process environment. - afc credential-helper only answers for the hub's own scheme and host:port, and honours ENDPOINT_URL/API_KEY like every other command. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gsa6jQ2Du2qW3PX2HFc88o
…trunk Upstream base: - Fetch the upstream remote via go-git with the workspace's upstream credentials (the git CLI fetch had none, so private upstreams failed) and record the upstream default branch under refs/remotes/upstream/HEAD. Rebuild, sync, and preview now resolve that ref as the base; FETCH_HEAD after `git fetch upstream` is the alphabetically first branch of the remote, so multi-branch upstreams were rebased onto the wrong base, and preview used the trunk's local HEAD. - Post-clone setup fetches upstream so the integration branch starts at the upstream base. Rebuild executor: - Clear stale state (in-progress cherry-pick/merge, dirty tree, leftover temporary branch) before starting, create the temp branch with -B, and restore the originally checked-out branch afterwards instead of leaving the trunk on a detached HEAD. - Replay only non-merge commits whose content is not already upstream (--no-merges --right-only --cherry-pick) and skip cherry-picks that become empty instead of treating them as conflicts. - Capture REBUILD_FAIL_MODE (not just the strategy) at enqueue time for auto-rebuilds triggered by sync and push; match the squash-merge PR number only as a subject suffix so reverts do not mark patches merged. - Opt-in REBUILD_PUSH_INTEGRATION_BRANCH=true pushes the rebuilt integration branch to origin. Merge jobs: - Check out the target branch before deleting the source branch; the job deleted the checked-out branch, leaving HEAD dangling and breaking every subsequent git operation on the workspace. Restore the original checkout on exit, reset in-progress state before rebasing, abort a rebase that a cancelled context interrupted, and fetch with the job context. - Surface CHECK_COMMAND output in the job record, fail closed when the variable store errors, and filter the merge list by workspace in SQL. Concurrency and data safety: - Add internal/wslock, a per-workspace lock held by rebuild and merge jobs and tried by sync, reclone, archive, rollback, rerere forget, batch rebase, and the git push worktree reset (409 workspace_busy for handlers). - Claim sync_status='syncing' with a conditional UPDATE. - Archive pushes local branches to origin with the stored credentials and keeps the clone when the push fails; reclone pushes with credentials. Archiving previously deleted the clone without pushing, losing branches that only existed on the hub. - Park soft-deleted patches at unique negative positions so compaction after a rebuild no longer violates UNIQUE(workspace_slug, position). - Rerere: list rr-cache entries by id with a resolved flag (git does not record paths in the cache, so path-based listing was always empty) and allow forgetting by id. - Job progress updates only apply to running jobs. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gsa6jQ2Du2qW3PX2HFc88o
- Scan agent_sessions.metadata into an untyped value and decode it from
either JSON text or the already-decoded map go-duckdb returns for JSON
columns; previously every session created with metadata failed to read
back ("unsupported Scan ... map[string]interface {} into *string").
- Populate hub_audit_events.timestamp from the emitter (microsecond UTC)
and normalise both sources of the unified audit query to epoch
microseconds so hub and agent events order and paginate consistently;
legacy rows with an empty timestamp fall back to ingested_at.
- Validate client-supplied timestamps on all ingestion endpoints
(events, outcomes, tool calls/errors, traces, postmortems, batches)
and cursors, returning 400 instead of a DuckDB conversion 500; values
are normalised to UTC.
- Apply the session_id filter in QueryAuditEvents/QuerySessionOutcomes.
- Retention: step 1 ages session_outcomes/tool_calls/tool_errors along
with agent_audit_events; step 8 no longer purges workspace-less hub
events as orphans; env overrides below 1 fall back to defaults.
- Wire afhub_sse_connections and afhub_audit_events_total through a
package-level metrics sink (audit.SetMetrics) used by the SSE manager,
emitter and ingestion handlers.
- Unified query tests now run against the production schema.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gsa6jQ2Du2qW3PX2HFc88o
gitcmd: - Run git in its own process group and kill the whole group on context cancellation (with a bounded WaitDelay) so hung helpers cannot pin a workspace lock; contexts without a deadline get DefaultTimeout (10m). - Strip repository-redirecting and config-injecting variables (GIT_DIR, GIT_WORK_TREE, GIT_INDEX_FILE, GIT_CONFIG_PARAMETERS, ...) from the inherited environment, force LC_ALL=C for parseable output, disable the editor/pager, and default the author/committer identity to af-hub <af-hub@localhost> when none is configured. Without an identity git refuses to commit inside the container, which broke carry-patch rebuilds and merge jobs. afc: - --wait (and the new `merge wait` / `rebuild wait` subcommands) poll immediately and exit non-zero when a job ends failed, dead_letter or cancelled, or a clone ends failed; the final record is still printed. - Every user-supplied path segment (slugs, keys, ids, rerere entries) is percent-encoded before it is put in a request path. - `secrets create` / `vars create` accept several KEY=VALUE arguments; create and update accept `--from-stdin` so values stay out of shell history; `credential set --from-stdin` reads the PAT or password. - `credential set` requires username and password together. - `patch update` rejects a call without any change flag. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gsa6jQ2Du2qW3PX2HFc88o
…anges - api.md / openapi.yaml / permissions.md: ownership is enforced on every workspace-scoped endpoint (404 for non-owners), workspace_busy error type and the per-workspace lock, archive push-before-delete, rerere entries by id with `resolved`, patch status=deleted rejected, rebuild job `upstream_head_sha` / `integration_branch_pushed`, audit timestamp validation and per-owner audit visibility, admin tokens on /user/*, org-tier variable resolution, and the CHECK_COMMAND sensitivity of vars:write. - cli.md: exit codes for --wait, `merge wait` / `rebuild wait`, `--from-stdin`, multiple KEY=VALUE arguments, credential pairing, credential helper environment and scheme matching, path encoding. - carry_patch_workflow.md: upstream base resolution, executor recovery, REBUILD_PUSH_INTEGRATION_BRANCH, rerere ids, updated limitations. - configuration.md: git identity defaults and hardened git environment. - New: docs/review_2026_09_codebase.md (findings, fixes, follow-ups), docs/apikit_issues.md (issue drafts for txsvc/apikit), errata for rerere-by-id and ownership enforcement. - jobqueue: goroutine-count tests wait for the expected delta instead of sampling the noisy process-wide count once. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gsa6jQ2Du2qW3PX2HFc88o
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR addresses critical findings from a comprehensive codebase review conducted in September 2026. The review identified deployment blockers, security vulnerabilities, carry-patch correctness issues, audit subsystem gaps, and git subprocess handling problems across the hub.
Summary of Changes
This is a large, multi-faceted fix addressing 5 critical, 27 high, 28 medium, and 8 low severity issues organized into five logical commit groups:
Deployment Blockers
gitbinary andlibstdc++to runtime image (was completely missing, breaking all git operations)Authorization & Injection
ValidateRefName()to prevent git option injection via branch/tag namesGIT_DIR,GIT_WORK_TREE,GIT_CONFIG_PARAMETERS) and defaults identityCarry-Patch Correctness
upstreampackage ensures rebuilds, syncs, and previews all use the same upstream default branch--end-of-optionsto prevent commit SHAs from being parsed as optionsFetchandResolveAuthsignatures to properly threadtransport.AuthMethodthrough rebuild pipelinewslockpackage provides per-workspace mutual exclusion for trunk mutationsAudit Subsystem
timestampfield to hub events; unified query now sorts by timestamp + event IDNormalizeTimestamp()to validate and normalize RFC 3339 timestamps from clientssession_idwhen providedGit & CLI Robustness
merge waitandrebuild waitsubcommands; fixed polling logic--poll-interval; exit 0 only on completion, non-zero on failurecheckout,branch,merge,cherry,cherry-pick,merge-base,configoperationsNotable Implementation Details
workspace.AuthorizeWorkspace()helper that returns workspace state for precondition checkshttps://user:token@host/...patternshttps://claude.ai/code/session_01Gsa6jQ2Du2qW3PX2HFc88o