-
Notifications
You must be signed in to change notification settings - Fork 1
Marketing + docs site (Vite + React + TypeScript) #11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
5d6d7e9
0c09e80
906cd82
2a8c6e2
50e643a
7976cc4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| name: Deploy site to GitHub Pages | ||
|
|
||
| on: | ||
| push: | ||
| branches: [main] | ||
| paths: ['web/**', '.github/workflows/pages.yml'] | ||
| workflow_dispatch: | ||
|
|
||
| permissions: | ||
| contents: read | ||
| pages: write | ||
| id-token: write | ||
|
|
||
| concurrency: | ||
| group: pages | ||
| cancel-in-progress: true | ||
|
|
||
| jobs: | ||
| build: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: 22 | ||
| cache: npm | ||
| cache-dependency-path: web/package-lock.json | ||
| - run: npm ci | ||
| working-directory: web | ||
| # Project pages are served from /<repo>/, so set the base path. | ||
| - run: VITE_BASE="/forge/" npm run build | ||
| working-directory: web | ||
| - uses: actions/upload-pages-artifact@v3 | ||
| with: | ||
| path: web/dist | ||
|
|
||
| deploy: | ||
| needs: build | ||
| runs-on: ubuntu-latest | ||
| environment: | ||
| name: github-pages | ||
| url: ${{ steps.deploy.outputs.page_url }} | ||
| steps: | ||
| - id: deploy | ||
| uses: actions/deploy-pages@v4 |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,71 @@ | ||||||||||||||||||
| import type { ReviewFinding } from './review.js'; | ||||||||||||||||||
| import type { OctokitLike } from './pr.js'; | ||||||||||||||||||
|
|
||||||||||||||||||
| /** | ||||||||||||||||||
| * Map GitHub Dependabot alerts (live data from the GitHub Advisory Database) to | ||||||||||||||||||
| * our normalized findings. Pure + testable. | ||||||||||||||||||
| */ | ||||||||||||||||||
| /** | ||||||||||||||||||
| * Neutralize Markdown control chars so untrusted API text can't break or inject | ||||||||||||||||||
| * into the rendered comment. Escapes the full set of GFM specials (incl. () # + ! .) | ||||||||||||||||||
| * for defense-in-depth, and flattens newlines. | ||||||||||||||||||
| */ | ||||||||||||||||||
| function md(s: unknown): string { | ||||||||||||||||||
| return String(s ?? '') | ||||||||||||||||||
| .replace(/[\\`*_{}[\]()#+.!~<>|=-]/g, '\\$&') | ||||||||||||||||||
| .replace(/\r?\n/g, ' '); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| export function mapAlertsToFindings(alerts: unknown[]): ReviewFinding[] { | ||||||||||||||||||
| const out: ReviewFinding[] = []; | ||||||||||||||||||
| for (const raw of alerts) { | ||||||||||||||||||
| const a = raw as any; | ||||||||||||||||||
| if (a?.state && a.state !== 'open') continue; | ||||||||||||||||||
| const adv = a?.security_advisory ?? {}; | ||||||||||||||||||
| const vuln = a?.security_vulnerability ?? {}; | ||||||||||||||||||
| const pkg = a?.dependency?.package ?? {}; | ||||||||||||||||||
| const sev = (adv.severity as string)?.toLowerCase(); | ||||||||||||||||||
| const severity: ReviewFinding['severity'] = | ||||||||||||||||||
| sev === 'critical' || sev === 'high' || sev === 'medium' || sev === 'low' ? sev : 'medium'; | ||||||||||||||||||
| const id = adv.cve_id || adv.ghsa_id || 'advisory'; | ||||||||||||||||||
| const patched = vuln?.first_patched_version?.identifier; | ||||||||||||||||||
| out.push({ | ||||||||||||||||||
| file: a?.dependency?.manifest_path || 'dependencies', | ||||||||||||||||||
| startLine: 1, | ||||||||||||||||||
| endLine: 1, | ||||||||||||||||||
| lens: 'security', | ||||||||||||||||||
| severity, | ||||||||||||||||||
| category: `Dependabot: ${id}`, | ||||||||||||||||||
| title: `Vulnerable dependency: ${md(pkg.name) || 'unknown'}`, | ||||||||||||||||||
| body: | ||||||||||||||||||
| `${md(adv.summary) || 'Known vulnerability in a dependency.'}\n\n` + | ||||||||||||||||||
| `Package: \`${md(pkg.name) || '?'}\` (${md(pkg.ecosystem) || '?'}). ` + | ||||||||||||||||||
| `Affected: ${md(vuln?.vulnerable_version_range) || '?'}. ` + | ||||||||||||||||||
| (patched ? `Fixed in **${md(patched)}** — upgrade to it or later.` : 'No patched version yet.') + | ||||||||||||||||||
| (typeof a?.html_url === 'string' ? `\n\n${a.html_url}` : ''), | ||||||||||||||||||
| }); | ||||||||||||||||||
|
Comment on lines
+40
to
+46
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔵 Low · 🔧 Quality · Unsanitized Dependabot API data is used in Markdown report The
Suggested change
|
||||||||||||||||||
| } | ||||||||||||||||||
| return out; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| /** | ||||||||||||||||||
| * Fetch open Dependabot alerts for the repo (best-effort). Returns [] if the | ||||||||||||||||||
| * feature is disabled, not permitted, or unavailable — never throws. | ||||||||||||||||||
| */ | ||||||||||||||||||
| export async function fetchDependabotFindings( | ||||||||||||||||||
| octokit: OctokitLike, | ||||||||||||||||||
| owner: string, | ||||||||||||||||||
| repo: string, | ||||||||||||||||||
| log: (msg: string) => void = () => {}, | ||||||||||||||||||
| ): Promise<ReviewFinding[]> { | ||||||||||||||||||
| try { | ||||||||||||||||||
| if (!octokit.rest.dependabot?.listAlertsForRepo) return []; | ||||||||||||||||||
| const res = await octokit.rest.dependabot.listAlertsForRepo({ owner, repo, state: 'open', per_page: 50 }); | ||||||||||||||||||
| const findings = mapAlertsToFindings(res.data); | ||||||||||||||||||
| if (findings.length) log(`ingested ${findings.length} Dependabot alert(s)`); | ||||||||||||||||||
| return findings; | ||||||||||||||||||
| } catch (err) { | ||||||||||||||||||
| log(`Dependabot alerts unavailable: ${(err as Error).message}`); | ||||||||||||||||||
| return []; | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| import { describe, it, expect } from 'vitest'; | ||
| import { mapAlertsToFindings, fetchDependabotFindings } from '../../src/github/dependabot.js'; | ||
|
|
||
| const alert = { | ||
| state: 'open', | ||
| html_url: 'https://github.com/o/r/security/dependabot/1', | ||
| dependency: { package: { ecosystem: 'npm', name: 'lodash' }, manifest_path: 'package.json' }, | ||
| security_advisory: { ghsa_id: 'GHSA-xxxx', cve_id: 'CVE-2021-23337', summary: 'Command injection in lodash', severity: 'high' }, | ||
| security_vulnerability: { vulnerable_version_range: '< 4.17.21', first_patched_version: { identifier: '4.17.21' } }, | ||
| }; | ||
|
|
||
| describe('mapAlertsToFindings', () => { | ||
| it('maps an open Dependabot alert to a finding', () => { | ||
| const [f] = mapAlertsToFindings([alert]); | ||
| expect(f).toMatchObject({ lens: 'security', severity: 'high', file: 'package.json', category: 'Dependabot: CVE-2021-23337' }); | ||
| expect(f.title).toContain('lodash'); | ||
| // version dots are markdown-escaped (renders as 4.17.21 on GitHub) | ||
| expect(f.body).toContain('4\\.17\\.21'); | ||
| }); | ||
|
|
||
| it('skips non-open alerts and defaults unknown severity to medium', () => { | ||
| expect(mapAlertsToFindings([{ ...alert, state: 'fixed' }])).toHaveLength(0); | ||
| const [f] = mapAlertsToFindings([{ ...alert, security_advisory: { ...alert.security_advisory, severity: 'weird' } }]); | ||
| expect(f.severity).toBe('medium'); | ||
| }); | ||
| }); | ||
|
|
||
| describe('fetchDependabotFindings', () => { | ||
| it('returns [] (no throw) when the endpoint errors or is absent', async () => { | ||
| const octokit: any = { rest: {} }; | ||
| expect(await fetchDependabotFindings(octokit, 'o', 'r')).toEqual([]); | ||
| const erroring: any = { rest: { dependabot: { listAlertsForRepo: async () => { throw new Error('403'); } } } }; | ||
| expect(await fetchDependabotFindings(erroring, 'o', 'r')).toEqual([]); | ||
| }); | ||
|
|
||
| it('returns findings when alerts are available', async () => { | ||
| const octokit: any = { rest: { dependabot: { listAlertsForRepo: async () => ({ data: [alert] }) } } }; | ||
| const findings = await fetchDependabotFindings(octokit, 'o', 'r'); | ||
| expect(findings).toHaveLength(1); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| node_modules | ||
| dist |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| <!doctype html> | ||
| <html lang="en"> | ||
| <head> | ||
| <meta charset="utf-8" /> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1" /> | ||
| <title>ShipIT Forge — autonomous GitHub coding agent</title> | ||
| <meta name="description" content="Autonomous GitHub coding agent: fixes issues, opens PRs, reviews code with a security lens, auto-fixes CI, and audits your repo. Bring your own model." /> | ||
| </head> | ||
| <body> | ||
| <div id="root"></div> | ||
| <script type="module" src="/src/main.tsx"></script> | ||
| </body> | ||
| </html> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔵 Low · 🛡️ Security ·
CWE-79Incomplete Markdown Neutralization
The
md()function is used to sanitize text from the Dependabot API before it's included in a Markdown comment on GitHub. The function's aim is to prevent Markdown injection by escaping special characters. However, the character set it escapes is incomplete. It misses several characters with special meaning in Markdown, such as parentheses(), hash (#), and exclamation mark (!).While current usage doesn't appear to create a vulnerability, if future code changes were to place sanitized text inside a different Markdown structure (e.g., inside a link
[text](url)), this could lead to injection or broken output. A more comprehensive sanitization provides better defense-in-depth.