Commits

6ea11a965c385d245caa7604982cd441fb0b9fdd
Switch branches/tags

Commits on May 10, 2026

  1. ssrf: ValidateWithResolve + use it in webhook Create/Update (SR2 H3)
    Pre-fix: webhook.Create / Update called only validateURL (scheme
    check) and ssrf.Validate (scheme + port). Loopback / RFC1918 /
    CGNAT / multicast hosts were rejected only at delivery time inside
    dialContext — admin could persist a hook with http://localhost or
    http://192.168.1.1 and only see failures on the deliveries page
    after the first attempt. Disallowed ports were caught (port 9090
    fails Validate); IPs were not.
    
    Post-fix:
    - ssrf.ValidateWithResolve runs Validate plus a DNS lookup +
      IsForbiddenIP check on every resolved IP. IP literals are
      matched directly without DNS. AllowedHosts and
      AllowPrivateNetworks behave the same as in dialContext.
    - webhook.Create + Update call ValidateWithResolve. The plain
      Validate is left in place as the cheap syntactic gate.
    - dialContext keeps re-resolving as defense in depth (DNS
      rebinding) — the validate-resolve check is *not* a substitute.
    
    ssrf_create_test pins the table directly: 127.0.0.1, [::1],
    192.168.1.1, 10.0.0.1, 172.16.0.1, port 9090 — all rejected.
    A public host on a default port still passes.
    espadonne committed
  2. audit: webhook actions get their own enums + missing trails (SR2 H1+M4)
    Pre-fix: webhook create/update overloaded ActionRepoCreated with
    a meta.action discriminator. Webhook delete/toggle/ping/redeliver
    were not audited at all — admin had no forensic record of who
    disabled, deleted, or replayed a hook.
    
    Post-fix:
    - New audit actions: ActionWebhook{Created,Updated,Deleted,
      ActiveSet,ActiveUnset,Pinged,Redelivered}.
    - All 7 webhook handlers now audit, all attributed via
      viewer.AuditActor so impersonation trails carry.
    - Bonus: ActionAdminRepoForceUnarchived added in preparation for
      SR2 H8 (split repoForceArchive into archive/unarchive).
    espadonne committed
  3. orgs: deny suspended actors on org/team mutations (SR2 C4)
    Pre-fix: S30/S31 wired org/team handlers to call orgs.IsOwner
    directly without going through policy.Can. A suspended user listed
    as an org owner could still invite, change roles, remove members,
    accept invitations, create teams, add team members, and grant
    team→repo access — bypassing the suspension gate every other
    mutation surface enforces. Same shape as SR1 C1, new surface.
    
    Lighter fix per SR2 sprint plan: short-circuit on viewer.IsSuspended
    at every point of entry. Heavier fix (real policy.Can actions for
    ActionOrgInvite/ActionTeamMemberAdd/...) is correct architecturally
    but is a larger refactor; left as forward-link.
    
    Gated:
    - requireOrgOwner — covers teamCreate, teamMemberAddRemove,
      teamRepoGrant via the existing helper.
    - invite — direct IsOwner caller, gated inline.
    - memberMutate — direct IsOwner caller, gated inline (covers
      changeRole + removeMember).
    - invitationAction — accept/decline now denies suspended users.
    
    Read-only IsOwner callers (teamsList, teamView, canSeeTeam,
    filterSecretTeams) intentionally NOT gated — suspension blocks
    writes, not reads (consistent with SR1).
    espadonne committed
  4. admin: actually send password reset emails (SR2 C3 + L6)
    Pre-fix: userResetPassword minted a token, persisted password_resets,
    audited ActionAdminUserPasswordReset, then `_ = tokEnc` discarded
    the token. admin.Deps had no email.Sender at all, so the comment
    'surfaced via the email path' was false from the start. The audit
    row claimed 'sent'; the user never received anything.
    
    Post-fix:
    - admin.Deps adds Email + Branding fields, mirroring auth.Deps.
    - admin_wiring.buildAdminHandlers takes the same email.Sender
      the auth surface uses (server.go threads pickEmailSender(cfg)
      through both paths).
    - userResetPassword calls email.ResetMessage + sender.Send. The
      audit row now carries email_sent: bool plus email_error: string
      on failure, so a stuck mailbox is visible in /admin/audit instead
      of silently misleading.
    - L6: server.go now passes version.Version into buildAdminHandlers
      instead of the literal 'dev', so /admin/system reflects the
      ld-flag-stamped build version.
    
    admin_deps_test pins the field contract — removing Email or
    Branding breaks compile.
    espadonne committed
  5. audit: thread RealActorID through repo settings + webhook recordings (SR2 H2)
    Substitutes (viewer.ID, raw_meta) with viewer.AuditActor(raw_meta)
    at every non-admin Audit.Record callsite in repo/settings_*.go and
    repo/webhooks.go. During an impersonated session the row is now
    attributed to the real admin and the impersonated user_id lands in
    meta — uniform with the existing /admin/* trail.
    
    admin/helpers.recordAdminAction also moves to AuditActor for one
    canonical substitution point.
    espadonne committed
  6. web: migrate UserActor callsites to viewer.PolicyActor (SR2 C1/C2)
    18 handler sites switched from policy.UserActor(viewer.ID,
    viewer.Username, viewer.IsSuspended, false) to viewer.PolicyActor().
    Includes profile, search, and repo (issues/PRs/lifecycle/social/
    fork/labels/repo home) surfaces.
    
    Pre-migration these all hardcoded IsSiteAdmin=false and ignored
    viewer.ImpersonatedUserID. Post-migration impersonation is a
    construct-time concern and admin-read-private gets a 200 instead
    of 404.
    
    PAT-bearing API handlers (api/checks.go, api/stars.go) and the
    SSH/HTTPS-git paths keep plain UserActor() — those gates reject
    suspended at credential check, and impersonation is impossible via
    non-cookie auth, so the false literals are correct by construction.
    espadonne committed
  7. policy: UserActorFromCurrentUser propagates impersonation + admin (SR2 C1/C2)
    Adds the canonical web-layer Actor constructor that propagates
    IsSuspended, IsSiteAdmin, Impersonating, ImpersonateWriteOK from
    the request-bound CurrentUser. Plain UserActor() left intact for
    SSH/HTTP-git/worker callers that don't have a CurrentUser handy.
    
    CurrentUser gains PolicyActor() and AuditActor(meta) helpers so
    non-admin handlers can build actors and record audit rows with
    uniform impersonation handling (SR2 H2).
    
    actor_test pins:
    - IsSuspended propagates (regression for SR1 C1)
    - IsSiteAdmin propagates (regression for SR2 C2)
    - Impersonating fires when ImpersonatedUserID != 0 (SR2 C1)
    - ImpersonateWriteOK is honored (read-only-by-default guarantee)
    - IsSiteAdmin stays false on the impersonated identity
    espadonne committed
  8. Merge pull request #43 from tenseleyFlow/fix/metrics-disable-promhttp-gzip
    metrics: DisableCompression on promhttp.HandlerOpts + pin in tests
    Matthew Forrester Wolffe committed
  9. espadonne committed
  10. Merge pull request #42 from tenseleyFlow/fix/aide-rebaseline-runbook
    docs(aide): re-baseline via aide --init, not aideinit (interactive trap)
    Matthew Forrester Wolffe committed
  11. Matthew Forrester Wolffe committed
  12. Merge pull request #40 from tenseleyFlow/chore/droplet-drift-audit-script
    audit: read-only droplet-drift checker (issue #38)
    Matthew Forrester Wolffe committed
  13. espadonne committed
  14. espadonne committed
  15. espadonne committed
  16. espadonne committed
  17. audit: read-only droplet-drift checker (issue #38)
    Compares md5 of every file the ansible roles install against the
    live droplet over a single ssh round-trip. TEMPLATE rows (those
    rendered from .j2 with inventory vars) are reported with stat
    info but not auto-diffed.
    
    Run after any PR that touches deploy/ansible/ to surface what
    needs to be pushed manually until we resolve the broader ansible
    ownership question (issue #38).
    espadonne committed
  18. espadonne committed
  19. espadonne committed
  20. Merge pull request #37 from tenseleyFlow/docs/env-backup-runbook
    docs(runbook): operator backup of /etc/shithub/web.env
    Matthew Forrester Wolffe committed
  21. Merge pull request #36 from tenseleyFlow/chore/db-migration-runbook
    docs(runbook): pgdata migration from root disk to block volume
    Matthew Forrester Wolffe committed
  22. Matthew Forrester Wolffe committed