荷物追跡のセキュリティ:配送データとAPIキーの保護

配送データは多くの開発者が認識している以上に機密性が高いものです。送り状番号からは、購入履歴、自宅住所、配達パターン、取引先との関係が明らかになる可能性があります。追跡連携を構築する際には、セキュリティを後回しにするのではなく、最初から重要な要素として取り組むべきです。このガイドでは、荷物追跡データを扱うすべてのアプリケーションに不可欠なセキュリティ対策を解説します。

APIキーのセキュリティ

クライアントサイドのコードにキーを公開しない

最もよくある間違いは、フロントエンドのJavaScriptにAPIキーを埋め込むことです。

// WRONG — API key visible to anyone who opens browser DevTools
const response = await fetch('https://api.whereparcel.com/v2/track', {
  headers: { 'Authorization': 'Bearer wp_live_abc123...' },
});

代わりに、リクエストをバックエンド経由でプロキシしましょう。

// Frontend — calls your own API
const response = await fetch('/api/tracking', {
  method: 'POST',
  body: JSON.stringify({ trackingNumber: '123456789012' }),
});

// Backend — adds API key server-side
app.post('/api/tracking', async (req, res) => {
  const result = await fetch('https://api.whereparcel.com/v2/track', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.WHEREPARCEL_API_KEY}:${process.env.WHEREPARCEL_SECRET_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      trackingItems: [{ carrier: req.body.carrier, trackingNumber: req.body.trackingNumber }],
    }),
  });

  res.json(await result.json());
});

環境変数を使用する

APIキーはコードではなく環境変数に保存しましょう。

# .env (never commit this file)
WHEREPARCEL_API_KEY=wp_live_abc123...

# .gitignore
.env
.env.local
.env.production

キーを定期的にローテーションする

キーのローテーションスケジュールを設定し、WhereParcelのダッシュボードで複数の有効なキーを管理しましょう。

  1. 新しいAPIキーを生成する
  2. 新しいキーをアプリケーションにデプロイする
  3. 本番環境で新しいキーが動作することを確認する
  4. 古いキーを無効化する

送り状番号の列挙攻撃の防止

送り状番号は連番や予測可能なパターンに従っていることが多くあります。適切な保護がなければ、攻撃者が送り状番号を列挙して他のお客様の配送データにアクセスする可能性があります。

追跡エンドポイントにレート制限を設定する

import rateLimit from 'express-rate-limit';

const trackingLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 30, // 30 requests per window per IP
  message: { error: 'Too many tracking requests, please try again later' },
  standardHeaders: true,
});

app.use('/api/tracking', trackingLimiter);

認証を必須にする

可能な限り、匿名での追跡検索を許可しないようにしましょう。

app.post('/api/tracking', authenticate, async (req, res) => {
  const { trackingNumber } = req.body;

  // Verify this tracking number belongs to the authenticated user
  const shipment = await db.shipments.findOne({
    trackingNumber,
    userId: req.user.id,
  });

  if (!shipment) {
    return res.status(404).json({ error: 'Shipment not found' });
  }

  // Only then fetch tracking data
  const tracking = await getTracking(trackingNumber, shipment.carrier);
  res.json(tracking);
});

リクエストのフィンガープリントを追加する

列挙攻撃を検出してブロックします。

async function detectEnumeration(req) {
  const key = `tracking:${req.ip}`;
  const recentNumbers = await redis.lrange(key, 0, -1);

  // Track which numbers this IP has queried
  await redis.lpush(key, req.body.trackingNumber);
  await redis.ltrim(key, 0, 99); // Keep last 100
  await redis.expire(key, 3600); // 1 hour window

  // Flag if querying many different tracking numbers
  const uniqueNumbers = new Set(recentNumbers).size;
  if (uniqueNumbers > 20) {
    await flagSuspiciousActivity(req.ip, 'tracking_enumeration');
    return true;
  }

  return false;
}

機密データの保護

データの最小化

必要なもののみを保存しましょう。

// Store minimal tracking data
const trackingRecord = {
  trackingNumber: shipment.trackingNumber,
  carrier: shipment.carrier,
  currentStatus: tracking.status,
  lastUpdated: tracking.lastEvent.timestamp,
  // DON'T store: full address, recipient name, package contents
};

ログ内の送り状番号をマスクする

