Skip to content

Commit 2de0810

Browse files
committed
Enforce secure process execution
- add a dependency-free, fail-closed child_process analyzer\n- cover safe and unsafe syntax with deterministic fixtures\n- run the audit for every pull request with telemetry disabled\n- migrate removable process probes to fixed executable argv calls\n\nCo-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent 61a9ec9 commit 2de0810

45 files changed

Lines changed: 1666 additions & 10 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/validate-repository-metadata.yml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@ name: validate-repository-metadata
22

33
on:
44
pull_request:
5-
branches:
6-
- main
75
workflow_dispatch:
86

97
jobs:
108
validate-repository-metadata:
119
name: validate-repository-metadata
1210
runs-on: ubuntu-latest
11+
env:
12+
POWER_PLATFORM_SKILLS_TELEMETRY_POWER_PAGES_OPTOUT: "1"
1313
steps:
1414
- name: checkout
1515
uses: actions/checkout@v4
@@ -30,3 +30,9 @@ jobs:
3030

3131
- name: validate-telemetry-ikeys
3232
run: node scripts/validate-telemetry-ikeys.js
33+
34+
- name: test-secure-process-execution-validator
35+
run: node --test scripts/tests/validate-secure-process-execution.test.js
36+
37+
- name: validate-secure-process-execution
38+
run: node scripts/validate-secure-process-execution.js

plugins/power-pages/AGENTS.md

Lines changed: 1 addition & 0 deletions

plugins/power-pages/scripts/lib/detect-browser.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
// Returns a Playwright channel name ('msedge', 'chrome', 'chromium').
55
// Used by the Playwright MCP launcher and the axe-core audit script.
66

7-
const { execSync } = require('child_process');
7+
const { execFileSync } = require('child_process');
88
const fs = require('fs');
99
const path = require('path');
1010
const os = require('os');
@@ -15,7 +15,9 @@ function exists(filePath) {
1515

1616
function whichExists(cmd) {
1717
try {
18-
execSync(`which ${cmd}`, { stdio: 'ignore' });
18+
// This helper only runs in the Linux branch below. Pass the fixed utility and
19+
// candidate name separately so a future candidate cannot become shell syntax.
20+
execFileSync('which', [cmd], { stdio: 'ignore', shell: false });
1921
return true;
2022
} catch {
2123
return false;

plugins/power-pages/skills/scan-code/scripts/check-tools.js

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/usr/bin/env node
22

3-
const { execSync } = require('child_process');
3+
const { execFileSync } = require('child_process');
44

55
if (process.argv.includes('--help')) {
66
process.stdout.write(`check-tools.js — Detects whether opengrep and trivy are installed.
@@ -23,19 +23,34 @@ Output (stdout, JSON):
2323
process.exit(0);
2424
}
2525

26-
function probe(cmd, parseVersion) {
26+
function probe(runVersion, parseVersion) {
2727
try {
28-
// 60s timeout — first invocation can be slow (cold start, antivirus scan, etc.)
29-
const out = execSync(cmd, { encoding: 'utf8', timeout: 60000, stdio: ['ignore', 'pipe', 'pipe'] });
28+
const out = runVersion();
3029
return { available: true, version: parseVersion(out), error: null };
3130
} catch (err) {
3231
return { available: false, version: null, error: (err.stderr || err.message || '').toString().trim() };
3332
}
3433
}
3534

35+
// Keep executable names at the child_process call sites. Passing an executable
36+
// into probe() would make future CLI/env-derived values indistinguishable from
37+
// this fixed two-tool allowlist to the repository security validator.
38+
const runOpenGrepVersion = () => execFileSync('opengrep', ['--version'], {
39+
encoding: 'utf8',
40+
timeout: 60000,
41+
stdio: ['ignore', 'pipe', 'pipe'],
42+
shell: false,
43+
});
44+
const runTrivyVersion = () => execFileSync('trivy', ['--version'], {
45+
encoding: 'utf8',
46+
timeout: 60000,
47+
stdio: ['ignore', 'pipe', 'pipe'],
48+
shell: false,
49+
});
50+
3651
const result = {
37-
opengrep: probe('opengrep --version', (out) => (out.match(/[\d.]+/) || [null])[0]),
38-
trivy: probe('trivy --version', (out) => {
52+
opengrep: probe(runOpenGrepVersion, (out) => (out.match(/[\d.]+/) || [null])[0]),
53+
trivy: probe(runTrivyVersion, (out) => {
3954
const m = out.match(/Version:\s*([\d.]+)/i) || out.match(/[\d.]+/);
4055
return m ? m[1] || m[0] : null;
4156
}),
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
'use strict';
2+
3+
const { spawn } = require('child_process');
4+
5+
function launch(options) {
6+
spawn('tool', [process.env.INPUT], options);
7+
}
8+
9+
module.exports = { launch };
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
'use strict';
2+
3+
require('child_process')['exec'](process.env.COMMAND);
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
'use strict';
2+
3+
const childProcess = require('child_process');
4+
const method = process.env.METHOD;
5+
6+
childProcess[method]('tool', []);
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { exec as run } from 'child_process';
2+
3+
run('pac pages upload --path ' + process.argv[2]);
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import childProcess from 'node:child_process';
2+
3+
childProcess.exec(process.env.COMMAND);
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
'use strict';
2+
3+
require('node:child_process').exec('tool ' + process.argv[2]);

0 commit comments

Comments
 (0)