ExpoAPI审计Skill expo-api-audit

这项技能全面审计 Expo/React Native 应用的 API 集成层,用于识别认证问题、硬编码数据、同步问题以及离线行为等关键问题。关键词包括:API 集成、认证处理、数据同步、离线行为。

移动开发 0 次安装 0 次浏览 更新于 3/3/2026

Expo API 审计

Expo API 集成审计

概览

这项技能审计 Expo(React Native)TypeScript 应用的 API 集成层,以识别差距、硬编码数据、认证问题和离线行为问题。适用于使用 Expo Router、expo-secure-store 和 expo-constants 的应用。

输入

开始前,从用户那里收集:

  • 已知问题(可选):怀疑存在问题的特定屏幕或流程
  • 输出格式偏好:markdown 报告、JSON 查找、修复 PR 或任务列表
  • 范围:全面审计或特定焦点(仅认证、仅离线等)

工具说明:ripgrep

如果 rg(ripgrep)可用,使用它而不是 grep —— 它的速度明显更快,并且自动忽略 node_modules/.git。所有 grep 命令在这项技能中都有 rg 等效命令:

# 检查 ripgrep 是否可用
which rg && echo "Use rg commands" || echo "Falling back to grep"

# 等效命令:
# grep -rn "pattern" --include="*.ts" | grep -v node_modules
# rg "pattern" -t ts

# grep -rln "pattern" --include="*.ts" | grep -v node_modules  
# rg -l "pattern" -t ts

第一阶段:发现

在审计之前构建一个心理模型。运行这些命令来定位关键文件:

# Orval 配置和生成的钩子
find . -name "orval.config.*" -o -name "*.orval.ts" 2>/dev/null | head -5
find . -type d -name "generated" | xargs -I{} ls {} 2>/dev/null | head -20

# API 客户端/变异器
rg -l "customInstance|axios\.create|baseURL" -t ts | head -10

# Zustand 存储
rg -l "create\(" -t ts | xargs rg -l "zustand|devtools" 2>/dev/null | head -20

# OpenAPI 规范
find . -name "openapi.json" -o -name "openapi.yaml" -o -name "swagger.json" 2>/dev/null

# 认证基础设施
rg -l "TokenManager|refreshToken|Bearer|interceptor" -t ts

# Expo 配置(环境变量,API URL)
cat app.config.js 2>/dev/null || cat app.config.ts 2>/dev/null || cat app.json
rg "expoConfig|Constants\.manifest" -t ts

关键文件映射

组件 典型位置 检查内容
Orval 配置 orval.config.ts 客户端类型、变异器路径、输出目录
生成的钩子 /api/generated//src/api/ 与 OpenAPI 规范的完整性对比
Axios 客户端 /api//services/ 拦截器、基础配置
令牌管理器 /services/auth/ 必须使用 expo-secure-store
Zustand 存储 /stores/ 持有服务器状态(违规?)
OpenAPI 规范 /docs/api/ 或根目录 最后修改、版本
Expo 配置 app.config.jsapp.json extra 中的 API URL,环境变量
屏幕 /app/(Expo Router) 基于文件的路由

第二阶段:API 层审计

2.1 Orval 生成健康

# 检查生成的代码是否符合规范
npx orval --dry-run 2>&1 | head -50

# 比较端点计数
jq '.paths | keys | length' docs/api/openapi.json  # 规范中的端点
find ./api/generated -name "*.ts" -exec grep -l "useQuery\|useMutation" {} \; | wc -l

验证:

  • [ ] 生成的类型与 OpenAPI 模式匹配
  • [ ] 所有规范端点都有相应的钩子
  • [ ] 自定义变异器注入认证头
  • [ ] 查询默认设置合理(staleTime,gcTime,retry)

2.2 认证令牌处理

检查 axios 客户端是否有这些模式:

// 必需:请求拦截器注入令牌
config.headers.Authorization = `Bearer ${token}`

// 必需:令牌到期前预刷新
if (tokenExpiresWithin(600)) await refreshToken()

// 必需:401 响应触发刷新
if (error.response?.status === 401) { /* 刷新逻辑 */ }

// 必需:刷新去重
if (isRefreshing) return pendingRefreshPromise

// 必需:刷新失败触发注销
clearTokens(); navigate('/auth')

Expo 特定检查:

# 令牌存储 - 必须使用 expo-secure-store,而不是 AsyncStorage
rg "AsyncStorage.*token|token.*AsyncStorage" -t ts -i  # BAD if found
rg "SecureStore|expo-secure-store" -t ts               # GOOD - should exist

# 环境变量 - 应使用 expo-constants 或 app.config.js
rg "process\.env\." -t ts                              # BAD for Expo (won't work in production)
rg "Constants\.expoConfig|Constants\.manifest" -t ts   # GOOD - Expo way
grep -l "extra:" app.config.* 2>/dev/null              # Config-based env vars

