Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 27 additions & 9 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -111,17 +111,35 @@ LLM_MAX_TOKENS=16384
# =============================================================================
# MCP servers are configured in mcp-servers.json
# Available servers:
# - github: Official GitHub MCP (requires Podman/Docker)
# - gitmcp: GitMCP.io for repo documentation
# - atlassian: Official Atlassian Rovo MCP (uses JIRA_* credentials above)
# - github: GitHub's remote MCP endpoint
# - atlassian: Locally hosted mcp-atlassian SSE endpoint
# - context7: Upstash Context7 for library docs
#
# Enable MCP server integrations
AGENT_ENABLE_MCP=true
# MCP servers to enable: '*' for all from mcp-servers.json, or comma-separated list
AGENT_MCP_SERVERS=*
# Restrict MCP tools to read-only operations (no create/update/delete)
AGENT_MCP_READ_ONLY=true
# Exact tools granted to host agents, as server:tool names. Empty is deny-all.
# Tool names vary by server version and account permissions; run `forge mcp-tools`
# against your deployment and remove any tool that is not required.
#
# Read-only tools commonly exposed by the servers in mcp-servers.json:
# github (default toolsets):
# get_me, get_file_contents, get_commit, list_branches, list_commits,
# list_releases, list_tags, search_code, search_repositories, issue_read,
# list_issues, search_issues, pull_request_read, search_pull_requests,
# search_users
# atlassian (core mcp-atlassian toolsets):
# jira_get_issue, jira_search, jira_get_project_issues,
# jira_batch_get_changelogs, jira_search_fields, jira_get_field_options,
# jira_get_transitions, confluence_search, confluence_get_page,
# confluence_get_page_children, confluence_get_page_history,
# confluence_get_page_diff, confluence_get_comments
# context7:
# resolve-library-id, query-docs
#
# Copy-ready minimal baseline for issue/code/documentation lookup:
AGENT_MCP_ALLOWED_TOOLS=github:get_me,github:get_file_contents,github:search_code,github:issue_read,github:pull_request_read,atlassian:jira_get_issue,atlassian:jira_search,context7:resolve-library-id,context7:query-docs
# Path to MCP servers config file (default: mcp-servers.json in project root)
AGENT_MCP_CONFIG_PATH=

Expand All @@ -130,10 +148,10 @@ AGENT_MCP_CONFIG_PATH=
# =============================================================================
# Enable agent tools (file operations, search, etc.)
AGENT_ENABLE_TOOLS=true
# Allowed tools: '*' for all, or comma-separated list (Read,Write,Edit,Glob,Grep,Bash,WebSearch)
AGENT_ALLOWED_TOOLS=*
# Working directory for agent file operations (empty = current directory)
AGENT_WORKING_DIRECTORY=
# Safe host built-ins only. Write and shell tools are always prohibited.
AGENT_ALLOWED_TOOLS=ls,read_file,glob,grep
# Dedicated virtual filesystem root (deployments should use /var/lib/forge/agent).
AGENT_ROOT_DIR=.forge/agent
# Backend type: filesystem, state, or store
AGENT_BACKEND=filesystem

Expand Down
4 changes: 3 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ RUN apt-get update && apt-get install -y --no-install-recommends \

