117 lines
3.7 KiB
Rego
117 lines
3.7 KiB
Rego
# Secret Detection Policy - OPA Rego
|
|
# Sprint: SPRINT_20260104_004_POLICY - Task PSD-010
|
|
#
|
|
# This Rego policy provides advanced logic for secret detection gates.
|
|
# Use this for complex organizations that need conditional logic based on
|
|
# environment, image source, or team ownership.
|
|
#
|
|
# Input schema (from ScanResult):
|
|
# input.secrets.findings[] - Array of SecretFinding objects
|
|
# input.secrets.bundle.version - Bundle version used for detection
|
|
# input.secrets.maskApplied - Whether masking was applied
|
|
# input.image.name - Full image name
|
|
# input.image.registry - Registry domain
|
|
# input.environment - Deployment environment (dev/staging/prod)
|
|
|
|
package stella.policy.secrets
|
|
|
|
import future.keywords.contains
|
|
import future.keywords.if
|
|
import future.keywords.in
|
|
|
|
default deny := []
|
|
default warn := []
|
|
|
|
# Block any critical secrets in production
|
|
deny contains msg if {
|
|
input.environment == "production"
|
|
finding := input.secrets.findings[_]
|
|
finding.severity == "critical"
|
|
msg := sprintf(
|
|
"BLOCKED: Critical secret '%s' detected in production image %s. Rule: %s",
|
|
[finding.ruleId, input.image.name, finding.ruleName]
|
|
)
|
|
}
|
|
|
|
# Block high-severity secrets with high confidence in all environments
|
|
deny contains msg if {
|
|
finding := input.secrets.findings[_]
|
|
finding.severity == "high"
|
|
finding.confidence == "high"
|
|
msg := sprintf(
|
|
"BLOCKED: High-confidence secret '%s' detected in %s. File: %s",
|
|
[finding.ruleName, input.image.name, finding.filePath]
|
|
)
|
|
}
|
|
|
|
# Allow low-confidence findings in dev, but block in prod/staging
|
|
deny contains msg if {
|
|
input.environment in {"production", "staging"}
|
|
finding := input.secrets.findings[_]
|
|
finding.severity in {"high", "critical"}
|
|
finding.confidence == "low"
|
|
msg := sprintf(
|
|
"BLOCKED: Low-confidence secret finding requires review before %s deployment. Rule: %s",
|
|
[input.environment, finding.ruleName]
|
|
)
|
|
}
|
|
|
|
# Warn on medium severity secrets in any environment
|
|
warn contains msg if {
|
|
finding := input.secrets.findings[_]
|
|
finding.severity == "medium"
|
|
msg := sprintf(
|
|
"WARNING: Medium-severity secret '%s' in %s. Consider adding to exceptions if legitimate.",
|
|
[finding.ruleName, finding.filePath]
|
|
)
|
|
}
|
|
|
|
# Warn if secret count exceeds threshold (potential bulk exposure)
|
|
warn contains msg if {
|
|
count(input.secrets.findings) > 10
|
|
msg := sprintf(
|
|
"WARNING: High number of secrets detected (%d findings). Review for bulk credential exposure.",
|
|
[count(input.secrets.findings)]
|
|
)
|
|
}
|
|
|
|
# Block export without masking
|
|
deny contains msg if {
|
|
input.context == "export"
|
|
not input.secrets.maskApplied
|
|
msg := "BLOCKED: Secrets must be masked before export. Enable masking in revelation policy."
|
|
}
|
|
|
|
# Require bundle signature verification
|
|
deny contains msg if {
|
|
input.environment == "production"
|
|
not input.secrets.bundle.verified
|
|
msg := sprintf(
|
|
"BLOCKED: Secret detection bundle '%s' signature verification failed.",
|
|
[input.secrets.bundle.id]
|
|
)
|
|
}
|
|
|
|
# Warn on outdated bundle in production
|
|
warn contains msg if {
|
|
input.environment == "production"
|
|
input.secrets.bundle.ageHours > 168 # 7 days
|
|
msg := sprintf(
|
|
"WARNING: Secret detection bundle is over 7 days old (version: %s). Update for latest rules.",
|
|
[input.secrets.bundle.version]
|
|
)
|
|
}
|
|
|
|
# Allowlist: Skip checks for internal base images
|
|
skip_secret_checks if {
|
|
startswith(input.image.registry, "internal.registry.")
|
|
input.image.isBaseImage
|
|
}
|
|
|
|
# Allowlist: Skip low-severity in dev environment
|
|
skip_warning[finding.id] if {
|
|
input.environment == "development"
|
|
finding := input.secrets.findings[_]
|
|
finding.severity == "low"
|
|
}
|