惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

V
V2EX
人人都是产品经理
人人都是产品经理
WordPress大学
WordPress大学
博客园 - Franky
小众软件
小众软件
酷 壳 – CoolShell
酷 壳 – CoolShell
Apple Machine Learning Research
Apple Machine Learning Research
爱范儿
爱范儿
IT之家
IT之家
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
V
Visual Studio Blog
S
SegmentFault 最新的问题
美团技术团队
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
有赞技术团队
有赞技术团队
B
Blog RSS Feed
Last Week in AI
Last Week in AI
Jina AI
Jina AI
博客园 - 司徒正美
The Cloudflare Blog
博客园_首页
博客园 - 聂微东
宝玉的分享
宝玉的分享
大猫的无限游戏
大猫的无限游戏

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
SSH Agent Forwarding vs ProxyJump: Why Agent Forwarding I...
Mahafuzur Rahaman · 2026-05-31 · via DEV Community

Thousands of tutorials recommend ForwardAgent yes. Most of them don't tell you what it actually does to your security posture. Here's the full picture.


You need to SSH from your laptop to a bastion, then from the bastion to an internal server. You've seen the solution in a dozen tutorials:

Host bastion
    ForwardAgent yes

Enter fullscreen mode Exit fullscreen mode

It works. It's convenient. And it creates a security hole that could let anyone with root on the bastion impersonate you to every server your key unlocks — for as long as your session is open.

This isn't a theoretical risk. It's a well-documented attack vector with a name: SSH agent hijacking. And the fix — ProxyJump — has been available since 2017 and solves the same problem without the exposure.

This article explains exactly what agent forwarding does under the hood, why it's dangerous, when (if ever) it's acceptable, and how ProxyJump eliminates the need for it in the most common use case.


What SSH Agent Forwarding Actually Does

To understand the risk, you need to understand the mechanism.

The SSH Agent

ssh-agent is a background process that holds your decrypted private keys in memory. When you run ssh host, the SSH client asks the agent to perform cryptographic operations (signing challenges) on its behalf. Your private key never leaves the agent — the client just asks the agent to sign things.

Your SSH client  ──── "sign this challenge" ────►  ssh-agent
                 ◄─── "here's the signature" ─────  ssh-agent

Enter fullscreen mode Exit fullscreen mode

The agent communicates through a Unix socket, stored at a path like /tmp/ssh-agent.XXXXX/agent.12345. The environment variable SSH_AUTH_SOCK points to this socket.

What Forwarding Does

When you SSH to a remote server with ForwardAgent yes, SSH does something specific: it creates a new socket on the remote server that tunnels back to your local agent.

Remote server /tmp/ssh-XXXXX/agent.5678  ──► tunnel ──►  Your local ssh-agent

Enter fullscreen mode Exit fullscreen mode

On the remote server, SSH_AUTH_SOCK points to this forwarded socket. Any process on the remote server that connects to this socket gets routed back to your local agent.

This means: from the remote server's perspective, your local agent is present and available. When you run ssh db.internal from the bastion, the bastion's SSH client uses the forwarded socket, which calls back to your local agent, which signs the challenge with your private key.

It feels like your key is on the bastion. It effectively is — for the duration of your session.

The Attack

Here's the problem. Anyone with root access on the remote server can:

  1. Find all active forwarded agent sockets: ls /tmp/ssh-*/
  2. Impersonate any user with a forwarded socket by setting SSH_AUTH_SOCK to their socket path
  3. Use that socket to authenticate as that user to any server their key unlocks
# An attacker with root on your bastion runs:
ls /tmp/ssh-*/
# Finds: /tmp/ssh-AbCdEf/agent.1234

SSH_AUTH_SOCK=/tmp/ssh-AbCdEf/agent.1234 ssh ubuntu@db.internal
# Connected. As you. Using your key.

Enter fullscreen mode Exit fullscreen mode

The attacker never sees your private key. They don't need to. They use your agent — which does the signing for them.

This works for every server your key can reach, for as long as your SSH session to the bastion remains open. If you leave a session running overnight, that window stays open overnight.

What Makes This Worse

  • It requires root on the compromised server. Root access is more common than people expect: a container escape, a sudo misconfiguration, an unpatched privilege escalation vulnerability.
  • It's invisible. Your agent doesn't log that it was used. You get no notification.
  • It's transitive. If you forward your agent to server A, and server A forwards it to server B, anyone with root on either server can use your agent.
  • It affects all keys in your agent. Not just the key you used to connect — every key loaded in ssh-agent.

