Files
smart-org/.env.example
T
mjalmoudy94 d41e00ad10 feat(delivery): git workspaces + SSRF host check (v2 phase A3-2)
The platform's side of D6: git runs on the host, in a gitdir that is never
mounted into a container, so repository credentials never reach the model.

- adapters/git_workspace.py: LocalGitWorkspaceAdapter implementing
  RepoWorkspacePort. Bare shared gitdir + a dev work tree mounted at
  /workspace + one linked worktree per task under work/.worktrees/<id>
  (excluded via the gitdir's info/exclude, not the project's .gitignore).
  Explicit --git-dir/--work-tree, core.hooksPath=/dev/null, fsmonitor off,
  quotePath off, GIT_CONFIG_NOSYSTEM, a private HOME per project, prompts off,
  one asyncio.Lock per project. Token via GIT_ASKPASS reading a 0600 file; SSH
  key as a 0600 temp file with a pinned known_hosts. Everything deleted in a
  finally; only a *path* ever travels through the environment.
- The bug this phase existed to find: --git-dir=<shared> does not address a
  linked worktree, so `git commit` in a task worktree committed straight to
  `dev`. Operations now address <gitdir>/worktrees/<task_id> explicitly — and
  never read the worktree's .git file, which a repository can rewrite.
- domain/platform/rules.py: is_blocked_address (pure) + assert_remote_host_allowed
  (resolves, refuses if any address is loopback/private/link-local/metadata).
  Shared by the git remote test and, later, every deploy target.
- ProjectExternalGit domain model + repository; container_delivery wires the
  real adapter behind GIT_WORKSPACE=local with a resolver that joins the
  project, its repo and its (decrypted) credentials.

Tests: 394 API (+48) against real git and a local bare repo; ruff and mypy clean.

Report: docs/plan/reports/agent-3-phase-2.md
2026-10-14 00:18:13 +03:30

129 lines
5.1 KiB
Bash