红旗:

  • 源代码中的硬编码令牌或 API 密钥
  • 使用 AsyncStorage 的令牌(必须使用 expo-secure-store
  • 使用 process.env 的 API URL(使用 expo-constants 替代)
  • 没有刷新去重(并行刷新调用)
  • 无限刷新循环(刷新端点返回 401)
  • 令牌检查和请求之间的竞态条件

2.3 直接 API 违规

查找绕过 Orval 生成钩子的调用:

# 原始 fetch(应使用生成的钩子)
rg "fetch\(" -t ts -t tsx --glob '!*.d.ts'

# 直接 axios(应使用 orval 变异器)
rg "axios\.|axios\(" -t ts -t tsx --glob '!*orval*'

# 手动 useQuery(应使用生成的)
rg "useQuery\(|useMutation\(" -t ts -t tsx --glob '!*generated*'

# 硬编码 URL
rg "https?://[^\"']*api" -t ts -t tsx

# Expo 特定:process.env 使用(在 Expo 生产构建中不起作用)
rg "process\.env\." -t ts -t tsx  # 应使用 Constants.expoConfig.extra 替代

分类每个发现:

  • 合法:第三方 API、文件上传、WebSocket
  • 违规:不使用生成的钩子的后端调用
  • 硬编码:应使用 expo-constants 的 URL
  • 环境错误process.env 使用(在 Expo 生产中破坏)

第三阶段:屏幕数据审计

对于 /app/(Expo Router)或 /screens/ 中的每个屏幕:

3.1 数据源分类

类别 模式 状态
API 数据 useGet*(), use*Query() ✓ 正确
缓存 React Query 提供过时数据 ✓ 预期
Zustand 商业数据在存储中 ⚠️ 应该是服务器状态?
硬编码 Mock 数组、占位符对象 ❌ 标记
派生 来自 API 数据的计算 ⚠️ 检查后端是否应该计算
# 查找没有 API 钩子的屏幕(可疑)
for f in $(find ./app -name "*.tsx" | grep -v "_layout"); do
  if ! grep -q "use.*Query\|use.*Mutation\|useGet\|usePost\|usePut\|useDelete" "$f"; then
    echo "NO API HOOKS: $f"
  fi
done

# 查找硬编码数组/对象(rg 版本)
rg "useState\(\[" -t tsx
rg "const.*=.*\[\{" -t tsx

3.2 用户交互审计

每个修改数据的用户操作都必须触发变异:

操作类型 必需模式
表单提交 useMutation + onSuccess 使无效
切换/开关 变异或防抖变异
删除 变异带有乐观更新或确认
拖拽/重新排序 放下时变异
设置更改 变异(不仅仅是 Zustand)
# 表单没有变异(可疑)
for f in $(rg -l "onSubmit|handleSubmit" -t tsx); do
  if ! rg -q "useMutation|usePost|usePut|usePatch" "$f"; then
    echo "FORM WITHOUT MUTATION: $f"
  fi
done

# 按钮处理程序审计
rg "onPress=|onClick=" -t tsx | head -50

3.3 Zustand 存储审计

Zustand 应该持有 客户端唯一 状态。如果存储包含:

  • 应该来自 API 的数据(用户、项目、记录)
  • 商业逻辑计算(应在服务器端)
  • React Query 缓存的副本
# 列出所有 Zustand 存储及其状态形状
rg "interface.*State|type.*State" stores/ -t ts

# 检查持久性中间件(可能会复制 RQ 缓存)
rg "persist\(" stores/ -t ts

有效的 Zustand 使用:认证状态、UI 偏好、导航状态、草稿表单 无效:获取的实体、计算的商业数据、任何带有 API 端点的内容

第四阶段:离线行为审计

4.1 网络模式配置

# 检查查询客户端默认值
rg "networkMode" -t ts

# 检查离线检测(Expo 支持两者)
rg "NetInfo|@react-native-community/netinfo" -t ts    # 社区包
rg "expo-network|Network\.getNetworkStateAsync" -t ts  # Expo 本地包
rg "isConnected|isInternetReachable" -t ts

预期模式:

  • 查询:networkMode: 'offlineFirst'(离线时提供过时数据)
  • 变异:networkMode: 'online' 或队列实现

4.2 离线场景测试

场景 预期行为 检查
离线加载屏幕 显示缓存数据或空状态 isLoading vs isFetching
离线提交表单 队列或清除错误消息 变异错误处理
离线刷新令牌 优雅失败,重新连接时重试 拦截器错误路径
应用后台然后离线 缓存持久 AsyncStorage/MMKV 检查
离线后重新连接 自动重新获取 refetchOnReconnect
# 检查离线队列实现
rg "offlineQueue|pendingMutations|syncQueue" -t ts

# 检查缓存持久性
rg "persistQueryClient|createAsyncStoragePersister|MMKV" -t ts

4.3 错误边界覆盖

# 查找错误边界
rg "ErrorBoundary|errorElement|onError" -t tsx

# 检查查询错误处理
rg "isError|error:" -t tsx | head -30

第五阶段:报告生成

按严重程度组织发现:

严重(认证/安全)

  • 硬编码凭证
  • 使用 AsyncStorage 的令牌(必须使用 expo-secure-store
  • process.env 用于秘密(在 Expo 构建中不起作用)
  • 认证绕过可能性
  • 缺少 401 处理

重大(数据完整性)

  • 表单未同步到 API
  • 用户操作未持久化
  • 前端业务逻辑
  • 提供过时数据作为新鲜数据
  • API URL 未使用 expo-constants

中等(可靠性)

  • 缺少错误处理
  • 没有离线回退
  • 竞态条件
  • 缺少加载状态

轻微(代码质量)

  • 直接 API 调用(应使用生成的)
  • Zustand 持有服务器状态
  • 不一致的模式

输出模板

Markdown 报告

# API 集成审计报告

## 执行摘要
- X 严重问题,Y 大问题,Z 中等

## 发现

### [严重] 令牌存储在 AsyncStorage 中
**文件**:`services/auth/tokenStorage.ts:15`
**问题**:JWT 令牌存储在 AsyncStorage 而不是 expo-secure-store
**修复**:迁移到 `import * as SecureStore from 'expo-secure-store'`

### [严重] 生产代码中的 process.env
**文件**:`api/client.ts:8`
**问题**:`process.env.API_URL` 在 Expo 生产构建中不起作用
**修复**:使用 expo-constants 中的 `Constants.expoConfig?.extra?.apiUrl`

### [重大] 个人资料表单未同步
**文件**:`app/profile/edit.tsx`
**问题**:表单仅保存到 Zustand,没有 API 调用
**修复**:提交时添加 `useUpdateProfile` 变异

JSON 查找

{
  "summary": { "critical": 2, "major": 2, "medium": 5 },
  "findings": [
    {
      "severity": "critical",
      "category": "auth",
      "file": "services/auth/tokenStorage.ts",
      "line": 15,
      "issue": "Tokens in AsyncStorage instead of expo-secure-store",
      "fix": "Migrate to SecureStore.setItemAsync/getItemAsync"
    },
    {
      "severity": "critical", 
      "category": "config",
      "file": "api/client.ts",
      "line": 8,
      "issue": "process.env.API_URL won't work in Expo builds",
      "fix": "Use Constants.expoConfig.extra.apiUrl"
    }
  ]
}

快速参考命令

# 全面审计(grep 版本)
echo "=== 直接 fetch ===" && grep -rn "fetch(" --include="*.ts" --include="*.tsx" | grep -v node_modules | grep -v ".d.ts"
echo "=== 直接 axios ===" && grep -rn "axios\." --include="*.ts" --include="*.tsx" | grep -v node_modules | grep -v orval
echo "=== 硬编码 URL ===" && grep -rn "http://\|https://" --include="*.ts" --include="*.tsx" | grep -v node_modules
echo "=== useState 数组 ===" && grep -rn "useState\(\[" --include="*.tsx" | grep -v node_modules
echo "=== 表单 ===" && grep -rn "onSubmit" --include="*.tsx" | grep -v node_modules
# 全面审计(ripgrep 版本 - 更快)
echo "=== 直接 fetch ===" && rg "fetch\(" -t ts -t tsx --glob '!*.d.ts'
echo "=== 直接 axios ===" && rg "axios\." -t ts -t tsx --glob '!*orval*'
echo "=== 硬编码 URL ===" && rg "https?://" -t ts -t tsx
echo "=== useState 数组 ===" && rg "useState\(\[" -t tsx
echo "=== 表单 ===" && rg "onSubmit" -t tsx

依赖项

如果命令失败,安装:

# ripgrep(强烈推荐 - 比 grep 快 10 倍)
brew install ripgrep  # 或 apt-get install ripgrep, cargo install ripgrep

# jq 用于 JSON 解析
brew install jq  # 或 apt-get install jq

# 对于 Orval 干运行
npm install -g orval  # 或使用 npx

安装

要使用这项技能与 Claude Code,将其添加到项目的 skills/ 目录:

my-expo-app/
├── app/                          # Expo Router 屏幕
├── stores/
├── api/
├── skills/
│   └── expo-api-audit/
│       └── SKILL.md
├── app.config.js                 # Expo 配置
├── package.json
└── ...

Claude Code 自动发现此目录中的技能。安装后,可以使用提示触发审计,例如:

  • “运行 API 集成审计”
  • “检查我的屏幕中的硬编码数据”
  • “审计我的认证令牌处理”
  • “查找不同步到 API 的表单”
  • “检查我是否正确使用 expo-secure-store”