アプリケーションログに送り状番号をそのまま出力してはいけません。

function maskTrackingNumber(number) {
  if (number.length <= 6) return '***';
  return number.slice(0, 3) + '*'.repeat(number.length - 6) + number.slice(-3);
}

// Log: "Tracking lookup for 123******890"
logger.info(`Tracking lookup for ${maskTrackingNumber(trackingNumber)}`);

保存データの暗号化

追跡データをキャッシュする場合は、機密フィールドを暗号化しましょう。

import { createCipheriv, createDecipheriv, randomBytes } from 'crypto';

function encryptField(text, key) {
  const iv = randomBytes(16);
  const cipher = createCipheriv('aes-256-gcm', key, iv);
  let encrypted = cipher.update(text, 'utf8', 'hex');
  encrypted += cipher.final('hex');
  const authTag = cipher.getAuthTag();
  return `${iv.toString('hex')}:${authTag.toString('hex')}:${encrypted}`;
}

Webhookのセキュリティ

Webhook経由で追跡更新を受信する際は、送信元を検証しましょう。

Webhook署名の検証

import crypto from 'crypto';

app.post('/webhooks/tracking', (req, res) => {
  const signature = req.headers['x-whereparcel-signature'];
  const payload = JSON.stringify(req.body);

  const expected = crypto
    .createHmac('sha256', process.env.WEBHOOK_SECRET)
    .update(payload)
    .digest('hex');

  if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  // Process webhook...
  res.status(200).json({ received: true });
});

HTTPSのみを使用する

Webhookエンドポイントには必ずHTTPSを使用してください。HTTPでのWebhook登録は拒否しましょう。

WebhookのソースIPを検証する

オプションとして、WhereParcelのIP範囲からのWebhook処理のみを許可することもできます。

const ALLOWED_IPS = [
  // WhereParcel webhook IPs — check docs for current list
  '203.0.113.0/24',
];

app.post('/webhooks/tracking', verifySourceIP(ALLOWED_IPS), processWebhook);

データプライバシーの遵守

GDPRへの考慮事項

欧州のお客様にサービスを提供する場合は以下を遵守しましょう。

  • データの最小化 — 必要な追跡データのみを収集する
  • 消去の権利 — お客様の追跡履歴を削除できるようにする
  • 目的の制限 — 追跡データを配達追跡以外の目的に使用しない
  • データ保持期間 — 古い追跡データの自動削除ポリシーを設定する
// Automatic data cleanup
async function cleanupOldTrackingData() {
  const retentionPeriod = 90; // days
  const cutoff = new Date();
  cutoff.setDate(cutoff.getDate() - retentionPeriod);

  await db.trackingEvents.deleteMany({
    createdAt: { $lt: cutoff },
  });
}

越境データに関する考慮事項

追跡データは本質的に国境を越えるものです。以下の規制に注意しましょう。

  • 韓国のPIPA — 個人情報保護法(韓国の配送データに適用)
  • 日本のAPPI — 個人情報の保護に関する法律
  • 中国のPIPL — 個人情報保護法

WhereParcelはこれらの規制に準拠してデータを処理していますが、お客様のアプリケーションでもデータを適切に取り扱う必要があります。

セキュリティチェックリスト

追跡連携の構築時に、以下のチェックリストを活用してください。

  • APIキーがコードではなく環境変数に保存されている
  • APIコールがクライアントからではなくバックエンド経由で行われている
  • 追跡エンドポイントにレート制限が設定されている
  • 追跡検索に認証が必要とされている
  • 送り状番号がログに全桁記録されていない
  • Webhook署名が検証されている
  • すべてのエンドポイントでHTTPSが適用されている
  • データ保持ポリシーが実装されている
  • 古い追跡データが自動的に削除されている
  • APIキーのローテーションスケジュールが定義されている
  • 列挙攻撃の検出が導入されている

まとめ

配送連携におけるセキュリティは、いくつかの核心的な原則に帰着します。APIキーを保護すること、追跡データへのアクセスを制御すること、Webhookの送信元を検証すること、そしてデータプライバシー規制を遵守することです。これらの対策は、ビジネスとお客様の機密性の高い配送情報の両方を守ります。

API認証の詳細については、はじめにガイドをご覧ください。Webhookのセキュリティについては、Webhook ベストプラクティスをご確認ください。