# Copy wheels and install
COPY --from=builder /wheels /wheels
RUN pip install --no-cache-dir /wheels/* && rm -rf /wheels
COPY skills/ /app/skills/
RUN pip install --no-cache-dir /wheels/* && rm -rf /wheels && \
mkdir -p /var/lib/forge/agent && chown -R forge:forge /var/lib/forge

# Switch to non-root user
USER forge
Expand Down
10 changes: 9 additions & 1 deletion charts/forge/templates/api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,10 @@ spec:
envFrom:
- secretRef:
name: {{ .Values.existingSecret }}
{{- if .Values.redis.enabled }}
env:
- name: AGENT_ROOT_DIR
value: /var/lib/forge/agent
{{- if .Values.redis.enabled }}
- name: REDIS_URL
value: redis://{{ include "forge.fullname" . }}-redis:6379/0
{{- end }}
Expand All @@ -48,6 +50,12 @@ spec:
httpGet: {path: /api/v1/live, port: http}
resources:
{{- toYaml .Values.resources.api | nindent 12 }}
volumeMounts:
- name: agent-root
mountPath: /var/lib/forge
volumes:
- name: agent-root
emptyDir: {}
---
apiVersion: v1
kind: Service
Expand Down
6 changes: 6 additions & 0 deletions charts/forge/templates/worker.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ spec:
- secretRef:
name: {{ .Values.existingSecret }}
env:
- name: AGENT_ROOT_DIR
value: /var/lib/forge/agent
{{- if .Values.redis.enabled }}
- name: REDIS_URL
value: redis://{{ include "forge.fullname" . }}-redis:6379/0
Expand Down Expand Up @@ -103,6 +105,8 @@ spec:
- name: metrics
containerPort: {{ .Values.worker.metricsPort }}
volumeMounts:
- name: agent-root
mountPath: /var/lib/forge
- name: workspaces
mountPath: {{ .Values.workspace.mountPath }}
{{- if .Values.googleCredentials.enabled }}
Expand All @@ -114,6 +118,8 @@ spec:
resources:
{{- toYaml .Values.resources.worker | nindent 12 }}
volumes:
- name: agent-root
emptyDir: {}
- name: workspaces
persistentVolumeClaim:
claimName: {{ .Values.workspace.claimName }}
Expand Down
39 changes: 37 additions & 2 deletions containers/entrypoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,39 @@
logger = logging.getLogger(__name__)


def _operational_shell_env() -> dict[str, str]:
"""Environment exposed to commands run by implementation/reviewer agents.

This prevents ordinary subprocess inheritance of provider and tracing secrets.
It is defense in depth only: secrets in the container's top-level environment
may still be obtainable by sufficiently capable code through facilities such
as ``/proc``.
"""
names = (
"PATH",
"HOME",
"LANG",
"LANGUAGE",
"LC_ALL",
"LC_CTYPE",
"TMPDIR",
"TMP",
"TEMP",
"GIT_AUTHOR_NAME",
"GIT_AUTHOR_EMAIL",
"GIT_COMMITTER_NAME",
"GIT_COMMITTER_EMAIL",
"GIT_USER_NAME",
"GIT_USER_EMAIL",
)
result = {name: os.environ[name] for name in names if name in os.environ}
result.setdefault("PATH", os.defpath)
result.setdefault("HOME", str(Path.home()))
result.setdefault("LANG", "C.UTF-8")
result.setdefault("TMPDIR", "/tmp")
return result


def command_timeout(default: int) -> int:
"""Return the configured per-command timeout, or the caller's default."""
raw_value = os.environ.get("CONTAINER_COMMAND_TIMEOUT")
Expand Down Expand Up @@ -633,7 +666,8 @@ async def run_agent_task(

backend = LocalShellBackend(
root_dir=str(workspace),
inherit_env=True,
inherit_env=False,
env=_operational_shell_env(),
virtual_mode=False,
timeout=command_timeout(600),
)
Expand Down Expand Up @@ -742,7 +776,8 @@ async def run_reviewer_agent(

backend = LocalShellBackend(
root_dir=str(workspace),
inherit_env=True,
inherit_env=False,
env=_operational_shell_env(),
virtual_mode=False,
timeout=command_timeout(600),
)
Expand Down
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ services:
environment:
- REDIS_URL=redis://redis:6379/0
- LOG_LEVEL=INFO
- AGENT_ROOT_DIR=/var/lib/forge/agent
env_file:
- .env
depends_on:
Expand Down
3 changes: 3 additions & 0 deletions docs/reference/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

All configuration is via environment variables in `.env`. See `.env.example` in the repository for the complete list with comments.

For production agent-root, tool, MCP, container, and secret-handling requirements, see
the [safe agent deployment guide](../security/agent-isolation.md).

## Required Variables

### Jira
Expand Down
Loading
Loading