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
6 changes: 6 additions & 0 deletions .changeset/unhide-app-security-commands.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@shopify/app': minor
'@shopify/cli': minor
---

Add `shopify app security` commands to check an app's source code for Shopify-specific security issues. `check` runs deterministic rules and generates checks for your coding agent to investigate, `record` saves the agent's findings, `review` shows the combined results, `instructions` prints the workflow for a coding agent, and `clean` removes local results.
520 changes: 520 additions & 0 deletions docs-shopify.dev/generated/generated_docs_data_v2.json

Large diffs are not rendered by default.

3 changes: 1 addition & 2 deletions packages/app/src/cli/commands/app/security/check.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ import {describe, expect, test, vi} from 'vitest'
vi.mock('../../../services/security-check.js')

describe('app security check command', () => {
test('is hidden and does not require linked app context', () => {
expect(SecurityCheck.hidden).toBe(true)
test('does not require linked app context', () => {
expect(SecurityCheck.prototype).toBeInstanceOf(BaseCommand)
expect(SecurityCheck.prototype).not.toBeInstanceOf(AppLinkedCommand)
expect(SecurityCheck.flags.path).toBe(appFlags.path)
Expand Down
2 changes: 0 additions & 2 deletions packages/app/src/cli/commands/app/security/check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import BaseCommand from '@shopify/cli-kit/node/base-command'
import {globalFlags} from '@shopify/cli-kit/node/cli'

export default class SecurityCheck extends BaseCommand {
static hidden = true

static summary =
'Check an app for Shopify-specific security issues and write deterministic-findings.json and agent-checks.json.'

Expand Down
3 changes: 1 addition & 2 deletions packages/app/src/cli/commands/app/security/clean.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,7 @@ function cleanedResult(appDirectory: string): SecurityCleanResult {
}

describe('app security clean command', () => {
test('is hidden and does not require linked app context', () => {
expect(SecurityClean.hidden).toBe(true)
test('does not require linked app context', () => {
expect(SecurityClean.prototype).toBeInstanceOf(BaseCommand)
expect(SecurityClean.prototype).not.toBeInstanceOf(AppLinkedCommand)
expect(SecurityClean.flags).not.toHaveProperty('json')
Expand Down
2 changes: 0 additions & 2 deletions packages/app/src/cli/commands/app/security/clean.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ import BaseCommand from '@shopify/cli-kit/node/base-command'
import {globalFlags} from '@shopify/cli-kit/node/cli'

export default class SecurityClean extends BaseCommand {
static hidden = true

static summary = 'Remove local app security check results.'

static descriptionWithMarkdown = `Deletes the results directory, \`.shopify/app-security/<results key>/\`, without asking. The results key is \`--client-id\` when you pass it, and otherwise the name of the app configuration file without \`.toml\`. Other results directories are left alone. Prints each removed path.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,7 @@ function configSelection(appDirectory: string, configFileName: string): AppSecur
}

describe('app security instructions command', () => {
test('is hidden and does not require linked app context', () => {
expect(SecurityInstructions.hidden).toBe(true)
test('does not require linked app context', () => {
expect(SecurityInstructions.prototype).toBeInstanceOf(BaseCommand)
expect(SecurityInstructions.prototype).not.toBeInstanceOf(AppLinkedCommand)
expect(SecurityInstructions.args).not.toHaveProperty('directory')
Expand Down
2 changes: 0 additions & 2 deletions packages/app/src/cli/commands/app/security/instructions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import {globalFlags} from '@shopify/cli-kit/node/cli'
import {resolvePath} from '@shopify/cli-kit/node/path'

export default class SecurityInstructions extends BaseCommand {
static hidden = true

static summary = 'Provide app security check instructions to a coding agent.'

static descriptionWithMarkdown = `Prints the complete workflow that a coding agent should follow to review app security check results.
Expand Down
3 changes: 1 addition & 2 deletions packages/app/src/cli/commands/app/security/record.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,7 @@ function recordedResult(appRoot: string) {
}

describe('app security record command', () => {
test('is hidden and does not require linked app context', () => {
expect(SecurityRecord.hidden).toBe(true)
test('does not require linked app context', () => {
expect(SecurityRecord.prototype).toBeInstanceOf(BaseCommand)
expect(SecurityRecord.prototype).not.toBeInstanceOf(AppLinkedCommand)
expect(SecurityRecord.flags).not.toHaveProperty('json')
Expand Down
2 changes: 0 additions & 2 deletions packages/app/src/cli/commands/app/security/record.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import BaseCommand from '@shopify/cli-kit/node/base-command'
import {globalFlags} from '@shopify/cli-kit/node/cli'

export default class SecurityRecord extends BaseCommand {
static hidden = true

static summary = 'Record agent findings from an app security check.'

static descriptionWithMarkdown = `Reads a coding agent's complete findings document from stdin, validates it, and replaces \`agent-findings.json\` in the results directory, \`.shopify/app-security/<results key>/\`. The results key is \`--client-id\` when you pass it, and otherwise the name of the app configuration file without \`.toml\`. \`--client-id\` is checked against your Shopify account before anything is read, so it needs you to be logged in.
Expand Down
3 changes: 1 addition & 2 deletions packages/app/src/cli/commands/app/security/review.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ import {describe, expect, test, vi} from 'vitest'
vi.mock('../../../services/security-review.js')

describe('app security review command', () => {
test('is hidden and does not require linked app context', () => {
expect(SecurityReview.hidden).toBe(true)
test('does not require linked app context', () => {
expect(SecurityReview.prototype).toBeInstanceOf(BaseCommand)
expect(SecurityReview.prototype).not.toBeInstanceOf(AppLinkedCommand)
expect(SecurityReview.flags).not.toHaveProperty('json')
Expand Down
2 changes: 0 additions & 2 deletions packages/app/src/cli/commands/app/security/review.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import BaseCommand from '@shopify/cli-kit/node/base-command'
import {globalFlags} from '@shopify/cli-kit/node/cli'

export default class SecurityReview extends BaseCommand {
static hidden = true

static summary = 'Show the combined app security check results.'

static descriptionWithMarkdown = `Combines the deterministic results (\`deterministic-findings.json\`, written by \`shopify app security check\`) with the recorded agent results (\`agent-findings.json\`, written by \`shopify app security record\`) and shows one view of every check: its findings, status and source. Both files are in the results directory, \`.shopify/app-security/<results key>/\`. \`--client-id\` is checked against your Shopify account before any results are read, so it needs you to be logged in.
Expand Down
Loading
Loading