Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
0f26941
Add Lint & format workflows.
Einswilli Sep 6, 2025
40e02aa
Merge branch 'AllDotPy:master' into master
Einswilli Sep 6, 2025
b9913ca
Add Code of Conduct.
Einswilli Sep 6, 2025
b6bd262
Add Contributions guide.
Einswilli Sep 6, 2025
f9dfa45
Merge branch 'master' of https://github.com/Einswilli/Valkyrie
Einswilli Sep 6, 2025
1afc3a0
Update CONTRUBUTING.md and Pyproject.toml files.
Einswilli Sep 6, 2025
30d578c
Update CONTRUBUTING.md and Pyproject.toml files.
Einswilli Sep 6, 2025
f1b4428
remove main.py file form the root dir.
Einswilli Sep 6, 2025
850e5d7
Merge branch 'AllDotPy:master' into master
Einswilli Sep 6, 2025
d6e7800
valkyrie.core: Add Scanner engine and types modules.
Einswilli Sep 6, 2025
dbaee48
feat(core): Add Scanner Configuration types and Scanner Engine.
Einswilli Sep 6, 2025
7df00ad
Merge branch 'AllDotPy:master' into master
Einswilli Sep 6, 2025
6c125cf
valkyrie.plugins: Add Security Rules Base class Secrets detector Plugin.
Einswilli Sep 7, 2025
b6c7d6c
__ # Merge branch 'master' of https://github.com/Einswilli/Valkyrie i…
Einswilli Sep 7, 2025
1ba0352
valkyrie.plugins: Add Plugin manager.
Einswilli Sep 7, 2025
d023343
valkyrie.plugins: Add Plugin manager.
Einswilli Sep 7, 2025
a2fef8b
valkyrie.plugins: Add Plugin manager.
Einswilli Sep 7, 2025
e90ba68
Merge branch 'AllDotPy:master' into master
Einswilli Sep 7, 2025
73474ef
valkyrie.plugins: Add Plugin manager.
Einswilli Sep 7, 2025
e1238c4
valkyrie.plugins: Add Plugin manager.
Einswilli Sep 7, 2025
4bfa0fa
Merge branch 'AllDotPy:master' into master
Einswilli Sep 7, 2025
e3b808a
Merge branch 'AllDotPy:master' into feat.plugins
Einswilli Sep 7, 2025
ae14c1d
Feat (plugins): Add Plugin manager
Einswilli Sep 7, 2025
f0310b4
refractor: valkyrie.plugins.secrets.
Einswilli Sep 8, 2025
ebb3a38
Merge branch 'AllDotPy:master' into master
Einswilli Sep 8, 2025
d9677d4
Merge branch 'AllDotPy:master' into feat.plugins
Einswilli Sep 8, 2025
a49198d
Merge branch 'master' into feat.plugins
Einswilli Sep 8, 2025
cf596f8
Merge branch into feat.plugins
Einswilli Sep 8, 2025
e071c5f
Merge branch 'master' into feat.plugins
Einswilli Sep 8, 2025
ce522fa
Refractor: Refractor secrets plugin to make it more cleanner.
Einswilli Sep 8, 2025
d716401
Merge branch 'AllDotPy:master' into master
Einswilli Sep 8, 2025
f405674
valkyrie,plugins: add vulnera vulnerablity scanner plugin
Einswilli Sep 11, 2025
bdc5a04
valkyrie,plugins: Fix typo in Vulnera.parser
Einswilli Sep 11, 2025
a35a54e
Merge branch 'AllDotPy:master' into master
Einswilli Sep 11, 2025
fd7fb9f
valkkyrie.plugins: Add iamx plugin foor IAM configuration Scanning
Einswilli Sep 12, 2025
46c4453
valkkyrie.plugins: Add iamx plugin foor IAM configuration Scanning
Einswilli Sep 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 139 additions & 0 deletions valkyrie/plugins/iamx/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import hashlib
from pathlib import Path
from typing import Dict, List, Any

from valkyrie.plugins import BaseSecurityRule
from valkyrie.core.types import (
ScanRule, ScannerPlugin, RuleMetadata, SecurityFinding,
FileLocation, SeverityLevel, FindingCategory,
)

from .conf import RISKY_PATTERNS


####
## IAM CONFIGURATION RULE
#####
class IAMConfigurationRule(BaseSecurityRule):
"""Rule for detecting risky IAM configurations"""

def __init__(self):
metadata = RuleMetadata(
id = "iam-001",
name = "IAM Configuration Scanner",
description = "Detects overly permissive IAM policies and configurations",
category = FindingCategory.IAM_CONFIG,
severity = SeverityLevel.HIGH,
author = "Valkyrie Core Team",
tags = {"iam", "aws", "gcp", "azure", "permissions"}
)
super().__init__(metadata)

# Define risky patterns
self.risky_patterns = RISKY_PATTERNS

def is_applicable(self, file_path: Path) -> bool:
"""Check if file contains IAM configurations"""

iam_files = {
'.json', '.yaml', '.yml', '.tf', '.hcl'
}

if file_path.suffix.lower() not in iam_files:
return False

# Check filename patterns
iam_patterns = [
'policy', 'iam', 'role', 'permission', 'access',
'cloudformation', 'terraform', 'main.tf'
]

filename_lower = file_path.name.lower()
return any(
pattern in filename_lower
for pattern in iam_patterns
)

async def scan(
self,
file_path: Path,
content: str
) -> List[SecurityFinding]:
"""Scan IAM configuration files"""

findings = []
lines = content.split('\n')

