CGI-Info

 view release on metacpan or  search on metacpan

t/is_ai.t  view on Meta::CPAN

	$ENV{HTTP_USER_AGENT} = 'Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); ChatGPT-User/1.0; +https://openai.com/bot)';
	$i = new_ok('CGI::Info');
	ok($i->is_ai() == 1,                  'ChatGPT-User => is_ai true');
	ok($i->is_robot() == 1,               'ChatGPT-User => is_robot true (no "bot" token - tests invariant)');

	# ---------------------------------------------------------------
	# PerplexityBot
	# ---------------------------------------------------------------
	$ENV{HTTP_USER_AGENT} = 'Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; PerplexityBot/1.0; +https://perplexity.ai/perplexitybot)';
	$i = new_ok('CGI::Info');
	ok($i->is_ai() == 1,                  'PerplexityBot => is_ai true');

	# ---------------------------------------------------------------
	# CCBot (Common Crawl -- primary data source for many LLM trainers)
	# ---------------------------------------------------------------
	$ENV{HTTP_USER_AGENT} = 'CCBot/2.0 (https://commoncrawl.org/faq/)';
	$i = new_ok('CGI::Info');
	ok($i->is_ai() == 1,                  'CCBot => is_ai true');

	# ---------------------------------------------------------------
	# cohere-ai -- unusual UA pattern with no "bot" or "spider" token
	# ---------------------------------------------------------------
	$ENV{HTTP_USER_AGENT} = 'cohere-ai/1.0';
	$i = new_ok('CGI::Info');
	ok($i->is_ai() == 1,                  'cohere-ai => is_ai true');
	ok($i->is_robot() == 1,               'cohere-ai => is_robot true (no "bot" token)');

	# ---------------------------------------------------------------
	# Google-Extended (AI training opt-out signal) -- no "bot"/"spider"
	# ---------------------------------------------------------------
	$ENV{HTTP_USER_AGENT} = 'Mozilla/5.0 (compatible; Google-Extended)';
	$i = new_ok('CGI::Info');
	ok($i->is_ai() == 1,                  'Google-Extended => is_ai true');
	ok($i->is_robot() == 1,               'Google-Extended => is_robot true (no "bot" token)');

	# ---------------------------------------------------------------
	# meta-externalagent (Meta/Facebook AI training) -- no bot/spider
	# ---------------------------------------------------------------
	$ENV{HTTP_USER_AGENT} = 'meta-externalagent/1.1 (+https://developers.facebook.com/docs/sharing/webmasters/crawler)';
	$i = new_ok('CGI::Info');
	ok($i->is_ai() == 1,                  'meta-externalagent => is_ai true');
	ok($i->is_robot() == 1,               'meta-externalagent => is_robot true (no "bot" token)');

	# ---------------------------------------------------------------
	# anthropic-ai token (alternative Anthropic UA pattern)
	# ---------------------------------------------------------------
	$ENV{HTTP_USER_AGENT} = 'anthropic-ai/1.0';
	$i = new_ok('CGI::Info');
	ok($i->is_ai() == 1,                  'anthropic-ai UA => is_ai true');
	ok($i->is_robot() == 1,               'anthropic-ai UA => is_robot true (no "bot" token)');

	# ---------------------------------------------------------------
	# Claude-Web (alternative Anthropic headless-browser UA)
	# ---------------------------------------------------------------
	$ENV{HTTP_USER_AGENT} = 'Claude-Web/1.0';
	$i = new_ok('CGI::Info');
	ok($i->is_ai() == 1,                  'Claude-Web => is_ai true');
	ok($i->is_robot() == 1,               'Claude-Web => is_robot true (no "bot" token)');

	# ---------------------------------------------------------------
	# Ordinary desktop browser -- must NOT trigger is_ai
	# ---------------------------------------------------------------
	$ENV{HTTP_USER_AGENT} = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
	$i = new_ok('CGI::Info');
	ok($i->is_ai() == 0,                  'Chrome desktop UA => is_ai false');
	ok($i->browser_type() ne 'ai',        'Chrome desktop UA => browser_type not ai');

	# ---------------------------------------------------------------
	# Googlebot -- is a robot (search engine) but NOT an AI trainer
	# ---------------------------------------------------------------
	$ENV{HTTP_USER_AGENT} = 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)';
	$i = new_ok('CGI::Info');
	ok($i->is_ai() == 0,                  'Googlebot => is_ai false (search, not AI trainer)');
	ok($i->is_robot() == 1,               'Googlebot => is_robot true');

	# ---------------------------------------------------------------
	# IS_AI environment variable override (for testing / classification)
	# ---------------------------------------------------------------
	{
		local $ENV{IS_AI} = 1;
		$ENV{HTTP_USER_AGENT} = 'Mozilla/5.0 Firefox/120';
		$i = new_ok('CGI::Info');
		ok($i->is_ai() == 1, 'IS_AI=1 env override forces true for non-AI UA');
	}

	{
		local $ENV{IS_AI} = 0;
		$ENV{HTTP_USER_AGENT} = 'ClaudeBot/1.0';
		$i = new_ok('CGI::Info');
		ok($i->is_ai() == 0, 'IS_AI=0 env override forces false for known AI UA');
	}

	# ---------------------------------------------------------------
	# Call-order invariant: is_robot() called BEFORE is_ai()
	# ChatGPT-User has no "bot"/"spider" token so the old is_robot()
	# regex alone would return 0; the fix is that is_robot() now calls
	# is_ai() internally.
	# ---------------------------------------------------------------
	$ENV{HTTP_USER_AGENT} = 'Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); ChatGPT-User/1.0; +https://openai.com/bot)';
	$i = new_ok('CGI::Info');
	ok($i->is_robot() == 1, 'call order: is_robot() first => true for ChatGPT-User');
	ok($i->is_ai()   == 1, 'call order: is_ai()    after => still true');

	# ---------------------------------------------------------------
	# Call-order invariant: is_ai() called BEFORE is_robot()
	# Setting $self->{is_robot} inside is_ai() means the subsequent
	# is_robot() call hits the cache and returns true.
	# ---------------------------------------------------------------
	$ENV{HTTP_USER_AGENT} = 'Claude-Web/1.0';
	$i = new_ok('CGI::Info');
	ok($i->is_ai()   == 1, 'call order: is_ai()   first => true for Claude-Web');
	ok($i->is_robot() == 1, 'call order: is_robot() after => true (set by is_ai)');

	# ---------------------------------------------------------------
	# Security invariant: SQL injection in an AI crawler UA must still
	# trigger status 403 via is_robot().  Previously is_ai() ran first
	# and the injection check was never reached.
	# is_ai() on a fresh instance still correctly identifies the UA.
	# ---------------------------------------------------------------
	$ENV{HTTP_USER_AGENT} = 'GPTBot/1.0 SELECT foo AND bar FROM baz';
	$i = new_ok('CGI::Info');
	ok($i->is_robot(),                   'SQL injection in GPTBot UA => is_robot true');
	cmp_ok($i->status(), '==', 403,      'SQL injection in GPTBot UA => status 403');

	# A separate instance checks the AI identity of the same UA pattern
	# (without injection the regex still matches GPTBot token)



( run in 0.619 second using v1.01-cache-2.11-cpan-9789f410c06 )