diff --git a/.gitignore b/.gitignore index 7c0db9b..b40ddab 100644 --- a/.gitignore +++ b/.gitignore @@ -37,4 +37,10 @@ dist-ssr .temp-audio/ .temp-audio-transcription/ +# Transcription / pipeline intermediate artifacts +# (These may embed Nostr events with upstream URLs; never commit them.) +.episodes-to-transcribe.json +.transcript-mapping.json +.show-notes-mapping.json + # Transcription temp files diff --git a/.show-notes-mapping.json b/.show-notes-mapping.json deleted file mode 100644 index 9dce8a9..0000000 --- a/.show-notes-mapping.json +++ /dev/null @@ -1,9 +0,0 @@ -[ - { - "dTag": "7c84973e-0c76-4d29-b12e-eff864e4efd0", - "title": "6/24 Ray Co Commissioners' Mtg", - "showNotes": "# County Commission Meeting Summary\n\n## Opening Proceedings\n\nThe meeting opened with the Pledge of Allegiance and an invocation prayer focused on guidance for county officials and protection for the community, including first responders, schools, farmers, and businesses.\n\n## Public Comment: 84th Street Road Conditions\n\nA resident addressed the commission regarding the deteriorated condition of 84th Street between South Point Drive and West Highway 210. Originally chip and sealed when the speaker moved to the area in 1999, the road has degraded to gravel and lime waste screenings. The resident reported significant dust issues affecting 21 residences in the area, with prevailing winds causing problems for homes on both sides of the street.\n\nThe resident presented a petition signed by 18 residents requesting road improvements. Additional concerns included:\n\n- **Drainage problems:** Non-existent ditches in some areas cause gravel to wash away during heavy rains.\n- **Intersection deterioration:** Potholes and breaking asphalt at the intersection of 210 and 84th Street, partially located in a state right-of-way, creating jurisdictional maintenance confusion.\n- **Historic damage:** The road was heavily damaged several years ago when bridge closures on T Highway forced rerouted grain truck traffic onto 84th Street.\n\nThe resident acknowledged the commission's apparent reluctance to use chip and seal but urged consideration of more permanent solutions, noting the hilly terrain complicates maintenance.\n\n## Public Comment: Follow-Up Questions from Kurt Croy\n\nKurt Croy returned with several follow-up items from a previous meeting:\n\n- **Building insurance:** Confirmed the $977,000 figure represents replacement cost, not the $97,000 originally questioned.\n- **Tabling process:** Requested clarification on the procedural timeline between a March 11 appearance and an April 16 reconsideration, with the commissioner expected to review the process.\n- **Road tool:** Inquired about a hydraulic issue with a road maintenance tool; no resolution was discussed on camera.\n- **ARPA funds:** Asked whether any American Rescue Plan Act funds remain; the commission indicated likely none are available.\n\n## Health Insurance Plan Presentation\n\nInsurance representative Johnny presented quarterly claims data for the county's self-funded health plan covering January through June:\n\n- **Financial performance:** The county has deposited $56,000 into the aggregate claims fund year-to-date, with gross paid claims of approximately $30,000. The plan is operating favorably at 53.7% of its attachment pointβ€”the strongest mid-year position in several years.\n- **Stop-loss protection:** The plan includes reinsurance coverage, meaning the county is not liable for claims exceeding deposited amounts.\n- **Prescription drug changes:** The plan is switching its high-cost drug sourcing mechanism mid-year after the current vendor stopped sourcing GLP-1 medications (such as Ozempic and Wegovy) needed by diabetic employees. The transition to a new sourcing program is expected to be seamless for employees while saving the plan money.\n- **Care navigation:** Acknowledged some difficulties with care navigation this year and planned to meet with employees individually to address provider access concerns.\n\n## Courthouse Lightning Damage\n\nThe commission reported that lightning struck the courthouse during a recent storm, damaging United Fiber transmission lines. Affected systems include:\n\n- **Collector's office:** Restored and operational.\n- **Planning and Zoning:** Phones and computers currently down.\n- **Assessor's office:** Experiencing computer and phone issues.\n- **Miscellaneous damage:** Even decorative string lights atop the courthouse were affected.\n\nThe county IT specialist is coordinating with an IT company based in Springfield to diagnose and repair the damage, with work ongoing.\n\n## Additional Notes\n\nThe meeting included brief informal discussion regarding recent severe weather across the region, including heavy rainfall and tornadoes affecting multiple counties and causing bridge damage in some areas.", - "shortSummary": "# County Commission Meeting Summary\n\nThe meeting opened with the Pledge of Allegiance and an invocation. During public comment, a resident of 84th Street presented a petition signed by 18 of 21 area residents requesting road improvements. The road, originally chip and seal, has degraded to gravel, creating significant dust problems. Ditches need work to prevent washout during heavy rains.\n\nA citizen followed up on previous questions regarding a building's $977,000 insurance valuation (confirmed as replacement cost), ARPA fund availability, and road tool repairs.\n\nThe county's health insurance consultant reported the plan is operating favorably, with $56,000 contributed and only $30,000 in claims paid year-to-date. A mid-year change to the high-cost drug sourcing mechanism was announced to ensure continued access to GLP-1 medications.\n\nLightning recently struck the courthouse, damaging phone and computer systems in several offices. IT staff are actively working to restore services, with some offices already back online.", - "success": true - } -] \ No newline at end of file diff --git a/.transcript-mapping.json b/.transcript-mapping.json deleted file mode 100644 index 7866215..0000000 --- a/.transcript-mapping.json +++ /dev/null @@ -1,58 +0,0 @@ -[ - { - "dTag": "7c84973e-0c76-4d29-b12e-eff864e4efd0", - "transcriptPath": "/home/runner/work/podstr/podstr/transcripts/6_24_Ray_Co_Commissioners_Mtg-1782309072.srt", - "transcriptUrl": "https://kurt-croix.github.io/podstr/transcripts/6_24_Ray_Co_Commissioners_Mtg-1782309072.srt", - "success": true, - "event": { - "content": "", - "created_at": 1782309072, - "id": "629859b532b193a1f44d15720ddfb9416f3d8b298ab3a7c194599e6fa1d268ce", - "kind": 30054, - "pubkey": "f38a7f8e088ea727e316b990da29cdf8d13352b5fa095941114b83fefa4b67fa", - "sig": "9e873bfc3befda5784120c6fadcd68e3875a3fae0d8be22eaf3f304c18a7b74c0539cb0452807519b3147fe05c8c4c87141e93f53167fa0efc2d512f71918883", - "tags": [ - [ - "d", - "7c84973e-0c76-4d29-b12e-eff864e4efd0" - ], - [ - "title", - "6/24 Ray Co Commissioners' Mtg" - ], - [ - "summary", - "Ray County Commissioner's meetings streaming" - ], - [ - "audio", - "https://customer-51tzzrmdygiq19h7.cloudflarestream.com/d3de40d13f0361ff5a54a21a956fb231/downloads/default.mp4?filename=6-24-ray-co-commissioners-mtg" - ], - [ - "image", - "https://image.nostr.build/c86663f2aca65e97b47f668f901d52826a690be1405d8068c7d81594358a0ba1.png" - ], - [ - "duration", - "0" - ], - [ - "alt", - "Podcast episode: 6/24 Ray Co Commissioners' Mtg" - ], - [ - "client", - "podstr-github-actions" - ], - [ - "t", - "livestream" - ], - [ - "livestream", - "30311:85df822a86599ffbe8143db1e1e1bf2d162fa60fc685c65515963e67cfd7499f:7c84973e-0c76-4d29-b12e-eff864e4efd0" - ] - ] - } - } -] \ No newline at end of file diff --git a/scripts/lib/conversion-utils.ts b/scripts/lib/conversion-utils.ts index bf57dfd..44d7199 100644 --- a/scripts/lib/conversion-utils.ts +++ b/scripts/lib/conversion-utils.ts @@ -8,23 +8,73 @@ import { NSyteBunkerSigner } from './nsyte-bunker-minimal'; import type { NostrEvent } from '@nostrify/nostrify'; import { getPublicKey } from 'nostr-tools'; +/** + * Strip Hugging Face access tokens and other bearer tokens from a URL. + * Removes `token=...`, `hf_token=...` query params and any `hf_...` literals. + * Prevents credential leakage into Nostr events and committed JSON artifacts. + */ +export function redactUrlSecrets(url: string): string { + try { + const parsed = new URL(url); + // Drop known token-bearing params from any host. + for (const key of ['token', 'hf_token', 'access_token', 'jwt']) { + parsed.searchParams.delete(key); + } + let cleaned = parsed.toString(); + // Final sweep for any inline hf_ literals (defensive). + cleaned = cleaned.replace(/hf_[A-Za-z0-9]{20,}/g, '[REDACTED]'); + return cleaned; + } catch { + // Not a parseable URL β€” scrub token-like literals anyway. + return url.replace(/hf_[A-Za-z0-9]{20,}/g, '[REDACTED]'); + } +} + +/** + * Recursively redact Hugging Face token literals from any string in a Nostr event + * (tags, content). Used before serializing events to disk to satisfy GitHub + * push protection and avoid leaking credentials to Nostr relays. + */ +export function redactEventSecrets(event: T): T { + const scrub = (s: string) => s.replace(/hf_[A-Za-z0-9]{20,}/g, '[REDACTED]'); + return { + ...event, + content: scrub(event.content), + tags: event.tags.map((tag) => + tag.map((value) => (typeof value === 'string' ? scrub(redactUrlSecrets(value)) : value)), + ), + }; +} + export function extractRecordingUrl(livestream: NostrEvent): string | null { const download = livestream.tags.find(([name]) => name === 'download')?.[1]; if (download) { - console.log('βœ… Found download tag (Shoshou recording):', download); - return download; + const safe = redactUrlSecrets(download); + if (safe !== download) { + console.warn('πŸ” Stripped credentials from download URL before publishing'); + } + console.log('βœ… Found download tag (Shoshou recording):', safe); + return safe; } const recording = livestream.tags.find(([name]) => name === 'recording')?.[1]; if (recording) { - console.log('βœ… Found recording tag:', recording); - return recording; + const safe = redactUrlSecrets(recording); + if (safe !== recording) { + console.warn('πŸ” Stripped credentials from recording URL before publishing'); + } + console.log('βœ… Found recording tag:', safe); + return safe; } const streaming = livestream.tags.find(([name]) => name === 'streaming')?.[1]; if (streaming) { - console.warn('⚠️ Using streaming URL instead of recording (quality may be poor):', streaming); - return streaming; + const safe = redactUrlSecrets(streaming); + if (safe !== streaming) { + console.warn('πŸ” Stripped credentials from streaming URL before publishing'); + } + console.warn('⚠️ Using streaming URL instead of recording (quality may be poor):', safe); + return safe; } console.error('❌ No download, recording, or streaming URL found'); diff --git a/scripts/transcribe-audio.ts b/scripts/transcribe-audio.ts index 1a83ced..47fe123 100644 --- a/scripts/transcribe-audio.ts +++ b/scripts/transcribe-audio.ts @@ -15,6 +15,7 @@ import { exec, spawn } from 'child_process'; import { EpisodeMetadata } from './lib/conversion-types'; import type { NostrEvent } from '@nostrify/nostrify'; import { queryRelay } from './lib/relay-query'; +import { redactEventSecrets } from './lib/conversion-utils'; interface TranscriptionResult { dTag: string; @@ -488,8 +489,15 @@ async function main() { console.warn('⚠️ Failed to cleanup temp directory:', error); } - // Save transcript mapping - await fs.writeFile(TRANSCRIPT_MAPPING_PATH, JSON.stringify(results, null, 2)); + // Save transcript mapping. + // Redact any embedded Hugging Face tokens from events before writing to disk + // so credentials never enter the git history (GitHub push protection blocks + // commits containing HF user access tokens). + const sanitized = results.map(({ event, ...rest }) => ({ + ...rest, + ...(event ? { event: redactEventSecrets(event) } : {}), + })); + await fs.writeFile(TRANSCRIPT_MAPPING_PATH, JSON.stringify(sanitized, null, 2)); console.log(`πŸ’Ύ Transcript mapping saved to: ${TRANSCRIPT_MAPPING_PATH}`); // Log summary