9 views
A coding agent that can npm test, edit files, and open PRs is useful. The same agent with long-lived credentials, outbound internet, and a shared EFS volume is a ransomware kit with a chatbot UI. On AWS, the unfair advantage is not “more tools” — it is an AWS Lambda coding agent sandbox that makes every tool call a sealed, auditable transaction.
⚡ TL;DR: Run each agent tool (lint, test, patch apply, repo read) inside an AWS Lambda coding agent sandbox: dedicated Lambda, tiny IAM policy, no standing secrets in env, and network locked to VPC endpoints you choose. Pass a per-session workspace via S3 (or ephemeral /tmp) — never a shared writable disk across tenants. Enforce wall-clock timeouts (e.g. 30–120s), memory caps, and concurrency limits. Return structured tool results (exit code, truncated stdout) to the model — not raw root shells. Illustrative: a 1024MB Node sandbox cold-starting in ~200–600ms, warm path under ~50–150ms for simple file ops.
For orchestration and privilege boundaries, see Amazon Bedrock Agents guardrails, AI code review bot IAM, and Lambda warm pools for agent tools.
Why an AWS Lambda coding agent sandbox beats always-on VMs
Long-running agent hosts feel convenient until one session leaves a maliciouspostinstall running. Lambda gives you:
- Process isolation per invocation — crash and malware die with the execution environment recycle.
- IAM as the real sandbox boundary — the model cannot escalate past the role.
- Cost that tracks tool use — idle agents cost $0; a misbehaving loop hits reserved concurrency and stops.
text
❌ Don’t give one “AgentRuntime” role User → Orchestrator (Bedrock / custom) → Tool router
├─ ReadRepoLambda (S3 GetObject only)
├─ ApplyPatchLambda (S3 read/write session prefix only)
├─ RunTestsLambda (no AWS APIs; VPC deny egress)
└─ OpenPRLambda (GitHub App token from Secrets Manager, scoped)
AdministratorAccess and hope prompt instructions hold.✅ Split tools; each Lambda’s policy is the capability the model is allowed to have.
Session workspaces: S3 prefixes, not shared disks
Treat every agent session ass3://agent-workspaces/{tenantId}/{sessionId}/. The orchestrator copies the repo snapshot (or sparse checkout artifact) into that prefix; tools only see that prefix.
typescript
IAM for ApplyPatch:
// apply-patch/handler.ts
import { S3Client, GetObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3";
import { createHash } from "crypto";
import { promises as fs } from "fs";
import path from "path";
import { execFile } from "child_process";
import { promisify } from "util";
const execFileAsync = promisify(execFile);
const s3 = new S3Client({});
const BUCKET = process.env.WORKSPACE_BUCKET!;
const MAX_DIFF_BYTES = 200_000; // ✅ hard cap — illustrative
type PatchEvent = {
tenantId: string;
sessionId: string;
relativePath: string;
unifiedDiff: string;
};
export const handler = async (event: PatchEvent) => {
assertIds(event.tenantId, event.sessionId);
if (Buffer.byteLength(event.unifiedDiff, "utf8") > MAX_DIFF_BYTES) {
return { ok: false, error: "diff_too_large" };
}
if (event.relativePath.includes("..") || path.isAbsolute(event.relativePath)) {
return { ok: false, error: "invalid_path" }; // ❌ path traversal
}
const prefix = `tenants/${event.tenantId}/sessions/${event.sessionId}/`;
const key = `${prefix}${event.relativePath}`;
const local = `/tmp/work/${event.relativePath}`;
await fs.mkdir(path.dirname(local), { recursive: true });
const obj = await s3.send(new GetObjectCommand({ Bucket: BUCKET, Key: key }));
const body = await obj.Body!.transformToByteArray();
await fs.writeFile(local, body);
// Apply with `patch` binary — no shell metacharacters
const diffPath = "/tmp/change.diff";
await fs.writeFile(diffPath, event.unifiedDiff);
try {
// ✅ execFile argv array — never exec(`patch < ${user}`)
await execFileAsync("patch", ["-p0", "--batch", "-i", diffPath], {
cwd: "/tmp/work",
timeout: 10_000,
maxBuffer: 2_000_000,
});
} catch (e: any) {
return { ok: false, error: "patch_failed", detail: String(e?.stderr || e).slice(0, 2000) };
}
const patched = await fs.readFile(local);
await s3.send(
new PutObjectCommand({
Bucket: BUCKET,
Key: key,
Body: patched,
Metadata: {
sha256: createHash("sha256").update(patched).digest("hex"),
session: event.sessionId,
},
})
);
return { ok: true, bytes: patched.length };
};
function assertIds(tenantId: string, sessionId: string) {
const re = /^[a-zA-Z0-9_-]{8,64}$/;
if (!re.test(tenantId) || !re.test(sessionId)) throw new Error("bad_ids");
}
json
Pass {
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": "arn:aws:s3:::agent-workspaces/tenants/${aws:PrincipalTag/tenantId}/sessions/*"
}
tenantId as a session tag on the assumed role so the condition key works. Illustrative pattern: orchestrator assumes AgentSessionRole with sts:TagSession.
Network deny-by-default for “run tests” sandboxes
RunTestsLambda should not call the public internet unless you explicitly allow a package mirror. Prefer:
- Bundle dependencies in the workspace artifact (CI already resolved lockfiles).
- Or allowlist an internal CodeArtifact / npm mirror via VPC endpoint / PrivateLink.
- Block SSM, STS, and cloud metadata misuse by not attaching broad AWS permissions at all.
hcl
resource "aws_security_group" "test_sandbox" {
name = "agent-test-sandbox"
vpc_id = var.vpc_id
# ❌ No egress 0.0.0.0/0
# ✅ Optional: allow HTTPS only to VPC endpoint SGs for CodeArtifact/S3
egress {
from_port = 443
to_port = 443
protocol = "tcp"
security_groups = [var.codeartifact_vpce_sg]
}
}
resource "aws_lambda_function" "run_tests" {
function_name = "agent-run-tests"
runtime = "nodejs20.x"
handler = "index.handler"
role = aws_iam_role.tests_no_aws_apis.arn
timeout = 120 # illustrative wall clock
memory_size = 2048 # tests often need CPU ~ proportional to memory
reserved_concurrent_executions = 20 # blast-radius cap
vpc_config {
subnet_ids = var.private_subnet_ids
security_group_ids = [aws_security_group.test_sandbox.id]
}
environment {
variables = {
# ❌ No GITHUB_TOKEN here
MAX_STDOUT_BYTES = "100000"
}
}
}
typescript
Illustrative latency: warm lint tool ~0.5–2s; full unit suite 15–90s — set the agent’s UX to “job started” rather than blocking a chat bubble for 90s. Fan long tests out via Step Functions or SQS if the orchestrator needs to stay snappy.
// run-tests — return truncated structured output to the LLM
export const handler = async (evt: { sessionPath: string; cmd: "test" | "lint" }) => {
const allowed = {
test: ["npm", ["test", "--", "--runInBand"]],
lint: ["npm", ["run", "lint"]],
} as const;
const [bin, args] = allowed[evt.cmd];
try {
const { stdout, stderr } = await execFileAsync(bin, args, {
cwd: evt.sessionPath,
timeout: 90_000,
maxBuffer: 100_000,
env: { PATH: "/usr/local/bin:/usr/bin", HOME: "/tmp", CI: "1" }, // ✅ minimal env
});
return {
ok: true,
exitCode: 0,
stdout: stdout.slice(0, 100_000),
stderr: stderr.slice(0, 20_000),
};
} catch (e: any) {
return {
ok: false,
exitCode: e.code ?? 1,
stdout: String(e.stdout || "").slice(0, 100_000),
stderr: String(e.stderr || e.message).slice(0, 20_000),
};
}
};
Secrets: short-lived, task-scoped, never in prompts
Opening a PR needs a GitHub App installation token — mint it insideOpenPRLambda, use once, discard.
python
Pair with Secrets Manager rotation and CloudTrail data events on the secret ARN. If the model “needs” credentials, it needs a tool, not a string in the system prompt.
# open_pr/handler.py — illustrative
import os, time, jwt, urllib.request, json, boto3
sm = boto3.client("secretsmanager")
def handler(event, _ctx):
# event: {tenantId, sessionId, branch, title, body}
secret = json.loads(sm.get_secret_value(SecretId=os.environ["GH_APP_SECRET_ARN"])["SecretString"])
token = mint_installation_token(secret, event["installation_id"])
# ✅ token lives only in this invocation's memory
# ❌ never return token to the model / never log Authorization headers
pr = create_pull_request(token, event)
return {"ok": True, "prUrl": pr["html_url"], "number": pr["number"]}
def mint_installation_token(secret, installation_id: int) -> str:
now = int(time.time())
payload = {"iat": now - 60, "exp": now + 540, "iss": secret["app_id"]}
app_jwt = jwt.encode(payload, secret["private_key"], algorithm="RS256")
req = urllib.request.Request(
f"https://api.github.com/app/installations/{installation_id}/access_tokens",
method="POST",
headers={
"Authorization": f"Bearer {app_jwt}",
"Accept": "application/vnd.github+json",
},
)
with urllib.request.urlopen(req, timeout=10) as resp:
return json.load(resp)["token"]
Orchestrator contract: structured tools only
Expose tools to the LLM as JSON schemas with enums — the model pickscmd: "lint" | "test", never a free-form shell string.
json
✅ Allowlist binaries and argv.{
"name": "run_checks",
"description": "Run lint or unit tests in the session sandbox",
"parameters": {
"type": "object",
"additionalProperties": false,
"required": ["cmd"],
"properties": {
"cmd": { "type": "string", "enum": ["lint", "test"] }
}
}
}
❌
{"cmd": "bash", "args": "-c", "script": user_text} is how you fail a security review.
Closing checklist
✅ Dos – ✅ One Lambda role per tool capability; session-tagged S3 prefixes – ✅execFile + allowlists; truncate stdout/stderr before returning to the model
– ✅ Deny-by-default egress; reserved concurrency as a kill switch
– ✅ Mint SCM tokens inside the PR tool; never persist them in DynamoDB chat logs
– ✅ Timeouts that match the tool (10s patch, 120s tests) — not 900s everywhere
❌ Don’ts
– ❌ Don’t run coding agents on a shared EC2 with Docker-in-Docker “for flexibility”
– ❌ Don’t put long-lived PATs in Lambda environment variables
– ❌ Don’t let the model supply shell strings or absolute paths
– ❌ Don’t share /tmp or EFS across tenants without ironclad isolation
– ❌ Don’t skip CloudWatch Logs redaction for Authorization and cookie headers
Related reading
- AWS Lambda Best Practices: Write Functions That Scale and Never Time Out
- Lambda in VPC Without the 10-Second Cold Start
- Hyper-Scale Serverless API Platform on AWS
- Terraform for Developers: Infrastructure as Code From Zero to Production AWS
- Amazon Bedrock Agents: Tool Use, Memory, and Production Guardrails (companion post)
- AI Code Review Bots: IAM, Secrets, and Least-Privilege Pipelines (companion post)
- CodeBuild sandboxes for AI coding agents
- Lambda destinations for failed agent tool invokes
- IAM condition keys for agent runtimes
Watch: 100 Days of AI on YouTube
Short videos from CheatCoders, one AI engineering topic per day.
Most viewed
- Python Decorators Explained: From Simple Wrappers to Production Patterns
- Claude Code vs Cursor vs GitHub Copilot: Choose by Workflow, and Set Up One Repo That Works With All Three
- AI Agent Frameworks in 2025: LangGraph vs CrewAI vs AutoGen vs Raw API
- Python String Methods: Every str Method With Real Production Examples
- REST API Design Best Practices: The Patterns That Make APIs a Joy to Use
Newly added
- AWS Systems Manager Parameter Store: Hierarchical Runtime Config for Coding Agents
- Amazon Cognito Identity Pools: Federate Coding-Agent Tool Callers Without Long-Lived Keys
- Amazon Athena for Coding Agents: Query Tool Audit Logs and Eval Traces on S3 Without a Warehouse
- AWS Batch for Coding Agents: Parallel Overnight PR Reviews and Long-Running Jobs Without Lambda Timeouts
- Amazon Bedrock Prompt Management: Version and A/B Test Coding-Agent System Prompts
Deep-dive PDF
Get the expanded guide for this post — extra diagrams-style checklists, failure modes, and a production walkthrough. Free when you subscribe to CheatCoders.
Already subscribed? or open the subscribe page.
Discover more from CheatCoders
Subscribe to get the latest posts sent to your email.

Pingback: Lambda Cold Starts on Node 20: Measure, Cut, and Keep Cutting - CheatCoders
Pingback: Cursor Rules for TypeScript Monorepos: Make AI Edits Stick - CheatCoders
Pingback: Bedrock Batch Inference: Overnight Refactor Diffs Across Large Repos - CheatCoders
So I’ve been wondering what exactly an SEO consultant spends their day doing, and after working with one for a few months I think I finally get it. They basically dig through your website, figure out what’s broken or slow, check what competitors are ranking for, and then give you a plan to fix things step by step. It’s less magic and more spreadsheets than people assume.
I guess the short version is they make sure Google and actual users can find and trust your site, through a mix of technical tweaks, content advice, and link building. It’s not an overnight fix though, mine warned me it’d take months before seeing real movement, and he was right. Has anyone had a consultant who delivered faster results, or is that just unrealistic?
So I hired an SEO consultant last year because I was drowning in Google Analytics and had no clue what half those metrics meant. Honestly the best money I spent was just getting someone to explain what actually matters instead of chasing every shiny metric. He kept telling me to focus on three things and ignore the rest, which felt weird at first but made sense later.
If I could go back I’d ask way more questions before signing anything, like how they measure success and what tools they actually use. A lot of this industry runs on jargon that sounds impressive but means nothing in practice. What questions do you guys usually ask before trusting someone with your site?
entity SEO explained [url=https://yourhomeyourwayltd.co.uk/what-an-seo-consultant-actually-does-advice/#entity-SEO-explained]https://yourhomeyourwayltd.co.uk/what-an-seo-consultant-actually-does-advice/[/url] .