🧩 How GPG Agent’s Socket Protocol Actually Works

March 1, 2026 · View on GitHub

The GPG Agent protocol is a simple, line‑oriented, ASCII text protocol, very similar in spirit to SMTP/IMAP-style command/response exchanges. The official documentation is terse, but the behavior is well‑established because gpg-connect-agent itself is just a thin wrapper around this protocol.

Below is a clear, practical breakdown of how to speak the protocol correctly so you can proxy it.


🧩 How GPG Agent’s Socket Protocol Actually Works

1. Encoding

  • All commands and responses are ASCII text.
  • Lines are terminated with LF (\n), not CRLF.
  • No binary framing, no length prefixes.
  • Data blocks (e.g., key material) are Base64 or percent‑escaped text depending on the command.

2. Basic Message Structure

Every message is a single line:

COMMAND arguments...\n

Examples:

GETINFO version
KEYINFO --list
SIGKEY 1234567890ABCDEF

3. Command/Response Flow

The protocol is strict request → response.
You must wait for the agent’s response before sending the next command.

✔️ Successful response

Always begins with:

OK

Optionally followed by text:

OK <some message>

❌ Error response

Begins with:

ERR <code> <description>

Example:

ERR 67108949 No such key

4. Status and Data Lines

Before the final OK or ERR, the agent may send:

Status lines

Start with:

S <keyword> <data...>

Example:

S PINENTRY_LAUNCHED 1234

Data blocks

Start with:

D <data...>

These are typically percent‑escaped or Base64 depending on the command.


5. Multi-line Data Blocks

Some commands (e.g., SETKEY, IMPORT_KEY) use a special block syntax:

INQUIRE <keyword>

This means the agent is asking the client to send data.

You respond with:

D <line1>
D <line2>
END

Then the agent continues with OK or ERR.


6. Example Full Exchange

Client:

GETINFO version

Server:

D 2.4.3
OK

Client:

SIGKEY 1234567890ABCDEF

Server:

S KEY_CREATED B 1234567890ABCDEF
OK

Client:

BYE

Server:

OK closing connection

7. Proxying Considerations

Since the protocol is:

  • ASCII
  • Line‑oriented
  • Strict request/response
  • No pipelining

Your proxy must:

✔️ Forward lines exactly as-is

Do not modify whitespace, percent escapes, or Base64.

✔️ Preserve ordering

Never send a new command until the previous one has produced OK or ERR.

✔️ Handle INQUIRE blocks

These are the only interactive cases.

✔️ Treat the socket as a byte stream

No packet boundaries exist—your “discrete packets” must be based on newline detection.


8. Best Reference Implementation

The easiest way to understand the protocol is to watch gpg-connect-agent:

gpg-connect-agent --verbose

You can type commands manually and see exactly what the agent sends back.


Handling INQUIRE / D / END blocks correctly is the trickiest part of the GPG Agent protocol, and it’s essential if you’re building a proxy. The good news is that the rules are consistent once you understand the flow.

Below is a clear, practical guide to handling these blocks safely and robustly.


🔐 What an INQUIRE Block Means

When the agent sends:

INQUIRE <keyword>

…it is pausing the command and asking the client to provide data.
This is the only time the agent expects the client to send more than one line before receiving an OK or ERR.

Examples of inquiries:

  • INQUIRE PINENTRY_LAUNCHED
  • INQUIRE NEEDPIN
  • INQUIRE PASSPHRASE
  • INQUIRE KEYDATA

🧭 The Required Client Response

When you receive an INQUIRE, you must send:

1. Zero or more data lines:

D <data>
D <more data>

2. A terminating line:

END

Only then will the agent resume processing and eventually send OK or ERR.


🧱 Safe Handling Rules (Critical for a Proxy)

1. Never send anything except D lines or END after an INQUIRE

If you send a normal command instead of D/END, the agent will treat it as malformed input and close the connection.

Your proxy must enforce:

  • After INQUIRE, the only valid outgoing lines are:
    • D ...
    • END

2. Do not forward INQUIRE to the remote server unless intended

If your proxy is translating or encapsulating the protocol, you must decide:

  • Are you forwarding the inquiry to the remote server?
  • Or are you handling it locally?

Most proxies simply forward everything, but you must ensure the remote side understands the protocol.


3. Preserve exact byte content in D lines

D lines may contain:

  • Percent‑escaped text
  • Base64
  • Arbitrary ASCII sequences

Your proxy must not modify or re-encode these lines.


4. Do not assume how many D lines will be sent

