@voratiq/sandbox-runtime 0.0.29-voratiq0
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 +10 -0
- package/LICENSE +201 -0
- package/NOTICE +12 -0
- package/README.md +17 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +158 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/sandbox/generate-seccomp-filter.d.ts +65 -0
- package/dist/sandbox/generate-seccomp-filter.d.ts.map +1 -0
- package/dist/sandbox/generate-seccomp-filter.js +185 -0
- package/dist/sandbox/generate-seccomp-filter.js.map +1 -0
- package/dist/sandbox/http-proxy.d.ts +14 -0
- package/dist/sandbox/http-proxy.d.ts.map +1 -0
- package/dist/sandbox/http-proxy.js +238 -0
- package/dist/sandbox/http-proxy.js.map +1 -0
- package/dist/sandbox/linux-sandbox-utils.d.ts +121 -0
- package/dist/sandbox/linux-sandbox-utils.d.ts.map +1 -0
- package/dist/sandbox/linux-sandbox-utils.js +723 -0
- package/dist/sandbox/linux-sandbox-utils.js.map +1 -0
- package/dist/sandbox/macos-sandbox-utils.d.ts +57 -0
- package/dist/sandbox/macos-sandbox-utils.d.ts.map +1 -0
- package/dist/sandbox/macos-sandbox-utils.js +611 -0
- package/dist/sandbox/macos-sandbox-utils.js.map +1 -0
- package/dist/sandbox/observability.d.ts +56 -0
- package/dist/sandbox/observability.d.ts.map +1 -0
- package/dist/sandbox/observability.js +140 -0
- package/dist/sandbox/observability.js.map +1 -0
- package/dist/sandbox/sandbox-config.d.ts +277 -0
- package/dist/sandbox/sandbox-config.d.ts.map +1 -0
- package/dist/sandbox/sandbox-config.js +166 -0
- package/dist/sandbox/sandbox-config.js.map +1 -0
- package/dist/sandbox/sandbox-manager.d.ts +50 -0
- package/dist/sandbox/sandbox-manager.d.ts.map +1 -0
- package/dist/sandbox/sandbox-manager.js +816 -0
- package/dist/sandbox/sandbox-manager.js.map +1 -0
- package/dist/sandbox/sandbox-schemas.d.ts +53 -0
- package/dist/sandbox/sandbox-schemas.d.ts.map +1 -0
- package/dist/sandbox/sandbox-schemas.js +3 -0
- package/dist/sandbox/sandbox-schemas.js.map +1 -0
- package/dist/sandbox/sandbox-utils.d.ts +83 -0
- package/dist/sandbox/sandbox-utils.d.ts.map +1 -0
- package/dist/sandbox/sandbox-utils.js +343 -0
- package/dist/sandbox/sandbox-utils.js.map +1 -0
- package/dist/sandbox/sandbox-violation-store.d.ts +19 -0
- package/dist/sandbox/sandbox-violation-store.d.ts.map +1 -0
- package/dist/sandbox/sandbox-violation-store.js +54 -0
- package/dist/sandbox/sandbox-violation-store.js.map +1 -0
- package/dist/sandbox/socks-proxy.d.ts +14 -0
- package/dist/sandbox/socks-proxy.d.ts.map +1 -0
- package/dist/sandbox/socks-proxy.js +109 -0
- package/dist/sandbox/socks-proxy.js.map +1 -0
- package/dist/utils/config-loader.d.ts +11 -0
- package/dist/utils/config-loader.d.ts.map +1 -0
- package/dist/utils/config-loader.js +60 -0
- package/dist/utils/config-loader.js.map +1 -0
- package/dist/utils/debug.d.ts +7 -0
- package/dist/utils/debug.d.ts.map +1 -0
- package/dist/utils/debug.js +25 -0
- package/dist/utils/debug.js.map +1 -0
- package/dist/utils/platform.d.ts +15 -0
- package/dist/utils/platform.d.ts.map +1 -0
- package/dist/utils/platform.js +49 -0
- package/dist/utils/platform.js.map +1 -0
- package/dist/utils/ripgrep.d.ts +20 -0
- package/dist/utils/ripgrep.d.ts.map +1 -0
- package/dist/utils/ripgrep.js +51 -0
- package/dist/utils/ripgrep.js.map +1 -0
- package/dist/vendor/seccomp/arm64/apply-seccomp +0 -0
- package/dist/vendor/seccomp/arm64/unix-block.bpf +0 -0
- package/dist/vendor/seccomp/x64/apply-seccomp +0 -0
- package/dist/vendor/seccomp/x64/unix-block.bpf +0 -0
- package/dist/vendor/seccomp-src/apply-seccomp.c +98 -0
- package/dist/vendor/seccomp-src/seccomp-unix-block.c +97 -0
- package/package.json +90 -0
- package/vendor/seccomp/arm64/apply-seccomp +0 -0
- package/vendor/seccomp/arm64/unix-block.bpf +0 -0
- package/vendor/seccomp/x64/apply-seccomp +0 -0
- package/vendor/seccomp/x64/unix-block.bpf +0 -0
- package/vendor/seccomp-src/apply-seccomp.c +98 -0
- package/vendor/seccomp-src/seccomp-unix-block.c +97 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Socks5Server } from '@pondwader/socks5-server';
|
|
2
|
+
import { type NetworkFilterResult } from './observability.js';
|
|
3
|
+
export interface SocksProxyServerOptions {
|
|
4
|
+
filter(port: number, host: string): Promise<boolean | NetworkFilterResult> | boolean | NetworkFilterResult;
|
|
5
|
+
}
|
|
6
|
+
export interface SocksProxyWrapper {
|
|
7
|
+
server: Socks5Server;
|
|
8
|
+
getPort(): number | undefined;
|
|
9
|
+
listen(port: number, hostname: string): Promise<number>;
|
|
10
|
+
close(): Promise<void>;
|
|
11
|
+
unref(): void;
|
|
12
|
+
}
|
|
13
|
+
export declare function createSocksProxyServer(options: SocksProxyServerOptions): SocksProxyWrapper;
|
|
14
|
+
//# sourceMappingURL=socks-proxy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"socks-proxy.d.ts","sourceRoot":"","sources":["../../src/sandbox/socks-proxy.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAG5D,OAAO,EAEL,KAAK,mBAAmB,EACzB,MAAM,oBAAoB,CAAA;AAE3B,MAAM,WAAW,uBAAuB;IACtC,MAAM,CACJ,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,OAAO,GAAG,mBAAmB,CAAC,GAAG,OAAO,GAAG,mBAAmB,CAAA;CAC1E;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,YAAY,CAAA;IACpB,OAAO,IAAI,MAAM,GAAG,SAAS,CAAA;IAC7B,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IACvD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACtB,KAAK,IAAI,IAAI,CAAA;CACd;AAED,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,uBAAuB,GAC/B,iBAAiB,CAwHnB"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { createServer } from '@pondwader/socks5-server';
|
|
2
|
+
import { logForDebugging } from '../utils/debug.js';
|
|
3
|
+
import { emitNetworkDecisionEvent, } from './observability.js';
|
|
4
|
+
export function createSocksProxyServer(options) {
|
|
5
|
+
const socksServer = createServer();
|
|
6
|
+
const normalizeFilterResult = (result) => {
|
|
7
|
+
if (typeof result === 'boolean') {
|
|
8
|
+
return { allowed: result, reason: 'no-match' };
|
|
9
|
+
}
|
|
10
|
+
return result;
|
|
11
|
+
};
|
|
12
|
+
socksServer.setRulesetValidator(async (conn) => {
|
|
13
|
+
try {
|
|
14
|
+
const hostname = conn.destAddress;
|
|
15
|
+
const port = conn.destPort;
|
|
16
|
+
logForDebugging(`Connection request to ${hostname}:${port}`);
|
|
17
|
+
const filterResult = normalizeFilterResult(await options.filter(port, hostname));
|
|
18
|
+
emitNetworkDecisionEvent({
|
|
19
|
+
host: hostname,
|
|
20
|
+
port,
|
|
21
|
+
decision: filterResult.allowed ? 'allow' : 'deny',
|
|
22
|
+
reason: filterResult.reason,
|
|
23
|
+
route: 'direct',
|
|
24
|
+
});
|
|
25
|
+
if (!filterResult.allowed) {
|
|
26
|
+
logForDebugging(`Connection blocked to ${hostname}:${port}`, {
|
|
27
|
+
level: 'error',
|
|
28
|
+
});
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
logForDebugging(`Connection allowed to ${hostname}:${port}`);
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
logForDebugging(`Error validating connection: ${error}`, {
|
|
36
|
+
level: 'error',
|
|
37
|
+
});
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
return {
|
|
42
|
+
server: socksServer,
|
|
43
|
+
getPort() {
|
|
44
|
+
// Access the internal server to get the port
|
|
45
|
+
// We need to use type assertion here as the server property is private
|
|
46
|
+
try {
|
|
47
|
+
const serverInternal = socksServer?.server;
|
|
48
|
+
if (serverInternal && typeof serverInternal?.address === 'function') {
|
|
49
|
+
const address = serverInternal.address();
|
|
50
|
+
if (address && typeof address === 'object' && 'port' in address) {
|
|
51
|
+
return address.port;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
// Server might not be listening yet or property access failed
|
|
57
|
+
logForDebugging(`Error getting port: ${error}`, { level: 'error' });
|
|
58
|
+
}
|
|
59
|
+
return undefined;
|
|
60
|
+
},
|
|
61
|
+
listen(port, hostname) {
|
|
62
|
+
return new Promise((resolve, reject) => {
|
|
63
|
+
const listeningCallback = () => {
|
|
64
|
+
const actualPort = this.getPort();
|
|
65
|
+
if (actualPort) {
|
|
66
|
+
logForDebugging(`SOCKS proxy listening on ${hostname}:${actualPort}`);
|
|
67
|
+
resolve(actualPort);
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
reject(new Error('Failed to get SOCKS proxy server port'));
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
socksServer.listen(port, hostname, listeningCallback);
|
|
74
|
+
});
|
|
75
|
+
},
|
|
76
|
+
async close() {
|
|
77
|
+
return new Promise((resolve, reject) => {
|
|
78
|
+
socksServer.close(error => {
|
|
79
|
+
if (error) {
|
|
80
|
+
// Only reject for actual errors, not for "already closed" states
|
|
81
|
+
// Check for common "already closed" error patterns
|
|
82
|
+
const errorMessage = error.message?.toLowerCase() || '';
|
|
83
|
+
const isAlreadyClosed = errorMessage.includes('not running') ||
|
|
84
|
+
errorMessage.includes('already closed') ||
|
|
85
|
+
errorMessage.includes('not listening');
|
|
86
|
+
if (!isAlreadyClosed) {
|
|
87
|
+
reject(error);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
resolve();
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
},
|
|
95
|
+
unref() {
|
|
96
|
+
// Access the internal server to call unref
|
|
97
|
+
try {
|
|
98
|
+
const serverInternal = socksServer?.server;
|
|
99
|
+
if (serverInternal && typeof serverInternal?.unref === 'function') {
|
|
100
|
+
serverInternal.unref();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
logForDebugging(`Error calling unref: ${error}`, { level: 'error' });
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=socks-proxy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"socks-proxy.js","sourceRoot":"","sources":["../../src/sandbox/socks-proxy.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type SandboxRuntimeConfig } from '../sandbox/sandbox-config.js';
|
|
2
|
+
/**
|
|
3
|
+
* Parse and validate sandbox configuration from a string
|
|
4
|
+
* Used for parsing config from control fd (JSON lines protocol)
|
|
5
|
+
*/
|
|
6
|
+
export declare function loadConfigFromString(content: string): SandboxRuntimeConfig | null;
|
|
7
|
+
/**
|
|
8
|
+
* Load and validate sandbox configuration from a file
|
|
9
|
+
*/
|
|
10
|
+
export declare function loadConfig(filePath: string): SandboxRuntimeConfig | null;
|
|
11
|
+
//# sourceMappingURL=config-loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-loader.d.ts","sourceRoot":"","sources":["../../src/utils/config-loader.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,oBAAoB,EAC1B,MAAM,8BAA8B,CAAA;AAErC;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,GACd,oBAAoB,GAAG,IAAI,CAe7B;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,oBAAoB,GAAG,IAAI,CAmCxE"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import { SandboxRuntimeConfigSchema, } from '../sandbox/sandbox-config.js';
|
|
3
|
+
/**
|
|
4
|
+
* Parse and validate sandbox configuration from a string
|
|
5
|
+
* Used for parsing config from control fd (JSON lines protocol)
|
|
6
|
+
*/
|
|
7
|
+
export function loadConfigFromString(content) {
|
|
8
|
+
if (!content.trim()) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
try {
|
|
12
|
+
const parsed = JSON.parse(content);
|
|
13
|
+
const result = SandboxRuntimeConfigSchema.safeParse(parsed);
|
|
14
|
+
if (!result.success) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
return result.data;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Load and validate sandbox configuration from a file
|
|
25
|
+
*/
|
|
26
|
+
export function loadConfig(filePath) {
|
|
27
|
+
try {
|
|
28
|
+
if (!fs.existsSync(filePath)) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
32
|
+
if (content.trim() === '') {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
// Parse JSON
|
|
36
|
+
const parsed = JSON.parse(content);
|
|
37
|
+
// Validate with zod schema
|
|
38
|
+
const result = SandboxRuntimeConfigSchema.safeParse(parsed);
|
|
39
|
+
if (!result.success) {
|
|
40
|
+
console.error(`Invalid configuration in ${filePath}:`);
|
|
41
|
+
result.error.issues.forEach(issue => {
|
|
42
|
+
const path = issue.path.join('.');
|
|
43
|
+
console.error(` - ${path}: ${issue.message}`);
|
|
44
|
+
});
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
return result.data;
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
// Log parse errors to help users debug invalid config files
|
|
51
|
+
if (error instanceof SyntaxError) {
|
|
52
|
+
console.error(`Invalid JSON in config file ${filePath}: ${error.message}`);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
console.error(`Failed to load config from ${filePath}: ${error}`);
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=config-loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-loader.js","sourceRoot":"","sources":["../../src/utils/config-loader.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,IAAI,CAAA;AACxB,OAAO,EACL,0BAA0B,GAE3B,MAAM,8BAA8B,CAAA;AAErC;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAClC,OAAe;IAEf,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;QACpB,OAAO,IAAI,CAAA;IACb,CAAC;IAED,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;QAClC,MAAM,MAAM,GAAG,0BAA0B,CAAC,SAAS,CAAC,MAAM,CAAC,CAAA;QAC3D,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,OAAO,IAAI,CAAA;QACb,CAAC;QACD,OAAO,MAAM,CAAC,IAAI,CAAA;IACpB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU,CAAC,QAAgB;IACzC,IAAI,CAAC;QACH,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC7B,OAAO,IAAI,CAAA;QACb,CAAC;QACD,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;QAClD,IAAI,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;YAC1B,OAAO,IAAI,CAAA;QACb,CAAC;QAED,aAAa;QACb,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;QAElC,2BAA2B;QAC3B,MAAM,MAAM,GAAG,0BAA0B,CAAC,SAAS,CAAC,MAAM,CAAC,CAAA;QAE3D,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,OAAO,CAAC,KAAK,CAAC,4BAA4B,QAAQ,GAAG,CAAC,CAAA;YACtD,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;gBAClC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;gBACjC,OAAO,CAAC,KAAK,CAAC,OAAO,IAAI,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC,CAAA;YAChD,CAAC,CAAC,CAAA;YACF,OAAO,IAAI,CAAA;QACb,CAAC;QAED,OAAO,MAAM,CAAC,IAAI,CAAA;IACpB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,4DAA4D;QAC5D,IAAI,KAAK,YAAY,WAAW,EAAE,CAAC;YACjC,OAAO,CAAC,KAAK,CAAC,+BAA+B,QAAQ,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC,CAAA;QAC5E,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,KAAK,CAAC,8BAA8B,QAAQ,KAAK,KAAK,EAAE,CAAC,CAAA;QACnE,CAAC;QACD,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"debug.d.ts","sourceRoot":"","sources":["../../src/utils/debug.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAAA;CAAE,GAC9C,IAAI,CAsBN"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple debug logging for standalone sandbox
|
|
3
|
+
*/
|
|
4
|
+
export function logForDebugging(message, options) {
|
|
5
|
+
// Only log if SRT_DEBUG environment variable is set
|
|
6
|
+
// Using SRT_DEBUG instead of DEBUG to avoid conflicts with other tools
|
|
7
|
+
// (DEBUG is commonly used by Node.js debug libraries and VS Code)
|
|
8
|
+
if (!process.env.SRT_DEBUG) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
const level = options?.level || 'info';
|
|
12
|
+
const prefix = '[SandboxDebug]';
|
|
13
|
+
// Always use stderr to avoid corrupting stdout JSON streams
|
|
14
|
+
switch (level) {
|
|
15
|
+
case 'error':
|
|
16
|
+
console.error(`${prefix} ${message}`);
|
|
17
|
+
break;
|
|
18
|
+
case 'warn':
|
|
19
|
+
console.warn(`${prefix} ${message}`);
|
|
20
|
+
break;
|
|
21
|
+
default:
|
|
22
|
+
console.error(`${prefix} ${message}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=debug.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"debug.js","sourceRoot":"","sources":["../../src/utils/debug.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,UAAU,eAAe,CAC7B,OAAe,EACf,OAA+C;IAE/C,oDAAoD;IACpD,uEAAuE;IACvE,kEAAkE;IAClE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC;QAC3B,OAAM;IACR,CAAC;IAED,MAAM,KAAK,GAAG,OAAO,EAAE,KAAK,IAAI,MAAM,CAAA;IACtC,MAAM,MAAM,GAAG,gBAAgB,CAAA;IAE/B,4DAA4D;IAC5D,QAAQ,KAAK,EAAE,CAAC;QACd,KAAK,OAAO;YACV,OAAO,CAAC,KAAK,CAAC,GAAG,MAAM,IAAI,OAAO,EAAE,CAAC,CAAA;YACrC,MAAK;QACP,KAAK,MAAM;YACT,OAAO,CAAC,IAAI,CAAC,GAAG,MAAM,IAAI,OAAO,EAAE,CAAC,CAAA;YACpC,MAAK;QACP;YACE,OAAO,CAAC,KAAK,CAAC,GAAG,MAAM,IAAI,OAAO,EAAE,CAAC,CAAA;IACzC,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform detection utilities
|
|
3
|
+
*/
|
|
4
|
+
export type Platform = 'macos' | 'linux' | 'windows' | 'unknown';
|
|
5
|
+
/**
|
|
6
|
+
* Get the WSL version (1 or 2+) if running in WSL.
|
|
7
|
+
* Returns undefined if not running in WSL.
|
|
8
|
+
*/
|
|
9
|
+
export declare function getWslVersion(): string | undefined;
|
|
10
|
+
/**
|
|
11
|
+
* Detect the current platform.
|
|
12
|
+
* Note: All Linux including WSL returns 'linux'. Use getWslVersion() to detect WSL1 (unsupported).
|
|
13
|
+
*/
|
|
14
|
+
export declare function getPlatform(): Platform;
|
|
15
|
+
//# sourceMappingURL=platform.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"platform.d.ts","sourceRoot":"","sources":["../../src/utils/platform.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,OAAO,GAAG,SAAS,GAAG,SAAS,CAAA;AAEhE;;;GAGG;AACH,wBAAgB,aAAa,IAAI,MAAM,GAAG,SAAS,CAwBlD;AAED;;;GAGG;AACH,wBAAgB,WAAW,IAAI,QAAQ,CAatC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform detection utilities
|
|
3
|
+
*/
|
|
4
|
+
import * as fs from 'fs';
|
|
5
|
+
/**
|
|
6
|
+
* Get the WSL version (1 or 2+) if running in WSL.
|
|
7
|
+
* Returns undefined if not running in WSL.
|
|
8
|
+
*/
|
|
9
|
+
export function getWslVersion() {
|
|
10
|
+
if (process.platform !== 'linux') {
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
try {
|
|
14
|
+
const procVersion = fs.readFileSync('/proc/version', { encoding: 'utf8' });
|
|
15
|
+
// Check for explicit WSL version markers (e.g., "WSL2", "WSL3", etc.)
|
|
16
|
+
const wslVersionMatch = procVersion.match(/WSL(\d+)/i);
|
|
17
|
+
if (wslVersionMatch && wslVersionMatch[1]) {
|
|
18
|
+
return wslVersionMatch[1];
|
|
19
|
+
}
|
|
20
|
+
// If no explicit WSL version but contains Microsoft, assume WSL1
|
|
21
|
+
// This handles the original WSL1 format: "4.4.0-19041-Microsoft"
|
|
22
|
+
if (procVersion.toLowerCase().includes('microsoft')) {
|
|
23
|
+
return '1';
|
|
24
|
+
}
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Detect the current platform.
|
|
33
|
+
* Note: All Linux including WSL returns 'linux'. Use getWslVersion() to detect WSL1 (unsupported).
|
|
34
|
+
*/
|
|
35
|
+
export function getPlatform() {
|
|
36
|
+
switch (process.platform) {
|
|
37
|
+
case 'darwin':
|
|
38
|
+
return 'macos';
|
|
39
|
+
case 'linux':
|
|
40
|
+
// WSL2+ is treated as Linux (same sandboxing)
|
|
41
|
+
// WSL1 is also returned as 'linux' but will fail isSupportedPlatform check
|
|
42
|
+
return 'linux';
|
|
43
|
+
case 'win32':
|
|
44
|
+
return 'windows';
|
|
45
|
+
default:
|
|
46
|
+
return 'unknown';
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=platform.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"platform.js","sourceRoot":"","sources":["../../src/utils/platform.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,MAAM,IAAI,CAAA;AAIxB;;;GAGG;AACH,MAAM,UAAU,aAAa;IAC3B,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QACjC,OAAO,SAAS,CAAA;IAClB,CAAC;IAED,IAAI,CAAC;QACH,MAAM,WAAW,GAAG,EAAE,CAAC,YAAY,CAAC,eAAe,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAA;QAE1E,sEAAsE;QACtE,MAAM,eAAe,GAAG,WAAW,CAAC,KAAK,CAAC,WAAW,CAAC,CAAA;QACtD,IAAI,eAAe,IAAI,eAAe,CAAC,CAAC,CAAC,EAAE,CAAC;YAC1C,OAAO,eAAe,CAAC,CAAC,CAAC,CAAA;QAC3B,CAAC;QAED,iEAAiE;QACjE,iEAAiE;QACjE,IAAI,WAAW,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;YACpD,OAAO,GAAG,CAAA;QACZ,CAAC;QAED,OAAO,SAAS,CAAA;IAClB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAA;IAClB,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW;IACzB,QAAQ,OAAO,CAAC,QAAQ,EAAE,CAAC;QACzB,KAAK,QAAQ;YACX,OAAO,OAAO,CAAA;QAChB,KAAK,OAAO;YACV,8CAA8C;YAC9C,2EAA2E;YAC3E,OAAO,OAAO,CAAA;QAChB,KAAK,OAAO;YACV,OAAO,SAAS,CAAA;QAClB;YACE,OAAO,SAAS,CAAA;IACpB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface RipgrepConfig {
|
|
2
|
+
command: string;
|
|
3
|
+
args?: string[];
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Check if ripgrep (rg) is available synchronously
|
|
7
|
+
* Returns true if rg is installed, false otherwise
|
|
8
|
+
*/
|
|
9
|
+
export declare function hasRipgrepSync(): boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Execute ripgrep with the given arguments
|
|
12
|
+
* @param args Command-line arguments to pass to rg
|
|
13
|
+
* @param target Target directory or file to search
|
|
14
|
+
* @param abortSignal AbortSignal to cancel the operation
|
|
15
|
+
* @param config Ripgrep configuration (command and optional args)
|
|
16
|
+
* @returns Array of matching lines (one per line of output)
|
|
17
|
+
* @throws Error if ripgrep exits with non-zero status (except exit code 1 which means no matches)
|
|
18
|
+
*/
|
|
19
|
+
export declare function ripGrep(args: string[], target: string, abortSignal: AbortSignal, config?: RipgrepConfig): Promise<string[]>;
|
|
20
|
+
//# sourceMappingURL=ripgrep.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ripgrep.d.ts","sourceRoot":"","sources":["../../src/utils/ripgrep.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;CAChB;AAED;;;GAGG;AACH,wBAAgB,cAAc,IAAI,OAAO,CAWxC;AAED;;;;;;;;GAQG;AACH,wBAAsB,OAAO,CAC3B,IAAI,EAAE,MAAM,EAAE,EACd,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,WAAW,EACxB,MAAM,GAAE,aAAiC,GACxC,OAAO,CAAC,MAAM,EAAE,CAAC,CAkCnB"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { spawnSync } from 'child_process';
|
|
2
|
+
import { execFile } from 'child_process';
|
|
3
|
+
/**
|
|
4
|
+
* Check if ripgrep (rg) is available synchronously
|
|
5
|
+
* Returns true if rg is installed, false otherwise
|
|
6
|
+
*/
|
|
7
|
+
export function hasRipgrepSync() {
|
|
8
|
+
try {
|
|
9
|
+
const result = spawnSync('which', ['rg'], {
|
|
10
|
+
stdio: 'ignore',
|
|
11
|
+
timeout: 1000,
|
|
12
|
+
});
|
|
13
|
+
return result.status === 0;
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Execute ripgrep with the given arguments
|
|
21
|
+
* @param args Command-line arguments to pass to rg
|
|
22
|
+
* @param target Target directory or file to search
|
|
23
|
+
* @param abortSignal AbortSignal to cancel the operation
|
|
24
|
+
* @param config Ripgrep configuration (command and optional args)
|
|
25
|
+
* @returns Array of matching lines (one per line of output)
|
|
26
|
+
* @throws Error if ripgrep exits with non-zero status (except exit code 1 which means no matches)
|
|
27
|
+
*/
|
|
28
|
+
export async function ripGrep(args, target, abortSignal, config = { command: 'rg' }) {
|
|
29
|
+
const { command, args: commandArgs = [] } = config;
|
|
30
|
+
return new Promise((resolve, reject) => {
|
|
31
|
+
execFile(command, [...commandArgs, ...args, target], {
|
|
32
|
+
maxBuffer: 20000000, // 20MB
|
|
33
|
+
signal: abortSignal,
|
|
34
|
+
timeout: 10000, // 10 second timeout
|
|
35
|
+
}, (error, stdout, stderr) => {
|
|
36
|
+
// Success case - exit code 0
|
|
37
|
+
if (!error) {
|
|
38
|
+
resolve(stdout.trim().split('\n').filter(Boolean));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
// Exit code 1 means "no matches found" - this is normal, return empty array
|
|
42
|
+
if (error.code === 1) {
|
|
43
|
+
resolve([]);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
// All other errors should fail
|
|
47
|
+
reject(new Error(`ripgrep failed with exit code ${error.code}: ${stderr || error.message}`));
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=ripgrep.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ripgrep.js","sourceRoot":"","sources":["../../src/utils/ripgrep.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAQxC;;;GAGG;AACH,MAAM,UAAU,cAAc;IAC5B,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,EAAE;YACxC,KAAK,EAAE,QAAQ;YACf,OAAO,EAAE,IAAI;SACd,CAAC,CAAA;QAEF,OAAO,MAAM,CAAC,MAAM,KAAK,CAAC,CAAA;IAC5B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAA;IACd,CAAC;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAC3B,IAAc,EACd,MAAc,EACd,WAAwB,EACxB,SAAwB,EAAE,OAAO,EAAE,IAAI,EAAE;IAEzC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,EAAE,EAAE,GAAG,MAAM,CAAA;IAElD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,QAAQ,CACN,OAAO,EACP,CAAC,GAAG,WAAW,EAAE,GAAG,IAAI,EAAE,MAAM,CAAC,EACjC;YACE,SAAS,EAAE,QAAU,EAAE,OAAO;YAC9B,MAAM,EAAE,WAAW;YACnB,OAAO,EAAE,KAAM,EAAE,oBAAoB;SACtC,EACD,CAAC,KAA+B,EAAE,MAAc,EAAE,MAAc,EAAE,EAAE;YAClE,6BAA6B;YAC7B,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAA;gBAClD,OAAM;YACR,CAAC;YAED,4EAA4E;YAC5E,IAAI,KAAK,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;gBACrB,OAAO,CAAC,EAAE,CAAC,CAAA;gBACX,OAAM;YACR,CAAC;YAED,+BAA+B;YAC/B,MAAM,CACJ,IAAI,KAAK,CACP,iCAAiC,KAAK,CAAC,IAAI,KAAK,MAAM,IAAI,KAAK,CAAC,OAAO,EAAE,CAC1E,CACF,CAAA;QACH,CAAC,CACF,CAAA;IACH,CAAC,CAAC,CAAA;AACJ,CAAC"}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* apply-seccomp.c - Apply seccomp BPF filter and exec command
|
|
3
|
+
*
|
|
4
|
+
* Usage: apply-seccomp <filter.bpf> <command> [args...]
|
|
5
|
+
*
|
|
6
|
+
* This program reads a pre-compiled BPF filter from a file, applies it
|
|
7
|
+
* using prctl(PR_SET_SECCOMP), and then execs the specified command.
|
|
8
|
+
*
|
|
9
|
+
* The BPF filter must be in the format expected by SECCOMP_MODE_FILTER:
|
|
10
|
+
* - struct sock_fprog { unsigned short len; struct sock_filter *filter; }
|
|
11
|
+
* - Each filter instruction is 8 bytes (BPF instruction format)
|
|
12
|
+
*
|
|
13
|
+
* Compile: gcc -static -O2 -o apply-seccomp apply-seccomp.c
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
#include <stdio.h>
|
|
17
|
+
#include <stdlib.h>
|
|
18
|
+
#include <string.h>
|
|
19
|
+
#include <unistd.h>
|
|
20
|
+
#include <fcntl.h>
|
|
21
|
+
#include <sys/prctl.h>
|
|
22
|
+
#include <linux/seccomp.h>
|
|
23
|
+
#include <linux/filter.h>
|
|
24
|
+
#include <errno.h>
|
|
25
|
+
|
|
26
|
+
#ifndef PR_SET_NO_NEW_PRIVS
|
|
27
|
+
#define PR_SET_NO_NEW_PRIVS 38
|
|
28
|
+
#endif
|
|
29
|
+
|
|
30
|
+
#ifndef SECCOMP_MODE_FILTER
|
|
31
|
+
#define SECCOMP_MODE_FILTER 2
|
|
32
|
+
#endif
|
|
33
|
+
|
|
34
|
+
#define MAX_FILTER_SIZE 4096 // Maximum BPF filter size in bytes
|
|
35
|
+
|
|
36
|
+
int main(int argc, char *argv[], char *envp[]) {
|
|
37
|
+
if (argc < 3) {
|
|
38
|
+
fprintf(stderr, "Usage: %s <filter.bpf> <command> [args...]\n", argv[0]);
|
|
39
|
+
return 1;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const char *filter_path = argv[1];
|
|
43
|
+
char **command_argv = &argv[2];
|
|
44
|
+
|
|
45
|
+
// Open and read BPF filter file
|
|
46
|
+
int fd = open(filter_path, O_RDONLY);
|
|
47
|
+
if (fd < 0) {
|
|
48
|
+
perror("Failed to open BPF filter file");
|
|
49
|
+
return 1;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Read filter into memory
|
|
53
|
+
unsigned char filter_bytes[MAX_FILTER_SIZE];
|
|
54
|
+
ssize_t filter_size = read(fd, filter_bytes, MAX_FILTER_SIZE);
|
|
55
|
+
close(fd);
|
|
56
|
+
|
|
57
|
+
if (filter_size < 0) {
|
|
58
|
+
perror("Failed to read BPF filter");
|
|
59
|
+
return 1;
|
|
60
|
+
}
|
|
61
|
+
if (filter_size == 0) {
|
|
62
|
+
fprintf(stderr, "BPF filter file is empty\n");
|
|
63
|
+
return 1;
|
|
64
|
+
}
|
|
65
|
+
if (filter_size % 8 != 0) {
|
|
66
|
+
fprintf(stderr, "Invalid BPF filter size: %zd (must be multiple of 8)\n", filter_size);
|
|
67
|
+
return 1;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Convert bytes to sock_filter instructions
|
|
71
|
+
unsigned short filter_len = filter_size / 8;
|
|
72
|
+
struct sock_filter *filter = (struct sock_filter *)filter_bytes;
|
|
73
|
+
|
|
74
|
+
// Set up sock_fprog structure
|
|
75
|
+
struct sock_fprog prog = {
|
|
76
|
+
.len = filter_len,
|
|
77
|
+
.filter = filter,
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// Set NO_NEW_PRIVS to allow seccomp without CAP_SYS_ADMIN
|
|
81
|
+
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) != 0) {
|
|
82
|
+
perror("prctl(PR_SET_NO_NEW_PRIVS) failed");
|
|
83
|
+
return 1;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Apply seccomp filter
|
|
87
|
+
if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog) != 0) {
|
|
88
|
+
perror("prctl(PR_SET_SECCOMP) failed");
|
|
89
|
+
return 1;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Exec the command with seccomp filter active
|
|
93
|
+
execvp(command_argv[0], command_argv);
|
|
94
|
+
|
|
95
|
+
// If we get here, exec failed
|
|
96
|
+
perror("execvp failed");
|
|
97
|
+
return 1;
|
|
98
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Seccomp BPF filter generator to block Unix domain socket creation
|
|
3
|
+
*
|
|
4
|
+
* This program generates a seccomp-bpf filter that blocks the socket() syscall
|
|
5
|
+
* when called with AF_UNIX as the domain argument. This prevents creation of
|
|
6
|
+
* Unix domain sockets while allowing all other socket types (AF_INET, AF_INET6, etc.)
|
|
7
|
+
* and all other syscalls.
|
|
8
|
+
*
|
|
9
|
+
* The filter is exported in a format compatible with bubblewrap's --seccomp flag.
|
|
10
|
+
*
|
|
11
|
+
* SECURITY LIMITATION - 32-bit x86 (ia32):
|
|
12
|
+
* TODO: This filter does NOT block socketcall() syscall, which is a security issue
|
|
13
|
+
* on 32-bit x86 systems. On ia32, the socket() syscall doesn't exist - instead,
|
|
14
|
+
* all socket operations are multiplexed through socketcall():
|
|
15
|
+
* - socketcall(SYS_SOCKET, [AF_UNIX, ...]) - can bypass this filter
|
|
16
|
+
* - socketcall(SYS_SOCKETPAIR, [AF_UNIX, ...]) - can bypass this filter
|
|
17
|
+
*
|
|
18
|
+
* To fix this, we need to add conditional rules that:
|
|
19
|
+
* 1. Check if socketcall() exists on the current architecture (32-bit x86 only)
|
|
20
|
+
* 2. Block socketcall(SYS_SOCKET, ...) when first arg of sub-call is AF_UNIX
|
|
21
|
+
* 3. Block socketcall(SYS_SOCKETPAIR, ...) when first arg of sub-call is AF_UNIX
|
|
22
|
+
*
|
|
23
|
+
* This requires inspecting the arguments passed to socketcall, which is more
|
|
24
|
+
* complex BPF logic. For now, 32-bit x86 is not supported.
|
|
25
|
+
*
|
|
26
|
+
* Compilation:
|
|
27
|
+
* gcc -o seccomp-unix-block seccomp-unix-block.c -lseccomp
|
|
28
|
+
*
|
|
29
|
+
* Usage:
|
|
30
|
+
* ./seccomp-unix-block <output-file>
|
|
31
|
+
*
|
|
32
|
+
* Dependencies:
|
|
33
|
+
* - libseccomp (libseccomp-dev package on Debian/Ubuntu)
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
#include <errno.h>
|
|
37
|
+
#include <fcntl.h>
|
|
38
|
+
#include <stdio.h>
|
|
39
|
+
#include <stdlib.h>
|
|
40
|
+
#include <string.h>
|
|
41
|
+
#include <unistd.h>
|
|
42
|
+
#include <seccomp.h>
|
|
43
|
+
#include <sys/socket.h>
|
|
44
|
+
#include <sys/stat.h>
|
|
45
|
+
#include <sys/types.h>
|
|
46
|
+
|
|
47
|
+
int main(int argc, char *argv[]) {
|
|
48
|
+
scmp_filter_ctx ctx;
|
|
49
|
+
int rc;
|
|
50
|
+
|
|
51
|
+
if (argc != 2) {
|
|
52
|
+
fprintf(stderr, "Usage: %s <output-file>\n", argv[0]);
|
|
53
|
+
return 1;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const char *output_file = argv[1];
|
|
57
|
+
|
|
58
|
+
/* Create seccomp context with default action ALLOW */
|
|
59
|
+
ctx = seccomp_init(SCMP_ACT_ALLOW);
|
|
60
|
+
if (ctx == NULL) {
|
|
61
|
+
fprintf(stderr, "Error: Failed to initialize seccomp context\n");
|
|
62
|
+
return 1;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/* Add rule to block socket(AF_UNIX, ...) */
|
|
66
|
+
/* socket() syscall signature: int socket(int domain, int type, int protocol) */
|
|
67
|
+
/* arg0 = domain (AF_UNIX = 1) */
|
|
68
|
+
rc = seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(socket), 1,
|
|
69
|
+
SCMP_A0(SCMP_CMP_EQ, AF_UNIX));
|
|
70
|
+
if (rc < 0) {
|
|
71
|
+
fprintf(stderr, "Error: Failed to add seccomp rule: %s\n", strerror(-rc));
|
|
72
|
+
seccomp_release(ctx);
|
|
73
|
+
return 1;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* Export the filter to a file */
|
|
77
|
+
int fd = open(output_file, O_CREAT | O_WRONLY | O_TRUNC, 0600);
|
|
78
|
+
if (fd < 0) {
|
|
79
|
+
fprintf(stderr, "Error: Failed to open output file: %s\n", strerror(errno));
|
|
80
|
+
seccomp_release(ctx);
|
|
81
|
+
return 1;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
rc = seccomp_export_bpf(ctx, fd);
|
|
85
|
+
if (rc < 0) {
|
|
86
|
+
fprintf(stderr, "Error: Failed to export seccomp filter: %s\n", strerror(-rc));
|
|
87
|
+
close(fd);
|
|
88
|
+
seccomp_release(ctx);
|
|
89
|
+
return 1;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* Clean up */
|
|
93
|
+
close(fd);
|
|
94
|
+
seccomp_release(ctx);
|
|
95
|
+
|
|
96
|
+
return 0;
|
|
97
|
+
}
|