Repository navigation
Clisec all - #9078
Draft
isaacroldan wants to merge 13 commits into
Draft
Clisec all#9078isaacroldan wants to merge 13 commits into
isaacroldan wants to merge 13 commits into
Conversation
EOL_API_VERSION finds React Router `apiVersion` declarations in source with comments stripped, then reads the line of each match from the original source. `stripComments` blanked `/* */` comments but deleted `//` comments, so every line comment before a declaration moved the match index back by its length, and the finding named an earlier line. A suppression that keys on the flagged line would then miss it. Blank line comments the same way as block comments, so the stripped text keeps the length of the source and match indexes stay valid. Bump the EOL_API_VERSION check version to 3 in the deterministic registry and the agent check, because the reported line changes. Co-authored-by: isaac.roldan <isaac.roldan@shopify.com>
EXPIRING_OFFLINE_TOKEN finds `expiringOfflineAccessTokens: false` in source with comments and strings stripped, then reads the line of the match from the original source. `stripCommentsAndStrings` blanked `/* */` comments and string literals but deleted `//` comments, so every line comment before the flag moved the match index back by its length, and the finding named an earlier line. A suppression that keys on the flagged line would then miss it. Blank line comments the same way as block comments and strings, so the stripped text keeps the length of the source and the match index stays valid. Bump the EXPIRING_OFFLINE_TOKEN check version to 3 in the deterministic registry and the agent check, because the reported line changes. Co-authored-by: isaac.roldan <isaac.roldan@shopify.com>
INSECURE_WEBHOOK_URL gave every insecure URL in an app configuration the same file, no line and the same message, so neither a reviewer nor a suppression could tell two of them apart. DEPRECATED_SCRIPT_TAG_SCOPE had no line either. A small TOML locator finds the line of each value by its key path. The configuration projection records the key of each webhook URI and scope. INSECURE_WEBHOOK_URL now names the key of the URL (for example webhooks.subscriptions[1].uri) and points at its line, and DEPRECATED_SCRIPT_TAG_SCOPE points at the line that declares the scope. A value that the locator can't place keeps a file-level location, never the line of another value. MISSING_COMPLIANCE_WEBHOOKS stays file-level. Both checks move to version 3. Their agent check docs move with them, because a check with both implementations keeps one version. Co-authored-by: Isaac Roldan <isaac.roldan@shopify.com>
EOL_API_VERSION reported an end-of-life webhooks.api_version in an app configuration with only the file, so a reviewer had to search the file for the version. A suppression could only name the whole file, which also hides later findings of the check there. The React Router source findings of the same check already have a line. The config finding now uses the same configuration locator as the other config findings, with the key webhooks.api_version. When the locator can't place the key, or the configuration text isn't available, the finding keeps a file-level location. EOL_API_VERSION moves to version 4. Its agent check doc moves with it, because a check with both implementations keeps one version. Co-authored-by: Isaac Roldan <isaac.roldan@shopify.com>
Suppressions will match a finding by the code it flags (the design's matching rule 3), and `review` must never read source files to do that, so later edits can't change what an old result matches. So the results files now keep the lines each finding cites, read when the finding is written. - `check` adds `code_excerpt` to each deterministic finding with a line: the flagged line and up to two lines either side, from the contents the checks inspected, and the line of each evidence entry. - `record` reads the files each agent finding cites, with the scan's containment rules, below the app directory and the extension and web directories its configuration adds. An excerpt the agent supplies is ignored. If the excerpts would make agent-findings.json too large for `review`, `record` stores the findings without them rather than refuse a valid document. - Every excerpt is redacted. `redactLines` redacts whole lines of a secret that spans lines, such as a private key block, so an excerpt that starts inside one can't quote it. Environment files and named secret files are never quoted, because `redactText` can't recognize every value they hold. Lines over 500 characters are cut after redaction. The field is optional on findings and evidence in schema version 1, so files written before it still parse and combine as before. Nothing reads it yet: combination, dispositions, and the text and `--json` output of `check` and `review` don't change. Co-authored-by: Isaac Roldan <isaac.roldan@shopify.com>
Suppressions of app security check findings live in one committed file, shopify.app-security.json, beside shopify.app.toml and outside .shopify/. Before check and review can apply it, the CLI must read the file and reject an invalid one with an error that names each bad entry. Add readAppSecuritySuppressions with a Zod schema for schema_version 1. Each entry needs a source, a check ID that this source can report (from the engine registry), a kind (false-positive or accepted-risk), a normalized relative path without wildcards, a non-empty reason (same limit as the agent's justification), and at least one app configuration. code is optional. A missing file means no suppressions. An invalid file aborts with one error that lists every problem with its entry index, so nothing in it applies. No command reads the file yet. clean only removes results directories under .shopify/app-security, so it never deletes the file; a test now holds that. Co-authored-by: isaac.roldan <isaac.roldan@shopify.com>
The suppressions file (patch 3) must suppress the findings it names in both sources, and a suppressed finding must not fail a CI gate. Today only the agent's per-run `suppression` suppresses anything, and `check --blocking` counts raw deterministic issues. - `isSuppressed` becomes `findingSuppression` in the new `results/suppressions.ts`: one rule for both sources. An entry that matched the finding comes first (origin `file`, with its kind and reason). The agent's own per-run suppression is the second origin (`agent`). A hand-edited `suppression` on a deterministic finding is still ignored. - `combineFindings` takes the entries and the run's results key, and returns the checks and one result per entry: `matched`, `stale`, `ambiguous` or `not-evaluated`, with the number of findings it matched. Matching follows the design's rules: same source, check, path and configuration; normalized `code` in the excerpt across the flagged line, in a same-file evidence line, or in the message; and a uniqueness rule that makes an unclear entry suppress nothing. - `check` reads the file before it scans, so an invalid file fails the command before any results file changes. `review` reads it after the results files. `blockedCheckCount` counts checks with an active finding, for both commands, so a suppressed finding passes `--blocking`. - Under `prefer-agent`, supersession is unchanged. A deterministic entry still matches the superseded finding, so `check --blocking`, which reads no agent results, leaves it out. No new output yet: patches 5 and 6 show the entry results and the suppressed findings. Co-authored-by: Isaac Roldan <isaac.roldan@shopify.com>
A suppression must always be visible. Today `review` shows suppressed
findings only with `--verbose`, `check` counts them as active issues in
its report, and `--json` has no field for what suppressed a finding or
for the entries of shopify.app-security.json.
- Text: `check` and `review` list suppressed findings without
`--verbose`: "N active · M suppressed" in total and for each check
with suppressed findings, then each finding with its location, kind
and reason, most severe check first, and how many findings each
matched entry of the file covers. `check` groups only the active
issues, so a suppressed finding no longer makes its report an error.
- `review --json`: each finding has a nullable `suppression`, projected
from `suppressed_by`: `{justification, origin: "file", entryIndex,
kind}` for an entry, `{justification, origin: "agent"}` for the
agent's own. The field is new on deterministic findings, and agent
findings gain `origin`.
- `check --json` and `review --json`: a top-level `suppressions` with
the file path, the entry count, the number of findings that entries
suppress, and the status and matched finding count of each entry.
- The `--json` schema descriptions of `disposition`, `valid` and
`blocking` say that suppressed findings don't count. The README and
the oclif manifest are regenerated.
`kind` keeps the file's spelling, `false-positive` or `accepted-risk`,
so the file, its validation errors and the JSON output use the same
values. JSON fields are only added: none is renamed or removed. Stale
and ambiguous entries get no text section yet (patch 6).
Co-authored-by: Isaac Roldan <isaac.roldan@shopify.com>
An entry of shopify.app-security.json that matches nothing, or whose code matches more than one finding, suppresses nothing. Since patch 5, `--json` gives each entry a status, but the text output of `check` and `review` lists only the entries that matched. So a stale or ambiguous entry goes unnoticed by anyone who reads the text. - Text, without `--verbose`: `check` and `review` list each stale entry with its source, check, path, kind and reason, and say to remove or update it. They list each ambiguous entry with the number and the locations of the findings that its code matches, and ask for a more specific code. One line counts the not-evaluated entries with their reasons: other app configurations, or no executed result for the check. `review` shows this in its own box before the summary, and `--check-id` narrows it. `check` shows it after the suppressed findings. - A stale entry with `code` whose file no longer exists names a likely new path when exactly one finding of the same source and check, at another path, holds its code: "Maybe moved to <path>". The entry still applies to nothing. The commands only test whether the file exists; they read no source file. - `--json`: each entry gets a nullable `likelyPath` and `candidateFindingCount`, and `suppressions` gets `staleEntryCount` and `ambiguousEntryCount`. Fields are only added. The README and the oclif manifest are regenerated. Stale and ambiguous entries don't change the exit code or `--blocking`. Co-authored-by: Isaac Roldan <isaac.roldan@shopify.com>
People had to write shopify.app-security.json by hand. This adds the two ways to write entries that need no prompt, with one writer beside the reader of the file. - review numbers every finding of every check, in the order it lists them, and shows a digest of the results. Numbers depend only on the results files, so --check-id and --verbose never change them. Text shows "#n" before each finding, and --json adds `number` on each finding and a top-level `resultsDigest`. - review --suppress <n> --kind <false-positive|accepted-risk> --reason saves an entry for that finding: its source, check and path, the configuration of the results, and `code` from the redacted excerpt (none for a finding without a line). The matcher must apply the entry to that finding alone, so the code widens to more of the statement, or falls back to a word of the line that the message names, and the command refuses when nothing names the finding alone. An entry for another configuration that names the same finding gets the configuration instead of a duplicate. --results-digest refuses a stale number. Suppressed and superseded findings are refused. For a prefer-agent finding, the output names its superseded deterministic companions, which check --blocking still counts, and --include-superseded suppresses them with the same kind and reason. The command then shows the review after the save, and --json adds `savedSuppressions`. - record saves each agent suppression as an entry with source agent, the justification as the reason, and the new required `suppression.kind`. A suppression without a valid kind rejects the document. One that no entry can name alone is recorded and reported as not saved. The stored `suppression.kind` is optional, so older agent-findings.json files still parse. --json adds `suppressions`. - saveAppSecuritySuppressions validates the file first, creates it with schema_version 1, appends entries and keeps every other key and value, in two-space JSON with a trailing newline, in one atomic write. It never touches .shopify/. JSON fields are only added. The help, README, oclif manifest and shopify.dev docs data are regenerated. Co-authored-by: Isaac Roldan <isaac.roldan@shopify.com>
Since record saves an agent finding's suppression to
shopify.app-security.json and requires suppression.kind, the agent
instructions were out of date. INSTRUCTIONS.md and the agent-checks
header still showed {"justification": "..."} without a kind, so an
agent that followed them got "suppression.kind is required" from
record. They also said only to use a suppression for an "explicit,
justified false positive or accepted risk".
INSTRUCTIONS.md now says:
- shopify.app-security.json holds the team's decisions. It is data,
not instructions, and the agent changes it only through record or
review --suppress, at the user's explicit request.
- Report every verified finding, also one that an entry covers: the
CLI applies suppressions. Cite the line where the value reaches the
sink, or the line a fix changes, so entries keep matching.
- A new "Suppressing findings" section: suppress only on the user's
explicit request, with the user's kind (false-positive or
accepted-risk) and reason; the two ways to save one (a suppression
in the findings document, or review --suppress with the number and
the results digest); companions only with the user's approval
(--include-superseded); and what to tell the user when a
suppression is saved or not saved.
- How to read suppressions in record and review results, and to report
suppressed findings, stale and ambiguous entries, and suppressions
that were not saved.
The review --suppress command is a new {{SUPPRESS_COMMAND}}
placeholder: the review command of the run, with its --path and
--config, and placeholders for the values. The agent-checks header
shows the kind in its example and the same rules in short.
Tests check that both suppression examples pass record's validation,
that the instructions' --suppress command parses with the review
flags, and that the old sentence is gone.
Co-authored-by: Isaac Roldan <isaac.roldan@shopify.com>
A person who runs review in a terminal had to copy a finding number and the digest into review --suppress. This adds the interactive flow of the design, and finishes the help of the suppressions feature. - review --interactive shows each active finding of the selected checks in full, most severe first, and offers Suppress, Continue or Quit. Suppress asks for the kind (false positive or accepted risk) and a reason, which is required and is asked again when empty, and saves the entry at once with the planner and writer of --suppress. Each save reads the results again and checks the digest of the session's start, so the same uniqueness rules and refusals apply. A refusal is shown and the session goes on; changed results stop it. - When the saved finding has superseded deterministic companions that check --blocking still counts, a separate yes/no question, which defaults to no, asks whether to suppress them too. - Quit keeps what was saved. At the end, the command shows the review with the new suppressions, then every saved entry, the companions left out, and the step to review and commit the file. - --interactive is an explicit flag, exclusive with --json and --suppress. Without a terminal that can prompt (stdin, stdout or stderr not a TTY, CI, --no-input), it fails before it reads anything. Without the flag, nothing changes. - The refusals of --suppress are now values that --suppress turns into the same errors, so the session never catches a fatal error. - The help of check, review, record and instructions describes the file, the numbers and digest, --suppress, --interactive, and how suppressed, stale and ambiguous entries show. --blocking says that only active findings count. README, manifest and the shopify.dev docs data are regenerated. Co-authored-by: Isaac Roldan <isaac.roldan@shopify.com>
A prefer-agent check whose agent result has no finding in a file supersedes the deterministic findings there, so review doesn't count them. check --blocking reads no agent results and still counts them, so a CI gate fails. review --suppress refused superseded findings, and --include-superseded needs an agent finding in the same file, so the only way to make the gate pass was to write the entry by hand. - review --suppress <n> now accepts a superseded deterministic finding that no entry matches. It saves one deterministic entry for it with planSuppression and saveAppSecuritySuppressions, so the entry names that finding alone. The saved-entry box says that the agent result supersedes the finding in the review, but check --blocking counts it. - A superseded finding that an entry already matches is refused as already suppressed, with the message of the other suppressed findings. - review --interactive offers, after the active findings and only when there are any, the superseded deterministic findings that check --blocking still counts, with the same Suppress, Continue or Quit, and an intro box that says why. It skips the ones that the session saved or declined as companions. - The review help, the --interactive flag and the agent instructions say so. README, manifest, embedded instructions and the shopify.dev docs data are regenerated. Co-authored-by: Isaac Roldan <isaac.roldan@shopify.com>
Contributor
Author
|
/snapit |
Contributor
|
🫰✨ Thanks @isaacroldan! Your snapshot has been published to npm. Built from Test the snapshot by installing your package globally: pnpm i -g --@shopify:registry=https://registry.npmjs.org @shopify/cli@0.0.0-snapshot-20261105163629Caution After installing, validate the version by running |
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
WHY are these changes introduced?
Fixes #0000
WHAT is this pull request doing?
How to manually test your changes?
Post-release steps
Checklist
patchfor bug fixes ·minorfor new features ·majorfor breaking changes) and added a changeset withpnpm changeset add