If your team has already locked down your REST and gRPC services, you might assume that your JSON-RPC APIs are covered as well. While some of the mechanisms in place can still help protect your JSON-RPC services, its single-endpoint, method-based architecture creates an attack surface that URL-based WAF rules, path-level access controls, and standard rate limiting weren’t designed for.
As JSON-RPC adoption grows across blockchain platforms (like the Ethereum RPC API), web applications, and microservice architectures, securing these protected APIs requires protocol-specific thinking. This guide covers the practices that matter most for JSON-RPC, from method-level authorization to automated vulnerability testing, so you can ship with confidence.
TL;DR
- JSON-RPC’s single-endpoint architecture creates unique risks. URL-based WAF rules and path-level access controls don’t work when every request hits the same endpoint. Security controls need to inspect the request body and method field directly.
- Enforce authorization at the method level, not just the endpoint level. A valid token confirms who the caller is, but without method-level controls, any authenticated user can call any method, including destructive admin operations.
- TLS is non-negotiable. Unlike gRPC, JSON-RPC doesn’t enforce encrypted transport by default. Use TLS 1.2+, strong cipher suites, and mTLS for service-to-service communication.
- Validate and schema-enforce every input. JSON-RPC parameters can be any JSON type, making injection attacks easy without strict type checking, parameterized queries, and allowlisted method names.
- Rate limit at multiple levels. Per-user and per-IP limits aren’t enough. Apply per-method limits and cap batch request sizes to prevent amplification attacks.
- Don’t leak information in error responses. Return generic errors with correlation IDs to clients, log the full details server-side, and use the same error code for both “method not found” and “unauthorized” to prevent method enumeration.
- Automate security testing in CI/CD. DAST tools like StackHawk test your running JSON-RPC endpoints for injection flaws, broken auth, and insecure configurations on every PR before vulnerabilities reach production.
What is JSON-RPC?
JSON-RPC is a remote procedure call protocol that utilizes JSON as its data format for structured data exchange. A successor to XML-RPC, the current version (JSON-RPC 2.0) defines a simple contract: the client sends a valid request object with a method name, named parameters, and a unique id, and the server responds with a result or an error.
A basic request looks like this:
{
"jsonrpc": "2.0",
"method": "getAccountBalance",
"params": { "accountId": "acct-12345" },
"id": 1
}
And the corresponding response:
{
"jsonrpc": "2.0",
"result": { "balance": 1250.00, "currency": "USD" },
"id": 1
}
If you’re coming from REST, the model is very different. REST maps HTTP methods to resource URLs for resource manipulation (like GET /api/users/123), while JSON-RPC routes everything through a single HTTP endpoint (typically /jsonrpc or /rpc) and uses the method field in the request body to determine what happens. gRPC is closer in spirit since it’s also method-based, but gRPC gets strict type enforcement for free through Protocol Buffers and defaults to HTTP/2 with TLS. JSON-RPC, which works across multiple programming languages and with any major HTTP client library, lacks either of those guardrails out of the box. That’s what makes its security story distinct.
JSON-RPC also supports batch requests, where a client sends multiple operations as an array of calls in a single HTTP request. That’s efficient for legitimate use cases, but it also creates an amplification vector that attackers love to exploit.
Why JSON-RPC APIs Need Dedicated Security Attention
If you’re already following general API security practices, that’s a solid foundation. But JSON-RPC introduces attack surface characteristics that REST-focused measures don’t fully cover.
Single endpoint exposure. Every JSON-RPC call hits the same URL path. Traditional WAF rules that rely on URL patterns or HTTP methods won’t help much without deep request-body inspection and custom rules. You can’t block /admin/* when every request goes to /jsonrpc. Your security controls need to inspect the JSON text in the request body and the method field to enforce access policies.
Method enumeration. Attackers can probe for available methods by sending requests with common method names. While JSON-RPC defines a Method not found error code (-32601), returning it verbatim lets attackers map your API surface by distinguishing “method not found” from “unauthorized.” Unlike REST APIs, where endpoint discovery requires URL scanning, JSON-RPC method discovery only requires guessing strings.
Batch request amplification. The batch request feature lets a client send dozens or hundreds of calls in a single HTTP request. An attacker can use this to bypass per-request rate limiting, amplify injection attacks, or exhaust server resources, all within what looks like one HTTP request at the network level. Batch requests can also mix authorized and unauthorized method calls, which means partial execution is a real risk if authorization isn’t enforced per call within the batch.
Loose parameter typing. JSON-RPC parameters in the request payload are JSON values, so they can be any type. Without strict schema enforcement, a parameter you expect to be a string could arrive as an object or array, leading to unexpected behavior or injection vulnerabilities.
Authentication and Authorization
Auth is just as important for JSON-RPC as for any other API. But the single-endpoint architecture means you need to think about authorization at the method level, not just the endpoint level.
For authentication, the standard approaches work fine. API keys, JWTs, and OAuth 2.0 access tokens using the bearer authentication scheme can all be passed in the Authorization HTTP request header the same way they would for a REST API. Where things diverge is authorization: a valid token value from an authorization server confirms who the caller is, but that alone isn’t enough. You need method-level controls to restrict access to specific methods based on each client’s role.
Consider this common pattern where a JSON-RPC handler checks authentication but nothing else:
app.post('/jsonrpc', authenticate, async (req, res) => {
const { method, params, id } = req.body;
const result = await executeMethod(method, params);
res.json({ jsonrpc: '2.0', result, id });
});
The problem is that any authenticated user can call any method, including admin.resetDatabase or system.shutdown. Authentication confirms who the caller is, but does nothing to restrict what they can do.
Add a method-level authorization check that maps user roles to permitted methods:
app.post('/jsonrpc', authenticate, async (req, res) => {
const { method, params, id } = req.body;
if (!isMethodAllowed(req.user.role, method)) {
return res.json({
jsonrpc: '2.0',
error: { code: -32600, message: 'Invalid Request' },
id
});
}
const result = await executeMethod(method, params);
res.json({ jsonrpc: '2.0', result, id });
});
Each request is now checked against the caller’s role before execution. Administrative methods like system.shutdown or admin.resetDatabase should be locked down to specific service accounts, not open to any authenticated user.
For sensitive operations, you might also want per-method authentication requirements. A read-only method like getPublicData might only need an API key, while transferFunds should require a JWT with additional claims verification.
The OWASP Authentication Cheat Sheet has more on implementing authentication mechanisms securely.
Transport Layer Security
TLS is non-negotiable for properly protected JSON-RPC APIs. Unlike gRPC, which defaults to TLS in most implementations, JSON-RPC doesn’t enforce encrypted transport by default. And because request bodies contain the method names and parameters (not just the URL path), unencrypted JSON-RPC traffic exposes exactly which methods are being called and with what data, compromising data integrity and confidentiality.
Critical TLS implementation requirements:
- Use TLS 1.2 or higher: Older versions have known vulnerabilities.
- Strong cipher suites only: Disable weak ciphers like RC4 and 3DES.
- Valid certificates: Use certificates issued by trusted certificate authorities, and enforce server certificate validation in your TLS client configuration.
- HSTS headers: For browser-based clients, enforce HTTPS with Strict-Transport-Security headers to prevent downgrade attacks.
JSON-RPC APIs are commonly exposed over both HTTP and WebSocket transports. If your service supports WebSocket connections, make sure you’re using a wss:// endpoint (WebSocket Secure) rather than an unencrypted ws:// endpoint. The same TLS requirements apply to both.
For service-to-service communication in microservice architectures, implement mutual TLS (mTLS). Both the client and server present certificates, providing bidirectional authentication on top of the TLS encryption layer. This matters most in enterprise deployment of internal JSON-RPC services that handle sensitive operations.
Input Validation and Injection Prevention
JSON-RPC’s flexible parameter data structures make input validation critical. gRPC services get type safety through Protocol Buffers, where the schema enforces parameter types at the serialization layer. JSON-RPC doesn’t have that. Parameters can be any JSON value (strings, numbers, objects, arrays), and without explicit schema enforcement, attackers have more room to craft malicious inputs.
Your first line of defense is schema validation. If your JSON-RPC service has an OpenRPC specification (the JSON-RPC equivalent of OpenAPI, or protobuf definitions in the gRPC world), use it to validate every incoming request against the expected parameter types, formats, and constraints.
Here’s a method handler that takes the accountId parameter and drops it straight into a query:
async function getUserBalance(params) {
const result = await db.query(
`SELECT balance FROM accounts WHERE id = '${params.accountId}'`
);
return result;
}
Because JSON-RPC parameters can be any JSON type, an attacker could pass an object like {“$ne”: “”} instead of a string (a classic NoSQL injection pattern), or inject SQL through a crafted string value. There’s no type check and no parameterized query to stop them.
A better approach is to validate the parameter type and format before it touches any downstream system, and always use parameterized queries:
async function getUserBalance(params) {
if (typeof params.accountId !== 'string' || !/^acct-[a-zA-Z0-9]+$/.test(params.accountId)) {
const err = new Error('Invalid params');
err.code = -32602;
throw err;
}
const result = await db.query(
'SELECT balance FROM accounts WHERE id = $1',
[params.accountId]
);
return result;
}
Now the handler rejects anything that isn’t a string matching the expected format, and the query is parameterized to prevent injection even if validation is somehow bypassed.
Beyond type checking, a few more validation practices for every JSON-RPC invocation:
- Allowlist method names: Only accept known method names. Return a generic error for anything else. Don’t distinguish between “method not found” and “method exists but you’re not authorized.”
- Validate parameter structure: Check that required parameters are present and that optional parameters are of the expected types.
- Sanitize string inputs: Apply context-appropriate sanitization for values that will touch database queries, command execution, or HTML rendering.
- Limit nested object depth: JSON-RPC parameters can contain deeply nested JSON data structures. Set a maximum depth to prevent stack overflow attacks.
- Cap batch request size: Limit the number of calls allowed in a single batch request to prevent amplification attacks.
For more on injection prevention, see the OWASP SQL Injection Prevention Cheat Sheet and our SQL injection guide.
Rate Limiting and Method-Level Controls
Without rate limiting, a single bad actor can overwhelm your JSON-RPC service. But standard per-IP or per-user rate limiting isn’t enough on its own. You need method-level controls that account for JSON-RPC’s quirks.
Effective rate limiting for JSON-RPC should operate at multiple levels:
- Per-user limits: Restrict individual users to reasonable request rates across all methods.
- Per-method limits: Apply stricter limits to expensive operations. A method like generateReport that triggers heavy computation should have a lower limit than a lightweight getStatus call.
- Batch request caps: Set a maximum number of calls per batch request, enforced before any method execution occurs. Without this, an attacker can send thousands of method calls in a single HTTP request, bypassing per-request rate limits entirely.
- Per-IP limits: Block aggressive scanning behavior from specific IP addresses.
In practice, a gateway-level rate-limiting config that covers all of these layers might look something like this:
rateLimiting:
global:
requestsPerMinute: 1000
methods:
"getBalance":
requestsPerMinute: 100
"transferFunds":
requestsPerMinute: 10
"admin.*":
requestsPerMinute: 5
batch:
maxCallsPerBatch: 20
Put your rate limiting at the API gateway level rather than in application code. It keeps things consistent across services and takes the load off your application servers. When a client exceeds their limit, return the standard JSON-RPC error with an appropriate message and include Retry-After headers in the HTTP response.
One more thing is to consider blocking or restricting access to introspection methods. A lot of JSON-RPC implementations expose methods like rpc.discover or system.listMethods that reveal the full API surface. Disable these in production, or at a minimum, restrict them to internal clients.
Error Handling and Logging
JSON-RPC 2.0 defines a structured error response format with numeric error codes. How you handle errors can either strengthen or undermine your security posture.
The spec reserves error codes -32700 through -32600 for protocol-level errors (parse error for invalid JSON text, invalid request, method not found, invalid params, internal error). Use these codes correctly (and define application-defined errors for your own error conditions), but watch what you put in the data field.
Here’s an error response that gives away too much:
{
"jsonrpc": "2.0",
"error": {
"code": -32603,
"message": "Internal error",
"data": {
"stack": "Error at DatabasePool.query (db.js:142)\n...",
"query": "SELECT * FROM users WHERE id = 'acct-12345'"
}
},
"id": 1
}
That hands an attacker the file path, the database query structure, and the ORM you’re using, which is everything they need to craft a targeted injection attack. Log those details server-side, but return only a correlation ID the client can reference for support:
{
"jsonrpc": "2.0",
"error": {
"code": -32603,
"message": "Internal error",
"data": { "requestId": "req-abc-123" }
},
"id": 1
}
Another JSON-RPC-specific trap is returning different error codes for “method not found” versus “method exists but access denied.” That lets attackers enumerate your available methods. Return the same error for both cases.
For logging, capture the essential context for each request:
- Method name and timestamp: Track what’s being called and when.
- Caller identity: Log the authenticated user or API key (never the full token or credential).
- Request ID: Use the JSON-RPC id field for correlation.
- Error details: Log full details server-side (stack traces, query info) while returning only generic messages to clients.
Keep an eye on your logs for suspicious patterns: rapid method enumeration attempts, unusually large batch requests, repeated auth failures, or access attempts to restricted methods. These usually mean someone is probing your API.
Automated Security Testing for JSON-RPC APIs
Implementing security best practices in code and infrastructure is one side of the coin. The other is verifying that your defenses actually work. Manual security testing is valuable, but it can’t keep pace with how fast teams ship code today. Automated testing ensures every code change is tested for vulnerabilities before it reaches production.
DAST (Dynamic Application Security Testing) tools work well for JSON-RPC APIs because they test the running application, sending real requests and analyzing real responses to find exploitable vulnerabilities.
StackHawk is a DAST platform that ships with native support for JSON-RPC 2.0. HawkScan connects to your JSON-RPC endpoint, discovers available methods via OpenRPC schemas or controlled discovery techniques, and fuzzes their parameters to find injection flaws, broken authentication, and insecure configurations.
In StackHawk, you can configure HawkScan to scan JSON-RPC services by adding a few lines to your stackhawk.yml:
# stackhawk.yml
app:
jsonRpcConf:
enabled: true
endpoint: /jsonrpc
filePath: '/path/to/openrpc.json'
If you have an OpenRPC schema, HawkScan uses it for automatic generation of test cases for each invoked method. If you don’t, it’ll attempt to discover methods automatically. You can also inject custom variable values for parameters that need specific formats:
app:
jsonRpcConf:
enabled: true
endpoint: /jsonrpc
fakerEnabled: true
customVariables:
- field: accountId
values:
- acct-001
- acct-002
- field: customerEmail
values:
- $faker:email
excludeMethods:
- "admin\\..*"
- "system\\.shutdown"
excludeMethods lets you skip methods that shouldn’t be scanned, like admin-only or destructive operations. With the fakerEnabled configuration flag turned on, HawkScan generates realistic test data that’s more likely to trigger edge-case vulnerabilities than random strings. For the full list of configuration options, see the JSON-RPC configuration documentation.
StackHawk offers smooth integration with your CI/CD pipeline, so every pull request gets scanned for JSON-RPC vulnerabilities before merge. This shift-left approach catches issues during your development process, when they’re cheapest and easiest to fix. For more on building a complete testing strategy, see our API security testing guide.
Conclusion
JSON-RPC‘s method-based, single-endpoint architecture creates security challenges that REST and even gRPC-focused practices don’t fully cover. Method enumeration, batch request amplification, and the lack of URL-based access control patterns all mean you need to think about security differently when building JSON-RPC services.
The practices in this guide (method-level authorization, strict input validation with schema enforcement, protocol-aware rate limiting, careful error handling, and automated security testing) form the access control layer you need for properly protected JSON-RPC APIs.
Security isn’t a one-time task. As you implement JSON-RPC services with new methods and capabilities, your security practices need to keep up. Regular automated testing catches new vulnerabilities as they’re introduced, and monitoring helps you spot and respond to attacks in real time.
Ready to test your JSON-RPC APIs for vulnerabilities before they hit production? Schedule a demo and see StackHawk in flight.