for line_num, line in enumerate(lines, 1):
for pattern_info in self.risky_patterns:
matches = pattern_info.pattern.finditer(line)

for match in matches:
finding = SecurityFinding(
id = hashlib.md5(f"{file_path}:{line_num}:{pattern_info.name}".encode()).hexdigest(),
title = f"Risky IAM Configuration: {pattern_info.name}",
description = pattern_info.description,
severity = pattern_info.severity,
category = self.metadata.category,
location = FileLocation(
file_path = file_path,
line_number = line_num,
column_start = match.start(),
column_end = match.end()
),
rule_id = self.metadata.id,
confidence = 0.8,
metadata={
"pattern_name": pattern_info["name"],
"line_content": line.strip(),
"cloud_provider": self._detect_cloud_provider(content)
},
remediation_advice = (
"Apply principle of least privilege. "
"Specify exact resources and actions needed."
)
)
findings.append(finding)

return findings

def _detect_cloud_provider(self, content: str) -> str:
"""Detect cloud provider from content"""

content_lower = content.lower()

if 'amazonaws.com' in content_lower or 'aws:' in content_lower:
return "AWS"
elif 'googleapis.com' in content_lower or 'gcp' in content_lower:
return "GCP"
elif 'azure' in content_lower or 'microsoft.com' in content_lower:
return "Azure"
else:
return "Unknown"


####
## IAM CONFIGURATION PLUGIN
#####
class IAMPlugin(ScannerPlugin):
"""Plugin for IAM configuration scanning"""

@property
def name(self) -> str:
return "iam-scanner"

@property
def version(self) -> str:
return "0.1.0"

async def initialize(self, config: Dict[str, Any]) -> None:
"""Initialize IAM plugin"""
pass

async def get_rules(self) -> List[ScanRule]:
"""Return IAM scanning rules"""
return [IAMConfigurationRule()]

async def cleanup(self) -> None:
"""Cleanup plugin resources"""
pass
62 changes: 62 additions & 0 deletions valkyrie/plugins/iamx/conf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import re
from dataclasses import dataclass
from typing import (
List, Pattern, Optional
)

from valkyrie.core.types import SeverityLevel


####
## SECRET PATTERN MODEL
#####
@dataclass
class RiskyPattern:
"""Pattern definition for secret detection"""

name: str
pattern: Pattern[str]
description: Optional[str] = None
severity: SeverityLevel = SeverityLevel.MEDIUM


####
RISKY_PATTERNS: List[RiskyPattern] = [
## AMAZON WEB SERVICES (AWS)
RiskyPattern(
**{
"name": "AWS Wildcard Resource",
"pattern": re.compile(r'"Resource"\s*:\s*"\*"', re.IGNORECASE),
"description": "Policy allows access to all resources",
"severity": SeverityLevel.CRITICAL
},
),
RiskyPattern(
**{
"name": "AWS Admin Access",
"pattern": re.compile(r'"Action"\s*:\s*"\*"', re.IGNORECASE),
"description": "Policy grants all actions (admin access)",
"severity": SeverityLevel.CRITICAL
},
),

## GOOGLE CLOUD (GCP)
RiskyPattern(
**{
"name": "GCP All Scopes",
"pattern": re.compile(r'https://www\.googleapis\.com/auth/cloud-platform', re.IGNORECASE),
"description": "Grants access to all Google Cloud Platform services",
"severity": SeverityLevel.HIGH
},
),

## MICROSOFT AZURE
RiskyPattern(
**{
"name": "Azure Contributor Role",
"pattern": re.compile(r'"roleDefinitionId".*"b24988ac-6180-42a0-ab88-20f7382dd24c"', re.IGNORECASE),
"description": "Grants broad contributor access to Azure resources",
"severity": SeverityLevel.MEDIUM
}
)
]
8 changes: 5 additions & 3 deletions valkyrie/plugins/secrets/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
FileLocation
)

from valkyrie.plugins.secrets.conf import (
from .conf import (
SECRETS_PATTERNS, SecretPattern
)

Expand Down Expand Up @@ -82,11 +82,13 @@ async def scan(self, file_path: Path, content: str) -> List[SecurityFinding]:
for line_num, line in enumerate(lines, 1):
# Skip comments and obvious false positives
line_lower = line.lower().strip()
if (line_lower.startswith('#') or
if (
line_lower.startswith('#') or
line_lower.startswith('//') or
'example' in line_lower or
'placeholder' in line_lower or
'your_api_key_here' in line_lower):
'your_api_key_here' in line_lower
):
continue

for pattern in self.patterns:
Expand Down
2 changes: 1 addition & 1 deletion valkyrie/plugins/vulnera/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ def name(self) -> str:

@property
def version(self) -> str:
return "1.0.0"
return "0.1.0"

async def initialize(self, config: Dict[str, Any]) -> None:
"""Initialize plugin and load vulnerability database"""
Expand Down
4 changes: 2 additions & 2 deletions valkyrie/plugins/vulnera/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ def parse(self) -> List[Dependency]:
#####
DependencyParser.register()
class PackageLockParser(BaseDependencyParser):
"""Parser foor package-lock.json (Node.js)"""
"""Parser for package-lock.json (Node.js)"""

@property
def dep_file(self):
Expand Down Expand Up @@ -596,7 +596,7 @@ def parse(self) -> List[Dependency]:
#####
DependencyParser.register()
class ComposerLockParser(BaseDependencyParser):
"""Parser pour composer.lock (PHP)"""
"""Parser for composer.lock (PHP)"""

@property
def dep_file(self):
Expand Down