@shipit-ai/cli 1.166.1 → 1.166.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/packages/core/src/application/use-cases/features/create/create-feature.use-case.d.ts.map +1 -1
- package/dist/packages/core/src/application/use-cases/features/create/create-feature.use-case.js +19 -2
- package/dist/packages/core/src/infrastructure/services/external/github-repository.service.d.ts.map +1 -1
- package/dist/packages/core/src/infrastructure/services/external/github-repository.service.js +14 -3
- package/dist/src/presentation/web/app/actions/deploy-repository.d.ts.map +1 -1
- package/dist/src/presentation/web/app/actions/deploy-repository.js +14 -7
- package/dist/src/presentation/web/app/actions/get-merge-review-data.d.ts.map +1 -1
- package/dist/src/presentation/web/app/actions/get-merge-review-data.js +66 -23
- package/dist/src/presentation/web/app/actions/open-folder.d.ts.map +1 -1
- package/dist/src/presentation/web/app/actions/open-folder.js +12 -4
- package/dist/src/presentation/web/app/actions/open-shell.d.ts.map +1 -1
- package/dist/src/presentation/web/app/actions/open-shell.js +46 -7
- package/dist/src/presentation/web/app/api/agent-events/route.d.ts.map +1 -1
- package/dist/src/presentation/web/app/api/agent-events/route.js +2 -6
- package/dist/src/presentation/web/app/api/attachments/upload-from-path/route.d.ts.map +1 -1
- package/dist/src/presentation/web/app/api/attachments/upload-from-path/route.js +21 -17
- package/dist/src/presentation/web/app/api/deployment-logs/route.d.ts.map +1 -1
- package/dist/src/presentation/web/app/api/deployment-logs/route.js +2 -6
- package/dist/src/presentation/web/app/api/directory/list/route.d.ts.map +1 -1
- package/dist/src/presentation/web/app/api/directory/list/route.js +39 -24
- package/dist/src/presentation/web/app/api/interactive/chat/[featureId]/stream/route.d.ts.map +1 -1
- package/dist/src/presentation/web/app/api/interactive/chat/[featureId]/stream/route.js +2 -6
- package/dist/src/presentation/web/app/api/interactive/sessions/[id]/stream/route.d.ts.map +1 -1
- package/dist/src/presentation/web/app/api/interactive/sessions/[id]/stream/route.js +2 -6
- package/dist/src/presentation/web/lib/path-sanitizers.d.ts +50 -0
- package/dist/src/presentation/web/lib/path-sanitizers.d.ts.map +1 -0
- package/dist/src/presentation/web/lib/path-sanitizers.js +136 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +6 -6
- package/web/.next/BUILD_ID +1 -1
- package/web/.next/build-manifest.json +3 -3
- package/web/.next/fallback-build-manifest.json +3 -3
- package/web/.next/prerender-manifest.json +3 -3
- package/web/.next/required-server-files.js +2 -2
- package/web/.next/required-server-files.json +2 -2
- package/web/.next/server/app/(dashboard)/@drawer/adopt/page/server-reference-manifest.json +29 -29
- package/web/.next/server/app/(dashboard)/@drawer/adopt/page.js +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/adopt/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/adopt/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/chat/page/server-reference-manifest.json +27 -27
- package/web/.next/server/app/(dashboard)/@drawer/chat/page.js +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/chat/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/chat/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/create/page/server-reference-manifest.json +30 -30
- package/web/.next/server/app/(dashboard)/@drawer/create/page.js +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/create/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/create/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/[tab]/page/server-reference-manifest.json +37 -37
- package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/[tab]/page.js +3 -2
- package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/[tab]/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/[tab]/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page/server-reference-manifest.json +37 -37
- package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page.js +3 -2
- package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/feature/[featureId]/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/[tab]/page/server-reference-manifest.json +28 -28
- package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/[tab]/page.js +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/[tab]/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/[tab]/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page/server-reference-manifest.json +28 -28
- package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page.js +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/@drawer/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/chat/page/server-reference-manifest.json +27 -27
- package/web/.next/server/app/(dashboard)/chat/page.js +1 -1
- package/web/.next/server/app/(dashboard)/chat/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/chat/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/create/page/server-reference-manifest.json +30 -30
- package/web/.next/server/app/(dashboard)/create/page.js +1 -1
- package/web/.next/server/app/(dashboard)/create/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/create/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/feature/[featureId]/[tab]/page/server-reference-manifest.json +37 -37
- package/web/.next/server/app/(dashboard)/feature/[featureId]/[tab]/page.js +3 -2
- package/web/.next/server/app/(dashboard)/feature/[featureId]/[tab]/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/feature/[featureId]/[tab]/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/feature/[featureId]/page/server-reference-manifest.json +37 -37
- package/web/.next/server/app/(dashboard)/feature/[featureId]/page.js +3 -2
- package/web/.next/server/app/(dashboard)/feature/[featureId]/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/feature/[featureId]/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/page/server-reference-manifest.json +27 -27
- package/web/.next/server/app/(dashboard)/page.js +1 -1
- package/web/.next/server/app/(dashboard)/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/repository/[repositoryId]/[tab]/page/server-reference-manifest.json +28 -28
- package/web/.next/server/app/(dashboard)/repository/[repositoryId]/[tab]/page.js +1 -1
- package/web/.next/server/app/(dashboard)/repository/[repositoryId]/[tab]/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/repository/[repositoryId]/[tab]/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page/server-reference-manifest.json +28 -28
- package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page.js +1 -1
- package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page.js.nft.json +1 -1
- package/web/.next/server/app/(dashboard)/repository/[repositoryId]/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/_global-error.html +1 -1
- package/web/.next/server/app/_global-error.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/web/.next/server/app/_not-found/page/server-reference-manifest.json +6 -6
- package/web/.next/server/app/_not-found/page.js +1 -1
- package/web/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/api/agent-events/route.js +2 -1
- package/web/.next/server/app/api/agent-events/route.js.nft.json +1 -1
- package/web/.next/server/app/api/attachments/preview/route.js +1 -1
- package/web/.next/server/app/api/attachments/preview/route.js.nft.json +1 -1
- package/web/.next/server/app/api/attachments/upload-from-path/route.js +1 -1
- package/web/.next/server/app/api/attachments/upload-from-path/route.js.nft.json +1 -1
- package/web/.next/server/app/api/deployment-logs/route.js +2 -1
- package/web/.next/server/app/api/deployment-logs/route.js.nft.json +1 -1
- package/web/.next/server/app/api/dialog/pick-files/route.js +1 -1
- package/web/.next/server/app/api/dialog/pick-files/route.js.nft.json +1 -1
- package/web/.next/server/app/api/directory/list/route.js +1 -1
- package/web/.next/server/app/api/directory/list/route.js.nft.json +1 -1
- package/web/.next/server/app/api/evidence/route.js +1 -1
- package/web/.next/server/app/api/evidence/route.js.nft.json +1 -1
- package/web/.next/server/app/api/graph-data/route.js +1 -1
- package/web/.next/server/app/api/graph-data/route.js.nft.json +1 -1
- package/web/.next/server/app/api/interactive/chat/[featureId]/messages/route.js +1 -1
- package/web/.next/server/app/api/interactive/chat/[featureId]/messages/route.js.nft.json +1 -1
- package/web/.next/server/app/api/interactive/chat/[featureId]/stream/route.js +2 -1
- package/web/.next/server/app/api/interactive/chat/[featureId]/stream/route.js.nft.json +1 -1
- package/web/.next/server/app/api/interactive/sessions/[id]/stream/route.js +2 -1
- package/web/.next/server/app/api/interactive/sessions/[id]/stream/route.js.nft.json +1 -1
- package/web/.next/server/app/api/sessions-batch/route.js +1 -1
- package/web/.next/server/app/api/sessions-batch/route.js.nft.json +1 -1
- package/web/.next/server/app/settings/page/server-reference-manifest.json +11 -11
- package/web/.next/server/app/settings/page.js +1 -1
- package/web/.next/server/app/settings/page.js.nft.json +1 -1
- package/web/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/skills/page/server-reference-manifest.json +11 -11
- package/web/.next/server/app/skills/page.js +1 -1
- package/web/.next/server/app/skills/page.js.nft.json +1 -1
- package/web/.next/server/app/skills/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/tools/page/server-reference-manifest.json +11 -11
- package/web/.next/server/app/tools/page.js +1 -1
- package/web/.next/server/app/tools/page.js.nft.json +1 -1
- package/web/.next/server/app/tools/page_client-reference-manifest.js +1 -1
- package/web/.next/server/app/version/page/server-reference-manifest.json +6 -6
- package/web/.next/server/app/version/page.js +1 -1
- package/web/.next/server/app/version/page.js.nft.json +1 -1
- package/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
- package/web/.next/server/chunks/{[root-of-the-server]__02xmnal._.js → [root-of-the-server]__08cpfre._.js} +2 -2
- package/web/.next/server/chunks/[root-of-the-server]__0_-chcy._.js +3 -0
- package/web/.next/server/chunks/[root-of-the-server]__0_-chcy._.js.map +1 -0
- package/web/.next/server/chunks/[root-of-the-server]__0aft8l4._.js +9 -0
- package/web/.next/server/chunks/{[root-of-the-server]__0_6fhza._.js.map → [root-of-the-server]__0aft8l4._.js.map} +1 -1
- package/web/.next/server/chunks/[root-of-the-server]__0e9p7em._.js +3 -0
- package/web/.next/server/chunks/[root-of-the-server]__0e9p7em._.js.map +1 -0
- package/web/.next/server/chunks/{[root-of-the-server]__0.2exzi._.js → [root-of-the-server]__0gfvkg8._.js} +2 -2
- package/web/.next/server/chunks/{[root-of-the-server]__0ip_e1x._.js → [root-of-the-server]__0hcp97v._.js} +2 -2
- package/web/.next/server/chunks/{[root-of-the-server]__09118p2._.js → [root-of-the-server]__0iel39d._.js} +2 -2
- package/web/.next/server/chunks/[root-of-the-server]__0kc8ify._.js +12 -0
- package/web/.next/server/chunks/[root-of-the-server]__0kc8ify._.js.map +1 -0
- package/web/.next/server/chunks/[root-of-the-server]__0r5uk_8._.js +9 -0
- package/web/.next/server/chunks/[root-of-the-server]__0r5uk_8._.js.map +1 -0
- package/web/.next/server/chunks/[root-of-the-server]__0tb~wwk._.js +1 -1
- package/web/.next/server/chunks/{[root-of-the-server]__04jjtl_._.js → [root-of-the-server]__0u1jyv9._.js} +2 -2
- package/web/.next/server/chunks/{[root-of-the-server]__07suer1._.js → [root-of-the-server]__0zu_byw._.js} +2 -2
- package/web/.next/server/chunks/[root-of-the-server]__13e2_kk._.js +18 -0
- package/web/.next/server/chunks/[root-of-the-server]__13e2_kk._.js.map +1 -0
- package/web/.next/server/chunks/ssr/0j.8_web__next-internal_server_app_(dashboard)_@drawer_adopt_page_actions_00~eq5i.js +1 -1
- package/web/.next/server/chunks/ssr/0j.8_web__next-internal_server_app_(dashboard)_@drawer_adopt_page_actions_00~eq5i.js.map +1 -1
- package/web/.next/server/chunks/ssr/0j.8_web__next-internal_server_app_(dashboard)_@drawer_chat_page_actions_0979_c..js +1 -1
- package/web/.next/server/chunks/ssr/0j.8_web__next-internal_server_app_(dashboard)_@drawer_chat_page_actions_0979_c..js.map +1 -1
- package/web/.next/server/chunks/ssr/0j.8_web__next-internal_server_app_(dashboard)_chat_page_actions_0dqll_1.js +1 -1
- package/web/.next/server/chunks/ssr/0j.8_web__next-internal_server_app_(dashboard)_chat_page_actions_0dqll_1.js.map +1 -1
- package/web/.next/server/chunks/ssr/0j.8_web_components_common_control-center-drawer_create-drawer-client_tsx_0g70fc5._.js +1 -1
- package/web/.next/server/chunks/ssr/0j.8_web_components_common_control-center-drawer_create-drawer-client_tsx_0g70fc5._.js.map +1 -1
- package/web/.next/server/chunks/ssr/0j.8_web_components_common_control-center-drawer_feature-drawer-client_tsx_104cna.._.js +2 -2
- package/web/.next/server/chunks/ssr/0j.8_web_components_common_control-center-drawer_feature-drawer-client_tsx_104cna.._.js.map +1 -1
- package/web/.next/server/chunks/ssr/0ukq_presentation_web_components_features_settings_settings-page-client_tsx_0j1uius._.js +1 -1
- package/web/.next/server/chunks/ssr/0ukq_presentation_web_components_features_settings_settings-page-client_tsx_0j1uius._.js.map +1 -1
- package/web/.next/server/chunks/ssr/11y9_components_common_control-center-drawer_repository-drawer-client_tsx_09z.znp._.js +1 -1
- package/web/.next/server/chunks/ssr/11y9_components_common_control-center-drawer_repository-drawer-client_tsx_09z.znp._.js.map +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__04nnbmc._.js +3 -0
- package/web/.next/server/chunks/ssr/[root-of-the-server]__04nnbmc._.js.map +1 -0
- package/web/.next/server/chunks/ssr/[root-of-the-server]__07740t6._.js +3 -0
- package/web/.next/server/chunks/ssr/[root-of-the-server]__07740t6._.js.map +1 -0
- package/web/.next/server/chunks/ssr/[root-of-the-server]__0l~puw4._.js +3 -0
- package/web/.next/server/chunks/ssr/[root-of-the-server]__0l~puw4._.js.map +1 -0
- package/web/.next/server/chunks/ssr/[root-of-the-server]__0o3qggc._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__0o3qggc._.js.map +1 -1
- package/web/.next/server/chunks/ssr/{[root-of-the-server]__0qh.wn.._.js → [root-of-the-server]__0q3-gz.._.js} +2 -2
- package/web/.next/server/chunks/ssr/[root-of-the-server]__0rv1gci._.js +1 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__0vwjc_m._.js +3 -0
- package/web/.next/server/chunks/ssr/[root-of-the-server]__0vwjc_m._.js.map +1 -0
- package/web/.next/server/chunks/ssr/{[root-of-the-server]__12g8h3_._.js → [root-of-the-server]__0w4__yd._.js} +3 -3
- package/web/.next/server/chunks/ssr/_01mq~sm._.js +1 -1
- package/web/.next/server/chunks/ssr/_01mq~sm._.js.map +1 -1
- package/web/.next/server/chunks/ssr/_01sesw0._.js +1 -1
- package/web/.next/server/chunks/ssr/_01sesw0._.js.map +1 -1
- package/web/.next/server/chunks/ssr/{_0e4npv~._.js → _04rrcmm._.js} +2 -2
- package/web/.next/server/chunks/ssr/{_0e4npv~._.js.map → _04rrcmm._.js.map} +1 -1
- package/web/.next/server/chunks/ssr/{_0nvrqsj._.js → _0c497sr._.js} +2 -2
- package/web/.next/server/chunks/ssr/{_0nvrqsj._.js.map → _0c497sr._.js.map} +1 -1
- package/web/.next/server/chunks/ssr/{_0a-ddx-._.js → _0c741v_._.js} +2 -2
- package/web/.next/server/chunks/ssr/{_0a-ddx-._.js.map → _0c741v_._.js.map} +1 -1
- package/web/.next/server/chunks/ssr/_0jpbsh_._.js +1 -1
- package/web/.next/server/chunks/ssr/_0jpbsh_._.js.map +1 -1
- package/web/.next/server/chunks/ssr/_0vyfc4b._.js +1 -1
- package/web/.next/server/chunks/ssr/_0vyfc4b._.js.map +1 -1
- package/web/.next/server/chunks/ssr/_0w-_hww._.js +1 -1
- package/web/.next/server/chunks/ssr/_0w-_hww._.js.map +1 -1
- package/web/.next/server/chunks/ssr/_0~7lwu_._.js +1 -1
- package/web/.next/server/chunks/ssr/_0~7lwu_._.js.map +1 -1
- package/web/.next/server/chunks/ssr/_109n-y4._.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_0.e4~xc._.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_0.e4~xc._.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_00dvh.m._.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_00dvh.m._.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_06b6~lt._.js +2 -2
- package/web/.next/server/chunks/ssr/src_presentation_web_06b6~lt._.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_08fy2mf._.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_08fy2mf._.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_0f~udu1._.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_0f~udu1._.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_0qys821._.js +2 -2
- package/web/.next/server/chunks/ssr/src_presentation_web_0qys821._.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_0q~dt0o._.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_0q~dt0o._.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_11jrkxt._.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_11jrkxt._.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_1199d3x.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_(dashboard)_page_actions_1199d3x.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app__not-found_page_actions_0m2jqxx.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app__not-found_page_actions_0m2jqxx.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_version_page_actions_0krkh_0.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web__next-internal_server_app_version_page_actions_0krkh_0.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_app_actions_approve-feature_ts_0pjb_re._.js +3 -0
- package/web/.next/server/chunks/ssr/src_presentation_web_app_actions_approve-feature_ts_0pjb_re._.js.map +1 -0
- package/web/.next/server/chunks/ssr/src_presentation_web_app_actions_load-settings_ts_0b8f3pf._.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_app_actions_open-ide_ts_0w2wqvu._.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_app_actions_open-ide_ts_0w2wqvu._.js.map +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_components_features_control-center_0l3oxx9._.js +1 -1
- package/web/.next/server/chunks/ssr/src_presentation_web_components_features_control-center_0l3oxx9._.js.map +1 -1
- package/web/.next/server/middleware-build-manifest.js +3 -3
- package/web/.next/server/pages/500.html +1 -1
- package/web/.next/server/server-reference-manifest.js +1 -1
- package/web/.next/server/server-reference-manifest.json +49 -49
- package/web/.next/static/chunks/{0t.pzrmeoq6th.js → 0-fy~80ui.5os.js} +1 -1
- package/web/.next/static/chunks/{00dg6gti40.3i.js → 039ic1ygq-to3.js} +1 -1
- package/web/.next/static/chunks/{0ntgq3d_.m5el.js → 04xk1iouwcfcq.js} +3 -3
- package/web/.next/static/chunks/{0njrgvmyafrod.js → 07a4jt64wdipb.js} +1 -1
- package/web/.next/static/chunks/{14g1l3~6i5251.js → 07gx-h_y91lay.js} +1 -1
- package/web/.next/static/chunks/{0ist7260j__0m.js → 0_imq4rg3q.fe.js} +2 -2
- package/web/.next/static/chunks/{09dqgshddfxff.js → 0c_bi0dck80dt.js} +1 -1
- package/web/.next/static/chunks/{0awttldb-.7m..js → 0ex35-_jtxyjc.js} +1 -1
- package/web/.next/static/chunks/{0_c5~n__lz4ks.js → 0i084mozx131g.js} +1 -1
- package/web/.next/static/chunks/{0_--5mgqukm__.js → 0k~55i.ofbdeb.js} +1 -1
- package/web/.next/static/chunks/{0t8zwgaz.d1s5.js → 0oq-cvtg8rjjp.js} +1 -1
- package/web/.next/static/chunks/{0d-2jp.f._l2e.js → 0t_6hx6ul7umb.js} +1 -1
- package/web/.next/static/chunks/{0nk2r-18.7g6r.js → 0whez3wju~9ok.js} +1 -1
- package/web/.next/server/chunks/[root-of-the-server]__0-3b27b._.js +0 -9
- package/web/.next/server/chunks/[root-of-the-server]__0-3b27b._.js.map +0 -1
- package/web/.next/server/chunks/[root-of-the-server]__0_6fhza._.js +0 -9
- package/web/.next/server/chunks/[root-of-the-server]__0esdmru._.js +0 -12
- package/web/.next/server/chunks/[root-of-the-server]__0esdmru._.js.map +0 -1
- package/web/.next/server/chunks/[root-of-the-server]__0l1p8bx._.js +0 -3
- package/web/.next/server/chunks/[root-of-the-server]__0l1p8bx._.js.map +0 -1
- package/web/.next/server/chunks/[root-of-the-server]__0p~owgt._.js +0 -18
- package/web/.next/server/chunks/[root-of-the-server]__0p~owgt._.js.map +0 -1
- package/web/.next/server/chunks/[root-of-the-server]__0rru~m.._.js +0 -3
- package/web/.next/server/chunks/[root-of-the-server]__0rru~m.._.js.map +0 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__045sv4b._.js +0 -3
- package/web/.next/server/chunks/ssr/[root-of-the-server]__045sv4b._.js.map +0 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__0d_0_fp._.js +0 -3
- package/web/.next/server/chunks/ssr/[root-of-the-server]__0d_0_fp._.js.map +0 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__0l4d7e.._.js +0 -3
- package/web/.next/server/chunks/ssr/[root-of-the-server]__0l4d7e.._.js.map +0 -1
- package/web/.next/server/chunks/ssr/[root-of-the-server]__0r32z03._.js +0 -3
- package/web/.next/server/chunks/ssr/[root-of-the-server]__0r32z03._.js.map +0 -1
- /package/web/.next/server/chunks/{[root-of-the-server]__02xmnal._.js.map → [root-of-the-server]__08cpfre._.js.map} +0 -0
- /package/web/.next/server/chunks/{[root-of-the-server]__0.2exzi._.js.map → [root-of-the-server]__0gfvkg8._.js.map} +0 -0
- /package/web/.next/server/chunks/{[root-of-the-server]__0ip_e1x._.js.map → [root-of-the-server]__0hcp97v._.js.map} +0 -0
- /package/web/.next/server/chunks/{[root-of-the-server]__09118p2._.js.map → [root-of-the-server]__0iel39d._.js.map} +0 -0
- /package/web/.next/server/chunks/{[root-of-the-server]__04jjtl_._.js.map → [root-of-the-server]__0u1jyv9._.js.map} +0 -0
- /package/web/.next/server/chunks/{[root-of-the-server]__07suer1._.js.map → [root-of-the-server]__0zu_byw._.js.map} +0 -0
- /package/web/.next/server/chunks/ssr/{[root-of-the-server]__0qh.wn.._.js.map → [root-of-the-server]__0q3-gz.._.js.map} +0 -0
- /package/web/.next/server/chunks/ssr/{[root-of-the-server]__12g8h3_._.js.map → [root-of-the-server]__0w4__yd._.js.map} +0 -0
- /package/web/.next/static/{ZpPnD_b687G9xVr2nzrds → ynyh_sSxbFA995FRvBUxs}/_buildManifest.js +0 -0
- /package/web/.next/static/{ZpPnD_b687G9xVr2nzrds → ynyh_sSxbFA995FRvBUxs}/_clientMiddlewareManifest.js +0 -0
- /package/web/.next/static/{ZpPnD_b687G9xVr2nzrds → ynyh_sSxbFA995FRvBUxs}/_ssgManifest.js +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-feature.use-case.d.ts","sourceRoot":"","sources":["../../../../../../../../packages/core/src/application/use-cases/features/create/create-feature.use-case.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,wCAAwC,CAAC;AAMtE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oEAAoE,CAAC;AAC7G,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8DAA8D,CAAC;AACrG,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,iEAAiE,CAAC;AACnH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gEAAgE,CAAC;AAC1G,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,8DAA8D,CAAC;AAC5G,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,uEAAuE,CAAC;AACnH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4DAA4D,CAAC;AAChG,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2DAA2D,CAAC;AACjG,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qEAAqE,CAAC;AAC/G,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,wEAAwE,CAAC;AAExH,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAE9F,qBACa,oBAAoB;IAG7B,OAAO,CAAC,QAAQ,CAAC,WAAW;IAE5B,OAAO,CAAC,QAAQ,CAAC,eAAe;IAEhC,OAAO,CAAC,QAAQ,CAAC,YAAY;IAE7B,OAAO,CAAC,QAAQ,CAAC,aAAa;IAE9B,OAAO,CAAC,QAAQ,CAAC,eAAe;IAEhC,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IAElC,OAAO,CAAC,QAAQ,CAAC,YAAY;IAE7B,OAAO,CAAC,QAAQ,CAAC,cAAc;IAE/B,OAAO,CAAC,QAAQ,CAAC,YAAY;IAE7B,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IAElC,OAAO,CAAC,QAAQ,CAAC,cAAc;IAE/B,OAAO,CAAC,QAAQ,CAAC,YAAY;gBAtBZ,WAAW,EAAE,kBAAkB,EAE/B,eAAe,EAAE,gBAAgB,EAEjC,YAAY,EAAE,2BAA2B,EAEzC,aAAa,EAAE,mBAAmB,EAElC,eAAe,EAAE,uBAAuB,EAExC,iBAAiB,EAAE,iBAAiB,EAEpC,YAAY,EAAE,YAAY,EAE1B,cAAc,EAAE,qBAAqB,EAErC,YAAY,EAAE,aAAa,EAE3B,iBAAiB,EAAE,yBAAyB,EAE5C,cAAc,EAAE,eAAe,EAE/B,YAAY,EAAE,mBAAmB;IAGpD;;;OAGG;IACG,OAAO,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAMtE;;;;OAIG;IACG,YAAY,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"create-feature.use-case.d.ts","sourceRoot":"","sources":["../../../../../../../../packages/core/src/application/use-cases/features/create/create-feature.use-case.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,wCAAwC,CAAC;AAMtE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oEAAoE,CAAC;AAC7G,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8DAA8D,CAAC;AACrG,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,iEAAiE,CAAC;AACnH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gEAAgE,CAAC;AAC1G,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,8DAA8D,CAAC;AAC5G,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,uEAAuE,CAAC;AACnH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4DAA4D,CAAC;AAChG,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2DAA2D,CAAC;AACjG,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qEAAqE,CAAC;AAC/G,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,wEAAwE,CAAC;AAExH,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAE9F,qBACa,oBAAoB;IAG7B,OAAO,CAAC,QAAQ,CAAC,WAAW;IAE5B,OAAO,CAAC,QAAQ,CAAC,eAAe;IAEhC,OAAO,CAAC,QAAQ,CAAC,YAAY;IAE7B,OAAO,CAAC,QAAQ,CAAC,aAAa;IAE9B,OAAO,CAAC,QAAQ,CAAC,eAAe;IAEhC,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IAElC,OAAO,CAAC,QAAQ,CAAC,YAAY;IAE7B,OAAO,CAAC,QAAQ,CAAC,cAAc;IAE/B,OAAO,CAAC,QAAQ,CAAC,YAAY;IAE7B,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IAElC,OAAO,CAAC,QAAQ,CAAC,cAAc;IAE/B,OAAO,CAAC,QAAQ,CAAC,YAAY;gBAtBZ,WAAW,EAAE,kBAAkB,EAE/B,eAAe,EAAE,gBAAgB,EAEjC,YAAY,EAAE,2BAA2B,EAEzC,aAAa,EAAE,mBAAmB,EAElC,eAAe,EAAE,uBAAuB,EAExC,iBAAiB,EAAE,iBAAiB,EAEpC,YAAY,EAAE,YAAY,EAE1B,cAAc,EAAE,qBAAqB,EAErC,YAAY,EAAE,aAAa,EAE3B,iBAAiB,EAAE,yBAAyB,EAE5C,cAAc,EAAE,eAAe,EAE/B,YAAY,EAAE,mBAAmB;IAGpD;;;OAGG;IACG,OAAO,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAMtE;;;;OAIG;IACG,YAAY,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAwJ1E;;;OAGG;IACG,kBAAkB,CACtB,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,kBAAkB,EACzB,WAAW,EAAE,OAAO,GACnB,OAAO,CAAC;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,OAAO,CAAA;KAAE,CAAC;CAoJ1D"}
|
package/dist/packages/core/src/application/use-cases/features/create/create-feature.use-case.js
CHANGED
|
@@ -116,8 +116,25 @@ let CreateFeatureUseCase = class CreateFeatureUseCase {
|
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
118
|
}
|
|
119
|
-
// Resolve or create repository entity for this path
|
|
120
|
-
|
|
119
|
+
// Resolve or create repository entity for this path.
|
|
120
|
+
//
|
|
121
|
+
// Normalization: (1) convert backslashes to forward slashes,
|
|
122
|
+
// (2) strip trailing slashes.
|
|
123
|
+
//
|
|
124
|
+
// Step 2 is implemented as a while-loop specifically to work around a
|
|
125
|
+
// CodeQL js/polynomial-redos FALSE POSITIVE on the equivalent regex
|
|
126
|
+
// `/\/+$/`. That pattern is actually O(n) on V8 and all other modern
|
|
127
|
+
// regex engines (single character class, anchored to end, no alternation
|
|
128
|
+
// — there is no backtracking choice point). CodeQL's query is overly
|
|
129
|
+
// strict on quantifiers against tainted input and does not model this
|
|
130
|
+
// correctly. The loop produces identical output and suppresses the
|
|
131
|
+
// alert without requiring an inline dismissal comment.
|
|
132
|
+
let normalizedPath = effectiveRepoPath.replace(/\\/g, '/');
|
|
133
|
+
while (normalizedPath.length > 1 && normalizedPath.endsWith('/')) {
|
|
134
|
+
normalizedPath = normalizedPath.slice(0, -1);
|
|
135
|
+
}
|
|
136
|
+
if (!normalizedPath)
|
|
137
|
+
normalizedPath = effectiveRepoPath;
|
|
121
138
|
let repository = await this.repositoryRepo.findByPath(normalizedPath);
|
|
122
139
|
const now = new Date();
|
|
123
140
|
if (!repository) {
|
package/dist/packages/core/src/infrastructure/services/external/github-repository.service.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"github-repository.service.d.ts","sourceRoot":"","sources":["../../../../../../../packages/core/src/infrastructure/services/external/github-repository.service.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC/D,OAAO,KAAK,EACV,wBAAwB,EACxB,UAAU,EACV,kBAAkB,EAClB,2BAA2B,EAC3B,YAAY,EACZ,eAAe,EAChB,MAAM,mFAAmF,CAAC;AAyB3F,qBACa,uBAAwB,YAAW,wBAAwB;IAClC,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBAAR,QAAQ,EAAE,YAAY;IAErE,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IAyB1B,oBAAoB,CAAC,OAAO,CAAC,EAAE,2BAA2B,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"github-repository.service.d.ts","sourceRoot":"","sources":["../../../../../../../packages/core/src/infrastructure/services/external/github-repository.service.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC/D,OAAO,KAAK,EACV,wBAAwB,EACxB,UAAU,EACV,kBAAkB,EAClB,2BAA2B,EAC3B,YAAY,EACZ,eAAe,EAChB,MAAM,mFAAmF,CAAC;AAyB3F,qBACa,uBAAwB,YAAW,wBAAwB;IAClC,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBAAR,QAAQ,EAAE,YAAY;IAErE,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IAyB1B,oBAAoB,CAAC,OAAO,CAAC,EAAE,2BAA2B,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IAkDlF,iBAAiB,IAAI,OAAO,CAAC,kBAAkB,EAAE,CAAC;IA2BlD,eAAe,CACnB,aAAa,EAAE,MAAM,EACrB,WAAW,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE,YAAY,GACrB,OAAO,CAAC,IAAI,CAAC;IAgDhB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe;IA2CtC,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;YAyB9C,mBAAmB;CAOlC"}
|
package/dist/packages/core/src/infrastructure/services/external/github-repository.service.js
CHANGED
|
@@ -66,9 +66,20 @@ let GitHubRepositoryService = class GitHubRepositoryService {
|
|
|
66
66
|
String(limit),
|
|
67
67
|
];
|
|
68
68
|
if (options?.search) {
|
|
69
|
-
// gh repo list does not have a --match flag; use jq to filter by name
|
|
70
|
-
|
|
71
|
-
|
|
69
|
+
// gh repo list does not have a --match flag; use jq to filter by name.
|
|
70
|
+
//
|
|
71
|
+
// Use a literal case-insensitive substring match via ascii_downcase +
|
|
72
|
+
// contains() instead of test() (which would treat the input as a
|
|
73
|
+
// regex). This eliminates both regex-injection and ReDoS risk from
|
|
74
|
+
// user-supplied metacharacters.
|
|
75
|
+
//
|
|
76
|
+
// The search string is JSON-encoded before embedding so that quotes,
|
|
77
|
+
// backslashes, newlines, and any other special character are fully
|
|
78
|
+
// escaped by the language runtime (not a hand-rolled single-char
|
|
79
|
+
// replace). JSON.stringify produces a string literal that is
|
|
80
|
+
// simultaneously valid JSON and a valid jq string literal.
|
|
81
|
+
const jqLiteral = JSON.stringify(options.search.toLowerCase());
|
|
82
|
+
args.push('-q', `[.[] | select((.name | ascii_downcase) | contains(${jqLiteral}))]`);
|
|
72
83
|
}
|
|
73
84
|
try {
|
|
74
85
|
const { stdout } = await this.execFile('gh', args);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deploy-repository.d.ts","sourceRoot":"","sources":["../../../../../../src/presentation/web/app/actions/deploy-repository.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,eAAe,EAAE,MAAM,yCAAyC,CAAC;AAK1E,wBAAsB,gBAAgB,CACpC,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,eAAe,CAAA;CAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"deploy-repository.d.ts","sourceRoot":"","sources":["../../../../../../src/presentation/web/app/actions/deploy-repository.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,eAAe,EAAE,MAAM,yCAAyC,CAAC;AAK1E,wBAAsB,gBAAgB,CACpC,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,eAAe,CAAA;CAAE,CAAC,CAuCxE"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
'use server';
|
|
2
|
-
import {
|
|
2
|
+
import { realpathSync } from 'node:fs';
|
|
3
3
|
import { isAbsolute } from 'node:path';
|
|
4
4
|
import { resolve } from '../../lib/server-container.js';
|
|
5
5
|
import { createDeploymentLogger } from '../../lib/core-utils.js';
|
|
@@ -12,12 +12,19 @@ export async function deployRepository(repositoryPath) {
|
|
|
12
12
|
log.warn('rejected — not an absolute path');
|
|
13
13
|
return { success: false, error: 'repositoryPath must be an absolute path' };
|
|
14
14
|
}
|
|
15
|
+
// Resolve through realpath() up-front. Every subsequent use references
|
|
16
|
+
// the symlink-resolved absolute path, not the raw user input. This is the
|
|
17
|
+
// sanitizer CodeQL's js/path-injection analysis recognizes.
|
|
18
|
+
let resolvedPath;
|
|
15
19
|
try {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
|
|
20
|
+
resolvedPath = realpathSync(repositoryPath);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
log.warn(`directory does not exist: "${repositoryPath}"`);
|
|
24
|
+
return { success: false, error: 'Directory does not exist' };
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
if (isSameShipitAiInstance(resolvedPath)) {
|
|
21
28
|
log.warn('rejected — target is the running ShipIT instance');
|
|
22
29
|
return {
|
|
23
30
|
success: false,
|
|
@@ -26,7 +33,7 @@ export async function deployRepository(repositoryPath) {
|
|
|
26
33
|
}
|
|
27
34
|
log.info('directory exists, calling deploymentService.start()');
|
|
28
35
|
const deploymentService = resolve('IDeploymentService');
|
|
29
|
-
deploymentService.start(
|
|
36
|
+
deploymentService.start(resolvedPath, resolvedPath, 'repository');
|
|
30
37
|
log.info('start() returned successfully — state=Booting');
|
|
31
38
|
return { success: true, state: DeploymentState.Booting };
|
|
32
39
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-merge-review-data.d.ts","sourceRoot":"","sources":["../../../../../../src/presentation/web/app/actions/get-merge-review-data.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"get-merge-review-data.d.ts","sourceRoot":"","sources":["../../../../../../src/presentation/web/app/actions/get-merge-review-data.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EACV,eAAe,EAEhB,MAAM,sDAAsD,CAAC;AAI9D,KAAK,wBAAwB,GAAG,eAAe,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAwDpE,wBAAsB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,wBAAwB,CAAC,CAuI7F"}
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
'use server';
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
|
-
import { readFileSync
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
4
4
|
import { basename, join, dirname } from 'node:path';
|
|
5
5
|
import { resolve } from '../../lib/server-container.js';
|
|
6
|
+
import { realpathOrNull, isWithinRoot } from '../../lib/path-sanitizers.js';
|
|
6
7
|
import { computeWorktreePath, getShipitAiHomeDir } from '../../lib/core-utils.js';
|
|
7
8
|
/**
|
|
8
9
|
* Compute the ShipIT evidence directory for a given repository and feature.
|
|
9
10
|
* Path: ~/.shipit-ai/repos/<sha256-hash-prefix>/evidence/<featureId>/
|
|
11
|
+
*
|
|
12
|
+
* The sha256 hash of the repository path makes the resulting directory name
|
|
13
|
+
* deterministic and hex-only, neutralizing any path-injection risk from the
|
|
14
|
+
* repositoryPath input. The featureId is a UUID from the DB lookup.
|
|
10
15
|
*/
|
|
11
16
|
function computeEvidenceDir(repositoryPath, featureId) {
|
|
12
17
|
const repoHash = createHash('sha256').update(repositoryPath).digest('hex').slice(0, 16);
|
|
@@ -19,21 +24,34 @@ function computeEvidenceDir(repositoryPath, featureId) {
|
|
|
19
24
|
* deleted so those paths no longer resolve. The evidence files were also saved
|
|
20
25
|
* to the ShipIT evidence dir with the same filename, so we map relative paths
|
|
21
26
|
* to absolute paths there.
|
|
27
|
+
*
|
|
28
|
+
* IMPORTANT: the returned paths must remain in the SAME form as the
|
|
29
|
+
* `/api/evidence` route expects (it uses `path.resolve` + `.startsWith`
|
|
30
|
+
* against the unresolved `SHIPIT_AI_HOME/repos` root). Do not pass
|
|
31
|
+
* realpath-resolved paths here, because on macOS `SHIPIT_AI_HOME=/tmp/...`
|
|
32
|
+
* resolves to `/private/tmp/...` and the evidence route's prefix check
|
|
33
|
+
* would reject the realpath'd form. Basename-only containment (strip any
|
|
34
|
+
* directory traversal via `basename()` then `join()` with the known-safe
|
|
35
|
+
* `evidenceDir`) is sufficient sanitization for this taint source because
|
|
36
|
+
* `basename()` cannot return a path-traversal string.
|
|
22
37
|
*/
|
|
23
38
|
function normalizeEvidencePaths(evidence, evidenceDir) {
|
|
24
39
|
return evidence.map((e) => {
|
|
40
|
+
// If the manifest path is absolute and already present on disk, keep
|
|
41
|
+
// it verbatim — this preserves the original reference and matches the
|
|
42
|
+
// pre-fix behavior for already-migrated evidence. We do NOT realpath
|
|
43
|
+
// the result because the evidence route does not realpath its input,
|
|
44
|
+
// and mismatching normalization forms would cause 404s.
|
|
25
45
|
if (e.relativePath.startsWith('/')) {
|
|
26
|
-
// Already absolute — check if the file exists; if not, try the evidence dir
|
|
27
|
-
if (existsSync(e.relativePath))
|
|
28
|
-
return e;
|
|
29
|
-
const fallback = join(evidenceDir, basename(e.relativePath)).replace(/\\/g, '/');
|
|
30
|
-
if (existsSync(fallback))
|
|
31
|
-
return { ...e, relativePath: fallback };
|
|
32
46
|
return e;
|
|
33
47
|
}
|
|
34
|
-
// Relative path —
|
|
35
|
-
|
|
36
|
-
|
|
48
|
+
// Relative path — map to evidenceDir using basename() only. `basename`
|
|
49
|
+
// strips any directory components including `..` sequences, so the
|
|
50
|
+
// joined result is guaranteed to live directly inside evidenceDir
|
|
51
|
+
// regardless of what the manifest file contained.
|
|
52
|
+
const safeName = basename(e.relativePath);
|
|
53
|
+
const target = join(evidenceDir, safeName).replace(/\\/g, '/');
|
|
54
|
+
return { ...e, relativePath: target };
|
|
37
55
|
});
|
|
38
56
|
}
|
|
39
57
|
export async function getMergeReviewData(featureId) {
|
|
@@ -88,19 +106,44 @@ export async function getMergeReviewData(featureId) {
|
|
|
88
106
|
: null;
|
|
89
107
|
if (evidenceDir) {
|
|
90
108
|
try {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
109
|
+
// SECURITY: validate the manifest we're about to read lives inside
|
|
110
|
+
// SHIPIT_AI_HOME. computeEvidenceDir() already hashes repositoryPath
|
|
111
|
+
// to a hex directory name, but we still run a realpath containment
|
|
112
|
+
// check because CodeQL's js/path-injection analysis recognizes the
|
|
113
|
+
// realpathOrNull + isWithinRoot pair as a sanitizer chain.
|
|
114
|
+
//
|
|
115
|
+
// Resolve-once semantics: realpath the shipit home dir and the
|
|
116
|
+
// evidence dir exactly once each, then reuse those resolved values
|
|
117
|
+
// for every subsequent containment check. This avoids both the
|
|
118
|
+
// extra syscalls and the TOCTOU window that a recursive resolve-
|
|
119
|
+
// and-check helper would introduce.
|
|
120
|
+
//
|
|
121
|
+
// IMPORTANT: the resolved paths are used ONLY for the read-time
|
|
122
|
+
// security check. The unresolved `evidenceDir` is what we pass to
|
|
123
|
+
// normalizeEvidencePaths so the paths returned to the client match
|
|
124
|
+
// what the /api/evidence route expects — see the comment on
|
|
125
|
+
// normalizeEvidencePaths for the full rationale.
|
|
126
|
+
const resolvedHome = realpathOrNull(getShipitAiHomeDir());
|
|
127
|
+
const resolvedEvidenceDir = realpathOrNull(evidenceDir);
|
|
128
|
+
if (resolvedHome &&
|
|
129
|
+
resolvedEvidenceDir &&
|
|
130
|
+
isWithinRoot(resolvedEvidenceDir, resolvedHome)) {
|
|
131
|
+
const resolvedManifest = realpathOrNull(join(resolvedEvidenceDir, 'manifest.json'));
|
|
132
|
+
if (resolvedManifest && isWithinRoot(resolvedManifest, resolvedEvidenceDir)) {
|
|
133
|
+
const raw = JSON.parse(readFileSync(resolvedManifest, 'utf-8'));
|
|
134
|
+
// Pass the UNRESOLVED evidenceDir so returned paths share the
|
|
135
|
+
// same root form the evidence route's prefix check expects.
|
|
136
|
+
const normalized = normalizeEvidencePaths(raw, evidenceDir);
|
|
137
|
+
// Deduplicate: same type + relativePath means the same evidence entry
|
|
138
|
+
const seen = new Set();
|
|
139
|
+
evidence = normalized.filter((e) => {
|
|
140
|
+
const key = `${e.type}:${e.relativePath}`;
|
|
141
|
+
if (seen.has(key))
|
|
142
|
+
return false;
|
|
143
|
+
seen.add(key);
|
|
144
|
+
return true;
|
|
145
|
+
});
|
|
146
|
+
}
|
|
104
147
|
}
|
|
105
148
|
}
|
|
106
149
|
catch {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"open-folder.d.ts","sourceRoot":"","sources":["../../../../../../src/presentation/web/app/actions/open-folder.ts"],"names":[],"mappings":"AAiBA,wBAAsB,UAAU,CAC9B,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"open-folder.d.ts","sourceRoot":"","sources":["../../../../../../src/presentation/web/app/actions/open-folder.ts"],"names":[],"mappings":"AAiBA,wBAAsB,UAAU,CAC9B,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAyC9D"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
'use server';
|
|
2
|
-
import {
|
|
2
|
+
import { realpathSync } from 'node:fs';
|
|
3
3
|
import { platform } from 'node:os';
|
|
4
4
|
import { isAbsolute, normalize } from 'node:path';
|
|
5
5
|
import { spawn } from 'node:child_process';
|
|
@@ -17,7 +17,15 @@ export async function openFolder(repositoryPath) {
|
|
|
17
17
|
return { success: false, error: 'repositoryPath must be an absolute path' };
|
|
18
18
|
}
|
|
19
19
|
try {
|
|
20
|
-
|
|
20
|
+
// Resolve through realpath() up-front. All subsequent uses of the path
|
|
21
|
+
// reference this symlink-resolved absolute value, not the raw user input.
|
|
22
|
+
// This eliminates path-injection via symlinks and is the sanitizer that
|
|
23
|
+
// CodeQL's js/path-injection analysis recognizes.
|
|
24
|
+
let resolvedPath;
|
|
25
|
+
try {
|
|
26
|
+
resolvedPath = realpathSync(repositoryPath);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
21
29
|
return { success: false, error: 'Directory not found' };
|
|
22
30
|
}
|
|
23
31
|
const entry = FOLDER_COMMANDS[platform()];
|
|
@@ -29,14 +37,14 @@ export async function openFolder(repositoryPath) {
|
|
|
29
37
|
}
|
|
30
38
|
// Normalize to platform-native separators — explorer.exe on Windows
|
|
31
39
|
// does not understand forward-slash paths and falls back to Documents.
|
|
32
|
-
const nativePath = normalize(
|
|
40
|
+
const nativePath = normalize(resolvedPath);
|
|
33
41
|
const child = spawn(entry.cmd, entry.args(nativePath), {
|
|
34
42
|
detached: true,
|
|
35
43
|
stdio: 'ignore',
|
|
36
44
|
});
|
|
37
45
|
child.on('error', () => undefined); // Prevent uncaught exception on spawn failure
|
|
38
46
|
child.unref();
|
|
39
|
-
return { success: true, path:
|
|
47
|
+
return { success: true, path: resolvedPath };
|
|
40
48
|
}
|
|
41
49
|
catch (error) {
|
|
42
50
|
const message = error instanceof Error ? error.message : 'Failed to open folder';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"open-shell.d.ts","sourceRoot":"","sources":["../../../../../../src/presentation/web/app/actions/open-shell.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"open-shell.d.ts","sourceRoot":"","sources":["../../../../../../src/presentation/web/app/actions/open-shell.ts"],"names":[],"mappings":"AAmDA,UAAU,cAAc;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,SAAS,CAC7B,KAAK,EAAE,cAAc,GACpB,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CA2F9E"}
|
|
@@ -1,10 +1,34 @@
|
|
|
1
1
|
'use server';
|
|
2
|
-
import {
|
|
2
|
+
import { realpathSync } from 'node:fs';
|
|
3
3
|
import { platform } from 'node:os';
|
|
4
4
|
import { isAbsolute } from 'node:path';
|
|
5
5
|
import { spawn } from 'node:child_process';
|
|
6
6
|
import { computeWorktreePath } from '../../lib/core-utils.js';
|
|
7
7
|
import { resolve } from '../../lib/server-container.js';
|
|
8
|
+
/**
|
|
9
|
+
* Resolve the target path through realpath() so that any symlink traversal
|
|
10
|
+
* happens up-front and the resulting absolute path is the authoritative
|
|
11
|
+
* value used for all subsequent spawn operations. Returns null if the path
|
|
12
|
+
* does not exist or cannot be resolved.
|
|
13
|
+
*/
|
|
14
|
+
function resolveTargetPath(repositoryPath, branch) {
|
|
15
|
+
try {
|
|
16
|
+
const base = branch ? computeWorktreePath(repositoryPath, branch) : repositoryPath;
|
|
17
|
+
return realpathSync(base);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* POSIX shell-escape a path for safe inclusion in a shell:true command string.
|
|
25
|
+
* Wraps in single quotes and escapes embedded single quotes using the
|
|
26
|
+
* standard '\'' pattern. All tool configurations that opt into shell:true
|
|
27
|
+
* use POSIX-style commands (`cd {dir} && exec <tool>`) and run on Unix only.
|
|
28
|
+
*/
|
|
29
|
+
function shellEscapePosixPath(p) {
|
|
30
|
+
return `'${p.replace(/'/g, `'\\''`)}'`;
|
|
31
|
+
}
|
|
8
32
|
// Fallback commands for the "system" terminal when no tool metadata entry exists.
|
|
9
33
|
// Uses a record lookup instead of if/else to prevent the bundler from
|
|
10
34
|
// tree-shaking platform branches at build time. Turbopack evaluates
|
|
@@ -28,9 +52,12 @@ export async function openShell(input) {
|
|
|
28
52
|
const settings = await loadSettings.execute();
|
|
29
53
|
const shell = settings.environment.shellPreference;
|
|
30
54
|
const terminalPref = settings.environment.terminalPreference ?? 'system';
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
55
|
+
// Resolve the target path through realpath() up-front. From this point
|
|
56
|
+
// on, `targetPath` is the authoritative, symlink-resolved absolute path
|
|
57
|
+
// used for every spawn call — never the raw user-supplied value.
|
|
58
|
+
const targetPath = resolveTargetPath(repositoryPath, branch);
|
|
59
|
+
if (!targetPath) {
|
|
60
|
+
return { success: false, error: 'Path does not exist' };
|
|
34
61
|
}
|
|
35
62
|
// Try to find the terminal in tool metadata via DI container.
|
|
36
63
|
// Using DI (not a direct import from tool-metadata) ensures that
|
|
@@ -42,9 +69,15 @@ export async function openShell(input) {
|
|
|
42
69
|
const service = resolve('IToolInstallerService');
|
|
43
70
|
const config = service.getTerminalOpenConfig(terminalPref);
|
|
44
71
|
if (config?.openDirectory.includes('{dir}')) {
|
|
45
|
-
const resolved = config.openDirectory.replace('{dir}', targetPath);
|
|
46
72
|
if (config.shell) {
|
|
47
|
-
|
|
73
|
+
// For shell:true tools (claude-code, codex-cli, etc.) the tool
|
|
74
|
+
// config is a POSIX shell string like `cd {dir} && exec claude`.
|
|
75
|
+
// Shell-escape the path to prevent command injection: a malicious
|
|
76
|
+
// path like `/tmp; rm -rf /` becomes `'/tmp; rm -rf /'` which the
|
|
77
|
+
// shell treats as a single literal argument to `cd`.
|
|
78
|
+
const escapedPath = shellEscapePosixPath(targetPath);
|
|
79
|
+
const command = config.openDirectory.replaceAll('{dir}', escapedPath);
|
|
80
|
+
const child = spawn(command, [], {
|
|
48
81
|
detached: true,
|
|
49
82
|
stdio: 'ignore',
|
|
50
83
|
shell: true,
|
|
@@ -53,7 +86,13 @@ export async function openShell(input) {
|
|
|
53
86
|
child.unref();
|
|
54
87
|
}
|
|
55
88
|
else {
|
|
56
|
-
|
|
89
|
+
// For non-shell tools (alacritty, kitty, etc.) the config is a
|
|
90
|
+
// whitespace-separated command. Split first, then substitute {dir}
|
|
91
|
+
// INTO AN ARGV ELEMENT (never back into a concatenated string).
|
|
92
|
+
// CodeQL recognizes the argv-form of spawn as sanitized input.
|
|
93
|
+
const tokens = config.openDirectory.split(/\s+/).filter(Boolean);
|
|
94
|
+
const command = tokens[0];
|
|
95
|
+
const args = tokens.slice(1).map((t) => t.replaceAll('{dir}', targetPath));
|
|
57
96
|
const child = spawn(command, args, {
|
|
58
97
|
detached: true,
|
|
59
98
|
stdio: 'ignore',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/app/api/agent-events/route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;
|
|
1
|
+
{"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/app/api/agent-events/route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAUH,eAAO,MAAM,OAAO,kBAAkB,CAAC;AAKvC,+EAA+E;AAC/E,YAAY,EAAE,uBAAuB,EAAE,MAAM,yEAAyE,CAAC;AAEvH,wBAAgB,GAAG,CAAC,OAAO,EAAE,OAAO,GAAG,QAAQ,CA+F9C"}
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* - Cleans up intervals on client disconnect
|
|
17
17
|
*/
|
|
18
18
|
import { resolve } from '../../../lib/server-container.js';
|
|
19
|
+
import { apiError } from '../../../lib/api-helpers.js';
|
|
19
20
|
// Force dynamic — SSE streams must never be statically optimized or cached
|
|
20
21
|
export const dynamic = 'force-dynamic';
|
|
21
22
|
const POLL_INTERVAL_MS = 2_000;
|
|
@@ -100,11 +101,6 @@ export function GET(request) {
|
|
|
100
101
|
});
|
|
101
102
|
}
|
|
102
103
|
catch (error) {
|
|
103
|
-
|
|
104
|
-
console.error('[SSE route] GET handler error:', error);
|
|
105
|
-
return new Response(JSON.stringify({ error: String(error) }), {
|
|
106
|
-
status: 500,
|
|
107
|
-
headers: { 'Content-Type': 'application/json' },
|
|
108
|
-
});
|
|
104
|
+
return apiError(500, 'Failed to open agent events stream', error);
|
|
109
105
|
}
|
|
110
106
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../../../../src/presentation/web/app/api/attachments/upload-from-path/route.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../../../../src/presentation/web/app/api/attachments/upload-from-path/route.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAoF3C,wBAAsB,IAAI,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,YAAY,CAAC,CA8ElE"}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server';
|
|
2
|
-
import { readFile
|
|
3
|
-
import { extname, basename, resolve as resolvePath
|
|
2
|
+
import { readFile } from 'fs/promises';
|
|
3
|
+
import { extname, basename, resolve as resolvePath } from 'path';
|
|
4
4
|
import { homedir } from 'node:os';
|
|
5
5
|
import { resolve } from '../../../../lib/server-container.js';
|
|
6
|
+
import { realpathWithinAllowedRootsAsync } from '../../../../lib/path-sanitizers.js';
|
|
6
7
|
const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10 MB
|
|
7
8
|
const ALLOWED_EXTENSIONS = new Set([
|
|
8
9
|
'.png',
|
|
@@ -91,25 +92,28 @@ export async function POST(request) {
|
|
|
91
92
|
: 'Files without an extension are not allowed',
|
|
92
93
|
}, { status: 400 });
|
|
93
94
|
}
|
|
94
|
-
// Path containment: resolve symlinks
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
95
|
+
// Path containment: resolve symlinks and verify within allowed roots
|
|
96
|
+
// in a single helper call. `physicalPath` is the single authoritative
|
|
97
|
+
// value — every subsequent filesystem call uses it, never the raw
|
|
98
|
+
// `path` input. This eliminates the TOCTOU window between the
|
|
99
|
+
// containment check and the read, and gives CodeQL a clean
|
|
100
|
+
// sanitizer→sink flow for js/path-injection.
|
|
101
|
+
const physicalPath = await realpathWithinAllowedRootsAsync(resolvePath(path), [
|
|
102
|
+
process.cwd(),
|
|
103
|
+
homedir(),
|
|
104
|
+
]);
|
|
105
|
+
if (!physicalPath) {
|
|
106
|
+
// Could be a missing file OR a path outside the allowed roots. Return
|
|
107
|
+
// a single generic 404 either way — leaking the distinction would
|
|
108
|
+
// let an attacker probe for existence of arbitrary paths on the host.
|
|
100
109
|
return NextResponse.json({ error: 'File not found or unreadable' }, { status: 404 });
|
|
101
110
|
}
|
|
102
|
-
const allowedRoots = [
|
|
103
|
-
await realpath(process.cwd()).catch(() => process.cwd()),
|
|
104
|
-
await realpath(homedir()).catch(() => homedir()),
|
|
105
|
-
];
|
|
106
|
-
const isAllowed = allowedRoots.some((root) => physicalPath === root || physicalPath.startsWith(root + sep));
|
|
107
|
-
if (!isAllowed) {
|
|
108
|
-
return NextResponse.json({ error: 'Access denied' }, { status: 403 });
|
|
109
|
-
}
|
|
110
111
|
let buffer;
|
|
111
112
|
try {
|
|
112
|
-
|
|
113
|
+
// Read using the validated physicalPath, NOT a re-resolution of `path`.
|
|
114
|
+
// This closes the TOCTOU gap where a symlink could be swapped between
|
|
115
|
+
// the containment check and the read.
|
|
116
|
+
buffer = await readFile(physicalPath);
|
|
113
117
|
}
|
|
114
118
|
catch {
|
|
115
119
|
return NextResponse.json({ error: 'File not found or unreadable' }, { status: 404 });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/app/api/deployment-logs/route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;
|
|
1
|
+
{"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../../../src/presentation/web/app/api/deployment-logs/route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAUH,eAAO,MAAM,OAAO,kBAAkB,CAAC;AAIvC,wBAAgB,GAAG,CAAC,OAAO,EAAE,OAAO,GAAG,QAAQ,CAkF9C"}
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* - Cleans up EventEmitter subscription on client disconnect
|
|
12
12
|
*/
|
|
13
13
|
import { resolve } from '../../../lib/server-container.js';
|
|
14
|
+
import { apiError } from '../../../lib/api-helpers.js';
|
|
14
15
|
// Force dynamic — SSE streams must never be statically optimized or cached
|
|
15
16
|
export const dynamic = 'force-dynamic';
|
|
16
17
|
const HEARTBEAT_INTERVAL_MS = 30_000;
|
|
@@ -84,11 +85,6 @@ export function GET(request) {
|
|
|
84
85
|
});
|
|
85
86
|
}
|
|
86
87
|
catch (error) {
|
|
87
|
-
|
|
88
|
-
console.error('[SSE route] GET /api/deployment-logs error:', error);
|
|
89
|
-
return new Response(JSON.stringify({ error: String(error) }), {
|
|
90
|
-
status: 500,
|
|
91
|
-
headers: { 'Content-Type': 'application/json' },
|
|
92
|
-
});
|
|
88
|
+
return apiError(500, 'Failed to open deployment logs stream', error);
|
|
93
89
|
}
|
|
94
90
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../../../../src/presentation/web/app/api/directory/list/route.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../../../../src/presentation/web/app/api/directory/list/route.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAc3C,wBAAsB,GAAG,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,YAAY,CAAC,CAyGjE"}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server';
|
|
2
|
-
import { readdir,
|
|
2
|
+
import { readdir, stat } from 'node:fs/promises';
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { apiError } from '../../../../lib/api-helpers.js';
|
|
6
|
+
import { realpathWithinAllowedRootsAsync } from '../../../../lib/path-sanitizers.js';
|
|
6
7
|
export async function GET(request) {
|
|
7
8
|
const url = new URL(request.url);
|
|
8
9
|
const rawPath = url.searchParams.get('path') ?? homedir();
|
|
@@ -10,25 +11,33 @@ export async function GET(request) {
|
|
|
10
11
|
if (!path.isAbsolute(rawPath)) {
|
|
11
12
|
return NextResponse.json({ error: 'Path must be absolute' }, { status: 400 });
|
|
12
13
|
}
|
|
13
|
-
|
|
14
|
-
//
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
// Keep two names for the same directory:
|
|
15
|
+
//
|
|
16
|
+
// - `displayPath` is what the client originally asked for (after
|
|
17
|
+
// path.resolve normalization). It's what the UI shows in the
|
|
18
|
+
// breadcrumb and what we echo back as `currentPath`. This matches
|
|
19
|
+
// user expectation — e.g. on macOS the user sees /tmp/foo, not the
|
|
20
|
+
// realpath'd /private/tmp/foo.
|
|
21
|
+
//
|
|
22
|
+
// - `physicalPath` is the realpath'd absolute path produced by the
|
|
23
|
+
// path-containment sanitizer. Every FILESYSTEM operation (stat,
|
|
24
|
+
// readdir, per-entry joins) uses this value, NEVER the display
|
|
25
|
+
// path. That gives CodeQL a clean sanitizer→sink flow for
|
|
26
|
+
// js/path-injection and closes the TOCTOU window where a symlink
|
|
27
|
+
// could be swapped between the containment check and the read.
|
|
28
|
+
const displayPath = path.resolve(rawPath);
|
|
29
|
+
const physicalPath = await realpathWithinAllowedRootsAsync(displayPath, [
|
|
30
|
+
process.cwd(),
|
|
31
|
+
homedir(),
|
|
32
|
+
]);
|
|
33
|
+
if (!physicalPath) {
|
|
34
|
+
// Could be a missing directory OR a path outside the allowed roots.
|
|
35
|
+
// Return a uniform 404 either way so we don't leak existence of
|
|
36
|
+
// arbitrary paths on the host.
|
|
20
37
|
return NextResponse.json({ error: 'Directory not found' }, { status: 404 });
|
|
21
38
|
}
|
|
22
|
-
const allowedRoots = [
|
|
23
|
-
await realpath(process.cwd()).catch(() => process.cwd()),
|
|
24
|
-
await realpath(homedir()).catch(() => homedir()),
|
|
25
|
-
];
|
|
26
|
-
const isAllowed = allowedRoots.some((root) => physicalPath === root || physicalPath.startsWith(root + path.sep));
|
|
27
|
-
if (!isAllowed) {
|
|
28
|
-
return NextResponse.json({ error: 'Access denied' }, { status: 403 });
|
|
29
|
-
}
|
|
30
39
|
try {
|
|
31
|
-
const dirStat = await stat(
|
|
40
|
+
const dirStat = await stat(physicalPath);
|
|
32
41
|
if (!dirStat.isDirectory()) {
|
|
33
42
|
return NextResponse.json({ error: 'Path is not a directory' }, { status: 400 });
|
|
34
43
|
}
|
|
@@ -40,29 +49,35 @@ export async function GET(request) {
|
|
|
40
49
|
return apiError(500, 'Failed to access directory', error);
|
|
41
50
|
}
|
|
42
51
|
try {
|
|
43
|
-
const dirents = await readdir(
|
|
52
|
+
const dirents = await readdir(physicalPath, { withFileTypes: true });
|
|
44
53
|
const entries = [];
|
|
45
54
|
const entryPromises = dirents.map(async (dirent) => {
|
|
46
55
|
if (!showHidden && dirent.name.startsWith('.')) {
|
|
47
56
|
return null;
|
|
48
57
|
}
|
|
49
|
-
|
|
58
|
+
// Build two path forms per entry: a physical path for stat() and a
|
|
59
|
+
// display path for the response payload. The client navigates using
|
|
60
|
+
// the display path on subsequent requests, so it must match the
|
|
61
|
+
// user-visible form of the parent directory — never the realpath'd
|
|
62
|
+
// form, which would surprise the user with /private/tmp on macOS.
|
|
63
|
+
const physicalEntry = path.join(physicalPath, dirent.name);
|
|
64
|
+
const displayEntry = path.join(displayPath, dirent.name);
|
|
50
65
|
try {
|
|
51
66
|
if (dirent.isDirectory()) {
|
|
52
|
-
const entryStat = await stat(
|
|
67
|
+
const entryStat = await stat(physicalEntry);
|
|
53
68
|
return {
|
|
54
69
|
name: dirent.name,
|
|
55
|
-
path:
|
|
70
|
+
path: displayEntry,
|
|
56
71
|
isDirectory: true,
|
|
57
72
|
updatedAt: entryStat.mtime.toISOString(),
|
|
58
73
|
};
|
|
59
74
|
}
|
|
60
75
|
if (dirent.isSymbolicLink()) {
|
|
61
|
-
const entryStat = await stat(
|
|
76
|
+
const entryStat = await stat(physicalEntry);
|
|
62
77
|
if (entryStat.isDirectory()) {
|
|
63
78
|
return {
|
|
64
79
|
name: dirent.name,
|
|
65
|
-
path:
|
|
80
|
+
path: displayEntry,
|
|
66
81
|
isDirectory: true,
|
|
67
82
|
updatedAt: entryStat.mtime.toISOString(),
|
|
68
83
|
};
|
|
@@ -80,7 +95,7 @@ export async function GET(request) {
|
|
|
80
95
|
entries.push(result);
|
|
81
96
|
}
|
|
82
97
|
}
|
|
83
|
-
return NextResponse.json({ entries, currentPath:
|
|
98
|
+
return NextResponse.json({ entries, currentPath: displayPath });
|
|
84
99
|
}
|
|
85
100
|
catch (error) {
|
|
86
101
|
return apiError(500, 'Failed to read directory', error);
|