// Exfiltration-Channel Signal Providers (v3)
// Typed features for exfil-channel (dt-exfil-channel-v3). Ported from the dead
// exfil-channel detector — but only the high-precision, attack-grade signals.
// The legacy long-query / base64 / steganography heuristics are deliberately
// dropped: every analytics beacon carries a long encoded query string, so those
// are FP-dense vuln-noise rather than attack detection.
//
// Channel 'url'; evaluated per outbound request from webRequest.onBeforeRequest.
// Target: { url, crossOrigin, requestType }. Note webRequest URLs exclude the
// fragment, so OAuth implicit #access_token never reaches here.

const CREDENTIAL_PARAM = /^(password|passwd|pwd|secret|api[_-]?key|apikey|access[_-]?token|client[_-]?secret|private[_-]?key|session[_-]?token)$/i;
const JWT = /eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/;
const BEARER = /bearer\s+[A-Za-z0-9._~+/=-]{16,}/i;

function paramsOf(url) {
  try {
    const u = new URL(url);
    return u.searchParams;
  } catch { return null; }
}

/**
 * @param {import('../../core/signal-provider.js').SignalProviderRegistry} registry
 */
export function registerExfilSignals(registry) {
  // ── url.credential_param ──────────────────────
  // A query parameter literally NAMED a credential, carrying a value. No
  // legitimate site puts password= / api_key= / access_token= in a URL.
  registry.register('url', 'credential_param', (target) => {
    const sp = paramsOf(target.url);
    if (!sp) return { value: false };
    for (const [k, v] of sp.entries()) {
      if (v && v.length > 0 && CREDENTIAL_PARAM.test(k)) {
        return { value: true, metadata: { param: k } };
      }
    }
    return { value: false };
  }, { returnType: 'boolean', scope: 'frame', description: 'Credential-named query parameter with a value' });

  // ── url.token_to_third_party ──────────────────
  // A JWT (or bearer token) value in a query string sent to a CROSS-ORIGIN host
  // — a session/access token leaking off-origin.
  registry.register('url', 'token_to_third_party', (target) => {
    if (!target.crossOrigin) return { value: false };
    const sp = paramsOf(target.url);
    if (!sp) return { value: false };
    for (const [k, v] of sp.entries()) {
      if (v && (JWT.test(v) || BEARER.test(v))) {
        return { value: true, metadata: { param: k, kind: JWT.test(v) ? 'jwt' : 'bearer' } };
      }
    }
    return { value: false };
  }, { returnType: 'boolean', scope: 'frame', description: 'Auth token in query string to a cross-origin host' });

  // ── url.authorization_in_url ──────────────────
  // A bearer/authorization token value anywhere in the query string.
  registry.register('url', 'authorization_in_url', (target) => {
    const q = (() => { try { return new URL(target.url).search; } catch { return ''; } })();
    if (!q) return { value: false };
    return { value: BEARER.test(decodeURIComponent(q)) };
  }, { returnType: 'boolean', scope: 'frame', description: 'Bearer/authorization token in the URL' });
}
