tenseleyflow/shithub / 0a4cf06

Browse files

runner/config: install pinned seccomp profile

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
0a4cf06cd2aa47e618d0a0d00aa89e611b4cd5d6
Parents
713648d
Tree
b036154

9 changed files

StatusFile+-
M cmd/shithubd-runner/run.go 31 21
M deploy/ansible/roles/shithubd-runner/defaults/main.yml 3 0
M deploy/ansible/roles/shithubd-runner/tasks/main.yml 9 0
M deploy/ansible/roles/shithubd-runner/templates/config.toml.j2 3 0
A deploy/runner-config/README.md 16 0
A deploy/runner-config/seccomp.json 875 0
M deploy/systemd/shithubd-runner.service 3 2
M internal/runner/config/config.go 33 13
M internal/runner/config/config_test.go 37 3
cmd/shithubd-runner/run.gomodified
@@ -58,8 +58,12 @@ var runCmd = &cobra.Command{
5858
 			Network:        cfg.Engine.Network,
5959
 			Memory:         cfg.Engine.Memory,
6060
 			CPUs:           cfg.Engine.CPUs,
61
+			SeccompProfile: cfg.Engine.SeccompProfile,
62
+			User:           cfg.Engine.User,
63
+			PidsLimit:      cfg.Engine.PidsLimit,
6164
 			Stdout:         os.Stdout,
6265
 			Stderr:         os.Stderr,
66
+			Logger:         logger,
6367
 		})
