defense-mcp-server 0.7.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +37 -0
- package/README.md +6 -3
- package/build/core/executor.d.ts.map +1 -1
- package/build/core/executor.js +32 -11
- package/build/core/installer.d.ts +4 -1
- package/build/core/installer.d.ts.map +1 -1
- package/build/core/installer.js +33 -9
- package/build/core/pam-utils.d.ts +233 -0
- package/build/core/pam-utils.d.ts.map +1 -0
- package/build/core/pam-utils.js +626 -0
- package/build/core/privilege-manager.d.ts.map +1 -1
- package/build/core/privilege-manager.js +4 -0
- package/build/core/safeguards.d.ts.map +1 -1
- package/build/core/safeguards.js +16 -1
- package/build/tools/access-control.d.ts.map +1 -1
- package/build/tools/access-control.js +126 -100
- package/build/tools/drift-detection.d.ts +2 -1
- package/build/tools/drift-detection.d.ts.map +1 -1
- package/build/tools/drift-detection.js +18 -2
- package/build/tools/reporting.d.ts +2 -1
- package/build/tools/reporting.d.ts.map +1 -1
- package/build/tools/reporting.js +18 -2
- package/build/tools/siem-integration.d.ts +3 -1
- package/build/tools/siem-integration.d.ts.map +1 -1
- package/build/tools/siem-integration.js +19 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,43 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
+
## [0.7.1] — 2026-03-14
|
|
10
|
+
|
|
11
|
+
### v0.7.1 — Critical PAM Hardening Fix
|
|
12
|
+
|
|
13
|
+
#### Security Fix
|
|
14
|
+
- **CRITICAL**: Fixed `pam_configure` action corrupting `/etc/pam.d/common-auth` — sed commands stripped whitespace separators between PAM fields, breaking ALL authentication system-wide (required GRUB recovery to fix)
|
|
15
|
+
- **CRITICAL**: Fixed `[success=N]` jump count not being updated after faillock rule insertion — would cause authentication denial on Debian/Ubuntu systems
|
|
16
|
+
|
|
17
|
+
#### New Module: `src/core/pam-utils.ts`
|
|
18
|
+
- Safe in-memory PAM config parser/serializer replacing fragile sed-based manipulation
|
|
19
|
+
- `parsePamConfig()` — Lossless parser handling comments, blanks, `@include`, bracket-style controls
|
|
20
|
+
- `serializePamConfig()` — Serializer with proper formatting (matching `pam-auth-update` canonical format)
|
|
21
|
+
- `validatePamConfig()` — Triple validation: field formatting, module existence, and `[success=N]` jump count correctness
|
|
22
|
+
- `adjustJumpCounts()` — Automatically updates bracket-control jump counts when rules are inserted/removed
|
|
23
|
+
- Manipulation helpers: `removeModuleRules()`, `insertBeforeModule()`, `insertAfterModule()` with pamType filter
|
|
24
|
+
- Sudo-aware I/O: `readPamFile()`, `writePamFile()` (atomic via `sudo install`), `backupPamFile()`, `restorePamFile()`
|
|
25
|
+
|
|
26
|
+
#### Safety Layers (Defense-in-Depth)
|
|
27
|
+
- Mandatory backup before any PAM file modification
|
|
28
|
+
- In-memory validation before writing (catches corrupted fields, missing pam_unix.so, wrong jump counts)
|
|
29
|
+
- Atomic file write using `sudo install -m 644 -o root -g root` (no partial state)
|
|
30
|
+
- Post-write re-read validation
|
|
31
|
+
- Auto-rollback on ANY failure (restores from backup automatically)
|
|
32
|
+
|
|
33
|
+
#### Security Review Remediations
|
|
34
|
+
- Fixed partial write state on chmod/chown failure (atomic `sudo install`)
|
|
35
|
+
- Fixed temp file symlink race (secure `mkdtempSync`)
|
|
36
|
+
- Fixed insert helpers matching by module only (added pamType filter)
|
|
37
|
+
- Fixed `backupPamFile` mutating BackupEntry internal state
|
|
38
|
+
- Fixed `restorePamFile` leaking PAM content to stdout via `tee`
|
|
39
|
+
- Added PAM modification warning to SafeguardRegistry
|
|
40
|
+
|
|
41
|
+
#### Testing
|
|
42
|
+
- 63 new tests in `tests/core/pam-utils.test.ts` covering parser, serializer, validator, jump count adjustment, manipulation helpers, and full faillock integration flow
|
|
43
|
+
- 7 new tests in `tests/tools/access-control.test.ts` for pam_configure regression testing
|
|
44
|
+
- Critical regression test: verifies concatenated PAM fields (the original lockout bug) can never be produced
|
|
45
|
+
|
|
9
46
|
## [0.7.0] — 2026-03-12
|
|
10
47
|
|
|
11
48
|
### v0.7.0 — Tool Consolidation & Sudo Hardening Overhaul
|
package/README.md
CHANGED
|
@@ -2,11 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
A Model Context Protocol (MCP) server that gives AI assistants access to **94 defensive security tools** on Linux. Connect it to Claude Desktop, Cursor, or any MCP-compatible client to harden systems, manage firewalls, scan for vulnerabilities, and enforce compliance — all through natural language conversation.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Why I Made This
|
|
6
|
+
Basically I'm a total noob when it comes to really serious system hardening so I thought I'd test the latest LLM models and see how far I could get. Turns out they're pretty helpful! I got tired of hardening my new systems by hand every time I spun up a new one so I made this MCP server to make it pretty easy. I jam packed as many security tools as I could into this thing so be prepared to burn tokens using it. Hopefully it helps you about half as much as its helped me.
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
## So What It Does
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
This server exposes Linux security tools as MCP tools that an AI assistant can invoke on your behalf. Instead of memorizing command syntax for dozens of security utilities, you describe what you want in plain English and the assistant calls the right tool with the right parameters. Sounds pretty good right!
|
|
11
|
+
|
|
12
|
+
Here are the tools:
|
|
10
13
|
|
|
11
14
|
| Module | What It Does |
|
|
12
15
|
|--------|-------------|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../../src/core/executor.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../../src/core/executor.ts"],"names":[],"mappings":"AAkFA;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,oCAAoC;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,uCAAuC;IACvC,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,kDAAkD;IAClD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,wCAAwC;IACxC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,uCAAuC;IACvC,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,iFAAiF;IACjF,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,0CAA0C;IAC1C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mCAAmC;IACnC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iEAAiE;IACjE,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,8BAA8B;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,6BAA6B;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,yCAAyC;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,oDAAoD;IACpD,QAAQ,EAAE,OAAO,CAAC;IAClB,0CAA0C;IAC1C,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,gBAAgB,EAAE,OAAO,CAAC;CAC3B;AA0FD;;;;;;;;;GASG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,aAAa,CAAC,CAmNxB"}
|
package/build/core/executor.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { join as pathJoin } from "node:path";
|
|
3
5
|
import { getConfig, getToolTimeout } from "./config.js";
|
|
4
6
|
import { SudoSession } from "./sudo-session.js";
|
|
5
7
|
import { SudoGuard } from "./sudo-guard.js";
|
|
@@ -8,14 +10,25 @@ import { resolveCommand, resolveSudoCommand } from "./command-allowlist.js";
|
|
|
8
10
|
/**
|
|
9
11
|
* Ordered list of known graphical sudo/SSH askpass helpers.
|
|
10
12
|
* The first one found on the system will be used as the SUDO_ASKPASS program.
|
|
13
|
+
*
|
|
14
|
+
* NOTE: The user-local MCP askpass helper is checked first (highest priority)
|
|
15
|
+
* since it is installed as part of the native deployment setup and is the
|
|
16
|
+
* preferred path for desktop environments running the server natively.
|
|
17
|
+
* It is placed at ~/.local/bin/mcp-askpass with mode 0700 (owner-only).
|
|
11
18
|
*/
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
function getAskpassCandidates() {
|
|
20
|
+
return [
|
|
21
|
+
// User-local MCP askpass wrapper (zenity/pinentry, installed by setup)
|
|
22
|
+
pathJoin(homedir(), ".local", "bin", "mcp-askpass"),
|
|
23
|
+
// System-level alternatives
|
|
24
|
+
"/usr/local/bin/mcp-askpass", // System-wide MCP askpass (if installed by admin)
|
|
25
|
+
"/usr/bin/ssh-askpass", // Generic (often symlinked to ksshaskpass/gnome)
|
|
26
|
+
"/usr/bin/ksshaskpass", // KDE Plasma
|
|
27
|
+
"/usr/lib/ssh/x11-ssh-askpass", // X11 classic
|
|
28
|
+
"/usr/libexec/openssh/gnome-ssh-askpass", // GNOME
|
|
29
|
+
"/usr/bin/lxqt-sudo", // LXQt
|
|
30
|
+
];
|
|
31
|
+
}
|
|
19
32
|
/** Cached result of askpass detection (null = not yet checked, undefined = not found) */
|
|
20
33
|
let cachedAskpass = null;
|
|
21
34
|
/**
|
|
@@ -43,14 +56,14 @@ function findAskpassHelper() {
|
|
|
43
56
|
cachedAskpass = undefined;
|
|
44
57
|
return undefined;
|
|
45
58
|
}
|
|
46
|
-
for (const candidate of
|
|
59
|
+
for (const candidate of getAskpassCandidates()) {
|
|
47
60
|
if (existsSync(candidate)) {
|
|
48
61
|
// SECURITY (CORE-016): Validate each candidate's ownership, permissions, and integrity
|
|
49
62
|
const validation = SudoGuard.validateAskpassPath(candidate);
|
|
50
63
|
if (validation.valid) {
|
|
51
64
|
cachedAskpass = candidate;
|
|
52
65
|
console.error(`[executor] Found verified askpass helper: ${candidate}`);
|
|
53
|
-
return
|
|
66
|
+
return candidate;
|
|
54
67
|
}
|
|
55
68
|
console.error(`[executor] Skipping askpass candidate ${candidate}: ${validation.reason}`);
|
|
56
69
|
}
|
|
@@ -77,8 +90,16 @@ function findAskpassHelper() {
|
|
|
77
90
|
* `command: "sudo", args: ["iptables", ...]`.
|
|
78
91
|
*/
|
|
79
92
|
function prepareSudoOptions(options) {
|
|
80
|
-
//
|
|
81
|
-
|
|
93
|
+
// FIX (ordering bug): By the time this function is called, the allowlist block
|
|
94
|
+
// in executeCommand() has already resolved "sudo" → "/usr/bin/sudo" (absolute
|
|
95
|
+
// path). The original guard `if (options.command !== "sudo")` would therefore
|
|
96
|
+
// always return early, permanently bypassing credential injection.
|
|
97
|
+
//
|
|
98
|
+
// Fix: accept both the bare name "sudo" (pre-allowlist, from tests or direct
|
|
99
|
+
// callers) and any absolute path ending in "/sudo" (post-allowlist resolution,
|
|
100
|
+
// the normal runtime path) so that injection works in both cases.
|
|
101
|
+
const isSudoCommand = options.command === "sudo" || options.command.endsWith("/sudo");
|
|
102
|
+
if (!isSudoCommand)
|
|
82
103
|
return options;
|
|
83
104
|
// Skip if the caller explicitly opted out (e.g. sudo-session.ts itself)
|
|
84
105
|
if (options.skipSudoInjection)
|
|
@@ -60,7 +60,10 @@ export interface InstallResult {
|
|
|
60
60
|
export declare const DEFENSIVE_TOOLS: ToolRequirement[];
|
|
61
61
|
/**
|
|
62
62
|
* Checks whether a tool binary is available on the system.
|
|
63
|
-
* Uses
|
|
63
|
+
* Uses the command allowlist (which already resolved paths via existsSync at
|
|
64
|
+
* startup) or falls back to probing standard binary directories with
|
|
65
|
+
* existsSync. This avoids shelling out to `which`, which is blocked by the
|
|
66
|
+
* command allowlist.
|
|
64
67
|
*/
|
|
65
68
|
export declare function checkTool(binary: string): Promise<{
|
|
66
69
|
installed: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"installer.d.ts","sourceRoot":"","sources":["../../src/core/installer.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"installer.d.ts","sourceRoot":"","sources":["../../src/core/installer.ts"],"names":[],"mappings":"AAWA;;GAEG;AACH,MAAM,MAAM,YAAY,GACpB,WAAW,GACX,UAAU,GACV,YAAY,GACZ,YAAY,GACZ,SAAS,GACT,QAAQ,GACR,gBAAgB,GAChB,YAAY,GACZ,WAAW,GACX,SAAS,GACT,WAAW,GACX,WAAW,GACX,YAAY,GACZ,SAAS,CAAC;AAEd;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,+BAA+B;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,4CAA4C;IAC5C,MAAM,EAAE,MAAM,CAAC;IACf,qCAAqC;IACrC,QAAQ,EAAE,YAAY,CAAC;IACvB,2BAA2B;IAC3B,QAAQ,EAAE,YAAY,CAAC;IACvB,iDAAiD;IACjD,QAAQ,EAAE,OAAO,CAAC;IAClB,iDAAiD;IACjD,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,4BAA4B;IAC5B,IAAI,EAAE,eAAe,CAAC;IACtB,oCAAoC;IACpC,SAAS,EAAE,OAAO,CAAC;IACnB,6CAA6C;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,oCAAoC;IACpC,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,4BAA4B;IAC5B,IAAI,EAAE,eAAe,CAAC;IACtB,qCAAqC;IACrC,OAAO,EAAE,OAAO,CAAC;IACjB,2BAA2B;IAC3B,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,eAAO,MAAM,eAAe,EAAE,eAAe,EAk6B5C,CAAC;AAYF;;;;;;GAMG;AACH,wBAAsB,SAAS,CAC7B,MAAM,EAAE,MAAM,GACb,OAAO,CAAC;IAAE,SAAS,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CA8ClE;AAED;;;;;GAKG;AACH,wBAAsB,aAAa,CACjC,QAAQ,CAAC,EAAE,YAAY,GACtB,OAAO,CAAC,eAAe,EAAE,CAAC,CAkB5B;AAED;;;;;GAKG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,eAAe,GACpB,OAAO,CAAC,aAAa,CAAC,CAwDxB;AAED;;;;;;GAMG;AACH,wBAAsB,cAAc,CAClC,QAAQ,CAAC,EAAE,YAAY,EACvB,MAAM,CAAC,EAAE,OAAO,GACf,OAAO,CAAC,aAAa,EAAE,CAAC,CAgC1B"}
|
package/build/core/installer.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
1
2
|
import { executeCommand } from "./executor.js";
|
|
2
3
|
import { detectDistro, getInstallCommand, getUpdateCommand, } from "./distro.js";
|
|
3
4
|
import { getConfig } from "./config.js";
|
|
5
|
+
import { resolveCommand } from "./command-allowlist.js";
|
|
4
6
|
/**
|
|
5
7
|
* Comprehensive list of defensive security tools across categories.
|
|
6
8
|
*/
|
|
@@ -922,21 +924,43 @@ export const DEFENSIVE_TOOLS = [
|
|
|
922
924
|
required: false,
|
|
923
925
|
},
|
|
924
926
|
];
|
|
927
|
+
/** Standard binary directories to probe when a binary is not in the command allowlist. */
|
|
928
|
+
const STANDARD_BINARY_DIRS = [
|
|
929
|
+
"/usr/bin",
|
|
930
|
+
"/usr/sbin",
|
|
931
|
+
"/bin",
|
|
932
|
+
"/sbin",
|
|
933
|
+
"/usr/local/bin",
|
|
934
|
+
"/usr/local/sbin",
|
|
935
|
+
];
|
|
925
936
|
/**
|
|
926
937
|
* Checks whether a tool binary is available on the system.
|
|
927
|
-
* Uses
|
|
938
|
+
* Uses the command allowlist (which already resolved paths via existsSync at
|
|
939
|
+
* startup) or falls back to probing standard binary directories with
|
|
940
|
+
* existsSync. This avoids shelling out to `which`, which is blocked by the
|
|
941
|
+
* command allowlist.
|
|
928
942
|
*/
|
|
929
943
|
export async function checkTool(binary) {
|
|
930
|
-
//
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
}
|
|
936
|
-
|
|
944
|
+
// Attempt to resolve via the command allowlist first (O(1) lookup, already
|
|
945
|
+
// verified with existsSync at startup).
|
|
946
|
+
let binaryPath;
|
|
947
|
+
try {
|
|
948
|
+
binaryPath = resolveCommand(binary);
|
|
949
|
+
}
|
|
950
|
+
catch {
|
|
951
|
+
// Binary not in the allowlist or not yet resolved; fall back to probing
|
|
952
|
+
// standard directories with existsSync.
|
|
953
|
+
for (const dir of STANDARD_BINARY_DIRS) {
|
|
954
|
+
const candidate = `${dir}/${binary}`;
|
|
955
|
+
if (existsSync(candidate)) {
|
|
956
|
+
binaryPath = candidate;
|
|
957
|
+
break;
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
if (!binaryPath) {
|
|
937
962
|
return { installed: false };
|
|
938
963
|
}
|
|
939
|
-
const binaryPath = whichResult.stdout.trim();
|
|
940
964
|
// Try to get version
|
|
941
965
|
let version;
|
|
942
966
|
const versionResult = await executeCommand({
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PAM configuration parser, serializer, validator, and file I/O manager.
|
|
3
|
+
*
|
|
4
|
+
* Replaces fragile sed-based PAM manipulation with safe in-memory operations:
|
|
5
|
+
* 1. Parse PAM config into structured records
|
|
6
|
+
* 2. Manipulate records (insert, remove, reorder)
|
|
7
|
+
* 3. Serialize back with correct formatting
|
|
8
|
+
* 4. Validate before writing
|
|
9
|
+
* 5. Write atomically with mandatory backup and auto-rollback
|
|
10
|
+
*
|
|
11
|
+
* @see docs/PAM-HARDENING-FIX.md for architecture details
|
|
12
|
+
*/
|
|
13
|
+
import { type BackupEntry } from "./backup-manager.js";
|
|
14
|
+
/** A PAM rule line: type control module [args...] */
|
|
15
|
+
export interface PamRule {
|
|
16
|
+
kind: "rule";
|
|
17
|
+
/** PAM type: auth, account, password, session (optionally prefixed with -) */
|
|
18
|
+
pamType: string;
|
|
19
|
+
/** Control flag: required, requisite, sufficient, optional, or [value=action ...] */
|
|
20
|
+
control: string;
|
|
21
|
+
/** Module path/name: pam_unix.so, pam_faillock.so, etc. */
|
|
22
|
+
module: string;
|
|
23
|
+
/** Module arguments: nullok, silent, deny=5, etc. */
|
|
24
|
+
args: string[];
|
|
25
|
+
/** Original raw text (preserved for round-trip fidelity). */
|
|
26
|
+
rawLine: string;
|
|
27
|
+
}
|
|
28
|
+
/** A comment line (starts with #). */
|
|
29
|
+
export interface PamComment {
|
|
30
|
+
kind: "comment";
|
|
31
|
+
text: string;
|
|
32
|
+
}
|
|
33
|
+
/** A blank/empty line. */
|
|
34
|
+
export interface PamBlank {
|
|
35
|
+
kind: "blank";
|
|
36
|
+
}
|
|
37
|
+
/** An @include directive. */
|
|
38
|
+
export interface PamInclude {
|
|
39
|
+
kind: "include";
|
|
40
|
+
target: string;
|
|
41
|
+
rawLine: string;
|
|
42
|
+
}
|
|
43
|
+
/** Union of all PAM line types. */
|
|
44
|
+
export type PamLine = PamRule | PamComment | PamBlank | PamInclude;
|
|
45
|
+
/** Thrown when PAM config validation fails. */
|
|
46
|
+
export declare class PamValidationError extends Error {
|
|
47
|
+
readonly errors: string[];
|
|
48
|
+
readonly filePath?: string | undefined;
|
|
49
|
+
constructor(errors: string[], filePath?: string | undefined);
|
|
50
|
+
}
|
|
51
|
+
/** Thrown when PAM file write fails or post-write validation fails. */
|
|
52
|
+
export declare class PamWriteError extends Error {
|
|
53
|
+
readonly filePath: string;
|
|
54
|
+
readonly backupId?: string | undefined;
|
|
55
|
+
constructor(message: string, filePath: string, backupId?: string | undefined);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Parse PAM config file content into structured records.
|
|
59
|
+
*
|
|
60
|
+
* Handles:
|
|
61
|
+
* - Standard rules: auth required pam_unix.so nullok
|
|
62
|
+
* - Complex controls: auth [success=1 default=ignore] pam_unix.so
|
|
63
|
+
* - Comments: # This is a comment
|
|
64
|
+
* - Blank lines: (preserved for formatting fidelity)
|
|
65
|
+
* - Include directives: @include common-auth
|
|
66
|
+
*
|
|
67
|
+
* **Critical**: The parser is **lossless**. Every line in the input appears
|
|
68
|
+
* in the output array. Unknown/unparseable lines are preserved as comments
|
|
69
|
+
* to prevent silent data loss.
|
|
70
|
+
*
|
|
71
|
+
* @param content - Raw PAM config file text
|
|
72
|
+
* @returns Array of PamLine records in file order
|
|
73
|
+
*/
|
|
74
|
+
export declare function parsePamConfig(content: string): PamLine[];
|
|
75
|
+
/**
|
|
76
|
+
* Serialize structured PAM records back to file content.
|
|
77
|
+
*
|
|
78
|
+
* For PamRule records, generates lines with consistent formatting:
|
|
79
|
+
* - Fields separated by 4-space padding
|
|
80
|
+
* - Module args separated by single spaces
|
|
81
|
+
*
|
|
82
|
+
* For PamComment, PamBlank, and PamInclude records, the original
|
|
83
|
+
* raw text is emitted unchanged (round-trip preservation).
|
|
84
|
+
*
|
|
85
|
+
* @param lines - Array of PamLine records
|
|
86
|
+
* @returns PAM config file content string (with trailing newline)
|
|
87
|
+
*/
|
|
88
|
+
export declare function serializePamConfig(lines: PamLine[]): string;
|
|
89
|
+
/**
|
|
90
|
+
* Validate PAM config for syntactic correctness.
|
|
91
|
+
*
|
|
92
|
+
* Checks:
|
|
93
|
+
* 1. Every PamRule has a valid pamType, non-empty control, and module ending in .so
|
|
94
|
+
* 2. At least one pam_unix.so rule exists (sanity check — PAM needs it)
|
|
95
|
+
* 3. No lines have concatenated fields (the bug that caused the lockout)
|
|
96
|
+
*
|
|
97
|
+
* Does NOT check:
|
|
98
|
+
* - Whether .so files exist on disk
|
|
99
|
+
* - Semantic correctness of control flags
|
|
100
|
+
*
|
|
101
|
+
* @param lines - Parsed PamLine array
|
|
102
|
+
* @returns Validation result with error details
|
|
103
|
+
*/
|
|
104
|
+
export declare function validatePamConfig(lines: PamLine[]): {
|
|
105
|
+
valid: boolean;
|
|
106
|
+
errors: string[];
|
|
107
|
+
};
|
|
108
|
+
/**
|
|
109
|
+
* Validate raw PAM config content string.
|
|
110
|
+
*
|
|
111
|
+
* Convenience wrapper that parses then validates.
|
|
112
|
+
*
|
|
113
|
+
* @param content - Raw PAM config file text
|
|
114
|
+
* @returns Validation result
|
|
115
|
+
*/
|
|
116
|
+
export declare function validatePamConfigContent(content: string): {
|
|
117
|
+
valid: boolean;
|
|
118
|
+
errors: string[];
|
|
119
|
+
};
|
|
120
|
+
/**
|
|
121
|
+
* Create a new PamRule record.
|
|
122
|
+
*
|
|
123
|
+
* @param pamType - PAM type (auth, account, password, session)
|
|
124
|
+
* @param control - Control flag (required, requisite, [success=1 default=ignore], etc.)
|
|
125
|
+
* @param module - Module name (pam_faillock.so, pam_unix.so, etc.)
|
|
126
|
+
* @param args - Module arguments
|
|
127
|
+
* @returns New PamRule with generated rawLine
|
|
128
|
+
*/
|
|
129
|
+
export declare function createPamRule(pamType: string, control: string, module: string, args: string[]): PamRule;
|
|
130
|
+
/**
|
|
131
|
+
* Remove all rules referencing a specific module.
|
|
132
|
+
*
|
|
133
|
+
* @param lines - Current PamLine array
|
|
134
|
+
* @param moduleName - Module to remove (e.g., "pam_faillock.so")
|
|
135
|
+
* @returns New array with matching rules removed
|
|
136
|
+
*/
|
|
137
|
+
export declare function removeModuleRules(lines: PamLine[], moduleName: string): PamLine[];
|
|
138
|
+
/**
|
|
139
|
+
* Insert a new rule BEFORE the first rule matching targetModule.
|
|
140
|
+
* If targetModule is not found, appends at the end.
|
|
141
|
+
*
|
|
142
|
+
* @param lines - Current PamLine array
|
|
143
|
+
* @param targetModule - Module to insert before (e.g., "pam_unix.so")
|
|
144
|
+
* @param newRule - The rule to insert
|
|
145
|
+
* @param options - Optional filters: pamType restricts match to specific PAM type
|
|
146
|
+
* @returns New array with the rule inserted
|
|
147
|
+
*/
|
|
148
|
+
export declare function insertBeforeModule(lines: PamLine[], targetModule: string, newRule: PamRule, options?: {
|
|
149
|
+
pamType?: string;
|
|
150
|
+
}): PamLine[];
|
|
151
|
+
/**
|
|
152
|
+
* Insert a new rule AFTER the first rule matching targetModule.
|
|
153
|
+
* If targetModule is not found, appends at the end.
|
|
154
|
+
*
|
|
155
|
+
* @param lines - Current PamLine array
|
|
156
|
+
* @param targetModule - Module to insert after (e.g., "pam_unix.so")
|
|
157
|
+
* @param newRule - The rule to insert
|
|
158
|
+
* @param options - Optional filters: pamType restricts match to specific PAM type
|
|
159
|
+
* @returns New array with the rule inserted
|
|
160
|
+
*/
|
|
161
|
+
export declare function insertAfterModule(lines: PamLine[], targetModule: string, newRule: PamRule, options?: {
|
|
162
|
+
pamType?: string;
|
|
163
|
+
}): PamLine[];
|
|
164
|
+
/**
|
|
165
|
+
* Find all rules referencing a specific module.
|
|
166
|
+
*
|
|
167
|
+
* @param lines - PamLine array to search
|
|
168
|
+
* @param moduleName - Module to find (e.g., "pam_faillock.so")
|
|
169
|
+
* @returns Array of matching PamRule records
|
|
170
|
+
*/
|
|
171
|
+
export declare function findModuleRules(lines: PamLine[], moduleName: string): PamRule[];
|
|
172
|
+
/**
|
|
173
|
+
* After inserting rules, adjust [success=N] jump counts on any rule
|
|
174
|
+
* that uses bracket-style controls with a success=N pattern.
|
|
175
|
+
*
|
|
176
|
+
* For each rule with [success=N ...], count how many rules now exist
|
|
177
|
+
* between that rule and pam_deny.so (requisite), and update N so that
|
|
178
|
+
* success still jumps PAST pam_deny.so.
|
|
179
|
+
*
|
|
180
|
+
* @param lines - PamLine array (typically after insertions)
|
|
181
|
+
* @returns New array with corrected jump counts
|
|
182
|
+
*/
|
|
183
|
+
export declare function adjustJumpCounts(lines: PamLine[]): PamLine[];
|
|
184
|
+
/**
|
|
185
|
+
* Read a PAM config file via sudo.
|
|
186
|
+
*
|
|
187
|
+
* @param filePath - Absolute path (e.g., /etc/pam.d/common-auth)
|
|
188
|
+
* @returns File content string
|
|
189
|
+
* @throws If sudo cat fails
|
|
190
|
+
*/
|
|
191
|
+
export declare function readPamFile(filePath: string): Promise<string>;
|
|
192
|
+
/**
|
|
193
|
+
* Write a PAM config file via sudo, with mandatory pre-write validation.
|
|
194
|
+
*
|
|
195
|
+
* Steps:
|
|
196
|
+
* 1. Parse the content with parsePamConfig()
|
|
197
|
+
* 2. Validate with validatePamConfig() — if invalid, throw (never write bad content)
|
|
198
|
+
* 3. Write to a secure temp directory (mkdtempSync — eliminates symlink race)
|
|
199
|
+
* 4. Use `sudo install -m 644 -o root -g root` for atomic write (eliminates partial-write state)
|
|
200
|
+
* 5. Post-write verification
|
|
201
|
+
*
|
|
202
|
+
* @param filePath - Absolute path
|
|
203
|
+
* @param content - PAM config content to write
|
|
204
|
+
* @throws PamValidationError if pre-write validation fails
|
|
205
|
+
* @throws PamWriteError if write or permission setting fails
|
|
206
|
+
*/
|
|
207
|
+
export declare function writePamFile(filePath: string, content: string): Promise<void>;
|
|
208
|
+
/**
|
|
209
|
+
* Backup a PAM file using the project BackupManager.
|
|
210
|
+
*
|
|
211
|
+
* Since PAM files are root-owned, this:
|
|
212
|
+
* 1. Reads content via sudo cat
|
|
213
|
+
* 2. Writes to a secure temp directory (eliminates symlink race)
|
|
214
|
+
* 3. Uses BackupManager.backupSync() to create a tracked backup
|
|
215
|
+
* 4. Returns a new object (does NOT mutate BackupManager's internal entry)
|
|
216
|
+
* 5. Cleans up the temp file/directory
|
|
217
|
+
*
|
|
218
|
+
* @param filePath - PAM file to backup
|
|
219
|
+
* @returns BackupEntry for later restore (with corrected originalPath)
|
|
220
|
+
*/
|
|
221
|
+
export declare function backupPamFile(filePath: string): Promise<BackupEntry>;
|
|
222
|
+
/**
|
|
223
|
+
* Restore a PAM file from backup.
|
|
224
|
+
*
|
|
225
|
+
* 1. Reads backup content from BackupManager's directory
|
|
226
|
+
* 2. Validates the backup content (refuse to restore garbage)
|
|
227
|
+
* 3. Writes to a secure temp file, then uses `sudo install` (eliminates tee stdout leak)
|
|
228
|
+
*
|
|
229
|
+
* @param backupEntry - The BackupEntry from backupPamFile()
|
|
230
|
+
* @throws If backup file is missing, invalid, or restore fails
|
|
231
|
+
*/
|
|
232
|
+
export declare function restorePamFile(backupEntry: BackupEntry): Promise<void>;
|
|
233
|
+
//# sourceMappingURL=pam-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pam-utils.d.ts","sourceRoot":"","sources":["../../src/core/pam-utils.ts"],"names":[],"mappings":"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"}
|