Skip to content
Merged
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
2 changes: 2 additions & 0 deletions actions/setup/js/github_api_helpers.cjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// @ts-check
/// <reference types="@actions/github-script" />

// @safe-outputs-exempt SEC-004: low-level GraphQL helper; body is sanitized by callers (add_comment.cjs, add_reaction_and_edit_comment.cjs) before it reaches this layer.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

File-level exemption silently blanket-covers all future functions in this module. The checker skips the entire file when it finds @safe-outputs-exempt SEC-004, so any future function added here that accepts user-controlled body content will be permanently invisible to SEC-004 with no warning.

💡 Details and suggested mitigation

check-safe-outputs-conformance.sh lines 145–148 does a whole-file continue on match:

if grep -q "`@safe-outputs-exempt`[[:space:]]\+SEC-004" "$handler"; then
  continue  # skips the whole file
fi

github_api_helpers.cjs is already imported by 11 other files. Only createDiscussionComment (line ~188) actually carries user-controlled body content — all other exports (logGraphQLError, fetchAllRepoLabels, getFileContent, resolveTopLevelDiscussionCommentId, etc.) are body-free today. But as the module grows, any future function that does accept a body will never be flagged.

Suggested mitigations (pick one):

  • Update the annotation to explicitly state the whole-file scope and the invariant: // @safe-outputs-exempt SEC-004: WHOLE-FILE — only createDiscussionComment carries user-controlled body; all other exports are body-free. Update if new body-taking functions are added.
  • Or update the checker to support a narrower function-adjacent annotation so the exemption scope is bounded.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Annotation asserts caller sanitization but nothing enforces that contract; stale annotation risk is real. The annotation names two callers as the sanitization guarantee, but there are currently 10+ other importers of this module and createDiscussionComment is a public export — any new caller can bypass sanitization silently.

💡 Details

Currently createDiscussionComment is called only from add_comment.cjs (sanitizes at line 646) and add_reaction_and_edit_comment.cjs (sanitizes at line 245). Those two are correct.

But the function is exported and the module is already imported by 11 other files. Nothing in the code, types, or checker prevents a third caller from passing unsanitized content. As the exemption suppresses the whole file from SEC-004 (see sibling comment), the usual safety net for new callers is also gone.

A low-cost mitigation: add a JSDoc @param note or a branded type so the expectation is machine-visible:

/**
 * `@param` {string} body - Comment body. MUST be pre-sanitized by the caller
 *   using sanitizeContent() before passing here. Do not add sanitization
 *   inside this function — double-sanitization strips allowed mentions.
 */
async function createDiscussionComment(github, discussionId, body, replyToId) {

This doesn't prevent misuse but makes the contract explicit in IDEs and makes it visually obvious when reviewing future callers.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/diagnosing-bugs] No regression test guards the exemption: if the checker's exemption regex ever changes (e.g. adding a colon variant), this annotation silently stops matching and the false positive returns — or a real violation could be silently skipped.

💡 Suggested regression test

Consider adding a small shell test that verifies the checker exits clean when run against a stub file containing the exemption annotation and a body field:

# test: sec-004 exemption is honoured
stub=$(mktemp --suffix=.cjs)
printf '%s\n' '// `@safe-outputs-exempt` SEC-004: test' 'const x = { "body": foo };' > "$stub"
grep -q '`@safe-outputs-exempt`[[:space:]]\+SEC-004' "$stub" && echo PASS || echo FAIL
rm "$stub"

Without this, a future refactor of the exemption pattern in check-safe-outputs-conformance.sh could re-introduce the false positive invisibly.

@copilot please address this.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/diagnosing-bugs] The createDiscussionComment JSDoc does not document the sanitization contract — that callers must pre-sanitize body before passing it in. A future caller could omit sanitization and silently introduce a real SEC-004 violation without the checker catching it (since the file is now exempt).

💡 Suggested JSDoc addition

Add a @remarks or a note to the @param body line:

/**
 * ...
 * `@param` {string} body - Comment body. MUST be pre-sanitized by the caller
 *   (e.g. via sanitizeContent()) before being passed here. This file is
 *   exempt from SEC-004 checks on the assumption that all callers sanitize.
 * ...
 */

This makes the invariant machine-readable and visible to future contributors without requiring them to trace back through the conformance checker script.

@copilot please address this.


/**
* GitHub API helper functions
* Provides common GitHub API operations with consistent error handling
Expand Down
Loading