ProxyJump: The Right Solution

ProxyJump was designed specifically for multi-hop SSH. It solves the "connect through a bastion" problem without forwarding your agent.

How ProxyJump Works

Instead of giving the bastion access to your agent, ProxyJump tells your local SSH client to make two connections — both originating from your machine:

Your machine  ──TCP──►  Bastion:22  ──TCP tunnel──►  db.internal:22
              (full SSH)             (proxied through bastion TCP)

Enter fullscreen mode Exit fullscreen mode

Your SSH client connects to the bastion and immediately asks the bastion to open a TCP connection to the target server. The bastion acts as a dumb TCP proxy — it passes bytes back and forth but has no involvement in the authentication. Your local SSH client handles authentication to both the bastion and the target.

The critical difference: your agent never touches the bastion. The bastion's SSH server forwards TCP traffic; it never performs any authentication on your behalf.

Implementation

Host bastion
    HostName bastion.example.com
    User ubuntu
    IdentityFile ~/.ssh/id_ed25519
    IdentitiesOnly yes
    # Note: No ForwardAgent here

Host db.internal
    User ubuntu
    IdentityFile ~/.ssh/id_ed25519
    IdentitiesOnly yes
    ProxyJump bastion

Enter fullscreen mode Exit fullscreen mode

That's it. ssh db.internal connects through the bastion, authenticates to both with your local agent, and never forwards the agent to either server.

Multi-Hop

Host deep.internal
    ProxyJump bastion,internal-gateway.example.com

Enter fullscreen mode Exit fullscreen mode

Each hop is a TCP proxy. Your local SSH client authenticates to each server in sequence. No agent is forwarded anywhere in the chain.

Files and Commands

ProxyJump works seamlessly with all SSH-based tools:

scp -J bastion file.txt ubuntu@db.internal:/tmp/
rsync -avz -e "ssh -J bastion" local/ ubuntu@db.internal:remote/
sftp -J bastion ubuntu@db.internal

Enter fullscreen mode Exit fullscreen mode


Side-by-Side Comparison

Agent Forwarding ProxyJump
Mechanism Forwards agent socket to remote TCP proxy through intermediate host
Authentication location Remote server calls back to local agent Local client authenticates everywhere
Agent exposure Agent available on remote server Agent never leaves local machine
Root attack surface Root on bastion can use your agent Root on bastion sees TCP bytes only
Session window Risk exists for entire session duration No persistent risk
Multi-hop Requires forwarding through each hop Handled natively, comma-separated
SCP/rsync support Works (but risk applies) Works, no risk
Available since OpenSSH 1.x OpenSSH 7.3 (2016)

When Agent Forwarding Might Be Acceptable

Agent forwarding isn't always wrong. There are narrow cases where it's appropriate:

Interactive development servers you fully control
If you're the only user on a server, you own root, and it exists solely as your dev environment — forwarding your agent there is low risk. The threat model requires an attacker to have root on that server; if you are root and the server isn't shared, that threat is remote.

Legacy systems that don't support ProxyJump
OpenSSH 7.3+ supports ProxyJump. Systems running older versions may need ProxyCommand or agent forwarding as a workaround. This should be a migration trigger, not a permanent state.

When you explicitly need the agent on the remote machine
Legitimate use cases: cloning private Git repos on a remote server without copying keys, or running SSH commands from a remote machine that genuinely need to reach a third server you can't reach directly. Even here, minimize the exposure window (connect, do the operation, disconnect).

Mitigations If You Must Use Agent Forwarding

If you genuinely need it, minimize the risk:

# Load only the specific key needed, with a time limit
ssh-add -t 3600 ~/.ssh/id_ed25519_specific   # Expires in 1 hour

# Confirm what's in your agent before forwarding
ssh-add -l

# Remove all keys after you're done
ssh-add -D

Enter fullscreen mode Exit fullscreen mode

Use ssh-add -c to require confirmation for every agent operation:

ssh-add -c ~/.ssh/id_ed25519

Enter fullscreen mode Exit fullscreen mode

