Skip to content
Merged
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
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ When you run `scc` or `scc start`:
- **Shows Quick Resume** if you have recent sessions for this workspace
- **Prints brief context** (workspace root, entry directory, team) before launching
- **Applies personal profile** (if saved) after team config, before workspace overrides
- **Bypass mode enabled**: Permission prompts are skipped by default since the Docker sandbox already provides isolation. Press `Shift+Tab` inside Claude to toggle permissions back on if needed
- **Bypass mode enabled**: Permission prompts are skipped by default since the Docker sandbox already provides isolation. This does not prevent access to files inside the mounted workspace. Press `Shift+Tab` inside Claude to toggle permissions back on if needed
- **Safety guard**: Won't auto-launch from suspicious directories (home, `/tmp`). Explicit paths like `scc start ~/` prompt for confirmation

**Keyboard shortcuts in dashboard:**
Expand Down Expand Up @@ -110,6 +110,13 @@ Organization security blocks cannot be overridden by teams or developers.

*"Approves" = teams can only select from org-allowed marketplaces; blocks always apply. "Extends" = can add plugins/settings, cannot remove org defaults.*

### Enforcement Scope (v1)

- SCC enforces org-managed plugins and MCP servers at runtime.
- MCP servers in repo `.mcp.json` or plugin bundles are outside SCC enforcement scope (block the plugin to restrict).
- `network_policy` is partially enforced (proxy env injection + MCP suppression under isolated), not a full egress firewall.
- `session.auto_resume` is advisory only in v1.

---

