File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -112,11 +112,12 @@ Matches OCAPI job execution by job ID. This catches both direct job commands and
112112
113113#### CLI Command ID
114114
115- Matches CLI commands by their oclif command ID:
115+ Matches CLI commands by their oclif command ID. Command rules are enforced automatically for ** every ** command before ` run() ` executes -- no per-command opt-in is needed :
116116
117117``` json
118118{ "command" : " sandbox:delete" , "action" : " confirm" }
119119{ "command" : " sandbox:*" , "action" : " block" }
120+ { "command" : " code:deploy" , "action" : " block" }
120121```
121122
122123### Evaluation Order
Original file line number Diff line number Diff line change @@ -161,6 +161,14 @@ export default class CodeDeploy extends CartridgeCommand<typeof CodeDeploy> {
161161 this . log ( ` ${ c . name } (${ c . src } )` ) ;
162162 }
163163
164+ // After safety evaluation passes, temporarily allow WebDAV DELETE operations
165+ // that are part of the deploy flow (cleanup of temp zip, --delete cartridge removal).
166+ const cleanupSafetyRule = this . safetyGuard . temporarilyAddRule ( {
167+ method : 'DELETE' ,
168+ path : '**/Cartridges/**' ,
169+ action : 'allow' ,
170+ } ) ;
171+
164172 try {
165173 // Optionally delete existing cartridges first
166174 if ( this . flags . delete ) {
@@ -240,6 +248,8 @@ export default class CodeDeploy extends CartridgeCommand<typeof CodeDeploy> {
240248 this . error ( t ( 'commands.code.deploy.failed' , 'Deployment failed: {{message}}' , { message : error . message } ) ) ;
241249 }
242250 throw error ;
251+ } finally {
252+ cleanupSafetyRule ( ) ;
243253 }
244254 }
245255}
Original file line number Diff line number Diff line change @@ -56,6 +56,14 @@ export default class CodeWatch extends CartridgeCommand<typeof CodeWatch> {
5656 this . log ( t ( 'commands.code.watch.codeVersion' , 'Code Version: {{version}}' , { version} ) ) ;
5757 }
5858
59+ // Temporarily allow WebDAV DELETE on Cartridges paths for the watch lifecycle.
60+ // The watcher DELETEs temp zip files after upload and syncs local file deletions.
61+ const cleanupSafetyRule = this . safetyGuard . temporarilyAddRule ( {
62+ method : 'DELETE' ,
63+ path : '**/Cartridges/**' ,
64+ action : 'allow' ,
65+ } ) ;
66+
5967 try {
6068 const result = await this . operations . watchCartridges ( this . instance , this . cartridgePath , {
6169 ...this . cartridgeOptions ,
@@ -92,6 +100,8 @@ export default class CodeWatch extends CartridgeCommand<typeof CodeWatch> {
92100 this . error ( t ( 'commands.code.watch.failed' , 'Watch failed: {{message}}' , { message : error . message } ) ) ;
93101 }
94102 throw error ;
103+ } finally {
104+ cleanupSafetyRule ( ) ;
95105 }
96106 }
97107}
Original file line number Diff line number Diff line change @@ -129,15 +129,14 @@ export default class JobExport extends JobCommand<typeof JobExport> {
129129
130130 const hostname = this . resolvedConfig . values . hostname ! ;
131131
132- // Safety evaluation — check rules for export job before executing
132+ // Safety evaluation — check rules for export job before executing.
133+ // Command-level rules are already evaluated generically in BaseCommand.init().
133134 const jobEvaluation = this . safetyGuard . evaluate ( { type : 'job' , jobId : 'sfcc-site-archive-export' } ) ;
134- const cmdEvaluation = this . safetyGuard . evaluate ( { type : 'command' , commandId : this . id } ) ;
135- const evaluation = jobEvaluation . action === 'allow' ? cmdEvaluation : jobEvaluation ;
136- if ( evaluation . action === 'block' ) {
137- this . error ( evaluation . reason , { exit : 1 } ) ;
135+ if ( jobEvaluation . action === 'block' ) {
136+ this . error ( jobEvaluation . reason , { exit : 1 } ) ;
138137 }
139- if ( evaluation . action === 'confirm' ) {
140- await this . confirmOrBlock ( evaluation ) ;
138+ if ( jobEvaluation . action === 'confirm' ) {
139+ await this . confirmOrBlock ( jobEvaluation ) ;
141140 }
142141
143142 // Build data units configuration
Original file line number Diff line number Diff line change @@ -72,15 +72,14 @@ export default class JobImport extends JobCommand<typeof JobImport> {
7272
7373 const hostname = this . resolvedConfig . values . hostname ! ;
7474
75- // Safety evaluation — check rules for import job before executing
75+ // Safety evaluation — check rules for import job before executing.
76+ // Command-level rules are already evaluated generically in BaseCommand.init().
7677 const jobEvaluation = this . safetyGuard . evaluate ( { type : 'job' , jobId : 'sfcc-site-archive-import' } ) ;
77- const cmdEvaluation = this . safetyGuard . evaluate ( { type : 'command' , commandId : this . id } ) ;
78- const evaluation = jobEvaluation . action === 'allow' ? cmdEvaluation : jobEvaluation ;
79- if ( evaluation . action === 'block' ) {
80- this . error ( evaluation . reason , { exit : 1 } ) ;
78+ if ( jobEvaluation . action === 'block' ) {
79+ this . error ( jobEvaluation . reason , { exit : 1 } ) ;
8180 }
82- if ( evaluation . action === 'confirm' ) {
83- await this . confirmOrBlock ( evaluation ) ;
81+ if ( jobEvaluation . action === 'confirm' ) {
82+ await this . confirmOrBlock ( jobEvaluation ) ;
8483 }
8584
8685 // Create lifecycle context
Original file line number Diff line number Diff line change @@ -95,16 +95,14 @@ export default class JobRun extends JobCommand<typeof JobRun> {
9595 'show-log' : showLog ,
9696 } = this . flags ;
9797
98- // Safety evaluation — check rules for this job before executing
98+ // Safety evaluation — check rules for this job before executing.
99+ // Command-level rules are already evaluated generically in BaseCommand.init().
99100 const jobEvaluation = this . safetyGuard . evaluate ( { type : 'job' , jobId} ) ;
100- const cmdEvaluation = this . safetyGuard . evaluate ( { type : 'command' , commandId : this . id } ) ;
101- // Use the more restrictive evaluation
102- const evaluation = jobEvaluation . action === 'allow' ? cmdEvaluation : jobEvaluation ;
103- if ( evaluation . action === 'block' ) {
104- this . error ( evaluation . reason , { exit : 1 } ) ;
101+ if ( jobEvaluation . action === 'block' ) {
102+ this . error ( jobEvaluation . reason , { exit : 1 } ) ;
105103 }
106- if ( evaluation . action === 'confirm' ) {
107- await this . confirmOrBlock ( evaluation ) ;
104+ if ( jobEvaluation . action === 'confirm' ) {
105+ await this . confirmOrBlock ( jobEvaluation ) ;
108106 }
109107
110108 // Parse parameters or body
Original file line number Diff line number Diff line change @@ -203,6 +203,10 @@ export abstract class BaseCommand<T extends typeof Command> extends Command {
203203 // Update safety guard with config-provided safety settings (merges env + config)
204204 this . initializeSafetyGuard ( ) ;
205205
206+ // Evaluate command-level safety rules for every command.
207+ // This enforces rules like { command: "code:deploy", action: "block" } generically.
208+ await this . evaluateCommandSafety ( ) ;
209+
206210 this . addTelemetryContext ( ) ;
207211 }
208212
@@ -753,6 +757,28 @@ export abstract class BaseCommand<T extends typeof Command> extends Command {
753757 }
754758 }
755759
760+ /**
761+ * Evaluate command-level safety rules for the current command.
762+ *
763+ * This runs at the end of init() so every command is evaluated against
764+ * command rules (e.g., `{ command: "code:deploy", action: "block" }`).
765+ * If no command rule matches, this is a no-op — level-based blocking
766+ * is handled by the HTTP middleware and assertDestructiveOperationAllowed().
767+ */
768+ private async evaluateCommandSafety ( ) : Promise < void > {
769+ const evaluation = this . safetyGuard . evaluate ( {
770+ type : 'command' ,
771+ commandId : this . id ,
772+ } ) ;
773+
774+ if ( evaluation . action === 'block' && evaluation . rule ) {
775+ this . error ( evaluation . reason , { exit : 1 } ) ;
776+ }
777+ if ( evaluation . action === 'confirm' && evaluation . rule ) {
778+ await this . confirmOrBlock ( evaluation ) ;
779+ }
780+ }
781+
756782 /**
757783 * Require interactive confirmation for a safety-guarded operation.
758784 *
You can’t perform that action at this time.
0 commit comments