3 Commits

Author SHA1 Message Date
pezkuwichain b012fcaaac fix(security): patch ws (high DoS) and dompurify (XSS) via npm audit fix
Unblocks the deploy security gate — production deps only, no major bumps.
2026-06-15 18:07:37 -07:00
pezkuwichain 7a1d3e7917 feat(social): wire DKS Rojname → news.pex.mom and Events → kurdishtts.pezkiwi.app
- KurdMedia: DKS Rojname channel now links to the Dijital Kurdistan News site
- Social Events item opens the Kurdish TTS app (was coming-soon/locked)
- applies across mobile drawer, mobile home and desktop landing pallets
2026-06-15 17:54:19 -07:00
pezkuwichain 2ee3caac0d fix(ci): audit only production deps in the deploy gate (--omit=dev) (#18)
The security-audit gate ran 'npm audit --audit-level=high' over all deps,
so newly-published advisories on build-only tooling (esbuild, elliptic via
vite-plugin-node-polyfills, etc.) repeatedly blocked production deploys
even though that code ships to no user. Scope the gate to production
dependencies with --omit=dev. Verified: 'npm audit --audit-level=high
--omit=dev' → 0 vulnerabilities. TruffleHog secret scanning is unchanged.
2026-06-12 23:39:55 -07:00
6 changed files with 47 additions and 25 deletions
+5 -2
View File
@@ -628,11 +628,14 @@ jobs:
with: with:
node-version: '20' node-version: '20'
- name: Web — npm audit (high + critical) - name: Web — npm audit (high + critical, production deps only)
working-directory: ./web working-directory: ./web
run: | run: |
npm install npm install
npm audit --audit-level=high # Audit only production dependencies. Build tooling (vite, esbuild,
# vite-plugin-node-polyfills → elliptic, etc.) ships to no user, and
# advisories on those dev deps kept blocking production deploys.
npm audit --audit-level=high --omit=dev
- name: TruffleHog — PR diff (verified secrets only) - name: TruffleHog — PR diff (verified secrets only)
if: github.event_name == 'pull_request' if: github.event_name == 'pull_request'
+6 -6
View File
@@ -5457,9 +5457,9 @@
} }
}, },
"node_modules/@walletconnect/jsonrpc-ws-connection/node_modules/ws": { "node_modules/@walletconnect/jsonrpc-ws-connection/node_modules/ws": {
"version": "7.5.10", "version": "7.5.11",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.11.tgz",
"integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "integrity": "sha512-zS54Oen9bITtp7kp2XM3AydrCIq1D+HwJOuH+c+e4LfpL/lotP5osijd+UoMnxwAam1GN8R4KtLAyIrIcBNpiA==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=8.3.0" "node": ">=8.3.0"
@@ -7651,9 +7651,9 @@
} }
}, },
"node_modules/dompurify": { "node_modules/dompurify": {
"version": "3.4.2", "version": "3.4.10",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.2.tgz", "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.10.tgz",
"integrity": "sha512-lHeS9SA/IKeIFFyYciHBr2n0v1VMPlSj843HdLOwjb2OxNwdq9Xykxqhk+FE42MzAdHvInbAolSE4mhahPpjXA==", "integrity": "sha512-0xzNv0e7oYC6yyuOGZIABPM4qtg3QxLFniDNPP4ZP90wR8Yq3zgwpRbrNiT4N3IKqDbbYFEJLV+JWEs19aZ//w==",
"license": "(MPL-2.0 OR Apache-2.0)", "license": "(MPL-2.0 OR Apache-2.0)",
"optionalDependencies": { "optionalDependencies": {
"@types/trusted-types": "^2.0.7" "@types/trusted-types": "^2.0.7"
+1 -1
View File
@@ -110,7 +110,7 @@ const APP_SECTIONS: AppSection[] = [
{ title: 'mobile.app.whatsKurd', icon: '💬', route: '/social/whatskurd' }, { title: 'mobile.app.whatsKurd', icon: '💬', route: '/social/whatskurd' },
{ title: 'mobile.app.forum', icon: '📰', route: '/forum' }, { title: 'mobile.app.forum', icon: '📰', route: '/forum' },
{ title: 'mobile.app.kurdMedia', icon: '📺', route: '/social/kurdmedia' }, { title: 'mobile.app.kurdMedia', icon: '📺', route: '/social/kurdmedia' },
{ title: 'mobile.app.events', icon: '📅', route: '/forum', comingSoon: true }, { title: 'mobile.app.events', icon: '📅', route: '/forum', href: 'https://kurdishtts.pezkiwi.app' },
{ title: 'mobile.app.help', icon: '❓', route: '/help' }, { title: 'mobile.app.help', icon: '❓', route: '/help' },
{ title: 'mobile.app.music', icon: '🎵', route: '/forum', comingSoon: true }, { title: 'mobile.app.music', icon: '🎵', route: '/forum', comingSoon: true },
{ title: 'mobile.app.rewshenbir',icon: '📡', imgIcon: '/rewshenbir-icon.png', route: '/rewshenbir', href: 'https://rewshenbir.pezkuwi.app' }, { title: 'mobile.app.rewshenbir',icon: '📡', imgIcon: '/rewshenbir-icon.png', route: '/rewshenbir', href: 'https://rewshenbir.pezkuwi.app' },
+1 -1
View File
@@ -87,7 +87,7 @@ const APP_SECTIONS: AppSection[] = [
{ title: 'mobile.app.whatsKurd', icon: '💬', route: '/social/whatskurd' }, { title: 'mobile.app.whatsKurd', icon: '💬', route: '/social/whatskurd' },
{ title: 'mobile.app.forum', icon: '📰', route: '/forum' }, { title: 'mobile.app.forum', icon: '📰', route: '/forum' },
{ title: 'mobile.app.kurdMedia', icon: '📺', route: '/social/kurdmedia' }, { title: 'mobile.app.kurdMedia', icon: '📺', route: '/social/kurdmedia' },
{ title: 'mobile.app.events', icon: '📅', route: '/forum', comingSoon: true }, { title: 'mobile.app.events', icon: '📅', route: '/forum', href: 'https://kurdishtts.pezkiwi.app' },
{ title: 'mobile.app.help', icon: '❓', route: '/help' }, { title: 'mobile.app.help', icon: '❓', route: '/help' },
{ title: 'mobile.app.music', icon: '🎵', route: '/forum', comingSoon: true }, { title: 'mobile.app.music', icon: '🎵', route: '/forum', comingSoon: true },
{ title: 'mobile.app.rewshenbir', icon: '📡', imgIcon: '/rewshenbir-icon.png', route: '/rewshenbir', href: 'https://rewshenbir.pezkuwi.app' }, { title: 'mobile.app.rewshenbir', icon: '📡', imgIcon: '/rewshenbir-icon.png', route: '/rewshenbir', href: 'https://rewshenbir.pezkuwi.app' },
@@ -1099,7 +1099,7 @@ const LandingPageDesktop: React.FC = () => {
<PalletItem icon="lp-i-chat" label={t('landing.pallets.whatskurd')} to="/social/whatskurd" requiresLogin /> <PalletItem icon="lp-i-chat" label={t('landing.pallets.whatskurd')} to="/social/whatskurd" requiresLogin />
<PalletItem icon="lp-i-forum" label={t('landing.pallets.forum')} to="/forum" /> <PalletItem icon="lp-i-forum" label={t('landing.pallets.forum')} to="/forum" />
<PalletItem icon="lp-i-media" label={t('landing.pallets.kurdmedia')} to="/social/kurdmedia" requiresLogin /> <PalletItem icon="lp-i-media" label={t('landing.pallets.kurdmedia')} to="/social/kurdmedia" requiresLogin />
<PalletItem icon="lp-i-cal" label={t('landing.pallets.events')} locked /> <PalletItem icon="lp-i-cal" label={t('landing.pallets.events')} external="https://kurdishtts.pezkiwi.app" />
<PalletItem icon="lp-i-help" label={t('landing.pallets.help')} to="/help" /> <PalletItem icon="lp-i-help" label={t('landing.pallets.help')} to="/help" />
<PalletItem icon="lp-i-music" label={t('landing.pallets.music')} locked /> <PalletItem icon="lp-i-music" label={t('landing.pallets.music')} locked />
<PalletItem imgSrc="/rewshenbir-icon.png" label={t('landing.pallets.rewshenbir')} external="https://rewshenbir.pezkuwi.app" /> <PalletItem imgSrc="/rewshenbir-icon.png" label={t('landing.pallets.rewshenbir')} external="https://rewshenbir.pezkuwi.app" />
+33 -14
View File
@@ -9,6 +9,7 @@ interface MediaChannel {
descriptionKu: string; descriptionKu: string;
description: string; description: string;
color: string; color: string;
url?: string;
} }
interface SocialPlatform { interface SocialPlatform {
@@ -21,7 +22,7 @@ interface SocialPlatform {
const MEDIA_CHANNELS: MediaChannel[] = [ const MEDIA_CHANNELS: MediaChannel[] = [
{ id: 'dkstv', nameKu: 'DKS TV', name: 'DKS TV', icon: '📺', descriptionKu: 'Televizyona Dewleta Dijîtal a Kurdistanê', description: 'Digital Kurdistan State Television', color: '#E53935' }, { id: 'dkstv', nameKu: 'DKS TV', name: 'DKS TV', icon: '📺', descriptionKu: 'Televizyona Dewleta Dijîtal a Kurdistanê', description: 'Digital Kurdistan State Television', color: '#E53935' },
{ id: 'dksgzt', nameKu: 'DKS Rojname', name: 'DKS Gazette', icon: '📰', descriptionKu: 'Nûçe û Daxuyaniyên Fermî', description: 'Official News & Announcements', color: '#1E88E5' }, { id: 'dksgzt', nameKu: 'DKS Rojname', name: 'DKS Gazette', icon: '📰', descriptionKu: 'Nûçe û Daxuyaniyên Fermî', description: 'Official News & Announcements', color: '#1E88E5', url: 'https://news.pex.mom' },
{ id: 'dksradio', nameKu: 'DKS Radyo', name: 'DKS Radio', icon: '📻', descriptionKu: 'Radyoya Dewleta Dijîtal a Kurdistanê', description: 'Digital Kurdistan State Radio', color: '#7B1FA2' }, { id: 'dksradio', nameKu: 'DKS Radyo', name: 'DKS Radio', icon: '📻', descriptionKu: 'Radyoya Dewleta Dijîtal a Kurdistanê', description: 'Digital Kurdistan State Radio', color: '#7B1FA2' },
{ id: 'dksmusic', nameKu: 'DKS Muzîk', name: 'DKS Music', icon: '🎵', descriptionKu: 'Weşana Muzîka Kurdî', description: 'Kurdish Music Streaming', color: '#00897B' }, { id: 'dksmusic', nameKu: 'DKS Muzîk', name: 'DKS Music', icon: '🎵', descriptionKu: 'Weşana Muzîka Kurdî', description: 'Kurdish Music Streaming', color: '#00897B' },
{ id: 'dkspodcast',nameKu: 'DKS Podcast', name: 'DKS Podcast', icon: '🎙️', descriptionKu: 'Podcast û Gotûbêjên Kurdî', description: 'Kurdish Podcasts & Talks', color: '#F4511E' }, { id: 'dkspodcast',nameKu: 'DKS Podcast', name: 'DKS Podcast', icon: '🎙️', descriptionKu: 'Podcast û Gotûbêjên Kurdî', description: 'Kurdish Podcasts & Talks', color: '#F4511E' },
@@ -71,20 +72,38 @@ export default function KurdMediaPage() {
<p className="text-sm text-gray-300 mb-1">{t('kurdMedia.channels.desc', 'Weşanên fermî yên Dewleta Dijîtal a Kurdistanê.')}</p> <p className="text-sm text-gray-300 mb-1">{t('kurdMedia.channels.desc', 'Weşanên fermî yên Dewleta Dijîtal a Kurdistanê.')}</p>
<p className="text-xs text-gray-500 mb-4">{t('kurdMedia.channels.descEn', 'Official broadcasts of Digital Kurdistan State. TV, radio, news and more.')}</p> <p className="text-xs text-gray-500 mb-4">{t('kurdMedia.channels.descEn', 'Official broadcasts of Digital Kurdistan State. TV, radio, news and more.')}</p>
<div className="space-y-3"> <div className="space-y-3">
{MEDIA_CHANNELS.map(ch => ( {MEDIA_CHANNELS.map(ch => {
<div key={ch.id} className="flex items-center gap-3 bg-gray-800 rounded-xl p-3"> const inner = (
<div className="w-12 h-12 rounded-xl flex items-center justify-center text-2xl flex-shrink-0" style={{ backgroundColor: ch.color }}> <>
{ch.icon} <div className="w-12 h-12 rounded-xl flex items-center justify-center text-2xl flex-shrink-0" style={{ backgroundColor: ch.color }}>
{ch.icon}
</div>
<div className="flex-1 min-w-0">
<p className="font-semibold text-white text-sm">{ch.nameKu}</p>
<p className="text-xs text-gray-400 truncate">{ch.descriptionKu}</p>
</div>
{ch.url ? (
<span className="text-[10px] font-bold text-green-400 bg-green-400/10 px-2 py-1 rounded-full flex-shrink-0">
{t('kurdMedia.open', 'Open')}
</span>
) : (
<span className="text-[10px] font-bold text-yellow-400 bg-yellow-400/10 px-2 py-1 rounded-full flex-shrink-0">
{t('kurdMedia.soon', 'Soon')}
</span>
)}
</>
);
return ch.url ? (
<a key={ch.id} href={ch.url} target="_blank" rel="noopener noreferrer"
className="flex items-center gap-3 bg-gray-800 rounded-xl p-3 hover:bg-gray-700 transition-colors">
{inner}
</a>
) : (
<div key={ch.id} className="flex items-center gap-3 bg-gray-800 rounded-xl p-3">
{inner}
</div> </div>
<div className="flex-1 min-w-0"> );
<p className="font-semibold text-white text-sm">{ch.nameKu}</p> })}
<p className="text-xs text-gray-400 truncate">{ch.descriptionKu}</p>
</div>
<span className="text-[10px] font-bold text-yellow-400 bg-yellow-400/10 px-2 py-1 rounded-full flex-shrink-0">
{t('kurdMedia.soon', 'Soon')}
</span>
</div>
))}
</div> </div>
</div> </div>
</div> </div>