### Organization Setup
Expand Down
4 changes: 2 additions & 2 deletions examples/04-org-stdio-hardened.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
{
"name": "playwright",
"type": "stdio",
"command": "npx",
"command": "/usr/local/bin/npx",
"args": ["@playwright/mcp@latest"]
}
]
Expand All @@ -46,7 +46,7 @@
{
"name": "playwright",
"type": "stdio",
"command": "npx",
"command": "/usr/local/bin/npx",
"args": ["@playwright/mcp@latest"]
},
{
Expand Down
108 changes: 93 additions & 15 deletions examples/99-complete-reference.json
Original file line number Diff line number Diff line change
@@ -1,36 +1,87 @@
{
"$schema": "../src/scc_cli/schemas/org-v1.schema.json",
"schema_version": "1.0.0",
"min_cli_version": "0.5.0",
"min_cli_version": "1.7.0",
"organization": {
"name": "Reference Organization",
"id": "reference-org",
"contact": "docs@example.com"
},
"marketplaces": {
"official-plugins": {
"source": "github",
"owner": "CCimen",
"repo": "sandboxed-code-plugins",
"branch": "main",
"path": "/"
},
"internal-gitlab": {
"source": "git",
"url": "https://gitlab.company.com/scc/plugins.git",
"branch": "main",
"path": "/"
},
"remote-manifest": {
"source": "url",
"url": "https://plugins.company.com/manifest.json",
"headers": {
"Authorization": "Bearer ${PLUGIN_API_TOKEN}"
},
"materialization_mode": "self_contained"
},
"local-dev": {
"source": "directory",
"path": "./local-plugins"
}
},
"security": {
"blocked_plugins": [
"pattern-*",
"exact-plugin-name",
"*-suffix"
"*-suffix",
"*-experimental",
"*-deprecated"
],
"blocked_mcp_servers": [
"*.blocked.com",
"specific-server"
"specific-server",
"*.untrusted.org"
],
"allow_stdio_mcp": true,
"allowed_stdio_prefixes": [
"/usr/local/bin",
"/opt/approved-tools"
]
],
"safety_net": {
"action": "block",
"block_force_push": true,
"block_reset_hard": true,
"block_branch_force_delete": true,
"block_checkout_restore": true,
"block_clean": true,
"block_stash_destructive": true
}
},
"defaults": {
"enabled_plugins": [
"scc-safety-net@official-plugins"
],
"disabled_plugins": [
"legacy-tool@official-plugins"
],
"allowed_plugins": [
"core-*",
"org-approved-*"
"org-approved-*",
"*@official-plugins"
],
"allowed_mcp_servers": [
"https://mcp.example.com/*"
"https://mcp.example.com/*",
"https://*.company.com/*"
],
"extra_marketplaces": [
"official-plugins"
],
"cache_ttl_hours": 24,
"network_policy": "unrestricted",
"session": {
"timeout_hours": 12,
Expand All @@ -41,12 +92,14 @@
"teams": {
"allow_additional_plugins": [
"team-*",
"community-*"
"community-*",
"*"
],
"allow_additional_mcp_servers": [
"team-a",
"team-b",
"special-team"
"special-team",
"federated-team"
]
},
"projects": {
Expand All @@ -57,16 +110,16 @@
"team-a": {
"description": "Example team A with HTTP MCP servers (authenticated and public)",
"additional_plugins": [
"team-plugin-1",
"team-plugin-2"
"team-plugin-1@official-plugins",
"team-plugin-2@official-plugins"
],
"additional_mcp_servers": [
{
"name": "context7",
"type": "http",
"url": "https://mcp.context7.com/mcp",
"headers": {
"CONTEXT7_API_KEY": "${CONTEXT7_API_KEY}"
"X-API-Key": "${CONTEXT7_API_KEY}"
}
},
{
Expand Down Expand Up @@ -101,26 +154,51 @@
}
},
"special-team": {
"description": "Team with stdio MCP server (requires allow_stdio_mcp: true)",
"description": "Team with stdio MCP server (requires allow_stdio_mcp: true in security)",
"additional_mcp_servers": [
{
"name": "playwright",
"type": "stdio",
"command": "npx",
"command": "/usr/local/bin/npx",
"args": [
"@playwright/mcp@latest"
],
"env": {
"PLAYWRIGHT_BROWSERS_PATH": "${HOME}/.cache/ms-playwright"
"PLAYWRIGHT_BROWSERS_PATH": "${HOME}/.cache/ms-playwright",
"DEBUG": "pw:api"
}
}
],
"network_policy": "isolated"
},
"federated-team": {
"description": "Federated team with external config source (team manages own config)",
"config_source": {
"source": "github",
"owner": "company",
"repo": "team-config",
"branch": "main",
"path": "scc/team-config.json",
"headers": {
"Authorization": "Bearer ${GITHUB_TOKEN}"
}
},
"trust": {
"inherit_org_marketplaces": true,
"allow_additional_marketplaces": true,
"marketplace_source_patterns": [
"https://github.com/company/*",
"https://gitlab.company.com/*"
]
},
"delegation": {
"allow_project_overrides": true
}
}
},
"stats": {
"enabled": true,
"user_identity_mode": "hash",
"retention_days": 365
"retention_days": 90
}
}
10 changes: 9 additions & 1 deletion src/scc_cli/adapters/docker_sandbox_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
from __future__ import annotations

from scc_cli import docker
from scc_cli.core.enums import NetworkPolicy
from scc_cli.core.network_policy import collect_proxy_env
from scc_cli.ports.models import SandboxHandle, SandboxSpec, SandboxState, SandboxStatus
from scc_cli.ports.sandbox_runtime import SandboxRuntime

Expand All @@ -26,13 +28,18 @@ def ensure_available(self) -> None:

def run(self, spec: SandboxSpec) -> SandboxHandle:
docker.prepare_sandbox_volume_for_credentials()
env_vars = dict(spec.env) if spec.env else {}
if spec.network_policy == NetworkPolicy.CORP_PROXY_ONLY.value:
for key, value in collect_proxy_env().items():
env_vars.setdefault(key, value)
runtime_env = env_vars or None
docker_cmd, _is_resume = docker.get_or_create_container(
workspace=spec.workspace_mount.source,
branch=None,
profile=None,
force_new=spec.force_new,
continue_session=spec.continue_session,
env_vars=spec.env or None,
env_vars=runtime_env,
)
container_name = _extract_container_name(docker_cmd)
plugin_settings = spec.agent_settings.content if spec.agent_settings else None
Expand All @@ -41,6 +48,7 @@ def run(self, spec: SandboxSpec) -> SandboxHandle:
org_config=spec.org_config,
container_workdir=spec.workdir,
plugin_settings=plugin_settings,
env_vars=runtime_env,
)
return SandboxHandle(
sandbox_id=container_name or "sandbox",
Expand Down
Loading
Loading