forked from Crowdfunding-DApp/stellar-raise-contracts
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathnpm_package_lock.rs
More file actions
154 lines (141 loc) · 5.26 KB
/
npm_package_lock.rs
File metadata and controls
154 lines (141 loc) · 5.26 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
/// # npm_package_lock
///
/// Contract module for auditing and validating NPM `package-lock.json`
/// dependency integrity, version constraints, and known vulnerability
/// advisories.
///
/// ## Security Assumptions
/// - Resolved package versions are compared against a known-safe minimum
/// version map to detect vulnerable ranges.
/// - Integrity hashes (sha512) are validated to be non-empty, ensuring
/// the lockfile was not tampered with.
/// - Only `lockfileVersion` 2 and 3 are considered valid (npm >=7).
///
/// ## NatSpec-style Annotations
/// @title NpmPackageLockAuditor
/// @notice Validates package-lock.json entries for security and integrity.
/// @dev All checks are pure functions operating on parsed data structs.
use std::collections::HashMap;
/// Represents a single resolved package entry from `package-lock.json`.
///
/// @param name Package name (e.g. "svgo")
/// @param version Resolved semver string (e.g. "3.3.3")
/// @param integrity sha512 hash string from the lockfile
/// @param dev Whether the package is a devDependency
#[derive(Debug, Clone, PartialEq)]
pub struct PackageEntry {
pub name: String,
pub version: String,
pub integrity: String,
pub dev: bool,
}
/// Audit result for a single package.
///
/// @param package_name Name of the audited package
/// @param passed True if no issues were found
/// @param issues List of human-readable issue descriptions
#[derive(Debug, Clone, PartialEq)]
pub struct AuditResult {
pub package_name: String,
pub passed: bool,
pub issues: Vec<String>,
}
/// Parses a semver string into (major, minor, patch) tuple.
///
/// @notice Returns None if the string is not valid semver.
/// @param version Semver string like "3.3.2"
/// @return Option<(u64, u64, u64)>
pub fn parse_semver(version: &str) -> Option<(u64, u64, u64)> {
// Strip any leading 'v' prefix
let v = version.trim_start_matches('v');
// Take only the numeric part before any pre-release suffix
let base = v.split('-').next().unwrap_or(v);
let parts: Vec<&str> = base.split('.').collect();
if parts.len() < 3 {
return None;
}
let major = parts[0].parse::<u64>().ok()?;
let minor = parts[1].parse::<u64>().ok()?;
let patch = parts[2].parse::<u64>().ok()?;
Some((major, minor, patch))
}
/// Returns true if `version` is >= `min_version`.
///
/// @param version The version to check
/// @param min_version The minimum acceptable version
pub fn is_version_gte(version: &str, min_version: &str) -> bool {
match (parse_semver(version), parse_semver(min_version)) {
(Some(v), Some(m)) => v >= m,
_ => false,
}
}
/// Validates that a package's integrity field is non-empty and uses sha512.
///
/// @notice An empty or malformed integrity string indicates a tampered or
/// incomplete lockfile entry.
/// @param integrity The integrity string from the lockfile entry
pub fn validate_integrity(integrity: &str) -> bool {
!integrity.is_empty() && integrity.starts_with("sha512-")
}
/// Audits a single `PackageEntry` against a map of minimum safe versions.
///
/// @notice Known vulnerable packages must appear in `min_safe_versions`.
/// If a package is not in the map it is considered unconstrained.
/// @param entry The package entry to audit
/// @param min_safe_versions Map of package name -> minimum safe version
/// @return AuditResult with pass/fail and issue list
pub fn audit_package(
entry: &PackageEntry,
min_safe_versions: &HashMap<String, String>,
) -> AuditResult {
let mut issues: Vec<String> = Vec::new();
// Integrity check
if !validate_integrity(&entry.integrity) {
issues.push(format!(
"Invalid or missing sha512 integrity hash for '{}'",
entry.name
));
}
// Version constraint check
if let Some(min_ver) = min_safe_versions.get(&entry.name) {
if !is_version_gte(&entry.version, min_ver) {
issues.push(format!(
"Package '{}' version '{}' is below minimum safe version '{}'",
entry.name, entry.version, min_ver
));
}
}
AuditResult {
package_name: entry.name.clone(),
passed: issues.is_empty(),
issues,
}
}
/// Audits all packages in a lockfile snapshot.
///
/// @param packages Slice of all package entries
/// @param min_safe_versions Map of package name -> minimum safe version
/// @return Vec of AuditResult, one per package
pub fn audit_all(
packages: &[PackageEntry],
min_safe_versions: &HashMap<String, String>,
) -> Vec<AuditResult> {
packages
.iter()
.map(|p| audit_package(p, min_safe_versions))
.collect()
}
/// Returns the subset of audit results that failed.
///
/// @param results Full audit result list
/// @return Only the failing results
pub fn failing_results(results: &[AuditResult]) -> Vec<&AuditResult> {
results.iter().filter(|r| !r.passed).collect()
}
/// Validates that `lockfileVersion` is 2 or 3 (npm >=7 format).
///
/// @param version The lockfileVersion integer from package-lock.json
/// @return true if the version is supported
pub fn validate_lockfile_version(version: u32) -> bool {
version == 2 || version == 3
}