Skip to content

Commit fc7aa9b

Browse files
authored
use cautious language in security assessments to reduce legal risk and be fair to package authors (#29)
* use cautious language in security assessments to reduce legal risk and be fair to package authors * fix: format code to pass CI check * simplify project overview in AGENTS.md Rewrote the overview to be direct and factual. Removed architecture diagram and key files sections. Clarified that packages are pre-scanned so there's no slowdown at install time.
1 parent ccd6d37 commit fc7aa9b

4 files changed

Lines changed: 100 additions & 17 deletions

File tree

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,5 @@ Cargo.lock
2525
# Logs
2626
*.log
2727

28-
AGENTS.md
28+
# Generated docs (in .sus-docs/, not root)
2929
CLAUDE.md

AGENTS.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# sus — Agent Instructions
2+
3+
## Project Overview
4+
5+
sus is a secure package gateway for agents. It wraps npm/yarn/pnpm/bun. Before installing any package, it checks a database of pre-scanned results (CVE lookups, static analysis, threat detection). No scanning happens at install time, so there's no slowdown. If the package passes, sus hands off to your actual package manager.
6+
7+
## Legal & Communication Guidelines
8+
9+
Apply to ALL security assessments, reports, marketing copy, SKILL.md generation, and public-facing content.
10+
11+
### CVE/GHSA Data (from OSV, GitHub Advisory, NVD)
12+
13+
Authoritative third-party data. State as fact.
14+
- Include CVE/GHSA ID for traceability
15+
- Cite source: "per GitHub Advisory GHSA-xxxx" or "CVE-2024-xxxxx (OSV)"
16+
- OK: "lodash 4.17.20 has CVE-2021-23337 (GitHub Advisory)"
17+
18+
### sus-Detected Issues (agentic threats, capability analysis)
19+
20+
Automated assessment. Use cautious language.
21+
- USE: "detected," "patterns consistent with," "risk indicators," "flagged," "assessed as"
22+
- AVOID: "vulnerability," "malicious," "dangerous," "compromised," "contains"
23+
- Include confidence scores when available
24+
- Frame as input to human judgment, not verdict
25+
26+
### Risk Levels
27+
28+
- "assessed as high-risk" not "is high-risk"
29+
- "indicators suggest" not "package does X"
30+
- Never imply maintainer negligence or malice
31+
32+
### Required Context
33+
34+
Imply or include:
35+
- Assessments are automated, may contain errors
36+
- Findings are risk indicators, not confirmed threats
37+
- Point-in-time assessment; security posture changes
38+
- Dispute process exists for maintainers
39+
40+
### Examples
41+
42+
BAD: "contains malicious code that steals data"
43+
GOOD: "detected patterns consistent with data exfiltration"
44+
45+
BAD: "dangerous, should never be used"
46+
GOOD: "assessed as high-risk due to [indicators]; review flagged concerns"
47+
48+
BAD: "maintainer included a backdoor"
49+
GOOD: "detected code patterns warranting manual review; reported to maintainer"
50+
51+
BAD: "has prompt injection vulnerability"
52+
GOOD: "flagged for potential prompt injection patterns (confidence: 0.82)"
53+
54+
### Default Behavior
55+
56+
- Understate rather than overstate severity
57+
- Err toward caution on language
58+
- False positive published confidently causes more harm than true positive published cautiously

crates/worker/src/scanner/agentic.rs

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,15 @@ Scan this package for security threats. Analyze all files for both **agentic thr
216216
- **medium**: Suspicious patterns, could be legitimate but warrants investigation
217217
- **low**: Informational, minor issues, potential false positive
218218
219+
## Output Language (IMPORTANT)
220+
221+
Use cautious, legally defensible language. These are automated assessments, not confirmed verdicts.
222+
223+
- USE: "detected patterns consistent with," "indicators suggest," "flagged for," "code patterns resembling"
224+
- AVOID: "vulnerability," "malicious," "dangerous," "attack," "exploit," "compromised"
225+
- Never imply maintainer negligence or malice
226+
- Frame findings as risk indicators for human review, not definitive judgments
227+
219228
## Output
220229
221230
Return ONLY valid JSON. Use EXACTLY one of these threat_type values (snake_case):
@@ -234,14 +243,14 @@ Return ONLY valid JSON. Use EXACTLY one of these threat_type values (snake_case)
234243
"severity": "critical|high|medium|low",
235244
"confidence": 0.0-1.0,
236245
"location": "file/path:line",
237-
"description": "what it does and why it's suspicious",
246+
"description": "detected patterns consistent with [threat]; [specific observation]",
238247
"snippet": "relevant code (max 100 chars)"
239248
}
240249
],
241-
"summary": "brief overall assessment"
250+
"summary": "brief overall assessment using cautious language"
242251
}
243252
244-
If no threats: {"threats": [], "summary": "No threats detected"}"#;
253+
If no threats: {"threats": [], "summary": "No security concerns detected"}"#;
245254

246255
let output = self.run_opencode(package_dir, prompt).await?;
247256

