arkaos 2.63.0 → 2.64.0
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/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.
|
|
1
|
+
2.64.0
|
|
Binary file
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
// autoMode.hard_deny defaults for Claude Code (PR45 v2.64.0).
|
|
2
|
+
//
|
|
3
|
+
// Claude Code v2.1.131+ shipped `autoMode.hard_deny` — actions that block
|
|
4
|
+
// unconditionally in auto mode regardless of allow rules. Without this,
|
|
5
|
+
// auto mode is structurally unsafe (allowlist alone cannot express
|
|
6
|
+
// "never run this even if a broader allow matches"). PR45 ships a
|
|
7
|
+
// curated default deny list and merges it into ~/.claude/settings.json
|
|
8
|
+
// without clobbering operator-authored entries.
|
|
9
|
+
//
|
|
10
|
+
// Operator extensions live in ~/.arkaos/hard-deny.json — installer
|
|
11
|
+
// reads it on every run and merges into the Claude settings file.
|
|
12
|
+
//
|
|
13
|
+
// Behaviour:
|
|
14
|
+
// - No-op when runtime is not Claude Code
|
|
15
|
+
// - Idempotent: merges by string equality; duplicates are dropped
|
|
16
|
+
// - Atomic write via .tmp + rename
|
|
17
|
+
// - Preserves all other settings.json content untouched
|
|
18
|
+
// - Never raises — failures logged but don't break the installer
|
|
19
|
+
|
|
20
|
+
import {
|
|
21
|
+
existsSync, readFileSync, writeFileSync, renameSync, copyFileSync, mkdirSync,
|
|
22
|
+
} from "node:fs";
|
|
23
|
+
import { homedir } from "node:os";
|
|
24
|
+
import { join, dirname } from "node:path";
|
|
25
|
+
|
|
26
|
+
// Curated default deny list. Each entry follows Claude Code's
|
|
27
|
+
// permission-rule syntax: ToolName(pattern). The patterns are
|
|
28
|
+
// load-bearing — pick conservatively, since auto mode without these
|
|
29
|
+
// rules silently allows them when a broader allow matches.
|
|
30
|
+
export const DEFAULT_HARD_DENY_RULES = [
|
|
31
|
+
// Destructive git operations
|
|
32
|
+
"Bash(git push --force*)",
|
|
33
|
+
"Bash(git push -f*)",
|
|
34
|
+
"Bash(git reset --hard*)",
|
|
35
|
+
"Bash(git clean -fd*)",
|
|
36
|
+
"Bash(git branch -D*)",
|
|
37
|
+
|
|
38
|
+
// Filesystem destruction
|
|
39
|
+
"Bash(rm -rf /*)",
|
|
40
|
+
"Bash(rm -rf ~/*)",
|
|
41
|
+
"Bash(rm -rf ~)",
|
|
42
|
+
"Bash(rm -rf .*)",
|
|
43
|
+
|
|
44
|
+
// Secrets and credential paths
|
|
45
|
+
"Read(~/.ssh/*)",
|
|
46
|
+
"Read(~/.ssh/**)",
|
|
47
|
+
"Read(~/.aws/credentials)",
|
|
48
|
+
"Read(~/.aws/config)",
|
|
49
|
+
"Read(~/.gnupg/*)",
|
|
50
|
+
"Read(~/.gnupg/**)",
|
|
51
|
+
"Read(~/.npmrc)",
|
|
52
|
+
"Read(~/.docker/config.json)",
|
|
53
|
+
"Read(~/.config/gh/*)",
|
|
54
|
+
"Read(/etc/shadow)",
|
|
55
|
+
"Read(/etc/sudoers)",
|
|
56
|
+
|
|
57
|
+
// Writes to credential / config dirs
|
|
58
|
+
"Write(~/.ssh/*)",
|
|
59
|
+
"Write(~/.aws/credentials)",
|
|
60
|
+
"Write(~/.gnupg/*)",
|
|
61
|
+
"Write(~/.npmrc)",
|
|
62
|
+
|
|
63
|
+
// Process / privilege escalation
|
|
64
|
+
"Bash(sudo *)",
|
|
65
|
+
"Bash(su -*)",
|
|
66
|
+
"Bash(chmod 777*)",
|
|
67
|
+
"Bash(curl * | sh*)",
|
|
68
|
+
"Bash(curl * | bash*)",
|
|
69
|
+
"Bash(wget * | sh*)",
|
|
70
|
+
"Bash(wget * | bash*)",
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
// Operator extension file. Lines added here are merged into the
|
|
75
|
+
// Claude settings on every install/update.
|
|
76
|
+
const _USER_EXTENSION_FILE = "hard-deny.json";
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
export function seedAutoModeHardDeny({
|
|
80
|
+
runtime = "claude-code",
|
|
81
|
+
home = homedir(),
|
|
82
|
+
defaults = DEFAULT_HARD_DENY_RULES,
|
|
83
|
+
} = {}) {
|
|
84
|
+
if (runtime !== "claude-code") {
|
|
85
|
+
return { skipped: "runtime-not-claude-code", action: null, count: 0 };
|
|
86
|
+
}
|
|
87
|
+
const settingsPath = join(home, ".claude", "settings.json");
|
|
88
|
+
if (!existsSync(settingsPath)) {
|
|
89
|
+
return { skipped: "claude-settings-not-found", action: null, count: 0 };
|
|
90
|
+
}
|
|
91
|
+
const userExtensions = readUserExtensions(home);
|
|
92
|
+
const merged = mergeUnique(defaults, userExtensions);
|
|
93
|
+
return writeMergedSettings(settingsPath, merged);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
function readUserExtensions(home) {
|
|
98
|
+
const path = join(home, ".arkaos", _USER_EXTENSION_FILE);
|
|
99
|
+
if (!existsSync(path)) return [];
|
|
100
|
+
try {
|
|
101
|
+
const data = JSON.parse(readFileSync(path, "utf-8"));
|
|
102
|
+
const rules = Array.isArray(data.hard_deny) ? data.hard_deny : [];
|
|
103
|
+
return rules.filter((r) => typeof r === "string" && r.length > 0);
|
|
104
|
+
} catch {
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
function mergeUnique(a, b) {
|
|
111
|
+
const seen = new Set();
|
|
112
|
+
const merged = [];
|
|
113
|
+
for (const rule of [...a, ...b]) {
|
|
114
|
+
if (!seen.has(rule)) {
|
|
115
|
+
seen.add(rule);
|
|
116
|
+
merged.push(rule);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return merged;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
function writeMergedSettings(settingsPath, mergedRules) {
|
|
124
|
+
let settings;
|
|
125
|
+
try {
|
|
126
|
+
settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
|
127
|
+
} catch {
|
|
128
|
+
return { skipped: "settings-not-parseable", action: null, count: 0 };
|
|
129
|
+
}
|
|
130
|
+
if (typeof settings !== "object" || settings === null) {
|
|
131
|
+
return { skipped: "settings-not-object", action: null, count: 0 };
|
|
132
|
+
}
|
|
133
|
+
settings.autoMode = settings.autoMode && typeof settings.autoMode === "object"
|
|
134
|
+
? settings.autoMode
|
|
135
|
+
: {};
|
|
136
|
+
const existing = Array.isArray(settings.autoMode.hard_deny)
|
|
137
|
+
? settings.autoMode.hard_deny
|
|
138
|
+
: [];
|
|
139
|
+
// Preserve any operator-authored rules already in settings.json,
|
|
140
|
+
// then merge our defaults on top. Operator wins on duplicates
|
|
141
|
+
// because they appear first in mergeUnique.
|
|
142
|
+
const finalRules = mergeUnique(existing, mergedRules);
|
|
143
|
+
if (sameRules(existing, finalRules)) {
|
|
144
|
+
return { skipped: null, action: "noop", count: finalRules.length };
|
|
145
|
+
}
|
|
146
|
+
settings.autoMode.hard_deny = finalRules;
|
|
147
|
+
// Atomic write
|
|
148
|
+
const tmp = `${settingsPath}.tmp-${process.pid}`;
|
|
149
|
+
writeFileSync(tmp, JSON.stringify(settings, null, 2) + "\n");
|
|
150
|
+
renameSync(tmp, settingsPath);
|
|
151
|
+
return {
|
|
152
|
+
skipped: null,
|
|
153
|
+
action: existing.length === 0 ? "created" : "merged",
|
|
154
|
+
count: finalRules.length,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
function sameRules(a, b) {
|
|
160
|
+
if (a.length !== b.length) return false;
|
|
161
|
+
const setA = new Set(a);
|
|
162
|
+
return b.every((r) => setA.has(r));
|
|
163
|
+
}
|
package/installer/index.js
CHANGED
|
@@ -311,6 +311,25 @@ export async function install({ runtime, path, force, skipSystem, withOllama })
|
|
|
311
311
|
console.log(` Warning: could not scaffold user-data (${err.message})`);
|
|
312
312
|
}
|
|
313
313
|
|
|
314
|
+
// PR45 v2.64.0 — seed autoMode.hard_deny defaults into
|
|
315
|
+
// ~/.claude/settings.json so auto mode blocks destructive actions
|
|
316
|
+
// (git push --force, ~/.ssh reads, rm -rf, sudo, etc.) regardless
|
|
317
|
+
// of allow rules. Preserves operator-authored entries; merges
|
|
318
|
+
// operator extensions from ~/.arkaos/hard-deny.json.
|
|
319
|
+
try {
|
|
320
|
+
const { seedAutoModeHardDeny } = await import("./hard-deny.js");
|
|
321
|
+
const denyResult = seedAutoModeHardDeny({ runtime });
|
|
322
|
+
if (!denyResult.skipped) {
|
|
323
|
+
if (denyResult.action === "created") {
|
|
324
|
+
console.log(` autoMode.hard_deny created (${denyResult.count} rules).`);
|
|
325
|
+
} else if (denyResult.action === "merged") {
|
|
326
|
+
console.log(` autoMode.hard_deny merged (${denyResult.count} rules, operator entries preserved).`);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
} catch (err) {
|
|
330
|
+
console.log(` Warning: could not seed autoMode.hard_deny (${err.message})`);
|
|
331
|
+
}
|
|
332
|
+
|
|
314
333
|
// PR43 v2.62.0 — auto-install default Claude Code plugins. Only runs
|
|
315
334
|
// when runtime is Claude Code AND the `claude` CLI is available.
|
|
316
335
|
// Idempotent: skips plugins already in installed_plugins.json.
|
package/package.json
CHANGED