mirror of
https://github.com/safedep/pmg.git
synced 2026-08-03 07:24:09 +02:00
feat/sandbox allow explicit dangerous pattern override (#239)
* feat(sandbox): allow opt-out of mandatory deny via explicit allow rules
Mandatory deny patterns (.env, .aws, .ssh, .gcloud, .kube, .gnupg,
.docker/config.json, .git/config) can now be opted out by listing the
exact literal post-expansion path in policy filesystem.allow_read /
allow_write, OR via --sandbox-allow read=... / write=... at runtime.
Both channels are treated at par.
Suppression is exact-match. Listing the CWD-absolute or HOME-absolute
form of a dangerous file additionally suppresses its **/<file> glob
sibling on the same direction so a single opt-out is sufficient.
Broad globs (${CWD}/**) and relative paths in user allow lists do not
suppress. The unnamed absolute form remains denied. .git/hooks is
unconditional and never suppressible (arbitrary code execution risk).
GetMandatoryDenyPatterns now returns split DenyRead / DenyWrite
slices and reports SuppressedRead / SuppressedWrite for audit. Both
translators emit per-direction deny rules and log.Warnf each
suppression. On Linux/bubblewrap, the tmpfs hide is restricted to the
intersection of DenyRead and DenyWrite; one-sided suppression falls
back to /dev/null (write) or the user's allow_read --ro-bind (read).
bwrap has no primitive that allows writes while denying reads, so
write-only opt-outs warn that the read-side mandatory deny is
unenforceable.
Updates docs/sandbox.md to document the opt-out, exact-match
semantics, and the Linux platform limitation. Updates pmg-e2e.yml to
create ./.env so the sandbox e2e test exercises the BLOCK case.
Closes #232
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: Code review fixes
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
b56a8e2a43
commit
d6755d3f44
@@ -218,6 +218,44 @@ test('BLOCK: .git/hooks in CWD is protected', () => {
|
||||
}
|
||||
});
|
||||
|
||||
// $CWD/.env should be protected from reads.
|
||||
// Two valid sandbox strategies:
|
||||
// macOS/seatbelt: read is denied outright (EPERM/EACCES)
|
||||
// Linux/bwrap: tmpfs masks the file with an empty placeholder
|
||||
// Both prevent the sandboxed process from exfiltrating real secrets.
|
||||
test('BLOCK: Read $CWD/.env', () => {
|
||||
const envPath = path.join(process.cwd(), '.env');
|
||||
|
||||
if (!fs.existsSync(envPath)) {
|
||||
console.log(' ⚠️ SKIP: $CWD/.env does not exist');
|
||||
return true;
|
||||
}
|
||||
|
||||
let contents;
|
||||
try {
|
||||
contents = fs.readFileSync(envPath, 'utf8');
|
||||
} catch (e) {
|
||||
if (e.code === 'EPERM' || e.code === 'EACCES') {
|
||||
console.log(' ✅ PASS: $CWD/.env read blocked (EPERM)');
|
||||
return true;
|
||||
}
|
||||
if (e.code === 'ENOENT') {
|
||||
console.log(' ✅ PASS: $CWD/.env hidden by tmpfs (ENOENT)');
|
||||
return true;
|
||||
}
|
||||
console.log(` ✅ PASS: $CWD/.env read blocked (${e.code})`);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (contents.length === 0) {
|
||||
console.log(' ✅ PASS: $CWD/.env masked by tmpfs (empty placeholder)');
|
||||
return true;
|
||||
}
|
||||
|
||||
console.log(' ❌ FAIL: Could read $CWD/.env contents');
|
||||
return false;
|
||||
});
|
||||
|
||||
// ============================================
|
||||
// TESTS THAT SHOULD BE ALLOWED
|
||||
// ============================================
|
||||
|
||||
Reference in New Issue
Block a user