Repository navigation
Add a GitHub Action to render the graph as an SVG, and fix token logging, SVG injection and vulnerable dependencies - #274
Open
satishjhanwer wants to merge 8 commits into
Conversation
Axios errors carry the request config, including the Authorization header with the token, so logging the whole error object can print the token into logs.
Adds src/cli.ts, which reads the same options as the /graph endpoint from ACTIVITY_* environment variables, fetches the contributions and writes the SVG to output_path. It exits with an error instead of writing the 'invalid user' SVG, so a failed run is not committed as a graph. action.yml wraps the CLI as a composite action. Inputs are passed through env rather than interpolated into the shell script. The README documents the action with an example workflow.
The catch block logged anything that wasn't an Error as-is, so a thrown object carrying the request config would print the Authorization header. Log a fixed message for those instead, and add a regression test that a failed request never puts the token in the logs.
- node.js.yml: pass secrets.TOKEN to npm test through env instead of echoing it into a .env file from an interpolated script, and limit the workflow's GITHUB_TOKEN to contents: read. - action.yml: install dependencies with --ignore-scripts. No dependency needs install scripts, and skipping them keeps third-party code from running in the job that holds the token.
- The title (custom_title, or the user's GitHub display name) was inserted into the SVG unescaped, so a crafted link could make the served SVG run script when opened directly. Escape it, and the invalid-user message too. - Color options were written into the <style> block as #<value> without checks, so a value could close the style tag. Only 3-8 digit hex codes are accepted now; anything else falls back to the theme color, as an empty value already did.
Updates to the latest versions, except the TypeScript 7 and ESLint 10 majors (axios 1.21.0, express 5.3.0, dotenv 18.0.9, moment 2.31.0, jest 30.5.2, ...). The lockfile is regenerated and installs with both npm 10 and npm 11.
- Override form-data under request to 2.5.6 (CVE-2025-7783). It comes in through node-chartist -> jsdom 9 -> request, which pins the vulnerable 2.3.x. - Apply the non-breaking npm audit fixes (path-to-regexp under express, brace-expansion, js-yaml, picomatch, flatted, browserslist). - Move nodemon to devDependencies. It is only used by the dev:start script and kept its vulnerable dependencies in the production install.
This was referenced Nov 1, 2026
Open
node-chartist was last published in 2017 and pulled in jsdom 9, request, @panosoft/ramda-utils and is_js, which has an unfixed ReDoS (CVE-2020-26302). createChart.ts already reached into its internals, so its ~100 lines of glue now live there: load Chartist 0.11.4 (the same version) into a jsdom 26 window, render, add the axis titles. The chart output is byte-identical; the existing chart snapshots pass unchanged. - npm audit --omit=dev now reports 0 vulnerabilities, and the production install drops from 259 to 163 packages. - The form-data override is removed, since request is no longer in the tree. - jsdom 26 is the newest line Jest can load; jsdom 27+ depends on ESM-only packages. - createGraph now returns Promise<string> instead of any, which exposed GraphArgs.line typed as Promise<string>. The test fixture passed a real Promise, so its snapshot contained '[object Promise]'; both are corrected.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR adds a GitHub Action that renders the activity graph to an SVG file inside the user's own workflow, so profiles don't depend on the shared Vercel instance and its rate limit. While building it I also fixed a few security issues: token exposure in logs and CI, plus markup/CSS injection in the rendered SVG. It also updates the dependencies and replaces the unmaintained
node-chartist, which leaves the production dependency tree with no known vulnerabilities.1. GitHub Action
The project only runs as an Express server today, so the only ways to use it are the shared deployment or self-hosting. The graph code itself doesn't depend on Express, so it can run directly in a workflow.
src/cli.ts: reads the same options as/graphfromACTIVITY_*environment variables, fetches the contributions and writes the SVG tooutput_path. It exits with an error instead of writing the "invalid user" SVG, so a failed run doesn't get committed as if it were a graph.action.yml: composite action that sets up Node 22, installs the production dependencies and runs the CLI. Every option from Common Options is an input with the same name. Inputs are passed throughenv, never interpolated into the shell script.Example usage:
The server, the
/graphendpoint and the Vercel deployment are unchanged.2. Keeping the token out of logs and CI
Fetcher: thecatchblock logged the raw error. Axios attaches the request config, includingAuthorization: bearer <token>, to thrown errors, so the token could be printed into server or Actions logs. Errors are now logged by message only, and values that aren'tErrorobjects are not logged at all.node.js.ymlinterpolatedsecrets.TOKENinto a shell script and wrote it to a.envfile on disk. It's now passed tonpm testthroughenv, and the workflow'sGITHUB_TOKENis limited tocontents: read.--omit=dev --ignore-scripts. No production dependency has an install script (the only one in the tree isfsevents, a macOS-only optional dev dependency), and skipping them keeps third-party code from running in the job that holds the token.3. Markup and CSS injection in the rendered SVG
Both issues affect the public
/graphendpoint:custom_title, or the user's GitHub display name when no title is given, was inserted into the SVG unescaped. A crafted link such as?custom_title=<script>...produced an SVG that runs script when opened directly. The title, and the invalid-user message, are now XML-escaped.bg_color,color,title_color,line,point,area_colorandborder_colorwere written into the<style>block as#<value>without validation, so a value likefff}</style><script>...could escape the CSS. Only 3–8 digit hex codes are accepted now. Anything else falls back to the theme color, as an empty value already did.Normal output is unchanged: the existing snapshot tests pass without updates.
4. Dependency updates and vulnerable packages
node-chartist: it was last published in 2017 and was the source of every remaining production vulnerability:is_js(CVE-2020-26302, no fixed release exists),form-data2.3.3 (CVE-2025-7783),@panosoft/ramda-utils,jsdom9,request,qs,tough-cookieanduuid.src/createChart.tsalready reached into its internals, so its small amount of glue code now lives there: load Chartist into a jsdom window, render the chart, add the axis titles. It now depends directly onchartist0.11.4 (the same version as before) andjsdom26.1.0.createGraphis now typedPromise<string>instead ofany. That exposedGraphArgs.linetyped asPromise<string>, and a test fixture that passed a real Promise, so its snapshot contained[object Promise]. Both are corrected. That one fixture line is the only snapshot change.npm audit fix: patched transitive versions ofpath-to-regexp(used by express),brace-expansion,js-yaml,picomatch,flattedandbrowserslist.nodemonmoved todevDependencies. It is only used by thedev:startscript, and independenciesit pulled its vulnerable sub-dependencies into production installs.npm cion npm 11.Result:
npm audit --omit=devreports 0 vulnerabilities (previously 10, including 3 high), and the production install drops from 259 to 163 packages. The remaining dev-only findings (jest, eslint, ts-jest) need the ESLint 10 and TypeScript 7 upgrades, which are better done separately.Testing
__test__/cli.test.ts: option mapping, empty inputs treated as unset, writing the SVG to a nested path, and failing without writing a file when the fetch fails.__test__/fetching.test.ts: a failed request never puts the token in the logs.__test__/injection.test.ts: escaping ofcustom_titleand the display name, invalid colors rejected, valid hex colors kept.node-chartist, so the rendered chart is identical.npm test: 19 of 20 pass. The one failure,utils.test.ts › GET /graph with correct credential, calls the live GitHub API and needs aTOKEN. It fails the same way locally without these changes, and CI provides the secret.tsc --noEmitand Prettier are clean.npm ci --omit=dev --ignore-scripts, invoked from another directory) against a real account. It wrote the graph correctly, escaped a<b>title, and ignored a malicious color value. An unknown username exits with code 1 and writes no file.***.