Skip to content

Policy Analyzer

Configuration Name: PolicyEvaluator

Description

The PolicyEvaluator analyzer evaluates user-defined policy rules for each step executed by Sift. A finding is reported if a sequence violates the user-specified Rego policy. The severity of a finding is configurable by the user.

Configuration Parameters

The PolicyEvaluator analyzer requires a policy. A policy stored in the Platform may be specified by its ID.

    - kind: PolicyEvaluator
      policyId: "00000000-0000-0000-0000-000000000000"

A policy stored on the local filesystem must be given a name that will be displayed on findings reported for any policy violation.

    - kind: PolicyEvaluator
      name: "MySecurityPolicy"
      policyFile: "policy.rego"

Rego Policy

A Rego policy used with the PolicyEvaluator must declare the package as "aptori" and define a "violations" rule. The "violations" rule must return an array of violation objects when a request sequence violates a policy rule. Each violation object must include a rule name, severity, and an array of reasons explaining why the rule was violated.

package aptori

violations contains {
    "ruleName": "Policy Rule A",
    "severity": "medium",
    "reasons": ["reason1", "reason2"],
} if {
    condition1
    condition2
}

In the above example, if both condition1 and condition2 are true (logical-AND), then a violation is reported for rule "Policy Rule A" with severity of "medium" and two reason strings.

The schema for a Violation is:

  • ruleName: string with display name for policy rule
  • severity: string that is one of "critical", "high", "medium", "low", "info"
  • reasons: an array of strings that describe reasons for a rule violation

To represent a logical-OR, a rule may be repeated with a different set of conditions, and optionally a different reason message. In the next example, reasonsRuleB will fail if condition1 AND condition2 are true, OR condition3 AND condition4 AND condition5 are true. Different reason messages are reported for the two cases.

package aptori

violations contains {
    "ruleName": "Policy Rule B",
    "severity": "high",
    "reasons": reasonsRuleB,
} if {
    len(reasonsRuleB) > 0
}

reasonsRuleB contains "some reason for failed rule" if {
    condition1
    condition2
}
reasonsRuleB contains "a different reason for failed rule" {
    condition3
    condition4
    condition5
}

If both sets of conditions are true, then both reason messages will be added to the violation. The reason messages are displayed in the finding that is reported to the Aptori Platform.

Any number of rules and expressions that add to the violations array may be defined in a policy.

Policy Evaluation

Rules in a policy are evaluated by Sift after every request to the target application. The sequence of request-response pairs that is being evaluated is passed in the input data. A pre-defined variable input is available to use when defining conditions. The value input.sequence contains the sequence of steps, each step represents a request-response pair made to the target application. A condition may refer to any value in a request or response in a sequence.

Input Data Schema

The schema of the input data is an object:

{
  "sequence": [               // array of Steps, in chronological order
    {
      "operationID": string,
      "invokedAt": string,    // timestamp in RFC3339 format
      "path": string,         // path portion of the request URL
      "method": string,       // HTTP method of request
      "status": string,       // One of "success" (for 2xx), "client-error" (for 4xx),
                              // or "server-error" (for 5xx)

      "input": {
        "body": {},           // JSON object in schema defined by API
        "header": {},         // map of request header fields
        "path": {},           // map of path parameters as defined by API
        "query": {},          // map of query parameters as defined by API
      },

      "output": {
        "header": {},         // map of response header fields
        "body": {},           // JSON object in schema defined by API
      },

      "request": {
        "body": string,       // base64-encoded raw request body
        "header": {},         // map of header to array of string.
        "method": string,     // HTTP method of request
        "url": string         // Complete request URL
      },
      "response": {
        "body": string,       // base64-encoded raw request body
        "header": {},         // map of header key to array of string.
        "statusCode": integer // HTTP response status code
      }
    }
  ]
}

Example Policy

The following policy verifies password strength using user-specified criteria. It contains multiple rules, such as length should be greater than or equal to 12 characters, password must contain upper-case, etc. This updatedPassword rule only returns a password when the method is "POST", "PUT", "PATCH" (e.g., an operation that modifies the password) and the request body contains a "password" field. Further, the rule has a condition that the operation must have succeeded, e.g., the application accepted the password values. If any of those conditions are false, then updatedPassword returns an undefined value that causes rules that reference it to be false.

