Skip to content
Open
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
1045 commits
Select commit Hold shift + click to select a range
a1bab38
Merge branch 'feat/e2e-dashboard-tests' of https://github.com/fabiovi…
fabiovincenzi Feb 20, 2026
28b570b
chore: improve error messages in push actions
jescalada Feb 20, 2026
143a9eb
chore: don't throw error on writePack, update tests
jescalada Feb 20, 2026
1ed9bd5
chore: push, don't overwrite history so browser works
andypols Feb 20, 2026
e6cec75
chore: fix push sort order to be latest-first
andypols Feb 20, 2026
960bad5
feat: add ErrorBoundary component to handle errors in Dashboard layout
dcoric Feb 20, 2026
5af1982
Merge remote-tracking branch 'finos/main' into denis-coric/fix-1392-l…
dcoric Feb 21, 2026
a2d3ffd
feat: add validateAttestation function to check attestation questions…
jescalada Feb 21, 2026
f2c0c60
feat: add CompletedAttestation, AttestationAnswer types and fix type …
jescalada Feb 21, 2026
8016d83
fix: failing e2e test due to boolean/string casting in validateAttest…
jescalada Feb 21, 2026
bae44da
refactor: src/services/routes/repo.ts into function, remove unnecessa…
jescalada Feb 21, 2026
8dd60cb
fix: RepoDetails classes and aria-label, PushRequests/PushesTable err…
jescalada Feb 21, 2026
e1a6008
Merge branch 'main' into tab1-update-website
jescalada Feb 23, 2026
692531b
Merge pull request #1286 from tabathad/tab1-update-website
jescalada Feb 23, 2026
c970f67
test: revert test removal due to type errors and use unknown casting …
jescalada Feb 23, 2026
78778ca
chore: add @andypols to list of maintainers
jescalada Feb 23, 2026
5316a81
Merge branch 'main' into add-reject-reason
andypols Feb 23, 2026
14ce0b6
Merge branch 'main' into feat/e2e-dashboard-tests
fabiovincenzi Feb 23, 2026
7ac5912
fix: integration test
andypols Feb 23, 2026
fa6c123
fix: improve error handling and logging in ErrorBoundary and auth ser…
dcoric Feb 23, 2026
67b85b2
Merge remote-tracking branch 'finos/main' into denis-coric/fix-1392-l…
dcoric Feb 23, 2026
5c4329c
Merge pull request #1396 from qube-rt/add-reject-reason
jescalada Feb 23, 2026
473c982
Merge branch 'main' into website-add-andy-to-maintainers
kriswest Feb 23, 2026
5f81021
docs: restructure contributing docs and improve E2E test infrastructure
coopernetes Feb 9, 2026
3974e2d
docs: move usage to quickstart
tabathad Feb 23, 2026
2472f39
Merge branch 'main' into docs/deployment-guide
tabathad Feb 23, 2026
e174392
docs(fix): remove duplicate usage in sidebar
tabathad Feb 23, 2026
0908706
docs: update path to installation page
tabathad Feb 23, 2026
dfa1d88
fix: rm duplicate supertest dependency
andypols Feb 24, 2026
7ee282c
Merge remote-tracking branch 'finos/main' into denis-coric/fix-1392-l…
dcoric Feb 24, 2026
28aac41
Apply suggestion from @kriswest
kriswest Feb 24, 2026
6713757
Merge pull request #1413 from finos/website-add-andy-to-maintainers
kriswest Feb 24, 2026
0bc60bf
Merge branch 'main' into update-quickstart
tabathad Feb 24, 2026
850ea55
Merge branch 'main' into rm-dup-supertest
jescalada Feb 25, 2026
2c2b093
Merge branch 'main' into denis-coric/fix-1392-linked
jescalada Feb 25, 2026
b937878
Merge pull request #1406 from dcoric/denis-coric/fix-1392-linked
jescalada Feb 25, 2026
48614fb
refactor(ui): replace async/await and promise chaining mixing with co…
fabiovincenzi Feb 25, 2026
ad24af3
fix(ssh): use authenticated user identity from transport layer in par…
fabiovincenzi Feb 25, 2026
a56700f
Merge remote-tracking branch 'upstream/main' into ssh-agent-on-pr987
fabiovincenzi Feb 25, 2026
be7759a
fix(pullRemote): restore concurrent request check and directory clean…
fabiovincenzi Feb 25, 2026
c4f36b7
fix(ssh): add proper TypeScript types to SSH key route params
fabiovincenzi Feb 25, 2026
79b6f7c
fix(ssh): use path.join in test assertions for cross-platform path co…
fabiovincenzi Feb 25, 2026
021e901
Merge branch 'main' into rm-dup-supertest
andypols Feb 25, 2026
bc8eedc
Merge pull request #1419 from andypols/rm-dup-supertest
jescalada Feb 25, 2026
977158b
Update website/docs/quickstart/usage.mdx
tabathad Feb 25, 2026
0c1b077
Merge branch 'main' into update-quickstart
tabathad Feb 25, 2026
a28ce83
Merge branch 'main' into 1174-remove-any-and-as-ts-wrapup
jescalada Feb 25, 2026
e828482
fix: rejection contents and type in autoActions.ts
jescalada Feb 25, 2026
d559cab
chore: ignore experimental packages in renovate.json (#1401)
jescalada Feb 26, 2026
a08379a
docs: move installation.mdx into quickstart directory
jescalada Feb 26, 2026
a1e62d3
docs: update sidebars.js
jescalada Feb 26, 2026
3392dd0
chore(deps): update github-actions - workflows - .github/workflows/do…
renovate[bot] Feb 26, 2026
097d2ff
Merge branch 'main' into update-quickstart
kriswest Feb 26, 2026
cd14cec
Merge pull request #1417 from tabathad/update-quickstart
kriswest Feb 26, 2026
f885211
Merge branch 'main' into docs/contrib-fixup
coopernetes Feb 26, 2026
7fb692e
Merge pull request #1398 from RBC/docs/contrib-fixup
coopernetes Feb 26, 2026
ea19387
chore(deps): update httpd:2.4 docker digest to 96b1e8f - localgit - l…
renovate[bot] Feb 26, 2026
9bb8059
Merge pull request #1425 from finos/renovate/localgit-manager
jescalada Feb 27, 2026
a685438
chore(deps): update actions/download-artifact action to v8 - workflow…
renovate[bot] Feb 27, 2026
730b5d1
Merge branch 'main' into move-sort-to-db
andypols Mar 2, 2026
363e3e1
chore: merge upstream main
fabiovincenzi Mar 2, 2026
ec728a0
fix: type error
andypols Mar 2, 2026
186c984
Merge branch 'main' into fix-dashboard-navigation
andypols Mar 2, 2026
fb025a0
Merge branch 'main' into 1174-remove-any-and-as-ts-wrapup
jescalada Mar 2, 2026
9e0ceec
test: fix confusing helper test
jescalada Mar 2, 2026
2105335
Merge pull request #1429 from finos/renovate/workflows-major-8-github…
jescalada Mar 3, 2026
dd291b0
chore(deps): update github-actions - workflows - .github/workflows/e2…
renovate[bot] Mar 3, 2026
96d1dd3
Merge pull request #1426 from finos/renovate/workflows-manager
jescalada Mar 3, 2026
bd82eaa
fix(deps): update dependency axios to ^1.13.6 - git-proxy-cli - packa…
renovate[bot] Mar 3, 2026
f938c42
Merge pull request #1427 from finos/renovate/git-proxy-cli-manager
jescalada Mar 3, 2026
23b69c8
chore(deps): update dependency @eslint/json to v1 - - package.json
renovate[bot] Mar 3, 2026
25277e3
Merge pull request #1434 from finos/renovate/major-1-npm
jescalada Mar 3, 2026
54293b7
Merge branch 'main' into fix-dashboard-navigation
andypols Mar 3, 2026
5b992bd
Merge branch 'main' into move-sort-to-db
andypols Mar 3, 2026
019c8a6
Merge pull request #1431 from qube-rt/move-sort-to-db
kriswest Mar 3, 2026
a177b01
chore(deps): update github-actions to v5 - workflows - .github/workfl…
renovate[bot] Mar 3, 2026
3872b14
Merge pull request #1437 from finos/renovate/workflows-major-5-github…
jescalada Mar 3, 2026
0a25231
Merge branch 'main' into fix-dashboard-navigation
andypols Mar 3, 2026
0448190
Merge pull request #1433 from qube-rt/fix-dashboard-navigation
jescalada Mar 3, 2026
28b337c
chore(deps): update github-actions to v6 - workflows - .github/workfl…
renovate[bot] Mar 3, 2026
53c9353
Merge pull request #1438 from finos/renovate/workflows-major-6-github…
jescalada Mar 3, 2026
f0478ac
chore(deps): update github-actions to v7 - workflows - .github/workfl…
renovate[bot] Mar 3, 2026
1bda10c
Merge pull request #1439 from finos/renovate/workflows-major-7-github…
jescalada Mar 3, 2026
bfa0e72
Merge branch 'main' into improve-test-coverage
jescalada Mar 3, 2026
2d0a092
fix: failing push test due to renamed field
jescalada Mar 3, 2026
b9b684a
Merge branch 'main' into improve-test-coverage
jescalada Mar 3, 2026
887a669
test: fix failing push test due to missing sort entry
jescalada Mar 3, 2026
f3b9e4e
Merge branch 'main' into ssh-agent-on-pr987
fabiovincenzi Mar 4, 2026
cd90d1b
chore: merge upstream main
fabiovincenzi Mar 4, 2026
8606a21
refactor(ui): replace promise chaining with async/await in repo service
fabiovincenzi Mar 4, 2026
5445793
chore: merge upstream main
fabiovincenzi Mar 4, 2026
2949e9e
test(e2e): add repo cleanup commands and fix delete using _id
fabiovincenzi Mar 4, 2026
95d2a8d
fix(e2e): use correct test-owner/test-repo in Cypress commands
fabiovincenzi Mar 4, 2026
0c76acd
fix(e2e): logout after cleaning
fabiovincenzi Mar 4, 2026
5a58db5
Merge pull request #1421 from fabiovincenzi/fix/async-await-consistency
jescalada Mar 4, 2026
1e272b4
chore(deps): update github-actions to v4 - workflows - .github/workfl…
renovate[bot] Mar 4, 2026
2f654fb
test: consolidate duplicate afterAll in testPush
jescalada Mar 4, 2026
bd985e2
Merge branch 'main' into improve-test-coverage
jescalada Mar 4, 2026
a3b612d
test: add cases for 401 and 404 checks in push routes
jescalada Mar 4, 2026
9b8bfed
Merge branch 'improve-test-coverage' of https://github.com/jescalada/…
jescalada Mar 4, 2026
c880780
test: add tests for fetch by ID and filtering
jescalada Mar 4, 2026
d33b3ab
test: add edge case checks for 400, 401, 403
jescalada Mar 4, 2026
6d26ccf
Merge pull request #1441 from finos/renovate/workflows-major-4-github…
jescalada Mar 4, 2026
2afedcc
chore(deps): update github-actions - workflows - .github/workflows/un…
renovate[bot] Mar 4, 2026
39fe246
test: add missing 404 edge case test for approval
jescalada Mar 4, 2026
3c43652
Merge pull request #1440 from finos/renovate/workflows-manager
jescalada Mar 4, 2026
698e39f
Merge branch 'main' into improve-test-coverage
jescalada Mar 4, 2026
fd151da
chore: add CustomTabs to show different push data
andypols Mar 4, 2026
7b504f5
chore: add StepsTimeline to show results from each push action
andypols Mar 4, 2026
6706274
chore: add optional numeric badge
andypols Mar 4, 2026
8f95c5c
chore: make header the primary colour to match dashboard
andypols Mar 4, 2026
ac353de
Merge pull request #1356 from jescalada/improve-test-coverage
jescalada Mar 5, 2026
9fa39cf
Update src/config/validators.ts
jescalada Mar 5, 2026
3fcbc58
Merge branch 'main' into 1174-remove-any-and-as-ts-wrapup
jescalada Mar 5, 2026
da95d85
Merge branch 'main' into 1174-remove-any-and-as-ts-wrapup
jescalada Mar 5, 2026
5f77ec5
refactor: flatten repo test endpoints and fix failing tests
jescalada Mar 5, 2026
0d378ec
fix: remove any types, improve error handler functions
jescalada Mar 5, 2026
5e9adf1
chore: add eslint license header plugin to devDependencies
jescalada Mar 6, 2026
ca6114c
chore: add license header plugin to eslint config, add licenseHeader.js
jescalada Mar 6, 2026
24d1a59
chore: add license headers
jescalada Mar 6, 2026
c6a578e
fix: relative path bug for license header file
jescalada Mar 6, 2026
9e287a1
Merge branch 'main' into display-git-push-actions
andypols Mar 6, 2026
308d747
Merge branch 'main' into feat/e2e-dashboard-tests
fabiovincenzi Mar 6, 2026
492ce79
refactor: remove or replace unnecessary console.log with step.log
jescalada Mar 6, 2026
dbd797b
chore: remove unnecessary ellipses on logs
jescalada Mar 6, 2026
3c68dd9
test: fix logging-reliant tests, remove log stubs/spies
jescalada Mar 8, 2026
90df884
Merge pull request #1447 from jescalada/1002-eslint-plugin-license-he…
jescalada Mar 8, 2026
adf28a1
Merge branch 'main' into 1281-improve-processors-logging
jescalada Mar 8, 2026
6a2cefe
Merge branch 'main' into 1174-remove-any-and-as-ts-wrapup
jescalada Mar 8, 2026
b2126db
chore: npm run lint:fix
jescalada Mar 8, 2026
bab1881
fix: error message check in cypress test
jescalada Mar 8, 2026
6f2d840
Merge branch 'main' into display-git-push-actions
andypols Mar 9, 2026
cdd9335
chore: add missing license headers
andypols Mar 9, 2026
ccef965
Merge branch 'main' into feat/e2e-dashboard-tests
jescalada Mar 9, 2026
1c4ba16
chore: add missing header
fabiovincenzi Mar 9, 2026
2dfb917
Merge branch 'main' into docs/deployment-guide
tabathad Mar 9, 2026
93c5bb6
Update src/proxy/processors/push-action/preReceive.ts
jescalada Mar 10, 2026
c37cbab
Update src/proxy/chain.ts
jescalada Mar 10, 2026
7486aaa
fix: parsePush error handling
jescalada Mar 10, 2026
833543e
chore: fix duplicate apache headers
jescalada Mar 10, 2026
4b7d295
Merge branch '1174-remove-any-and-as-ts-wrapup' of https://github.com…
jescalada Mar 10, 2026
2ee4f68
chore: merge upstream main
fabiovincenzi Mar 11, 2026
c3bd14e
Merge branch 'ssh-agent-on-pr987' of https://github.com/fabiovincenzi…
fabiovincenzi Mar 11, 2026
5e2d0a9
fix: do not overwrite publicKeys on updateUser
fabiovincenzi Mar 11, 2026
a4f12d4
refactor: remove logs for chunks/changes in scanDiff
jescalada Mar 11, 2026
c9a8bb3
chore: ensure Timeline gets steps
andypols Mar 11, 2026
a94864a
chore: ensure errorCount does not fail in steps undefined
andypols Mar 11, 2026
17dc7f2
chore: open first error step
andypols Mar 11, 2026
1c6c541
Merge pull request #1323 from jescalada/1174-remove-any-and-as-ts-wrapup
kriswest Mar 11, 2026
35adcb6
Merge branch 'main' into 1281-improve-processors-logging
jescalada Mar 14, 2026
93286e8
refactor: rename error handling functions, add step error logger
jescalada Mar 14, 2026
b744a20
Merge branch 'main' into feat/e2e-dashboard-tests
jescalada Mar 15, 2026
9f148a4
Merge pull request #1403 from fabiovincenzi/feat/e2e-dashboard-tests
jescalada Mar 15, 2026
7a1ca00
fix: use CompletedAttestation.answers in AttestationView
fabiovincenzi Mar 16, 2026
d65c9b8
Merge pull request #1459 from fabiovincenzi/fix/attestation-view-answ…
jescalada Mar 16, 2026
cd42eeb
chore: remove parsePush logging
jescalada Mar 16, 2026
c6c703e
Merge branch 'main' into 1281-improve-processors-logging
jescalada Mar 16, 2026
864559e
Merge pull request #1448 from jescalada/1281-improve-processors-logging
jescalada Mar 16, 2026
f52760d
chore: bump git-proxy version to 2.0.0-rc.5
jescalada Mar 16, 2026
cc5e5f3
Merge pull request #1461 from finos/rc5-bump
jescalada Mar 16, 2026
264223d
chore: drop node 20 support, replace with 22
jescalada Mar 16, 2026
072563b
chore: run `npm audit fix`
jescalada Mar 16, 2026
142223e
chore: add Artistic-2.0 to allowed licenses
jescalada Mar 16, 2026
e75310d
docs: explain Node deprecation in CONTRIBUTING.md
jescalada Mar 16, 2026
6459080
fix: broken link to usage.mdx
jescalada Mar 17, 2026
a13f35c
Merge branch 'main' into docs/deployment-guide
jescalada Mar 17, 2026
758b2db
Merge pull request #1385 from tabathad/docs/deployment-guide
jescalada Mar 17, 2026
ec1a77d
Merge branch 'main' into display-git-push-actions
andypols Mar 18, 2026
f957ef8
Merge branch 'main' into 1456-drop-node-20-support
jescalada Mar 19, 2026
868652d
chore: remove node 20 from deployment guide, run `npm audit fix`
jescalada Mar 19, 2026
4fa50d3
chore: remove obsolete CI comment
jescalada Mar 19, 2026
00b6a06
chore: show message when no commit data
andypols Mar 19, 2026
ec0a993
Merge pull request #1462 from finos/1456-drop-node-20-support
jescalada Mar 22, 2026
244686a
chore(deps): update github-actions - workflows - .github/workflows/ci…
renovate[bot] Mar 22, 2026
7bbce24
Merge pull request #1465 from finos/renovate/workflows-manager
jescalada Mar 22, 2026
a635ae4
chore(deps): update httpd:2.4 docker digest to 331548c - localgit - l…
renovate[bot] Mar 22, 2026
68bf3db
Merge pull request #1466 from finos/renovate/localgit-manager
jescalada Mar 22, 2026
6ed9a5e
chore(deps): update dependency node to v24 - workflows - .github/work…
renovate[bot] Mar 22, 2026
6f9a16d
Merge branch 'main' into display-git-push-actions
andypols Mar 23, 2026
06bbe90
Merge pull request #1436 from finos/renovate/workflows-major-24-githu…
coopernetes Mar 23, 2026
fca94af
chore(deps): update node.js to v24 - - dockerfile
renovate[bot] Mar 23, 2026
c0ba816
Merge pull request #1445 from finos/renovate/major-24-dockerfile
coopernetes Mar 23, 2026
2cdd607
Update meeting_minutes.md issue tempalte
kriswest Mar 23, 2026
f097a06
Merge pull request #1473 from finos/kriswest-patch-meeting-minutes-te…
jescalada Mar 24, 2026
7d61115
chore(deps): update actions/upload-artifact action to v6 - workflows …
renovate[bot] Mar 24, 2026
212ab9d
Merge pull request #1468 from finos/renovate/workflows-major-6-github…
jescalada Mar 24, 2026
0cf32c2
chore(deps): update github/codeql-action digest to 72c0b0e - workflow…
renovate[bot] Mar 24, 2026
aecfa3d
Merge pull request #1469 from finos/renovate/workflows-manager
jescalada Mar 24, 2026
d48e3e0
chore(deps): update docker/setup-buildx-action action to v4 - workflo…
renovate[bot] Mar 24, 2026
8eb40f1
Merge pull request #1475 from finos/renovate/workflows-major-4-github…
jescalada Mar 24, 2026
e2bfd5f
chore(deps): update github-actions to v7 - workflows - .github/workfl…
renovate[bot] Mar 24, 2026
50f88d7
Merge pull request #1476 from finos/renovate/workflows-major-7-github…
jescalada Mar 24, 2026
8089983
fix(plugins): update sample plugin imports to use package subpath exp…
coopernetes Mar 26, 2026
0332a30
Merge branch 'main' into display-git-push-actions
andypols Mar 26, 2026
232980a
fix: release drafter permission error (#1482)
jescalada Mar 26, 2026
42e5131
Apply suggestion from @coopernetes
coopernetes Mar 26, 2026
01c544f
Merge branch 'main' into display-git-push-actions
andypols Mar 26, 2026
4e2eea8
Merge pull request #1446 from qube-rt/display-git-push-actions
andypols Mar 26, 2026
6529fc3
fix: broken plugin exports
jescalada Mar 25, 2026
e788ab7
Merge pull request #1489 from finos/1474-fix-broken-subpath-exports
jescalada Mar 31, 2026
579345b
ci: split e2e into parallel vitest and cypress jobs
fabiovincenzi Apr 1, 2026
1c57272
ci: add Docker BuildKit cache to e2e workflow
fabiovincenzi Apr 1, 2026
6ecdfc0
ci: add e2e result job to satisfy branch protection check
fabiovincenzi Apr 1, 2026
4b47144
docs: fix link to installation page
gep13 Apr 1, 2026
5e81660
Merge branch 'main' into fix/plugin-sample-exports
coopernetes Apr 1, 2026
3328267
ci: remove redundant artifact upload/download in CI
fabiovincenzi Apr 2, 2026
1d41221
ci: skip redundant npm ci in cypress-io/github-action
fabiovincenzi Apr 2, 2026
084a77a
ci: use --no-fund flag for npm ci
fabiovincenzi Apr 2, 2026
95110da
Update badge link: stages → maturity in README.md
TheJuanAndOnly99 Apr 2, 2026
a7936c6
chore: fix formatting
jescalada Apr 3, 2026
fc085d4
Merge pull request #1491 from gep13/patch-1
jescalada Apr 3, 2026
1c45104
Merge branch 'main' into ci/e2e-optimization
jescalada Apr 3, 2026
6900045
Merge pull request #1490 from fabiovincenzi/ci/e2e-optimization
jescalada Apr 3, 2026
330a12a
Merge branch 'main' into ci/optimize-ci
jescalada Apr 3, 2026
c0b4a8d
Merge pull request #1492 from fabiovincenzi/ci/optimize-ci
jescalada Apr 3, 2026
d4a2c38
Merge branch 'main' into update-badge
jescalada Apr 8, 2026
848746e
Merge branch 'main' into fix/plugin-sample-exports
jescalada Apr 8, 2026
fda6e3c
fix: add missing utils/errors subpath export
fabiovincenzi Apr 9, 2026
8b606df
fix: restrict tsconfig types to node
fabiovincenzi Apr 9, 2026
064a955
fix: update stale imports in CLI test utils
fabiovincenzi Apr 9, 2026
b9bf5c2
fix: handle 403 and unhandled status codes in CLI
fabiovincenzi Apr 9, 2026
0e42bd4
fix: correct test config path and invalidate cache
fabiovincenzi Apr 9, 2026
1b3e695
fix: add attestationConfig to test proxy config
fabiovincenzi Apr 9, 2026
e389ced
ci: add c8 for CLI subprocess coverage
fabiovincenzi Apr 10, 2026
f2a6118
ci: add CLI test coverage script and source maps
fabiovincenzi Apr 10, 2026
103b4f1
ci: add CLI build step to CI workflow
fabiovincenzi Apr 10, 2026
488b22b
ci: add c8 to depcheck ignores
fabiovincenzi Apr 10, 2026
5783c6c
ci: separate CLI coverage output and upload both reports
fabiovincenzi Apr 10, 2026
5696ebf
ci: fix Windows quoting and increase test timeout for CLI
fabiovincenzi Apr 10, 2026
4d337c4
chore: add coverage-cli to gitignore
fabiovincenzi Apr 10, 2026
ba4f08e
Merge pull request #1499 from fabiovincenzi/fix/cli-restore
jescalada Apr 13, 2026
0821c36
Merge branch 'main' into ci/cli-test-coverage
fabiovincenzi Apr 13, 2026
25d1239
Merge pull request #1500 from fabiovincenzi/ci/cli-test-coverage
jescalada Apr 13, 2026
ab44342
Merge branch 'main' into fix/plugin-sample-exports
kriswest Apr 13, 2026
70d42d5
Merge pull request #1484 from finos/fix/plugin-sample-exports
jescalada Apr 13, 2026
b8f0d0b
chore: bump git-proxy to v2.0.0-rc.6
jescalada Apr 14, 2026
a827aa3
chore: npm run audit
jescalada Apr 15, 2026
2019faf
Merge pull request #1502 from finos/bump-to-rc.6
jescalada Apr 16, 2026
53c9fc3
chore: improve docker-publish.yml flow
jescalada Apr 19, 2026
bec60df
Merge branch 'main' into update-badge
kriswest Apr 20, 2026
1dbb249
Merge branch 'main' into improve-docker-publish-flow
kriswest Apr 20, 2026
e4715f2
Merge pull request #1505 from jescalada/improve-docker-publish-flow
kriswest Apr 20, 2026
4469ed7
Merge branch 'main' into update-badge
06kellyjac Apr 20, 2026
6981427
Merge pull request #1493 from finos/update-badge
kriswest Apr 20, 2026
53a3f3a
Merge remote-tracking branch 'upstream/main' into ssh-agent-on-pr987
fabiovincenzi Apr 22, 2026
6056c34
fix(security): validate req.body is a Buffer before parsing pkt-lines
fabiovincenzi Apr 22, 2026
fac846d
fix(security): add typeof/isArray guards to satisfy CodeQL type-confu…
fabiovincenzi Apr 22, 2026
2452a1e
fix(security): prevent shell injection in ssh-keyscan host verification
fabiovincenzi Apr 22, 2026
ccf8b63
refactor(ssh): centralize ssh2 internal API access with version guards
fabiovincenzi Apr 22, 2026
d9fffe3
chore: run format
fabiovincenzi Apr 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,10 @@
}
},
"required": ["privateKeyPath", "publicKeyPath"]
},
"agentForwardingErrorMessage": {
"type": "string",
"description": "Custom error message shown when SSH agent forwarding is not enabled. If not specified, a default message with git config commands will be used. This allows organizations to customize instructions based on their security policies."
}
},
"required": ["enabled"]
Expand Down
405 changes: 405 additions & 0 deletions docs/SSH_ARCHITECTURE.md

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
"dependencies": {
"@material-ui/core": "^4.12.4",
"@material-ui/icons": "4.11.3",
"@material-ui/lab": "^4.0.0-alpha.61",
"@primer/octicons-react": "^19.19.0",
"@seald-io/nedb": "^4.1.2",
"axios": "^1.12.2",
Expand All @@ -90,6 +91,7 @@
"concurrently": "^9.2.1",
"connect-mongo": "^5.1.0",
"cors": "^2.8.5",
"dayjs": "^1.11.13",
"diff2html": "^3.4.52",
"env-paths": "^3.0.0",
"escape-string-regexp": "^5.0.0",
Expand Down Expand Up @@ -119,6 +121,7 @@
"react-router-dom": "6.30.1",
"simple-git": "^3.28.0",
"ssh2": "^1.16.0",
Comment thread
fabiovincenzi marked this conversation as resolved.
"sshpk": "^1.18.0",
"uuid": "^11.1.0",
"validator": "^13.15.15",
"yargs": "^17.7.2"
Expand Down
48 changes: 40 additions & 8 deletions src/cli/ssh-key.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import * as fs from 'fs';
import * as path from 'path';
import axios from 'axios';
import { utils } from 'ssh2';
import * as crypto from 'crypto';

const API_BASE_URL = process.env.GIT_PROXY_API_URL || 'http://localhost:3000';
const GIT_PROXY_COOKIE_FILE = path.join(
Expand All @@ -23,6 +25,23 @@ interface ErrorWithResponse {
message: string;
}

// Calculate SHA-256 fingerprint from SSH public key
// Note: This function is duplicated in src/service/routes/users.js to keep CLI and server independent
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need to keep the CLI and server independent? Since the CLI is already importing a few things from the parent package.

Perhaps we could extract this function to src/service/routes/utils.ts for better testing and dealing with potential bugs. 🤔

function calculateFingerprint(publicKeyStr: string): string | null {
try {
const parsed = utils.parseKey(publicKeyStr);
if (!parsed || parsed instanceof Error) {
return null;
}
const pubKey = parsed.getPublicSSH();
const hash = crypto.createHash('sha256').update(pubKey).digest('base64');
return `SHA256:${hash}`;
} catch (err) {
console.error('Error calculating fingerprint:', err);
return null;
}
}

async function addSSHKey(username: string, keyPath: string): Promise<void> {
try {
// Check for authentication
Expand Down Expand Up @@ -83,15 +102,28 @@ async function removeSSHKey(username: string, keyPath: string): Promise<void> {
// Read the public key file
const publicKey = fs.readFileSync(keyPath, 'utf8').trim();

// Make the API request
await axios.delete(`${API_BASE_URL}/api/v1/user/${username}/ssh-keys`, {
data: { publicKey },
withCredentials: true,
headers: {
'Content-Type': 'application/json',
Cookie: cookies,
// Strip the comment from the key (everything after the last space)
const keyWithoutComment = publicKey.split(' ').slice(0, 2).join(' ');

// Calculate fingerprint
const fingerprint = calculateFingerprint(keyWithoutComment);
if (!fingerprint) {
console.error('Invalid SSH key format. Unable to calculate fingerprint.');
process.exit(1);
}

console.log(`Removing SSH key with fingerprint: ${fingerprint}`);

// Make the API request using fingerprint in path
await axios.delete(
`${API_BASE_URL}/api/v1/user/${username}/ssh-keys/${encodeURIComponent(fingerprint)}`,
{
withCredentials: true,
headers: {
Cookie: cookies,
},
},
});
);

console.log('SSH key removed successfully!');
} catch (error) {
Expand Down
1 change: 1 addition & 0 deletions src/db/file/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,5 @@ export const {
updateUser,
addPublicKey,
removePublicKey,
getPublicKeys,
} = users;
41 changes: 29 additions & 12 deletions src/db/file/users.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import fs from 'fs';
import Datastore from '@seald-io/nedb';

import { User, UserQuery } from '../types';
import { User, UserQuery, PublicKeyRecord } from '../types';
import { DuplicateSSHKeyError, UserNotFoundError } from '../../errors/DatabaseErrors';

const COMPACTION_INTERVAL = 1000 * 60 * 60 * 24; // once per day
Expand Down Expand Up @@ -181,7 +181,7 @@ export const getUsers = (query: Partial<UserQuery> = {}): Promise<User[]> => {
});
};

export const addPublicKey = (username: string, publicKey: string): Promise<void> => {
export const addPublicKey = (username: string, publicKey: PublicKeyRecord): Promise<void> => {
return new Promise((resolve, reject) => {
// Check if this key already exists for any user
findUserBySSHKey(publicKey)
Expand All @@ -202,20 +202,28 @@ export const addPublicKey = (username: string, publicKey: string): Promise<void>
if (!user.publicKeys) {
user.publicKeys = [];
}
if (!user.publicKeys.includes(publicKey)) {
user.publicKeys.push(publicKey);
updateUser(user)
.then(() => resolve())
.catch(reject);
} else {
resolve();

// Check if key already exists (by key content or fingerprint)
const keyExists = user.publicKeys.some(
(k) =>
k.key === publicKey.key || (k.fingerprint && k.fingerprint === publicKey.fingerprint),
);

if (keyExists) {
reject(new Error('SSH key already exists'));
return;
}

user.publicKeys.push(publicKey);
updateUser(user)
.then(() => resolve())
.catch(reject);
})
.catch(reject);
});
};

export const removePublicKey = (username: string, publicKey: string): Promise<void> => {
export const removePublicKey = (username: string, fingerprint: string): Promise<void> => {
return new Promise((resolve, reject) => {
findUser(username)
.then((user) => {
Expand All @@ -228,7 +236,7 @@ export const removePublicKey = (username: string, publicKey: string): Promise<vo
resolve();
return;
}
user.publicKeys = user.publicKeys.filter((key) => key !== publicKey);
user.publicKeys = user.publicKeys.filter((k) => k.fingerprint !== fingerprint);
updateUser(user)
.then(() => resolve())
.catch(reject);
Expand All @@ -239,7 +247,7 @@ export const removePublicKey = (username: string, publicKey: string): Promise<vo

export const findUserBySSHKey = (sshKey: string): Promise<User | null> => {
return new Promise<User | null>((resolve, reject) => {
db.findOne({ publicKeys: sshKey }, (err: Error | null, doc: User) => {
db.findOne({ 'publicKeys.key': sshKey }, (err: Error | null, doc: User) => {
// ignore for code coverage as neDB rarely returns errors even for an invalid query
/* istanbul ignore if */
if (err) {
Expand All @@ -254,3 +262,12 @@ export const findUserBySSHKey = (sshKey: string): Promise<User | null> => {
});
});
};

export const getPublicKeys = (username: string): Promise<PublicKeyRecord[]> => {
return findUser(username).then((user) => {
if (!user) {
throw new Error('User not found');
}
return user.publicKeys || [];
});
};
14 changes: 8 additions & 6 deletions src/db/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { AuthorisedRepo } from '../config/generated/config';
import { PushQuery, Repo, RepoQuery, Sink, User, UserQuery } from './types';
import { PushQuery, Repo, RepoQuery, Sink, User, UserQuery, PublicKeyRecord } from './types';
import * as bcrypt from 'bcryptjs';
import * as config from '../config';
import * as mongo from './mongo';
Expand Down Expand Up @@ -171,9 +171,11 @@ export const findUserBySSHKey = (sshKey: string): Promise<User | null> =>
sink.findUserBySSHKey(sshKey);
export const getUsers = (query?: Partial<UserQuery>): Promise<User[]> => sink.getUsers(query);
export const deleteUser = (username: string): Promise<void> => sink.deleteUser(username);
export const updateUser = (user: Partial<User>): Promise<void> => sink.updateUser(user);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the Partial here being removed? I think this was changed at some point to allow proper typing for activeDirectory.ts and some test files.

We could alternatively fix the usages, but it makes the most sense for a DB update function (triggered through a PATCH endpoint) to take a partial version of the related entity.

export const addPublicKey = (username: string, publicKey: string): Promise<void> =>
export const updateUser = (user: User): Promise<void> => sink.updateUser(user);
export const addPublicKey = (username: string, publicKey: PublicKeyRecord): Promise<void> =>
sink.addPublicKey(username, publicKey);
export const removePublicKey = (username: string, publicKey: string): Promise<void> =>
sink.removePublicKey(username, publicKey);
export type { PushQuery, Repo, Sink, User } from './types';
export const removePublicKey = (username: string, fingerprint: string): Promise<void> =>
sink.removePublicKey(username, fingerprint);
export const getPublicKeys = (username: string): Promise<PublicKeyRecord[]> =>
sink.getPublicKeys(username);
export type { PushQuery, Repo, Sink, User, PublicKeyRecord } from './types';
1 change: 1 addition & 0 deletions src/db/mongo/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,5 @@ export const {
updateUser,
addPublicKey,
removePublicKey,
getPublicKeys,
} = users;
37 changes: 30 additions & 7 deletions src/db/mongo/users.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { OptionalId, Document, ObjectId } from 'mongodb';
import { toClass } from '../helper';
import { User } from '../types';
import { User, PublicKeyRecord } from '../types';
import { connect } from './helper';
import _ from 'lodash';
import { DuplicateSSHKeyError } from '../../errors/DatabaseErrors';
Expand Down Expand Up @@ -71,32 +71,55 @@ export const updateUser = async (user: Partial<User>): Promise<void> => {
await collection.updateOne(filter, { $set: userWithoutId }, options);
};

export const addPublicKey = async (username: string, publicKey: string): Promise<void> => {
export const addPublicKey = async (username: string, publicKey: PublicKeyRecord): Promise<void> => {
// Check if this key already exists for any user
const existingUser = await findUserBySSHKey(publicKey);
const existingUser = await findUserBySSHKey(publicKey.key);

if (existingUser && existingUser.username.toLowerCase() !== username.toLowerCase()) {
throw new DuplicateSSHKeyError(existingUser.username);
}

// Key doesn't exist for other users
const collection = await connect(collectionName);

const user = await collection.findOne({ username: username.toLowerCase() });
if (!user) {
throw new Error('User not found');
}

const keyExists = user.publicKeys?.some(
(k: PublicKeyRecord) =>
k.key === publicKey.key || (k.fingerprint && k.fingerprint === publicKey.fingerprint),
);

if (keyExists) {
throw new Error('SSH key already exists');
}

await collection.updateOne(
{ username: username.toLowerCase() },
{ $addToSet: { publicKeys: publicKey } },
{ $push: { publicKeys: publicKey } },
);
};

export const removePublicKey = async (username: string, publicKey: string): Promise<void> => {
export const removePublicKey = async (username: string, fingerprint: string): Promise<void> => {
const collection = await connect(collectionName);
await collection.updateOne(
{ username: username.toLowerCase() },
{ $pull: { publicKeys: publicKey } },
{ $pull: { publicKeys: { fingerprint: fingerprint } } },
);
};

export const findUserBySSHKey = async function (sshKey: string): Promise<User | null> {
const collection = await connect(collectionName);
const doc = await collection.findOne({ publicKeys: { $eq: sshKey } });
const doc = await collection.findOne({ 'publicKeys.key': { $eq: sshKey } });
return doc ? toClass(doc, User.prototype) : null;
};

export const getPublicKeys = async (username: string): Promise<PublicKeyRecord[]> => {
const user = await findUser(username);
if (!user) {
throw new Error('User not found');
}
return user.publicKeys || [];
};
18 changes: 13 additions & 5 deletions src/db/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@ export type QueryValue = string | boolean | number | undefined;

export type UserRole = 'canPush' | 'canAuthorise';

export type PublicKeyRecord = {
key: string;
name: string;
addedAt: string;
fingerprint: string;
};

export class Repo {
project: string;
name: string;
Expand Down Expand Up @@ -58,7 +65,7 @@ export class User {
email: string;
admin: boolean;
oidcId?: string | null;
publicKeys?: string[];
publicKeys?: PublicKeyRecord[];
displayName?: string | null;
title?: string | null;
_id?: string;
Expand All @@ -70,7 +77,7 @@ export class User {
email: string,
admin: boolean,
oidcId: string | null = null,
publicKeys: string[] = [],
publicKeys: PublicKeyRecord[] = [],
_id?: string,
) {
this.username = username;
Expand Down Expand Up @@ -110,7 +117,8 @@ export interface Sink {
getUsers: (query?: Partial<UserQuery>) => Promise<User[]>;
createUser: (user: User) => Promise<void>;
deleteUser: (username: string) => Promise<void>;
updateUser: (user: Partial<User>) => Promise<void>;
addPublicKey: (username: string, publicKey: string) => Promise<void>;
removePublicKey: (username: string, publicKey: string) => Promise<void>;
updateUser: (user: User) => Promise<void>;
addPublicKey: (username: string, publicKey: PublicKeyRecord) => Promise<void>;
removePublicKey: (username: string, fingerprint: string) => Promise<void>;
getPublicKeys: (username: string) => Promise<PublicKeyRecord[]>;
}
38 changes: 38 additions & 0 deletions src/proxy/processors/pktLineParser.ts
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might want to rename this to packetLineParser.ts for better searchability!

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively, we could rename this to parsePushUtils and include the other helper functions from parsePush such as getCommitData, getPackData, etc.

This might make the parsePush action a bit easier to understand.

Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { PACKET_SIZE } from './constants';

/**
* Parses the packet lines from a buffer into an array of strings.
* Also returns the offset immediately following the parsed lines (including the flush packet).
* @param {Buffer} buffer - The buffer containing the packet data.
* @return {[string[], number]} An array containing the parsed lines and the offset after the last parsed line/flush packet.
*/
export const parsePacketLines = (buffer: Buffer): [string[], number] => {
const lines: string[] = [];
let offset = 0;

while (offset + PACKET_SIZE <= buffer.length) {
const lengthHex = buffer.toString('utf8', offset, offset + PACKET_SIZE);
const length = Number(`0x${lengthHex}`);

// Prevent non-hex characters from causing issues
if (isNaN(length) || length < 0) {
throw new Error(`Invalid packet line length ${lengthHex} at offset ${offset}`);
}

// length of 0 indicates flush packet (0000)
if (length === 0) {
offset += PACKET_SIZE; // Include length of the flush packet
break;
}

// Make sure we don't read past the end of the buffer
if (offset + length > buffer.length) {
throw new Error(`Invalid packet line length ${lengthHex} at offset ${offset}`);
}

const line = buffer.toString('utf8', offset + PACKET_SIZE, offset + length);
lines.push(line);
offset += length; // Move offset to the start of the next line's length prefix
}
return [lines, offset];
};
Loading
Loading