Skip to content

Commit 5132913

Browse files
authored
Merge pull request #88 from Automattic/add/codeql
ci: add CodeQL Scan workflow
2 parents 75d26a2 + a3f9c10 commit 5132913

File tree

12 files changed

+112
-23
lines changed

12 files changed

+112
-23
lines changed

.github/workflows/codeql.yml

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
name: CodeQL
2+
3+
on:
4+
push:
5+
branches:
6+
- trunk
7+
pull_request:
8+
branches:
9+
- trunk
10+
schedule:
11+
- cron: '24 5 * * 2'
12+
13+
jobs:
14+
analyze:
15+
name: Analyze
16+
runs-on: ubuntu-latest
17+
timeout-minutes: 360
18+
permissions:
19+
security-events: write
20+
actions: read
21+
contents: read
22+
strategy:
23+
fail-fast: false
24+
matrix:
25+
language:
26+
- javascript-typescript
27+
- actions
28+
steps:
29+
- name: Checkout repository
30+
uses: actions/checkout@v4
31+
32+
- name: Initialize CodeQL
33+
uses: github/codeql-action/init@v3
34+
with:
35+
languages: ${{ matrix.language }}
36+
queries: security-and-quality
37+
38+
- name: Perform CodeQL Analysis
39+
uses: github/codeql-action/analyze@v3
40+
with:
41+
category: '/language:${{matrix.language}}'

.github/workflows/lint.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,14 @@ concurrency:
1414
group: ${{ github.workflow }}-${{ github.ref }}
1515
cancel-in-progress: true
1616

17+
permissions:
18+
contents: read
19+
1720
jobs:
1821
test:
1922
runs-on: ubuntu-latest
23+
permissions:
24+
contents: read
2025

2126
steps:
2227
- name: Checkout

.github/workflows/npm-prepare-release.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,22 @@ on:
1414
- minor
1515
- major
1616

17+
permissions:
18+
contents: read
19+
1720
jobs:
1821
prepare:
1922
name: Prepare a new npm release
23+
permissions:
24+
contents: write
25+
pull-requests: write
2026
runs-on: ubuntu-latest
2127
steps:
2228
- name: Check out the source code
2329
uses: actions/checkout@v4
2430

2531
- name: Run npm-prepare-release
26-
uses: Automattic/vip-actions/npm-prepare-release@v0.1.2
32+
uses: Automattic/vip-actions/npm-prepare-release@1137b91acf0f5ea4e0db044bcf14ceabed9b068f # trunk
2733
with:
2834
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
2935
npm-version-type: ${{ inputs.npm-version-type }}

.github/workflows/npm-publish.yml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,21 @@ on:
44
pull_request:
55
types: [closed]
66

7+
permissions:
8+
contents: read
9+
710
jobs:
811
publish:
912
name: Publish to npm
1013
runs-on: ubuntu-latest
1114
if: contains( github.event.pull_request.labels.*.name, '[ Type ] NPM version update' ) && startsWith( github.head_ref, 'release/') && github.event.pull_request.merged == true
15+
permissions:
16+
contents: write
17+
id-token: write
18+
pull-requests: write
1219
steps:
13-
- uses: Automattic/vip-actions/npm-publish@v0.1.2
20+
- uses: Automattic/vip-actions/npm-publish@1137b91acf0f5ea4e0db044bcf14ceabed9b068f # trunk
1421
with:
1522
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
1623
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
24+
PROVENANCE: 'true'

.github/workflows/stale.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ on:
44
schedule:
55
- cron: '0 0 * * *'
66

7+
permissions:
8+
contents: read
9+
710
jobs:
811
stale:
912
name: Stale
@@ -12,4 +15,4 @@ jobs:
1215
issues: write
1316
pull-requests: write
1417
steps:
15-
- uses: Automattic/vip-actions/stale@trunk
18+
- uses: Automattic/vip-actions/stale@1137b91acf0f5ea4e0db044bcf14ceabed9b068f # trunk

.github/workflows/test.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,14 @@ concurrency:
1414
group: ${{ github.workflow }}-${{ github.ref }}
1515
cancel-in-progress: true
1616

17+
permissions:
18+
contents: read
19+
1720
jobs:
1821
test:
1922
runs-on: ubuntu-latest
23+
permissions:
24+
contents: read
2025

2126
strategy:
2227
matrix:

