From 5e30bc6be2c816601f78796b2fcd97e80f3236e2 Mon Sep 17 00:00:00 2001 From: Adam Moussa <166072409+amoussa1229@users.noreply.github.com> Date: Sat, 27 Jun 2026 19:29:03 -0400 Subject: [PATCH] Point dev agent at seahaven-open-swe-dev org + un-pin the owner guard (#27) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dev now runs against the dedicated seahaven-open-swe-dev org (repo openswe-dev-sandbox), isolated from the real Sea Haven org. Two changes: - config-store.ts: iacManagedSsm repo targeting is now per-env — dev = seahaven-open-swe-dev/openswe-dev-sandbox, prod stays Sea-Haven-Industries/open-swe-pilot. ALLOWED_GITHUB_ORGS tracks the env owner. Adds a dev-only SEED_USER_MAPPINGS param so the triggering GitHub login (amoussa1229) resolves and @openswe comments aren't skipped. - fetch-config.sh: replace the unconditional hard-pin to Sea-Haven-Industries with an owner GUARD that HONORS the configured owner (OPENSWE_REPO_OWNER override, else the SSM value) but forces a per-env safe org when the normalized owner is blank or the upstream langchain-ai. Normalization (lowercase, strip whitespace, first path segment, drop dots) catches langchain-ai/, langchain-ai., and case variants without over-blocking legit orgs (e.g. langchain-ai-fork). Fallback org is per-env so dev can't fall back into the real org. Reviews: GPT-4.1 cross-review APPROVE (round 1 found a path/dot bypass -> hardened, round 2 clean); /sh-security-review authz one LOW (env-invariant fallback) -> fixed. Positive org allowlist still enforced by the app via ALLOWED_GITHUB_ORGS. --- deploy/seahaven/fetch-config.sh | 52 ++++++++++++++++++++-------- infra/lib/constructs/config-store.ts | 28 ++++++++++----- 2 files changed, 58 insertions(+), 22 deletions(-) diff --git a/deploy/seahaven/fetch-config.sh b/deploy/seahaven/fetch-config.sh index f350d5c4..e029ed5b 100755 --- a/deploy/seahaven/fetch-config.sh +++ b/deploy/seahaven/fetch-config.sh @@ -80,10 +80,20 @@ APP_ENV_LINK="${APP_DIR}/.env" # symlink -> ENV_FILE SERVICE_USER="${SERVICE_USER:-openswe}" SERVICE_GROUP="${SERVICE_GROUP:-${SERVICE_USER}}" -# Sea Haven override: upstream defaults DEFAULT_REPO_OWNER to "langchain-ai". -# For the Sea Haven deployment it MUST be the org. fetch-config forces this so a -# stale/blank SSM value can never point the agent at the upstream org. -SH_REPO_OWNER="${OPENSWE_REPO_OWNER:-Sea-Haven-Industries}" +# Sea Haven owner guard inputs (applied after the store is read, below). The owner +# normally comes from SSM /open-swe-/DEFAULT_REPO_OWNER; OPENSWE_REPO_OWNER is an +# explicit operator override that wins over the store. FORBIDDEN = the upstream org +# the fork must never target; SAFE = the fallback when the resolved owner is blank or +# forbidden. (Upper/lower + whitespace are normalized before the guard check.) +OPENSWE_REPO_OWNER="${OPENSWE_REPO_OWNER:-}" +FORBIDDEN_REPO_OWNER="langchain-ai" +# Fallback org when the resolved owner is blank/forbidden — PER-ENV (mirrors the +# iacManagedSsm owner) so a dev box can NEVER fall back into the real Sea Haven org; +# it stays isolated in its own dev org. Defends the blank/upstream cases in-env. +case "$ENV" in + dev) SAFE_REPO_OWNER="seahaven-open-swe-dev" ;; + *) SAFE_REPO_OWNER="Sea-Haven-Industries" ;; +esac for bin in aws jq; do command -v "$bin" >/dev/null 2>&1 || { echo "fetch-config: '$bin' not found on PATH" >&2; exit 3; } @@ -191,16 +201,30 @@ while IFS=$'\t' read -r nb vb; do done < <(batch_get_secrets_tsv) log "loaded ${secret_count} secret(s) from Secrets Manager" -# --- Sea Haven DEFAULT_REPO_OWNER hard pin ----------------------------------- -# T5 OSWE-OWNER-04: a HARD pin, not a deny-list. Whatever the store holds (blank, -# upstream 'langchain-ai', a case/space variant, or any other org), the owner is -# unconditionally forced to the Sea Haven org so the agent can never target the -# wrong owner. -cur_owner="${VARS[DEFAULT_REPO_OWNER]:-}" -if [ -n "$cur_owner" ] && [ "$cur_owner" != "$SH_REPO_OWNER" ]; then - log "WARNING: SSM DEFAULT_REPO_OWNER differs from the pinned org -> hard-pinning to '${SH_REPO_OWNER}'" -fi -VARS[DEFAULT_REPO_OWNER]="$SH_REPO_OWNER" +# --- Sea Haven DEFAULT_REPO_OWNER guard -------------------------------------- +# OSWE-OWNER-04 (revised for multi-org): HONOR the configured owner — the +# OPENSWE_REPO_OWNER env override if set, else the store value — so per-env orgs +# work (dev = seahaven-open-swe-dev, prod = Sea-Haven-Industries). But GUARD the two +# values that must NEVER reach the agent: blank, and the upstream 'langchain-ai' org +# (the fork's origin). Either falls back to the Sea Haven org so a stale/blank/mis-set +# value can never point the agent upstream. Comparison is case- and whitespace- +# insensitive. The POSITIVE org allowlist is enforced by the app (ALLOWED_GITHUB_ORGS). +resolved_owner="${OPENSWE_REPO_OWNER:-${VARS[DEFAULT_REPO_OWNER]:-}}" +# Normalize for the guard CHECK ONLY (the original value is what gets stored when +# allowed): lowercase, strip whitespace, take the FIRST path segment so a value like +# 'langchain-ai/open-swe' still trips the guard, and drop dots (GitHub owners contain +# none) so 'langchain-ai.' can't slip past. Homoglyph/unicode variants are out of scope +# here — the owner comes from admin-written SSM/IaC, not attacker-controlled input. +norm_owner="$(printf '%s' "$resolved_owner" | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]')" +norm_owner="${norm_owner%%/*}" +norm_owner="${norm_owner//./}" +case "$norm_owner" in + "" | "$FORBIDDEN_REPO_OWNER") + log "WARNING: DEFAULT_REPO_OWNER ('${resolved_owner:-}') is blank or the upstream org -> forcing '${SAFE_REPO_OWNER}'" + resolved_owner="$SAFE_REPO_OWNER" + ;; +esac +VARS[DEFAULT_REPO_OWNER]="$resolved_owner" # --- FAIL-FAST: required vars ------------------------------------------------- # Hard-required regardless of mode: diff --git a/infra/lib/constructs/config-store.ts b/infra/lib/constructs/config-store.ts index b07fb0dd..2f759d48 100644 --- a/infra/lib/constructs/config-store.ts +++ b/infra/lib/constructs/config-store.ts @@ -123,16 +123,21 @@ export function iacManagedSsm(env: EnvName): Record { // recon: seahaven.com Route53 zone + *.seahaven.com ACM cert are live on the ALB // — distinct from the on-prem seahavenind.com). dev = openswe-dev, prod = openswe. const host = `https://openswe${env === "dev" ? "-dev" : ""}.seahaven.com`; - return { + // Repo targeting is PER-ENV: dev drives the disposable sandbox repo in the + // dedicated seahaven-open-swe-dev org (isolates dev-agent activity from the real + // Sea Haven org); prod stays on the Sea Haven org pilot repo. fetch-config's owner + // GUARD honors this value (rejecting only blank / the upstream langchain-ai org). + const repo = + env === "dev" + ? { owner: "seahaven-open-swe-dev", name: "openswe-dev-sandbox" } + : { owner: "Sea-Haven-Industries", name: "open-swe-pilot" }; + const managed: Record = { // Sandbox provider — plan keeps stock langsmith (T9). Stable. SANDBOX_TYPE: "langsmith", - // Sea Haven org pin. fetch-config ALSO hard-pins this at boot, but owning the - // real value here keeps SSM self-consistent rather than blank. - DEFAULT_REPO_OWNER: "Sea-Haven-Industries", - // Repo allowlist (comma list) — the Sea Haven org. Stable/derivable. - ALLOWED_GITHUB_ORGS: "Sea-Haven-Industries", - // Pilot repo (project memory: Sea-Haven-Industries/open-swe-pilot). - DEFAULT_REPO_NAME: "open-swe-pilot", + DEFAULT_REPO_OWNER: repo.owner, + // Repo-owner allowlist (comma list) — the env's org. The app gates triggers on this. + ALLOWED_GITHUB_ORGS: repo.owner, + DEFAULT_REPO_NAME: repo.name, // Dashboard URLs — derived from the public host. DASHBOARD_BASE_URL: host, DASHBOARD_API_BASE_URL: host, @@ -140,6 +145,13 @@ export function iacManagedSsm(env: EnvName): Record { // Primary builder model (project memory team_settings: anthropic:claude-opus-4-8). LLM_MODEL_ID: "anthropic:claude-opus-4-8", }; + // Dev e2e smoke: seed the owner's user_mapping so an @openswe comment from the + // triggering GitHub login resolves (an unmapped commenter is silently skipped). + // Dev-only — prod seeds its mappings via its own operator config. login:email. + if (env === "dev") { + managed.SEED_USER_MAPPINGS = "amoussa1229:adam@seahavenind.com"; + } + return managed; } /**