From 22509106db5ea7ede0674514a494f1fb5fa9b69e Mon Sep 17 00:00:00 2001 From: Vichitra Attri Date: Sun, 18 Oct 2026 07:12:57 +0000 Subject: [PATCH 1/7] research: write blinded items for independent ground-truth raters gt_sample.py now also writes items.json: the sampled findings shuffled under neutral ids, without the harness's answer, the stratum or the schemes' outcomes. sample.csv's new item column is the key back to the truth. A persisting finding becomes a pair item (same issue at the old and the new line?), an introduced one an introduced item (new in this commit, or existed before?). The labelling page and a second rater both work from this file. Signed-off-by: Vichitra Attri --- research/finding_identity/gt_sample.py | 55 +- .../paper/ground_truth_check/items.json | 7422 +++++++++++++++++ .../paper/ground_truth_check/sample.csv | 202 +- 3 files changed, 7574 insertions(+), 105 deletions(-) create mode 100644 research/finding_identity/paper/ground_truth_check/items.json diff --git a/research/finding_identity/gt_sample.py b/research/finding_identity/gt_sample.py index ea2b22b..66711b4 100644 --- a/research/finding_identity/gt_sample.py +++ b/research/finding_identity/gt_sample.py @@ -16,7 +16,13 @@ It writes ``sample.md``, one entry per finding with the code before and after the commit and a link to the commit, and ``sample.csv`` with an empty ``verdict`` column to fill in: ``same`` (the old and new finding are the same issue), ``different``, or ``unclear``. -The sample is seeded, so rerunning it gives the same findings. + +For independent raters it also writes ``items.json``: the same findings, shuffled under +neutral ids (``sample.csv``'s ``item`` column is the key), without the harness's answer, +the stratum or the schemes' outcomes. A persisting finding becomes a ``pair`` item ("is +the code flagged at the old line the same issue as at the new line?"), an introduced one +an ``introduced`` item ("did this issue already exist before the commit?"). +The sample is seeded, so rerunning it gives the same findings and the same order. """ from __future__ import annotations @@ -30,6 +36,9 @@ from pathlib import Path from typing import Any +import codeguard.rules # noqa: F401 (registers the built-in rules) +from codeguard.engine.registry import REGISTRY + from .corpus import load_corpus HERE = Path(__file__).parent @@ -69,12 +78,20 @@ def _git(repo: Path, *args: str) -> str: return done.stdout.decode("utf-8", "replace") if done.returncode == 0 else "" -def snippet(repo: Path, commit: str, path: str, line: int, context: int = 4) -> str: - """Lines around *line* of *path* at *commit*, numbered, the line itself marked.""" +def snippet_lines( + repo: Path, commit: str, path: str, line: int, context: int = 4 +) -> list[tuple[int, str]]: + """``(line number, text)`` for the lines around *line* of *path* at *commit*.""" lines = _git(repo, "show", f"{commit}:{path}").split("\n") lo, hi = max(1, line - context), min(len(lines), line + context) + return [(n, lines[n - 1].rstrip()) for n in range(lo, hi + 1)] + + +def snippet(repo: Path, commit: str, path: str, line: int, context: int = 4) -> str: + """Lines around *line* of *path* at *commit*, numbered, the line itself marked.""" return "\n".join( - f"{'>' if n == line else ' '}{n:5d} {lines[n - 1]}" for n in range(lo, hi + 1) + f"{'>' if n == line else ' '}{n:5d} {text}" + for n, text in snippet_lines(repo, commit, path, line, context) ) @@ -164,6 +181,12 @@ def write_sample( "", ] rows = [] + items = [] + titles = {r.id: r.title for r in REGISTRY.all()} + # Neutral item ids in a seeded order unrelated to the strata. + order = list(range(1, len(chosen) + 1)) + random.Random(len(chosen)).shuffle(order) # noqa: S311 (blinding order, not security) + item_of = {i: f"F{position:03d}" for position, i in enumerate(order, start=1)} for i, (stratum, label, rec, old) in enumerate(chosen, start=1): repo = repos / label new = rec["commit"] @@ -191,9 +214,31 @@ def write_sample( if rec["truth"] == "new" or rec.get("truth_kind") == "edited": hunks = diff_hunks(repo, old, new, path, old_path, rec["line"]) md += ["Diff near the line:", "", "```diff", hunks, "```", ""] + old_line = rec.get("old_line") + items.append( + { + "item": item_of[i], + "kind": "pair" if rec["truth"] == "known" else "introduced", + "repository": label, + "commit_url": url, + "rule": rec["rule"], + "rule_title": titles.get(rec["rule"], ""), + "path": path, + "old_path": old_path, + "old_line": old_line, + "line": rec["line"], + "before": snippet_lines(repo, old, old_path, old_line) if old_line else [], + "after": snippet_lines(repo, new, path, rec["line"]), + "diff": "\n".join( + row.rstrip() + for row in diff_hunks(repo, old, new, path, old_path, rec["line"]).split("\n") + ), + } + ) rows.append( { "id": i, + "item": item_of[i], "stratum": stratum, "repository": label, "commit": new, @@ -210,6 +255,8 @@ def write_sample( # Quoted source can end lines in spaces; the repository's hooks strip them. text = "\n".join(row.rstrip() for row in "\n".join(md).split("\n")) (out / "sample.md").write_text(text.rstrip("\n") + "\n", encoding="utf-8") + items.sort(key=lambda it: str(it["item"])) + (out / "items.json").write_text(json.dumps(items, indent=1) + "\n", encoding="utf-8") with (out / "sample.csv").open("w", newline="", encoding="utf-8") as f: w = csv.DictWriter(f, fieldnames=list(rows[0])) w.writeheader() diff --git a/research/finding_identity/paper/ground_truth_check/items.json b/research/finding_identity/paper/ground_truth_check/items.json new file mode 100644 index 0000000..ff74c5a --- /dev/null +++ b/research/finding_identity/paper/ground_truth_check/items.json @@ -0,0 +1,7422 @@ +[ + { + "item": "F001", + "kind": "introduced", + "repository": "ghost", + "commit_url": "https://github.com/TryGhost/Ghost/commit/7da91e095b2803c62bc67fe70f2c4f0d114846d9", + "rule": "CG-PERF-101", + "rule_title": "await inside a loop runs the iterations one after another", + "path": "tools/scripts/test/ci-path-filters.test.js", + "old_path": "tools/scripts/test/ci-path-filters.test.js", + "old_line": null, + "line": 69, + "before": [], + "after": [ + [ + 65, + " const docs = await getFilterPatterns('docs');" + ], + [ + 66, + " assert.ok(docs.some((pattern) => picomatch(pattern, { dot: true })(inventory)));" + ], + [ + 67, + "" + ], + [ + 68, + " for (const filterName of ['core', 'any-code', 'e2e']) {" + ], + [ + 69, + " const patterns = await getFilterPatterns(filterName);" + ], + [ + 70, + " const positives = patterns.filter((pattern) => !pattern.startsWith('!'));" + ], + [ + 71, + " const exclusions = patterns.filter((pattern) => pattern.startsWith('!'));" + ], + [ + 72, + " const matches = (path) =>" + ], + [ + 73, + " (positives.length === 0 ||" + ] + ], + "diff": "@@ -56,6 +60,83 @@ describe('CI path filters', () => {\n assert.ok(e2e.includes('!**/*.mdx'));\n });\n\n+ it('treats the service inventory as docs and keeps code checks for mixed changes', async () => {\n+ const inventory = 'ghost/core/server/services/service-inventory.yaml';\n+ const docs = await getFilterPatterns('docs');\n+ assert.ok(docs.some((pattern) => picomatch(pattern, { dot: true })(inventory)));\n+\n+ for (const filterName of ['core', 'any-code', 'e2e']) {\n+ const patterns = await getFilterPatterns(filterName);\n+ const positives = patterns.filter((pattern) => !pattern.startsWith('!'));\n+ const exclusions = patterns.filter((pattern) => pattern.startsWith('!'));\n+ const matches = (path) =>\n+ (positives.length === 0 ||\n+ positives.some((pattern) => picomatch(pattern, { dot: true })(path))) &&\n+ exclusions.every((pattern) => picomatch(pattern, { dot: true })(path));\n+ const metadata = [inventory, 'ghost/core/server/services/README.md'];\n+\n+ assert.strictEqual(metadata.some(matches), false, filterName);\n+ for (const runtime of [\n+ 'ghost/core/server/services/identity-tokens/index.ts',\n+ 'ghost/core/server/services/other-metadata.yaml',\n+ '.github/workflows/ci.yml',\n+ ]) {\n+ assert.strictEqual([...metadata, runtime].some(matches), true, `${filterName}: ${runtime}`);\n+ }\n+ }\n+\n+ const anyCode = await getFilterPatterns('any-code');\n+ assert.ok(\n+ anyCode.every((pattern) =>\n+ picomatch(pattern, { dot: true })(\n+ 'ghost/test/unit/server/services/service-inventory.test.ts',\n+ ),\n+ ),\n+ 'Changes to the inventory validator must still run code checks',\n+ );\n+ });\n+\n... (45 more lines)" + }, + { + "item": "F002", + "kind": "pair", + "repository": "erpnext", + "commit_url": "https://github.com/frappe/erpnext/commit/028a9ed1d94ce99702dd65b32ca3aede52b52006", + "rule": "CG-QUAL-005", + "rule_title": "Function takes too many parameters", + "path": "erpnext/stock/doctype/pick_list/pick_list.py", + "old_path": "erpnext/stock/doctype/pick_list/pick_list.py", + "old_line": 1354, + "line": 1451, + "before": [ + [ + 1350, + "\titem_location_map[item_doc.item_code] = available_locations" + ], + [ + 1351, + "\treturn locations" + ], + [ + 1352, + "" + ], + [ + 1353, + "" + ], + [ + 1354, + "def get_available_item_locations(" + ], + [ + 1355, + "\titem_code," + ], + [ + 1356, + "\tfrom_warehouses," + ], + [ + 1357, + "\trequired_qty," + ], + [ + 1358, + "\tcompany," + ] + ], + "after": [ + [ + 1447, + "\titem_location_map[item_doc.item_code] = available_locations" + ], + [ + 1448, + "\treturn locations" + ], + [ + 1449, + "" + ], + [ + 1450, + "" + ], + [ + 1451, + "def get_available_item_locations(" + ], + [ + 1452, + "\titem_code," + ], + [ + 1453, + "\tfrom_warehouses," + ], + [ + 1454, + "\trequired_qty," + ], + [ + 1455, + "\tcompany," + ] + ], + "diff": "(no diff hunk near this line)" + }, + { + "item": "F003", + "kind": "pair", + "repository": "erpnext", + "commit_url": "https://github.com/frappe/erpnext/commit/750bf9d9b378fb92eab68733ae727214f3961c0a", + "rule": "CG-PERF-002", + "rule_title": "String built with += inside a loop", + "path": "erpnext/stock/utils.py", + "old_path": "erpnext/stock/utils.py", + "old_line": 159, + "line": 185, + "before": [ + [ + 155, + "\t\t\t\t\t\tfrappe.bold(field), frappe.bold(\"Inventory Dimension\")" + ], + [ + 156, + "\t\t\t\t\t)" + ], + [ + 157, + "\t\t\t\t)" + ], + [ + 158, + "\t\t\targs[field] = value" + ], + [ + 159, + "\t\t\textra_cond += f\" and {field} = %({field})s\"" + ], + [ + 160, + "" + ], + [ + 161, + "\tlast_entry = get_previous_sle(args, extra_cond=extra_cond)" + ], + [ + 162, + "" + ], + [ + 163, + "\tif with_valuation_rate:" + ] + ], + "after": [ + [ + 181, + "\t\t\t\t\t\tfrappe.bold(field), frappe.bold(\"Inventory Dimension\")" + ], + [ + 182, + "\t\t\t\t\t)" + ], + [ + 183, + "\t\t\t\t)" + ], + [ + 184, + "\t\t\targs[field] = value" + ], + [ + 185, + "\t\t\textra_cond += f\" and {field} = %({field})s\"" + ], + [ + 186, + "" + ], + [ + 187, + "\tlast_entry = get_previous_sle(args, extra_cond=extra_cond)" + ], + [ + 188, + "" + ], + [ + 189, + "\tif with_valuation_rate:" + ] + ], + "diff": "(no diff hunk near this line)" + }, + { + "item": "F004", + "kind": "pair", + "repository": "kiwi", + "commit_url": "https://github.com/kiwitcms/Kiwi/commit/362febe93063899b8c7468cf7490c26028368a5c", + "rule": "CG-SEC-109", + "rule_title": "Redirect to a URL taken from the request", + "path": "tcms/testplans/static/testplans/js/clone.js", + "old_path": "tcms/testplans/static/testplans/js/clone.js", + "old_line": 110, + "line": 112, + "before": [ + [ + 106, + "" + ], + [ + 107, + " if (newPlanIds.length === 1) {" + ], + [ + 108, + " window.location.assign(`/plan/${newPlanIds[0]}/`)" + ], + [ + 109, + " } else {" + ], + [ + 110, + " window.location.assign(document.referrer || '/')" + ], + [ + 111, + " }" + ], + [ + 112, + " }" + ], + [ + 113, + "" + ], + [ + 114, + " if (!rows.length) {" + ] + ], + "after": [ + [ + 108, + " const redirectToClonedPlan = function () {" + ], + [ + 109, + " if (isTree || newPlanIds.length === 1) {" + ], + [ + 110, + " window.location.assign(`/plan/${newPlanIds[0]}/`)" + ], + [ + 111, + " } else {" + ], + [ + 112, + " window.location.assign(document.referrer || '/')" + ], + [ + 113, + " }" + ], + [ + 114, + " }" + ], + [ + 115, + "" + ], + [ + 116, + " // in tree mode set the parent of every clone, which can only be" + ] + ], + "diff": "@@ -104,11 +105,44 @@ export function pageTestplansCloneReadyHandler () {\n return\n }\n\n- if (newPlanIds.length === 1) {\n- window.location.assign(`/plan/${newPlanIds[0]}/`)\n- } else {\n- window.location.assign(document.referrer || '/')\n+ const redirectToClonedPlan = function () {\n+ if (isTree || newPlanIds.length === 1) {\n+ window.location.assign(`/plan/${newPlanIds[0]}/`)\n+ } else {\n+ window.location.assign(document.referrer || '/')\n+ }\n }\n+\n+ // in tree mode set the parent of every clone, which can only be\n+ // done once all of the clones have been created.\n+ if (isTree) {\n+ let updatedCount = 0\n+\n+ const updateIsDone = function () {\n+ updatedCount += 1\n+\n+ if (updatedCount === rows.length) {\n+ redirectToClonedPlan()\n+ }\n+ }\n+\n+ rows.each(function () {\n+ const row = $(this)\n+\n+ jsonRPC(\n+ 'TestPlan.update',\n+ [\n+ clonedPlanIds[row.data('plan-id')],\n+ { parent: clonedPlanIds[row.attr('data-parent-id')] }\n+ ],\n+ updateIsDone\n+ )\n... (10 more lines)" + }, + { + "item": "F005", + "kind": "pair", + "repository": "pgadmin", + "commit_url": "https://github.com/pgadmin-org/pgadmin4/commit/5f56805162c117ac1de17368ef8fdc0b07221e2c", + "rule": "CG-QUAL-101", + "rule_title": "Loose equality (== / !=)", + "path": "web/pgadmin/tools/sqleditor/static/js/components/QueryToolComponent.jsx", + "old_path": "web/pgadmin/tools/sqleditor/static/js/components/QueryToolComponent.jsx", + "old_line": 813, + "line": 872, + "before": [ + [ + 809, + " let existIdx = _.findIndex(qtState.connection_list, (conn)=>{" + ], + [ + 810, + " conn.role= conn.role == ''? null :conn.role;" + ], + [ + 811, + " return(" + ], + [ + 812, + " conn.sid == connectionData.sid && conn.database_name == connectionData.database_name" + ], + [ + 813, + " && conn.user == connectionData.user && conn.role == connectionData.role" + ], + [ + 814, + " );" + ], + [ + 815, + " });" + ], + [ + 816, + " if(existIdx > -1) {" + ], + [ + 817, + " reject(new Error(gettext('Connection with this configuration already present.')));" + ] + ], + "after": [ + [ + 868, + "" + ], + [ + 869, + " let existIdx = _.findIndex(qtState.connection_list, (conn)=>{" + ], + [ + 870, + " return(" + ], + [ + 871, + " conn.sid == connectionData.sid && conn.database_name == connectionData.database_name" + ], + [ + 872, + " && conn.user == connectionData.user && (conn.role || null) == (connectionData.role || null)" + ], + [ + 873, + " );" + ], + [ + 874, + " });" + ], + [ + 875, + " if(existIdx > -1) {" + ], + [ + 876, + " reject(new Error(gettext('Connection with this configuration already present.')));" + ] + ], + "diff": "@@ -807,10 +867,9 @@ export default function QueryToolComponent({params, pgWindow, pgAdmin, selectedN\n };\n\n let existIdx = _.findIndex(qtState.connection_list, (conn)=>{\n- conn.role= conn.role == ''? null :conn.role;\n return(\n conn.sid == connectionData.sid && conn.database_name == connectionData.database_name\n- && conn.user == connectionData.user && conn.role == connectionData.role\n+ && conn.user == connectionData.user && (conn.role || null) == (connectionData.role || null)\n );\n });\n if(existIdx > -1) {\n@@ -818,11 +877,12 @@ export default function QueryToolComponent({params, pgWindow, pgAdmin, selectedN\n return;\n }\n updateQueryToolConnection(connectionData, true)\n- .catch((err)=>{\n- reject(err instanceof Error ? err : Error(gettext('Something went wrong')));\n- }).then(()=>{\n+ .then(()=>{\n resolve();\n onClose();\n+ })\n+ .catch((err)=>{\n+ reject(err instanceof Error ? err : Error(gettext('Something went wrong')));\n });\n });\n }}" + }, + { + "item": "F006", + "kind": "pair", + "repository": "arches", + "commit_url": "https://github.com/archesproject/arches/commit/2e02ce6e61999d93c10b05d853ae08f1912fa294", + "rule": "CG-QUAL-104", + "rule_title": "var declaration", + "path": "arches/app/media/js/views/graph/permission-manager/permission-settings-form.js", + "old_path": "arches/app/media/js/views/graph/permission-manager/permission-settings-form.js", + "old_line": 128, + "line": 210, + "before": [ + [ + 124, + " });" + ], + [ + 125, + " }," + ], + [ + 126, + "" + ], + [ + 127, + " revert: function() {" + ], + [ + 128, + " var self = this;" + ], + [ + 129, + " var postData = {" + ], + [ + 130, + " 'selectedIdentities': this.selectedIdentities()," + ], + [ + 131, + " 'selectedCards': this.selectedCards()" + ], + [ + 132, + " };" + ] + ], + "after": [ + [ + 206, + " });" + ], + [ + 207, + " }," + ], + [ + 208, + "" + ], + [ + 209, + " revert: function() {" + ], + [ + 210, + " var self = this;" + ], + [ + 211, + " if (!this.canSubmit()) {" + ], + [ + 212, + " return;" + ], + [ + 213, + " }" + ], + [ + 214, + " var confirm = arches.translations.graphDesignerPermissionsResetConfirm;" + ] + ], + "diff": "@@ -109,48 +188,64 @@ var PermissionSettingsForm = Backbone.View.extend({\n })\n };\n\n+ this.loading(true);\n $.ajax({\n type: 'POST',\n url: arches.urls.permission_data,\n- data: JSON.stringify(postData),\n- success: function(res) {\n+ data: JSON.stringify(postData)\n+ })\n+ .done(function() {\n+ self.showSuccess(arches.translations.graphDesignerPermissionsApplied);\n self.trigger('save');\n- self.clearUserPermissionCache();\n- // adds event to trigger dirty state in graph-designer\n- document.dispatchEvent(\n- new Event('permissionsSave')\n- );\n- }\n- });\n+ })\n+ .fail(function(response) {\n+ self.showError(response);\n+ })\n+ .always(function() {\n+ self.loading(false);\n+ });\n },\n\n revert: function() {\n+ var self = this;\n+ if (!this.canSubmit()) {\n+ return;\n+ }\n+ var confirm = arches.translations.graphDesignerPermissionsResetConfirm;\n+ this.alert(new AlertViewModel(\n+ 'ep-alert-red',\n+ confirm.title,\n... (53 more lines)" + }, + { + "item": "F007", + "kind": "introduced", + "repository": "inventree", + "commit_url": "https://github.com/inventree/InvenTree/commit/99893f3453c6a213a331c022e82cbafbde7ebd51", + "rule": "CG-QUAL-101", + "rule_title": "Loose equality (== / !=)", + "path": "src/frontend/src/components/tables/InvenTreeTable.tsx", + "old_path": "src/frontend/src/components/tables/InvenTreeTable.tsx", + "old_line": null, + "line": 940, + "before": [], + "after": [ + [ + 936, + " params: queryParams," + ], + [ + 937, + " timeout: 10 * 1000" + ], + [ + 938, + " })" + ], + [ + 939, + " .then((response) => {" + ], + [ + 940, + " if (generation != nestedGeneration.current) {" + ], + [ + 941, + " return;" + ], + [ + 942, + " }" + ], + [ + 943, + "" + ], + [ + 944, + " let results = response.data?.results ?? response.data ?? [];" + ] + ], + "diff": "@@ -776,6 +862,170 @@ export function InvenTreeTableInternal>({\n tableState.setRecords(tableData ?? apiData ?? []);\n }, [tableData, apiData]);\n\n+ // Serialized params, so that nested data is not discarded on every render\n+ const paramsKey: string = useMemo(\n+ () => JSON.stringify(tableProps.params ?? {}),\n+ [tableProps.params]\n+ );\n+\n+ // Discard nested data whenever the top-level table data is reloaded,\n+ // or the nesting configuration changes (as child rows depend on it)\n+ useEffect(() => {\n+ nestedGeneration.current += 1;\n+ // Keep the existing (empty) state object to avoid a needless re-render\n+ setNestedState((state) => (Object.keys(state).length > 0 ? {} : state));\n+ }, [\n+ props.nestedRows,\n+ url,\n+ tableData,\n+ apiData,\n+ paramsKey,\n+ pageSize,\n+ sortStatus,\n+ tableState.page,\n+ tableState.searchTerm,\n+ tableState.tableKey,\n+ tableState.filterSet.activeFilters,\n+ tableSearchParams\n+ ]);\n+\n+ // Expand or collapse a nested row, fetching child rows if required\n+ toggleNestedRow.current = (record: T) => {\n+ const nestedRows = props.nestedRows;\n+\n+ if (!nestedRows || !url) {\n+ return;\n+ }\n+\n+ const key = getRowKey(record);\n... (131 more lines)" + }, + { + "item": "F008", + "kind": "introduced", + "repository": "lnbits", + "commit_url": "https://github.com/lnbits/lnbits/commit/c0806ca59c90464e424f881a92efd8033fd9bd2b", + "rule": "CG-PERF-101", + "rule_title": "await inside a loop runs the iterations one after another", + "path": "tests/e2e/extension-helpers.ts", + "old_path": "tests/e2e/extension-helpers.ts", + "old_line": null, + "line": 540, + "before": [], + "after": [ + [ + 536, + " await expect(tab).toBeVisible({timeout: 60_000})" + ], + [ + 537, + " for (let attempt = 0; attempt < 3; attempt += 1) {" + ], + [ + 538, + " await tab.click()" + ], + [ + 539, + " try {" + ], + [ + 540, + " await expect(tab).toHaveAttribute('aria-selected', 'true', {" + ], + [ + 541, + " timeout: 5_000" + ], + [ + 542, + " })" + ], + [ + 543, + " return" + ], + [ + 544, + " } catch (_error) {" + ] + ], + "diff": "@@ -0,0 +1,658 @@\n+import {expect, type Frame, type Locator, type Page} from '@playwright/test'\n+\n+import {\n+ browserJson,\n+ delay,\n+ isRecord,\n+ type LNbitsE2EServer,\n+ waitForResult\n+} from './fixtures'\n+\n+const INSTALLABLE_EXTENSION_REFRESH_TASK =\n+ 'refresh_installable_extensions_cache'\n+\n+export type ExtensionUnderTest = {\n+ extId: string\n+ manifestUrl: string\n+ name: string\n+ permissionTexts?: string[]\n+ repository: string\n+}\n+\n+export type E2EWallet = {\n+ adminkey: string\n+ id: string\n+ inkey: string\n+ name: string\n+}\n+\n+export async function login(\n+ page: Page,\n+ server: LNbitsE2EServer\n+): Promise {\n+ await page.goto('/')\n+ await page.locator('input[name=\"username\"]').fill(server.username)\n+ await page.locator('input[name=\"password\"]').fill(server.password)\n+ await page.getByRole('button', {name: /^login$/i}).click()\n+ await expect(page).toHaveURL(/\\/wallet\\/[^/]+$/)\n+ await dismissDisclaimer(page)\n+}\n... (620 more lines)" + }, + { + "item": "F009", + "kind": "pair", + "repository": "edx-platform", + "commit_url": "https://github.com/openedx/edx-platform/commit/b3f862e61aa84a5f8c027081b7d1a07a8619917d", + "rule": "CG-SEC-013", + "rule_title": "Request data used as a file path", + "path": "cms/djangoapps/contentstore/api/views/course_import.py", + "old_path": "cms/djangoapps/contentstore/api/views/course_import.py", + "old_line": 142, + "line": 145, + "before": [ + [ + 138, + " if not course_dir.isdir():" + ], + [ + 139, + " os.mkdir(course_dir)" + ], + [ + 140, + "" + ], + [ + 141, + " log.debug(f'importing course to {temp_filepath}')" + ], + [ + 142, + " with open(temp_filepath, \"wb+\") as temp_file:" + ], + [ + 143, + " for chunk in request.FILES['course_data'].chunks():" + ], + [ + 144, + " temp_file.write(chunk)" + ], + [ + 145, + "" + ], + [ + 146, + " log.info(\"Course import %s: Upload complete\", course_key)" + ] + ], + "after": [ + [ + 141, + " os.makedirs(course_dir, exist_ok=True)" + ], + [ + 142, + "" + ], + [ + 143, + " try:" + ], + [ + 144, + " log.debug(f'importing course to {temp_filepath}')" + ], + [ + 145, + " with open(temp_filepath, \"wb+\") as temp_file:" + ], + [ + 146, + " for chunk in request.FILES['course_data'].chunks():" + ], + [ + 147, + " temp_file.write(chunk)" + ], + [ + 148, + "" + ], + [ + 149, + " log.info(\"Course import %s: Upload complete\", course_key)" + ] + ], + "diff": "@@ -131,22 +134,26 @@ class CourseImportView(CourseImportExportViewMixin, GenericAPIView):\n developer_message='Parameter in the wrong format',\n error_code='internal_error',\n )\n- course_dir = path(settings.GITHUB_REPO_ROOT) / base64.urlsafe_b64encode(\n- repr(course_key).encode('utf-8')\n- ).decode('utf-8')\n+ # Staging directory private to this upload, so that a concurrent import of\n+ # the same course cannot overwrite this archive or delete it on its way out.\n+ course_dir = course_import_working_dir(course_key, f'upload-{uuid4().hex}')\n temp_filepath = course_dir / filename\n- if not course_dir.isdir():\n- os.mkdir(course_dir)\n-\n- log.debug(f'importing course to {temp_filepath}')\n- with open(temp_filepath, \"wb+\") as temp_file:\n- for chunk in request.FILES['course_data'].chunks():\n- temp_file.write(chunk)\n-\n- log.info(\"Course import %s: Upload complete\", course_key)\n- with open(temp_filepath, 'rb') as local_file:\n- django_file = File(local_file)\n- storage_path = course_import_export_storage.save('olx_import/' + filename, django_file)\n+ os.makedirs(course_dir, exist_ok=True)\n+\n+ try:\n+ log.debug(f'importing course to {temp_filepath}')\n+ with open(temp_filepath, \"wb+\") as temp_file:\n+ for chunk in request.FILES['course_data'].chunks():\n+ temp_file.write(chunk)\n+\n+ log.info(\"Course import %s: Upload complete\", course_key)\n+ with open(temp_filepath, 'rb') as local_file:\n+ django_file = File(local_file)\n+ storage_path = course_import_export_storage.save('olx_import/' + filename, django_file)\n+ finally:\n+ # The archive now lives in storage; the import task downloads it into its\n+ # own working directory, so this staging copy is no longer needed.\n+ remove_course_import_working_dir(course_dir)\n\n... (3 more lines)" + }, + { + "item": "F010", + "kind": "pair", + "repository": "lnbits", + "commit_url": "https://github.com/lnbits/lnbits/commit/f5989201d9c21a4690334f91bbbb421214c989e7", + "rule": "CG-QUAL-102", + "rule_title": "Empty catch block", + "path": "lnbits/static/js/wasm-extension-component.js", + "old_path": "lnbits/static/js/wasm-extension-component.js", + "old_line": 904, + "line": 1126, + "before": [ + [ + 900, + " socket.addEventListener('message', event => {" + ], + [ + 901, + " let data = event.data" + ], + [ + 902, + " try {" + ], + [ + 903, + " data = JSON.parse(event.data)" + ], + [ + 904, + " } catch (_error) {}" + ], + [ + 905, + "" + ], + [ + 906, + " this.sendBridgeEvent({" + ], + [ + 907, + " event: 'payment.update'," + ], + [ + 908, + " subscriptionId," + ] + ], + "after": [ + [ + 1122, + " socket.addEventListener('message', event => {" + ], + [ + 1123, + " let data = event.data" + ], + [ + 1124, + " try {" + ], + [ + 1125, + " data = JSON.parse(event.data)" + ], + [ + 1126, + " } catch (_error) {}" + ], + [ + 1127, + "" + ], + [ + 1128, + " this.sendBridgeEvent({" + ], + [ + 1129, + " event: 'payment.update'," + ], + [ + 1130, + " subscriptionId," + ] + ], + "diff": "(no diff hunk near this line)" + }, + { + "item": "F011", + "kind": "pair", + "repository": "indico", + "commit_url": "https://github.com/indico/indico/commit/1ab29e13596cacf234ce75f7c0d4be217010ba6d", + "rule": "CG-QUAL-003", + "rule_title": "Function is too complex", + "path": "indico/modules/events/registration/util.py", + "old_path": "indico/modules/events/registration/util.py", + "old_line": 474, + "line": 474, + "before": [ + [ + 470, + " return registration" + ], + [ + 471, + "" + ], + [ + 472, + "" + ], + [ + 473, + "@no_autoflush" + ], + [ + 474, + "def modify_registration(registration, data, management=False, notify_user=True):" + ], + [ + 475, + " from indico.modules.events.registration.tasks import delete_previous_registration_file" + ], + [ + 476, + "" + ], + [ + 477, + " user = session.user if session else None" + ], + [ + 478, + " old_data = snapshot_registration_data(registration)" + ] + ], + "after": [ + [ + 470, + " return registration" + ], + [ + 471, + "" + ], + [ + 472, + "" + ], + [ + 473, + "@no_autoflush" + ], + [ + 474, + "def modify_registration(registration, data, management=False, notify_user=True):" + ], + [ + 475, + " from indico.modules.events.registration.tasks import delete_previous_registration_file" + ], + [ + 476, + "" + ], + [ + 477, + " user = session.user if session else None" + ], + [ + 478, + " old_data = snapshot_registration_data(registration)" + ] + ], + "diff": "(no diff hunk near this line)" + }, + { + "item": "F012", + "kind": "pair", + "repository": "documenso", + "commit_url": "https://github.com/documenso/documenso/commit/9c27ce6d185f7e25eec80439b931c4709723a2ae", + "rule": "CG-PERF-101", + "rule_title": "await inside a loop runs the iterations one after another", + "path": "packages/app-tests/e2e/teams/team-documents.spec.ts", + "old_path": "packages/app-tests/e2e/teams/team-documents.spec.ts", + "old_line": 37, + "line": 29, + "before": [ + [ + 33, + "" + ], + [ + 34, + " // Check counts after filtering." + ], + [ + 35, + " await checkDocumentTabCount(page, 'Inbox', 0);" + ], + [ + 36, + " await checkDocumentTabCount(page, 'Pending', 2);" + ], + [ + 37, + " await checkDocumentTabCount(page, 'Completed', 0);" + ], + [ + 38, + " await checkDocumentTabCount(page, 'Draft', 1);" + ], + [ + 39, + " await checkDocumentTabCount(page, 'All', 3);" + ], + [ + 40, + "" + ], + [ + 41, + " await apiSignout({ page });" + ] + ], + "after": [ + [ + 25, + " // Apply filter." + ], + [ + 26, + " await toggleDocumentSenderFilter(page, teamMember2.name ?? '');" + ], + [ + 27, + "" + ], + [ + 28, + " // Check counts after filtering." + ], + [ + 29, + " await checkDocumentCounts(page, { inbox: 0, pending: 2, completed: 0, draft: 1, all: 3 });" + ], + [ + 30, + "" + ], + [ + 31, + " await apiSignout({ page });" + ], + [ + 32, + " }" + ], + [ + 33, + "});" + ] + ], + "diff": "@@ -20,23 +20,13 @@ test('[TEAMS]: check team documents count', async ({ page }) => {\n });\n\n // Check document counts.\n- await checkDocumentTabCount(page, 'Inbox', 0);\n- await checkDocumentTabCount(page, 'Pending', 2);\n- await checkDocumentTabCount(page, 'Completed', 1);\n- await checkDocumentTabCount(page, 'Draft', 2);\n- await checkDocumentTabCount(page, 'All', 5);\n+ await checkDocumentCounts(page, { inbox: 0, pending: 2, completed: 1, draft: 2, all: 5 });\n\n // Apply filter.\n- await page.locator('button').filter({ hasText: 'Sender: All' }).click();\n- await page.getByRole('option', { name: teamMember2.name ?? '' }).click();\n- await page.waitForURL(/senderIds/);\n+ await toggleDocumentSenderFilter(page, teamMember2.name ?? '');\n\n // Check counts after filtering.\n- await checkDocumentTabCount(page, 'Inbox', 0);\n- await checkDocumentTabCount(page, 'Pending', 2);\n- await checkDocumentTabCount(page, 'Completed', 0);\n- await checkDocumentTabCount(page, 'Draft', 1);\n- await checkDocumentTabCount(page, 'All', 3);\n+ await checkDocumentCounts(page, { inbox: 0, pending: 2, completed: 0, draft: 1, all: 3 });\n\n await apiSignout({ page });\n }" + }, + { + "item": "F013", + "kind": "pair", + "repository": "hoppscotch", + "commit_url": "https://github.com/hoppscotch/hoppscotch/commit/863b1b29a803f6c456b04dd82d19a53c7afc9102", + "rule": "CG-QUAL-101", + "rule_title": "Loose equality (== / !=)", + "path": "packages/hoppscotch-common/src/lib/sync/settings/index.ts", + "old_path": "packages/hoppscotch-selfhost-web/src/platform/settings/web/index.ts", + "old_line": 49, + "line": 42, + "before": [ + [ + 45, + " const res = await getUserSettings()" + ], + [ + 46, + "" + ], + [ + 47, + " // create user settings if it doesn't exist" + ], + [ + 48, + " E.isLeft(res) &&" + ], + [ + 49, + " res.left.error == \"user_settings/not_found\" &&" + ], + [ + 50, + " (await createUserSettings(JSON.stringify(getDefaultSettings())))" + ], + [ + 51, + "" + ], + [ + 52, + " if (E.isRight(res)) {" + ], + [ + 53, + " runDispatchWithOutSyncing(() => {" + ] + ], + "after": [ + [ + 38, + "async function loadUserSettings() {" + ], + [ + 39, + " const res = await getUserSettings()" + ], + [ + 40, + "" + ], + [ + 41, + " // create user settings if it doesn't exist" + ], + [ + 42, + " if (E.isLeft(res) && res.left.error == \"user_settings/not_found\") {" + ], + [ + 43, + " const createRes = await createUserSettings(" + ], + [ + 44, + " JSON.stringify(getDefaultSettings())" + ], + [ + 45, + " )" + ], + [ + 46, + "" + ] + ], + "diff": "@@ -45,11 +39,19 @@ async function loadUserSettings() {\n const res = await getUserSettings()\n\n // create user settings if it doesn't exist\n- E.isLeft(res) &&\n- res.left.error == \"user_settings/not_found\" &&\n- (await createUserSettings(JSON.stringify(getDefaultSettings())))\n+ if (E.isLeft(res) && res.left.error == \"user_settings/not_found\") {\n+ const createRes = await createUserSettings(\n+ JSON.stringify(getDefaultSettings())\n+ )\n\n- if (E.isRight(res)) {\n+ if (E.isRight(createRes)) {\n+ runDispatchWithOutSyncing(() => {\n+ bulkApplySettings(\n+ JSON.parse(createRes.right.createUserSettings.properties)\n+ )\n+ })\n+ }\n+ } else if (E.isRight(res)) {\n runDispatchWithOutSyncing(() => {\n bulkApplySettings(JSON.parse(res.right.me.settings.properties))\n })\n" + }, + { + "item": "F014", + "kind": "introduced", + "repository": "erpnext", + "commit_url": "https://github.com/frappe/erpnext/commit/8c85cef29b0b80a23a499516a94d4319cf45503d", + "rule": "CG-QUAL-104", + "rule_title": "var declaration", + "path": "erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js", + "old_path": "erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.js", + "old_line": null, + "line": 119, + "before": [], + "after": [ + [ + 115, + "\t\t\t\t});" + ], + [ + 116, + "" + ], + [ + 117, + "\t\t\t\tvar charge_precision = precision(\"applicable_charges\", items[0]);" + ], + [ + 118, + "\t\t\t\tvar total = flt(this.frm.doc.total_taxes_and_charges);" + ], + [ + 119, + "\t\t\t\tvar sign = total < 0 ? -1 : 1;" + ], + [ + 120, + "\t\t\t\tvar diff = flt(total - total_charges, charge_precision);" + ], + [ + 121, + "\t\t\t\tfor (const item of items.slice().reverse()) {" + ], + [ + 122, + "\t\t\t\t\tif (!diff) break;" + ], + [ + 123, + "\t\t\t\t\tvar share = flt(item.applicable_charges) * sign;" + ] + ], + "diff": "@@ -114,9 +114,19 @@ erpnext.stock.LandedCostVoucher = class LandedCostVoucher extends erpnext.stock.\n \t\t\t\t\ttotal_charges += item.applicable_charges;\n \t\t\t\t});\n\n-\t\t\t\tif (total_charges != this.frm.doc.total_taxes_and_charges) {\n-\t\t\t\t\tvar diff = this.frm.doc.total_taxes_and_charges - flt(total_charges);\n-\t\t\t\t\titems.slice(-1)[0].applicable_charges += diff;\n+\t\t\t\tvar charge_precision = precision(\"applicable_charges\", items[0]);\n+\t\t\t\tvar total = flt(this.frm.doc.total_taxes_and_charges);\n+\t\t\t\tvar sign = total < 0 ? -1 : 1;\n+\t\t\t\tvar diff = flt(total - total_charges, charge_precision);\n+\t\t\t\tfor (const item of items.slice().reverse()) {\n+\t\t\t\t\tif (!diff) break;\n+\t\t\t\t\tvar share = flt(item.applicable_charges) * sign;\n+\t\t\t\t\tvar adjustment = diff * sign > 0 ? diff : sign * Math.max(diff * sign, -share);\n+\t\t\t\t\titem.applicable_charges = flt(\n+\t\t\t\t\t\tflt(item.applicable_charges) + adjustment,\n+\t\t\t\t\t\tcharge_precision\n+\t\t\t\t\t);\n+\t\t\t\t\tdiff = flt(diff - adjustment, charge_precision);\n \t\t\t\t}\n \t\t\t\trefresh_field(\"items\");\n \t\t\t}\n" + }, + { + "item": "F015", + "kind": "introduced", + "repository": "calcom", + "commit_url": "https://github.com/calcom/cal.com/commit/ab21c7f805a089fa3a11ffd61c4a9aecc349c16c", + "rule": "CG-SEC-105", + "rule_title": "Hardcoded secret", + "path": "packages/features/bookings/lib/handleNewBooking/test/reschedule.test.ts", + "old_path": "packages/features/bookings/lib/handleNewBooking/test/reschedule.test.ts", + "old_line": null, + "line": 806, + "before": [], + "after": [ + [ + 802, + " {" + ], + [ + 803, + " type: appStoreMetadata.dailyvideo.type," + ], + [ + 804, + " uid: \"MOCK_ID\"," + ], + [ + 805, + " meetingId: \"MOCK_ID\"," + ], + [ + 806, + " meetingPassword: \"MOCK_PASS\"," + ], + [ + 807, + " meetingUrl: \"http://mock-dailyvideo.example.com\"," + ], + [ + 808, + " }," + ], + [ + 809, + " {" + ], + [ + 810, + " type: appStoreMetadata.googlecalendar.type," + ] + ], + "diff": "@@ -690,8 +660,201 @@ describe(\"handleNewBooking\", () => {\n );\n\n describe(\"Event Type that requires confirmation\", () => {\n- // NOTE: Tests for BOOKING_REQUESTED webhook have been moved to the new webhook architecture test suite\n- // See packages/features/webhooks/lib/__tests__/ for tests using the producer/consumer pattern\n+ test(\n+ `should reschedule a booking that requires confirmation in PENDING state - When a booker(who is not the organizer himself) is doing the reschedule\n+ 1. Should cancel the existing booking\n+ 2. Should delete existing calendar invite and Video meeting\n+ 2. Should create a new booking in the database in PENDING state\n+ 3. Should send BOOKING Requested scenario emails to the booker as well as organizer\n+ 4. Should trigger BOOKING_REQUESTED webhook instead of BOOKING_RESCHEDULED\n+ `,\n+ async ({ emails }) => {\n+ const handleNewBooking = getNewBookingHandler();\n+ const subscriberUrl = \"http://my-webhook.example.com\";\n+ const booker = getBooker({\n+ email: \"booker@example.com\",\n+ name: \"Booker\",\n+ });\n+\n+ const organizer = getOrganizer({\n+ name: \"Organizer\",\n+ email: \"organizer@example.com\",\n+ id: 101,\n+ schedules: [TestData.schedules.IstWorkHours],\n+ credentials: [getGoogleCalendarCredential()],\n+ selectedCalendars: [TestData.selectedCalendars.google],\n+ });\n+ const { dateString: plus1DateString } = getDate({ dateIncrement: 1 });\n+ const uidOfBookingToBeRescheduled = \"n5Wv3eHgconAED2j4gcVhP\";\n+\n+ const scenarioData = getScenarioData({\n+ webhooks: [\n+ {\n+ userId: organizer.id,\n+ eventTriggers: [\"BOOKING_CREATED\"],\n+ subscriberUrl,\n+ active: true,\n... (164 more lines)" + }, + { + "item": "F016", + "kind": "pair", + "repository": "kitsune", + "commit_url": "https://github.com/mozilla/kitsune/commit/59b6937db573505c764bec9c87bd786981848487", + "rule": "CG-QUAL-104", + "rule_title": "var declaration", + "path": "kitsune/sumo/static/sumo/js/markup.js", + "old_path": "kitsune/sumo/static/sumo/js/markup.js", + "old_line": 633, + "line": 633, + "before": [ + [ + 629, + " this.closeTag = \"\";" + ], + [ + 630, + " this.defaultText = \"\";" + ], + [ + 631, + " }," + ], + [ + 632, + " openModal: function (ev) {" + ], + [ + 633, + " var me = this;" + ], + [ + 634, + " var editorEl = me.textarea.closest(\".editor\");" + ], + [ + 635, + " var mediaSearchUrl = editorEl ? editorEl.dataset.mediaSearchUrl : undefined;" + ], + [ + 636, + " var galleryUrl = editorEl ? editorEl.dataset.mediaGalleryUrl : \"\";" + ], + [ + 637, + " var html = parseHTML(" + ] + ], + "after": [ + [ + 629, + " this.closeTag = \"\";" + ], + [ + 630, + " this.defaultText = \"\";" + ], + [ + 631, + " }," + ], + [ + 632, + " openModal: function (ev) {" + ], + [ + 633, + " var me = this;" + ], + [ + 634, + " var editorEl = me.textarea.closest(\".editor\");" + ], + [ + 635, + " var mediaSearchUrl = editorEl ? editorEl.dataset.mediaSearchUrl : undefined;" + ], + [ + 636, + " var galleryUrl = editorEl ? editorEl.dataset.mediaGalleryUrl : \"\";" + ], + [ + 637, + " var html = parseHTML(" + ] + ], + "diff": "(no diff hunk near this line)" + }, + { + "item": "F017", + "kind": "pair", + "repository": "openlibrary", + "commit_url": "https://github.com/internetarchive/openlibrary/commit/53015559ebcd126850e7b07792ebc7bcfa847919", + "rule": "CG-QUAL-104", + "rule_title": "var declaration", + "path": "openlibrary/plugins/openlibrary/js/jsdef.js", + "old_path": "openlibrary/plugins/openlibrary/js/jsdef.js", + "old_line": 77, + "line": 80, + "before": [ + [ + 73, + " this.index0 = -1;" + ], + [ + 74, + "}" + ], + [ + 75, + "" + ], + [ + 76, + "ForLoop.prototype.next = function() {" + ], + [ + 77, + " var i = this.index0+1;" + ], + [ + 78, + "" + ], + [ + 79, + " this.index0 = i;" + ], + [ + 80, + " this.index = i+1;" + ], + [ + 81, + "" + ] + ], + "after": [ + [ + 76, + " this.index0 = -1;" + ], + [ + 77, + "}" + ], + [ + 78, + "" + ], + [ + 79, + "ForLoop.prototype.next = function () {" + ], + [ + 80, + " var i = this.index0 + 1;" + ], + [ + 81, + "" + ], + [ + 82, + " this.index0 = i;" + ], + [ + 83, + " this.index = i + 1;" + ], + [ + 84, + "" + ] + ], + "diff": "@@ -73,17 +76,17 @@ export function ForLoop(parent, seq) {\n this.index0 = -1;\n }\n\n-ForLoop.prototype.next = function() {\n- var i = this.index0+1;\n+ForLoop.prototype.next = function () {\n+ var i = this.index0 + 1;\n\n this.index0 = i;\n- this.index = i+1;\n+ this.index = i + 1;\n\n- this.first = (i === 0);\n- this.last = (i === this.length-1);\n+ this.first = i === 0;\n+ this.last = i === this.length - 1;\n\n- this.odd = (this.index % 2 === 1);\n- this.even = (this.index % 2 === 0);\n+ this.odd = this.index % 2 === 1;\n+ this.even = this.index % 2 === 0;\n this.parity = ['even', 'odd'][this.index % 2];\n\n this.revindex0 = this.length - i;" + }, + { + "item": "F018", + "kind": "pair", + "repository": "ckan", + "commit_url": "https://github.com/ckan/ckan/commit/ea3c0fd8814de0fa83e744bcb4d5ab5a93902a34", + "rule": "CG-QUAL-003", + "rule_title": "Function is too complex", + "path": "ckan/logic/action/update.py", + "old_path": "ckan/logic/action/update.py", + "old_line": 474, + "line": 500, + "before": [ + [ + 470, + "" + ], + [ + 471, + " return pkg_custom" + ], + [ + 472, + "" + ], + [ + 473, + "" + ], + [ + 474, + "def package_revise(context: Context, data_dict: DataDict) -> ActionResult.PackageRevise:" + ], + [ + 475, + " '''Revise a dataset (package) selectively with match, filter and" + ], + [ + 476, + " update parameters." + ], + [ + 477, + "" + ], + [ + 478, + " You must be authorized to edit the dataset and the groups that it belongs" + ] + ], + "after": [ + [ + 496, + "" + ], + [ + 497, + " return pkg_custom" + ], + [ + 498, + "" + ], + [ + 499, + "" + ], + [ + 500, + "def package_revise(context: Context, data_dict: DataDict) -> ActionResult.PackageRevise:" + ], + [ + 501, + " '''Revise a dataset (package) selectively with match, filter and" + ], + [ + 502, + " update parameters." + ], + [ + 503, + "" + ], + [ + 504, + " You must be authorized to edit the dataset and the groups that it belongs" + ] + ], + "diff": "(no diff hunk near this line)" + }, + { + "item": "F019", + "kind": "pair", + "repository": "inventree", + "commit_url": "https://github.com/inventree/InvenTree/commit/5244b8a4f21436f699ea266eb5a7381f899409f0", + "rule": "CG-QUAL-003", + "rule_title": "Function is too complex", + "path": "src/backend/InvenTree/plugin/registry.py", + "old_path": "src/backend/InvenTree/plugin/registry.py", + "old_line": 688, + "line": 689, + "before": [ + [ + 684, + "" + ], + [ + 685, + " # endregion" + ], + [ + 686, + "" + ], + [ + 687, + " # region general internal loading / activating / deactivating / unloading" + ], + [ + 688, + " def _init_plugin(self, plugin, configs: dict):" + ], + [ + 689, + " \"\"\"Initialise a single plugin." + ], + [ + 690, + "" + ], + [ + 691, + " Args:" + ], + [ + 692, + " plugin: Plugin module" + ] + ], + "after": [ + [ + 685, + "" + ], + [ + 686, + " # endregion" + ], + [ + 687, + "" + ], + [ + 688, + " # region general internal loading / activating / deactivating / unloading" + ], + [ + 689, + " def _init_plugin(self, plugin, configs: dict):" + ], + [ + 690, + " \"\"\"Initialise a single plugin." + ], + [ + 691, + "" + ], + [ + 692, + " Args:" + ], + [ + 693, + " plugin: Plugin module" + ] + ], + "diff": "(no diff hunk near this line)" + }, + { + "item": "F020", + "kind": "introduced", + "repository": "ckan", + "commit_url": "https://github.com/ckan/ckan/commit/7a60264189f543fba04043d2193b09f545a6ef32", + "rule": "CG-QUAL-104", + "rule_title": "var declaration", + "path": "ckan/public-midnight-blue/base/javascript/modules/data-viewer.js", + "old_path": "ckan/public-midnight-blue/base/javascript/modules/data-viewer.js", + "old_line": null, + "line": 27, + "before": [], + "after": [ + [ + 23, + " }," + ], + [ + 24, + "" + ], + [ + 25, + " _onLoad: function() {" + ], + [ + 26, + " var self = this;" + ], + [ + 27, + " var loc = document.head.querySelector(['meta[name=site-root]'])?.content;" + ], + [ + 28, + " // see if page is in part of the same domain" + ], + [ + 29, + " if (this.el.attr('src').substring(0, loc.length) === loc) {" + ], + [ + 30, + " this._recalibrate();" + ], + [ + 31, + " setTimeout(function() {" + ] + ], + "diff": "@@ -24,7 +24,7 @@ this.ckan.module('data-viewer', function (jQuery) {\n\n _onLoad: function() {\n var self = this;\n- var loc = $('body').data('site-root');\n+ var loc = document.head.querySelector(['meta[name=site-root]'])?.content;\n // see if page is in part of the same domain\n if (this.el.attr('src').substring(0, loc.length) === loc) {\n this._recalibrate();\n" + }, + { + "item": "F021", + "kind": "pair", + "repository": "warehouse", + "commit_url": "https://github.com/pypi/warehouse/commit/7320d9052b59a7e3b1c2122034396f5d339cd3e0", + "rule": "CG-SEC-103", + "rule_title": "DOM XSS sink assigned a non-literal value", + "path": "tests/frontend/filter_list_controller_test.js", + "old_path": "tests/frontend/filter_list_controller_test.js", + "old_line": 39, + "line": 101, + "before": [ + [ + 35, + " describe(\"is initialized as expected\", () => {" + ], + [ + 36, + " describe(\"makes expected elements visible\", () => {" + ], + [ + 37, + " let application;" + ], + [ + 38, + " beforeEach(() => {" + ], + [ + 39, + " document.body.innerHTML = `" + ], + [ + 40, + "
" + ], + [ + 41, + " ${testFixtureHTMLVisibilityToggle}" + ], + [ + 42, + "
" + ], + [ + 43, + " `;" + ] + ], + "after": [ + [ + 97, + " expect(Array.from(filterMyAttr.options).map(option => [option.value, option.selected])).toEqual(values);" + ], + [ + 98, + " };" + ], + [ + 99, + " const appStart = async function () {" + ], + [ + 100, + " const div = document.createElement(\"div\");" + ], + [ + 101, + " div.innerHTML = `" + ], + [ + 102, + "
" + ], + [ + 103, + " ${testFixtureHTMLVisibilityToggle}" + ], + [ + 104, + " ${testFixtureHTMLShowing}" + ], + [ + 105, + " ${testFixtureHTMLFilters}" + ] + ], + "diff": "@@ -5,226 +5,424 @@\n\n import {Application} from \"@hotwired/stimulus\";\n import FilterListController from \"../../warehouse/static/js/warehouse/controllers/filter_list_controller\";\n+import {delay} from \"./utils\";\n\n\n const testFixtureHTMLVisibilityToggle = `\n-

Initially hidden, should end up shown.

\n-

Initially shown, should end up hidden.

\n+

\n+ Initially hidden, should end up shown.\n+

\n+

\n+ Initially shown, should end up hidden.\n+

\n `;\n const testFixtureHTMLShowing = `\n

\n `;\n const testFixtureHTMLFilters = `\n-\n- \n+\n+\n+