@yail259/overnight 0.2.0 → 1.0.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/README.md +71 -225
- package/bin/overnight.js +2 -0
- package/dist/cli.js +103923 -23493
- package/package.json +27 -6
- package/bun.lock +0 -63
- package/src/cli.ts +0 -538
- package/src/notify.ts +0 -50
- package/src/report.ts +0 -115
- package/src/runner.ts +0 -660
- package/src/security.ts +0 -162
- package/src/types.ts +0 -81
- package/tsconfig.json +0 -15
package/src/security.ts
DELETED
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
import { appendFileSync } from "fs";
|
|
2
|
-
import { resolve, relative, isAbsolute } from "path";
|
|
3
|
-
import { type SecurityConfig, DEFAULT_DENY_PATTERNS } from "./types.js";
|
|
4
|
-
|
|
5
|
-
// Simple glob pattern matching (supports * and **)
|
|
6
|
-
function matchesPattern(filePath: string, pattern: string): boolean {
|
|
7
|
-
// Normalize path
|
|
8
|
-
const normalizedPath = filePath.replace(/\\/g, "/");
|
|
9
|
-
|
|
10
|
-
// Convert glob to regex
|
|
11
|
-
let regex = pattern
|
|
12
|
-
.replace(/\./g, "\\.") // Escape dots
|
|
13
|
-
.replace(/\*\*/g, "{{GLOBSTAR}}") // Placeholder for **
|
|
14
|
-
.replace(/\*/g, "[^/]*") // * matches anything except /
|
|
15
|
-
.replace(/{{GLOBSTAR}}/g, ".*"); // ** matches anything including /
|
|
16
|
-
|
|
17
|
-
// Match anywhere in path if pattern doesn't start with /
|
|
18
|
-
if (!pattern.startsWith("/")) {
|
|
19
|
-
regex = `(^|/)${regex}`;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
return new RegExp(regex + "$").test(normalizedPath);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function isPathWithinSandbox(filePath: string, sandboxDir: string): boolean {
|
|
26
|
-
const absolutePath = isAbsolute(filePath) ? filePath : resolve(process.cwd(), filePath);
|
|
27
|
-
const absoluteSandbox = isAbsolute(sandboxDir) ? sandboxDir : resolve(process.cwd(), sandboxDir);
|
|
28
|
-
|
|
29
|
-
const relativePath = relative(absoluteSandbox, absolutePath);
|
|
30
|
-
|
|
31
|
-
// If relative path starts with .. or is absolute, it's outside sandbox
|
|
32
|
-
return !relativePath.startsWith("..") && !isAbsolute(relativePath);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function isPathDenied(filePath: string, denyPatterns: string[]): string | null {
|
|
36
|
-
for (const pattern of denyPatterns) {
|
|
37
|
-
if (matchesPattern(filePath, pattern)) {
|
|
38
|
-
return pattern;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
return null;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export function createSecurityHooks(config: SecurityConfig) {
|
|
45
|
-
const sandboxDir = config.sandbox_dir;
|
|
46
|
-
const denyPatterns = config.deny_patterns ?? DEFAULT_DENY_PATTERNS;
|
|
47
|
-
const auditLog = config.audit_log;
|
|
48
|
-
|
|
49
|
-
// PreToolUse hook for path validation
|
|
50
|
-
const preToolUseHook = async (
|
|
51
|
-
input: Record<string, unknown>,
|
|
52
|
-
_toolUseId: string | null,
|
|
53
|
-
_context: { signal?: AbortSignal }
|
|
54
|
-
) => {
|
|
55
|
-
const hookEventName = input.hook_event_name as string;
|
|
56
|
-
if (hookEventName !== "PreToolUse") return {};
|
|
57
|
-
|
|
58
|
-
const toolName = input.tool_name as string;
|
|
59
|
-
const toolInput = input.tool_input as Record<string, unknown>;
|
|
60
|
-
|
|
61
|
-
// Extract file path based on tool
|
|
62
|
-
let filePath: string | undefined;
|
|
63
|
-
if (toolName === "Read" || toolName === "Write" || toolName === "Edit") {
|
|
64
|
-
filePath = toolInput.file_path as string;
|
|
65
|
-
} else if (toolName === "Glob" || toolName === "Grep") {
|
|
66
|
-
filePath = toolInput.path as string;
|
|
67
|
-
} else if (toolName === "Bash") {
|
|
68
|
-
// For Bash, we can't easily validate paths, but we can log
|
|
69
|
-
const command = toolInput.command as string;
|
|
70
|
-
if (auditLog) {
|
|
71
|
-
const timestamp = new Date().toISOString();
|
|
72
|
-
appendFileSync(auditLog, `${timestamp} [BASH] ${command}\n`);
|
|
73
|
-
}
|
|
74
|
-
return {};
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (!filePath) return {};
|
|
78
|
-
|
|
79
|
-
// Check sandbox
|
|
80
|
-
if (sandboxDir && !isPathWithinSandbox(filePath, sandboxDir)) {
|
|
81
|
-
return {
|
|
82
|
-
hookSpecificOutput: {
|
|
83
|
-
hookEventName,
|
|
84
|
-
permissionDecision: "deny",
|
|
85
|
-
permissionDecisionReason: `Path "${filePath}" is outside sandbox directory "${sandboxDir}"`,
|
|
86
|
-
},
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Check deny patterns
|
|
91
|
-
const matchedPattern = isPathDenied(filePath, denyPatterns);
|
|
92
|
-
if (matchedPattern) {
|
|
93
|
-
return {
|
|
94
|
-
hookSpecificOutput: {
|
|
95
|
-
hookEventName,
|
|
96
|
-
permissionDecision: "deny",
|
|
97
|
-
permissionDecisionReason: `Path "${filePath}" matches deny pattern "${matchedPattern}"`,
|
|
98
|
-
},
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return {};
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
// PostToolUse hook for audit logging
|
|
106
|
-
const postToolUseHook = async (
|
|
107
|
-
input: Record<string, unknown>,
|
|
108
|
-
_toolUseId: string | null,
|
|
109
|
-
_context: { signal?: AbortSignal }
|
|
110
|
-
) => {
|
|
111
|
-
if (!auditLog) return {};
|
|
112
|
-
|
|
113
|
-
const hookEventName = input.hook_event_name as string;
|
|
114
|
-
if (hookEventName !== "PostToolUse") return {};
|
|
115
|
-
|
|
116
|
-
const toolName = input.tool_name as string;
|
|
117
|
-
const toolInput = input.tool_input as Record<string, unknown>;
|
|
118
|
-
const timestamp = new Date().toISOString();
|
|
119
|
-
|
|
120
|
-
let logEntry = `${timestamp} [${toolName}]`;
|
|
121
|
-
|
|
122
|
-
if (toolName === "Read" || toolName === "Write" || toolName === "Edit") {
|
|
123
|
-
logEntry += ` ${toolInput.file_path}`;
|
|
124
|
-
} else if (toolName === "Glob") {
|
|
125
|
-
logEntry += ` pattern=${toolInput.pattern} path=${toolInput.path ?? "."}`;
|
|
126
|
-
} else if (toolName === "Grep") {
|
|
127
|
-
logEntry += ` pattern=${toolInput.pattern}`;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
appendFileSync(auditLog, logEntry + "\n");
|
|
131
|
-
return {};
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
return {
|
|
135
|
-
PreToolUse: [
|
|
136
|
-
{ matcher: "Read|Write|Edit|Glob|Grep|Bash", hooks: [preToolUseHook] },
|
|
137
|
-
],
|
|
138
|
-
PostToolUse: [
|
|
139
|
-
{ matcher: "Read|Write|Edit|Glob|Grep|Bash", hooks: [postToolUseHook] },
|
|
140
|
-
],
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
export function validateSecurityConfig(config: SecurityConfig): void {
|
|
145
|
-
if (config.sandbox_dir) {
|
|
146
|
-
const resolved = isAbsolute(config.sandbox_dir)
|
|
147
|
-
? config.sandbox_dir
|
|
148
|
-
: resolve(process.cwd(), config.sandbox_dir);
|
|
149
|
-
console.log(` Sandbox: ${resolved}`);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
const denyPatterns = config.deny_patterns ?? DEFAULT_DENY_PATTERNS;
|
|
153
|
-
console.log(` Deny patterns: ${denyPatterns.length} patterns`);
|
|
154
|
-
|
|
155
|
-
if (config.max_turns) {
|
|
156
|
-
console.log(` Max turns: ${config.max_turns}`);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
if (config.audit_log) {
|
|
160
|
-
console.log(` Audit log: ${config.audit_log}`);
|
|
161
|
-
}
|
|
162
|
-
}
|
package/src/types.ts
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
export interface SecurityConfig {
|
|
2
|
-
sandbox_dir?: string; // All paths must be under this directory
|
|
3
|
-
deny_patterns?: string[]; // Block files matching these glob patterns
|
|
4
|
-
max_turns?: number; // Max agent iterations (prevents runaway)
|
|
5
|
-
audit_log?: string; // Path to audit log file
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export interface JobConfig {
|
|
9
|
-
id?: string; // Stable task identifier for dependency references
|
|
10
|
-
depends_on?: string[]; // IDs of tasks that must complete before this one
|
|
11
|
-
prompt: string;
|
|
12
|
-
working_dir?: string;
|
|
13
|
-
timeout_seconds?: number;
|
|
14
|
-
stall_timeout_seconds?: number;
|
|
15
|
-
verify?: boolean;
|
|
16
|
-
verify_prompt?: string;
|
|
17
|
-
allowed_tools?: string[];
|
|
18
|
-
retry_count?: number;
|
|
19
|
-
retry_delay?: number;
|
|
20
|
-
security?: SecurityConfig;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export interface JobResult {
|
|
24
|
-
task: string;
|
|
25
|
-
status: "success" | "failed" | "timeout" | "stalled" | "verification_failed";
|
|
26
|
-
result?: string;
|
|
27
|
-
error?: string;
|
|
28
|
-
duration_seconds: number;
|
|
29
|
-
verified: boolean;
|
|
30
|
-
retries: number;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export interface InProgressTask {
|
|
34
|
-
hash: string;
|
|
35
|
-
prompt: string;
|
|
36
|
-
sessionId?: string; // SDK session ID for resumption
|
|
37
|
-
startedAt: string;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export interface RunState {
|
|
41
|
-
completed: Record<string, JobResult>; // keyed by task hash
|
|
42
|
-
inProgress?: InProgressTask; // currently running task
|
|
43
|
-
timestamp: string;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export interface TasksFile {
|
|
47
|
-
defaults?: {
|
|
48
|
-
timeout_seconds?: number;
|
|
49
|
-
stall_timeout_seconds?: number;
|
|
50
|
-
verify?: boolean;
|
|
51
|
-
verify_prompt?: string;
|
|
52
|
-
allowed_tools?: string[];
|
|
53
|
-
security?: SecurityConfig;
|
|
54
|
-
};
|
|
55
|
-
tasks: (string | JobConfig)[];
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export const DEFAULT_TOOLS = ["Read", "Edit", "Write", "Glob", "Grep"];
|
|
59
|
-
export const DEFAULT_TIMEOUT = 300;
|
|
60
|
-
export const DEFAULT_STALL_TIMEOUT = 120;
|
|
61
|
-
export const DEFAULT_RETRY_COUNT = 3;
|
|
62
|
-
export const DEFAULT_RETRY_DELAY = 5;
|
|
63
|
-
export const DEFAULT_VERIFY_PROMPT = "Review what you just implemented. Check for correctness, completeness, and compile errors. Fix any issues you find.";
|
|
64
|
-
export const DEFAULT_STATE_FILE = ".overnight-state.json";
|
|
65
|
-
export const DEFAULT_NTFY_TOPIC = "overnight";
|
|
66
|
-
export const DEFAULT_MAX_TURNS = 100;
|
|
67
|
-
export const DEFAULT_DENY_PATTERNS = [
|
|
68
|
-
"**/.env",
|
|
69
|
-
"**/.env.*",
|
|
70
|
-
"**/.git/config",
|
|
71
|
-
"**/credentials*",
|
|
72
|
-
"**/*.key",
|
|
73
|
-
"**/*.pem",
|
|
74
|
-
"**/*.p12",
|
|
75
|
-
"**/id_rsa*",
|
|
76
|
-
"**/id_ed25519*",
|
|
77
|
-
"**/.ssh/*",
|
|
78
|
-
"**/.aws/*",
|
|
79
|
-
"**/.npmrc",
|
|
80
|
-
"**/.netrc",
|
|
81
|
-
];
|
package/tsconfig.json
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
"module": "ESNext",
|
|
5
|
-
"moduleResolution": "bundler",
|
|
6
|
-
"esModuleInterop": true,
|
|
7
|
-
"strict": true,
|
|
8
|
-
"skipLibCheck": true,
|
|
9
|
-
"outDir": "dist",
|
|
10
|
-
"rootDir": "src",
|
|
11
|
-
"declaration": true,
|
|
12
|
-
"types": ["node"]
|
|
13
|
-
},
|
|
14
|
-
"include": ["src/**/*"]
|
|
15
|
-
}
|