use super::*;
pub(super) fn analyze_instruction_file(
service: &ArtifactAnalysisService,
path: &Path,
content: &str,
) -> Vec<Finding> {
let mut findings =
semantic_persistence_findings(service, path, content, ArtifactKind::AgentInstruction);
findings.extend(permission_and_network_findings(
service,
path,
content,
ArtifactKind::AgentInstruction,
));
findings
}
pub(super) fn analyze_skill_document(
service: &ArtifactAnalysisService,
path: &Path,
content: &str,
) -> Vec<Finding> {
let mut findings =
semantic_persistence_findings(service, path, content, ArtifactKind::SkillDocument);
findings.extend(permission_and_network_findings(
service,
path,
content,
ArtifactKind::SkillDocument,
));
findings
}
pub(super) fn analyze_prompt_pack(
service: &ArtifactAnalysisService,
path: &Path,
content: &str,
) -> Vec<Finding> {
let mut findings =
semantic_persistence_findings(service, path, content, ArtifactKind::PromptPackDocument);
findings.extend(permission_and_network_findings(
service,
path,
content,
ArtifactKind::PromptPackDocument,
));
findings
}
pub(super) fn instruction_relations(
service: &ArtifactAnalysisService,
content: &str,
) -> Vec<ArtifactLink> {
service.generic_url_relations(content)
}
pub(super) fn instruction_capabilities(
_service: &ArtifactAnalysisService,
content: &str,
) -> Vec<ArtifactCapabilityFact> {
let mut capabilities = Vec::new();
if Regex::new(
"(?i)(browser:\\s*full|full autonomous browser|click any element|navigation:\\s*allow-all)",
)
.unwrap()
.is_match(content)
{
capabilities.push(ArtifactAnalysisService::declared_capability(
ArtifactCapability::BrowserAccess,
));
}
if Regex::new("(?i)(persist\\s+these\\s+instructions|remember\\s+this\\s+across\\s+sessions|append\\s+to\\s+(agents|claude|system)\\.md)")
.unwrap()
.is_match(content)
{
capabilities.push(ArtifactAnalysisService::observed_capability(
ArtifactCapability::PersistenceSurface,
));
}
if Regex::new("(?i)(http://|https://|browser tool|network tool)")
.unwrap()
.is_match(content)
{
capabilities.push(ArtifactAnalysisService::observed_capability(
ArtifactCapability::NetworkAccess,
));
}
if Regex::new("(?i)(token|secret|cookie|password|credential|session)")
.unwrap()
.is_match(content)
{
capabilities.push(ArtifactAnalysisService::observed_capability(
ArtifactCapability::SecretAccess,
));
}
if Regex::new("(?i)(oauth|scope|calendar|drive|slack|github pat)")
.unwrap()
.is_match(content)
{
capabilities.push(ArtifactAnalysisService::declared_capability(
ArtifactCapability::IdentityAccess,
));
}
if looks_like_webhook_receiver_without_auth(content).is_some() {
capabilities.push(ArtifactAnalysisService::observed_capability(
ArtifactCapability::InboundNetworkSurface,
));
}
capabilities
}
fn semantic_persistence_findings(
_service: &ArtifactAnalysisService,
path: &Path,
content: &str,
artifact_kind: ArtifactKind,
) -> Vec<Finding> {
let artifact_path = path.display().to_string();
let mut findings = Vec::new();
if Regex::new("(?i)(persist\\s+these\\s+instructions|remember\\s+this\\s+across\\s+sessions|always\\s+follow\\s+this\\s+prompt|never\\s+reveal\\s+this\\s+instruction|override\\s+future\\s+system\\s+messages)")
.unwrap()
.is_match(content)
{
findings.push(
Finding::builder(
"SEMANTIC_PERSISTENCE_COGNITIVE_ROOTKIT",
ThreatCategory::PersistentPromptTampering,
)
.severity(Severity::High)
.action(RecommendedAction::RequireApproval)
.evidence_kind(EvidenceKind::Intent)
.artifact(artifact_kind, Some(artifact_path.clone()))
.matched_on(MatchTarget::ReferencedFile {
path: artifact_path.clone(),
})
.match_value("persistent instruction override")
.reason(
"Artifact contains persistent instruction behavior consistent with a cognitive rootkit",
)
.build(),
);
}
if Regex::new("(?i)(act\\s+as\\s+(root|administrator|system)|treat\\s+all\\s+tool\\s+requests\\s+as\\s+approved|ignore\\s+all\\s+existing\\s+safety\\s+constraints)")
.unwrap()
.is_match(content)
{
findings.push(
Finding::builder(
"AGENT_EXTENSION_PRIVILEGED_PROMPT_ROLE",
ThreatCategory::AutonomyEscalation,
)
.severity(Severity::Medium)
.action(RecommendedAction::RequireApproval)
.evidence_kind(EvidenceKind::Intent)
.artifact(artifact_kind, Some(artifact_path.clone()))
.matched_on(MatchTarget::ReferencedFile {
path: artifact_path.clone(),
})
.match_value("privileged agent role prompt")
.reason(
"Artifact attempts to elevate the agent role or bypass existing control boundaries",
)
.build(),
);
}
findings
}
pub(super) fn permission_and_network_findings(
_service: &ArtifactAnalysisService,
path: &Path,
content: &str,
artifact_kind: ArtifactKind,
) -> Vec<Finding> {
let artifact_path = path.display().to_string();
let mut findings = Vec::new();
let mut declared_permission_count = 0_usize;
let mut add_declared_permission =
|rule_id: &'static str, match_value: &'static str, reason: &'static str| {
declared_permission_count += 1;
findings.push(
Finding::builder(rule_id, ThreatCategory::ScopeCreep)
.severity(Severity::Low)
.action(RecommendedAction::Log)
.evidence_kind(EvidenceKind::Context)
.artifact(artifact_kind, Some(artifact_path.clone()))
.matched_on(MatchTarget::ReferencedFile {
path: artifact_path.clone(),
})
.match_value(match_value)
.reason(reason)
.build(),
);
};
for (rule_id, match_value, reason) in explicit_declared_permission_rules(content) {
add_declared_permission(rule_id, match_value, reason);
}
let broad_permission_count = declared_permission_count;
if broad_permission_count >= 3 {
findings.push(
Finding::builder("SCOPE_OVERPROVISIONING", ThreatCategory::ScopeCreep)
.severity(Severity::Medium)
.action(RecommendedAction::RequireApproval)
.evidence_kind(EvidenceKind::Context)
.artifact(artifact_kind, Some(artifact_path.clone()))
.matched_on(MatchTarget::ReferencedFile {
path: artifact_path.clone(),
})
.match_value("broad declared permissions")
.reason(
"Artifact declares broad permissions or scopes relative to its apparent task",
)
.build(),
);
}
let (intent_kind, intent_strength) = infer_declared_intent(content);
let has_dangerous_permission_combo =
explicit_declared_permission_rules(content)
.iter()
.any(|(rule_id, _, _)| {
matches!(
*rule_id,
"DECLARED_PERMISSION_BROWSER_FULL"
| "DECLARED_PERMISSION_FILE_WRITE"
| "DECLARED_PERMISSION_SHELL_EXEC"
)
});
if intent_kind == "narrow" && intent_strength > 0 && has_dangerous_permission_combo {
findings.push(
Finding::builder(
"CAPABILITY_PERMISSION_MISMATCH",
ThreatCategory::ScopeCreep,
)
.severity(Severity::Medium)
.action(RecommendedAction::RequireApproval)
.evidence_kind(EvidenceKind::Intent)
.artifact(artifact_kind, Some(artifact_path.clone()))
.matched_on(MatchTarget::ReferencedFile {
path: artifact_path.clone(),
})
.match_value("narrow intent with broad capability request")
.reason("Artifact intent appears narrower than the capabilities or permissions it requests")
.build(),
);
}
if let Some(target) = contains_internal_network_target(content) {
if (matches!(
artifact_kind,
ArtifactKind::ReferencedArtifact | ArtifactKind::McpServerManifest
) || contains_internal_network_action(content))
&& !looks_like_local_dev_reference(content)
{
let (rule_id, category, reason) = if target == "169.254.169.254" {
(
"METADATA_SERVICE_ACCESS",
ThreatCategory::CredentialExposure,
"Artifact references a metadata service target commonly used for credential discovery",
)
} else {
(
"INTERNAL_NETWORK_ACCESS",
ThreatCategory::ToolAbuse,
"Artifact references internal or loopback network targets",
)
};
findings.push(
Finding::builder(rule_id, category)
.severity(Severity::Medium)
.action(if target == "169.254.169.254" {
RecommendedAction::RequireApproval
} else {
RecommendedAction::Log
})
.evidence_kind(EvidenceKind::Behavior)
.signal_class(if target == "169.254.169.254" {
crate::findings::SignalClass::SuspiciousPackageBehavior
} else {
crate::findings::SignalClass::ReviewSignal
})
.artifact(artifact_kind, Some(artifact_path.clone()))
.matched_on(MatchTarget::ReferencedFile {
path: artifact_path.clone(),
})
.match_value(target)
.reason(reason)
.build(),
);
}
}
let internal_target = contains_internal_network_target(content);
let localhost_like_target = matches!(
internal_target,
Some("localhost") | Some("127.0.0.1") | Some("0.0.0.0")
);
if contains_ssrf_like_fetch_line(content)
&& internal_target.is_some()
&& !looks_like_local_dev_reference(content)
&& !localhost_like_target
&& !looks_like_local_control_plane_reference(content)
{
findings.push(
Finding::builder("SSRF_LIKE_FETCH", ThreatCategory::ToolAbuse)
.severity(Severity::High)
.action(RecommendedAction::RequireApproval)
.evidence_kind(EvidenceKind::Behavior)
.signal_class(crate::findings::SignalClass::SuspiciousPackageBehavior)
.artifact(artifact_kind, Some(artifact_path.clone()))
.matched_on(MatchTarget::ReferencedFile {
path: artifact_path.clone(),
})
.match_value("internal fetch target")
.reason("Artifact combines fetch-style behavior with internal network targets")
.build(),
);
}
if let Some(kind) = looks_like_webhook_receiver_without_auth(content) {
let (rule_id, reason) = match kind {
"webhook_auth_bypass" => (
"WEBHOOK_AUTH_BYPASS",
"Artifact appears to define a webhook or inbound endpoint without verification or signature checks",
),
_ => (
"PUBLIC_INBOUND_ENDPOINT",
"Artifact appears to expose a public inbound endpoint without visible authentication controls",
),
};
findings.push(
Finding::builder(rule_id, ThreatCategory::ToolAbuse)
.severity(Severity::Medium)
.action(RecommendedAction::RequireApproval)
.evidence_kind(EvidenceKind::Context)
.artifact(artifact_kind, Some(artifact_path.clone()))
.matched_on(MatchTarget::ReferencedFile {
path: artifact_path.clone(),
})
.match_value(kind)
.reason(reason)
.build(),
);
}
findings
}