Skip to content

fix(deploy)+feat(auth): self-host first-run bootstrap, anonymous demo, and guided OIDC/SSO setup - #4335

Merged
agent-bom merged 7 commits into
mainfrom
fix/self-host-secret-bootstrap
Jul 21, 2026
Merged

fix(deploy)+feat(auth): self-host first-run bootstrap, anonymous demo, and guided OIDC/SSO setup#4335
agent-bom merged 7 commits into
mainfrom
fix/self-host-secret-bootstrap

Conversation

@msaad00

@msaad00 msaad00 commented Jul 21, 2026

Copy link
Copy Markdown
Owner

Two related self-host improvements surfaced by dogfooding a fresh docker compose up, combined into one PR.

Part 1 — first-run bootstrap fixes (every item reproduced on a fresh box)

  1. Secrets written unreadable by container users. Preflight wrote deploy/secrets/* as 0400 root. Compose (non-swarm) bind-mounts them into /run/secrets/* preserving host perms (the mode/uid/gid long-syntax fields are swarm-only), so the non-root API user and non-root postgres init user get Permission denied — the API crash-loops ([Errno 13] Permission denied: '/run/secrets/audit_hmac_key') and postgres can't read the app password. Preflight now writes 0644; the manual chmod 0400 guidance in docs/compose comments is aligned so it stops re-triggering the crash-loop.
  2. Silent passwordless app role. init.sql read the app password with current_setting('init.app_password', true) (missing_ok) and created agent_bom_app with a NULL password when the secret was unreadable — surfacing later as a confusing password authentication failed for user "agent_bom_app". It now RAISE EXCEPTION at the real root cause.
  3. Public demo could never open anonymously. hosted-poc set DEMO_ESTATE=1 + NO_AUTH_ROLE=viewer but not ALLOW_UNAUTHENTICATED_API=1, so the seeded demo estate fell back to the API-key sign-in wall. Flag added (+ test assertion updated).

k8s/Helm is not affected — the chart runs non-root with fsGroup, keeping defaultMode: 0400 secret mounts readable (verified on a kind cluster). Untouched.

Part 2 — guided OIDC/SSO onboarding (agent-bom auth setup-oidc)

Onboarding on top of the existing browser-OIDC mechanism — collects, validates, and emits config; does not fork or touch auth/RBAC/token-validation.

  • New auth command group + setup-oidc wizard (interactive + non-interactive flags). Derives redirect_uri = <base>/v1/auth/oidc/callback (verified against the real route), defaults AGENT_BOM_OIDC_AUDIENCE to the client ID (matches verify_browser_id_token), and emits AGENT_BOM_OIDC_ALLOW_DEFAULT_TENANT=1 for the single-tenant Google case (no tenant claim). Connectivity check reuses the existing OIDC discovery. Writes an env file only on --write/confirmation (0644, so the compose env_file is readable by the non-root API container).
  • docs/AUTH_SSO.md — 5-minute Google path + generic OIDC (Okta/Entra/Auth0/Keycloak), cross-linked from ENTERPRISE_DEPLOYMENT.
  • Sign-in page hint now points to the command + doc.

Validation

  • pytest tests/test_auth_setup_oidc.py tests/test_hosted_poc_preflight.py tests/test_compose_secrets_healthcheck.py61 passed (discovery mocked, no network).
  • ruff check / ruff format / mypy clean on changed py; CLI registration/help suites pass (no regression).
  • init.sql validated against a live postgres:16: empty password → init aborts; happy path → role created with password.
  • UI hint is a text-only JSX copy change (no new imports/logic); npm run lint/build not run in the ephemeral worktree — flagged as the one unverified gate.

@msaad00 msaad00 changed the title fix(deploy): self-host first-run secret bootstrap + anonymous demo fix(deploy)+feat(auth): self-host first-run bootstrap, anonymous demo, and guided OIDC/SSO setup Jul 21, 2026
msaad00 added 3 commits July 21, 2026 01:07
…il loud

Two first-run defects surfaced by dogfooding the Docker Compose self-host on a
fresh VM:

1. Preflight (--write-secret) wrote deploy/secrets/* as 0400. Compose
   (non-swarm) bind-mounts these into /run/secrets/* preserving host perms
   (the mode/uid/gid long-syntax fields are swarm-only, ignored by compose), so
   the non-root container users — postgres UID 70 running initdb, the API app
   user — hit EACCES and crash-loop (e.g. Permission denied on
   /run/secrets/audit_hmac_key). Write 0644 instead; on a single-tenant
   self-host VM the host filesystem is the trust boundary. Aligned the manual
   secret-generation guidance (deploy/secrets/README.md, DEPLOY_QUICKSTART,
   DEPLOY_PLATFORM, compose header comments, init-wrapper hint) to 0644.

2. init.sql read init.app_password with missing_ok=true and silently skipped
   app-role creation when unset/empty, so a failed init-wrapper (e.g.
   unreadable secret) surfaced later as an opaque runtime
   "password authentication failed for user agent_bom_app". Now RAISE EXCEPTION
   to abort init loudly at the real root cause.

Added regression tests: preflight writes 0644; init.sql raises (does not skip)
when the app-password GUC is unset. k8s/Helm path confirmed unaffected —
defaultMode 0400 stays readable under fsGroup 1000 (verified empirically on
kind v1.36.1).
…pens without a sign-in wall

DEMO_ESTATE + NO_AUTH_ROLE=viewer never took effect because
AGENT_BOM_ALLOW_UNAUTHENTICATED_API was unset, so the seeded demo estate
fell back to the API-key sign-in page instead of opening anonymously.
Add an `auth` command group with a `setup-oidc` subcommand that onboards
browser SSO ("Sign in with Google" or any OIDC issuer) without hand-wiring
env vars. It only collects, validates, and emits AGENT_BOM_OIDC_* config —
it does not fork or change the auth mechanism.

- Interactive (TTY) and fully flag-driven non-interactive modes.
- Derives the redirect URI as <base-url>/v1/auth/oidc/callback (the real
  dashboard callback route) and defaults audience to the client ID (the
  browser ID token aud convention).
- Emits AGENT_BOM_OIDC_ALLOW_DEFAULT_TENANT=1 for a single-tenant self-host
  so login actually resolves a tenant; suppressed when --tenant-claim is set.
- Reuses oidc.discover_oidc for the issuer connectivity check (warns, never
  fails) rather than forking discovery.
- Optional file write to deploy/secrets/oidc.env at mode 0644 (compose
  env_file must be readable by the non-root API container); only on --write
  or explicit confirmation, with a client-secret directory warning.
- Point the dashboard sign-in hint and a new docs/AUTH_SSO.md at the command.
@msaad00
msaad00 force-pushed the fix/self-host-secret-bootstrap branch from cbfe471 to 2bcfa4b Compare July 21, 2026 05:07
@agent-bom
agent-bom enabled auto-merge (squash) July 21, 2026 05:08
agent-bom
agent-bom previously approved these changes Jul 21, 2026
andres-linero
andres-linero previously approved these changes Jul 21, 2026
… aborts; document auth CLI group

- init.sql: distinguish NULL app_password GUC (unset — Alembic migrations,
  integration-test bootstrap, wrapper-less dev provision the role out of band)
  from an empty GUC (misconfigured secret). NULL skips app-role creation;
  empty RAISE EXCEPTION. The previous unconditional RAISE broke the Postgres
  Integration Contract, which runs init.sql on the test DB without the GUC.
- site-docs/reference/cli.md: add the 'auth' / 'auth setup-oidc' rows so
  check_cli_reference_alignment passes.
@msaad00
msaad00 dismissed stale reviews from andres-linero and agent-bom via 5558f1c July 21, 2026 05:28
andres-linero
andres-linero previously approved these changes Jul 21, 2026
agent-bom
agent-bom previously approved these changes Jul 21, 2026
xdist workers reused threading.local coverage-warning state, so a prior
offline_ecosystem_gap (no reason key) made
test_package_scan_preserves_parser_coverage_warning KeyError on [0][reason].
Reset scan warning channels in the autouse fixture and assert by reason.

Co-authored-by: W S <msaad00@users.noreply.github.com>
@cursor
cursor Bot dismissed stale reviews from agent-bom and andres-linero via 51d5919 July 21, 2026 05:45
agent-bom
agent-bom previously approved these changes Jul 21, 2026
andres-linero
andres-linero previously approved these changes Jul 21, 2026
andres-linero pushed a commit that referenced this pull request Jul 21, 2026
…-backed auto-enable (#4336)

<!-- CURSOR_AGENT_PR_BODY_BEGIN -->
## Summary

Closes the 2026-07-21 audit **P0/P1** gaps in one PR (does not touch
hosted-poc compose anonymous-demo wiring — that stays in #4335).

- **Demo resilience:** `demo-redeploy` triggers on successful `Release`
`workflow_run` (GITHUB_TOKEN-safe), builds images before `up -d`, fails
loud if `DEMO_DEPLOY_DIR` is not a git checkout; Deployment Freshness
probes `demo.agent-bom.com/health`; README documents `uvx agent-bom scan
--demo --offline` fallback.
- **MCP attest user surface:** `agent-bom attest mcp sign|verify`
derives evidence from scan JSON; 0.96.4 changelog line narrowed to
“foundation”; CLI_MAP + site-docs updated.
- **Store-backed default path:** unset
`AGENT_BOM_GRAPH_STORE_BACKED_BUILD` auto-enables above
`AGENT_BOM_GRAPH_STORE_BACKED_MIN_ENTITIES` (default 5000); explicit off
preserved; ADR-006 + env allowlist updated.
- **README visuals:** how-it-works + blast-radius light/dark diagrams
simplified to the three-lane product story and a single blast-radius
path.
- **CI hygiene:** drop no-op `dependency-pin-check.yml` and the
non-blocking main-only `ci.yml` `agent-bom-scan` job (covered by PR
Security Gate + post-merge self-scan).

## Verification

```bash
uv run ruff check src/
uv run mypy src/agent_bom/mcp_scan_attestation.py src/agent_bom/cli/_attest_group.py src/agent_bom/api/pipeline.py --ignore-missing-imports --disable-error-code import-untyped
uv run pytest -q tests/test_mcp_attest_cli.py tests/api/test_store_backed_auto_enable.py tests/test_mcp_scan_attestation.py
# 28 passed
uv run pre-commit run --files <changed files>   # ruff/format/bandit/env-var/comment hygiene passed; mypy hook follows imports into pre-existing no-any-return elsewhere
uv run agent-bom scan --demo --offline -f json -o /tmp/demo-smoke.json
# demo smoke produced findings JSON (exit 1 expected for critical findings)
uv run agent-bom attest mcp --help
```

## Notes

- Host restore for a live 502 remains an operator/VM concern; this PR
hardens redeploy trigger + monitoring + local fallback.
- Residual store-backed peak is still O(N) — docs state measured
reduction, not a strict memory bound.
- Intentionally avoids `deploy/docker-compose.hosted-poc.yml` to stay
clear of #4335.
- Develop-skill style local gates run before this push; waiting on CI to
finish (do not stack follow-up commits unless CI is red for a real
failure).

[how-it-works
light](https://cursor.com/agents/bc-a579ae44-c48d-4515-8cf6-2da701406ba6/artifacts?path=%2Fopt%2Fcursor%2Fartifacts%2Fhow-it-works-light.svg)
[blast-radius
light](https://cursor.com/agents/bc-a579ae44-c48d-4515-8cf6-2da701406ba6/artifacts?path=%2Fopt%2Fcursor%2Fartifacts%2Fblast-radius-light.svg)


<sub>To show artifacts inline, <a
href="https://cursor.com/dashboard/cloud-agents#my-pull-requests">enable</a>
in settings.</sub>
<!-- CURSOR_AGENT_PR_BODY_END -->

<div><a
href="https://cursor.com/agents/bc-a579ae44-c48d-4515-8cf6-2da701406ba6"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/assets/images/open-in-web-light.png"><img
alt="Open in Web" width="114" height="28"
src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a>&nbsp;<a
href="https://cursor.com/background-agent?bcId=bc-a579ae44-c48d-4515-8cf6-2da701406ba6"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img
alt="Open in Cursor" width="131" height="28"
src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a>&nbsp;</div>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: W S <msaad00@users.noreply.github.com>
The scanner-coverage-warning reset commit also deleted the auth/storage/server
env-var restoration loop in reset_global_test_state(). Without it, a test that
sets AGENT_BOM_POSTGRES_URL (or rate-limiter env) leaks it into later tests in
the same xdist worker, which then fail with 'psycopg not installed' /
'Postgres rate limiter could not initialize'. Restore the loop (kept on main);
the _reset_scan_warning_state() addition stays.
@msaad00
msaad00 dismissed stale reviews from andres-linero and agent-bom via 830385a July 21, 2026 15:21
@msaad00 msaad00 closed this Jul 21, 2026
auto-merge was automatically disabled July 21, 2026 15:36

Pull request was closed

@msaad00 msaad00 reopened this Jul 21, 2026
@agent-bom
agent-bom enabled auto-merge (squash) July 21, 2026 15:38
@agent-bom
agent-bom merged commit fe1b462 into main Jul 21, 2026
31 checks passed
@agent-bom
agent-bom deleted the fix/self-host-secret-bootstrap branch July 21, 2026 15:45
agent-bom added a commit that referenced this pull request Jul 21, 2026
…all (#4337)

Fixes six first-run bugs in the Helm chart — the k8s analogs of the
Docker-Compose self-host issues (#4335). Found by static audit, fixed
and verified with `helm lint`/`template` + a kind-cluster test. No live
EKS install was run (see residual).

## Bugs fixed
1. **Half-wired auth Secret (HIGH).** The recommended
`focused-pilot`/`eks-vanilla`/`sqlite-pilot` profiles `envFrom` a Secret
(`agent-bom-control-plane[-auth]`) that no template creates and no
example documented → pods stuck `CreateContainerConfigError` on first
install. Ship `examples/control-plane-auth-secret.example.yaml` (one
block per profile, every key commented + generate command) and document
it in `examples/README.md` with a per-profile required-key matrix.
2. **Multi-replica login trap (HIGH).** Default `api.replicas: 2` makes
`AGENT_BOM_BROWSER_SESSION_SIGNING_KEY` mandatory (`browser_session.py`
fails closed with `BrowserSessionError`), but no profile listed it → UI
login dead. Now in the documented required-key set.
3. **Snowflake key mode (MED).** `cronjob.yaml` mounted the key
`defaultMode: 0400`; set to `0440`. (Kind test showed kubelet fsGroup
already rewrites 0400→0440 on disk, so this was not a live break —
`0440` makes the manifest match enforced reality.)
4. **Dead-dashboard auth posture (MED).** Non-OIDC profiles had no
configured auth backend. Each profile now documents a working backend;
the sqlite demo gets a visible `AGENT_BOM_ALLOW_UNAUTHENTICATED_API=1`
(viewer).
5. **Undocumented required audit key (LOW).** `eks-vanilla` forces
`AGENT_BOM_REQUIRE_AUDIT_HMAC=1`; `AGENT_BOM_AUDIT_HMAC_KEY` is now in
its required-key list.
6. **Broken quick-start in values header (LOW).** `values.yaml` header
no longer implies a bare `--set controlPlane.enabled=true` works; points
at a profile + the Secret requirement (matching README). The
migration-job `fail` guard is untouched.

**S1 (cookie-secure):** no change needed — `_session_cookie_secure`
already defaults Secure for production/clustered (replicas>1); both TLS
profiles run replicas 2. Forcing it on the plain-HTTP sqlite demo would
drop the login cookie. CORS confirmed unneeded (same-origin proxying).

## Secret keys (confirmed against code, not guessed)
`AGENT_BOM_POSTGRES_URL`/`AGENT_BOM_DB`,
`AGENT_BOM_BROWSER_SESSION_SIGNING_KEY`, `AGENT_BOM_AUDIT_HMAC_KEY`,
`AGENT_BOM_CONNECTIONS_KEY`, `AGENT_BOM_API_KEYS` (`key:role`).

## Validation
- `helm lint deploy/helm/agent-bom` → 0 failed.
- `helm template` for each affected profile → renders; every `secretRef`
now resolves to a documented example.
- `pytest tests/test_deploy_manifests.py` → 70 passed (4 new tests
proven RED on main → GREEN).
- Kind-cluster test confirmed the fsGroup/0400→0440 mount behavior.
- Chart/appVersion + image tags stay 0.97.1 (version-alignment gate
untouched).

## Residual
Verified at render + secret-key-contract + code-behavior + kind level. A
full end-to-end `helm install` against real RDS/ALB/IRSA (live browser
login on EKS) was not run.

Co-authored-by: Andres Linero <andres.nyc.col@gmail.com>
Co-authored-by: agent-bom <andwgdysaad@gmail.com>
andres-linero pushed a commit that referenced this pull request Jul 21, 2026
<!-- CURSOR_AGENT_PR_BODY_BEGIN -->
## Summary

- Bump release-managed package, UI, Docker, Helm, registry, OpenClaw,
Snowflake, and docs surfaces to **0.97.2**.
- Move `[Unreleased]` into the `0.97.2` changelog covering auth/OIDC
bootstrap (#4335), Helm first-install auth Secrets (#4337), attest CLI +
demo/store-backed graph (#4336), and MCP honesty / audit exit codes
(#4346).
- Promote simplified teal blast-radius SVGs (replacing the denser
violet-accent art that lagged how-it-works) and align
`product-screenshots.json` metadata (no full PNG recapture).

## Verification

```bash
uv run agent-bom --version   # 0.97.2
uv run agent-bom scan --demo --offline -o /tmp/ab-demo-scan.json
uv run agent-bom attest mcp sign ... --server filesystem-server --tenant-id demo  # signed=true
uv run agent-bom attest mcp verify ... --public-key ... --tenant-id demo
uv run agent-bom auth setup-oidc --provider google --client-id demo-client --base-url https://example.internal --non-interactive
helm lint deploy/helm/agent-bom
helm template ab deploy/helm/agent-bom -f deploy/helm/agent-bom/examples/eks-control-plane-sqlite-pilot-values.yaml
uv run python scripts/bump-version.py 0.97.2 --check
uv run python scripts/check_release_consistency.py
uv run python scripts/check_product_surface_contract.py
uv run python scripts/export_openapi.py --check
uv run python scripts/generate_agent_capability_manifest.py --check
uv run python scripts/lint_release_workflow.py .github/workflows/release.yml
uv run pytest -q tests/test_version_alignment.py tests/test_mcp_attest_cli.py
# 9 passed
curl -fsS https://demo.agent-bom.com/health
# status=ok, version=0.97.1 (hosted still on prior tag until release), unauthenticated_allowed=true
```

## Notes

- Prepares the release only; does **not** tag or publish PyPI/Docker.
- After merge: tag `v0.97.2` via the normal Release workflow.
- `#4344` (external pre-install dependency risk signal proposal) is
intentionally deferred — not a patch blocker.
- Screenshot PNGs remain the same-day 0.97.1 DEMO DATA capture; manifest
`visible_version`/`release_version` aligned for the release gate.
<!-- CURSOR_AGENT_PR_BODY_END -->

<div><a
href="https://cursor.com/agents/bc-a579ae44-c48d-4515-8cf6-2da701406ba6"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/assets/images/open-in-web-light.png"><img
alt="Open in Web" width="114" height="28"
src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a>&nbsp;<a
href="https://cursor.com/background-agent?bcId=bc-a579ae44-c48d-4515-8cf6-2da701406ba6"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img
alt="Open in Cursor" width="131" height="28"
src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a>&nbsp;</div>

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: W S <msaad00@users.noreply.github.com>
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.

4 participants