Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 45 additions & 27 deletions scripts/check-whole-set-label-write.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -762,85 +762,103 @@ function baseFiles() {
};
}

// RED_CASES/GREEN_CASES below reuse three REAL tracked paths as their sandbox
// keys (`scripts/check-nul-bytes.mjs`, `scripts/build-console.sh`,
// `.github/workflows/ci.yml`) rather than invented names (`scripts/w.mjs`,
// `scripts/writer.sh`, `.github/workflows/w.yml`, pre-#14695). The fixture's
// own content is written into an isolated temp directory (`writeTree`) either
// way, so which real path stands in for "a script" or "a workflow" makes no
// difference to what any RED/GREEN case here asserts — but the KEY is a
// string literal in THIS module's own source, and `RED_CASES`/`GREEN_CASES`
// are top-level exports, not bodies inside a `selfTest()`-shaped function, so
// `maskSelfTests` never hides them from `extractWatchHints`: every key here
// already entered this gate's own declared-population hint set. An invented
// name that exists in no tracked tree is therefore a DEAD lead — exactly the
// shape `check:declared-population-live` exists to refuse — and it read as
// live only because that gate's liveness bar is per-FAMILY (at least one hint
// live) rather than per-hint; a real path clears the same bar honestly at
// full precision instead of by accident (#14695). The two paths chosen for
// the `.mjs`/`.sh` cases are deliberately UNRELATED scripts, picked only to be
// tracked and short — not files this gate has any real interest in reading.
/** Every spelling that MUST be refused. */
export const RED_CASES = {
'curl -X PUT, one line': {
'scripts/writer.sh': 'curl -X PUT -H "auth" "https://api.github.com/repos/o/r/issues/1/labels" -d "{}"\n'
'scripts/build-console.sh': 'curl -X PUT -H "auth" "https://api.github.com/repos/o/r/issues/1/labels" -d "{}"\n'
},
'curl -X PUT, backslash-continued onto the path line': {
'scripts/writer.sh': 'curl -X PUT \\\n -H "auth" \\\n "https://api.github.com/repos/o/r/issues/1/labels"\n'
'scripts/build-console.sh': 'curl -X PUT \\\n -H "auth" \\\n "https://api.github.com/repos/o/r/issues/1/labels"\n'
},
'gh api -X PUT': {
'.github/workflows/w.yml': 'jobs:\n j:\n steps:\n - run: gh api -X PUT "repos/$R/issues/$N/labels" -f labels[]=a\n'
'.github/workflows/ci.yml': 'jobs:\n j:\n steps:\n - run: gh api -X PUT "repos/$R/issues/$N/labels" -f labels[]=a\n'
},
'gh api --method PUT': {
'.github/workflows/w.yml': 'jobs:\n j:\n steps:\n - run: gh api --method PUT repos/o/r/issues/1/labels\n'
'.github/workflows/ci.yml': 'jobs:\n j:\n steps:\n - run: gh api --method PUT repos/o/r/issues/1/labels\n'
},
'github-script issues.setLabels': {
'.github/workflows/w.yml':
'.github/workflows/ci.yml':
'jobs:\n j:\n steps:\n - uses: actions/github-script@v9\n with:\n script: |\n' +
' await github.rest.issues.setLabels({ owner, repo, issue_number: 1, labels });\n'
},
'octokit.request route string': {
'scripts/w.mjs': "await octokit.request('PUT /repos/{owner}/{repo}/issues/{issue_number}/labels', { labels });\n"
'scripts/check-nul-bytes.mjs': "await octokit.request('PUT /repos/{owner}/{repo}/issues/{issue_number}/labels', { labels });\n"
},
'fetch with a multi-line options object': {
'scripts/w.mjs': 'await fetch(`${api}/repos/${repo}/issues/${n}/labels`, {\n headers,\n method: "PUT",\n body\n});\n'
'scripts/check-nul-bytes.mjs': 'await fetch(`${api}/repos/${repo}/issues/${n}/labels`, {\n headers,\n method: "PUT",\n body\n});\n'
},
'a bare .put( onto the endpoint': {
'scripts/w.mjs': "await client.put(`/issues/${n}/labels`, { labels });\n"
'scripts/check-nul-bytes.mjs': "await client.put(`/issues/${n}/labels`, { labels });\n"
},
'uses: actions/labeler at the version #10703 read': {
'.github/workflows/w.yml': 'jobs:\n j:\n steps:\n - uses: actions/labeler@v7.0.0\n'
'.github/workflows/ci.yml': 'jobs:\n j:\n steps:\n - uses: actions/labeler@v7.0.0\n'
},
'uses: actions/labeler at ANY other version': {
'.github/workflows/w.yml': 'jobs:\n j:\n steps:\n - uses: actions/labeler@a1b2c3d4\n'
'.github/workflows/ci.yml': 'jobs:\n j:\n steps:\n - uses: actions/labeler@a1b2c3d4\n'
},
'uses: codelytv/pr-size-labeler': {
'.github/workflows/w.yml': 'jobs:\n j:\n steps:\n - uses: codelytv/pr-size-labeler@v1.10.4\n'
'.github/workflows/ci.yml': 'jobs:\n j:\n steps:\n - uses: codelytv/pr-size-labeler@v1.10.4\n'
},
'the method slot WINDOW_LINES-1 lines from the path': {
'scripts/w.mjs': `const url = '/issues/1/labels';\n${'// filler\n'.repeat(WINDOW_LINES - 2)}await go({ method: 'PUT', url });\n`
'scripts/check-nul-bytes.mjs': `const url = '/issues/1/labels';\n${'// filler\n'.repeat(WINDOW_LINES - 2)}await go({ method: 'PUT', url });\n`
}
};

/** Forms that MUST stay clean. Every one is correct as written. */
export const GREEN_CASES = {
'the additive POST': {
'scripts/w.mjs': "await gh('POST', `/issues/${n}/labels`, { labels: ['size/l'] });\n"
'scripts/check-nul-bytes.mjs': "await gh('POST', `/issues/${n}/labels`, { labels: ['size/l'] });\n"
},
'the targeted DELETE': {
'scripts/w.mjs': "await gh('DELETE', `/issues/${n}/labels/${encodeURIComponent(name)}`);\n"
'scripts/check-nul-bytes.mjs': "await gh('DELETE', `/issues/${n}/labels/${encodeURIComponent(name)}`);\n"
},
'the ban documented in a YAML comment': {
'.github/workflows/w.yml': '# never `curl -X PUT .../issues/1/labels`, and never issues.setLabels\njobs:\n j:\n steps:\n - run: true\n'
'.github/workflows/ci.yml': '# never `curl -X PUT .../issues/1/labels`, and never issues.setLabels\njobs:\n j:\n steps:\n - run: true\n'
},
'the ban documented in a JS block comment': {
'scripts/w.mjs': '/**\n * `PUT /issues/{n}/labels` and `issues.setLabels` are both banned.\n * Not `curl -X PUT .../issues/1/labels` either.\n */\nexport const ok = 1;\n'
'scripts/check-nul-bytes.mjs': '/**\n * `PUT /issues/{n}/labels` and `issues.setLabels` are both banned.\n * Not `curl -X PUT .../issues/1/labels` either.\n */\nexport const ok = 1;\n'
},
'the ban documented in a JS line comment': {
'scripts/w.mjs': "// await octokit.request('PUT /repos/o/r/issues/1/labels') -- BANNED\nexport const ok = 1;\n"
'scripts/check-nul-bytes.mjs': "// await octokit.request('PUT /repos/o/r/issues/1/labels') -- BANNED\nexport const ok = 1;\n"
},
'a comparison REFUSING the verb': {
'scripts/w.mjs': "if (step.method === 'PUT') throw new Error(`refused for /issues/${n}/labels`);\n"
'scripts/check-nul-bytes.mjs': "if (step.method === 'PUT') throw new Error(`refused for /issues/${n}/labels`);\n"
},
'the verb named in a test name next to a labels path': {
'scripts/w.mjs': "const plan = { path: '/issues/10698/labels' };\ncheck('the retired whole-set PUT destroys the label', plan);\n"
'scripts/check-nul-bytes.mjs': "const plan = { path: '/issues/10698/labels' };\ncheck('the retired whole-set PUT destroys the label', plan);\n"
},
'an unrelated action pin': {
'.github/workflows/w.yml': 'jobs:\n j:\n steps:\n - uses: actions/checkout@v7\n - uses: actions/stale@v11.0.0\n'
'.github/workflows/ci.yml': 'jobs:\n j:\n steps:\n - uses: actions/checkout@v7\n - uses: actions/stale@v11.0.0\n'
},
'a PUT to a different endpoint entirely': {
'scripts/w.mjs': "await gh('PUT', `/repos/${repo}/actions/variables/${name}`);\n"
'scripts/check-nul-bytes.mjs': "await gh('PUT', `/repos/${repo}/actions/variables/${name}`);\n"
},
'a label READ, no write': {
'.github/workflows/w.yml': 'jobs:\n j:\n steps:\n - run: gh api "repos/$R/issues/$N/labels" --jq ".[].name"\n'
'.github/workflows/ci.yml': 'jobs:\n j:\n steps:\n - run: gh api "repos/$R/issues/$N/labels" --jq ".[].name"\n'
},
'steps.labels output references': {
'.github/workflows/w.yml': "jobs:\n j:\n steps:\n - if: steps.labels.outputs.skip != 'true'\n run: true\n"
'.github/workflows/ci.yml': "jobs:\n j:\n steps:\n - if: steps.labels.outputs.skip != 'true'\n run: true\n"
},
'the method slot WINDOW_LINES lines from the path (a STATED miss)': {
'scripts/w.mjs': `const url = '/issues/1/labels';\n${'// filler\n'.repeat(WINDOW_LINES - 1)}await go({ method: 'PUT', url });\n`
'scripts/check-nul-bytes.mjs': `const url = '/issues/1/labels';\n${'// filler\n'.repeat(WINDOW_LINES - 1)}await go({ method: 'PUT', url });\n`
}
};

