Address CR2 review: add OrgSlugAuth + OrgIdAuth securityDefinitions
CI / Canvas Deploy Reminder (pull_request) Blocked by required conditions
Lint shellcheck (arm64 pilot) / shellcheck-arm64 (pilot) (pull_request) Waiting to run
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 4s
CI / Detect changes (pull_request) Successful in 12s
CI / Python Lint & Test (pull_request) Successful in 11s
E2E API Smoke Test / detect-changes (pull_request) Successful in 6s
E2E Chat / detect-changes (pull_request) Successful in 8s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 12s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 10s
Harness Replays / detect-changes (pull_request) Successful in 11s
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (pull_request) Successful in 4s
Lint no tenant GITEA or GITHUB token write / Scan for repo-host token write into tenant workspace surface (pull_request) Successful in 5s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 5s
gate-check-v3 / gate-check (pull_request) Failing after 7s
qa-review / approved (pull_request) Failing after 5s
sop-checklist / na-declarations (pull_request) N/A: (none)
sop-checklist / all-items-acked (pull_request) Successful in 9s
security-review / approved (pull_request) Failing after 10s
sop-checklist / review-refire (pull_request) Has been skipped
sop-tier-check / tier-check (pull_request) Successful in 5s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 1m8s
CI / Platform (Go) (pull_request) Failing after 2m6s
CI / Canvas (Next.js) (pull_request) Successful in 5s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 4s
CI / all-required (pull_request) Failing after 9m0s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 2s
E2E Chat / E2E Chat (pull_request) Successful in 11s
Harness Replays / Harness Replays (pull_request) Successful in 11s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 2m17s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 2m24s

