The Problem: “It’s Just a GitHub Repo… What Could Go Wrong?”
As developers, we live on GitHub.
It’s second nature to clone a new repo, install dependencies, and run it — especially if it looks legit, comes from a job interviewer, or promises a cool new framework demo.
But what if that repo isn’t what it seems?
In a blog post by David Dodda, he shared how a fake interview coding task almost led him to execute malware on his laptop.
The attacker simply sent him a “GitHub repo” and asked him to run it quickly during the interview.
No zero-day exploits. No kernel hacking. Just developer trust — exploited.
This isn’t rare. Modern attacks increasingly target developers themselves, because developer machines often hold API keys, SSH credentials, cloud configs, and internal tokens.
What Can Go Wrong When You “Just Run It”
Let’s break it down:
-
Malicious scripts can steal ~/.ssh/id_rsa
, .aws/credentials
, or browser cookies.
-
npm/pip/postinstall hooks can silently execute arbitrary shell commands.
-
Dockerfiles and Makefiles can run curl | bash
patterns on your host.
-
Container escapes or volume mounts can expose your real home directory.
-
Credential managers (like gh auth
, aws configure
, gcloud
) can be hijacked.
In short: “git clone && run” is the new double-clicking random .exe
.
The Safer Way: Use a Dev Container Sandbox
The solution isn’t to stop exploring open source — it’s to do it safely.
A dev container (like those supported by VS Code or Podman/Docker) gives you an isolated environment that mirrors a developer machine but with strong boundaries:
-
Separate filesystem
-
Non-root user
-
Optional network isolation
-
Disposable (destroy on exit)
-
No host credentials by default
So even if the code is malicious, it’s trapped inside the sandbox.
Our Solution: The safe-dev-runner
To make this safe workflow easy, I created safe-dev-runner — a lightweight way to clone and run any GitHub project inside a locked-down dev container.
Step 1 – Clone the Runner
git clone https://github.com/Randhir123/safe-dev-runner.git
cd safe-dev-runner
Step 2 – Build the image
./dev.sh --build
This pulls mcr.microsoft.com/devcontainers/base:ubuntu
, installs packages, and tags the result as dev:latest
. Re-run whenever you modify dev.dockerfile
.
Step 3 - Launching the Dev Shell
From any project directory:
/path/to/dev.sh
The script mounts the current directory and caches into the container. You drop into /bin/bash
as user vscode
(UID 1000). Install dependencies and run commands there—your working tree stays on the host.
Example Workflow
Let's dry run this solution. We want to safely run examples in repository - https://github.com/a2aproject/a2a-samples.git. This repository contains code samples and demos which use the Agent2Agent (A2A) Protocol.
Clone and Work on a GitHub Project
% git clone https://github.com/a2aproject/a2a-samples.git
Cloning into 'a2a-samples'...
remote: Enumerating objects: 5055, done.
remote: Counting objects: 100% (131/131), done.
remote: Compressing objects: 100% (56/56), done.
remote: Total 5055 (delta 90), reused 75 (delta 75), pack-reused 4924 (from 2)
Receiving objects: 100% (5055/5055), 27.77 MiB | 15.23 MiB/s, done.
Resolving deltas: 100% (2804/2804), done.
% cd a2a-samples
% ~/pers/dev_containers/dev.sh
This launches the dev container and mounts the folder inside it. Once inside, navigate to the sample that you want to run. Start the server inside the dev container.
vscode ➜ /Users/randhirkumarsingh/pers/a2a-samples (main) $ cd samples/python/agents/
vscode ➜ .../a2a-samples/samples/python/agents (main) $ ls
README.md a2a_telemetry adk_facts analytics beeai-chat crewai github-agent langgraph mindsdb travel_planner_agent
a2a-mcp-without-framework adk_cloud_run ag2 any_agent_adversarial_multiagent birthday_planner_adk dice_agent_grpc headless_agent_auth llama_index_file_chat number_guessing_game veo_video_gen
a2a_mcp adk_expense_reimbursement airbnb_planner_multiagent azureaifoundry_sdk content_planner dice_agent_rest helloworld marvin semantickernel
vscode ➜ .../a2a-samples/samples/python/agents (main) $ cd helloworld/
vscode ➜ .../samples/python/agents/helloworld (main) $ ls
Containerfile README.md __init__.py __main__.py agent_executor.py pyproject.toml test_client.py uv.lock
vscode ➜ .../samples/python/agents/helloworld (main) $ uv run .
Using CPython 3.12.3 interpreter at: /usr/bin/python3
Creating virtual environment at: .venv
Built helloworld @ file:///Users/randhirkumarsingh/pers/a2a-samples/samples/python/agents/helloworld
░░░░░░░░░░░░░░░░░░░░ [0/55] Installing wheels... warning: Failed to hardlink files; falling back to full copy. This may lead to degraded performance.
If the cache and target directories are on different filesystems, hardlinking may not be supported.
If this is intentional, set `export UV_LINK_MODE=copy` or use `--link-mode=copy` to suppress this warning.
Installed 55 packages in 1.38s
INFO: Started server process [217]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:9999 (Press CTRL+C to quit)
To run the client, ssh into the same container and start client.
% podman container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
09d03112ed08 localhost/dev:latest 7 minutes ago Up 7 minutes great_hellman
% podman exec -it 09d03112ed08 /bin/bash
vscode ➜ /Users/randhirkumarsingh/pers/a2a-samples $ ls
CODE_OF_CONDUCT.md CONTRIBUTING.md LICENSE README.md SECURITY.md demo extensions format.sh notebooks pyproject.toml requirements-dev.txt samples
vscode ➜ /Users/randhirkumarsingh/pers/a2a-samples (main) $ cd samples/python/agents/helloworld/
vscode ➜ .../samples/python/agents/helloworld (main) $ uv run test_client.py
INFO:__main__:Attempting to fetch public agent card from: http://localhost:9999/.well-known/agent-card.json
INFO:httpx:HTTP Request: GET http://localhost:9999/.well-known/agent-card.json "HTTP/1.1 200 OK"
INFO:a2a.client.card_resolver:Successfully fetched agent card data from http://localhost:9999/.well-known/agent-card.json: {'capabilities': {'streaming': True}, 'defaultInputModes': ['text'], 'defaultOutputModes': ['text'], 'description': 'Just a hello world agent', 'name': 'Hello World Agent', 'preferredTransport': 'JSONRPC', 'protocolVersion': '0.3.0', 'skills': [{'description': 'just returns hello world', 'examples': ['hi', 'hello world'], 'id': 'hello_world', 'name': 'Returns hello world', 'tags': ['hello world']}], 'supportsAuthenticatedExtendedCard': True, 'url': 'http://localhost:9999/', 'version': '1.0.0'}
INFO:__main__:Successfully fetched public agent card:
INFO:__main__:{
"capabilities": {
"streaming": true
},
"defaultInputModes": [
"text"
],
"defaultOutputModes": [
"text"
],
"description": "Just a hello world agent",
"name": "Hello World Agent",
"preferredTransport": "JSONRPC",
"protocolVersion": "0.3.0",
"skills": [
{
"description": "just returns hello world",
"examples": [
"hi",
"hello world"
],
"id": "hello_world",
"name": "Returns hello world",
"tags": [
"hello world"
]
}
],
"supportsAuthenticatedExtendedCard": true,
"url": "http://localhost:9999/",
"version": "1.0.0"
}
INFO:__main__:
Using PUBLIC agent card for client initialization (default).
INFO:__main__:
Public card supports authenticated extended card. Attempting to fetch from: http://localhost:9999/agent/authenticatedExtendedCard
INFO:httpx:HTTP Request: GET http://localhost:9999/agent/authenticatedExtendedCard "HTTP/1.1 200 OK"
INFO:a2a.client.card_resolver:Successfully fetched agent card data from http://localhost:9999/agent/authenticatedExtendedCard: {'capabilities': {'streaming': True}, 'defaultInputModes': ['text'], 'defaultOutputModes': ['text'], 'description': 'The full-featured hello world agent for authenticated users.', 'name': 'Hello World Agent - Extended Edition', 'preferredTransport': 'JSONRPC', 'protocolVersion': '0.3.0', 'skills': [{'description': 'just returns hello world', 'examples': ['hi', 'hello world'], 'id': 'hello_world', 'name': 'Returns hello world', 'tags': ['hello world']}, {'description': 'A more enthusiastic greeting, only for authenticated users.', 'examples': ['super hi', 'give me a super hello'], 'id': 'super_hello_world', 'name': 'Returns a SUPER Hello World', 'tags': ['hello world', 'super', 'extended']}], 'supportsAuthenticatedExtendedCard': True, 'url': 'http://localhost:9999/', 'version': '1.0.1'}
INFO:__main__:Successfully fetched authenticated extended agent card:
INFO:__main__:{
"capabilities": {
"streaming": true
},
"defaultInputModes": [
"text"
],
"defaultOutputModes": [
"text"
],
"description": "The full-featured hello world agent for authenticated users.",
"name": "Hello World Agent - Extended Edition",
"preferredTransport": "JSONRPC",
"protocolVersion": "0.3.0",
"skills": [
{
"description": "just returns hello world",
"examples": [
"hi",
"hello world"
],
"id": "hello_world",
"name": "Returns hello world",
"tags": [
"hello world"
]
},
{
"description": "A more enthusiastic greeting, only for authenticated users.",
"examples": [
"super hi",
"give me a super hello"
],
"id": "super_hello_world",
"name": "Returns a SUPER Hello World",
"tags": [
"hello world",
"super",
"extended"
]
}
],
"supportsAuthenticatedExtendedCard": true,
"url": "http://localhost:9999/",
"version": "1.0.1"
}
INFO:__main__:
Using AUTHENTICATED EXTENDED agent card for client initialization.
/Users/randhirkumarsingh/pers/a2a-samples/samples/python/agents/helloworld/test_client.py:105: DeprecationWarning: A2AClient is deprecated and will be removed in a future version. Use ClientFactory to create a client with a JSON-RPC transport.
client = A2AClient(
INFO:__main__:A2AClient initialized.
INFO:httpx:HTTP Request: POST http://localhost:9999/ "HTTP/1.1 200 OK"
{'id': '1cf9eb0d-acbe-42b3-8dc1-221d2313afba', 'jsonrpc': '2.0', 'result': {'kind': 'message', 'messageId': '3688180e-c299-43bf-9fd5-f537abcffbd0', 'parts': [{'kind': 'text', 'text': 'Hello World'}], 'role': 'agent'}}
INFO:httpx:HTTP Request: POST http://localhost:9999/ "HTTP/1.1 200 OK"
{'id': 'c0df8bf9-c6f6-4a97-8e3e-d31d77d07616', 'jsonrpc': '2.0', 'result': {'kind': 'message', 'messageId': 'ced9cea4-0fde-42b9-a1ee-b718a06f1524', 'parts': [{'kind': 'text', 'text': 'Hello World'}], 'role': 'agent'}}
vscode ➜ .../samples/python/agents/helloworld (main) $
Because both processes live in the same container, no extra port publishing is required. We have demonstrated how to run a project from GitHub safely inside a dev container.
Conclusion
Running open-source code safely is now a developer security skill.
The incident that was described could happen to anyone — but it’s preventable with a little discipline and the right tooling.
Use containers as your armor, not your afterthought.
And if you want a simple, reusable setup — grab safe-dev-runner and start exploring securely today.