diff --git a/.env.example b/.env.example index f45e296386..460a4492c7 100644 --- a/.env.example +++ b/.env.example @@ -63,28 +63,29 @@ GITTENSORY_REVIEW_ENRICHMENT=false # # Unknown names warn and are ignored; a typo-only list runs no analyzers. # BEGIN GENERATED REES ANALYZERS # Current analyzer names: -# dependency,lockfileDrift,secret,license,installScript,heavyDependency,actionPin,eol,redos -# provenance,codeowners,secretLog,assetWeight,typosquat,commitSignature,iacMisconfig,nativeBuild -# history,docCommentDrift,duplication,churnHotspot,blameLink,approvalIntegrity,ciCheckSignals -# undocumentedExport,staleBranch,commitHygiene,pendingReviewRequests,testRatio,migrationSafety -# looseRange,terminology,todoMarker,magicNumber,conflictMarker,debugLeftover,commitLint +# dependency,lockfileDrift,secret,license,installScript,heavyDependency,hardcodedUrl,actionPin +# eol,redos,provenance,codeowners,secretLog,assetWeight,typosquat,commitSignature,iacMisconfig +# nativeBuild,history,docCommentDrift,duplication,churnHotspot,blameLink,approvalIntegrity +# ciCheckSignals,undocumentedExport,staleBranch,commitHygiene,pendingReviewRequests,testRatio +# migrationSafety,looseRange,terminology,todoMarker,magicNumber,conflictMarker,debugLeftover +# commitLint # # Profile defaults: -# fast: dependency,lockfileDrift,secret,license,installScript,heavyDependency,actionPin,eol -# redos,provenance,secretLog,typosquat,iacMisconfig,nativeBuild,testRatio,migrationSafety -# looseRange,terminology,todoMarker,magicNumber,conflictMarker,debugLeftover +# fast: dependency,lockfileDrift,secret,license,installScript,heavyDependency,hardcodedUrl +# actionPin,eol,redos,provenance,secretLog,typosquat,iacMisconfig,nativeBuild,testRatio +# migrationSafety,looseRange,terminology,todoMarker,magicNumber,conflictMarker,debugLeftover # balanced (default): dependency,lockfileDrift,secret,license,installScript,heavyDependency +# hardcodedUrl,actionPin,eol,redos,provenance,codeowners,secretLog,assetWeight,typosquat +# commitSignature,iacMisconfig,nativeBuild,history,docCommentDrift,duplication,churnHotspot +# blameLink,approvalIntegrity,ciCheckSignals,undocumentedExport,staleBranch,commitHygiene +# pendingReviewRequests,testRatio,migrationSafety,looseRange,terminology,todoMarker,magicNumber +# conflictMarker,debugLeftover,commitLint +# deep: dependency,lockfileDrift,secret,license,installScript,heavyDependency,hardcodedUrl # actionPin,eol,redos,provenance,codeowners,secretLog,assetWeight,typosquat,commitSignature # iacMisconfig,nativeBuild,history,docCommentDrift,duplication,churnHotspot,blameLink # approvalIntegrity,ciCheckSignals,undocumentedExport,staleBranch,commitHygiene # pendingReviewRequests,testRatio,migrationSafety,looseRange,terminology,todoMarker,magicNumber # conflictMarker,debugLeftover,commitLint -# deep: dependency,lockfileDrift,secret,license,installScript,heavyDependency,actionPin,eol -# redos,provenance,codeowners,secretLog,assetWeight,typosquat,commitSignature,iacMisconfig -# nativeBuild,history,docCommentDrift,duplication,churnHotspot,blameLink,approvalIntegrity -# ciCheckSignals,undocumentedExport,staleBranch,commitHygiene,pendingReviewRequests,testRatio -# migrationSafety,looseRange,terminology,todoMarker,magicNumber,conflictMarker,debugLeftover -# commitLint # END GENERATED REES ANALYZERS # Submitter-reputation spend control (internal-only): downgrades new/burst/low-rep diff --git a/apps/gittensory-ui/src/lib/rees-analyzers.ts b/apps/gittensory-ui/src/lib/rees-analyzers.ts index 2f982b6675..30e72ae3f9 100644 --- a/apps/gittensory-ui/src/lib/rees-analyzers.ts +++ b/apps/gittensory-ui/src/lib/rees-analyzers.ts @@ -219,6 +219,31 @@ export const REES_ANALYZERS = [ notes: "Only reports packages with trivial direct usage so the finding stays actionable.", }, }, + { + name: "hardcodedUrl", + title: "Hardcoded URLs and endpoints", + category: "config", + cost: "local", + defaultEnabled: true, + profiles: ["fast", "balanced", "deep"], + requires: ["files"], + limits: { + maxFindings: 25, + maxLineChars: 2000, + maxHostChars: 40, + }, + docs: { + summary: + "Flags absolute HTTP(S) URLs and raw IP:port endpoints newly added in non-test, non-config source.", + looksAt: + "Added lines in changed source files, excluding tests, config manifests, and comment/import lines.", + reports: + "File, line, kind (http-url or ip-endpoint), and a redacted host — never full paths or queries.", + network: "Pure local analyzer. No external network call.", + notes: + "localhost, 127.0.0.1, and example.com are allowlisted. Distinct from the secret scanner — no credential detection.", + }, + }, { name: "actionPin", title: "Unpinned GitHub Actions", diff --git a/review-enrichment/analyzer-metadata.json b/review-enrichment/analyzer-metadata.json index d86d778f5d..5bd5db75a5 100644 --- a/review-enrichment/analyzer-metadata.json +++ b/review-enrichment/analyzer-metadata.json @@ -236,6 +236,33 @@ "notes": "Only reports packages with trivial direct usage so the finding stays actionable." } }, + { + "name": "hardcodedUrl", + "title": "Hardcoded URLs and endpoints", + "category": "config", + "cost": "local", + "defaultEnabled": true, + "profiles": [ + "fast", + "balanced", + "deep" + ], + "requires": [ + "files" + ], + "limits": { + "maxFindings": 25, + "maxLineChars": 2000, + "maxHostChars": 40 + }, + "docs": { + "summary": "Flags absolute HTTP(S) URLs and raw IP:port endpoints newly added in non-test, non-config source.", + "looksAt": "Added lines in changed source files, excluding tests, config manifests, and comment/import lines.", + "reports": "File, line, kind (http-url or ip-endpoint), and a redacted host — never full paths or queries.", + "network": "Pure local analyzer. No external network call.", + "notes": "localhost, 127.0.0.1, and example.com are allowlisted. Distinct from the secret scanner — no credential detection." + } + }, { "name": "actionPin", "title": "Unpinned GitHub Actions", diff --git a/review-enrichment/src/analyzers/hardcoded-url.ts b/review-enrichment/src/analyzers/hardcoded-url.ts new file mode 100644 index 0000000000..c5790d50ba --- /dev/null +++ b/review-enrichment/src/analyzers/hardcoded-url.ts @@ -0,0 +1,143 @@ +// Hardcoded-URL / raw-endpoint analyzer (#2027). Flags absolute HTTP(S) URLs and IP:port endpoints newly +// added in non-test, non-config source — often environment leakage or a value that should come from config. +// Distinct from the secret scanner (no credential); this is a portability/config-hygiene signal. Pure compute +// over added lines, no network. Hostnames are redacted/truncated in findings — never full paths or queries. +import type { EnrichRequest, HardcodedUrlFinding } from "../types.js"; +import { isMagicNumberSourcePath } from "./magic-number.js"; + +const MAX_FINDINGS = 25; +const MAX_LINE_CHARS = 2000; +const MAX_HOST_CHARS = 40; + +const CONFIG_PATH_RE = + /(?:^|\/)(?:docker-compose[^/]*\.ya?ml|compose[^/]*\.ya?ml|values(?:\.[^/]+)?\.ya?ml|\.env(?:\.[^/]+)?|.*\.(?:tf|tfvars|hcl|ya?ml|json|toml|ini|conf|env)|Dockerfile(?:\.[^/]+)?|nginx[^/]*\.conf)$/i; + +const HTTP_URL_RE = /https?:\/\/[^\s'"\`<>]+/gi; +const IP_ENDPOINT_RE = /\b(?:\d{1,3}\.){3}\d{1,3}:\d{1,5}\b/g; + +const ALLOWLISTED_HOSTS = new Set(["localhost", "127.0.0.1", "example.com"]); + +function isConfigPath(path: string): boolean { + return CONFIG_PATH_RE.test(path); +} + +function isScannablePath(path: string): boolean { + return isMagicNumberSourcePath(path) && !isConfigPath(path); +} + +function redactHost(host: string): string { + const lower = host.toLowerCase(); + if (lower.length <= MAX_HOST_CHARS) return lower; + return `${lower.slice(0, MAX_HOST_CHARS - 3)}...`; +} + +function isAllowlistedHost(host: string): boolean { + const lower = host.toLowerCase(); + if (ALLOWLISTED_HOSTS.has(lower)) return true; + if (lower.endsWith(".example.com")) return true; + return false; +} + +function hostFromHttpUrl(url: string): string { + const match = /^https?:\/\/([^/?#:]+)(?::\d+)?/i.exec(url); + return match?.[1] ?? url; +} + +function isCommentLine(line: string): boolean { + const trimmed = line.trimStart(); + return /^(?:\/\/|#|\/\*|\*|