6468
 		r := runnerpkg.New(runnerpkg.Options{
6569
 			API:          client,
@@ -90,6 +94,9 @@ func init() {
9094
 	runCmd.Flags().String("network", "", "Container network")
9195
 	runCmd.Flags().String("memory", "", "Container memory limit")
9296
 	runCmd.Flags().String("cpus", "", "Container CPU limit")
97
+	runCmd.Flags().String("seccomp-profile", "", "Container seccomp profile path")
98
+	runCmd.Flags().String("container-user", "", "Default container user")
99
+	runCmd.Flags().Int("pids-limit", 0, "Container PID limit")
93100
 	runCmd.Flags().String("log-level", "", "Log level: debug, info, warn, error")
94101
 	runCmd.Flags().String("log-format", "", "Log format: text or json")
95102
 }
@@ -108,6 +115,9 @@ func flagOverrides(cmd *cobra.Command) map[string]string {
108115
 		"network":         "engine.network",
109116
 		"memory":          "engine.memory",
110117
 		"cpus":            "engine.cpus",
118
+		"seccomp-profile": "engine.seccomp_profile",
119
+		"container-user":  "engine.user",
120
+		"pids-limit":      "engine.pids_limit",
111121
 		"log-level":       "log.level",
112122
 		"log-format":      "log.format",
113123
 	}
deploy/ansible/roles/shithubd-runner/defaults/main.ymlmodified
@@ -17,5 +17,8 @@ shithub_runner_default_image: ghcr.io/shithub/runner-nix:1.0
1717
 shithub_runner_network: bridge
1818
 shithub_runner_memory: 2g
1919
 shithub_runner_cpus: "2"
20
+shithub_runner_seccomp_profile: /etc/shithubd-runner/seccomp.json
21
+shithub_runner_container_user: "65534:65534"
22
+shithub_runner_pids_limit: 512
2023
 shithub_runner_log_level: info
2124
 shithub_runner_log_format: text
deploy/ansible/roles/shithubd-runner/tasks/main.ymlmodified
@@ -93,6 +93,15 @@
9393
   no_log: true
9494
   notify: restart shithubd-runner
9595
 
96
+- name: Runner seccomp profile
97
+  copy:
98
+    src: "{{ playbook_dir }}/../runner-config/seccomp.json"
99
+    dest: "{{ shithub_runner_seccomp_profile }}"
100
+    owner: root
101
+    group: shithub-runner
102
+    mode: "0640"
103
+  notify: restart shithubd-runner
104
+
96105
 - name: Runner systemd unit
97106
   copy:
98107
     src: "{{ playbook_dir }}/../systemd/shithubd-runner.service"
deploy/ansible/roles/shithubd-runner/templates/config.toml.j2modified
@@ -20,6 +20,9 @@ default_image = "{{ shithub_runner_default_image }}"
2020
 network = "{{ shithub_runner_network }}"
2121
 memory = "{{ shithub_runner_memory }}"
2222
 cpus = "{{ shithub_runner_cpus }}"
23
+seccomp_profile = "{{ shithub_runner_seccomp_profile }}"
24
+user = "{{ shithub_runner_container_user }}"
25
+pids_limit = {{ shithub_runner_pids_limit }}
2326
 
2427
 [log]
2528
 level = "{{ shithub_runner_log_level }}"
deploy/runner-config/README.mdadded
@@ -0,0 +1,16 @@
1
+# Runner config assets
2
+
3
+`seccomp.json` is a pinned copy of Docker/Moby's default seccomp
4
+profile. It is copied to `/etc/shithubd-runner/seccomp.json` by the
5
+`shithubd-runner` Ansible role and passed to each step container via:
6
+
7
+```sh
8
+--security-opt=seccomp=/etc/shithubd-runner/seccomp.json
9
+```
10
+
11
+Source: `moby/moby` commit
12
+`7d169a7f0ccd8f79edb6ad02ba20025cb487b217`,
13
+`vendor/github.com/moby/profiles/seccomp/default.json`.
14
+
15
+Update this file deliberately when changing Docker daemon versions or
16
+runner syscall posture.
deploy/runner-config/seccomp.jsonadded
@@ -0,0 +1,875 @@
1
+{
2
+	"defaultAction": "SCMP_ACT_ERRNO",
3
+	"defaultErrnoRet": 1,
4
+	"archMap": [
5
+		{
6
+			"architecture": "SCMP_ARCH_X86_64",
7
+			"subArchitectures": [
8
+				"SCMP_ARCH_X86",
9
+				"SCMP_ARCH_X32"
10
+			]
11
+		},
12
+		{
13
+			"architecture": "SCMP_ARCH_AARCH64",
14
+			"subArchitectures": [
15
+				"SCMP_ARCH_ARM"
16
+			]
17
+		},
18
+		{
19
+			"architecture": "SCMP_ARCH_MIPS64",
20
+			"subArchitectures": [
21
+				"SCMP_ARCH_MIPS",
22
+				"SCMP_ARCH_MIPS64N32"
23
+			]
24
+		},
25
+		{
26
+			"architecture": "SCMP_ARCH_MIPS64N32",
27
+			"subArchitectures": [
28
+				"SCMP_ARCH_MIPS",
29
+				"SCMP_ARCH_MIPS64"
30
+			]
31
+		},
32
+		{
33
+			"architecture": "SCMP_ARCH_MIPSEL64",
34
+			"subArchitectures": [
35
+				"SCMP_ARCH_MIPSEL",
36
+				"SCMP_ARCH_MIPSEL64N32"
37
+			]
38
+		},
39
+		{
40
+			"architecture": "SCMP_ARCH_MIPSEL64N32",
41
+			"subArchitectures": [
42
+				"SCMP_ARCH_MIPSEL",
43
+				"SCMP_ARCH_MIPSEL64"
44
+			]
45
+		},
46
+		{
47
+			"architecture": "SCMP_ARCH_S390X",
48
+			"subArchitectures": [
49
+				"SCMP_ARCH_S390"
50
+			]
51
+		},
52
+		{
53
+			"architecture": "SCMP_ARCH_RISCV64",
54
+			"subArchitectures": null
55
+		},
56
+		{
57
+			"architecture": "SCMP_ARCH_LOONGARCH64",
58
+			"subArchitectures": null
59
+		}
60
+	],
61
+	"syscalls": [
62
+		{
63
+			"names": [
64
+				"accept",
65
+				"accept4",
66
+				"access",
67
+				"adjtimex",
68
+				"alarm",
69
+				"bind",
70
+				"brk",
71
+				"cachestat",
72
+				"capget",
73
+				"capset",
74
+				"chdir",
75
+				"chmod",
76
+				"chown",
77
+				"chown32",
78
+				"clock_adjtime",
79
+				"clock_adjtime64",
80
+				"clock_getres",
81
+				"clock_getres_time64",
82
+				"clock_gettime",
83
+				"clock_gettime64",
84
+				"clock_nanosleep",
85
+				"clock_nanosleep_time64",
86
+				"close",
87
+				"close_range",
88
+				"connect",
89
+				"copy_file_range",
90
+				"creat",
91
+				"dup",
92
+				"dup2",
93
+				"dup3",
94
+				"epoll_create",
95
+				"epoll_create1",
96
+				"epoll_ctl",
97
+				"epoll_ctl_old",
98
+				"epoll_pwait",
99
+				"epoll_pwait2",
100
+				"epoll_wait",
101
+				"epoll_wait_old",
102
+				"eventfd",
103
+				"eventfd2",
104
+				"execve",
105
+				"execveat",
106
+				"exit",
107
+				"exit_group",
108
+				"faccessat",
109
+				"faccessat2",
110
+				"fadvise64",
111
+				"fadvise64_64",
112
+				"fallocate",
113
+				"fanotify_mark",
114
+				"fchdir",
115
+				"fchmod",
116
+				"fchmodat",
117
+				"fchmodat2",
118
+				"fchown",
119
+				"fchown32",
120
+				"fchownat",
121
+				"fcntl",
122
+				"fcntl64",
123
+				"fdatasync",
124
+				"fgetxattr",
125
+				"flistxattr",
126
+				"flock",
127
+				"fork",
128
+				"fremovexattr",
129
+				"fsetxattr",
130
+				"fstat",
131
+				"fstat64",
132
+				"fstatat64",
133
+				"fstatfs",
134
+				"fstatfs64",
135
+				"fsync",
136
+				"ftruncate",
137
+				"ftruncate64",
138
+				"futex",
139
+				"futex_requeue",
140
+				"futex_time64",
141
+				"futex_wait",
142
+				"futex_waitv",
143
+				"futex_wake",
144
+				"futimesat",
145
+				"getcpu",
146
+				"getcwd",
147
+				"getdents",
148
+				"getdents64",
149
+				"getegid",
150
+				"getegid32",
151
+				"geteuid",
152
+				"geteuid32",
153
+				"getgid",
154
+				"getgid32",
155
+				"getgroups",
156
+				"getgroups32",
157
+				"getitimer",
158
+				"getpeername",
159
+				"getpgid",
160
+				"getpgrp",
161
+				"getpid",
162
+				"getppid",
163
+				"getpriority",
164
+				"getrandom",
165
+				"getresgid",
166
+				"getresgid32",
167
+				"getresuid",
168
+				"getresuid32",
169
+				"getrlimit",
170
+				"get_robust_list",
171
+				"getrusage",
172
+				"getsid",
173
+				"getsockname",
174
+				"getsockopt",
175
+				"get_thread_area",
176
+				"gettid",
177
+				"gettimeofday",
178
+				"getuid",
179
+				"getuid32",
180
+				"getxattr",
181
+				"getxattrat",
182
+				"inotify_add_watch",
183
+				"inotify_init",
184
+				"inotify_init1",
185
+				"inotify_rm_watch",
186
+				"io_cancel",
187
+				"ioctl",
188
+				"io_destroy",
189
+				"io_getevents",
190
+				"io_pgetevents",
191
+				"io_pgetevents_time64",
192
+				"ioprio_get",
193
+				"ioprio_set",
194
+				"io_setup",
195
+				"io_submit",
196
+				"ipc",
197
+				"kill",
198
+				"landlock_add_rule",
199
+				"landlock_create_ruleset",
200
+				"landlock_restrict_self",
201
+				"lchown",
202
+				"lchown32",
203
+				"lgetxattr",
204
+				"link",
205
+				"linkat",
206
+				"listen",
207
+				"listmount",
208
+				"listxattr",
209
+				"listxattrat",
210
+				"llistxattr",
211
+				"_llseek",
212
+				"lremovexattr",
213
+				"lseek",
214
+				"lsetxattr",
215
+				"lstat",
216
+				"lstat64",
217
+				"madvise",
218
+				"map_shadow_stack",
219
+				"membarrier",
220
+				"memfd_create",
221
+				"memfd_secret",
222
+				"mincore",
223
+				"mkdir",
224
+				"mkdirat",
225
+				"mknod",
226
+				"mknodat",
227
+				"mlock",
228
+				"mlock2",
229
+				"mlockall",
230
+				"mmap",
231
+				"mmap2",
232
+				"mprotect",
233
+				"mq_getsetattr",
234
+				"mq_notify",
235
+				"mq_open",
236
+				"mq_timedreceive",
237
+				"mq_timedreceive_time64",
238
+				"mq_timedsend",
239
+				"mq_timedsend_time64",
240
+				"mq_unlink",
241
+				"mremap",
242
+				"mseal",
243
+				"msgctl",
244
+				"msgget",
245
+				"msgrcv",
246
+				"msgsnd",
247
+				"msync",
248
+				"munlock",
249
+				"munlockall",
250
+				"munmap",
251
+				"name_to_handle_at",
252
+				"nanosleep",
253
+				"newfstatat",
254
+				"_newselect",
255
+				"open",
256
+				"openat",
257
+				"openat2",
258
+				"pause",
259
+				"pidfd_open",
260
+				"pidfd_send_signal",
261
+				"pipe",
262
+				"pipe2",
263
+				"pkey_alloc",
264
+				"pkey_free",
265
+				"pkey_mprotect",
266
+				"poll",
267
+				"ppoll",
268
+				"ppoll_time64",
269
+				"prctl",
270
+				"pread64",
271
+				"preadv",
272
+				"preadv2",
273
+				"prlimit64",
274
+				"process_mrelease",
275
+				"pselect6",
276
+				"pselect6_time64",
277
+				"pwrite64",
278
+				"pwritev",
279
+				"pwritev2",
280
+				"read",
281
+				"readahead",
282
+				"readlink",
283
+				"readlinkat",
284
+				"readv",
285
+				"recv",
286
+				"recvfrom",
287
+				"recvmmsg",
288
+				"recvmmsg_time64",
289
+				"recvmsg",
290
+				"remap_file_pages",
291
+				"removexattr",
292
+				"removexattrat",
293
+				"rename",
294
+				"renameat",
295
+				"renameat2",
296
+				"restart_syscall",
297
+				"riscv_hwprobe",
298
+				"rmdir",
299
+				"rseq",
300
+				"rt_sigaction",
301
+				"rt_sigpending",
302
+				"rt_sigprocmask",
303
+				"rt_sigqueueinfo",
304
+				"rt_sigreturn",
305
+				"rt_sigsuspend",
306
+				"rt_sigtimedwait",
307
+				"rt_sigtimedwait_time64",
308
+				"rt_tgsigqueueinfo",
309
+				"sched_getaffinity",
310
+				"sched_getattr",
311
+				"sched_getparam",
312
+				"sched_get_priority_max",
313
+				"sched_get_priority_min",
314
+				"sched_getscheduler",
315
+				"sched_rr_get_interval",
316
+				"sched_rr_get_interval_time64",
317
+				"sched_setaffinity",
318
+				"sched_setattr",
319
+				"sched_setparam",
320
+				"sched_setscheduler",
321
+				"sched_yield",
322
+				"seccomp",
323
+				"select",
324
+				"semctl",
325
+				"semget",
326
+				"semop",
327
+				"semtimedop",
328
+				"semtimedop_time64",
329
+				"send",
330
+				"sendfile",
331
+				"sendfile64",
332
+				"sendmmsg",
333
+				"sendmsg",
334
+				"sendto",
335
+				"setfsgid",
336
+				"setfsgid32",
337
+				"setfsuid",
338
+				"setfsuid32",
339
+				"setgid",
340
+				"setgid32",
341
+				"setgroups",
342
+				"setgroups32",
343
+				"setitimer",
344
+				"setpgid",
345
+				"setpriority",
346
+				"setregid",
347
+				"setregid32",
348
+				"setresgid",
349
+				"setresgid32",
350
+				"setresuid",
351
+				"setresuid32",
352
+				"setreuid",
353
+				"setreuid32",
354
+				"setrlimit",
355
+				"set_robust_list",
356
+				"setsid",
357
+				"setsockopt",
358
+				"set_thread_area",
359
+				"set_tid_address",
360
+				"setuid",
361
+				"setuid32",
362
+				"setxattr",
363
+				"setxattrat",
364
+				"shmat",
365
+				"shmctl",
366
+				"shmdt",
367
+				"shmget",
368
+				"shutdown",
369
+				"sigaltstack",
370
+				"signalfd",
371
+				"signalfd4",
372
+				"sigprocmask",
373
+				"sigreturn",
374
+				"socketcall",
375
+				"socketpair",
376
+				"splice",
377
+				"stat",
378
+				"stat64",
379
+				"statfs",
380
+				"statfs64",
381
+				"statmount",
382
+				"statx",
383
+				"symlink",
384
+				"symlinkat",
385
+				"sync",
386
+				"sync_file_range",
387
+				"syncfs",
388
+				"sysinfo",
389
+				"tee",
390
+				"tgkill",
391
+				"time",
392
+				"timer_create",
393
+				"timer_delete",
394
+				"timer_getoverrun",
395
+				"timer_gettime",
396
+				"timer_gettime64",
397
+				"timer_settime",
398
+				"timer_settime64",
399
+				"timerfd_create",
400
+				"timerfd_gettime",
401
+				"timerfd_gettime64",
402
+				"timerfd_settime",
403
+				"timerfd_settime64",
404
+				"times",
405
+				"tkill",
406
+				"truncate",
407
+				"truncate64",
408
+				"ugetrlimit",
409
+				"umask",
410
+				"uname",
411
+				"unlink",
412
+				"unlinkat",
413
+				"uretprobe",
414
+				"utime",
415
+				"utimensat",
416
+				"utimensat_time64",
417
+				"utimes",
418
+				"vfork",
419
+				"vmsplice",
420
+				"wait4",
421
+				"waitid",
422
+				"waitpid",
423
+				"write",
424
+				"writev"
425
+			],
426
+			"action": "SCMP_ACT_ALLOW"
427
+		},
428
+		{
429
+			"names": [
430
+				"process_vm_readv",
431
+				"process_vm_writev",
432
+				"ptrace"
433
+			],
434
+			"action": "SCMP_ACT_ALLOW",
435
+			"includes": {
436
+				"minKernel": "4.8"
437
+			}
438
+		},
439
+		{
440
+			"names": [
441
+				"socket"
442
+			],
443
+			"action": "SCMP_ACT_ALLOW",
444
+			"args": [
445
+				{
446
+					"index": 0,
447
+					"value": 38,
448
+					"op": "SCMP_CMP_LT"
449
+				}
450
+			]
451
+		},
452
+		{
453
+			"names": [
454
+				"socket"
455
+			],
456
+			"action": "SCMP_ACT_ALLOW",
457
+			"args": [
458
+				{
459
+					"index": 0,
460
+					"value": 39,
461
+					"op": "SCMP_CMP_EQ"
462
+				}
463
+			]
464
+		},
465
+		{
466
+			"names": [
467
+				"socket"
468
+			],
469
+			"action": "SCMP_ACT_ALLOW",
470
+			"args": [
471
+				{
472
+					"index": 0,
473
+					"value": 40,
474
+					"op": "SCMP_CMP_GT"
475
+				}
476
+			]
477
+		},
478
+		{
479
+			"names": [
480
+				"personality"
481
+			],
482
+			"action": "SCMP_ACT_ALLOW",
483
+			"args": [
484
+				{
485
+					"index": 0,
486
+					"value": 0,
487
+					"op": "SCMP_CMP_EQ"
488
+				}
489
+			]
490
+		},
491
+		{
492
+			"names": [
493
+				"personality"
494
+			],
495
+			"action": "SCMP_ACT_ALLOW",
496
+			"args": [
497
+				{
498
+					"index": 0,
499
+					"value": 8,
500
+					"op": "SCMP_CMP_EQ"
501
+				}
502
+			]
503
+		},
504
+		{
505
+			"names": [
506
+				"personality"
507
+			],
508
+			"action": "SCMP_ACT_ALLOW",
509
+			"args": [
510
+				{
511
+					"index": 0,
512
+					"value": 131072,
513
+					"op": "SCMP_CMP_EQ"
514
+				}
515
+			]
516
+		},
517
+		{
518
+			"names": [
519
+				"personality"
520
+			],
521
+			"action": "SCMP_ACT_ALLOW",
522
+			"args": [
523
+				{
524
+					"index": 0,
525
+					"value": 131080,
526
+					"op": "SCMP_CMP_EQ"
527
+				}
528
+			]
529
+		},
530
+		{
531
+			"names": [
532
+				"personality"
533
+			],
534
+			"action": "SCMP_ACT_ALLOW",
535
+			"args": [
536
+				{
537
+					"index": 0,
538
+					"value": 4294967295,
539
+					"op": "SCMP_CMP_EQ"
540
+				}
541
+			]
542
+		},
543
+		{
544
+			"names": [
545
+				"sync_file_range2",
546
+				"swapcontext"
547
+			],
548
+			"action": "SCMP_ACT_ALLOW",
549
+			"includes": {
550
+				"arches": [
551
+					"ppc64le"
552
+				]
553
+			}
554
+		},
555
+		{
556
+			"names": [
557
+				"arm_fadvise64_64",
558
+				"arm_sync_file_range",
559
+				"sync_file_range2",
560
+				"breakpoint",
561
+				"cacheflush",
562
+				"set_tls"
563
+			],
564
+			"action": "SCMP_ACT_ALLOW",
565
+			"includes": {
566
+				"arches": [
567
+					"arm",
568
+					"arm64"
569
+				]
570
+			}
571
+		},
572
+		{
573
+			"names": [
574
+				"arch_prctl"
575
+			],
576
+			"action": "SCMP_ACT_ALLOW",
577
+			"includes": {
578
+				"arches": [
579
+					"amd64",
580
+					"x32"
581
+				]
582
+			}
583
+		},
584
+		{
585
+			"names": [
586
+				"modify_ldt"
587
+			],
588
+			"action": "SCMP_ACT_ALLOW",
589
+			"includes": {
590
+				"arches": [
591
+					"amd64",
592
+					"x32",
593
+					"x86"
594
+				]
595
+			}
596
+		},
597
+		{
598
+			"names": [
599
+				"s390_pci_mmio_read",
600
+				"s390_pci_mmio_write",
601
+				"s390_runtime_instr"
602
+			],
603
+			"action": "SCMP_ACT_ALLOW",
604
+			"includes": {
605
+				"arches": [
606
+					"s390",
607
+					"s390x"
608
+				]
609
+			}
610
+		},
611
+		{
612
+			"names": [
613
+				"riscv_flush_icache"
614
+			],
615
+			"action": "SCMP_ACT_ALLOW",
616
+			"includes": {
617
+				"arches": [
618
+					"riscv64"
619
+				]
620
+			}
621
+		},
622
+		{
623
+			"names": [
624
+				"open_by_handle_at"
625
+			],
626
+			"action": "SCMP_ACT_ALLOW",
627
+			"includes": {
628
+				"caps": [
629
+					"CAP_DAC_READ_SEARCH"
630
+				]
631
+			}
632
+		},
633
+		{
634
+			"names": [
635
+				"bpf",
636
+				"clone",
637
+				"clone3",
638
+				"fanotify_init",
639
+				"fsconfig",
640
+				"fsmount",
641
+				"fsopen",
642
+				"fspick",
643
+				"lookup_dcookie",
644
+				"lsm_get_self_attr",
645
+				"lsm_list_modules",
646
+				"lsm_set_self_attr",
647
+				"mount",
648
+				"mount_setattr",
649
+				"move_mount",
650
+				"open_tree",
651
+				"perf_event_open",
652
+				"quotactl",
653
+				"quotactl_fd",
654
+				"setdomainname",
655
+				"sethostname",
656
+				"setns",
657
+				"syslog",
658
+				"umount",
659
+				"umount2",
660
+				"unshare"
661
+			],
662
+			"action": "SCMP_ACT_ALLOW",
663
+			"includes": {
664
+				"caps": [
665
+					"CAP_SYS_ADMIN"
666
+				]
667
+			}
668
+		},
669
+		{
670
+			"names": [
671
+				"clone"
672
+			],
673
+			"action": "SCMP_ACT_ALLOW",
674
+			"args": [
675
+				{
676
+					"index": 0,
677
+					"value": 2114060288,
678
+					"op": "SCMP_CMP_MASKED_EQ"
679
+				}
680
+			],
681
+			"excludes": {
682
+				"caps": [
683
+					"CAP_SYS_ADMIN"
684
+				],
685
+				"arches": [
686
+					"s390",
687
+					"s390x"
688
+				]
689
+			}
690
+		},
691
+		{
692
+			"names": [
693
+				"clone"
694
+			],
695
+			"action": "SCMP_ACT_ALLOW",
696
+			"args": [
697
+				{
698
+					"index": 1,
699
+					"value": 2114060288,
700
+					"op": "SCMP_CMP_MASKED_EQ"
701
+				}
702
+			],
703
+			"comment": "s390 parameter ordering for clone is different",
704
+			"includes": {
705
+				"arches": [
706
+					"s390",
707
+					"s390x"
708
+				]
709
+			},
710
+			"excludes": {
711
+				"caps": [
712
+					"CAP_SYS_ADMIN"
713
+				]
714
+			}
715
+		},
716
+		{
717
+			"names": [
718
+				"clone3"
719
+			],
720
+			"action": "SCMP_ACT_ERRNO",
721
+			"errnoRet": 38,
722
+			"excludes": {
723
+				"caps": [
724
+					"CAP_SYS_ADMIN"
725
+				]
726
+			}
727
+		},
728
+		{
729
+			"names": [
730
+				"reboot"
731
+			],
732
+			"action": "SCMP_ACT_ALLOW",
733
+			"includes": {
734
+				"caps": [
735
+					"CAP_SYS_BOOT"
736
+				]
737
+			}
738
+		},
739
+		{
740
+			"names": [
741
+				"chroot"
742
+			],
743
+			"action": "SCMP_ACT_ALLOW",
744
+			"includes": {
745
+				"caps": [
746
+					"CAP_SYS_CHROOT"
747
+				]
748
+			}
749
+		},
750
+		{
751
+			"names": [
752
+				"delete_module",
753
+				"init_module",
754
+				"finit_module"
755
+			],
756
+			"action": "SCMP_ACT_ALLOW",
757
+			"includes": {
758
+				"caps": [
759
+					"CAP_SYS_MODULE"
760
+				]
761
+			}
762
+		},
763
+		{
764
+			"names": [
765
+				"acct"
766
+			],
767
+			"action": "SCMP_ACT_ALLOW",
768
+			"includes": {
769
+				"caps": [
770
+					"CAP_SYS_PACCT"
771
+				]
772
+			}
773
+		},
774
+		{
775
+			"names": [
776
+				"kcmp",
777
+				"pidfd_getfd",
778
+				"process_madvise",
779
+				"process_vm_readv",
780
+				"process_vm_writev",
781
+				"ptrace"
782
+			],
783
+			"action": "SCMP_ACT_ALLOW",
784
+			"includes": {
785
+				"caps": [
786
+					"CAP_SYS_PTRACE"
787
+				]
788
+			}
789
+		},
790
+		{
791
+			"names": [
792
+				"iopl",
793
+				"ioperm"
794
+			],
795
+			"action": "SCMP_ACT_ALLOW",
796
+			"includes": {
797
+				"caps": [
798
+					"CAP_SYS_RAWIO"
799
+				]
800
+			}
801
+		},
802
+		{
803
+			"names": [
804
+				"settimeofday",
805
+				"stime",
806
+				"clock_settime",
807
+				"clock_settime64"
808
+			],
809
+			"action": "SCMP_ACT_ALLOW",
810
+			"includes": {
811
+				"caps": [
812
+					"CAP_SYS_TIME"
813
+				]
814
+			}
815
+		},
816
+		{
817
+			"names": [
818
+				"vhangup"
819
+			],
820
+			"action": "SCMP_ACT_ALLOW",
821
+			"includes": {
822
+				"caps": [
823
+					"CAP_SYS_TTY_CONFIG"
824
+				]
825
+			}
826
+		},
827
+		{
828
+			"names": [
829
+				"get_mempolicy",
830
+				"mbind",
831
+				"set_mempolicy",
832
+				"set_mempolicy_home_node"
833
+			],
834
+			"action": "SCMP_ACT_ALLOW",
835
+			"includes": {
836
+				"caps": [
837
+					"CAP_SYS_NICE"
838
+				]
839
+			}
840
+		},
841
+		{
842
+			"names": [
843
+				"syslog"
844
+			],
845
+			"action": "SCMP_ACT_ALLOW",
846
+			"includes": {
847
+				"caps": [
848
+					"CAP_SYSLOG"
849
+				]
850
+			}
851
+		},
852
+		{
853
+			"names": [
854
+				"bpf"
855
+			],
856
+			"action": "SCMP_ACT_ALLOW",
857
+			"includes": {
858
+				"caps": [
859
+					"CAP_BPF"
860
+				]
861
+			}
862
+		},
863
+		{
864
+			"names": [
865
+				"perf_event_open"
866
+			],
867
+			"action": "SCMP_ACT_ALLOW",
868
+			"includes": {
869
+				"caps": [
870
+					"CAP_PERFMON"
871
+				]
872
+			}
873
+		}
874
+	]
875
+}
deploy/systemd/shithubd-runner.servicemodified
@@ -14,8 +14,9 @@ Restart=on-failure
1414
 RestartSec=2
1515
 LimitNOFILE=65535
1616
 
17
-# Docker access is intentionally broad in S41d. S41e narrows network and
18
-# secret exposure; until then this unit is only for trusted runner hosts.
17
+# Docker socket access still makes the host trusted infrastructure.
18
+# Container-level hardening lives in internal/runner/engine/docker.go
19
+# and the pinned seccomp profile installed under /etc/shithubd-runner.
1920
 NoNewPrivileges=yes
2021
 ProtectSystem=strict
2122
 ProtectHome=yes
internal/runner/config/config.gomodified
@@ -28,6 +28,9 @@ const (
2828
 	DefaultPath            = "/etc/shithubd-runner/config.toml"
2929
 	EnvPrefix              = "SHITHUB_RUNNER_"
3030
 	defaultImage           = "ghcr.io/shithub/runner-nix:1.0"
31
+	defaultSeccompProfile  = "/etc/shithubd-runner/seccomp.json"
32
+	defaultContainerUser   = "65534:65534"
33
+	defaultContainerPIDMax = 512
3134
 )
3235
 
3336
 // LoadOptions controls config resolution. Zero value uses the default path,
@@ -65,6 +68,9 @@ type EngineConfig struct {
6568
 	Network        string `toml:"network"`
6669
 	Memory         string `toml:"memory"`
6770
 	CPUs           string `toml:"cpus"`
71
+	SeccompProfile string `toml:"seccomp_profile"`
72
+	User           string `toml:"user"`
73
+	PidsLimit      int    `toml:"pids_limit"`
6874
 }
6975
 
7076
 type LogConfig struct {
@@ -90,6 +96,9 @@ func Defaults() Config {
9096
 			Network:        "bridge",
9197
 			Memory:         "2g",
9298
 			CPUs:           "2",
99
+			SeccompProfile: defaultSeccompProfile,
100
+			User:           defaultContainerUser,
101
+			PidsLimit:      defaultContainerPIDMax,
93102
 		},
94103
 		Log: LogConfig{
95104
 			Level:  "info",
@@ -237,6 +246,17 @@ func Validate(c *Config) error {
237246
 	if strings.TrimSpace(c.Engine.CPUs) == "" {
238247
 		return errors.New("runner config: engine.cpus is required")
239248
 	}
249
+	c.Engine.SeccompProfile = strings.TrimSpace(c.Engine.SeccompProfile)
250
+	if c.Engine.SeccompProfile == "" {
251
+		return errors.New("runner config: engine.seccomp_profile is required")
252
+	}
253
+	c.Engine.User = strings.TrimSpace(c.Engine.User)
254
+	if c.Engine.User == "" {
255
+		return errors.New("runner config: engine.user is required")
256
+	}
257
+	if c.Engine.PidsLimit <= 0 {
258
+		return fmt.Errorf("runner config: engine.pids_limit must be positive, got %d", c.Engine.PidsLimit)
259
+	}
240260
 
241261
 	switch strings.ToLower(c.Log.Level) {
242262
 	case "debug", "info", "warn", "error":
internal/runner/config/config_test.gomodified
@@ -25,6 +25,15 @@ func TestLoad_DefaultsWithToken(t *testing.T) {
2525
 	if cfg.Engine.Kind != "docker" {
2626
 		t.Fatalf("Engine.Kind: %q", cfg.Engine.Kind)
2727
 	}
28
+	if cfg.Engine.SeccompProfile != "/etc/shithubd-runner/seccomp.json" {
29
+		t.Fatalf("Engine.SeccompProfile: %q", cfg.Engine.SeccompProfile)
30
+	}
31
+	if cfg.Engine.User != "65534:65534" {
32
+		t.Fatalf("Engine.User: %q", cfg.Engine.User)
33
+	}
34
+	if cfg.Engine.PidsLimit != 512 {
35
+		t.Fatalf("Engine.PidsLimit: %d", cfg.Engine.PidsLimit)
36
+	}
2837
 	if cfg.Runner.PollInterval != 5*time.Second {
2938
 		t.Fatalf("PollInterval: %v", cfg.Runner.PollInterval)
3039
 	}
@@ -52,6 +61,9 @@ default_image = "file-image"
5261
 network = "none"
5362
 memory = "1g"
5463
 cpus = "1"
64
+seccomp_profile = "/file/seccomp.json"
65
+user = "1000:1000"
66
+pids_limit = 64
5567
 `
5668
 	if err := os.WriteFile(path, []byte(body), 0o600); err != nil {
5769
 		t.Fatalf("WriteFile: %v", err)
@@ -61,6 +73,7 @@ cpus = "1"
6173
 		ConfigPath: path,
6274
 		Environ: []string{
6375
 			"SHITHUB_RUNNER_TOKEN=alias-token",
76
+			"SHITHUB_RUNNER_ENGINE__PIDS_LIMIT=256",
6477
 			"SHITHUB_RUNNER_RUNNER__CAPACITY=3",
6578
 			"SHITHUB_RUNNER_RUNNER__LABELS=self-hosted,linux,x64",
6679
 		},
@@ -68,6 +81,8 @@ cpus = "1"
6881
 			"server.base_url":        "https://flag.example/path/",
6982
 			"runner.capacity":        "4",
7083
 			"runner.poll_interval":   "2s",
84
+			"engine.seccomp_profile": "/flag/seccomp.json",
85
+			"engine.user":            "123:456",
7186
 		},
7287
 	})
7388
 	if err != nil {
@@ -88,6 +103,15 @@ cpus = "1"
88103
 	if want := []string{"self-hosted", "linux", "x64"}; !reflect.DeepEqual(cfg.Runner.Labels, want) {
89104
 		t.Fatalf("Labels: got %#v want %#v", cfg.Runner.Labels, want)
90105
 	}
106
+	if cfg.Engine.SeccompProfile != "/flag/seccomp.json" {
107
+		t.Fatalf("SeccompProfile: %q", cfg.Engine.SeccompProfile)
108
+	}
109
+	if cfg.Engine.User != "123:456" {
110
+		t.Fatalf("User: %q", cfg.Engine.User)
111
+	}
112
+	if cfg.Engine.PidsLimit != 256 {
113
+		t.Fatalf("PidsLimit: %d", cfg.Engine.PidsLimit)
114
+	}
91115
 }
92116
 
93117
 func TestLoad_RequiresToken(t *testing.T) {
@@ -117,3 +141,13 @@ func TestValidate_RejectsBadEngineKind(t *testing.T) {
117141
 		t.Fatal("Validate returned nil error")
118142
 	}
119143
 }
144
+
145
+func TestValidate_RejectsBadPidsLimit(t *testing.T) {
146
+	t.Parallel()
147
+	cfg := Defaults()
148
+	cfg.Runner.Token = "tok"
149
+	cfg.Engine.PidsLimit = 0
150
+	if err := Validate(&cfg); err == nil {
151
+		t.Fatal("Validate returned nil error")
152
+	}
153
+}