CR2 (5-axis review) flagged: the original PR mentioned X-Molecule-Org-Slug
and X-Molecule-Org-Id only in the BearerAuth @description prose. Generated
SDK clients (per RFC #1706 Phases 3-6) wouldn't actually send those
headers, so any client built from the spec would 401/403 against prod.

Fix:
- Add OrgSlugAuth + OrgIdAuth as formal apikey securityDefinitions in
  cmd/server/main.go
- Bind every annotated endpoint with `@Security BearerAuth && OrgSlugAuth`
  (AND semantics — generated clients send both headers, not OR)
- Regenerate docs/openapi/swagger.{yaml,json}

Verified: swagger.yaml security blocks now show
  - BearerAuth: []
    OrgSlugAuth: []
(single requirement object with both schemes = AND in OpenAPI 2.0).

No backend behavior change. Handler tests pass.

Refs: PR #1707, RFC #1706, CR2 review comment.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-22 23:51:54 -07:00
parent 665cec1991
commit f01636cd76
4 changed files with 64 additions and 16 deletions
+11 -1
View File
@@ -10,7 +10,17 @@
// @securityDefinitions.apikey BearerAuth
// @in header
// @name Authorization
// @description Bearer token. The platform also reads X-Molecule-Org-Slug or X-Molecule-Org-Id for tenant routing.
// @description Bearer token issued by Gitea (org-admin or persona PAT) or by the platform's signup/SSO flow.
//
// @securityDefinitions.apikey OrgSlugAuth
// @in header
// @name X-Molecule-Org-Slug
// @description Tenant routing header — required on every /workspaces/{id}/* request so the platform edge can route to the correct per-tenant workspace-server. Either X-Molecule-Org-Slug (human-readable, e.g. "agents-team") or X-Molecule-Org-Id (UUID) must be sent; slug is preferred for client code.
//
// @securityDefinitions.apikey OrgIdAuth
// @in header
// @name X-Molecule-Org-Id
// @description Tenant routing header (UUID form). Alternative to X-Molecule-Org-Slug. At least one of OrgSlugAuth or OrgIdAuth must be sent alongside BearerAuth.
package main
import (
+25 -7
View File
@@ -16,7 +16,8 @@
"get": {
"security": [
{
"BearerAuth": []
"BearerAuth": [],
"OrgSlugAuth": []
}
],
"produces": [
@@ -56,7 +57,8 @@
"post": {
"security": [
{
"BearerAuth": []
"BearerAuth": [],
"OrgSlugAuth": []
}
],
"consumes": [
@@ -113,7 +115,8 @@
"delete": {
"security": [
{
"BearerAuth": []
"BearerAuth": [],
"OrgSlugAuth": []
}
],
"produces": [
@@ -163,7 +166,8 @@
"patch": {
"security": [
{
"BearerAuth": []
"BearerAuth": [],
"OrgSlugAuth": []
}
],
"consumes": [
@@ -233,7 +237,8 @@
"get": {
"security": [
{
"BearerAuth": []
"BearerAuth": [],
"OrgSlugAuth": []
}
],
"produces": [
@@ -282,7 +287,8 @@
"post": {
"security": [
{
"BearerAuth": []
"BearerAuth": [],
"OrgSlugAuth": []
}
],
"produces": [
@@ -494,10 +500,22 @@
},
"securityDefinitions": {
"BearerAuth": {
"description": "Bearer token. The platform also reads X-Molecule-Org-Slug or X-Molecule-Org-Id for tenant routing.",
"description": "Bearer token issued by Gitea (org-admin or persona PAT) or by the platform's signup/SSO flow.",
"type": "apiKey",
"name": "Authorization",
"in": "header"
},
"OrgIdAuth": {
"description": "Tenant routing header (UUID form). Alternative to X-Molecule-Org-Slug. At least one of OrgSlugAuth or OrgIdAuth must be sent alongside BearerAuth.",
"type": "apiKey",
"name": "X-Molecule-Org-Id",
"in": "header"
},
"OrgSlugAuth": {
"description": "Tenant routing header — required on every /workspaces/{id}/* request so the platform edge can route to the correct per-tenant workspace-server. Either X-Molecule-Org-Slug (human-readable, e.g. \"agents-team\") or X-Molecule-Org-Id (UUID) must be sent; slug is preferred for client code.",
"type": "apiKey",
"name": "X-Molecule-Org-Slug",
"in": "header"
}
}
}
+22 -2
View File
@@ -138,6 +138,7 @@ paths:
$ref: '#/definitions/handlers.errorResponse'
security:
- BearerAuth: []
OrgSlugAuth: []
summary: List schedules for a workspace
tags:
- schedules
@@ -173,6 +174,7 @@ paths:
$ref: '#/definitions/handlers.errorResponse'
security:
- BearerAuth: []
OrgSlugAuth: []
summary: Create a schedule
tags:
- schedules
@@ -206,6 +208,7 @@ paths:
$ref: '#/definitions/handlers.errorResponse'
security:
- BearerAuth: []
OrgSlugAuth: []
summary: Delete a schedule
tags:
- schedules
@@ -250,6 +253,7 @@ paths:
$ref: '#/definitions/handlers.errorResponse'
security:
- BearerAuth: []
OrgSlugAuth: []
summary: Update a schedule
tags:
- schedules
@@ -281,6 +285,7 @@ paths:
$ref: '#/definitions/handlers.errorResponse'
security:
- BearerAuth: []
OrgSlugAuth: []
summary: Get past runs of a schedule
tags:
- schedules
@@ -314,6 +319,7 @@ paths:
$ref: '#/definitions/handlers.errorResponse'
security:
- BearerAuth: []
OrgSlugAuth: []
summary: Fire a schedule manually
tags:
- schedules
@@ -321,9 +327,23 @@ schemes:
- https
securityDefinitions:
BearerAuth:
description: Bearer token. The platform also reads X-Molecule-Org-Slug or X-Molecule-Org-Id
for tenant routing.
description: Bearer token issued by Gitea (org-admin or persona PAT) or by the
platform's signup/SSO flow.
in: header
name: Authorization
type: apiKey
OrgIdAuth:
description: Tenant routing header (UUID form). Alternative to X-Molecule-Org-Slug.
At least one of OrgSlugAuth or OrgIdAuth must be sent alongside BearerAuth.
in: header
name: X-Molecule-Org-Id
type: apiKey
OrgSlugAuth:
description: Tenant routing header — required on every /workspaces/{id}/* request
so the platform edge can route to the correct per-tenant workspace-server. Either
X-Molecule-Org-Slug (human-readable, e.g. "agents-team") or X-Molecule-Org-Id
(UUID) must be sent; slug is preferred for client code.
in: header
name: X-Molecule-Org-Slug
type: apiKey
swagger: "2.0"
@@ -81,7 +81,7 @@ type scheduleResponse struct {
// @Success 200 {array} scheduleResponse
// @Failure 500 {object} errorResponse
// @Router /workspaces/{id}/schedules [get]
// @Security BearerAuth
// @Security BearerAuth && OrgSlugAuth
func (h *ScheduleHandler) List(c *gin.Context) {
workspaceID := c.Param("id")
ctx := c.Request.Context()
@@ -140,7 +140,7 @@ type createScheduleRequest struct {
// @Failure 400 {object} errorResponse
// @Failure 500 {object} errorResponse
// @Router /workspaces/{id}/schedules [post]
// @Security BearerAuth
// @Security BearerAuth && OrgSlugAuth
func (h *ScheduleHandler) Create(c *gin.Context) {
workspaceID := c.Param("id")
ctx := c.Request.Context()
@@ -222,7 +222,7 @@ type updateScheduleRequest struct {
// @Failure 404 {object} errorResponse
// @Failure 500 {object} errorResponse
// @Router /workspaces/{id}/schedules/{scheduleId} [patch]
// @Security BearerAuth
// @Security BearerAuth && OrgSlugAuth
func (h *ScheduleHandler) Update(c *gin.Context) {
scheduleID := c.Param("scheduleId")
workspaceID := c.Param("id") // #113: bind to owning workspace to prevent IDOR
@@ -308,7 +308,7 @@ func (h *ScheduleHandler) Update(c *gin.Context) {
// @Failure 404 {object} errorResponse
// @Failure 500 {object} errorResponse
// @Router /workspaces/{id}/schedules/{scheduleId} [delete]
// @Security BearerAuth
// @Security BearerAuth && OrgSlugAuth
func (h *ScheduleHandler) Delete(c *gin.Context) {
scheduleID := c.Param("scheduleId")
workspaceID := c.Param("id") // #113: bind to owning workspace to prevent IDOR
@@ -341,7 +341,7 @@ func (h *ScheduleHandler) Delete(c *gin.Context) {
// @Failure 404 {object} errorResponse
// @Failure 500 {object} errorResponse
// @Router /workspaces/{id}/schedules/{scheduleId}/run [post]
// @Security BearerAuth
// @Security BearerAuth && OrgSlugAuth
func (h *ScheduleHandler) RunNow(c *gin.Context) {
scheduleID := c.Param("scheduleId")
workspaceID := c.Param("id")
@@ -381,7 +381,7 @@ func (h *ScheduleHandler) RunNow(c *gin.Context) {
// @Success 200 {array} historyEntry
// @Failure 500 {object} errorResponse
// @Router /workspaces/{id}/schedules/{scheduleId}/history [get]
// @Security BearerAuth
// @Security BearerAuth && OrgSlugAuth
func (h *ScheduleHandler) History(c *gin.Context) {
scheduleID := c.Param("scheduleId")
workspaceID := c.Param("id")