Every time the forwarded socket is used, you'll get a local prompt asking you to confirm. An attacker exploiting your forwarded agent would trigger this prompt — though if you're away from your machine, you might confirm it without thinking.

Lock down the forwarding to the minimum scope:

# Only forward to a specific, trusted host — not everything
Host trusted-dev-server
    HostName 10.0.0.50
    ForwardAgent yes

Host *
    ForwardAgent no   # Default: off

Enter fullscreen mode Exit fullscreen mode


Auditing and Detecting Agent Forwarding

Find Active Forwarded Sockets

On any server you're connected to:

# Your own forwarded socket
echo $SSH_AUTH_SOCK

# All agent sockets (if you have permission)
ls /tmp/ssh-*/

# As root: find all agent sockets across all users
find /tmp -name "agent.*" -type s 2>/dev/null

Enter fullscreen mode Exit fullscreen mode

Check Who's Using Your Agent

There's no built-in logging for agent use, but you can add it with ssh-agent wrappers or tools like ssh-audit-agent. A simpler approach: use hardware security keys (YubiKey, etc.) which require physical touch for each signing operation — a forwarded socket can't silently use the key without your physical presence.

On the Server: Restrict Agent Forwarding

If you administer the bastion or any intermediate server, disable forwarding for users who don't need it:

# /etc/ssh/sshd_config
AllowAgentForwarding no

Enter fullscreen mode Exit fullscreen mode

This is a global disable. If ProxyJump is your standard access pattern, there's no reason for the bastion to accept agent forwarding at all.


Migrating an Existing Setup

If your team currently uses agent forwarding for bastion access, migration to ProxyJump is straightforward and backward-compatible.

Step 1: Add ProxyJump to ~/.ssh/config

Host bastion
    HostName bastion.example.com
    User ubuntu
    IdentityFile ~/.ssh/id_ed25519
    IdentitiesOnly yes
    ForwardAgent no          # Explicitly disable

Host *.internal
    ProxyJump bastion
    User ubuntu
    IdentityFile ~/.ssh/id_ed25519
    IdentitiesOnly yes

Enter fullscreen mode Exit fullscreen mode

Step 2: Verify Target Server Keys Are in known_hosts

With agent forwarding, the bastion performed the second-hop authentication, so only the bastion's key was in your local known_hosts. With ProxyJump, your local client authenticates to every hop, so every server's key needs to be in your local known_hosts.

# Add target server keys through the proxy
ssh-keyscan -J bastion db.internal >> ~/.ssh/known_hosts

Enter fullscreen mode Exit fullscreen mode

Step 3: Disable Agent Forwarding on the Bastion

# /etc/ssh/sshd_config on bastion
AllowAgentForwarding no

Enter fullscreen mode Exit fullscreen mode

sudo systemctl restart ssh

Enter fullscreen mode Exit fullscreen mode

Step 4: Verify

# Connect to target — should work without agent forwarding
ssh db.internal

# Verify agent socket is not present on bastion
ssh bastion "echo \$SSH_AUTH_SOCK"
# Should be empty

Enter fullscreen mode Exit fullscreen mode


The Deeper Principle

The agent forwarding problem illustrates a broader security principle: convenience and security are often in tension, and convenience has a way of winning by default.

Agent forwarding feels seamless precisely because it's transparent. You never see the forwarded socket. You never see it being used. The attack would also be transparent — the attacker uses your agent, achieves their goal, and you see nothing.

ProxyJump is equally transparent from a user experience perspective. ssh db.internal works the same way. The difference is what's happening underneath — and what an attacker on an intermediate server can and cannot access.

When two solutions have the same user-facing behavior and one has a material security advantage, the choice should be obvious. The only reason agent forwarding persists is inertia: it's what tutorials written before 2017 recommended, and those tutorials are still being followed today.


Quick Reference

Replace this:

Host bastion
    ForwardAgent yes

Enter fullscreen mode Exit fullscreen mode

With this:

Host bastion
    ForwardAgent no

Host *.internal
    ProxyJump bastion

Enter fullscreen mode Exit fullscreen mode

And on the bastion server:

# /etc/ssh/sshd_config
AllowAgentForwarding no

Enter fullscreen mode Exit fullscreen mode

That's the migration. It takes five minutes and eliminates an entire category of attack surface.


Follow for more SSH security content. Next: ControlMaster and SSH connection multiplexing for faster deployments.