一、风险与目标风险:越权字段访问、过度抓取与复杂查询导致资源耗尽、输入污染。目标:字段级Scope授权、统一深度与复杂度上限、操作名与持久化查询强制。

二、字段Scope映射与授权type Token = { sub: string; scope: string[] }

const fieldScope: Record<string, string> = {

'email': 'read:email',

'ssn': 'read:ssn',

'balance': 'read:balance'

}

function hasScope(tok: Token, need: string): boolean {

return tok.scope.includes(need)

}

function authorizeFields(tok: Token, fields: string[]): boolean {

for (const f of fields) {

const need = fieldScope[f]

if (need && !hasScope(tok, need)) return false

}

return true

}

三、查询深度与复杂度限制function maxDepth(query: string): number {

let depth = 0

let max = 0

for (const ch of query) {

if (ch === '{') { depth++; if (depth > max) max = depth }

else if (ch === '}') { depth = Math.max(0, depth - 1) }

}

return max

}

function estimateComplexity(query: string): number {

const m = query.match(/[a-zA-Z_][a-zA-Z0-9_]*/g) || []

const ignore = new Set(['query','mutation','subscription','fragment','on','true','false'])

let count = 0

for (const w of m) if (!ignore.has(w)) count++

return count

}

四、字段提取与校验function extractFields(query: string, allow: string[]): string[] {

const out: string[] = []

for (const f of allow) {

const re = new RegExp(`\\b${f}\\b`)

if (re.test(query)) out.push(f)

}

return out

}

五、持久化查询与操作名type Persisted = { id: string; text: string }

function requireOperationName(name: string | undefined): boolean {

return typeof name === 'string' && name.length >= 3 && name.length <= 64

}

function matchPersisted(text: string, db: Record<string, Persisted>, id: string | undefined): boolean {

if (!id) return false

const item = db[id]

if (!item) return false

return item.text === text

}

六、统一拦截示例type Req = { body: { query: string; operationName?: string; persistedId?: string }; token: Token }

type Res = { status: (n: number) => Res; end: (b?: string) => void }

function guardGraphQL(req: Req, res: Res, persisted: Record<string, { id: string; text: string }>) {

const q = req.body.query || ''

const name = req.body.operationName

const id = req.body.persistedId

if (!requireOperationName(name)) return res.status(400).end('invalid_operation')

const depth = maxDepth(q)

if (depth > 8) return res.status(400).end('depth_exceeded')

const complexity = estimateComplexity(q)

if (complexity > 100) return res.status(400).end('complexity_exceeded')

if (!matchPersisted(q, persisted, id)) return res.status(400).end('unknown_query')

const fields = extractFields(q, Object.keys(fieldScope))

if (!authorizeFields(req.token, fields)) return res.status(403).end('forbidden')

}

七、验收清单深度≤8与复杂度≤100;操作名长度3-64;只接受持久化查询。字段级Scope映射生效,未授权字段拒绝访问;输入不含非法标识符。输出与执行路径可审计并关联操作名与持久化查询ID。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部