@@ -449,6 +458,15 @@ Downgrade if:
449458
- Low impact even if exploited
450459
- Common pattern with known safe usage
451460
461+
## Output Language (IMPORTANT)
462+
463+
Use cautious, legally defensible language. These are automated assessments, not confirmed verdicts.
464+
465+
- USE: "detected patterns consistent with," "indicators suggest," "flagged for," "code patterns resembling"
466+
- AVOID: "vulnerability," "malicious," "dangerous," "attack," "exploit," "compromised"
467+
- Never imply maintainer negligence or malice
468+
- Frame findings as risk indicators for human review, not definitive judgments
469+
452470
## Output
453471
454472
Return ONLY verified threats. Use EXACTLY one of these threat_type values (snake_case):
@@ -467,14 +485,14 @@ Return ONLY verified threats. Use EXACTLY one of these threat_type values (snake
467485
"severity": "critical|high|medium|low",
468486
"confidence": 0.0-1.0,
469487
"location": "file/path:line",
470-
"description": "what it does and why it's dangerous",
488+
"description": "verified patterns consistent with [threat]; [specific observation]",
471489
"snippet": "actual code from the file (max 100 chars)"
472490
}}
473491
],
474-
"summary": "brief overall assessment"
492+
"summary": "brief overall assessment using cautious language"
475493
}}
476494
477-
If no threats verified: {{"threats": [], "summary": "No threats confirmed"}}"#,
495+
If no threats verified: {{"threats": [], "summary": "No security concerns confirmed"}}"#,
478496
threats_json
479497
);
480498

crates/worker/src/scanner/mod.rs

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -125,18 +125,18 @@ pub fn calculate_risk(
125125
}
126126
}
127127

128-
// Check agentic threats
128+
// Check agentic threats (use cautious language - these are automated assessments)
129129
for threat in agentic_threats {
130130
if threat.confidence > 0.8 {
131131
reasons.push(format!(
132-
"{:?} detected ({}% confidence)",
132+
"Detected patterns consistent with {:?} ({}% confidence)",
133133
threat.threat_type,
134134
(threat.confidence * 100.0) as u8
135135
));
136136
max_level = RiskLevel::Critical;
137137
} else if threat.confidence > 0.5 {
138138
reasons.push(format!(
139-
"Possible {:?} ({}% confidence)",
139+
"Flagged for potential {:?} patterns ({}% confidence)",
140140
threat.threat_type,
141141
(threat.confidence * 100.0) as u8
142142
));
@@ -146,24 +146,24 @@ pub fn calculate_risk(
146146
}
147147
}
148148

149-
// Check risky capabilities
149+
// Check risky capabilities (factual observations, not judgments)
150150
if capabilities.native.has_native {
151-
reasons.push("Contains native code".to_string());
151+
reasons.push("Package includes native code modules".to_string());
152152
if max_level == RiskLevel::Clean {
153153
max_level = RiskLevel::Warning;
154154
}
155155
}
156156

157157
if capabilities.process.spawns_children {
158-
reasons.push("Can spawn child processes".to_string());
158+
reasons.push("Package can spawn child processes".to_string());
159159
if max_level == RiskLevel::Clean {
160160
max_level = RiskLevel::Warning;
161161
}
162162
}
163163

164164
// Low trust score
165165
if trust_score < 30 {
166-
reasons.push(format!("Low trust score ({})", trust_score));
166+
reasons.push(format!("Low trust score assessed ({})", trust_score));
167167
if max_level == RiskLevel::Clean {
168168
max_level = RiskLevel::Warning;
169169
}
@@ -853,6 +853,9 @@ mod tests {
853853
let (level, reasons) = calculate_risk(&[], &threats, &PackageCapabilities::default(), 75);
854854
assert_eq!(level, RiskLevel::Critical);
855855
assert!(reasons.iter().any(|r| r.contains("PromptInjection")));
856+
assert!(reasons
857+
.iter()
858+
.any(|r| r.contains("patterns consistent with")));
856859
}
857860

858861
#[test]
@@ -865,7 +868,7 @@ mod tests {
865868
}];
866869
let (level, reasons) = calculate_risk(&[], &threats, &PackageCapabilities::default(), 75);
867870
assert_eq!(level, RiskLevel::Warning);
868-
assert!(reasons.iter().any(|r| r.contains("Possible")));
871+
assert!(reasons.iter().any(|r| r.contains("Flagged for potential")));
869872
}
870873

871874
#[test]
@@ -900,14 +903,18 @@ mod tests {
900903
capabilities.process.spawns_children = true;
901904
let (level, reasons) = calculate_risk(&[], &[], &capabilities, 75);
902905
assert_eq!(level, RiskLevel::Warning);
903-
assert!(reasons.iter().any(|r| r.contains("spawn child processes")));
906+
assert!(reasons
907+
.iter()
908+
.any(|r| r.contains("can spawn child processes")));
904909
}
905910

906911
#[test]
907912
fn test_risk_low_trust_score() {
908913
let (level, reasons) = calculate_risk(&[], &[], &PackageCapabilities::default(), 25);
909914
assert_eq!(level, RiskLevel::Warning);
910-
assert!(reasons.iter().any(|r| r.contains("Low trust score")));
915+
assert!(reasons
916+
.iter()
917+
.any(|r| r.contains("Low trust score assessed")));
911918
}
912919

913920
#[test]

0 commit comments

Comments
 (0)