A laptop on a wooden desk

Coding Securely in the Age of AI

I have moved my development environments off my desktop.

There is a reason for that change. In my ChainDrop write-up, This was a huge wake up call and after the dust settled I asked myself "how should I rethink my developer experience?" and what are the new truths now and in the future with coding. I came to two very important realizations:

  • AI is so good that it most likely does not need access to configs / secrets to write the code as long as you have end to end testing with your CI/CD as it can validate there.
  • My desktop has access to sensitive files, credentials, and accounts. Running untrusted code there puts too much at risk.

These truths really changed how I approached my desktop configuration and actually have made things even better.

How I Configure My Desktop / Laptop

I still use a Mac, but I’m considering Linux now that most of my development happens in the cloud. With builds running through CI/CD or Xcode Cloud, I have less need for Xcode locally. My desktop setup is pretty simple:

  • Safari: Everyday browsing and web-based email.
  • Brave: Development work with Claude, Codex, Cursor, GitHub, and similar tools.
  • Visual Studio Code: Reviewing and editing code through GitHub Codespaces. It feels local, but the code runs in a separate cloud environment.
  • Password Manager: Installed locally, with no access granted to coding agents.
  • Screen Recording App: The desktop app gives me better recordings than the browser-based tools I’ve tried.
  • Slack: I still use the desktop app because the web version has felt sluggish, though I may give it another try.
  • Cloud Drive: I still need it, even though it holds a lot of personal information. Keeping development isolated makes me more comfortable leaving it connected.

That’s about it. My desktop is simpler, and I’m still just as productive.

Configuring AI

The Keyv repository shows how I configure my development environments for more secure coding, with settings in .cursor, .devcontainer, and .claude. I plan to add .codex as well.

Most of my Codex work happens in Visual Studio Code connected to GitHub Codespaces. I prefer that workflow to the Codex Cloud interface. Once the Dev Container is configured, I install the Codex extension and start working directly in the cloud workspace.

I put this security setup into my defense-in-depth skill so an AI agent can help configure the protections around its own work. It covers Aikido Safe Chain for package installs in cloud development environments, a seven-day dependency cooldown, pinned GitHub Actions and container images, and limited CI permissions. To use it, connect your agent to my agentic MCP server and ask it to apply defense-in-depth-nodejs to your repository. It audits the setup, works through one improvement per pull request, and tracks progress in DEFENSE_IN_DEPTH.md. Review and merge each change, then tell the agent continue to pick up the next item. It currently targets Node.js, but I plan to expand it to other languages as I move more of my work toward Rust and TypeScript. I have already taken that approach with my dependency-management skill, which supports Node.js, Rust, Python, Go, and .NET.

What My Agents Can Access

I keep credentials out of coding agents' prompts and development workspaces by default. That includes production environment files, service API keys, database passwords, and publishing tokens. The agent gets the code and the test setup it needs to work on the change.

If a task needs a token, I create a temporary one for a non-production service with a very short time to live (TTL). I revoke and delete it as soon as the PR is done, even if it has not expired yet. I never give agents production tokens or GitHub access. Keeping those permissions under my control preserves the checks and balances around reviewing, merging, and releasing their work.

Validation starts inside that workspace. Agents can use mocks to test behavior, or spin up local services and load seeded test data. If a change needs a database or another service to test properly, I explicitly ask the agent to set that up. Local in this case means inside the isolated development environment. That gives it a way to exercise the application, reproduce failures, and test edge cases without connecting to production.

End-to-end validation against real integrations happens at the CI/CD layer. Any credentials needed for those runs stay there. Moving credentials into CI still means reviewing the code that will run with them: a job can use the secrets and permissions it receives. That is why protecting the workflows themselves is part of this setup.

All agent-written code goes through the pull request process. The defense-in-depth checklist defines the controls around that process, from the first dependency install through release:

  • Pull requests and required checks: Changes to the default branch go through a PR, with the repository's required CI checks configured as merge gates. Direct pushes, force pushes, and branch deletion are blocked. The baseline retains an owner bypass through the PR process, so it does not guarantee an independent reviewer on every change.
  • Review of sensitive configuration: CODEOWNERS covers workflows, scripts, and the editor, agent, and Dev Container configuration. Code-owner review is required for changes to those paths, subject to that owner bypass. On public repositories, workflow runs from outside contributors also require maintainer approval.
  • Dependency controls: The package manager is pinned, the lockfile is committed, and CI uses frozen-lockfile installs. New package versions have a seven-day cooldown, including my own packages; missing release timestamps fail the check. Dependency lifecycle scripts are blocked by default, with exceptions reviewed explicitly. pnpm also blocks exotic transitive dependency sources and rejects versions with weaker publishing trust evidence through trustPolicy: no-downgrade.
  • Checks during installation: Cloud development environments install packages through Aikido Safe Chain. Its bootstrap uses a pinned release and verifies the installer checksum, and failed setup leaves installs blocked. CI jobs install Socket Firewall, and package installs run through its sfw wrapper. These checks happen when packages are installed, alongside the separate review of dependency changes.
  • Pinned build tools: GitHub Actions are pinned to full commit SHAs, and Dev Container images are pinned by digest. The repository restricts which Actions can run. This keeps the selected action code and container image tied to specific versions when a tag changes upstream.
  • Limited workflow permissions: CI starts with read-only repository access. Extra permissions are granted only to jobs that need them, and checkouts that do not push leave no persisted Git credentials. Generated output is saved as an artifact. Repository write access is reserved for jobs whose purpose requires it, such as creating a release.
  • Workflow security checks: zizmor checks workflows on every PR for problems such as script injection, excessive permissions, and credential persistence. Workflows that execute untrusted PR code do not use pull_request_target. Caches are kept separate across trust boundaries, and artifact-publishing workflows disable package-manager caching to reduce the risk of a poisoned cache reaching a privileged job.
  • Code and dependency scanning: Aikido scans every build for vulnerabilities, exposed secrets, and code issues. Socket reviews dependency changes for suspicious behavior such as new install scripts, network access, or obfuscated code. Secret scanning and push protection add repository-level checks where supported. This baseline disables Dependabot in favor of Aikido and Socket; other dependency updaters can open PRs, with automatic merging disabled.

For npm libraries, publishing has another set of checks. The release workflow must pass an Aikido scan before staging a package. CI uses short-lived OIDC authentication with permission to stage only, so there is no npm publish token in the workspace or CI secrets. I use Drydock for a final check of the exact staged package before publication. It compares the package with the previous release and flags suspicious changes, such as new install scripts or credential reads. After reviewing the report, a maintainer approves promotion in npm with 2FA. Package settings require 2FA and disallow token-based publishing, while repository metadata keeps provenance tied to the correct project. Tag creation is restricted to repository admins, and newly published GitHub releases have immutable assets and tags.

The account and administrative controls matter too. The checklist includes passkeys or hardware keys for GitHub and npm, securely stored recovery codes, and private vulnerability reporting for public repositories. A repository admin applies the final lockdown settings after the earlier controls are in place. Each repo tracks its actual rollout in DEFENSE_IN_DEPTH.md, and its public SECURITY.md describes the protections already implemented.

Final Thoughts

I keep refining this setup as new security incidents emerge. It has helped me stay productive while reducing my attack surface. I hope it gives you a useful starting point for securing your own development workflow.

Next
RustBoy AI: Learning About Agents by Making a Movie