package aptori

violations contains {
    "ruleName": "Password Strength",
    "severity": "high",
    "reasons": PasswordViolations,
} if {
    count(PasswordViolations) > 0
}

PasswordViolations contains "must have lower-case character" if {
    not regex.match(`[a-z]`, updatedPassword)
}

PasswordViolations contains "must have upper-case character" if {
    not regex.match(`[A-Z]`, updatedPassword)
}

PasswordViolations contains "must have a numeric digit" if {
    not regex.match(`[0-9]`, updatedPassword)
}

PasswordViolations contains "must have symbol character" if {
    not regex.match(`[!@#$%^&*;]`, updatedPassword)
}

PasswordViolations contains "must have at least 12 characters" if {
    not count(updatedPassword) >= 12
}

# updatedPassword contains the value of a password that was
# accepted by an operation that stores a password.
updatedPassword := x if {
    some step in input.sequence
    step.status == "success"
    step.method in {"POST", "PUT", "PATCH"}
    x := step.input.body.password
}

Some details to observe in the above example:

  • violations is the rule evaluated by Sift. If it contains a non-empty array of violation objects, then Sift reports a finding to the Aptori Platform.
  • A violation is only added to violations if there are one or more reason strings added to PasswordViolations.
  • Multiple rules for PasswordViolations are defined. Each rule checks a different condition of the password strength criteria and adds a reason string to PasswordViolations if true.
  • Conditions in each PasswordViolations rule check the value of a modified password, as determined by the value of updatedPassword.
  • If a value is not present in the input data, a condition that uses the value is undefined and the condition is not considered true. Hence, if there is an operation with method "PUT" but it does not contain a field "password" in the request body, then updatedPassword has the undefined value, and rules that use it will not be true.

Test your policy using Rego Playground

Test your policy using Rego Playground. This is a sample value for the input data that describes a request sequence being evaluated. This sample may be pasted into the input box and altered for testing your policy.

{
  "sequence": [
    {
      "operationID": "registerUser",
      "invokedAt": "2025-03-27T18:31:15.836538-07:00",
      "method": "POST",
      "path": "/users/v1/register",
      "status": "success",

      "input": {
        "body": {
          "email": "[email protected]",
          "password": "NotSecret",
          "username": "robert42"
        },
        "header": {
          "Content-Type": "application/json"
        }
      },

      "output": {
        "body": {
          "message": "Successfully registered. Login to receive an auth token.",
          "status": "success"
        },
        "header": {
          "Content-Length": "92",
          "Content-Type": "application/json",
          "Date": "Fri, 28 Mar 2025 01:31:15 GMT"
        }
      },

      "request": {
          "body": "eyJlbWFpbCI6ICJib2JAZXhhbXBsZS5jb20iLCAicGFzc3dvcmQiOiAiTm90U2VjcmV0IiwgInVzZXJuYW1lIjogInJvYmVydDQyIn0K",
          "header": {
              "Content-Type":    ["application/json"]
          },
          "method": "POST",
          "url":    "https://example.com/api/v1/register"
      },

      "response": {
          "body": "eyAibWVzc2FnZSI6ICJTdWNjZXNzZnVsbHkgcmVnaXN0ZXJlZC4gTG9naW4gdG8gcmVjZWl2ZSBhbiBhdXRoIHRva2VuLiIsICJzdGF0dXMiOiAic3VjY2VzcyIgfQo=",
          "header": {
              "Content-Length": ["95"],
              "Content-Type":   ["application/json"],
              "Date":           ["Fri, 28 Mar 2025 01:31:15 GMT"]
          },
          "statusCode": 201
      }
    }
  ]
}

Faults Reported

When a policy reports a violation, the policy name is included in the title of the finding. The severity field of a violation determines the severity of the finding. The ruleName and reasons fields of a violation are displayed in the step comment of the finding.

Fault Identifier Title Summary Solution Severity
SPF-100 Policy - {policyName} Operation sequence violates one or more rules in user-specified policy. Modify the application logic to satisfy all expected behaviors. {severity} as specified in violation