Some inquiries expect:

  • A single line (e.g., a passphrase)
  • Multiple lines (e.g., key import)
  • Zero lines (client just sends END)

Your proxy must treat D lines as a stream until it sees END.


5. After sending END, wait for OK or ERR

The agent will not accept new commands until it finishes the inquiry.

Example:

INQUIRE NEEDPIN

Client:

D hunter2
END

Agent:

OK

Only now may the next command be sent.


6. Handle nested inquiries (rare but possible)

The agent can send another INQUIRE after you respond to the first one, before the final OK.

Your proxy must be prepared for:

INQUIRE A
  → D ... / END
INQUIRE B
  → D ... / END
OK

This is uncommon but valid.


🧪 Example of a Full Safe Exchange

Agent:

INQUIRE NEEDPIN

Proxy → Client:

(forward unchanged)

Client → Proxy:

D 70617373776F7264
END

Proxy → Agent:

(forward unchanged)

Agent:

OK

Proxy → Client:

(forward unchanged)


🛡️ Safety Checklist for Your Proxy

RequirementWhy it matters
Detect INQUIRE linesSwitch into “inquiry mode”
Only allow D and END until inquiry completesPrevent protocol corruption
Forward data exactly as receivedAvoid breaking percent‑encoding
Wait for OK/ERR before next commandMaintain strict request/response order
Support multiple sequential inquiriesSome operations require them
Never pipeline commandsGPG agent does not support pipelining

9. Extra Socket vs. Main Socket (Security Model)

This bridge connects exclusively to agent-extra-socket (S.gpg-agent.extra), not to the main agent-socket (S.gpg-agent). This is a deliberate security choice.

Why the extra socket is used

gpg-agent exposes two Unix sockets (or, on Windows, Assuan TCP-over-localhost sockets):

Socketgpgconf --list-dirs keyPurpose
Main socketagent-socketFull-privilege local socket — all commands allowed
Extra socketagent-extra-socketRestricted socket for remote/forwarded access

The extra socket is specifically designed for SSH-forwarding and VS Code remote scenarios where the socket is tunnelled to an untrusted host. gpg-agent enforces command restrictions at the protocol layer before executing any operation.

Commands forbidden on the extra socket

The following commands return ERR 67109115 Forbidden regardless of how they are invoked:

PRESET_PASSPHRASE   → ERR 67109115 Forbidden
CLEAR_PASSPHRASE    → ERR 67109115 Forbidden
GET_PASSPHRASE      → ERR 67109115 Forbidden

These are the commands that could cache or expose plaintext passphrases. All other public-key operations (PKDECRYPT, PKSIGN, GENKEY, KEYINFO, HAVEKEY, GETINFO, READKEY, KEYATTR, …) are permitted.

OPTION arguments on the extra socket

The OPTION verb itself is permitted. Most OPTION arguments are accepted because they configure per-session display and locale preferences that do not affect security:

OPTION argumentAcceptedNotes
display=<X11 display>✅Sets X11 display for pinentry
ttyname=<path>✅Sets TTY for pinentry
ttytype=<type>✅Sets terminal type
lc-messages=<locale>✅Locale for messages
lc-ctype=<locale>✅Locale for character classification
allow-pinentry-notify✅Enables pinentry-launched status lines
no-grab✅Disables X11 keyboard grab in pinentry
pinentry-mode=<mode>✅ (with caveats)See note below
putenv=<NAME>=<value>✅Sets env var forwarded to pinentry subprocess

pinentry-mode note

OPTION pinentry-mode loopback is permitted on the extra socket. Loopback mode redirects passphrase prompts back to the Assuan client rather than launching pinentry. In this bridge’s deployment scenario the Assuan client is the gpg-bridge-request extension on the remote machine — which does not implement passphrase prompting and will cause the operation to fail with an error. This is the correct outcome: the bridge does not handle secrets.

putenv note

OPTION putenv injects environment variables into the pinentry subprocess. On the extra socket this arrives via the forwarded tunnel, meaning a remote process could influence pinentry’s environment. The practical risk is low: pinentry only reads a small set of environment variables (DISPLAY, GPG_TERM, locale vars), and the same-user restriction means the remote process already runs as the Windows user that owns the gpg-agent.

Bridge-side policy

No bridge-side allowlist or denylist is implemented or needed. gpg-agent is the correct trust anchor for command authorization. Adding a bridge-side filter would:

  • Introduce false negatives (new legitimate commands get blocked)
  • Provide no additional security (gpg-agent already enforces the boundary)
  • Create a maintenance burden as gpg-agent evolves