# ---------------------------------------------------------------------------
# Smart Organization — sample environment file.
# Copy to `.env` and fill in real values. NEVER commit a real `.env`.
# Every external provider defaults to `mock` so the stack runs with zero
# credentials. Flip the *_PROVIDER vars once real credentials exist.
# ---------------------------------------------------------------------------
APP_ENV=development
APP_URL=http://localhost:3000
API_URL=http://localhost:8000
# Origins the browser is allowed to call the API from, comma-separated.
# `localhost` and `127.0.0.1` are DIFFERENT origins to a browser: if you start
# the dev server with `-H 127.0.0.1` and this list only has `localhost`, every
# request fails with an opaque "Disallowed CORS origin". Both are listed here.
# Production must set this explicitly — the dev default is rejected at boot.
CORS_ORIGINS=http://localhost:3000,http://127.0.0.1:3000
# --- Data stores -----------------------------------------------------------
DATABASE_URL=postgresql+psycopg://agy:agy@localhost:5432/agy_web
REDIS_URL=redis://localhost:6379/0
# --- Security --------------------------------------------------------------
# Generate with: python -c "import secrets; print(secrets.token_urlsafe(48))"
JWT_SECRET=change-me
# Must be exactly 32 bytes, base64/hex encoded.
ENCRYPTION_KEY=change-me-32-bytes
# --- AI models (hidden from customers) -------------------------------------
# agy (Google Antigravity) is the only model provider. The setting is kept for
# compatibility with older .env files; any value is ignored (with a warning).
AI_DEFAULT_PROVIDER=agy
# --- agy (Google Antigravity) ----------------------------------------------
# The only model provider. There are no API keys: accounts are Google
# identities logged in via OAuth from the admin console
# (/admin/agy-accounts → افزودن اکانت agy). Any number of accounts can be added —
# the rotation pool spreads load across them and benches a rate-limited one
# until its quota window resets. AGY_CLIENT_ID/SECRET default to the public
# Antigravity CLI application; override them to use your own Google OAuth
# client. AGY_REDIRECT_URI must stay a loopback URL.
AGY_CLIENT_ID=
AGY_CLIENT_SECRET=
AGY_REDIRECT_URI=http://localhost:51121/oauth-callback
# Seconds an account is benched after a 429 when Google sends no Retry-After.
AGY_429_COOLDOWN_SECONDS=900
# --- Git provider ----------------------------------------------------------
# mock | gitea
GIT_PROVIDER=mock
GITEA_URL=
GITEA_INTERNAL_URL=
GITEA_ADMIN_TOKEN=
# --- Preview domain routing ------------------------------------------------
# mock | caddy
DOMAIN_ROUTER=mock
PREVIEW_ROOT_DOMAIN=msa.artacloud.ir
PREVIEW_PORT_START=8080
PREVIEW_PORT_END=8999
CADDYFILE_PATH=/etc/caddy/Caddyfile
# --- Sandbox ---------------------------------------------------------------
# mock | daytona | e2b
SANDBOX_PROVIDER=mock
DAYTONA_API_KEY=
DAYTONA_API_URL=
E2B_API_KEY=
# --- Payments --------------------------------------------------------------
# mock | stripe
PAYMENT_PROVIDER=mock
STRIPE_SECRET_KEY=
STRIPE_WEBHOOK_SECRET=
# --- Error logging ---------------------------------------------------------
# Every failure the platform catches is written to the `error_events` table and
# is readable at /admin/errors (platform owner only). Errors are redacted before
# they are stored — passwords, tokens, API keys, JWTs and DSN credentials are
# stripped by app/core/redact.py.
#
# ERROR_LOG_ENABLED=0 turns persistence off entirely; failures still reach
# stderr, but nothing is written to the database.
ERROR_LOG_ENABLED=1
# Whether 4xx responses are recorded too. Off by default: a public API receives
# a constant stream of 404s from scanners, and recording them all buries the
# genuine bugs. Turn it on while debugging a misbehaving client.
# 5xx responses are always recorded, whatever this is set to.
ERROR_LOG_CLIENT_ERRORS=0
# ===========================================================================
# v2 workstream settings. Each agent edits ONLY its own block.
# ===========================================================================
# >>> agent-1 (org platform)
MCP_GATEWAY_HOST=127.0.0.1
MCP_GATEWAY_PORT=8765
MCP_TOKEN_TTL_DAYS=90
MEMORY_ORG_CHAR_CAP=60000
PLATFORM_INVITE_TTL_HOURS=72
# <<< agent-1
# >>> agent-2 (AI workforce engine)
ENGINE_MAX_TOOL_CALLS_PER_TURN=40
ENGINE_TURN_TIMEOUT_SECONDS=900
ENGINE_MAX_CYCLES=6
ENGINE_MAX_EMPLOYEES=20
ENGINE_TICK_SECONDS=10
# <<< agent-2
# >>> agent-3 (delivery & infra)
SANDBOX_DOCKER_BIN=docker
SANDBOX_WORKSPACES_ROOT=./.data/workspaces
SANDBOX_DEFAULT_CPUS=1
SANDBOX_DEFAULT_MEMORY_MB=1024
DEMO_PORT_START=8100
DEMO_PORT_END=8999
DELIVERY_POLL_SECONDS=60
DEPLOY_ALLOW_PRIVATE_HOSTS=false
ARTIFACTS_ROOT=./.data/artifacts
# fake (default, no git on the host) | local (real git workspaces)
GIT_WORKSPACE=fake
ALLOW_CUSTOM_DOMAINS=false
# `protocol.file.allow` for host git calls. The hardened default is `never`: a
# repository must not be able to make git read a local path through a submodule
# or `--upload-pack`. Set to true ONLY to test against a local bare repository.
GIT_ALLOW_FILE_PROTOCOL=false
# <<< agent-3