src/cli.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -430,7 +430,7 @@ harmonia.on( 'issue', ( issue: Issue ) => {
430430
const documentation = issue.documentation ? `(${ issue.documentation })` : '';
431431

432432
// Replace \n with \n\t\t to keep new lines aligned
433-
const message = issue.message.replace( '\n', '\n\t\t' );
433+
const message = issue.message.replace( /\n/g, '\n\t\t' );
434434
logToConsole( ` ${ issueTypeString } \t${ message } ${ documentation }` );
435435

436436
// If it's a Blocker or Error, and the issue includes a stdout, print it out.

src/tests/docker/build.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import path from 'path';
22
import Test from '../../lib/tests/test';
33
import chalk from 'chalk';
4-
import { executeShell, executeShellSync } from '../../utils/shell';
4+
import { escapeShellArg, executeShell, executeShellSync } from '../../utils/shell';
55
import Harmonia from '../../harmonia';
66

77
export default class DockerBuild extends Test {
@@ -70,7 +70,7 @@ export default class DockerBuild extends Test {
7070
*/
7171
private async buildApp(): Promise<string> {
7272
const script = path.resolve( __dirname, '../../..', 'scripts/build-app/build.sh' );
73-
const subprocess = executeShell( `bash ${ script }`, {
73+
const subprocess = executeShell( `bash ${ escapeShellArg( script ) }`, {
7474
...this.envVariables,
7575
NODE_VERSION: this.nodeVersion,
7676
} );
@@ -97,7 +97,7 @@ export default class DockerBuild extends Test {
9797
this.notice( `Using a data-only image ${ chalk.yellow( dataImage ) } ` );
9898

9999
const script = path.resolve( __dirname, '../../..', 'scripts/data-only/build.sh' );
100-
const subprocess = executeShell( `bash ${ script }`, {
100+
const subprocess = executeShell( `bash ${ escapeShellArg( script ) }`, {
101101
NODE_VERSION: this.nodeVersion,
102102
DATAONLY_IMAGE: dataImage,
103103
} );
@@ -123,7 +123,7 @@ export default class DockerBuild extends Test {
123123
*/
124124
private getDockerImage( dockerImage: string ): string | boolean {
125125
try {
126-
const subprocess = executeShellSync( `docker images --filter reference=${ dockerImage } --format {{.Repository}}:{{.Tag}}` );
126+
const subprocess = executeShellSync( `docker images --filter reference=${ escapeShellArg( dockerImage ) } --format {{.Repository}}:{{.Tag}}` );
127127

128128
if ( subprocess.stdout === '' || subprocess.exitCode !== 0 ) {
129129
return false;

src/tests/docker/healthcheck.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import chalk from 'chalk';
22

33
import Test from '../../lib/tests/test';
4-
import { executeShell } from '../../utils/shell';
4+
import { escapeShellArg, executeShell } from '../../utils/shell';
55
import fetchWithTiming, { HarmoniaFetchError } from '../../utils/http';
66

77
const CACHE_HEALTHCHECK_ROUTE = '/cache-healthcheck?';
@@ -57,7 +57,7 @@ export default class HealthcheckTest extends Test {
5757

5858
let logs;
5959
try {
60-
const subprocess = await executeShell( `docker logs ${ this.containerName } --since ${ this.startDate }` );
60+
const subprocess = await executeShell( `docker logs ${ escapeShellArg( this.containerName ) } --since ${ escapeShellArg( this.startDate ) }` );
6161
logs = subprocess.all;
6262
} catch ( err ) {
6363
this.log( 'Error getting docker logs: ' + ( err as Error ).message );

src/tests/docker/run.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import Test from '../../lib/tests/test';
22
import chalk from 'chalk';
3-
import { executeShell } from '../../utils/shell';
3+
import { escapeShellArg, executeShell } from '../../utils/shell';
44
import { createHash } from 'crypto';
55
import { wait } from '../../utils/wait';
66
import Harmonia from '../../harmonia';
@@ -51,7 +51,7 @@ export default class DockerRun extends Test {
5151

5252
// Build the `--env` string of options for Docker with the environment variable keys
5353
const environmentVarDockerOption = Object.keys( environmentVars ).reduce( ( string, envVarName ) => {
54-
return `${ string } -e ${ envVarName }`;
54+
return `${ string } -e ${ escapeShellArg( envVarName ) }`;
5555
}, '' );
5656

5757
let dockerNetwork = `-p ${ this.port }:${ this.port }`;
@@ -60,8 +60,8 @@ export default class DockerRun extends Test {
6060
dockerNetwork = '--network host';
6161
}
6262

63-
const dockerCommand = `docker run -t ${ dockerNetwork } --name ${ this.containerName } ${ environmentVarDockerOption } ${ this.imageTag }`;
64-
const subprocess = executeShell( dockerCommand, environmentVars );
63+
const dockerCommand = `docker run -t ${ dockerNetwork } --name ${ escapeShellArg( this.containerName ) } ${ environmentVarDockerOption } ${ escapeShellArg( this.imageTag ) }`;
64+
const subprocess = executeShell( dockerCommand, environmentVars );
6565

6666
if ( Harmonia.isVerbose() ) {
6767
subprocess.stdout?.pipe( process.stdout );

src/tests/health/base.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import chalk from 'chalk';
22

33
import Test from '../../lib/tests/test';
4-
import { executeShell } from '../../utils/shell';
4+
import { escapeShellArg, executeShell } from '../../utils/shell';
55
import fetchWithTiming, { HarmoniaFetchError, TimedResponse } from '../../utils/http';
66
import Issue from '../../lib/issue';
77
import { isWebUri } from '../../utils/url';
@@ -49,8 +49,8 @@ export default abstract class BaseHealthTest extends Test {
4949
} catch ( error ) {
5050
if ( error instanceof HarmoniaFetchError ) {
5151
// Get logs
52-
const subprocess = await executeShell( `docker logs ${ this.containerName } --since ${ error.getStartDate().toISOString() } ` +
53-
`--until ${ error.getEndDate().toISOString() }` );
52+
const subprocess = await executeShell( `docker logs ${ escapeShellArg( this.containerName ) } --since ${ escapeShellArg( error.getStartDate().toISOString() ) } ` +
53+
`--until ${ escapeShellArg( error.getEndDate().toISOString() ) }` );
5454
const logs = subprocess.all;
5555

5656
this.error( `Error fetching ${ error.getURL() }: ${ error.message }`, undefined, { all: logs } );
@@ -93,7 +93,7 @@ export default abstract class BaseHealthTest extends Test {
9393
// Check for logs
9494
let logs;
9595
try {
96-
const subprocess = await executeShell( `docker logs ${ this.containerName } --since ${ request.startDate.toISOString() }` );
96+
const subprocess = await executeShell( `docker logs ${ escapeShellArg( this.containerName ) } --since ${ escapeShellArg( request.startDate.toISOString() ) }` );
9797
logs = subprocess.all;
9898
} catch ( err ) {
9999
this.log( 'Error getting docker logs: ' + ( err as Error ).message );

src/utils/shell.ts

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,38 @@
1-
import execa, { ExecaChildProcess } from 'execa';
1+
import { platform } from 'node:os';
2+
import execa, { type ExecaChildProcess } from 'execa';
23

34
const subprocesses: ExecaChildProcess[] = [];
45
let cwd = process.cwd();
56

6-
export function executeShell( command, envVars = {} ) {
7+
export function escapeShellArg( arg: string ): string {
8+
if ( ! arg ) {
9+
return '""';
10+
}
11+
12+
if ( platform() === 'win32' ) {
13+
// For Windows cmd.exe, we need to handle several special characters
14+
// First handle backslashes and double quotes
15+
let escaped = arg.replace( /(\\*)"/g, '$1$1\\"' ).replace( /(\\*)$/, '$1$1' );
16+
17+
// Then handle special shell characters: ^ ! % ~ & < > | ' `
18+
escaped = escaped.replace( /([&^|<>()!"%~])/g, '^$1' );
19+
20+
return `"${ escaped }"`;
21+
}
22+
23+
// Unix/Linux/macOS: single quotes around the string and escape single quotes within
24+
return `'${ arg.replace( /'/g, "'\\''" ) }'`;
25+
}
26+
27+
export function executeShell( command: string, envVars = {} ) {
728
const envVariables = {
829
VIP_GO_APP_ID: 'unknown',
930
};
1031

1132
const promise = execa.command( command, {
1233
all: true,
1334
cwd,
14-
env: Object.assign( {}, envVariables, envVars ),
35+
env: { ...envVariables, ...envVars },
1536
} );
1637

1738
subprocesses.push( promise );
@@ -32,15 +53,15 @@ export function executeShell( command, envVars = {} ) {
3253
return promise;
3354
}
3455

35-
export function executeShellSync( command, envVars = {} ) {
56+
export function executeShellSync( command: string, envVars = {} ) {
3657
const envVariables = {
3758
VIP_GO_APP_ID: 'unknown',
3859
};
3960

4061
return execa.commandSync( command, {
4162
all: true,
4263
cwd,
43-
env: Object.assign( {}, envVariables, envVars ),
64+
env: { ...envVariables, ...envVars },
4465
} );
4566
}
4667

0 commit comments

Comments
 (0)