Config-Abstraction

 view release on metacpan or  search on metacpan

Changes  view on Meta::CPAN

	[ Security ]
	- Fix YAML code injection: YAML::XS v0.91 ignores $YAML::XS::DisableCode and
	  materialises callable CODE refs from !!perl/code tags. New private helper
	  _sanitize_yaml_values() recursively replaces CODE and GLOB refs with undef
	  after every LoadFile/Load call. Do not rely on DisableCode for security.
	- Fix XML external entity (XXE) injection at all four XMLin() call sites: file
	  content is now pre-read and checked for <!ENTITY ... SYSTEM|PUBLIC declarations
	  before parsing; XMLin receives a string reference rather than a filename so no
	  entity is resolved. Files containing external entity declarations are skipped
	  with a carp warning.
	- Fix XXE bypass via Config::Auto fallback: after the XML XXE guard blocked a
	  file and set $data to undef, YAML::XS returned a non-HASH scalar (truthy),
	  causing the if(!$data) fallback chain to run; Config::Auto then re-read and
	  expanded the entity. Fixed by saving raw file content before the parsers run
	  and conditioning the entire fallback chain on the absence of entity declarations.
	- Fix encryption_key_file silent failure: when encryption_key_file was specified
	  explicitly in the constructor but the named file did not exist, the module
	  silently fell back to unencrypted operation. The constructor now croaks
	  immediately. Files sourced from environment variables still fail silently.

	[ Performance ]

t/extended_tests.t  view on Meta::CPAN

};

# ===========================================================================
# Config::Abstract hidden via local %INC + Test::Without::Module:
# - line 701 FALSE: _load_driver('Config::Abstract') returns 0
# - line 719 condition A=FALSE,B=TRUE: $data is truthy (scalar from YAML) but
#   NOT a HASH ref, which is the one sub-condition path not yet covered.
#
# Content "just a plain string" is parsed by YAML::XS as a truthy scalar.
# XMLin at line 697 fails (not XML) so $data stays as that truthy scalar.
# With Config::Abstract blocked, line 701 is FALSE → $data unchanged → line 719:
#   !$data    = FALSE (data truthy)
#   ref ne HASH = TRUE (ref is "")
# This is the previously-uncovered A=FALSE,B=TRUE condition path.
# ===========================================================================

subtest '_load_config() - 701 FALSE + 719 A=FALSE,B=TRUE via Config::Abstract hidden' => sub {
	# Remove Config::Abstract from the %INC cache so that the eval "require"
	# inside _load_driver will search @INC (where Test::Without::Module blocks it).
	# 'local %INC' restores the cache after the subtest exits.
	local %INC = %INC;

t/mutant_killers.t  view on Meta::CPAN

	my $cfg3 = Config::Abstraction->new(
		data        => { sentinel2 => 'safe2' },
		config_dirs => [File::Spec->curdir()],	# '.' → curdir
	);
	ok(defined($cfg3), 'object created with curdir config_dirs');
	is($cfg3->get('sentinel2'), 'safe2', 'sentinel2 present with curdir effective_dir');
};

# ===========================================================================
# COND_INV_1365_8
# _load_config() - XXE blocked in all-parsers (config_file) XML path
# Kills: inverting the entity check would allow XXE to execute
# ===========================================================================
subtest '_load_config() - config_file XML with XXE entity blocked (COND_INV_1365_8)' => sub {
	my $dir = tempdir(CLEANUP => 1);
	# Use a full XML file with header so early XML detection fires (line 1363)
	my $xxe = '<?xml version="1.0"?>' .
		'<!DOCTYPE c [<!ENTITY e SYSTEM "file:///etc/passwd">]>' .
		'<config><key>&e;</key></config>';
	my $path = _write_file($dir, 'myconfig', $xxe);

	my $cfg;
	_silenced(sub {
		eval {

t/mutant_killers.t  view on Meta::CPAN

	ok(defined($cfg), 'self-closing XML config_file handled');
	# If COND_INV_1468_9 were inverted, XML::Simple would be skipped even when present
	SKIP: {
		skip 'XML::Simple not installed', 2
			unless eval { require XML::Simple; 1 };
		is($cfg->get('dbhost'), 'fallback-db', 'late XML fallback: attribute key loaded');
		is($cfg->get('port'),   '3307',        'late XML fallback: port attribute loaded');
	}
};

subtest '_load_config() - late XML fallback with XXE entity blocked (COND_INV_1470_10)' => sub {
	my $dir = tempdir(CLEANUP => 1);
	# Self-closing XML with XXE — no <?xml header, so goes through late path
	my $path = _write_file($dir, 'xxe_noheader',
		'<!DOCTYPE d [<!ENTITY s SYSTEM "file:///etc/passwd">]><c k="&s;"/>');

	my $cfg;
	_silenced(sub {
		eval {
			$cfg = Config::Abstraction->new(
				data        => { guard => 'present' },

t/mutant_killers.t  view on Meta::CPAN

	);
	is($cfg2->get('v'), 'my_plaintext', 'token from encrypt_value decrypts correctly');
};

# ===========================================================================
# COND_INV_2592_5
# _parse_config_string() - XXE entity check blocks expansion in parsed XML strings
# Kills: inverting !~ to =~ would allow XXE content, block clean XML
# Uses TestProxy to bypass the UNIVERSAL::isa access guard
# ===========================================================================
subtest '_parse_config_string() - XML with XXE entity blocked (COND_INV_2592_5)' => sub {
	SKIP: {
		skip 'XML::Simple not installed', 2
			unless eval { require XML::Simple; 1 };

		my $cfg = Config::Abstraction::MutantProxy->new(
			data        => { _init => 1 },
			config_dirs => [],
		);

		my $xxe_xml = '<?xml version="1.0"?>' .



( run in 0.890 second using v1.01-cache-2.11-cpan-800906f7e73 )