| workflow_payload |
name: Runner maintenance access diagnostic (read o name: Runner maintenance access diagnostic (read only)
"on":
push:
branches: [codex/runner-maintenance-diagnostic-20261101]
paths: [.gitea/workflows/runner-maintenance-diagnostic.yml]
workflow_dispatch:
jobs:
observe:
name: Observe existing runner maintenance boundary
runs-on: ubuntu-latest
if: github.repository == 'luoanwu/enterprise-platform'
steps:
- name: Read execution identity and existing service metadata
run: |
set -eu
if ! command -v node >/dev/null 2>&1; then
echo 'RUNNER_MAINTENANCE_READONLY_UNAVAILABLE node-not-installed'
exit 2
fi
node <<'NODE'
const fs = require('node:fs');
const os = require('node:os');
const { spawnSync } = require('node:child_process');
const commands = [];
function run(command, args) {
const allowed = { systemctl: ['list-unit-files', 'show'], docker: ['version', 'info', 'ps', 'inspect'] };
const sudoListOnly = command === 'sudo' && JSON.stringify(args) === '["-n","-l"]';
if (!sudoListOnly && !allowed[command]?.includes(args[0])) throw new Error('READ_ONLY_OPERATION_NOT_ALLOWED');
const r = spawnSync(command, args, { encoding: 'utf8', timeout: 5000, maxBuffer: 256 * 1024, input: '' });
commands.push({ command, args, exitCode: r.status, errorCode: r.error?.code ?? null });
return { ok: r.status === 0, stdout: r.stdout ?? '' };
}
function safePath(value) {
if (typeof value !== 'string' || value.length > 300 || /[\r\n]/.test(value)) return 'UNAVAILABLE';
return /secret|credential|token|password|private/i.test(value) ? 'REDACTED_PRIVATE_PATH' : value;
}
function metadata(path) {
try {
const stat = fs.statSync(path);
const access = mode => { try { fs.accessSync(path, mode); return true; } catch { return false; } };
return { path, exists: true, uid: stat.uid, gid: stat.gid, mode: (stat.mode & 0o777).toString(8),
readable: access(fs.constants.R_OK), writable: access(fs.constants.W_OK),
socket: stat.isSocket(), directory: stat.isDirectory() };
} catch { return { path, exists: false }; }
}
function publicFile(path) {
try { return fs.readFileSync(path, 'utf8').slice(0, 8192); } catch { return null; }
}
const report = {
kind: 'RunnerMaintenanceReadOnlyDiagnostic', schemaVersion: 1, observedAtUTC: new Date().toISOString(),
releaseEligible: false, purpose: 'Execution-boundary and existing maintenance-capability observation only',
ci: { repository: process.env.GITHUB_REPOSITORY ?? null, sourceSha: process.env.GITHUB_SHA ?? null,
runId: process.env.GITHUB_RUN_ID ?? null, rawGithubRunAttempt: process.env.GITHUB_RUN_ATTEMPT ?? null,
rawGiteaRunAttempt: process.env.GITEA_RUN_ATTEMPT ?? null, runnerName: process.env.RUNNER_NAME ?? null },
execution: { hostname: os.hostname(), platform: os.platform(), arch: os.arch(),
uid: process.getuid?.(), gid: process.getgid?.(), groups: process.getgroups?.(),
rootInExecutionContext: process.getuid?.() === 0,
dockerMarker: fs.existsSync('/.dockerenv'), containerEnvMarker: fs.existsSync('/run/.containerenv'),
pid1Command: publicFile('/proc/1/comm')?.trim() ?? null,
uidMap: publicFile('/proc/self/uid_map')?.trim() ?? null },
commonPathMetadataOnly: [
'/var/run/docker.sock', '/run/docker.sock', '/etc/gitea/app.ini',
'/data/gitea/conf/app.ini', '/var/lib/gitea', '/data/gitea', '/data/git/repositories',
'/etc/act_runner/config.yaml', '/etc/gitea-runner/config.yaml',
].map(metadata),
services: [], docker: { accessible: false, relatedContainers: [] },
nonInteractiveSudo: { listingAvailable: false, unrestrictedRuleObserved: false, relevantCommandNames: [] },
};
const osRelease = publicFile('/etc/os-release');
report.execution.osName = osRelease?.match(/^PRETTY_NAME="?([^"\n]+)"?$/m)?.[1] ?? null;
const units = run('systemctl', ['list-unit-files', '--type=service', '--no-legend', '--no-pager']);
if (units.ok) {
const names = units.stdout.split('\n').map(l => l.trim().split(/\s+/)[0])
.filter(n => /^(?:gitea|act[-_]?runner|gitea[-_]?runner)[\w@.-]*\.service$/i.test(n)).slice(0, 20);
for (const name of names) {
const r = run('systemctl', ['show', name, '--no-pager', '--property=LoadState,ActiveState,FragmentPath,User']);
const properties = {};
if (r.ok) for (const line of r.stdout.split('\n')) {
const m = /^(LoadState|ActiveState|FragmentPath|User)=(.*)$/.exec(line);
if (m) properties[m[1]] = m[1] === 'FragmentPath' ? safePath(m[2]) : m[2].slice(0, 150);
}
report.services.push({ name, querySucceeded: r.ok, properties });
}
}
const sudo = run('sudo', ['-n', '-l']);
report.nonInteractiveSudo.listingAvailable = sudo.ok;
if (sudo.ok) {
report.nonInteractiveSudo.unrestrictedRuleObserved = /^\s*\([^)\n]+\)\s*(?:NOPASSWD:\s*)?ALL\s*$/m.test(sudo.stdout);
report.nonInteractiveSudo.relevantCommandNames = ['systemctl','docker','podman','pg_dump','pg_restore','mysqldump','gitea','act_runner','gitea-runner']
.filter(n => sudo.stdout.includes(n));
}
const version = run('docker', ['version', '--format', '{{.Server.Version}}']);
report.docker.accessible = version.ok;
report.docker.serverVersion = version.ok ? version.stdout.trim().slice(0, 80) : null;
if (version.ok) {
const info = run('docker', ['info', '--format', '{{json .Name}}']);
if (info.ok) { try { report.docker.daemonName = JSON.parse(info.stdout); } catch {} }
const list = run('docker', ['ps', '-a', '--format', '{{.ID}}\t{{.Names}}\t{{.Image}}\t{{.Status}}']);
if (list.ok) for (const line of list.stdout.split('\n')) {
const [id, name, image, status] = line.split('\t');
if (!/^[a-f0-9]{12,64}$/.test(id ?? '') || !/gitea|act[-_]?runner|gitea[-_]?runner/i.test((name ?? '') + ' ' + (image ?? ''))) continue;
if (report.docker.relatedContainers.length >= 15) break;
const container = { id, name, image, status };
const mounts = run('docker', ['inspect', '--format', '{{json .Mounts}}', id]);
if (mounts.ok) {
try { container.mounts = JSON.parse(mounts.stdout).map(m => ({
type: m.Type, source: safePath(m.Source), destination: safePath(m.Destination), rw: m.RW,
})); } catch { container.mounts = 'UNPARSEABLE'; }
}
const workingDir = run('docker', ['inspect', '--format', '{{index .Config.Labels "com.docker.compose.project.working_dir"}}', id]);
if (workingDir.ok) container.composeWorkingDirectory = safePath(workingDir.stdout.trim());
const configFiles = run('docker', ['inspect', '--format', '{{index .Config.Labels "com.docker.compose.project.config_files"}}', id]);
if (configFiles.ok) container.composeConfigFiles = safePath(configFiles.stdout.trim());
report.docker.relatedContainers.push(container);
}
}
report.commands = commands;
report.boundaries = {
repositorySecretsRequested: false, environmentDumped: false, secretConfigContentsRead: false,
dockerEnvironmentInspected: false, privilegedServiceCommandExecuted: false, dockerMutationExecuted: false,
credentialOrPermissionChanges: false, arbitraryNetworkProbes: false, backupsOrServiceChanges: false,
contextRootOrDockerAccessIsNotProofOfGiteaHostOwnership: true,
};
report.conclusion = 'DIAGNOSTIC_ONLY: maintenance admission requires verified target, authorized service control, complete storage inventory and tested backup/restore; unknowns are not passes';
console.log('RUNNER_MAINTENANCE_READONLY_JSON ' + JSON.stringify(report));
NODE
shell: bash
timeout-minutes: "4"
permissions:
contents: read
... |