diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..cea5566 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,13 @@ +.env +.git +.github +.gitignore +*.pfx +node_modules +tests +Dockerfile +renovate.json +eslint.config.mjs +copilot-instructions.node_modules.gitlab-ci.yml +.markdownlint.yml +.markdownlintignore \ No newline at end of file diff --git a/.env.example b/.env.example index 1c2d1a0..7b89ef8 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,8 @@ server.port=8080 server.hostname=localhost +server.ssl=false +server.ssl.pfx=localhost.pfx +server.ssl.pfxPassphrase='PFX_PASSPHRASE' logger.transports.console.enabled=true logger.transports.console.level=info logger.transports.amqp.enabled=false diff --git a/.github/workflows/markdownlint.yml b/.github/workflows/lint.yml similarity index 82% rename from .github/workflows/markdownlint.yml rename to .github/workflows/lint.yml index f1803b0..e0f2876 100644 --- a/.github/workflows/markdownlint.yml +++ b/.github/workflows/lint.yml @@ -1,4 +1,4 @@ -name: Markdown Lint +name: Lint on: pull_request: @@ -21,8 +21,8 @@ jobs: - name: Clear npm cache run: npm cache clean --force - - name: Install markdownlint-cli + - name: Install dependencies run: npm install - - name: Run markdownlint + - name: Run lint run: npm run lint \ No newline at end of file diff --git a/.gitignore b/.gitignore index 13dfa36..7405ca3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .env -node_modules/ \ No newline at end of file +node_modules/ +*.pfx \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..dd92979 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,24 @@ +FROM node:lts-bullseye-slim + +ENV NODE_ENV=production +ENV server.port=8080 +ENV server.hostname=localhost +ENV logger.transports.console.enabled=true +ENV logger.transports.console.level=info + +RUN apt-get update +RUN apt-get install -y --no-install-recommends dumb-init + +EXPOSE 8080 + +WORKDIR /usr/src/app + +COPY --chown=node:node . . + +RUN npm install --omit=dev + +USER node + +HEALTHCHECK CMD curl http://localhost:8080/health || exit 1 + +ENTRYPOINT ["dumb-init", "node", "server.js"] \ No newline at end of file diff --git a/README.md b/README.md index 8601f12..fd15d6e 100644 --- a/README.md +++ b/README.md @@ -2,53 +2,37 @@ [![Verified on MseeP](https://mseep.ai/badge.svg)](https://mseep.ai/app/1a935343-666d-457a-b210-2e0d27e9ef81) -A customizable `.github/copilot-instructions.md` file that guides **GitHub Copilot** toward **secure coding defaults** across **Java, Node.js, and C#**. +A comprehensive toolkit to guide **GitHub Copilot** toward **secure coding practices**. This project includes customizable instructions and security-focused prompts to help development teams identify and mitigate security risks effectively. -Designed for security-conscious development teams, this config helps Copilot suggest safer code patterns, avoid common vulnerabilities, and reinforce good practices โ€” without slowing down your workflow. +Designed for security-conscious teams, this configuration ensures Copilot suggests safer code patterns, avoids common vulnerabilities, and reinforces best practices โ€” all without disrupting your workflow. --- ## ๐Ÿ” What's Inside -This Copilot configuration includes: +This project offers: -- **Secure-by-default guidance** for all languages (input validation, secret handling, safe logging) +- **Secure-by-default guidance** for all languages (e.g., input validation, secret handling, safe logging). - **Language-specific secure patterns**: - โ˜• Java - ๐ŸŸฉ Node.js - ๐ŸŸฆ C# - ๐Ÿ Python -- **"Do Not Suggest" lists** to block risky Copilot completions (e.g. `eval`, inline SQL, insecure deserialization) -- **AI hallucination protections** (package spoofing, non-existent APIs, misinformation risks) -- **Mentorship-style dev tips** to help newer engineers build safe habits over time -- **An MCP server** to make using these prompts in other projects easier +- **"Do Not Suggest" lists** to block risky Copilot completions (e.g., `eval`, inline SQL, insecure deserialization). +- **AI hallucination protections** to prevent package spoofing, non-existent APIs, and misinformation risks. +- **Mentorship-style tips** to help newer engineers build secure coding habits. +- **An MCP server** for seamless integration of these prompts into other projects. --- -## ๐Ÿง  Using Prompts for Code Reviews (Copilot Chat) - -If your organization has [Copilot Prompt Customization](https://code.visualstudio.com/docs/copilot/copilot-customization#_prompt-files-experimental) enabled, you can guide Copilot Chat to run secure code audits using the included prompt files. - -1. Open any file in your IDE (e.g., `tests/secret-hardcode.js`) -2. Open the Copilot Chat sidebar -3. Type: - -```bash -@prompt .github/prompts/check-for-secrets.md -``` - -Copilot will scan the file using the selected prompt and return flagged issues, reasoning, and remediation tips. - -> โ„น๏ธ Note: If your org disables `chat.promptFiles`, you can manually paste the prompt contents into Copilot Chat or use them in PRs, checklists, and reviews. - ## ๐Ÿ—‚๏ธ Prompt Catalogue -Explore the available prompt files and their intended purpose: +Explore the available prompts and their intended use cases: | Prompt | Description | Intended Use | | --- | --- | --- | | [assess-logging.prompt.md](prompts/assess-logging.prompt.md) | Identify unsafe logging and exposure of sensitive data. | Audit log output for leaks and recommend safer patterns. | -| [business-logic-review.prompt.md](prompts/business-logic-review.prompt.md) | Analyze overall business logic flow and decision making. | Map application behaviour and critique critical logic paths. | +| [business-logic-review.prompt.md](prompts/business-logic-review.prompt.md) | Analyze overall business logic flow and decision making. | Map application behavior and critique critical logic paths. | | [check-access-controls.prompt.md](prompts/check-access-controls.prompt.md) | Audit authorization and access control weaknesses. | Ensure RBAC/ABAC enforcement and consistent permission checks. | | [check-for-secrets.prompt.md](prompts/check-for-secrets.prompt.md) | Detect hardcoded secrets and credentials. | Locate embedded keys or tokens and suggest secure storage. | | [check-for-unvalidated-genai-acceptances.prompt.md](prompts/check-for-unvalidated-genai-acceptances.prompt.md) | Find unvalidated AI-generated code or hallucinated assets. | Verify that AI suggestions are real, tested, and documented. | @@ -57,30 +41,11 @@ Explore the available prompt files and their intended purpose: | [secure-code-review.prompt.md](prompts/secure-code-review.prompt.md) | Perform a comprehensive security review of the codebase. | Conduct an end-to-end audit for security issues. | | [validate-input-handling.prompt.md](prompts/validate-input-handling.prompt.md) | Check for missing or unsafe input validation. | Evaluate request handling for validation and sanitization gaps. | -## ๐Ÿงช Testing the Prompts - -The `tests/` folder contains small, focused files designed to trigger specific security prompts: - -| File | Targets | -|--------------------------------|-----------------------------------| -| `secret-hardcode.js` | check-for-secrets.md | -| `unvalidated-input.java` | validate-input-handling.md | -| `insecure-api.cs` | scan-for-insecure-apis.md | -| `logs-sensitive-data.go` | assess-logging.md | -| `weak-auth-flow.ts` | review-auth-flows.md | -| `overtrusted-genai-snippet.js` | unvalidated-genai-acceptances.md | - -To run a test: - -1. Open a file in `tests/` -2. Run the related prompt in Copilot Chat -3. Review and refine based on Copilotโ€™s feedback - --- ## ๐Ÿ“ฆ How to Use in a Real Project -### Static Files +### Leveraging Static Files 1. Copy the `copilot-instructions.md` file into your repo under: `.github/copilot-instructions.md` @@ -88,52 +53,81 @@ To run a test: 2. Drop the prompts you want into: `.github/prompts/` -3. Use prompt-driven reviews in Copilot Chat during coding, PRs, or audits +3. Open the prompt you wish to run within your IDE -### Leveraging the included MCP Server +4. Click the `Run Prompt` button to the top-right of the file -The MCP server is designed to simplify the integration of secure coding prompts into your development workflow. Follow these steps to ensure a smooth experience: + ![Run Prompt Button](images/example-run_prompt.png) -#### 1. Setting Up the MCP Server + > โ„น๏ธ **Note**: If you don't see the run prompt button; check to make sure the `Chat: Prompt Files` functionality is enabled in your settings + > ![Chat Prompt Files Setting](images/example-chat_prompt_files.png) -```bash -npm install -cp .env.example .env -npm start -``` +### Leveraging the MCP Server -- **`npm install`**: Installs all required dependencies. -- **`cp .env.example .env`**: Creates a `.env` file for configuration. Update it with your specific settings. -- **`npm start`**: Launches the MCP server on `http://localhost:8080/mcp`. +The MCP server simplifies the integration of secure coding prompts into your workflow. Follow these steps: -#### Environment Variables +#### Run MCP from source -The MCP server reads configuration from a `.env` file. The following variables can be set: +1. Install dependencies -| Variable | Description | Default | Required | -| --- | --- | --- | --- | -| `server.port` | Port the MCP server listens on. | `8080` | Optional | -| `server.hostname` | Hostname the server binds to. | `localhost` | Optional | -| `logger.transports.console.enabled` | Enable console logging output. | `false` | Optional | -| `logger.transports.console.level` | Log level for console output. | `info` | Optional | -| `logger.transports.amqp.enabled` | Enable AMQP-based logging. | `false` | Optional | -| `logger.transports.amqp.level` | Log level for AMQP transport. | `http` | Optional | -| `logger.transports.amqp.hostname` | Hostname of the AMQP broker. | `localhost` | Optional | -| `logger.transports.amqp.port` | Port for the AMQP broker. | `5672` | Optional | -| `logger.transports.amqp.username` | Username for AMQP authentication. | `guest` | Optional | -| `logger.transports.amqp.password` | Password for AMQP authentication. | `guest` | Optional | -| `logger.transports.amqp.exchange` | Exchange name used for AMQP logging. | `logs` | Optional | -| `logger.transports.amqp.vhost` | Virtual host for AMQP logging. | `/logs` | Optional | -| `logger.transports.amqp.heartbeat` | Heartbeat interval in seconds. | `60` | Optional | -| `logger.transports.amqp.locale` | Locale for the AMQP connection. | `en_US` | Optional | -| `logger.transports.amqp.type` | AMQP exchange type. | `direct` | Optional | -| `logger.transports.amqp.durable` | Whether the AMQP exchange is durable. | `false` | Optional | + ```bash + npm install + ``` -All variables are optional; the server runs with these defaults. Set them in `.env` to customize behavior or enable logging transports. +2. Setup environment -#### 2. Configuring VSCode for MCP + ```bash + cp .env.example .env + ``` + + > The MCP server reads configuration from a `.env` file. Customize the following variables as needed: + > + > | Variable | Description | Default | + > | --- | --- | --- | + > | `server.port` | Port the MCP server listens on. | `8080` | + > | `server.ssl`| Whether to use ssl for express server | `false` | + > | `server.ssl.pfx` | Path to pfx file | `localhost.pfx` | + > | `server.ssl.pfx.passphrase` | Passphrase for pfx file | `PFX_PASSPHRASE` | + > | `server.hostname` | Hostname the server binds to. | `localhost` | + > | `logger.transports.console.enabled` | Enable console logging output. | `false` | + > | `logger.transports.console.level` | Log level for console output. | `info` | + > | `logger.transports.amqp.enabled` | Enable AMQP-based logging. | `false` | + > | `logger.transports.amqp.level` | Log level for AMQP transport. | `http` | + > | `logger.transports.amqp.hostname` | Hostname of the AMQP broker. | `localhost` | + > | `logger.transports.amqp.port` | Port for the AMQP broker. | `5672` | + > | `logger.transports.amqp.username` | Username for AMQP authentication. | `guest` | + > | `logger.transports.amqp.password` | Password for AMQP authentication. | `guest` | + > | `logger.transports.amqp.exchange` | Exchange name used for AMQP logging. | `logs` | + > | `logger.transports.amqp.vhost` | Virtual host for AMQP logging. | `/logs` | + > | `logger.transports.amqp.heartbeat` | Heartbeat interval in seconds. | `60` | + > | `logger.transports.amqp.locale` | Locale for the AMQP connection. | `en_US` | + > | `logger.transports.amqp.type` | AMQP exchange type. | `direct` | + > | `logger.transports.amqp.durable` | Whether the AMQP exchange is durable. | `false` | + +3. Start the server + + ```bash + npm start + ``` + +#### Run MCP in Docker + +1. Build docker container + + ```bash + docker build -t copilot-security-mcp . + ``` + +2. Run docker container + + ```bash + docker run -d -p 8080:8080 copilot-security-mcp + ``` + +#### Configuring VSCode for MCP 1. Open VSCode and run the `MCP: Open User Configuration` command. + 2. Add the following JSON configuration: ```json @@ -147,16 +141,18 @@ All variables are optional; the server runs with these defaults. Set them in `.e ``` 3. Save the configuration. + 4. Navigate to the Extensions menu in VSCode. + 5. Locate the `copilot-instructions-mcp` server, click the settings cog, and select `start server`. -#### 3. Using MCP with GitHub Copilot +#### Using MCP with GitHub Copilot + +1. Open GitHub Copilot Chat. -- Open GitHub Copilot Chat. -- Ask it to run any of the prompts against your repository or specific files. -- Example: `Please request and run the secure code review prompt using the MCP server.` +2. Ask it to run any of the prompts against your repository or specific files. -This setup ensures developers can easily leverage the MCP server to enhance their secure coding practices. + **Example:** `Please get and run the secure code review prompt.` --- @@ -180,7 +176,7 @@ Use these npm scripts to work on the project: | `npm run lint` | Runs ESLint and Markdownlint to verify code and docs. | | `npm run lint:fix` | Attempts to automatically fix linting issues. | -**Recommended workflow:** run `npm run lint` (and `npm run lint:fix` if needed) before committing or opening a PR. +**Recommended workflow:** Run `npm run lint` (and `npm run lint:fix` if needed) before committing or opening a PR. --- diff --git a/images/example-chat_prompt_files.png b/images/example-chat_prompt_files.png new file mode 100644 index 0000000..ab0879e Binary files /dev/null and b/images/example-chat_prompt_files.png differ diff --git a/images/example-run_prompt.png b/images/example-run_prompt.png new file mode 100644 index 0000000..819c9cc Binary files /dev/null and b/images/example-run_prompt.png differ diff --git a/server.js b/server.js index f86f5ad..03b7c9e 100644 --- a/server.js +++ b/server.js @@ -1,26 +1,66 @@ +import fs from 'fs'; +import path from 'path'; +import https from 'https'; import { config, logger } from 'copilot-instructions-mcp/core'; import app from './src/express_app.js'; -app.listen(config.server.port, (error) => { - if (error) { - logger.error('Error starting server', { - source: 'server.listenHTTP', - details: { - port: config.server.port, - hostname: config.server.hostname, - }, - error: { - message: error.message || 'Unknown error', - stack: error.stack || 'No stack trace available', - }, - }); - } else { - logger.info(`Server listening on port ${config.server.port}`, { - source: 'server.listenHTTP', - details: { - port: config.server.port, - hostname: config.server.hostname, - }, - }); - } -}); +function listenHTTP() { + app.listen(config.server.port, (error) => { + if (error) { + logger.error('Error starting server', { + source: 'server.listenHTTP', + details: { + port: config.server.port, + hostname: config.server.hostname, + }, + error: { + message: error.message || 'Unknown error', + stack: error.stack || 'No stack trace available', + }, + }); + } else { + logger.info(`Server listening on port ${config.server.port}`, { + source: 'server.listenHTTP', + details: { + port: config.server.port, + hostname: config.server.hostname, + }, + }); + } + }); +} + +function listenHTTPS() { + https.createServer({ + pfx: fs.readFileSync(path.resolve(config.server['ssl.pfx'])), + passphrase: config.server['ssl.pfx.passphrase'], + }, app).listen(config.server.port, (error) => { + if (error) { + logger.error('Error starting server', { + source: 'server.listenHTTPS', + details: { + port: config.server.port, + hostname: config.server.hostname, + }, + error: { + message: error.message || 'Unknown error', + stack: error.stack || 'No stack trace available', + }, + }); + } else { + logger.info(`Server listening on port ${config.server.port}`, { + source: 'server.listenHTTPS', + details: { + port: config.server.port, + hostname: config.server.hostname, + }, + }); + } + }); +} + +if (config.server.ssl) { + listenHTTPS(); +} else { + listenHTTP(); +} diff --git a/src/core/config.js b/src/core/config.js index e590ad0..8f84c7b 100644 --- a/src/core/config.js +++ b/src/core/config.js @@ -5,6 +5,9 @@ dotenv.config({ quiet: true }); const server = { port: process.env['server.port'] ? parseInt(process.env['server.port'], 10) : 8080, hostname: process.env['server.hostname'] || 'localhost', + ssl: process.env['server.ssl'] === 'true' || false, + 'ssl.pfx': process.env['server.ssl.pfx'] || 'localhost.pfx', + 'ssl.pfx.passphrase': process.env['server.ssl.pfx.passphrase'] || 'PFX_PASSPHRASE', }; const logger = { diff --git a/src/express_app.js b/src/express_app.js index 47dfa51..e46cf20 100644 --- a/src/express_app.js +++ b/src/express_app.js @@ -17,6 +17,8 @@ app.get('/', (req, res) => { return res.status(200).send(homeHTML); }); +app.get('/health', (req, res) => res.status(200).send('OK')); + app.get('/mcp', (req, res) => { // TODO: Log request details /** TODO: Implement MCP Server GET Specification diff --git a/src/mcp_server.js b/src/mcp_server.js index 8bd0b47..619f215 100644 --- a/src/mcp_server.js +++ b/src/mcp_server.js @@ -7,10 +7,13 @@ import mcpTools from 'copilot-instructions-mcp/mcp_tools'; function makeMCPServer() { const server = new McpServer({ - name: 'copilot-instructions-mcp', + name: 'copilot-security-mcp', version: '1.0.0', + title: 'Copilot Security Instructions MCP', + }); + // Register Tools mcpTools.list_resources(server); mcpTools.list_prompts(server); mcpTools.get_prompt(server);