// BlogPages.jsx — blog listing + one full sample article
// Shell sets window.BLOG_MODE ('list' | 'post'); listing lives at root, article in /blog/.
const SAMPLE_SLUG = 'graphql-alias-batching-auth-bypass';
const BLOG_POSTS = [
{ slug: SAMPLE_SLUG, tag: 'API', featured: true, title: 'GraphQL alias batching: one request, a thousand auth checks bypassed', date: 'May 18, 2026', read: '12 min', author: 'GreySurface team', excerpt: 'A single GraphQL document can carry hundreds of aliased queries. When the resolver authorizes the operation but not each field, batching turns a rate-limited endpoint into an enumeration oracle. Here is the pattern, and how we test for it.' },
{ tag: 'AD', title: 'ADCS ESC1 in the wild: eleven engagements, eight findings', date: 'May 02, 2026', read: '9 min', author: 'Identity practice', excerpt: 'Certificate Services misconfigurations remain the fastest path to Domain Admin we see. A field guide to ESC1 from the last quarter of internal engagements.' },
{ tag: 'AI', title: 'Indirect prompt injection through a calendar invite', date: 'Apr 24, 2026', read: '6 min', author: 'AI testing', excerpt: 'The model never saw a malicious prompt from the user. It read one from a meeting description it was asked to summarize, then called a tool it should not have.' },
{ tag: 'Cloud', title: 'PMapper at scale: finding the role we should not be able to assume', date: 'Apr 11, 2026', read: '10 min', author: 'Cloud practice', excerpt: 'Graphing IAM trust before touching a single service is how we find the two-policies-away compromise that posture tools score as informational.' },
{ tag: 'Mobile', title: 'Frida-based jailbreak detection bypass', date: 'Mar 28, 2026', read: '11 min', author: 'Mobile practice', excerpt: 'A walkthrough of a current bypass technique, and the conversation we have with clients about whether more hardening is worth it.' },
{ tag: 'Web', title: 'HTTP/2 request smuggling: three gadgets from this quarter', date: 'Mar 15, 2026', read: '8 min', author: 'Web practice', excerpt: 'Three gadgets we used this quarter to poison caches and cross the trust boundary between front end and origin.' },
];
const PostCard2 = ({ p }) => {
const [h, setH] = React.useState(false);
const live = !!p.slug;
const Tag = live ? 'a' : 'div';
return (
setH(true)} onMouseLeave={() => setH(false)}
style={{
display: 'flex', flexDirection: 'column', textDecoration: 'none',
background: h && live ? '#141516' : '#0f1011', border: `1px solid ${h && live ? '#34343a' : '#23252a'}`,
borderRadius: 12, padding: 24, position: 'relative', overflow: 'hidden',
transition: 'all 220ms', transform: h && live ? 'translateY(-2px)' : 'translateY(0)', height: '100%',
}}>
{/* Article body */}
GraphQL gives clients enormous flexibility in how they shape a request. That flexibility is the point, and it is also the problem. A single GraphQL document can carry hundreds of independent operations, each one aliased so the response keys do not collide. When a server authorizes the operation but not each field resolver, that flexibility quietly becomes an enumeration oracle.We see this pattern on roughly four in ten GraphQL engagements. It rarely shows up in a scanner report, because the individual query looks benign. The damage is in the multiplier.The shape of the bugMost rate limiting and abuse protection lives at the HTTP layer: requests per IP, per token, per minute. GraphQL collapses many logical operations into one HTTP request. So a limit of "60 requests per minute" becomes meaningless when one request can contain 900 aliased lookups.{`query Harvest {
a0: user(id: "1") { email phone }
a1: user(id: "2") { email phone }
a2: user(id: "3") { email phone }
# ... generated up to a899
}`}If the resolver checks "is this caller allowed to run a user query?" but not "is this caller allowed to read this user?", the response returns 900 records for the price of one request that never tripped a rate limit.The root cause is almost always object-level authorization evaluated at the wrong layer. Batching does not create the flaw, but it makes it far more damaging.How we test for itWe start by pulling the full schema, either through introspection or by inferring it from observed traffic. Then we build an authorization matrix: for every type that exposes an identifier argument, which roles should be able to read which instances. With that matrix in hand, the test is mechanical.{`# 1. enumerate aliasable, id-bearing fields from the schema
# 2. craft a single document with N aliases across an id range
# 3. send as a low-privilege (or anonymous) principal
# 4. diff returned objects against the authz matrix
# 5. anything returned outside the matrix is a finding`}We also test the same document against query-cost and depth limits, because the two defenses interact: a server that rejects deep queries may still happily resolve a wide, shallow, heavily-aliased one.Remediation that holdsField-level authorization is the durable fix: authorize inside the resolver, against the object being returned, not at the operation boundary. Query-complexity analysis and alias caps are useful speed bumps, but they are mitigations, not controls. We recommend both, in that order, and we retest the deployed fix before we close the finding.If you run a GraphQL surface and have not specifically tested aliased batching against your authorization model, it is worth an afternoon. It is one of the highest-yield, lowest-effort tests we run, and one of the most commonly missed.
{/* Author / CTA strip */}