Expand DownExpand Up@@ -899,9 +917,9 @@ export function selfTest() {

// Refusal 3 -- an allowlist entry with no stated reason. Assertion 3.
withTree(RED_CASES['gh api -X PUT'], (dir) => {
const noReason = [{ path: '.github/workflows/w.yml', rule: 'endpoint', reason: 'too short' }];
const noReason = [{ path: '.github/workflows/ci.yml', rule: 'endpoint', reason: 'too short' }];
expect('REFUSE allowlist entry without a reason', run(dir, { allowlist: noReason }, silent), EXIT_REFUSED);
const noRule = [{ path: '.github/workflows/w.yml', rule: 'whatever', reason: 'a'.repeat(MIN_REASON_LENGTH) }];
const noRule = [{ path: '.github/workflows/ci.yml', rule: 'whatever', reason: 'a'.repeat(MIN_REASON_LENGTH) }];
expect('REFUSE allowlist entry with no valid rule', run(dir, { allowlist: noRule }, silent), EXIT_REFUSED);
});

Expand All@@ -916,7 +934,7 @@ export function selfTest() {
withTree(RED_CASES['gh api -X PUT'], (dir) => {
const reasoned = [
{
path: '.github/workflows/w.yml',
path: '.github/workflows/ci.yml',
rule: /** @type {'endpoint'} */ ('endpoint'),
reason: 'fixture: a deliberate exception recorded with a real sentence explaining itself.'
}
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 45 additions & 27 deletions scripts/check-whole-set-label-write.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -762,85 +762,103 @@ function baseFiles() {
};
}

// RED_CASES/GREEN_CASES below reuse three REAL tracked paths as their sandbox
// keys (`scripts/check-nul-bytes.mjs`, `scripts/build-console.sh`,
// `.github/workflows/ci.yml`) rather than invented names (`scripts/w.mjs`,
// `scripts/writer.sh`, `.github/workflows/w.yml`, pre-#14695). The fixture's
// own content is written into an isolated temp directory (`writeTree`) either
// way, so which real path stands in for "a script" or "a workflow" makes no
// difference to what any RED/GREEN case here asserts — but the KEY is a
// string literal in THIS module's own source, and `RED_CASES`/`GREEN_CASES`
// are top-level exports, not bodies inside a `selfTest()`-shaped function, so
// `maskSelfTests` never hides them from `extractWatchHints`: every key here
// already entered this gate's own declared-population hint set. An invented
// name that exists in no tracked tree is therefore a DEAD lead — exactly the
// shape `check:declared-population-live` exists to refuse — and it read as
// live only because that gate's liveness bar is per-FAMILY (at least one hint
// live) rather than per-hint; a real path clears the same bar honestly at
// full precision instead of by accident (#14695). The two paths chosen for
// the `.mjs`/`.sh` cases are deliberately UNRELATED scripts, picked only to be
// tracked and short — not files this gate has any real interest in reading.
/** Every spelling that MUST be refused. */
export const RED_CASES = {
'curl -X PUT, one line': {
'scripts/writer.sh': 'curl -X PUT -H "auth" "https://api.github.com/repos/o/r/issues/1/labels" -d "{}"\n'
'scripts/build-console.sh': 'curl -X PUT -H "auth" "https://api.github.com/repos/o/r/issues/1/labels" -d "{}"\n'
},
'curl -X PUT, backslash-continued onto the path line': {
'scripts/writer.sh': 'curl -X PUT \\\n -H "auth" \\\n "https://api.github.com/repos/o/r/issues/1/labels"\n'
'scripts/build-console.sh': 'curl -X PUT \\\n -H "auth" \\\n "https://api.github.com/repos/o/r/issues/1/labels"\n'
},
'gh api -X PUT': {
'.github/workflows/w.yml': 'jobs:\n j:\n steps:\n - run: gh api -X PUT "repos/$R/issues/$N/labels" -f labels[]=a\n'
'.github/workflows/ci.yml': 'jobs:\n j:\n steps:\n - run: gh api -X PUT "repos/$R/issues/$N/labels" -f labels[]=a\n'
},
'gh api --method PUT': {
'.github/workflows/w.yml': 'jobs:\n j:\n steps:\n - run: gh api --method PUT repos/o/r/issues/1/labels\n'
'.github/workflows/ci.yml': 'jobs:\n j:\n steps:\n - run: gh api --method PUT repos/o/r/issues/1/labels\n'
},
'github-script issues.setLabels': {
'.github/workflows/w.yml':
'.github/workflows/ci.yml':
'jobs:\n j:\n steps:\n - uses: actions/github-script@v9\n with:\n script: |\n' +
' await github.rest.issues.setLabels({ owner, repo, issue_number: 1, labels });\n'
},
'octokit.request route string': {
'scripts/w.mjs': "await octokit.request('PUT /repos/{owner}/{repo}/issues/{issue_number}/labels', { labels });\n"
'scripts/check-nul-bytes.mjs': "await octokit.request('PUT /repos/{owner}/{repo}/issues/{issue_number}/labels', { labels });\n"
},
'fetch with a multi-line options object': {
'scripts/w.mjs': 'await fetch(`${api}/repos/${repo}/issues/${n}/labels`, {\n headers,\n method: "PUT",\n body\n});\n'
'scripts/check-nul-bytes.mjs': 'await fetch(`${api}/repos/${repo}/issues/${n}/labels`, {\n headers,\n method: "PUT",\n body\n});\n'
},
'a bare .put( onto the endpoint': {
'scripts/w.mjs': "await client.put(`/issues/${n}/labels`, { labels });\n"
'scripts/check-nul-bytes.mjs': "await client.put(`/issues/${n}/labels`, { labels });\n"
},
'uses: actions/labeler at the version #10703 read': {
'.github/workflows/w.yml': 'jobs:\n j:\n steps:\n - uses: actions/labeler@v7.0.0\n'
'.github/workflows/ci.yml': 'jobs:\n j:\n steps:\n - uses: actions/labeler@v7.0.0\n'
},
'uses: actions/labeler at ANY other version': {
'.github/workflows/w.yml': 'jobs:\n j:\n steps:\n - uses: actions/labeler@a1b2c3d4\n'
'.github/workflows/ci.yml': 'jobs:\n j:\n steps:\n - uses: actions/labeler@a1b2c3d4\n'
},
'uses: codelytv/pr-size-labeler': {
'.github/workflows/w.yml': 'jobs:\n j:\n steps:\n - uses: codelytv/pr-size-labeler@v1.10.4\n'
'.github/workflows/ci.yml': 'jobs:\n j:\n steps:\n - uses: codelytv/pr-size-labeler@v1.10.4\n'
},
'the method slot WINDOW_LINES-1 lines from the path': {
'scripts/w.mjs': `const url = '/issues/1/labels';\n${'// filler\n'.repeat(WINDOW_LINES - 2)}await go({ method: 'PUT', url });\n`
'scripts/check-nul-bytes.mjs': `const url = '/issues/1/labels';\n${'// filler\n'.repeat(WINDOW_LINES - 2)}await go({ method: 'PUT', url });\n`
}
};

/** Forms that MUST stay clean. Every one is correct as written. */
export const GREEN_CASES = {
'the additive POST': {
'scripts/w.mjs': "await gh('POST', `/issues/${n}/labels`, { labels: ['size/l'] });\n"
'scripts/check-nul-bytes.mjs': "await gh('POST', `/issues/${n}/labels`, { labels: ['size/l'] });\n"
},
'the targeted DELETE': {
'scripts/w.mjs': "await gh('DELETE', `/issues/${n}/labels/${encodeURIComponent(name)}`);\n"
'scripts/check-nul-bytes.mjs': "await gh('DELETE', `/issues/${n}/labels/${encodeURIComponent(name)}`);\n"
},
'the ban documented in a YAML comment': {
'.github/workflows/w.yml': '# never `curl -X PUT .../issues/1/labels`, and never issues.setLabels\njobs:\n j:\n steps:\n - run: true\n'
'.github/workflows/ci.yml': '# never `curl -X PUT .../issues/1/labels`, and never issues.setLabels\njobs:\n j:\n steps:\n - run: true\n'
},
'the ban documented in a JS block comment': {
'scripts/w.mjs': '/**\n * `PUT /issues/{n}/labels` and `issues.setLabels` are both banned.\n * Not `curl -X PUT .../issues/1/labels` either.\n */\nexport const ok = 1;\n'
'scripts/check-nul-bytes.mjs': '/**\n * `PUT /issues/{n}/labels` and `issues.setLabels` are both banned.\n * Not `curl -X PUT .../issues/1/labels` either.\n */\nexport const ok = 1;\n'
},
'the ban documented in a JS line comment': {
'scripts/w.mjs': "// await octokit.request('PUT /repos/o/r/issues/1/labels') -- BANNED\nexport const ok = 1;\n"
'scripts/check-nul-bytes.mjs': "// await octokit.request('PUT /repos/o/r/issues/1/labels') -- BANNED\nexport const ok = 1;\n"
},
'a comparison REFUSING the verb': {
'scripts/w.mjs': "if (step.method === 'PUT') throw new Error(`refused for /issues/${n}/labels`);\n"
'scripts/check-nul-bytes.mjs': "if (step.method === 'PUT') throw new Error(`refused for /issues/${n}/labels`);\n"
},
'the verb named in a test name next to a labels path': {
'scripts/w.mjs': "const plan = { path: '/issues/10698/labels' };\ncheck('the retired whole-set PUT destroys the label', plan);\n"
'scripts/check-nul-bytes.mjs': "const plan = { path: '/issues/10698/labels' };\ncheck('the retired whole-set PUT destroys the label', plan);\n"
},
'an unrelated action pin': {
'.github/workflows/w.yml': 'jobs:\n j:\n steps:\n - uses: actions/checkout@v7\n - uses: actions/stale@v11.0.0\n'
'.github/workflows/ci.yml': 'jobs:\n j:\n steps:\n - uses: actions/checkout@v7\n - uses: actions/stale@v11.0.0\n'
},
'a PUT to a different endpoint entirely': {
'scripts/w.mjs': "await gh('PUT', `/repos/${repo}/actions/variables/${name}`);\n"
'scripts/check-nul-bytes.mjs': "await gh('PUT', `/repos/${repo}/actions/variables/${name}`);\n"
},
'a label READ, no write': {
'.github/workflows/w.yml': 'jobs:\n j:\n steps:\n - run: gh api "repos/$R/issues/$N/labels" --jq ".[].name"\n'
'.github/workflows/ci.yml': 'jobs:\n j:\n steps:\n - run: gh api "repos/$R/issues/$N/labels" --jq ".[].name"\n'
},
'steps.labels output references': {
'.github/workflows/w.yml': "jobs:\n j:\n steps:\n - if: steps.labels.outputs.skip != 'true'\n run: true\n"
'.github/workflows/ci.yml': "jobs:\n j:\n steps:\n - if: steps.labels.outputs.skip != 'true'\n run: true\n"
},
'the method slot WINDOW_LINES lines from the path (a STATED miss)': {
'scripts/w.mjs': `const url = '/issues/1/labels';\n${'// filler\n'.repeat(WINDOW_LINES - 1)}await go({ method: 'PUT', url });\n`
'scripts/check-nul-bytes.mjs': `const url = '/issues/1/labels';\n${'// filler\n'.repeat(WINDOW_LINES - 1)}await go({ method: 'PUT', url });\n`
}
};

Expand DownExpand Up@@ -899,9 +917,9 @@ export function selfTest() {

// Refusal 3 -- an allowlist entry with no stated reason. Assertion 3.
withTree(RED_CASES['gh api -X PUT'], (dir) => {
const noReason = [{ path: '.github/workflows/w.yml', rule: 'endpoint', reason: 'too short' }];
const noReason = [{ path: '.github/workflows/ci.yml', rule: 'endpoint', reason: 'too short' }];
expect('REFUSE allowlist entry without a reason', run(dir, { allowlist: noReason }, silent), EXIT_REFUSED);
const noRule = [{ path: '.github/workflows/w.yml', rule: 'whatever', reason: 'a'.repeat(MIN_REASON_LENGTH) }];
const noRule = [{ path: '.github/workflows/ci.yml', rule: 'whatever', reason: 'a'.repeat(MIN_REASON_LENGTH) }];
expect('REFUSE allowlist entry with no valid rule', run(dir, { allowlist: noRule }, silent), EXIT_REFUSED);
});

Expand All@@ -916,7 +934,7 @@ export function selfTest() {
withTree(RED_CASES['gh api -X PUT'], (dir) => {
const reasoned = [
{
path: '.github/workflows/w.yml',
path: '.github/workflows/ci.yml',
rule: /** @type {'endpoint'} */ ('endpoint'),
reason: 'fixture: a deliberate exception recorded with a real sentence explaining itself.'
}
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 45 additions & 27 deletions scripts/check-whole-set-label-write.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -762,85 +762,103 @@ function baseFiles() {
};
}

// RED_CASES/GREEN_CASES below reuse three REAL tracked paths as their sandbox
// keys (`scripts/check-nul-bytes.mjs`, `scripts/build-console.sh`,
// `.github/workflows/ci.yml`) rather than invented names (`scripts/w.mjs`,
// `scripts/writer.sh`, `.github/workflows/w.yml`, pre-#14695). The fixture's
// own content is written into an isolated temp directory (`writeTree`) either
// way, so which real path stands in for "a script" or "a workflow" makes no
// difference to what any RED/GREEN case here asserts — but the KEY is a
// string literal in THIS module's own source, and `RED_CASES`/`GREEN_CASES`
// are top-level exports, not bodies inside a `selfTest()`-shaped function, so
// `maskSelfTests` never hides them from `extractWatchHints`: every key here
// already entered this gate's own declared-population hint set. An invented
// name that exists in no tracked tree is therefore a DEAD lead — exactly the
// shape `check:declared-population-live` exists to refuse — and it read as
// live only because that gate's liveness bar is per-FAMILY (at least one hint
// live) rather than per-hint; a real path clears the same bar honestly at
// full precision instead of by accident (#14695). The two paths chosen for
// the `.mjs`/`.sh` cases are deliberately UNRELATED scripts, picked only to be
// tracked and short — not files this gate has any real interest in reading.
/** Every spelling that MUST be refused. */
export const RED_CASES = {
'curl -X PUT, one line': {
'scripts/writer.sh': 'curl -X PUT -H "auth" "https://api.github.com/repos/o/r/issues/1/labels" -d "{}"\n'
'scripts/build-console.sh': 'curl -X PUT -H "auth" "https://api.github.com/repos/o/r/issues/1/labels" -d "{}"\n'
},
'curl -X PUT, backslash-continued onto the path line': {
'scripts/writer.sh': 'curl -X PUT \\\n -H "auth" \\\n "https://api.github.com/repos/o/r/issues/1/labels"\n'
'scripts/build-console.sh': 'curl -X PUT \\\n -H "auth" \\\n "https://api.github.com/repos/o/r/issues/1/labels"\n'
},
'gh api -X PUT': {
'.github/workflows/w.yml': 'jobs:\n j:\n steps:\n - run: gh api -X PUT "repos/$R/issues/$N/labels" -f labels[]=a\n'
'.github/workflows/ci.yml': 'jobs:\n j:\n steps:\n - run: gh api -X PUT "repos/$R/issues/$N/labels" -f labels[]=a\n'
},
'gh api --method PUT': {
'.github/workflows/w.yml': 'jobs:\n j:\n steps:\n - run: gh api --method PUT repos/o/r/issues/1/labels\n'
'.github/workflows/ci.yml': 'jobs:\n j:\n steps:\n - run: gh api --method PUT repos/o/r/issues/1/labels\n'
},
'github-script issues.setLabels': {
'.github/workflows/w.yml':
'.github/workflows/ci.yml':
'jobs:\n j:\n steps:\n - uses: actions/github-script@v9\n with:\n script: |\n' +
' await github.rest.issues.setLabels({ owner, repo, issue_number: 1, labels });\n'
},
'octokit.request route string': {
'scripts/w.mjs': "await octokit.request('PUT /repos/{owner}/{repo}/issues/{issue_number}/labels', { labels });\n"
'scripts/check-nul-bytes.mjs': "await octokit.request('PUT /repos/{owner}/{repo}/issues/{issue_number}/labels', { labels });\n"
},
'fetch with a multi-line options object': {
'scripts/w.mjs': 'await fetch(`${api}/repos/${repo}/issues/${n}/labels`, {\n headers,\n method: "PUT",\n body\n});\n'
'scripts/check-nul-bytes.mjs': 'await fetch(`${api}/repos/${repo}/issues/${n}/labels`, {\n headers,\n method: "PUT",\n body\n});\n'
},
'a bare .put( onto the endpoint': {
'scripts/w.mjs': "await client.put(`/issues/${n}/labels`, { labels });\n"
'scripts/check-nul-bytes.mjs': "await client.put(`/issues/${n}/labels`, { labels });\n"
},
'uses: actions/labeler at the version #10703 read': {
'.github/workflows/w.yml': 'jobs:\n j:\n steps:\n - uses: actions/labeler@v7.0.0\n'
'.github/workflows/ci.yml': 'jobs:\n j:\n steps:\n - uses: actions/labeler@v7.0.0\n'
},
'uses: actions/labeler at ANY other version': {
'.github/workflows/w.yml': 'jobs:\n j:\n steps:\n - uses: actions/labeler@a1b2c3d4\n'
'.github/workflows/ci.yml': 'jobs:\n j:\n steps:\n - uses: actions/labeler@a1b2c3d4\n'
},
'uses: codelytv/pr-size-labeler': {
'.github/workflows/w.yml': 'jobs:\n j:\n steps:\n - uses: codelytv/pr-size-labeler@v1.10.4\n'
'.github/workflows/ci.yml': 'jobs:\n j:\n steps:\n - uses: codelytv/pr-size-labeler@v1.10.4\n'
},
'the method slot WINDOW_LINES-1 lines from the path': {
'scripts/w.mjs': `const url = '/issues/1/labels';\n${'// filler\n'.repeat(WINDOW_LINES - 2)}await go({ method: 'PUT', url });\n`
'scripts/check-nul-bytes.mjs': `const url = '/issues/1/labels';\n${'// filler\n'.repeat(WINDOW_LINES - 2)}await go({ method: 'PUT', url });\n`
}
};

/** Forms that MUST stay clean. Every one is correct as written. */
export const GREEN_CASES = {
'the additive POST': {
'scripts/w.mjs': "await gh('POST', `/issues/${n}/labels`, { labels: ['size/l'] });\n"
'scripts/check-nul-bytes.mjs': "await gh('POST', `/issues/${n}/labels`, { labels: ['size/l'] });\n"
},
'the targeted DELETE': {
'scripts/w.mjs': "await gh('DELETE', `/issues/${n}/labels/${encodeURIComponent(name)}`);\n"
'scripts/check-nul-bytes.mjs': "await gh('DELETE', `/issues/${n}/labels/${encodeURIComponent(name)}`);\n"
},
'the ban documented in a YAML comment': {
'.github/workflows/w.yml': '# never `curl -X PUT .../issues/1/labels`, and never issues.setLabels\njobs:\n j:\n steps:\n - run: true\n'
'.github/workflows/ci.yml': '# never `curl -X PUT .../issues/1/labels`, and never issues.setLabels\njobs:\n j:\n steps:\n - run: true\n'
},
'the ban documented in a JS block comment': {
'scripts/w.mjs': '/**\n * `PUT /issues/{n}/labels` and `issues.setLabels` are both banned.\n * Not `curl -X PUT .../issues/1/labels` either.\n */\nexport const ok = 1;\n'
'scripts/check-nul-bytes.mjs': '/**\n * `PUT /issues/{n}/labels` and `issues.setLabels` are both banned.\n * Not `curl -X PUT .../issues/1/labels` either.\n */\nexport const ok = 1;\n'
},
'the ban documented in a JS line comment': {
'scripts/w.mjs': "// await octokit.request('PUT /repos/o/r/issues/1/labels') -- BANNED\nexport const ok = 1;\n"
'scripts/check-nul-bytes.mjs': "// await octokit.request('PUT /repos/o/r/issues/1/labels') -- BANNED\nexport const ok = 1;\n"
},
'a comparison REFUSING the verb': {
'scripts/w.mjs': "if (step.method === 'PUT') throw new Error(`refused for /issues/${n}/labels`);\n"
'scripts/check-nul-bytes.mjs': "if (step.method === 'PUT') throw new Error(`refused for /issues/${n}/labels`);\n"
},
'the verb named in a test name next to a labels path': {
'scripts/w.mjs': "const plan = { path: '/issues/10698/labels' };\ncheck('the retired whole-set PUT destroys the label', plan);\n"
'scripts/check-nul-bytes.mjs': "const plan = { path: '/issues/10698/labels' };\ncheck('the retired whole-set PUT destroys the label', plan);\n"
},
'an unrelated action pin': {
'.github/workflows/w.yml': 'jobs:\n j:\n steps:\n - uses: actions/checkout@v7\n - uses: actions/stale@v11.0.0\n'
'.github/workflows/ci.yml': 'jobs:\n j:\n steps:\n - uses: actions/checkout@v7\n - uses: actions/stale@v11.0.0\n'
},
'a PUT to a different endpoint entirely': {
'scripts/w.mjs': "await gh('PUT', `/repos/${repo}/actions/variables/${name}`);\n"
'scripts/check-nul-bytes.mjs': "await gh('PUT', `/repos/${repo}/actions/variables/${name}`);\n"
},
'a label READ, no write': {
'.github/workflows/w.yml': 'jobs:\n j:\n steps:\n - run: gh api "repos/$R/issues/$N/labels" --jq ".[].name"\n'
'.github/workflows/ci.yml': 'jobs:\n j:\n steps:\n - run: gh api "repos/$R/issues/$N/labels" --jq ".[].name"\n'
},
'steps.labels output references': {
'.github/workflows/w.yml': "jobs:\n j:\n steps:\n - if: steps.labels.outputs.skip != 'true'\n run: true\n"
'.github/workflows/ci.yml': "jobs:\n j:\n steps:\n - if: steps.labels.outputs.skip != 'true'\n run: true\n"
},
'the method slot WINDOW_LINES lines from the path (a STATED miss)': {
'scripts/w.mjs': `const url = '/issues/1/labels';\n${'// filler\n'.repeat(WINDOW_LINES - 1)}await go({ method: 'PUT', url });\n`
'scripts/check-nul-bytes.mjs': `const url = '/issues/1/labels';\n${'// filler\n'.repeat(WINDOW_LINES - 1)}await go({ method: 'PUT', url });\n`
}
};

Expand DownExpand Up@@ -899,9 +917,9 @@ export function selfTest() {

// Refusal 3 -- an allowlist entry with no stated reason. Assertion 3.
withTree(RED_CASES['gh api -X PUT'], (dir) => {
const noReason = [{ path: '.github/workflows/w.yml', rule: 'endpoint', reason: 'too short' }];
const noReason = [{ path: '.github/workflows/ci.yml', rule: 'endpoint', reason: 'too short' }];
expect('REFUSE allowlist entry without a reason', run(dir, { allowlist: noReason }, silent), EXIT_REFUSED);
const noRule = [{ path: '.github/workflows/w.yml', rule: 'whatever', reason: 'a'.repeat(MIN_REASON_LENGTH) }];
const noRule = [{ path: '.github/workflows/ci.yml', rule: 'whatever', reason: 'a'.repeat(MIN_REASON_LENGTH) }];
expect('REFUSE allowlist entry with no valid rule', run(dir, { allowlist: noRule }, silent), EXIT_REFUSED);
});

Expand All@@ -916,7 +934,7 @@ export function selfTest() {
withTree(RED_CASES['gh api -X PUT'], (dir) => {
const reasoned = [
{
path: '.github/workflows/w.yml',
path: '.github/workflows/ci.yml',
rule: /** @type {'endpoint'} */ ('endpoint'),
reason: 'fixture: a deliberate exception recorded with a real sentence explaining itself.'
}
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 45 additions & 27 deletions scripts/check-whole-set-label-write.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -762,85 +762,103 @@ function baseFiles() {
};
}

// RED_CASES/GREEN_CASES below reuse three REAL tracked paths as their sandbox
// keys (`scripts/check-nul-bytes.mjs`, `scripts/build-console.sh`,
// `.github/workflows/ci.yml`) rather than invented names (`scripts/w.mjs`,
// `scripts/writer.sh`, `.github/workflows/w.yml`, pre-#14695). The fixture's
// own content is written into an isolated temp directory (`writeTree`) either
// way, so which real path stands in for "a script" or "a workflow" makes no
// difference to what any RED/GREEN case here asserts — but the KEY is a
// string literal in THIS module's own source, and `RED_CASES`/`GREEN_CASES`
// are top-level exports, not bodies inside a `selfTest()`-shaped function, so
// `maskSelfTests` never hides them from `extractWatchHints`: every key here
// already entered this gate's own declared-population hint set. An invented
// name that exists in no tracked tree is therefore a DEAD lead — exactly the
// shape `check:declared-population-live` exists to refuse — and it read as
// live only because that gate's liveness bar is per-FAMILY (at least one hint
// live) rather than per-hint; a real path clears the same bar honestly at
// full precision instead of by accident (#14695). The two paths chosen for
// the `.mjs`/`.sh` cases are deliberately UNRELATED scripts, picked only to be
// tracked and short — not files this gate has any real interest in reading.
/** Every spelling that MUST be refused. */
export const RED_CASES = {
'curl -X PUT, one line': {
'scripts/writer.sh': 'curl -X PUT -H "auth" "https://api.github.com/repos/o/r/issues/1/labels" -d "{}"\n'
'scripts/build-console.sh': 'curl -X PUT -H "auth" "https://api.github.com/repos/o/r/issues/1/labels" -d "{}"\n'
},
'curl -X PUT, backslash-continued onto the path line': {
'scripts/writer.sh': 'curl -X PUT \\\n -H "auth" \\\n "https://api.github.com/repos/o/r/issues/1/labels"\n'
'scripts/build-console.sh': 'curl -X PUT \\\n -H "auth" \\\n "https://api.github.com/repos/o/r/issues/1/labels"\n'
},
'gh api -X PUT': {
'.github/workflows/w.yml': 'jobs:\n j:\n steps:\n - run: gh api -X PUT "repos/$R/issues/$N/labels" -f labels[]=a\n'
'.github/workflows/ci.yml': 'jobs:\n j:\n steps:\n - run: gh api -X PUT "repos/$R/issues/$N/labels" -f labels[]=a\n'
},
'gh api --method PUT': {
'.github/workflows/w.yml': 'jobs:\n j:\n steps:\n - run: gh api --method PUT repos/o/r/issues/1/labels\n'
'.github/workflows/ci.yml': 'jobs:\n j:\n steps:\n - run: gh api --method PUT repos/o/r/issues/1/labels\n'
},
'github-script issues.setLabels': {
'.github/workflows/w.yml':
'.github/workflows/ci.yml':
'jobs:\n j:\n steps:\n - uses: actions/github-script@v9\n with:\n script: |\n' +
' await github.rest.issues.setLabels({ owner, repo, issue_number: 1, labels });\n'
},
'octokit.request route string': {
'scripts/w.mjs': "await octokit.request('PUT /repos/{owner}/{repo}/issues/{issue_number}/labels', { labels });\n"
'scripts/check-nul-bytes.mjs': "await octokit.request('PUT /repos/{owner}/{repo}/issues/{issue_number}/labels', { labels });\n"
},
'fetch with a multi-line options object': {
'scripts/w.mjs': 'await fetch(`${api}/repos/${repo}/issues/${n}/labels`, {\n headers,\n method: "PUT",\n body\n});\n'
'scripts/check-nul-bytes.mjs': 'await fetch(`${api}/repos/${repo}/issues/${n}/labels`, {\n headers,\n method: "PUT",\n body\n});\n'
},
'a bare .put( onto the endpoint': {
'scripts/w.mjs': "await client.put(`/issues/${n}/labels`, { labels });\n"
'scripts/check-nul-bytes.mjs': "await client.put(`/issues/${n}/labels`, { labels });\n"
},
'uses: actions/labeler at the version #10703 read': {
'.github/workflows/w.yml': 'jobs:\n j:\n steps:\n - uses: actions/labeler@v7.0.0\n'
'.github/workflows/ci.yml': 'jobs:\n j:\n steps:\n - uses: actions/labeler@v7.0.0\n'
},
'uses: actions/labeler at ANY other version': {
'.github/workflows/w.yml': 'jobs:\n j:\n steps:\n - uses: actions/labeler@a1b2c3d4\n'
'.github/workflows/ci.yml': 'jobs:\n j:\n steps:\n - uses: actions/labeler@a1b2c3d4\n'
},
'uses: codelytv/pr-size-labeler': {
'.github/workflows/w.yml': 'jobs:\n j:\n steps:\n - uses: codelytv/pr-size-labeler@v1.10.4\n'
'.github/workflows/ci.yml': 'jobs:\n j:\n steps:\n - uses: codelytv/pr-size-labeler@v1.10.4\n'
},
'the method slot WINDOW_LINES-1 lines from the path': {
'scripts/w.mjs': `const url = '/issues/1/labels';\n${'// filler\n'.repeat(WINDOW_LINES - 2)}await go({ method: 'PUT', url });\n`
'scripts/check-nul-bytes.mjs': `const url = '/issues/1/labels';\n${'// filler\n'.repeat(WINDOW_LINES - 2)}await go({ method: 'PUT', url });\n`
}
};

/** Forms that MUST stay clean. Every one is correct as written. */
export const GREEN_CASES = {
'the additive POST': {
'scripts/w.mjs': "await gh('POST', `/issues/${n}/labels`, { labels: ['size/l'] });\n"
'scripts/check-nul-bytes.mjs': "await gh('POST', `/issues/${n}/labels`, { labels: ['size/l'] });\n"
},
'the targeted DELETE': {
'scripts/w.mjs': "await gh('DELETE', `/issues/${n}/labels/${encodeURIComponent(name)}`);\n"
'scripts/check-nul-bytes.mjs': "await gh('DELETE', `/issues/${n}/labels/${encodeURIComponent(name)}`);\n"
},
'the ban documented in a YAML comment': {
'.github/workflows/w.yml': '# never `curl -X PUT .../issues/1/labels`, and never issues.setLabels\njobs:\n j:\n steps:\n - run: true\n'
'.github/workflows/ci.yml': '# never `curl -X PUT .../issues/1/labels`, and never issues.setLabels\njobs:\n j:\n steps:\n - run: true\n'
},
'the ban documented in a JS block comment': {
'scripts/w.mjs': '/**\n * `PUT /issues/{n}/labels` and `issues.setLabels` are both banned.\n * Not `curl -X PUT .../issues/1/labels` either.\n */\nexport const ok = 1;\n'
'scripts/check-nul-bytes.mjs': '/**\n * `PUT /issues/{n}/labels` and `issues.setLabels` are both banned.\n * Not `curl -X PUT .../issues/1/labels` either.\n */\nexport const ok = 1;\n'
},
'the ban documented in a JS line comment': {
'scripts/w.mjs': "// await octokit.request('PUT /repos/o/r/issues/1/labels') -- BANNED\nexport const ok = 1;\n"
'scripts/check-nul-bytes.mjs': "// await octokit.request('PUT /repos/o/r/issues/1/labels') -- BANNED\nexport const ok = 1;\n"
},
'a comparison REFUSING the verb': {
'scripts/w.mjs': "if (step.method === 'PUT') throw new Error(`refused for /issues/${n}/labels`);\n"
'scripts/check-nul-bytes.mjs': "if (step.method === 'PUT') throw new Error(`refused for /issues/${n}/labels`);\n"
},
'the verb named in a test name next to a labels path': {
'scripts/w.mjs': "const plan = { path: '/issues/10698/labels' };\ncheck('the retired whole-set PUT destroys the label', plan);\n"
'scripts/check-nul-bytes.mjs': "const plan = { path: '/issues/10698/labels' };\ncheck('the retired whole-set PUT destroys the label', plan);\n"
},
'an unrelated action pin': {
'.github/workflows/w.yml': 'jobs:\n j:\n steps:\n - uses: actions/checkout@v7\n - uses: actions/stale@v11.0.0\n'
'.github/workflows/ci.yml': 'jobs:\n j:\n steps:\n - uses: actions/checkout@v7\n - uses: actions/stale@v11.0.0\n'
},
'a PUT to a different endpoint entirely': {
'scripts/w.mjs': "await gh('PUT', `/repos/${repo}/actions/variables/${name}`);\n"
'scripts/check-nul-bytes.mjs': "await gh('PUT', `/repos/${repo}/actions/variables/${name}`);\n"
},
'a label READ, no write': {
'.github/workflows/w.yml': 'jobs:\n j:\n steps:\n - run: gh api "repos/$R/issues/$N/labels" --jq ".[].name"\n'
'.github/workflows/ci.yml': 'jobs:\n j:\n steps:\n - run: gh api "repos/$R/issues/$N/labels" --jq ".[].name"\n'
},
'steps.labels output references': {
'.github/workflows/w.yml': "jobs:\n j:\n steps:\n - if: steps.labels.outputs.skip != 'true'\n run: true\n"
'.github/workflows/ci.yml': "jobs:\n j:\n steps:\n - if: steps.labels.outputs.skip != 'true'\n run: true\n"
},
'the method slot WINDOW_LINES lines from the path (a STATED miss)': {
'scripts/w.mjs': `const url = '/issues/1/labels';\n${'// filler\n'.repeat(WINDOW_LINES - 1)}await go({ method: 'PUT', url });\n`
'scripts/check-nul-bytes.mjs': `const url = '/issues/1/labels';\n${'// filler\n'.repeat(WINDOW_LINES - 1)}await go({ method: 'PUT', url });\n`
}
};

Expand DownExpand Up@@ -899,9 +917,9 @@ export function selfTest() {

// Refusal 3 -- an allowlist entry with no stated reason. Assertion 3.
withTree(RED_CASES['gh api -X PUT'], (dir) => {
const noReason = [{ path: '.github/workflows/w.yml', rule: 'endpoint', reason: 'too short' }];
const noReason = [{ path: '.github/workflows/ci.yml', rule: 'endpoint', reason: 'too short' }];
expect('REFUSE allowlist entry without a reason', run(dir, { allowlist: noReason }, silent), EXIT_REFUSED);
const noRule = [{ path: '.github/workflows/w.yml', rule: 'whatever', reason: 'a'.repeat(MIN_REASON_LENGTH) }];
const noRule = [{ path: '.github/workflows/ci.yml', rule: 'whatever', reason: 'a'.repeat(MIN_REASON_LENGTH) }];
expect('REFUSE allowlist entry with no valid rule', run(dir, { allowlist: noRule }, silent), EXIT_REFUSED);
});

Expand All@@ -916,7 +934,7 @@ export function selfTest() {
withTree(RED_CASES['gh api -X PUT'], (dir) => {
const reasoned = [
{
path: '.github/workflows/w.yml',
path: '.github/workflows/ci.yml',
rule: /** @type {'endpoint'} */ ('endpoint'),
reason: 'fixture: a deliberate exception recorded with a real sentence explaining itself.'
}
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 45 additions & 27 deletions scripts/check-whole-set-label-write.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -762,85 +762,103 @@ function baseFiles() {
};
}

// RED_CASES/GREEN_CASES below reuse three REAL tracked paths as their sandbox
// keys (`scripts/check-nul-bytes.mjs`, `scripts/build-console.sh`,
// `.github/workflows/ci.yml`) rather than invented names (`scripts/w.mjs`,
// `scripts/writer.sh`, `.github/workflows/w.yml`, pre-#14695). The fixture's
// own content is written into an isolated temp directory (`writeTree`) either
// way, so which real path stands in for "a script" or "a workflow" makes no
// difference to what any RED/GREEN case here asserts — but the KEY is a
// string literal in THIS module's own source, and `RED_CASES`/`GREEN_CASES`
// are top-level exports, not bodies inside a `selfTest()`-shaped function, so
// `maskSelfTests` never hides them from `extractWatchHints`: every key here
// already entered this gate's own declared-population hint set. An invented
// name that exists in no tracked tree is therefore a DEAD lead — exactly the
// shape `check:declared-population-live` exists to refuse — and it read as
// live only because that gate's liveness bar is per-FAMILY (at least one hint
// live) rather than per-hint; a real path clears the same bar honestly at
// full precision instead of by accident (#14695). The two paths chosen for
// the `.mjs`/`.sh` cases are deliberately UNRELATED scripts, picked only to be
// tracked and short — not files this gate has any real interest in reading.
/** Every spelling that MUST be refused. */
export const RED_CASES = {
'curl -X PUT, one line': {
'scripts/writer.sh': 'curl -X PUT -H "auth" "https://api.github.com/repos/o/r/issues/1/labels" -d "{}"\n'
'scripts/build-console.sh': 'curl -X PUT -H "auth" "https://api.github.com/repos/o/r/issues/1/labels" -d "{}"\n'
},
'curl -X PUT, backslash-continued onto the path line': {
'scripts/writer.sh': 'curl -X PUT \\\n -H "auth" \\\n "https://api.github.com/repos/o/r/issues/1/labels"\n'
'scripts/build-console.sh': 'curl -X PUT \\\n -H "auth" \\\n "https://api.github.com/repos/o/r/issues/1/labels"\n'
},
'gh api -X PUT': {
'.github/workflows/w.yml': 'jobs:\n j:\n steps:\n - run: gh api -X PUT "repos/$R/issues/$N/labels" -f labels[]=a\n'
'.github/workflows/ci.yml': 'jobs:\n j:\n steps:\n - run: gh api -X PUT "repos/$R/issues/$N/labels" -f labels[]=a\n'
},
'gh api --method PUT': {
'.github/workflows/w.yml': 'jobs:\n j:\n steps:\n - run: gh api --method PUT repos/o/r/issues/1/labels\n'
'.github/workflows/ci.yml': 'jobs:\n j:\n steps:\n - run: gh api --method PUT repos/o/r/issues/1/labels\n'
},
'github-script issues.setLabels': {
'.github/workflows/w.yml':
'.github/workflows/ci.yml':
'jobs:\n j:\n steps:\n - uses: actions/github-script@v9\n with:\n script: |\n' +
' await github.rest.issues.setLabels({ owner, repo, issue_number: 1, labels });\n'
},
'octokit.request route string': {
'scripts/w.mjs': "await octokit.request('PUT /repos/{owner}/{repo}/issues/{issue_number}/labels', { labels });\n"
'scripts/check-nul-bytes.mjs': "await octokit.request('PUT /repos/{owner}/{repo}/issues/{issue_number}/labels', { labels });\n"
},
'fetch with a multi-line options object': {
'scripts/w.mjs': 'await fetch(`${api}/repos/${repo}/issues/${n}/labels`, {\n headers,\n method: "PUT",\n body\n});\n'
'scripts/check-nul-bytes.mjs': 'await fetch(`${api}/repos/${repo}/issues/${n}/labels`, {\n headers,\n method: "PUT",\n body\n});\n'
},
'a bare .put( onto the endpoint': {
'scripts/w.mjs': "await client.put(`/issues/${n}/labels`, { labels });\n"
'scripts/check-nul-bytes.mjs': "await client.put(`/issues/${n}/labels`, { labels });\n"
},
'uses: actions/labeler at the version #10703 read': {
'.github/workflows/w.yml': 'jobs:\n j:\n steps:\n - uses: actions/labeler@v7.0.0\n'
'.github/workflows/ci.yml': 'jobs:\n j:\n steps:\n - uses: actions/labeler@v7.0.0\n'
},
'uses: actions/labeler at ANY other version': {
'.github/workflows/w.yml': 'jobs:\n j:\n steps:\n - uses: actions/labeler@a1b2c3d4\n'
'.github/workflows/ci.yml': 'jobs:\n j:\n steps:\n - uses: actions/labeler@a1b2c3d4\n'
},
'uses: codelytv/pr-size-labeler': {
'.github/workflows/w.yml': 'jobs:\n j:\n steps:\n - uses: codelytv/pr-size-labeler@v1.10.4\n'
'.github/workflows/ci.yml': 'jobs:\n j:\n steps:\n - uses: codelytv/pr-size-labeler@v1.10.4\n'
},
'the method slot WINDOW_LINES-1 lines from the path': {
'scripts/w.mjs': `const url = '/issues/1/labels';\n${'// filler\n'.repeat(WINDOW_LINES - 2)}await go({ method: 'PUT', url });\n`
'scripts/check-nul-bytes.mjs': `const url = '/issues/1/labels';\n${'// filler\n'.repeat(WINDOW_LINES - 2)}await go({ method: 'PUT', url });\n`
}
};

/** Forms that MUST stay clean. Every one is correct as written. */
export const GREEN_CASES = {
'the additive POST': {
'scripts/w.mjs': "await gh('POST', `/issues/${n}/labels`, { labels: ['size/l'] });\n"
'scripts/check-nul-bytes.mjs': "await gh('POST', `/issues/${n}/labels`, { labels: ['size/l'] });\n"
},
'the targeted DELETE': {
'scripts/w.mjs': "await gh('DELETE', `/issues/${n}/labels/${encodeURIComponent(name)}`);\n"
'scripts/check-nul-bytes.mjs': "await gh('DELETE', `/issues/${n}/labels/${encodeURIComponent(name)}`);\n"
},
'the ban documented in a YAML comment': {
'.github/workflows/w.yml': '# never `curl -X PUT .../issues/1/labels`, and never issues.setLabels\njobs:\n j:\n steps:\n - run: true\n'
'.github/workflows/ci.yml': '# never `curl -X PUT .../issues/1/labels`, and never issues.setLabels\njobs:\n j:\n steps:\n - run: true\n'
},
'the ban documented in a JS block comment': {
'scripts/w.mjs': '/**\n * `PUT /issues/{n}/labels` and `issues.setLabels` are both banned.\n * Not `curl -X PUT .../issues/1/labels` either.\n */\nexport const ok = 1;\n'
'scripts/check-nul-bytes.mjs': '/**\n * `PUT /issues/{n}/labels` and `issues.setLabels` are both banned.\n * Not `curl -X PUT .../issues/1/labels` either.\n */\nexport const ok = 1;\n'
},
'the ban documented in a JS line comment': {
'scripts/w.mjs': "// await octokit.request('PUT /repos/o/r/issues/1/labels') -- BANNED\nexport const ok = 1;\n"
'scripts/check-nul-bytes.mjs': "// await octokit.request('PUT /repos/o/r/issues/1/labels') -- BANNED\nexport const ok = 1;\n"
},
'a comparison REFUSING the verb': {
'scripts/w.mjs': "if (step.method === 'PUT') throw new Error(`refused for /issues/${n}/labels`);\n"
'scripts/check-nul-bytes.mjs': "if (step.method === 'PUT') throw new Error(`refused for /issues/${n}/labels`);\n"
},
'the verb named in a test name next to a labels path': {
'scripts/w.mjs': "const plan = { path: '/issues/10698/labels' };\ncheck('the retired whole-set PUT destroys the label', plan);\n"
'scripts/check-nul-bytes.mjs': "const plan = { path: '/issues/10698/labels' };\ncheck('the retired whole-set PUT destroys the label', plan);\n"
},
'an unrelated action pin': {
'.github/workflows/w.yml': 'jobs:\n j:\n steps:\n - uses: actions/checkout@v7\n - uses: actions/stale@v11.0.0\n'
'.github/workflows/ci.yml': 'jobs:\n j:\n steps:\n - uses: actions/checkout@v7\n - uses: actions/stale@v11.0.0\n'
},
'a PUT to a different endpoint entirely': {
'scripts/w.mjs': "await gh('PUT', `/repos/${repo}/actions/variables/${name}`);\n"
'scripts/check-nul-bytes.mjs': "await gh('PUT', `/repos/${repo}/actions/variables/${name}`);\n"
},
'a label READ, no write': {
'.github/workflows/w.yml': 'jobs:\n j:\n steps:\n - run: gh api "repos/$R/issues/$N/labels" --jq ".[].name"\n'
'.github/workflows/ci.yml': 'jobs:\n j:\n steps:\n - run: gh api "repos/$R/issues/$N/labels" --jq ".[].name"\n'
},
'steps.labels output references': {
'.github/workflows/w.yml': "jobs:\n j:\n steps:\n - if: steps.labels.outputs.skip != 'true'\n run: true\n"
'.github/workflows/ci.yml': "jobs:\n j:\n steps:\n - if: steps.labels.outputs.skip != 'true'\n run: true\n"
},
'the method slot WINDOW_LINES lines from the path (a STATED miss)': {
'scripts/w.mjs': `const url = '/issues/1/labels';\n${'// filler\n'.repeat(WINDOW_LINES - 1)}await go({ method: 'PUT', url });\n`
'scripts/check-nul-bytes.mjs': `const url = '/issues/1/labels';\n${'// filler\n'.repeat(WINDOW_LINES - 1)}await go({ method: 'PUT', url });\n`
}
};

Expand DownExpand Up@@ -899,9 +917,9 @@ export function selfTest() {

// Refusal 3 -- an allowlist entry with no stated reason. Assertion 3.
withTree(RED_CASES['gh api -X PUT'], (dir) => {
const noReason = [{ path: '.github/workflows/w.yml', rule: 'endpoint', reason: 'too short' }];
const noReason = [{ path: '.github/workflows/ci.yml', rule: 'endpoint', reason: 'too short' }];
expect('REFUSE allowlist entry without a reason', run(dir, { allowlist: noReason }, silent), EXIT_REFUSED);
const noRule = [{ path: '.github/workflows/w.yml', rule: 'whatever', reason: 'a'.repeat(MIN_REASON_LENGTH) }];
const noRule = [{ path: '.github/workflows/ci.yml', rule: 'whatever', reason: 'a'.repeat(MIN_REASON_LENGTH) }];
expect('REFUSE allowlist entry with no valid rule', run(dir, { allowlist: noRule }, silent), EXIT_REFUSED);
});

Expand All@@ -916,7 +934,7 @@ export function selfTest() {
withTree(RED_CASES['gh api -X PUT'], (dir) => {
const reasoned = [
{
path: '.github/workflows/w.yml',
path: '.github/workflows/ci.yml',
rule: /** @type {'endpoint'} */ ('endpoint'),
reason: 'fixture: a deliberate exception recorded with a real sentence explaining itself.'
}
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 45 additions & 27 deletions scripts/check-whole-set-label-write.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -762,85 +762,103 @@ function baseFiles() {
};
}

// RED_CASES/GREEN_CASES below reuse three REAL tracked paths as their sandbox
// keys (`scripts/check-nul-bytes.mjs`, `scripts/build-console.sh`,
// `.github/workflows/ci.yml`) rather than invented names (`scripts/w.mjs`,
// `scripts/writer.sh`, `.github/workflows/w.yml`, pre-#14695). The fixture's
// own content is written into an isolated temp directory (`writeTree`) either
// way, so which real path stands in for "a script" or "a workflow" makes no
// difference to what any RED/GREEN case here asserts — but the KEY is a
// string literal in THIS module's own source, and `RED_CASES`/`GREEN_CASES`
// are top-level exports, not bodies inside a `selfTest()`-shaped function, so
// `maskSelfTests` never hides them from `extractWatchHints`: every key here
// already entered this gate's own declared-population hint set. An invented
// name that exists in no tracked tree is therefore a DEAD lead — exactly the
// shape `check:declared-population-live` exists to refuse — and it read as
// live only because that gate's liveness bar is per-FAMILY (at least one hint
// live) rather than per-hint; a real path clears the same bar honestly at
// full precision instead of by accident (#14695). The two paths chosen for
// the `.mjs`/`.sh` cases are deliberately UNRELATED scripts, picked only to be
// tracked and short — not files this gate has any real interest in reading.
/** Every spelling that MUST be refused. */
export const RED_CASES = {
'curl -X PUT, one line': {
'scripts/writer.sh': 'curl -X PUT -H "auth" "https://api.github.com/repos/o/r/issues/1/labels" -d "{}"\n'
'scripts/build-console.sh': 'curl -X PUT -H "auth" "https://api.github.com/repos/o/r/issues/1/labels" -d "{}"\n'
},
'curl -X PUT, backslash-continued onto the path line': {
'scripts/writer.sh': 'curl -X PUT \\\n -H "auth" \\\n "https://api.github.com/repos/o/r/issues/1/labels"\n'
'scripts/build-console.sh': 'curl -X PUT \\\n -H "auth" \\\n "https://api.github.com/repos/o/r/issues/1/labels"\n'
},
'gh api -X PUT': {
'.github/workflows/w.yml': 'jobs:\n j:\n steps:\n - run: gh api -X PUT "repos/$R/issues/$N/labels" -f labels[]=a\n'
'.github/workflows/ci.yml': 'jobs:\n j:\n steps:\n - run: gh api -X PUT "repos/$R/issues/$N/labels" -f labels[]=a\n'
},
'gh api --method PUT': {
'.github/workflows/w.yml': 'jobs:\n j:\n steps:\n - run: gh api --method PUT repos/o/r/issues/1/labels\n'
'.github/workflows/ci.yml': 'jobs:\n j:\n steps:\n - run: gh api --method PUT repos/o/r/issues/1/labels\n'
},
'github-script issues.setLabels': {
'.github/workflows/w.yml':
'.github/workflows/ci.yml':
'jobs:\n j:\n steps:\n - uses: actions/github-script@v9\n with:\n script: |\n' +
' await github.rest.issues.setLabels({ owner, repo, issue_number: 1, labels });\n'
},
'octokit.request route string': {
'scripts/w.mjs': "await octokit.request('PUT /repos/{owner}/{repo}/issues/{issue_number}/labels', { labels });\n"
'scripts/check-nul-bytes.mjs': "await octokit.request('PUT /repos/{owner}/{repo}/issues/{issue_number}/labels', { labels });\n"
},
'fetch with a multi-line options object': {
'scripts/w.mjs': 'await fetch(`${api}/repos/${repo}/issues/${n}/labels`, {\n headers,\n method: "PUT",\n body\n});\n'
'scripts/check-nul-bytes.mjs': 'await fetch(`${api}/repos/${repo}/issues/${n}/labels`, {\n headers,\n method: "PUT",\n body\n});\n'
},
'a bare .put( onto the endpoint': {
'scripts/w.mjs': "await client.put(`/issues/${n}/labels`, { labels });\n"
'scripts/check-nul-bytes.mjs': "await client.put(`/issues/${n}/labels`, { labels });\n"
},
'uses: actions/labeler at the version #10703 read': {
'.github/workflows/w.yml': 'jobs:\n j:\n steps:\n - uses: actions/labeler@v7.0.0\n'
'.github/workflows/ci.yml': 'jobs:\n j:\n steps:\n - uses: actions/labeler@v7.0.0\n'
},
'uses: actions/labeler at ANY other version': {
'.github/workflows/w.yml': 'jobs:\n j:\n steps:\n - uses: actions/labeler@a1b2c3d4\n'
'.github/workflows/ci.yml': 'jobs:\n j:\n steps:\n - uses: actions/labeler@a1b2c3d4\n'
},
'uses: codelytv/pr-size-labeler': {
'.github/workflows/w.yml': 'jobs:\n j:\n steps:\n - uses: codelytv/pr-size-labeler@v1.10.4\n'
'.github/workflows/ci.yml': 'jobs:\n j:\n steps:\n - uses: codelytv/pr-size-labeler@v1.10.4\n'
},
'the method slot WINDOW_LINES-1 lines from the path': {
'scripts/w.mjs': `const url = '/issues/1/labels';\n${'// filler\n'.repeat(WINDOW_LINES - 2)}await go({ method: 'PUT', url });\n`
'scripts/check-nul-bytes.mjs': `const url = '/issues/1/labels';\n${'// filler\n'.repeat(WINDOW_LINES - 2)}await go({ method: 'PUT', url });\n`
}
};

/** Forms that MUST stay clean. Every one is correct as written. */
export const GREEN_CASES = {
'the additive POST': {
'scripts/w.mjs': "await gh('POST', `/issues/${n}/labels`, { labels: ['size/l'] });\n"
'scripts/check-nul-bytes.mjs': "await gh('POST', `/issues/${n}/labels`, { labels: ['size/l'] });\n"
},
'the targeted DELETE': {
'scripts/w.mjs': "await gh('DELETE', `/issues/${n}/labels/${encodeURIComponent(name)}`);\n"
'scripts/check-nul-bytes.mjs': "await gh('DELETE', `/issues/${n}/labels/${encodeURIComponent(name)}`);\n"
},
'the ban documented in a YAML comment': {
'.github/workflows/w.yml': '# never `curl -X PUT .../issues/1/labels`, and never issues.setLabels\njobs:\n j:\n steps:\n - run: true\n'
'.github/workflows/ci.yml': '# never `curl -X PUT .../issues/1/labels`, and never issues.setLabels\njobs:\n j:\n steps:\n - run: true\n'
},
'the ban documented in a JS block comment': {
'scripts/w.mjs': '/**\n * `PUT /issues/{n}/labels` and `issues.setLabels` are both banned.\n * Not `curl -X PUT .../issues/1/labels` either.\n */\nexport const ok = 1;\n'
'scripts/check-nul-bytes.mjs': '/**\n * `PUT /issues/{n}/labels` and `issues.setLabels` are both banned.\n * Not `curl -X PUT .../issues/1/labels` either.\n */\nexport const ok = 1;\n'
},
'the ban documented in a JS line comment': {
'scripts/w.mjs': "// await octokit.request('PUT /repos/o/r/issues/1/labels') -- BANNED\nexport const ok = 1;\n"
'scripts/check-nul-bytes.mjs': "// await octokit.request('PUT /repos/o/r/issues/1/labels') -- BANNED\nexport const ok = 1;\n"
},
'a comparison REFUSING the verb': {
'scripts/w.mjs': "if (step.method === 'PUT') throw new Error(`refused for /issues/${n}/labels`);\n"
'scripts/check-nul-bytes.mjs': "if (step.method === 'PUT') throw new Error(`refused for /issues/${n}/labels`);\n"
},
'the verb named in a test name next to a labels path': {
'scripts/w.mjs': "const plan = { path: '/issues/10698/labels' };\ncheck('the retired whole-set PUT destroys the label', plan);\n"
'scripts/check-nul-bytes.mjs': "const plan = { path: '/issues/10698/labels' };\ncheck('the retired whole-set PUT destroys the label', plan);\n"
},
'an unrelated action pin': {
'.github/workflows/w.yml': 'jobs:\n j:\n steps:\n - uses: actions/checkout@v7\n - uses: actions/stale@v11.0.0\n'
'.github/workflows/ci.yml': 'jobs:\n j:\n steps:\n - uses: actions/checkout@v7\n - uses: actions/stale@v11.0.0\n'
},
'a PUT to a different endpoint entirely': {
'scripts/w.mjs': "await gh('PUT', `/repos/${repo}/actions/variables/${name}`);\n"
'scripts/check-nul-bytes.mjs': "await gh('PUT', `/repos/${repo}/actions/variables/${name}`);\n"
},
'a label READ, no write': {
'.github/workflows/w.yml': 'jobs:\n j:\n steps:\n - run: gh api "repos/$R/issues/$N/labels" --jq ".[].name"\n'
'.github/workflows/ci.yml': 'jobs:\n j:\n steps:\n - run: gh api "repos/$R/issues/$N/labels" --jq ".[].name"\n'
},
'steps.labels output references': {
'.github/workflows/w.yml': "jobs:\n j:\n steps:\n - if: steps.labels.outputs.skip != 'true'\n run: true\n"
'.github/workflows/ci.yml': "jobs:\n j:\n steps:\n - if: steps.labels.outputs.skip != 'true'\n run: true\n"
},
'the method slot WINDOW_LINES lines from the path (a STATED miss)': {
'scripts/w.mjs': `const url = '/issues/1/labels';\n${'// filler\n'.repeat(WINDOW_LINES - 1)}await go({ method: 'PUT', url });\n`
'scripts/check-nul-bytes.mjs': `const url = '/issues/1/labels';\n${'// filler\n'.repeat(WINDOW_LINES - 1)}await go({ method: 'PUT', url });\n`
}
};

Expand DownExpand Up@@ -899,9 +917,9 @@ export function selfTest() {

// Refusal 3 -- an allowlist entry with no stated reason. Assertion 3.
withTree(RED_CASES['gh api -X PUT'], (dir) => {
const noReason = [{ path: '.github/workflows/w.yml', rule: 'endpoint', reason: 'too short' }];
const noReason = [{ path: '.github/workflows/ci.yml', rule: 'endpoint', reason: 'too short' }];
expect('REFUSE allowlist entry without a reason', run(dir, { allowlist: noReason }, silent), EXIT_REFUSED);
const noRule = [{ path: '.github/workflows/w.yml', rule: 'whatever', reason: 'a'.repeat(MIN_REASON_LENGTH) }];
const noRule = [{ path: '.github/workflows/ci.yml', rule: 'whatever', reason: 'a'.repeat(MIN_REASON_LENGTH) }];
expect('REFUSE allowlist entry with no valid rule', run(dir, { allowlist: noRule }, silent), EXIT_REFUSED);
});

Expand All@@ -916,7 +934,7 @@ export function selfTest() {
withTree(RED_CASES['gh api -X PUT'], (dir) => {
const reasoned = [
{
path: '.github/workflows/w.yml',
path: '.github/workflows/ci.yml',
rule: /** @type {'endpoint'} */ ('endpoint'),
reason: 'fixture: a deliberate exception recorded with a real sentence explaining itself.'
}
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 45 additions & 27 deletions scripts/check-whole-set-label-write.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -762,85 +762,103 @@ function baseFiles() {
};
}

// RED_CASES/GREEN_CASES below reuse three REAL tracked paths as their sandbox
// keys (`scripts/check-nul-bytes.mjs`, `scripts/build-console.sh`,
// `.github/workflows/ci.yml`) rather than invented names (`scripts/w.mjs`,
// `scripts/writer.sh`, `.github/workflows/w.yml`, pre-#14695). The fixture's
// own content is written into an isolated temp directory (`writeTree`) either
// way, so which real path stands in for "a script" or "a workflow" makes no
// difference to what any RED/GREEN case here asserts — but the KEY is a
// string literal in THIS module's own source, and `RED_CASES`/`GREEN_CASES`
// are top-level exports, not bodies inside a `selfTest()`-shaped function, so
// `maskSelfTests` never hides them from `extractWatchHints`: every key here
// already entered this gate's own declared-population hint set. An invented
// name that exists in no tracked tree is therefore a DEAD lead — exactly the
// shape `check:declared-population-live` exists to refuse — and it read as
// live only because that gate's liveness bar is per-FAMILY (at least one hint
// live) rather than per-hint; a real path clears the same bar honestly at
// full precision instead of by accident (#14695). The two paths chosen for
// the `.mjs`/`.sh` cases are deliberately UNRELATED scripts, picked only to be
// tracked and short — not files this gate has any real interest in reading.
/** Every spelling that MUST be refused. */
export const RED_CASES = {
'curl -X PUT, one line': {
'scripts/writer.sh': 'curl -X PUT -H "auth" "https://api.github.com/repos/o/r/issues/1/labels" -d "{}"\n'
'scripts/build-console.sh': 'curl -X PUT -H "auth" "https://api.github.com/repos/o/r/issues/1/labels" -d "{}"\n'
},
'curl -X PUT, backslash-continued onto the path line': {
'scripts/writer.sh': 'curl -X PUT \\\n -H "auth" \\\n "https://api.github.com/repos/o/r/issues/1/labels"\n'
'scripts/build-console.sh': 'curl -X PUT \\\n -H "auth" \\\n "https://api.github.com/repos/o/r/issues/1/labels"\n'
},
'gh api -X PUT': {
'.github/workflows/w.yml': 'jobs:\n j:\n steps:\n - run: gh api -X PUT "repos/$R/issues/$N/labels" -f labels[]=a\n'
'.github/workflows/ci.yml': 'jobs:\n j:\n steps:\n - run: gh api -X PUT "repos/$R/issues/$N/labels" -f labels[]=a\n'
},
'gh api --method PUT': {
'.github/workflows/w.yml': 'jobs:\n j:\n steps:\n - run: gh api --method PUT repos/o/r/issues/1/labels\n'
'.github/workflows/ci.yml': 'jobs:\n j:\n steps:\n - run: gh api --method PUT repos/o/r/issues/1/labels\n'
},
'github-script issues.setLabels': {
'.github/workflows/w.yml':
'.github/workflows/ci.yml':
'jobs:\n j:\n steps:\n - uses: actions/github-script@v9\n with:\n script: |\n' +
' await github.rest.issues.setLabels({ owner, repo, issue_number: 1, labels });\n'
},
'octokit.request route string': {
'scripts/w.mjs': "await octokit.request('PUT /repos/{owner}/{repo}/issues/{issue_number}/labels', { labels });\n"
'scripts/check-nul-bytes.mjs': "await octokit.request('PUT /repos/{owner}/{repo}/issues/{issue_number}/labels', { labels });\n"
},
'fetch with a multi-line options object': {
'scripts/w.mjs': 'await fetch(`${api}/repos/${repo}/issues/${n}/labels`, {\n headers,\n method: "PUT",\n body\n});\n'
'scripts/check-nul-bytes.mjs': 'await fetch(`${api}/repos/${repo}/issues/${n}/labels`, {\n headers,\n method: "PUT",\n body\n});\n'
},
'a bare .put( onto the endpoint': {
'scripts/w.mjs': "await client.put(`/issues/${n}/labels`, { labels });\n"
'scripts/check-nul-bytes.mjs': "await client.put(`/issues/${n}/labels`, { labels });\n"
},
'uses: actions/labeler at the version #10703 read': {
'.github/workflows/w.yml': 'jobs:\n j:\n steps:\n - uses: actions/labeler@v7.0.0\n'
'.github/workflows/ci.yml': 'jobs:\n j:\n steps:\n - uses: actions/labeler@v7.0.0\n'
},
'uses: actions/labeler at ANY other version': {
'.github/workflows/w.yml': 'jobs:\n j:\n steps:\n - uses: actions/labeler@a1b2c3d4\n'
'.github/workflows/ci.yml': 'jobs:\n j:\n steps:\n - uses: actions/labeler@a1b2c3d4\n'
},
'uses: codelytv/pr-size-labeler': {
'.github/workflows/w.yml': 'jobs:\n j:\n steps:\n - uses: codelytv/pr-size-labeler@v1.10.4\n'
'.github/workflows/ci.yml': 'jobs:\n j:\n steps:\n - uses: codelytv/pr-size-labeler@v1.10.4\n'
},
'the method slot WINDOW_LINES-1 lines from the path': {
'scripts/w.mjs': `const url = '/issues/1/labels';\n${'// filler\n'.repeat(WINDOW_LINES - 2)}await go({ method: 'PUT', url });\n`
'scripts/check-nul-bytes.mjs': `const url = '/issues/1/labels';\n${'// filler\n'.repeat(WINDOW_LINES - 2)}await go({ method: 'PUT', url });\n`
}
};

/** Forms that MUST stay clean. Every one is correct as written. */
export const GREEN_CASES = {
'the additive POST': {
'scripts/w.mjs': "await gh('POST', `/issues/${n}/labels`, { labels: ['size/l'] });\n"
'scripts/check-nul-bytes.mjs': "await gh('POST', `/issues/${n}/labels`, { labels: ['size/l'] });\n"
},
'the targeted DELETE': {
'scripts/w.mjs': "await gh('DELETE', `/issues/${n}/labels/${encodeURIComponent(name)}`);\n"
'scripts/check-nul-bytes.mjs': "await gh('DELETE', `/issues/${n}/labels/${encodeURIComponent(name)}`);\n"
},
'the ban documented in a YAML comment': {
'.github/workflows/w.yml': '# never `curl -X PUT .../issues/1/labels`, and never issues.setLabels\njobs:\n j:\n steps:\n - run: true\n'
'.github/workflows/ci.yml': '# never `curl -X PUT .../issues/1/labels`, and never issues.setLabels\njobs:\n j:\n steps:\n - run: true\n'
},
'the ban documented in a JS block comment': {
'scripts/w.mjs': '/**\n * `PUT /issues/{n}/labels` and `issues.setLabels` are both banned.\n * Not `curl -X PUT .../issues/1/labels` either.\n */\nexport const ok = 1;\n'
'scripts/check-nul-bytes.mjs': '/**\n * `PUT /issues/{n}/labels` and `issues.setLabels` are both banned.\n * Not `curl -X PUT .../issues/1/labels` either.\n */\nexport const ok = 1;\n'
},
'the ban documented in a JS line comment': {
'scripts/w.mjs': "// await octokit.request('PUT /repos/o/r/issues/1/labels') -- BANNED\nexport const ok = 1;\n"
'scripts/check-nul-bytes.mjs': "// await octokit.request('PUT /repos/o/r/issues/1/labels') -- BANNED\nexport const ok = 1;\n"
},
'a comparison REFUSING the verb': {
'scripts/w.mjs': "if (step.method === 'PUT') throw new Error(`refused for /issues/${n}/labels`);\n"
'scripts/check-nul-bytes.mjs': "if (step.method === 'PUT') throw new Error(`refused for /issues/${n}/labels`);\n"
},
'the verb named in a test name next to a labels path': {
'scripts/w.mjs': "const plan = { path: '/issues/10698/labels' };\ncheck('the retired whole-set PUT destroys the label', plan);\n"
'scripts/check-nul-bytes.mjs': "const plan = { path: '/issues/10698/labels' };\ncheck('the retired whole-set PUT destroys the label', plan);\n"
},
'an unrelated action pin': {
'.github/workflows/w.yml': 'jobs:\n j:\n steps:\n - uses: actions/checkout@v7\n - uses: actions/stale@v11.0.0\n'
'.github/workflows/ci.yml': 'jobs:\n j:\n steps:\n - uses: actions/checkout@v7\n - uses: actions/stale@v11.0.0\n'
},
'a PUT to a different endpoint entirely': {
'scripts/w.mjs': "await gh('PUT', `/repos/${repo}/actions/variables/${name}`);\n"
'scripts/check-nul-bytes.mjs': "await gh('PUT', `/repos/${repo}/actions/variables/${name}`);\n"
},
'a label READ, no write': {
'.github/workflows/w.yml': 'jobs:\n j:\n steps:\n - run: gh api "repos/$R/issues/$N/labels" --jq ".[].name"\n'
'.github/workflows/ci.yml': 'jobs:\n j:\n steps:\n - run: gh api "repos/$R/issues/$N/labels" --jq ".[].name"\n'
},
'steps.labels output references': {
'.github/workflows/w.yml': "jobs:\n j:\n steps:\n - if: steps.labels.outputs.skip != 'true'\n run: true\n"
'.github/workflows/ci.yml': "jobs:\n j:\n steps:\n - if: steps.labels.outputs.skip != 'true'\n run: true\n"
},
'the method slot WINDOW_LINES lines from the path (a STATED miss)': {
'scripts/w.mjs': `const url = '/issues/1/labels';\n${'// filler\n'.repeat(WINDOW_LINES - 1)}await go({ method: 'PUT', url });\n`
'scripts/check-nul-bytes.mjs': `const url = '/issues/1/labels';\n${'// filler\n'.repeat(WINDOW_LINES - 1)}await go({ method: 'PUT', url });\n`
}
};

Expand DownExpand Up@@ -899,9 +917,9 @@ export function selfTest() {

// Refusal 3 -- an allowlist entry with no stated reason. Assertion 3.
withTree(RED_CASES['gh api -X PUT'], (dir) => {
const noReason = [{ path: '.github/workflows/w.yml', rule: 'endpoint', reason: 'too short' }];
const noReason = [{ path: '.github/workflows/ci.yml', rule: 'endpoint', reason: 'too short' }];
expect('REFUSE allowlist entry without a reason', run(dir, { allowlist: noReason }, silent), EXIT_REFUSED);
const noRule = [{ path: '.github/workflows/w.yml', rule: 'whatever', reason: 'a'.repeat(MIN_REASON_LENGTH) }];
const noRule = [{ path: '.github/workflows/ci.yml', rule: 'whatever', reason: 'a'.repeat(MIN_REASON_LENGTH) }];
expect('REFUSE allowlist entry with no valid rule', run(dir, { allowlist: noRule }, silent), EXIT_REFUSED);
});

Expand All@@ -916,7 +934,7 @@ export function selfTest() {
withTree(RED_CASES['gh api -X PUT'], (dir) => {
const reasoned = [
{
path: '.github/workflows/w.yml',
path: '.github/workflows/ci.yml',
rule: /** @type {'endpoint'} */ ('endpoint'),
reason: 'fixture: a deliberate exception recorded with a real sentence explaining itself.'
}
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 45 additions & 27 deletions scripts/check-whole-set-label-write.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -762,85 +762,103 @@ function baseFiles() {
};
}

// RED_CASES/GREEN_CASES below reuse three REAL tracked paths as their sandbox
// keys (`scripts/check-nul-bytes.mjs`, `scripts/build-console.sh`,
// `.github/workflows/ci.yml`) rather than invented names (`scripts/w.mjs`,
// `scripts/writer.sh`, `.github/workflows/w.yml`, pre-#14695). The fixture's
// own content is written into an isolated temp directory (`writeTree`) either
// way, so which real path stands in for "a script" or "a workflow" makes no
// difference to what any RED/GREEN case here asserts — but the KEY is a
// string literal in THIS module's own source, and `RED_CASES`/`GREEN_CASES`
// are top-level exports, not bodies inside a `selfTest()`-shaped function, so
// `maskSelfTests` never hides them from `extractWatchHints`: every key here
// already entered this gate's own declared-population hint set. An invented
// name that exists in no tracked tree is therefore a DEAD lead — exactly the
// shape `check:declared-population-live` exists to refuse — and it read as
// live only because that gate's liveness bar is per-FAMILY (at least one hint
// live) rather than per-hint; a real path clears the same bar honestly at
// full precision instead of by accident (#14695). The two paths chosen for
// the `.mjs`/`.sh` cases are deliberately UNRELATED scripts, picked only to be
// tracked and short — not files this gate has any real interest in reading.
/** Every spelling that MUST be refused. */
export const RED_CASES = {
'curl -X PUT, one line': {
'scripts/writer.sh': 'curl -X PUT -H "auth" "https://api.github.com/repos/o/r/issues/1/labels" -d "{}"\n'
'scripts/build-console.sh': 'curl -X PUT -H "auth" "https://api.github.com/repos/o/r/issues/1/labels" -d "{}"\n'
},
'curl -X PUT, backslash-continued onto the path line': {
'scripts/writer.sh': 'curl -X PUT \\\n -H "auth" \\\n "https://api.github.com/repos/o/r/issues/1/labels"\n'
'scripts/build-console.sh': 'curl -X PUT \\\n -H "auth" \\\n "https://api.github.com/repos/o/r/issues/1/labels"\n'
},
'gh api -X PUT': {
'.github/workflows/w.yml': 'jobs:\n j:\n steps:\n - run: gh api -X PUT "repos/$R/issues/$N/labels" -f labels[]=a\n'
'.github/workflows/ci.yml': 'jobs:\n j:\n steps:\n - run: gh api -X PUT "repos/$R/issues/$N/labels" -f labels[]=a\n'
},
'gh api --method PUT': {
'.github/workflows/w.yml': 'jobs:\n j:\n steps:\n - run: gh api --method PUT repos/o/r/issues/1/labels\n'
'.github/workflows/ci.yml': 'jobs:\n j:\n steps:\n - run: gh api --method PUT repos/o/r/issues/1/labels\n'
},
'github-script issues.setLabels': {
'.github/workflows/w.yml':
'.github/workflows/ci.yml':
'jobs:\n j:\n steps:\n - uses: actions/github-script@v9\n with:\n script: |\n' +
' await github.rest.issues.setLabels({ owner, repo, issue_number: 1, labels });\n'
},
'octokit.request route string': {
'scripts/w.mjs': "await octokit.request('PUT /repos/{owner}/{repo}/issues/{issue_number}/labels', { labels });\n"
'scripts/check-nul-bytes.mjs': "await octokit.request('PUT /repos/{owner}/{repo}/issues/{issue_number}/labels', { labels });\n"
},
'fetch with a multi-line options object': {
'scripts/w.mjs': 'await fetch(`${api}/repos/${repo}/issues/${n}/labels`, {\n headers,\n method: "PUT",\n body\n});\n'
'scripts/check-nul-bytes.mjs': 'await fetch(`${api}/repos/${repo}/issues/${n}/labels`, {\n headers,\n method: "PUT",\n body\n});\n'
},
'a bare .put( onto the endpoint': {
'scripts/w.mjs': "await client.put(`/issues/${n}/labels`, { labels });\n"
'scripts/check-nul-bytes.mjs': "await client.put(`/issues/${n}/labels`, { labels });\n"
},
'uses: actions/labeler at the version #10703 read': {
'.github/workflows/w.yml': 'jobs:\n j:\n steps:\n - uses: actions/labeler@v7.0.0\n'
'.github/workflows/ci.yml': 'jobs:\n j:\n steps:\n - uses: actions/labeler@v7.0.0\n'
},
'uses: actions/labeler at ANY other version': {
'.github/workflows/w.yml': 'jobs:\n j:\n steps:\n - uses: actions/labeler@a1b2c3d4\n'
'.github/workflows/ci.yml': 'jobs:\n j:\n steps:\n - uses: actions/labeler@a1b2c3d4\n'
},
'uses: codelytv/pr-size-labeler': {
'.github/workflows/w.yml': 'jobs:\n j:\n steps:\n - uses: codelytv/pr-size-labeler@v1.10.4\n'
'.github/workflows/ci.yml': 'jobs:\n j:\n steps:\n - uses: codelytv/pr-size-labeler@v1.10.4\n'
},
'the method slot WINDOW_LINES-1 lines from the path': {
'scripts/w.mjs': `const url = '/issues/1/labels';\n${'// filler\n'.repeat(WINDOW_LINES - 2)}await go({ method: 'PUT', url });\n`
'scripts/check-nul-bytes.mjs': `const url = '/issues/1/labels';\n${'// filler\n'.repeat(WINDOW_LINES - 2)}await go({ method: 'PUT', url });\n`
}
};

/** Forms that MUST stay clean. Every one is correct as written. */
export const GREEN_CASES = {
'the additive POST': {
'scripts/w.mjs': "await gh('POST', `/issues/${n}/labels`, { labels: ['size/l'] });\n"
'scripts/check-nul-bytes.mjs': "await gh('POST', `/issues/${n}/labels`, { labels: ['size/l'] });\n"
},
'the targeted DELETE': {
'scripts/w.mjs': "await gh('DELETE', `/issues/${n}/labels/${encodeURIComponent(name)}`);\n"
'scripts/check-nul-bytes.mjs': "await gh('DELETE', `/issues/${n}/labels/${encodeURIComponent(name)}`);\n"
},
'the ban documented in a YAML comment': {
'.github/workflows/w.yml': '# never `curl -X PUT .../issues/1/labels`, and never issues.setLabels\njobs:\n j:\n steps:\n - run: true\n'
'.github/workflows/ci.yml': '# never `curl -X PUT .../issues/1/labels`, and never issues.setLabels\njobs:\n j:\n steps:\n - run: true\n'
},
'the ban documented in a JS block comment': {
'scripts/w.mjs': '/**\n * `PUT /issues/{n}/labels` and `issues.setLabels` are both banned.\n * Not `curl -X PUT .../issues/1/labels` either.\n */\nexport const ok = 1;\n'
'scripts/check-nul-bytes.mjs': '/**\n * `PUT /issues/{n}/labels` and `issues.setLabels` are both banned.\n * Not `curl -X PUT .../issues/1/labels` either.\n */\nexport const ok = 1;\n'
},
'the ban documented in a JS line comment': {
'scripts/w.mjs': "// await octokit.request('PUT /repos/o/r/issues/1/labels') -- BANNED\nexport const ok = 1;\n"
'scripts/check-nul-bytes.mjs': "// await octokit.request('PUT /repos/o/r/issues/1/labels') -- BANNED\nexport const ok = 1;\n"
},
'a comparison REFUSING the verb': {
'scripts/w.mjs': "if (step.method === 'PUT') throw new Error(`refused for /issues/${n}/labels`);\n"
'scripts/check-nul-bytes.mjs': "if (step.method === 'PUT') throw new Error(`refused for /issues/${n}/labels`);\n"
},
'the verb named in a test name next to a labels path': {
'scripts/w.mjs': "const plan = { path: '/issues/10698/labels' };\ncheck('the retired whole-set PUT destroys the label', plan);\n"
'scripts/check-nul-bytes.mjs': "const plan = { path: '/issues/10698/labels' };\ncheck('the retired whole-set PUT destroys the label', plan);\n"
},
'an unrelated action pin': {
'.github/workflows/w.yml': 'jobs:\n j:\n steps:\n - uses: actions/checkout@v7\n - uses: actions/stale@v11.0.0\n'
'.github/workflows/ci.yml': 'jobs:\n j:\n steps:\n - uses: actions/checkout@v7\n - uses: actions/stale@v11.0.0\n'
},
'a PUT to a different endpoint entirely': {
'scripts/w.mjs': "await gh('PUT', `/repos/${repo}/actions/variables/${name}`);\n"
'scripts/check-nul-bytes.mjs': "await gh('PUT', `/repos/${repo}/actions/variables/${name}`);\n"
},
'a label READ, no write': {
'.github/workflows/w.yml': 'jobs:\n j:\n steps:\n - run: gh api "repos/$R/issues/$N/labels" --jq ".[].name"\n'
'.github/workflows/ci.yml': 'jobs:\n j:\n steps:\n - run: gh api "repos/$R/issues/$N/labels" --jq ".[].name"\n'
},
'steps.labels output references': {
'.github/workflows/w.yml': "jobs:\n j:\n steps:\n - if: steps.labels.outputs.skip != 'true'\n run: true\n"
'.github/workflows/ci.yml': "jobs:\n j:\n steps:\n - if: steps.labels.outputs.skip != 'true'\n run: true\n"
},
'the method slot WINDOW_LINES lines from the path (a STATED miss)': {
'scripts/w.mjs': `const url = '/issues/1/labels';\n${'// filler\n'.repeat(WINDOW_LINES - 1)}await go({ method: 'PUT', url });\n`
'scripts/check-nul-bytes.mjs': `const url = '/issues/1/labels';\n${'// filler\n'.repeat(WINDOW_LINES - 1)}await go({ method: 'PUT', url });\n`
}
};

Expand DownExpand Up@@ -899,9 +917,9 @@ export function selfTest() {

// Refusal 3 -- an allowlist entry with no stated reason. Assertion 3.
withTree(RED_CASES['gh api -X PUT'], (dir) => {
const noReason = [{ path: '.github/workflows/w.yml', rule: 'endpoint', reason: 'too short' }];
const noReason = [{ path: '.github/workflows/ci.yml', rule: 'endpoint', reason: 'too short' }];
expect('REFUSE allowlist entry without a reason', run(dir, { allowlist: noReason }, silent), EXIT_REFUSED);
const noRule = [{ path: '.github/workflows/w.yml', rule: 'whatever', reason: 'a'.repeat(MIN_REASON_LENGTH) }];
const noRule = [{ path: '.github/workflows/ci.yml', rule: 'whatever', reason: 'a'.repeat(MIN_REASON_LENGTH) }];
expect('REFUSE allowlist entry with no valid rule', run(dir, { allowlist: noRule }, silent), EXIT_REFUSED);
});

Expand All@@ -916,7 +934,7 @@ export function selfTest() {
withTree(RED_CASES['gh api -X PUT'], (dir) => {
const reasoned = [
{
path: '.github/workflows/w.yml',
path: '.github/workflows/ci.yml',
rule: /** @type {'endpoint'} */ ('endpoint'),
reason: 'fixture: a deliberate exception recorded with a real sentence explaining itself.'
}
Expand Down
Loading
Loading