diff --git a/.github/workflows/large-payload-tester.lock.yml b/.github/workflows/large-payload-tester.lock.yml index 8f1662fe..1f94740d 100644 --- a/.github/workflows/large-payload-tester.lock.yml +++ b/.github/workflows/large-payload-tester.lock.yml @@ -149,7 +149,7 @@ jobs: const determineAutomaticLockdown = require('/opt/gh-aw/actions/determine_automatic_lockdown.cjs'); await determineAutomaticLockdown(github, context, core); - name: Download container images - run: bash /opt/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.13.12 ghcr.io/github/gh-aw-firewall/squid:0.13.12 ghcr.io/github/gh-aw-mcpg:latest ghcr.io/github/github-mcp-server:v0.30.2 ghcr.io/github/github-mcp-server:v0.30.3 mcp/filesystem node:lts-alpine + run: bash /opt/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.13.12 ghcr.io/github/gh-aw-firewall/squid:0.13.12 ghcr.io/github/gh-aw-mcpg:v0.0.103 ghcr.io/github/github-mcp-server:v0.30.2 ghcr.io/github/github-mcp-server:v0.30.3 mcp/filesystem node:lts-alpine - name: Write Safe Outputs Config run: | mkdir -p /opt/gh-aw/safeoutputs @@ -404,7 +404,7 @@ jobs: # Register API key as secret to mask it from logs echo "::add-mask::${MCP_GATEWAY_API_KEY}" export GH_AW_ENGINE="copilot" - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_LOCKDOWN -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:latest' + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_LOCKDOWN -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.0.103' mkdir -p /home/runner/.copilot cat << MCPCONFIG_EOF | bash /opt/gh-aw/actions/start_mcp_gateway.sh diff --git a/.github/workflows/nightly-mcp-stress-test.lock.yml b/.github/workflows/nightly-mcp-stress-test.lock.yml index cc0e4553..f0eb0827 100644 --- a/.github/workflows/nightly-mcp-stress-test.lock.yml +++ b/.github/workflows/nightly-mcp-stress-test.lock.yml @@ -147,7 +147,7 @@ jobs: const determineAutomaticLockdown = require('/opt/gh-aw/actions/determine_automatic_lockdown.cjs'); await determineAutomaticLockdown(github, context, core); - name: Download container images - run: bash /opt/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.13.12 ghcr.io/github/gh-aw-firewall/squid:0.13.12 ghcr.io/github/gh-aw-mcpg:latest ghcr.io/github/github-mcp-server:v0.30.2 ghcr.io/github/github-mcp-server:v0.30.3 mcp/brave-search mcp/duckduckgo mcp/everart mcp/fetch mcp/filesystem mcp/gdrive mcp/git mcp/google-maps mcp/hackernews-mcp mcp/kubernetes mcp/memory mcp/puppeteer mcp/sentry mcp/sequentialthinking mcp/slack mcp/sqlite mcp/time mcp/wikipedia-mcp mcp/youtube-transcript mcr.microsoft.com/playwright/mcp mcr.microsoft.com/playwright:v1.49.1-noble node:lts-alpine + run: bash /opt/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.13.12 ghcr.io/github/gh-aw-firewall/squid:0.13.12 ghcr.io/github/gh-aw-mcpg:v0.0.103 ghcr.io/github/github-mcp-server:v0.30.2 ghcr.io/github/github-mcp-server:v0.30.3 mcp/brave-search mcp/duckduckgo mcp/everart mcp/fetch mcp/filesystem mcp/gdrive mcp/git mcp/google-maps mcp/hackernews-mcp mcp/kubernetes mcp/memory mcp/puppeteer mcp/sentry mcp/sequentialthinking mcp/slack mcp/sqlite mcp/time mcp/wikipedia-mcp mcp/youtube-transcript mcr.microsoft.com/playwright/mcp mcr.microsoft.com/playwright:v1.49.1-noble node:lts-alpine - name: Write Safe Outputs Config run: | mkdir -p /opt/gh-aw/safeoutputs @@ -411,7 +411,7 @@ jobs: # Register API key as secret to mask it from logs echo "::add-mask::${MCP_GATEWAY_API_KEY}" export GH_AW_ENGINE="copilot" - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_LOCKDOWN -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -e BRAVE_API_KEY -e EVERART_API_KEY -e GOOGLE_APPLICATION_CREDENTIALS -e GOOGLE_MAPS_API_KEY -e KUBECONFIG -e KUBERNETES_CLUSTER_URL -e SENTRY_DSN -e SLACK_BOT_TOKEN -e SLACK_TEAM_ID -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:latest' + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_LOCKDOWN -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -e BRAVE_API_KEY -e EVERART_API_KEY -e GOOGLE_APPLICATION_CREDENTIALS -e GOOGLE_MAPS_API_KEY -e KUBECONFIG -e KUBERNETES_CLUSTER_URL -e SENTRY_DSN -e SLACK_BOT_TOKEN -e SLACK_TEAM_ID -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.0.103' mkdir -p /home/runner/.copilot cat << MCPCONFIG_EOF | bash /opt/gh-aw/actions/start_mcp_gateway.sh diff --git a/README.md b/README.md index d6ebfdde..b2cda3c5 100644 --- a/README.md +++ b/README.md @@ -273,7 +273,7 @@ Available Commands: Flags: -c, --config string Path to config file --config-stdin Read MCP server configuration from stdin (JSON format). When enabled, overrides --config - --enable-difc Enable DIFC enforcement and session requirement (requires sys___init call before tool access) + --enable-difc Enable DIFC enforcement for information flow control --env string Path to .env file to load environment variables -h, --help help for awmg -l, --listen string HTTP server listen address (default "127.0.0.1:3000") diff --git a/config.example.toml b/config.example.toml index 9cce5475..11ff0de7 100644 --- a/config.example.toml +++ b/config.example.toml @@ -109,7 +109,7 @@ args = [ # ============================================================================ # Enable Data Information Flow Control (DIFC) security model (default: false) -# When true, requires sys___init call before tool access +# When enabled, enforces information flow control policies on tool calls # This is an experimental feature - keep disabled for standard MCP compatibility # enable_difc = false diff --git a/internal/cmd/flags_difc.go b/internal/cmd/flags_difc.go index 71cd2564..b40bec0e 100644 --- a/internal/cmd/flags_difc.go +++ b/internal/cmd/flags_difc.go @@ -19,7 +19,7 @@ var ( func init() { RegisterFlag(func(cmd *cobra.Command) { - cmd.Flags().BoolVar(&enableDIFC, "enable-difc", getDefaultEnableDIFC(), "Enable DIFC enforcement and session requirement (requires sys___init call before tool access)") + cmd.Flags().BoolVar(&enableDIFC, "enable-difc", getDefaultEnableDIFC(), "Enable DIFC enforcement for information flow control") }) } diff --git a/internal/server/unified.go b/internal/server/unified.go index a539124c..db71ed93 100644 --- a/internal/server/unified.go +++ b/internal/server/unified.go @@ -846,48 +846,35 @@ func (us *UnifiedServer) ensureSessionDirectory(sessionID string) error { } // requireSession checks that a session has been initialized for this request -// When DIFC is disabled (default), automatically creates a session if one doesn't exist +// Sessions are automatically created if one doesn't exist (for standard MCP client compatibility) func (us *UnifiedServer) requireSession(ctx context.Context) error { sessionID := us.getSessionID(ctx) log.Printf("Checking session for ID: %s", sessionID) - // If DIFC is disabled (default), use double-checked locking to auto-create session - if !us.enableDIFC { - us.sessionMu.RLock() - session := us.sessions[sessionID] - us.sessionMu.RUnlock() - - if session == nil { - // Need to create session - acquire write lock - us.sessionMu.Lock() - // Double-check after acquiring write lock to avoid race condition - if us.sessions[sessionID] == nil { - log.Printf("DIFC disabled: auto-creating session for ID: %s", sessionID) - us.sessions[sessionID] = NewSession(sessionID, "") - log.Printf("Session auto-created for ID: %s", sessionID) - - // Ensure session directory exists in payload mount point - // This is done after releasing the lock to avoid holding it during I/O - us.sessionMu.Unlock() - if err := us.ensureSessionDirectory(sessionID); err != nil { - logger.LogWarn("client", "Failed to create session directory for session=%s: %v", sessionID, err) - // Don't fail - payloads will attempt to create the directory when needed - } - return nil - } - us.sessionMu.Unlock() - } - return nil - } - - // DIFC is enabled - require explicit session initialization + // Use double-checked locking to auto-create session if needed us.sessionMu.RLock() session := us.sessions[sessionID] us.sessionMu.RUnlock() if session == nil { - log.Printf("Session not found for ID: %s. Available sessions: %v", sessionID, us.getSessionKeys()) - return fmt.Errorf("sys___init must be called before any other tool calls") + // Need to create session - acquire write lock + us.sessionMu.Lock() + // Double-check after acquiring write lock to avoid race condition + if us.sessions[sessionID] == nil { + log.Printf("Auto-creating session for ID: %s", sessionID) + us.sessions[sessionID] = NewSession(sessionID, "") + log.Printf("Session auto-created for ID: %s", sessionID) + + // Ensure session directory exists in payload mount point + // This is done after releasing the lock to avoid holding it during I/O + us.sessionMu.Unlock() + if err := us.ensureSessionDirectory(sessionID); err != nil { + logger.LogWarn("client", "Failed to create session directory for session=%s: %v", sessionID, err) + // Don't fail - payloads will attempt to create the directory when needed + } + return nil + } + us.sessionMu.Unlock() } log.Printf("Session validated for ID: %s", sessionID) diff --git a/internal/server/unified_test.go b/internal/server/unified_test.go index 4e1eaf4f..2d2236c9 100644 --- a/internal/server/unified_test.go +++ b/internal/server/unified_test.go @@ -182,10 +182,17 @@ func TestRequireSession(t *testing.T) { err = us.requireSession(ctxWithSession) assert.NoError(t, err, "requireSession() failed for valid session") - // Test with invalid session (DIFC enabled) - ctxWithInvalidSession := context.WithValue(ctx, SessionIDContextKey, "invalid-session") - err = us.requireSession(ctxWithInvalidSession) - require.Error(t, err, "requireSession() should fail for invalid session when DIFC is enabled") + // Test with non-existent session - should auto-create even with DIFC enabled + ctxWithNewSession := context.WithValue(ctx, SessionIDContextKey, "new-session") + err = us.requireSession(ctxWithNewSession) + require.NoError(t, err, "requireSession() should auto-create session even when DIFC is enabled") + + // Verify session was created + us.sessionMu.RLock() + newSession, exists := us.sessions["new-session"] + us.sessionMu.RUnlock() + require.True(t, exists, "Session should have been auto-created") + require.NotNil(t, newSession, "Session should not be nil") } func TestRequireSession_DifcDisabled(t *testing.T) { @@ -432,8 +439,8 @@ func TestRequireSession_EdgeCases(t *testing.T) { enableDIFC: true, sessionID: "nonexistent", preCreate: false, - wantErr: true, - description: "should deny access to nonexistent session when DIFC enabled", + wantErr: false, // Sessions are auto-created regardless of DIFC setting + description: "should auto-create session even when DIFC enabled", }, { name: "DIFC disabled without session",