pmacs/src/audit/rules.rs

155 lines
5.3 KiB
Rust

// audit/rules.rs --- Rule metadata table aligned with audit-rules.scm.
//! Rule metadata for the audit lint (T M7.9).
//!
//! [`DEFAULT_RULES`] is a fixed-order array; each entry corresponds
//! by index to the `pattern_index` produced by
//! [`tree_sitter::QueryMatch::pattern_index`] when running the
//! [`crate::audit::AUDIT_QUERY_SOURCE`] query against a Lua AST. The
//! ordering here mirrors the comments in
//! `audit/audit-rules.scm`; a unit test asserts that the pattern
//! count and the table length agree at compile time.
use serde::{Deserialize, Serialize};
/// Rule severity. The CLI exits non-zero if any [`Severity::Error`]
/// finding is reported.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Severity {
/// Hard violation. Forbidden by the v1.0 rule set; the audit
/// reviewer cannot classify these away without changing the
/// rule set itself.
Error,
/// Soft violation. Permitted only for packages whose manifest
/// declares the relevant capability (filesystem write, process
/// spawn). The classification mechanism that promotes these to
/// errors lives in M11.
Warning,
/// Informational finding. The reviewer must classify each one;
/// the lint cannot decide on its own (e.g., reach-around to
/// another package's private surface requires knowing that
/// package's `exports`).
Info,
}
/// Static rule metadata. The `query` field exists for documentation
/// only --- the actual matching uses the compiled
/// [`tree_sitter::Query`] from `audit/audit-rules.scm`. We keep the
/// human-readable rule shape here so JSON consumers can render
/// findings without re-parsing the .scm file.
#[derive(Debug, Clone, Copy)]
pub struct AuditRule {
/// Stable identifier referenced in CI configuration and the
/// JSON report. Lowercase, hyphen-separated.
pub name: &'static str,
/// Severity at which this rule fires.
pub severity: Severity,
/// One-line human-readable explanation.
pub message: &'static str,
}
/// The v1.0 rule table. Order **must** mirror `audit-rules.scm`'s
/// pattern order; an index mismatch silently misclassifies findings.
///
/// The unit test `default_rules_count_matches_query_pattern_count`
/// asserts the alignment at runtime.
pub const DEFAULT_RULES: &[AuditRule] = &[
// 0
AuditRule {
name: "no-private-surface-require",
severity: Severity::Error,
message: "require() of pmacs._internal.* / pmacs.core.* surface is forbidden",
},
// 1
AuditRule {
name: "no-private-surface-identifier",
severity: Severity::Error,
message: "identifiers prefixed with `_pmacs_internal_` or `_core_` are private",
},
// 2
AuditRule {
name: "no-ffi-call",
severity: Severity::Error,
message: "ffi.cdef / ffi.load / ffi.metatype escape the Lua sandbox",
},
// 3
AuditRule {
name: "no-package-loadlib",
severity: Severity::Error,
message: "package.loadlib loads native code outside the package sandbox",
},
// 4
AuditRule {
name: "no-package-cpath-mutation",
severity: Severity::Error,
message: "mutating package.cpath extends the C-loader search path",
},
// 5
AuditRule {
name: "no-debug-sethook",
severity: Severity::Error,
message: "debug.sethook would override the M7.8 cancellation hook",
},
// 6
AuditRule {
name: "no-debug-setmetatable",
severity: Severity::Error,
message: "debug.setmetatable bypasses normal metatable rules",
},
// 7
AuditRule {
name: "no-rawget-rawset-on-globals",
severity: Severity::Error,
message: "rawget(_G,...) / rawset(_G,...) escape per-package _ENV sandboxing",
},
// 8
AuditRule {
name: "no-setfenv-getfenv",
severity: Severity::Error,
message: "setfenv / getfenv manipulate other packages' environments",
},
// 9
AuditRule {
name: "no-fs-mutation-io-open-write",
severity: Severity::Warning,
message: "io.open(..., \"w|a|+\") writes the filesystem; declare fs access",
},
// 10
AuditRule {
name: "no-fs-mutation-os",
severity: Severity::Warning,
message: "os.remove / os.rename mutate the filesystem; declare fs access",
},
// 11
AuditRule {
name: "no-process-spawn-io",
severity: Severity::Warning,
message: "io.popen spawns a child process; declare process access",
},
// 12
AuditRule {
name: "no-process-spawn-os",
severity: Severity::Warning,
message: "os.execute spawns a child process; declare process access",
},
// 13
AuditRule {
name: "no-process-spawn-pmacs",
severity: Severity::Warning,
message: "pmacs.process.spawn launches a managed child; declare process access",
},
// 14
AuditRule {
name: "reach-around-require",
severity: Severity::Info,
message: "dotted require may target another package's non-exported submodule",
},
// 15
AuditRule {
name: "reach-around-require-field",
severity: Severity::Info,
message: "private-looking field access on require() may reach another package's internals",
},
];